1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
|
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Gio::Actions for use with <image>.
*
* Copyright (C) 2022 Tavmjong Bah
*
* The contents of this file may be used under the GNU General Public License Version 2 or later.
*
*/
#include "actions-element-image.h"
#include "actions-helper.h"
#include <iostream>
#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
#include <gtkmm.h> // OK, we lied. We pop-up an message dialog if external editor not found and if we have a GUI.
#include <glibmm/i18n.h>
#include "inkscape-application.h"
#include "inkscape-window.h"
#include "message-stack.h"
#include "preferences.h"
#include "selection.h" // Selection
#include "object/sp-image.h"
#include "object/sp-clippath.h"
#include "object/sp-rect.h"
#include "ui/tools/select-tool.h"
#include "util/format_size.h"
#include "xml/href-attribute-helper.h"
Glib::ustring image_get_editor_name(bool is_svg)
{
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
Glib::ustring editor;
if (is_svg) {
editor = prefs->getString("/options/svgeditor/value", "inkscape");
} else {
editor = prefs->getString("/options/bitmapeditor/value", "gimp");
}
return editor;
}
// Note that edits are external to Inkscape and thus we cannot undo them!
void image_edit(InkscapeApplication *app)
{
auto selection = app->get_active_selection();
if (selection->isEmpty()) {
// Nothing to do.
return;
}
auto document = selection->document();
for (auto item : selection->items()) {
auto image = cast<SPImage>(item);
if (image) {
Inkscape::XML::Node *node = item->getRepr();
const gchar *href = Inkscape::getHrefAttribute(*node).second;
if (!href) {
show_output("image_edit: no xlink:href");
continue;
}
if (strncmp (href, "data", 4) == 0) {
show_output("image_edit: cannot edit embedded image");
continue;
}
// Find filename.
std::string filename = href;
if (strncmp (href, "file", 4) == 0) {
filename = Glib::filename_from_uri(href);
}
if (Glib::path_is_absolute(filename)) {
// Do nothing
} else if (document->getDocumentBase()) {
filename = Glib::build_filename(document->getDocumentBase(), filename);
} else {
filename = Glib::build_filename(Glib::get_current_dir(), filename);
}
// Bitmap or SVG?
bool is_svg = false;
if (filename.substr(filename.find_last_of(".") + 1) == "SVG" ||
filename.substr(filename.find_last_of(".") + 1) == "svg") {
is_svg = true;
}
// Get editor.
auto editor = image_get_editor_name(is_svg);
#ifdef _WIN32
// Parsing is done according to Unix shell rules, need to enclose editor path by single
// quotes (everything before file extension).
int index = editor.find(".exe");
if (index < 0) index = editor.find(".bat");
if (index < 0) index = editor.find(".com");
if (index < 0) index = editor.length();
editor.insert(index, "'");
editor.insert(0, "'");
#endif
Glib::ustring command = editor + " '" + filename + "'";
GError* error = nullptr;
g_spawn_command_line_async(command.c_str(), &error);
if (error) {
Glib::ustring message = _("Failed to edit external image.\n<small>Note: Path to editor can be set in Preferences dialog.</small>");
Glib::ustring message2 = Glib::ustring::compose(_("System error message: %1"), error->message);
auto window = app->get_active_window();
if (window) {
Gtk::MessageDialog dialog(*window, message, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK);
dialog.property_destroy_with_parent() = true;
dialog.set_name("SetEditorDialog");
dialog.set_title(_("External Edit Image:"));
dialog.set_secondary_text(message2);
dialog.run();
} else {
show_output(Glib::ustring("image_edit: ") + message.raw());
}
g_error_free(error);
error = nullptr;
}
}
}
}
/**
* Attempt to crop an image's physical pixels by the rectangle give
* OR if not specified, by any applied clipping object.
*/
void image_crop(InkscapeApplication *app)
{
auto win = app->get_active_window();
auto doc = app->get_active_document();
auto msg = win->get_desktop()->messageStack();
auto tool = win->get_desktop()->getEventContext();
int done = 0;
int bytes = 0;
auto selection = app->get_active_selection();
if (selection->isEmpty()) {
msg->flash(Inkscape::ERROR_MESSAGE, _("Nothing selected."));
return;
}
// Find a target rectangle, if provided.
Geom::OptRect target;
SPRect *rect = nullptr;
for (auto item : selection->items()) {
rect = cast<SPRect>(item);
if (rect) {
target = rect->geometricBounds(rect->i2doc_affine());
break;
}
}
// For each selected item, we loop through and attempt to crop the
// raster image to the geometric bounds of the clipping object.
for (auto item : selection->items()) {
if (auto image = cast<SPImage>(item)) {
bytes -= std::strlen(image->href);
Geom::OptRect area;
if (target) {
// MODE A. Crop to selected rectangle.
area = target;
} else if (auto clip = image->getClipObject()) {
// MODE B. Crop to image's xisting clip region
area = clip->geometricBounds(image->i2doc_affine());
}
done += (int)(area && image->cropToArea(*area));
bytes += std::strlen(image->href);
}
}
if (rect) {
rect->deleteObject();
}
// Tell the user what happened, since so many things could have changed.
if (done) {
// The select tool has no idea the image description needs updating. Force it.
if (auto selector = dynamic_cast<Inkscape::UI::Tools::SelectTool*>(tool)) {
selector->updateDescriber(selection);
}
std::stringstream ss;
ss << ngettext("<b>%d</b> image cropped", "<b>%d</b> images cropped", done);
if (bytes < 0) {
ss << ", " << ngettext("%s byte removed", "%s bytes removed", abs(bytes));
} else if (bytes > 0) {
ss << ", <b>" << ngettext("%s byte added!", "%s bytes added!", bytes) << "</b>";
}
// Do flashing after select tool update.
msg->flashF(Inkscape::INFORMATION_MESSAGE, ss.str().c_str(), done, Inkscape::Util::format_size(abs(bytes)).c_str());
Inkscape::DocumentUndo::done(doc, "ActionImageCrop", "Crop Images");
} else {
msg->flash(Inkscape::WARNING_MESSAGE, _("No images cropped!"));
}
}
std::vector<std::vector<Glib::ustring>> raw_data_element_image =
{
// clang-format off
{"app.element-image-crop", N_("Crop image to clip"), "Image", N_("Remove parts of the image outside the applied clipping area.") },
{"app.element-image-edit", N_("Edit externally"), "Image", N_("Edit image externally (image must be selected and not embedded).") },
// clang-format on
};
void
add_actions_element_image(InkscapeApplication* app)
{
auto *gapp = app->gio_app();
// clang-format off
gapp->add_action( "element-image-crop", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&image_crop), app));
gapp->add_action( "element-image-edit", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&image_edit), app));
// clang-format on
app->get_action_extra_data().add_data(raw_data_element_image);
}
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
|