// SPDX-License-Identifier: GPL-2.0-or-later /* * Gio::Actions for use with . * * 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 #include // Not ! To eventually allow a headless version! #include // OK, we lied. We pop-up an message dialog if external editor not found and if we have a GUI. #include #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(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.\nNote: Path to editor can be set in Preferences dialog."); 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(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(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(tool)) { selector->updateDescriber(selection); } std::stringstream ss; ss << ngettext("%d image cropped", "%d images cropped", done); if (bytes < 0) { ss << ", " << ngettext("%s byte removed", "%s bytes removed", abs(bytes)); } else if (bytes > 0) { ss << ", " << ngettext("%s byte added!", "%s bytes added!", bytes) << ""; } // 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> 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(sigc::ptr_fun(&image_crop), app)); gapp->add_action( "element-image-edit", sigc::bind(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 :