diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index 12a8a9ab4bcb24f70be5094a85fe17fbe6f67548..b06e7b3443e7e8a70b92035d486c53adfd97c3e3 100644 --- a/po/POTFILES.src.in +++ b/po/POTFILES.src.in @@ -12,6 +12,8 @@ ../src/actions/actions-edit-window.cpp ../src/actions/actions-edit.cpp ../src/actions/actions-effect.cpp +../src/actions/actions-element-a.cpp +../src/actions/actions-element-image.cpp ../src/actions/actions-extra-data.cpp ../src/actions/actions-file-window.cpp ../src/actions/actions-file.cpp diff --git a/share/keys/carbon.xml b/share/keys/carbon.xml index 93667a8a332e7cb6c18e7ed2e29dc9358e79733f..24aab1d0a8ddf4c7f7ec9b1037eb74c6d9addde4 100644 --- a/share/keys/carbon.xml +++ b/share/keys/carbon.xml @@ -238,8 +238,8 @@ See "inkscape.xml" for information about file structure. - - + + diff --git a/share/keys/inkscape.xml b/share/keys/inkscape.xml index 58c2b3f8e12597467676cac660cc23c6c442ffda..6c0bf3c8dfa5daf9daeda74146114c49a068a30f 100644 --- a/share/keys/inkscape.xml +++ b/share/keys/inkscape.xml @@ -307,8 +307,8 @@ override) the bindings in the main default.xml. - - + + diff --git a/share/ui/menus.ui b/share/ui/menus.ui index a01e9f7902103dfdd2592e1547abe86386b187f5..27bb2faeb83fed32b6c310ffc0d2faa4e0d43b67 100644 --- a/share/ui/menus.ui +++ b/share/ui/menus.ui @@ -665,11 +665,11 @@
_Show/Hide Current Layer - win.layer-toggle-hide + win.layer-hide-toggle _Lock/Unlock Current Layer - win.layer-toggle-lock + win.layer-lock-toggle
@@ -917,11 +917,11 @@
Unhide All - win.unhide-all + app.unhide-all Unlock All - win.unlock-all + app.unlock-all
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 49fd8f3743997822645189c74354db2d11c85a38..7e0cbc0501d57551e9392bfba005e6b6a19d5b40 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -217,6 +217,10 @@ set(inkscape_SRC actions/actions-edit.cpp actions/actions-effect.h actions/actions-effect.cpp + actions/actions-element-a.h + actions/actions-element-a.cpp + actions/actions-element-image.h + actions/actions-element-image.cpp actions/actions-file-window.h actions/actions-file-window.cpp actions/actions-file.h diff --git a/src/actions/actions-element-a.cpp b/src/actions/actions-element-a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b5a3bfb8f2e2639595b9c1e7e5b9c2858f61e10c --- /dev/null +++ b/src/actions/actions-element-a.cpp @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Gio::Actions for use with (for anchor or hyper link). + * + * 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-a.h" + +#include + +#include // Not ! To eventually allow a headless version! +#include + +#include "inkscape-application.h" +#include "inkscape-window.h" +#include "preferences.h" + +#include "selection.h" // Selection +#include "object/sp-anchor.h" +#include "ui/dialog/dialog-container.h" + +// void anchor_add(InkscapeApplication *app) +// { +// auto selection = app->get_active_selection(); +// auto anchor = selection->group(1); +// selection->set(anchor); + +// if (app->get_active_window()) { +// app->get_active_window()->get_desktop()->getContainer()->new_dialog("ObjectAttributes"); +// } +// } + +// XML not modified. Requires GUI. +void anchor_open_link(InkscapeApplication* app) +{ + auto window = app->get_active_window(); + if (window) { + auto selection = app->get_active_selection(); + for (auto item : selection->items()) { + auto anchor = dynamic_cast(item); + if (anchor) { + const char* href = anchor->href; + if (href) { + try { + window->show_uri(href, GDK_CURRENT_TIME); + } catch (const Glib::Error &e) { + std::cerr << "anchor_open_link: cannot open " << href << " " << e.what() << std::endl; + } + } + } + } + } +} + +std::vector> raw_data_element_a = +{ + // clang-format off + {"app.element-a-open-link", N_("Open link"), "Anchor", N_("Add an anchor to an object.") }, + // clang-format on +}; + +void +add_actions_element_a(InkscapeApplication* app) +{ + auto *gapp = app->gio_app(); + + // clang-format off + gapp->add_action( "element-a-open-link", sigc::bind(sigc::ptr_fun(&anchor_open_link), app)); + // clang-format on + + app->get_action_extra_data().add_data(raw_data_element_a); +} + + +/* + 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 : diff --git a/src/actions/actions-element-a.h b/src/actions/actions-element-a.h new file mode 100644 index 0000000000000000000000000000000000000000..9285a1115547b0179b0ffa9001a7c1b243e84e5e --- /dev/null +++ b/src/actions/actions-element-a.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Gio::Actions for use with (for anchor or hyper link). + * + * Copyright (C) 2022 Tavmjong Bah + * + * The contents of this file may be used under the GNU General Public License Version 2 or later. + * + */ + +#ifndef INK_ACTIONS_ELEMENT_A_H +#define INK_ACTIONS_ELEMENT_A_H + +class InkscapeApplication; + +void add_actions_element_a(InkscapeApplication* app); + +#endif // INK_ACTIONS_ELEMENT_A_H + +/* + 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 : diff --git a/src/actions/actions-element-image.cpp b/src/actions/actions-element-image.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a85d459c0c71dc5d7a4837032b300c393e0cd4f9 --- /dev/null +++ b/src/actions/actions-element-image.cpp @@ -0,0 +1,155 @@ +// 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 + +#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 "preferences.h" + +#include "selection.h" // Selection +#include "object/sp-image.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 = dynamic_cast(item); + if (image) { + + Inkscape::XML::Node *node = item->getRepr(); + const gchar *href = node->attribute("xlink:href"); + if (!href) { + std::cerr << "image_edit: no xlink:href" << std::endl; + continue; + } + + if (strncmp (href, "data", 4) == 0) { + std::cerr << "image_edit: cannot edit embedded image" << std::endl; + 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 { + std::cerr << "image_edit: " << message << std::endl; + } + g_error_free(error); + error = nullptr; + } + } + } +} + +std::vector> raw_data_element_image = +{ + // clang-format off + {"app.element-image-edit", N_("Edit externally"), "Image", N_("Edit image externally (image must be selected and not embeded).") }, + // clang-format on +}; + +void +add_actions_element_image(InkscapeApplication* app) +{ + auto *gapp = app->gio_app(); + + // clang-format off + 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 : diff --git a/src/actions/actions-element-image.h b/src/actions/actions-element-image.h new file mode 100644 index 0000000000000000000000000000000000000000..32a71a571914639626e679d5675075a9ea4d3c5d --- /dev/null +++ b/src/actions/actions-element-image.h @@ -0,0 +1,29 @@ +// 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. + * + */ + +#ifndef INK_ACTIONS_ELEMENT_IMAGE_H +#define INK_ACTIONS_ELEMENT_IMAGE_H + +class InkscapeApplication; + +void add_actions_element_image(InkscapeApplication* app); + +#endif // INK_ACTIONS_ELEMENT_IMAGE_H + +/* + 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 : diff --git a/src/actions/actions-hide-lock.cpp b/src/actions/actions-hide-lock.cpp index b108f1ff4abb510d6406894e1bc24f78bd5292af..adcbfc85b042e92e64deb7ad521d88bad12f50fd 100644 --- a/src/actions/actions-hide-lock.cpp +++ b/src/actions/actions-hide-lock.cpp @@ -5,60 +5,233 @@ * * Authors: * Sushant A A + * Tavmjong Bah * - * Copyright (C) 2021 Authors + * Copyright (C) 2021-2022 Authors * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include "actions-hide-lock.h" + #include // Not ! To eventually allow a headless version! #include -#include "actions-hide-lock.h" #include "inkscape-application.h" -#include "inkscape-window.h" -#include "desktop.h" #include "document-undo.h" -#include "selection-chemistry.h" +#include "object/sp-root.h" + +// Helper to unlock/unhide everything. (Could also be used to lock/hide everything but that isn't very useful.) +static bool +hide_lock_recurse(bool (*f)(SPItem*, bool), SPItem *item, bool hide_or_lock) +{ + bool changed = false; + + if (f(item, hide_or_lock)) { + changed = true; + } + + for (auto& child : item->children) { + auto item = dynamic_cast(&child); + if (item && hide_lock_recurse(f, item, hide_or_lock)) { + changed = true; + } + } + + return changed; +} + +// Helper to hide/unhide one item. +bool +hide_lock_hide(SPItem* item, bool hide) +{ + bool changed = false; + if (item->isHidden() != hide) { + item->setHidden(hide); + changed = true; + } + return changed; +} + +// Helper to lock/unlock one item. +bool +hide_lock_lock(SPItem* item, bool lock) +{ + bool changed = false; + if (item->isLocked() != lock) { + item->setLocked(lock); + changed = true; + } + return changed; +} + +// Unhide all void -hide_lock_unhide_all(InkscapeWindow* win) +hide_lock_unhide_all(InkscapeApplication* app) { - SPDesktop* dt = win->get_desktop(); - SPDocument *doc = dt->getDocument(); - unhide_all(dt); - Inkscape::DocumentUndo::done(doc, _("Unhide all objects in the current layer"), ""); + auto document = app->get_active_document(); + auto root = document->getRoot(); + + bool changed = hide_lock_recurse(&hide_lock_hide, root, false); // Unhide + + if (changed) { + Inkscape::DocumentUndo::done(document, _("Unhid all objects in the current layer"), ""); + } } +// Unlock all void -hide_lock_unlock_all(InkscapeWindow* win) +hide_lock_unlock_all(InkscapeApplication* app) { - SPDesktop* dt = win->get_desktop(); - SPDocument *doc = dt->getDocument(); - unlock_all(dt); - Inkscape::DocumentUndo::done(doc, _("Unlock all objects in the current layer"), ""); + auto document = app->get_active_document(); + auto root = document->getRoot(); + + bool changed = hide_lock_recurse(&hide_lock_lock, root, false); // Unlock + + if (changed) { + Inkscape::DocumentUndo::done(document, _("Unlocked all objects in the current layer"), ""); + } +} + +// Unhide selected items and their descendents. +void +hide_lock_unhide_below(InkscapeApplication *app) +{ + auto selection = app->get_active_selection(); + if (!selection) { + std::cerr << "hide_lock_unhide_below: no selection!" << std::endl; + return; + } + + bool changed = false; + for (auto item : selection->items()) { + if (hide_lock_recurse(&hide_lock_hide, item, false)) { + changed = true; + } + } + + if (changed) { + auto document = app->get_active_document(); + Inkscape::DocumentUndo::done(document, _("Unhid selected items and their descendents."), ""); + } +} + +// Unlock selected items and their descendents. +void +hide_lock_unlock_below(InkscapeApplication *app) +{ + auto selection = app->get_active_selection(); + if (!selection) { + std::cerr << "hide_lock_unhide_below: no selection!" << std::endl; + return; + } + + bool changed = false; + for (auto item : selection->items()) { + if (hide_lock_recurse(&hide_lock_lock, item, false)) { + changed = true; + } + } + + if (changed) { + auto document = app->get_active_document(); + Inkscape::DocumentUndo::done(document, _("Unlocked selected items and their descendents."), ""); + } +} + +// Hide/unhide selected items. +void +hide_lock_hide_selected(InkscapeApplication* app, bool hide) +{ + auto selection = app->get_active_selection(); + if (!selection) { + std::cerr << "hide_lock_hide_selected: no selection!" << std::endl; + return; + } + + bool changed = false; + for (auto item : selection->items()) { + if (hide_lock_hide(item, hide)) { + changed = true; + } + } + + if (changed) { + auto document = app->get_active_document(); + Inkscape::DocumentUndo::done(document, (hide ? _("Hid selected items.") : _("Unhid selected items.")), ""); + selection->clear(); + } +} + +// Lock/Unlock selected items. +void +hide_lock_lock_selected(InkscapeApplication* app, bool lock) +{ + auto selection = app->get_active_selection(); + if (!selection) { + std::cerr << "hide_lock_lock_selected: no selection!" << std::endl; + return; + } + + bool changed = false; + for (auto item : selection->items()) { + if (hide_lock_lock(item, lock)) { + changed = true; + } + } + + if (changed) { + auto document = app->get_active_document(); + Inkscape::DocumentUndo::done(document, (lock ? _("Locked selected items.") : _("Unlocked selected items.")), ""); + selection->clear(); + } } std::vector> raw_data_hide_lock = { // clang-format off - {"win.unhide-all", N_("Unhide All"), "Hide and Lock", N_("Unhide all objects in the current layer") }, - {"win.unlock-all", N_("Unlock All"), "Hide and Lock", N_("Unlock all objects in the current layer") } + {"app.unhide-all", N_("Unhide All"), "Hide and Lock", N_("Unhide all objects in the current layer") }, + {"app.unlock-all", N_("Unlock All"), "Hide and Lock", N_("Unlock all objects in the current layer") }, + + {"app.selection-hide", N_("Hide selection"), "Hide and Lock", N_("Hide all selected objects") }, + {"app.selection-unhide", N_("Unhide selection"), "Hide and Lock", N_("Unhide all selected objects") }, + {"app.selection-unhide-below", N_("Unhide descendents"), "Hide and Lock", N_("Unhide all items inside selected objects") }, + + {"app.selection-lock", N_("Lock selection"), "Hide and Lock", N_("Lock all selected objects") }, + {"app.selection-unlock", N_("Unlock selection"), "Hide and Lock", N_("Unlock all selected objects") }, + {"app.selection-unlock-below", N_("Unlock descendents"), "Hide and Lock", N_("Unlock all items inside selected objects") }, // clang-format on }; void -add_actions_hide_lock(InkscapeWindow* win) +add_actions_hide_lock(InkscapeApplication* app) { + auto *gapp = app->gio_app(); + // clang-format off - win->add_action( "unhide-all", sigc::bind(sigc::ptr_fun(&hide_lock_unhide_all), win)); - win->add_action( "unlock-all", sigc::bind(sigc::ptr_fun(&hide_lock_unlock_all), win)); + gapp->add_action( "unhide-all", sigc::bind( sigc::ptr_fun(&hide_lock_unhide_all), app)); + gapp->add_action( "unlock-all", sigc::bind( sigc::ptr_fun(&hide_lock_unlock_all), app)); + + gapp->add_action( "selection-hide", sigc::bind(sigc::ptr_fun(&hide_lock_hide_selected), app, true )); + gapp->add_action( "selection-unhide", sigc::bind(sigc::ptr_fun(&hide_lock_hide_selected), app, false)); + gapp->add_action( "selection-unhide-below", sigc::bind( sigc::ptr_fun(&hide_lock_unhide_below), app)); + + gapp->add_action( "selection-lock", sigc::bind(sigc::ptr_fun(&hide_lock_lock_selected), app, true )); + gapp->add_action( "selection-unlock", sigc::bind(sigc::ptr_fun(&hide_lock_lock_selected), app, false)); + gapp->add_action( "selection-unlock-below", sigc::bind( sigc::ptr_fun(&hide_lock_unlock_below), app)); // clang-format on - auto app = InkscapeApplication::instance(); - if (!app) { - std::cerr << "add_actions_hide_lock: no app!" << std::endl; - return; - } app->get_action_extra_data().add_data(raw_data_hide_lock); -} \ No newline at end of file +} + +/* + 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/actions/actions-hide-lock.h b/src/actions/actions-hide-lock.h index fc7fb3eb4f5471bc64ee7e3aef946e1410719afd..457de146f06ea446590dec5dfe4cc3189b985e83 100644 --- a/src/actions/actions-hide-lock.h +++ b/src/actions/actions-hide-lock.h @@ -12,8 +12,8 @@ #ifndef INK_ACTIONS_HIDE_LOCK_H #define INK_ACTIONS_HIDE_LOCK_H -class InkscapeWindow; +class InkscapeApplication; -void add_actions_hide_lock(InkscapeWindow* win); +void add_actions_hide_lock(InkscapeApplication* app); -#endif // INK_ACTIONS_HIDE_LOCK_H \ No newline at end of file +#endif // INK_ACTIONS_HIDE_LOCK_H diff --git a/src/actions/actions-layer.cpp b/src/actions/actions-layer.cpp index 175dd4c1488f56f446a4e149896ac90faf86c262..78504d749d31f50265210b22b3ff29d0ac8eb59a 100644 --- a/src/actions/actions-layer.cpp +++ b/src/actions/actions-layer.cpp @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later /** \file * - * Actions for Layers + * Actions for Layers. + * + * These all require a window. To do: remove this requirement. * * Authors: * Sushant A A @@ -23,6 +25,16 @@ #include "ui/icon-names.h" #include "document-undo.h" +/* + * A layer is a group element with a special Inkscape attribute (Inkscape:groupMode) set to + * "layer". It is typically directly placed in the element but it is also possible to put + * inside any other layer (a "layer" inside a normal group is considered a group). The GUI tracks + * which is the "Current" layer. The "Current" layer is set when a new selection initiated + * (i.e. when not adding objects to a previous selection), when it is chosen in the "Layers and + * Objects" dialog, when using the previous/next layer menu items, and when moving objects to + * adjacent layers. + */ + void layer_new(InkscapeWindow* win) { @@ -33,7 +45,75 @@ layer_new(InkscapeWindow* win) } void -layer_rename(InkscapeWindow* win) +layer_duplicate (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + + if (!dt->layerManager().isRoot()) { + + dt->selection->duplicate(true, true); // This requires the selection to be a layer! + Inkscape::DocumentUndo::done(dt->getDocument(), _("Duplicate layer"), INKSCAPE_ICON("layer-duplicate")); + dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Duplicated layer.")); + + } else { + dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer.")); + } +} + +void +layer_delete (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + auto root = dt->layerManager().currentRoot(); + + if (!dt->layerManager().isRoot()) { + + dt->getSelection()->clear(); + SPObject *old_layer = dt->layerManager().currentLayer(); + SPObject *old_parent = old_layer->parent; + SPObject *old_parent_parent = (old_parent != nullptr) ? old_parent->parent : nullptr; + + SPObject *survivor = Inkscape::previous_layer(root, old_layer); + if (survivor != nullptr && survivor->parent == old_layer) { + while (survivor != nullptr && + survivor->parent != old_parent && + survivor->parent != old_parent_parent) + { + survivor = Inkscape::previous_layer(root, survivor); + } + } + + if (survivor == nullptr || (survivor->parent != old_parent && survivor->parent != old_layer)) { + survivor = Inkscape::next_layer(root, old_layer); + while (survivor != nullptr && + survivor != old_parent && + survivor->parent != old_parent) + { + survivor = Inkscape::next_layer(root, survivor); + } + } + + // Deleting the old layer before switching layers is a hack to trigger the + // listeners of the deletion event (as happens when old_layer is deleted using the + // xml editor). See + // http://sourceforge.net/tracker/index.php?func=detail&aid=1339397&group_id=93438&atid=604306 + // + old_layer->deleteObject(); + + if (survivor) { + dt->layerManager().setCurrentLayer(survivor); + } + + Inkscape::DocumentUndo::done(dt->getDocument(), _("Delete layer"), INKSCAPE_ICON("layer-delete")); + dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Deleted layer.")); + + } else { + dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer.")); + } +} + +void +layer_rename (InkscapeWindow* win) { SPDesktop* dt = win->get_desktop(); @@ -42,7 +122,23 @@ layer_rename(InkscapeWindow* win) } void -layer_toggle_hide (InkscapeWindow* win) +layer_hide_all (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + dt->layerManager().toggleHideAllLayers(true); + Inkscape::DocumentUndo::maybeDone(dt->getDocument(), "layer:hideall", _("Hide all layers"), ""); +} + +void +layer_unhide_all (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + dt->layerManager().toggleHideAllLayers(false); + Inkscape::DocumentUndo::maybeDone(dt->getDocument(), "layer:showall", _("Show all layers"), ""); +} + +void +layer_hide_toggle (InkscapeWindow* win) { SPDesktop* dt = win->get_desktop(); auto layer = dt->layerManager().currentLayer(); @@ -55,7 +151,37 @@ layer_toggle_hide (InkscapeWindow* win) } void -layer_toggle_lock (InkscapeWindow* win) +layer_hide_toggle_others (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + auto layer = dt->layerManager().currentLayer(); + + if (!layer || dt->layerManager().isRoot()) { + dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer.")); + } else { + dt->layerManager().toggleLayerSolo( layer ); // Weird name! + Inkscape::DocumentUndo::done(dt->getDocument(), _("Hide other layers"), ""); + } +} + +void +layer_lock_all (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + dt->layerManager().toggleLockAllLayers(true); + Inkscape::DocumentUndo::maybeDone(dt->getDocument(), "layer:lockall", _("Lock all layers"), ""); +} + +void +layer_unlock_all (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + dt->layerManager().toggleLockAllLayers(false); + Inkscape::DocumentUndo::maybeDone(dt->getDocument(), "layer:unlockall", _("Unlock all layers"), ""); +} + +void +layer_lock_toggle (InkscapeWindow* win) { SPDesktop* dt = win->get_desktop(); auto layer = dt->layerManager().currentLayer(); @@ -67,6 +193,20 @@ layer_toggle_lock (InkscapeWindow* win) } } +void +layer_lock_toggle_others (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + auto layer = dt->layerManager().currentLayer(); + + if (!layer || dt->layerManager().isRoot()) { + dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer.")); + } else { + dt->layerManager().toggleLockOtherLayers( layer ); + Inkscape::DocumentUndo::done(dt->getDocument(), _("Lock other layers"), ""); + } +} + void layer_previous (InkscapeWindow* win) { @@ -237,66 +377,75 @@ layer_bottom (InkscapeWindow* win) } void -layer_duplicate (InkscapeWindow* win) +layer_to_group (InkscapeWindow* win) { SPDesktop* dt = win->get_desktop(); + auto layer = dt->layerManager().currentLayer(); - if (!dt->layerManager().isRoot()) { - - dt->selection->duplicate(true, true); - Inkscape::DocumentUndo::done(dt->getDocument(), _("Duplicate layer"), INKSCAPE_ICON("layer-duplicate")); - dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Duplicated layer.")); - - } else { + if (!layer || dt->layerManager().isRoot()) { dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer.")); + return; } + + layer->setLayerMode(SPGroup::GROUP); + layer->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT); + Inkscape::DocumentUndo::done(dt->getDocument(), _("Layer to group"), INKSCAPE_ICON("dialog-objects")); } void -layer_delete (InkscapeWindow* win) +layer_from_group (InkscapeWindow* win) { SPDesktop* dt = win->get_desktop(); - auto root = dt->layerManager().currentRoot(); + auto selection = dt->getSelection(); - if (!dt->layerManager().isRoot()) { + std::vector items(selection->items().begin(), selection->items().end()); + if (items.size() != 1) { + std::cerr << "layer_to_group: only one selected item allowed!" << std::endl; + return; + } - dt->getSelection()->clear(); - SPObject *old_layer = dt->layerManager().currentLayer(); - SPObject *old_parent = old_layer->parent; - SPObject *old_parent_parent = (old_parent != nullptr) ? old_parent->parent : nullptr; + auto group = dynamic_cast(items[0]); + if (group && group->isLayer()) { + dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Group already layer.")); + return; + } - SPObject *survivor = Inkscape::previous_layer(root, old_layer); - if (survivor != nullptr && survivor->parent == old_layer) { - while (survivor != nullptr && - survivor->parent != old_parent && - survivor->parent != old_parent_parent) - { - survivor = Inkscape::previous_layer(root, survivor); - } - } + group->setLayerMode(SPGroup::LAYER); + group->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT); + Inkscape::DocumentUndo::done(dt->getDocument(), _("Group to layer"), INKSCAPE_ICON("dialog-objects")); +} - if (survivor == nullptr || (survivor->parent != old_parent && survivor->parent != old_layer)) { - survivor = Inkscape::next_layer(root, old_layer); - while (survivor != nullptr && - survivor != old_parent && - survivor->parent != old_parent) - { - survivor = Inkscape::next_layer(root, survivor); - } - } +// Does not change XML. +void +group_enter (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + auto selection = dt->selection; - // Deleting the old layer before switching layers is a hack to trigger the - old_layer->deleteObject(); + std::vector items(selection->items().begin(), selection->items().end()); + if (items.size() == 1 && dynamic_cast(items[0])) { + // Only one item and it is a group! + dt->layerManager().setCurrentLayer(items[0]); + selection->clear(); + } +} - if (survivor) { - dt->layerManager().setCurrentLayer(survivor); - } +// Does not change XML. +void +group_exit (InkscapeWindow* win) +{ + SPDesktop* dt = win->get_desktop(); + auto selection = dt->selection; - Inkscape::DocumentUndo::done(dt->getDocument(), _("Delete layer"), INKSCAPE_ICON("layer-delete")); - dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Deleted layer.")); + auto parent = dt->layerManager().currentLayer()->parent; + dt->layerManager().setCurrentLayer(parent); + std::vector items(selection->items().begin(), selection->items().end()); + if (items.size() == 1 && dynamic_cast(items[0]->parent) ) { + // Only one item selected and the parent is a group! + selection->set(items[0]->parent); } else { - dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer.")); + selection->clear(); } } @@ -304,20 +453,31 @@ std::vector> raw_data_layer = { // clang-format off {"win.layer-new", N_("Add Layer"), "Layers", N_("Create a new layer")}, + {"win.layer-duplicate", N_("Duplicate Current Layer"), "Layers", N_("Duplicate an existing layer")}, + {"win.layer-delete", N_("Delete Current Layer"), "Layers", N_("Delete the current layer")}, {"win.layer-rename", N_("Rename Layer"), "Layers", N_("Rename the current layer")}, + {"win.layer-toggle-hide", N_("Show/Hide Current Layer"), "Layers", N_("Toggle visibility of current layer")}, {"win.layer-toggle-lock", N_("Lock/Unlock Current Layer"), "Layers", N_("Toggle lock on current layer")}, + {"win.layer-previous", N_("Switch to Layer Abov_e"), "Layers", N_("Switch to the layer above the current")}, {"win.layer-next", N_("Switch to Layer Belo_w"), "Layers", N_("Switch to the layer below the current")}, + {"win.selection-move-to-layer-above", N_("Move Selection to Layer Above"), "Layers", N_("Move selection to the layer above the current")}, {"win.selection-move-to-layer-below", N_("Move Selection to Layer Bel_ow"), "Layers", N_("Move selection to the layer below the current")}, {"win.selection-move-to-layer", N_("Move Selection to Layer..."), "Layers", N_("Move selection to layer")}, + {"win.layer-top", N_("Layer to Top"), "Layers", N_("Raise the current layer to the top")}, {"win.layer-raise", N_("Raise Layer"), "Layers", N_("Raise the current layer")}, {"win.layer-lower", N_("Lower Layer"), "Layers", N_("Lower the current layer")}, {"win.layer-bottom", N_("Layer to Bottom"), "Layers", N_("Lower the current layer to the bottom")}, - {"win.layer-duplicate", N_("Duplicate Current Layer"), "Layers", N_("Duplicate an existing layer")}, - {"win.layer-delete", N_("Delete Current Layer"), "Layers", N_("Delete the current layer")} + + {"win.layer-to-group", N_("Layer to Group"), "Layers", N_("Convert the current layer to a group")}, + {"win.layer-from-group", N_("Layer from Group"), "Layers", N_("Convert the a group to a layer")}, + + // These use Layer technology even if they don't act on layers. + {"win.selection-group-enter", N_("Enter Group"), "Select", N_("Enter group")}, + {"win.selection-group-exit", N_("Exit Group"), "Select", N_("Exit group")}, // clang-format on }; @@ -326,20 +486,37 @@ add_actions_layer(InkscapeWindow* win) { // clang-format off win->add_action("layer-new", sigc::bind(sigc::ptr_fun(&layer_new), win)); + win->add_action("layer-duplicate", sigc::bind(sigc::ptr_fun(&layer_duplicate), win)); + win->add_action("layer-delete", sigc::bind(sigc::ptr_fun(&layer_delete), win)); win->add_action("layer-rename", sigc::bind(sigc::ptr_fun(&layer_rename), win)); - win->add_action("layer-toggle-hide", sigc::bind(sigc::ptr_fun(&layer_toggle_hide), win)); - win->add_action("layer-toggle-lock", sigc::bind(sigc::ptr_fun(&layer_toggle_lock), win)); + + win->add_action("layer-hide-all", sigc::bind(sigc::ptr_fun(&layer_hide_all), win)); + win->add_action("layer-unhide-all", sigc::bind(sigc::ptr_fun(&layer_unhide_all), win)); + win->add_action("layer-hide-toggle", sigc::bind(sigc::ptr_fun(&layer_hide_toggle), win)); + win->add_action("layer-hide-toggle-others", sigc::bind(sigc::ptr_fun(&layer_hide_toggle_others), win)); + + win->add_action("layer-lock-all", sigc::bind(sigc::ptr_fun(&layer_lock_all), win)); + win->add_action("layer-unlock-all", sigc::bind(sigc::ptr_fun(&layer_unlock_all), win)); + win->add_action("layer-lock-toggle", sigc::bind(sigc::ptr_fun(&layer_lock_toggle), win)); + win->add_action("layer-lock-toggle-others", sigc::bind(sigc::ptr_fun(&layer_lock_toggle_others), win)); + win->add_action("layer-previous", sigc::bind(sigc::ptr_fun(&layer_previous), win)); win->add_action("layer-next", sigc::bind(sigc::ptr_fun(&layer_next), win)); + win->add_action("selection-move-to-layer-above", sigc::bind(sigc::ptr_fun(&selection_move_to_layer_above), win)); win->add_action("selection-move-to-layer-below", sigc::bind(sigc::ptr_fun(&selection_move_to_layer_below), win)); win->add_action("selection-move-to-layer", sigc::bind(sigc::ptr_fun(&selection_move_to_layer), win)); + win->add_action("layer-top", sigc::bind(sigc::ptr_fun(&layer_top), win)); win->add_action("layer-raise", sigc::bind(sigc::ptr_fun(&layer_raise), win)); win->add_action("layer-lower", sigc::bind(sigc::ptr_fun(&layer_lower), win)); win->add_action("layer-bottom", sigc::bind(sigc::ptr_fun(&layer_bottom), win)); - win->add_action("layer-duplicate", sigc::bind(sigc::ptr_fun(&layer_duplicate), win)); - win->add_action("layer-delete", sigc::bind(sigc::ptr_fun(&layer_delete), win)); + + win->add_action("layer-to-group", sigc::bind(sigc::ptr_fun(&layer_to_group), win)); + win->add_action("layer-from-group", sigc::bind(sigc::ptr_fun(&layer_from_group), win)); + + win->add_action("selection-group-enter", sigc::bind(sigc::ptr_fun(&group_enter), win)); + win->add_action("selection-group-exit", sigc::bind(sigc::ptr_fun(&group_exit), win)); // clang-format on auto app = InkscapeApplication::instance(); @@ -349,3 +526,14 @@ add_actions_layer(InkscapeWindow* win) } app->get_action_extra_data().add_data(raw_data_layer); } + +/* + 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/actions/actions-object.cpp b/src/actions/actions-object.cpp index 3796e251926072b0adc0ccd2d8c1dec5a22944d4..cb6731ba2bcaa01883f2cd95302f931ffb648caf 100644 --- a/src/actions/actions-object.cpp +++ b/src/actions/actions-object.cpp @@ -102,7 +102,7 @@ object_unlink_clones(InkscapeApplication *app) } void -set_clip(InkscapeApplication *app) +object_clip_set(InkscapeApplication *app) { Inkscape::Selection *selection = app->get_active_selection(); @@ -111,7 +111,7 @@ set_clip(InkscapeApplication *app) } void -object_set_inverse(InkscapeApplication *app) +object_clip_set_inverse(InkscapeApplication *app) { Inkscape::Selection *selection = app->get_active_selection(); @@ -122,7 +122,7 @@ object_set_inverse(InkscapeApplication *app) } void -object_release(InkscapeApplication *app) +object_clip_release(InkscapeApplication *app) { Inkscape::Selection *selection = app->get_active_selection(); @@ -133,16 +133,25 @@ object_release(InkscapeApplication *app) } void -set_mask(InkscapeApplication *app) +object_clip_set_group(InkscapeApplication *app) +{ + Inkscape::Selection *selection = app->get_active_selection(); + selection->setClipGroup(); + // Undo added in setClipGroup(). +} + +void +object_mask_set(InkscapeApplication *app) { Inkscape::Selection *selection = app->get_active_selection(); // Object Mask Set selection->setMask(false, false); + // Undo added in setMask(). } void -object_set_inverse_mask(InkscapeApplication *app) +object_mask_set_inverse(InkscapeApplication *app) { Inkscape::Selection *selection = app->get_active_selection(); @@ -153,7 +162,7 @@ object_set_inverse_mask(InkscapeApplication *app) } void -object_release_mask(InkscapeApplication *app) +object_mask_release(InkscapeApplication *app) { Inkscape::Selection *selection = app->get_active_selection(); @@ -259,15 +268,19 @@ std::vector> raw_data_object = // clang-format off {"app.object-set-attribute", N_("Set Attribute"), "Object", N_("Set or update an attribute of selected objects; usage: object-set-attribute:attribute name, attribute value;")}, {"app.object-set-property", N_("Set Property"), "Object", N_("Set or update a property on selected objects; usage: object-set-property:property name, property value;")}, + {"app.object-unlink-clones", N_("Unlink Clones"), "Object", N_("Unlink clones and symbols")}, {"app.object-to-path", N_("Object To Path"), "Object", N_("Convert shapes to paths")}, {"app.object-stroke-to-path", N_("Stroke to Path"), "Object", N_("Convert strokes to paths")}, + {"app.object-set-clip", N_("Object Clip Set"), "Object", N_("Apply clipping path to selection (using the topmost object as clipping path)")}, {"app.object-set-inverse-clip", N_("Object Clip Set Inverse"), "Object", N_("Apply inverse clipping path to selection (using the topmost object as clipping path)")}, {"app.object-release-clip", N_("Object Clip Release"), "Object", N_("Remove clipping path from selection")}, + {"app.object-set-clip-group", N_("Object Clip Set Group"), "Object", N_("Create a self-clipping group to which objects (not contributing to the clip-path) can be added")}, {"app.object-set-mask", N_("Object Mask Set"), "Object", N_("Apply mask to selection (using the topmost object as mask)")}, {"app.object-set-inverse-mask", N_("Object Mask Set Inverse"), "Object", N_("Set Inverse (LPE)")}, {"app.object-release-mask", N_("Object Mask Release"), "Object", N_("Remove mask from selection")}, + {"app.object-rotate-90-cw", N_("Object Rotate 90"), "Object", N_("Rotate selection 90° clockwise")}, {"app.object-rotate-90-ccw", N_("Object Rotate 90 CCW"), "Object", N_("Rotate selection 90° counter-clockwise")}, {"app.object-flip-horizontal", N_("Object Flip Horizontal"), "Object", N_("Flip selected objects horizontally")}, @@ -293,29 +306,28 @@ add_actions_object(InkscapeApplication* app) auto *gapp = app->gio_app(); - // Debian 9 has 2.50.0 -#if GLIB_CHECK_VERSION(2, 52, 0) - // clang-format off gapp->add_action_with_parameter( "object-set-attribute", String, sigc::bind(sigc::ptr_fun(&object_set_attribute), app)); gapp->add_action_with_parameter( "object-set-property", String, sigc::bind(sigc::ptr_fun(&object_set_property), app)); + gapp->add_action( "object-unlink-clones", sigc::bind(sigc::ptr_fun(&object_unlink_clones), app)); gapp->add_action( "object-to-path", sigc::bind(sigc::ptr_fun(&object_to_path), app)); gapp->add_action( "object-stroke-to-path", sigc::bind(sigc::ptr_fun(&object_stroke_to_path), app)); - gapp->add_action( "object-set-clip", sigc::bind(sigc::ptr_fun(&set_clip), app)); - gapp->add_action( "object-set-inverse-clip", sigc::bind(sigc::ptr_fun(&object_set_inverse), app)); - gapp->add_action( "object-release-clip", sigc::bind(sigc::ptr_fun(&object_release), app)); - gapp->add_action( "object-set-mask", sigc::bind(sigc::ptr_fun(&set_mask), app)); - gapp->add_action( "object-set-inverse-mask", sigc::bind(sigc::ptr_fun(&object_set_inverse_mask), app)); - gapp->add_action( "object-release-mask", sigc::bind(sigc::ptr_fun(&object_release_mask), app)); + + gapp->add_action( "object-set-clip", sigc::bind(sigc::ptr_fun(&object_clip_set), app)); + gapp->add_action( "object-set-inverse-clip", sigc::bind(sigc::ptr_fun(&object_clip_set_inverse), app)); + gapp->add_action( "object-release-clip", sigc::bind(sigc::ptr_fun(&object_clip_release), app)); + gapp->add_action( "object-set-clip-group", sigc::bind(sigc::ptr_fun(&object_clip_set_group), app)); + gapp->add_action( "object-set-mask", sigc::bind(sigc::ptr_fun(&object_mask_set), app)); + gapp->add_action( "object-set-inverse-mask", sigc::bind(sigc::ptr_fun(&object_mask_set_inverse), app)); + gapp->add_action( "object-release-mask", sigc::bind(sigc::ptr_fun(&object_mask_release), app)); + gapp->add_action( "object-rotate-90-cw", sigc::bind(sigc::ptr_fun(&object_rotate_90_cw), app)); gapp->add_action( "object-rotate-90-ccw", sigc::bind(sigc::ptr_fun(&object_rotate_90_cw), app)); gapp->add_action( "object-flip-horizontal", sigc::bind(sigc::ptr_fun(&object_flip_horizontal), app)); gapp->add_action( "object-flip-vertical", sigc::bind(sigc::ptr_fun(&object_flip_vertical), app)); // clang-format on -#endif - app->get_action_extra_data().add_data(raw_data_object); app->get_action_hint_data().add_data(hint_data_object); } diff --git a/src/actions/actions-selection-object.cpp b/src/actions/actions-selection-object.cpp index b922bcda6c195687cf999d870d32b1a7302be9a0..21b668678b426b458ef0f240ccea08aad6fb2b71 100644 --- a/src/actions/actions-selection-object.cpp +++ b/src/actions/actions-selection-object.cpp @@ -22,9 +22,12 @@ #include "actions-selection-object.h" #include "actions-helper.h" #include "inkscape-application.h" +#include "inkscape-window.h" // Anchor #include "inkscape.h" #include "selection.h" +#include "ui/dialog/dialog-container.h" // Used by select_object_link() to open dialog to add hyperlink. + void select_object_group(InkscapeApplication* app) { @@ -52,6 +55,21 @@ select_object_ungroup_pop(InkscapeApplication* app) selection->popFromGroup(); } +void +select_object_link(InkscapeApplication* app) +{ + Inkscape::Selection *selection = app->get_active_selection(); + + // Group with + auto anchor = selection->group(1); + selection->set(anchor); + + // Open dialog to set link. + if (app->get_active_window()) { + app->get_active_window()->get_desktop()->getContainer()->new_dialog("ObjectAttributes"); + } +} + void selection_top(InkscapeApplication* app) { @@ -94,10 +112,12 @@ std::vector> raw_data_selection_object = { "app.selection-group", N_("Group"), "Select", N_("Group selected objects")}, { "app.selection-ungroup", N_("Ungroup"), "Select", N_("Ungroup selected objects")}, { "app.selection-ungroup-pop", N_("Pop Selected Objects out of Group"), "Select", N_("Pop selected objects out of group")}, + { "app.selection-link", N_("Link"), "Select", N_("Add an anchor to selected objects")}, + { "app.selection-top", N_("Raise to Top"), "Select", N_("Raise selection to top")}, { "app.selection-raise", N_("Raise"), "Select", N_("Raise selection one step")}, { "app.selection-lower", N_("Lower"), "Select", N_("Lower selection one step")}, - { "app.selection-bottom", N_("Lower to Bottom"), "Select", N_("Lower selection to bottom")} + { "app.selection-bottom", N_("Lower to Bottom"), "Select", N_("Lower selection to bottom")}, // clang-format on }; @@ -107,9 +127,12 @@ add_actions_selection_object(InkscapeApplication* app) auto *gapp = app->gio_app(); // clang-format off + // See actions-layer.cpp for "enter-group" and "exit-group". gapp->add_action( "selection-group", sigc::bind(sigc::ptr_fun(&select_object_group), app)); gapp->add_action( "selection-ungroup", sigc::bind(sigc::ptr_fun(&select_object_ungroup), app)); gapp->add_action( "selection-ungroup-pop", sigc::bind(sigc::ptr_fun(&select_object_ungroup_pop), app)); + gapp->add_action( "selection-link", sigc::bind(sigc::ptr_fun(&select_object_link), app)); + gapp->add_action( "selection-top", sigc::bind(sigc::ptr_fun(&selection_top), app)); gapp->add_action( "selection-raise", sigc::bind(sigc::ptr_fun(&selection_raise), app)); gapp->add_action( "selection-lower", sigc::bind(sigc::ptr_fun(&selection_lower), app)); diff --git a/src/actions/actions-selection-window.cpp b/src/actions/actions-selection-window.cpp index b68a6444a980db38565badc5efecf910bd0104c2..79c2ba65e0939cf092cfba786e870a7226ae6ce1 100644 --- a/src/actions/actions-selection-window.cpp +++ b/src/actions/actions-selection-window.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later /** \file * - * Actions related to selection wich require desktop. + * Actions related to selection which require desktop. * * These are linked to the desktop as they operate differently * depending on if the node tool is in use or not. diff --git a/src/inkscape-application.cpp b/src/inkscape-application.cpp index a975e484de565ec270c7b0e720a68d78d319d739..e8f9a5efcf92c2a2a43401f3e8462ce573e9e42f 100644 --- a/src/inkscape-application.cpp +++ b/src/inkscape-application.cpp @@ -53,6 +53,9 @@ #include "actions/actions-file.h" // Actions #include "actions/actions-edit.h" // Actions #include "actions/actions-effect.h" // Actions +#include "actions/actions-element-a.h" // Actions +#include "actions/actions-element-image.h" // Actions +#include "actions/actions-hide-lock.h" // Actions #include "actions/actions-object.h" // Actions #include "actions/actions-object-align.h" // Actions #include "actions/actions-output.h" // Actions @@ -601,7 +604,10 @@ InkscapeApplication::InkscapeApplication() add_actions_base(this); // actions that are GUI independent add_actions_edit(this); // actions for editing add_actions_effect(this); // actions for Filters and Extensions + add_actions_element_a(this); // actions for the SVG a (anchor) element + add_actions_element_image(this); // actions for the SVG image element add_actions_file(this); // actions for file handling + add_actions_hide_lock(this); // actions for hiding/locking items. add_actions_object(this); // actions for object manipulation add_actions_object_align(this); // actions for object alignment add_actions_output(this); // actions for file export diff --git a/src/inkscape-window.cpp b/src/inkscape-window.cpp index 84485a38536f47ad1ce530342d4458beff5d30e2..6806efdbc5f04f558981c08a35081c700f8bf881 100644 --- a/src/inkscape-window.cpp +++ b/src/inkscape-window.cpp @@ -29,7 +29,6 @@ #include "actions/actions-edit-window.h" #include "actions/actions-file-window.h" #include "actions/actions-help-url.h" -#include "actions/actions-hide-lock.h" #include "actions/actions-layer.h" #include "actions/actions-node-align.h" // Node alignment. #include "actions/actions-paths.h" // TEMP @@ -110,7 +109,6 @@ InkscapeWindow::InkscapeWindow(SPDocument* document) add_actions_edit_window(this); // Actions to edit. add_actions_file_window(this); // Actions for file actions which are desktop dependent. add_actions_help_url(this); // Actions to help url. - add_actions_hide_lock(this); // Actions to transform dialog. add_actions_layer(this); // Actions for layer. add_actions_node_align(this); // Actions to align and distribute nodes (requiring Node tool). add_actions_path(this); // Actions for paths. TEMP diff --git a/src/object/object-set.h b/src/object/object-set.h index abe4500df7b2090edf65b3b15bf47d1f17b9b4bc..b9ffaeaec98bee87dd46734ee8e12a68cd72d4eb 100644 --- a/src/object/object-set.h +++ b/src/object/object-set.h @@ -409,7 +409,7 @@ public: void relink(); void cloneOriginal(); void cloneOriginalPathLPE(bool allow_transforms = false); - Inkscape::XML::Node* group(); + Inkscape::XML::Node* group(int type = 0); void popFromGroup(); void ungroup(bool skip_undo = false); diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index 6508c9093feaf3722404d2d28b79252d7e815059..2d192979136ec408977640882e9f4dfff489d1d9 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -781,7 +781,7 @@ void sp_edit_invert_in_all_layers(SPDesktop *desktop) sp_edit_select_all_full(desktop, true, true); } -Inkscape::XML::Node* ObjectSet::group() { +Inkscape::XML::Node* ObjectSet::group(int type) { SPDocument *doc = document(); if(!doc) return nullptr; @@ -790,7 +790,7 @@ Inkscape::XML::Node* ObjectSet::group() { return nullptr; } Inkscape::XML::Document *xml_doc = doc->getReprDoc(); - Inkscape::XML::Node *group = xml_doc->createElement("svg:g"); + Inkscape::XML::Node *group = (type == 0) ? xml_doc->createElement("svg:g") : xml_doc->createElement("svg:a"); std::vector p(xmlNodes().begin(), xmlNodes().end()); std::sort(p.begin(), p.end(), sp_repr_compare_position_bool); @@ -851,7 +851,11 @@ Inkscape::XML::Node* ObjectSet::group() { topmost_parent->addChildAtPos(group, topmost + 1); set(doc->getObjectByRepr(group)); - DocumentUndo::done(doc, _("Group"), INKSCAPE_ICON("object-group")); + if (type == 0) { + DocumentUndo::done(doc, _("Group"), INKSCAPE_ICON("object-group")); + } else { + DocumentUndo::done(doc, _("Anchor"), INKSCAPE_ICON("object-group")); + } return group; } diff --git a/src/ui/contextmenu.cpp b/src/ui/contextmenu.cpp index 91d660971d4312d2c84b48675e86f268450626cc..5a10ead402a40047f38f393bcdaa407a97d87135 100644 --- a/src/ui/contextmenu.cpp +++ b/src/ui/contextmenu.cpp @@ -4,6 +4,9 @@ * Context menu */ /* Authors: + * Tavmjong Bah + * + * Rewrite of code authored by: * Lauris Kaplinski * Frank Felfe * bulia byak @@ -11,6 +14,7 @@ * Abhishek Sharma * Kris De Gussem * + * Copyright (C) 2022 Tavmjong Bah * Copyright (C) 2012 Kris De Gussem * Copyright (C) 2010 authors * Copyright (C) 1999-2005 authors @@ -22,995 +26,284 @@ #include "contextmenu.h" -#include #include -#include - -#include -#include -#include -#include #include "desktop.h" #include "document.h" -#include "document-undo.h" -#include "inkscape.h" #include "layer-manager.h" -#include "message-context.h" -#include "message-stack.h" #include "selection.h" -#include "selection-chemistry.h" -#include "shortcuts.h" -#include "verbs.h" - -#include "helper/action-context.h" -#include "helper/action.h" - -#include "include/gtkmm_version.h" - -#include "live_effects/lpe-powerclip.h" -#include "live_effects/lpe-powermask.h" #include "object/sp-anchor.h" -#include "object/sp-clippath.h" #include "object/sp-image.h" -#include "object/sp-mask.h" #include "object/sp-shape.h" #include "object/sp-text.h" #include "ui/desktop/menu-icon-shift.h" -//#include "ui/dialog/dialog-manager.h" -#include "ui/dialog/dialog-container.h" -#include "ui/dialog/layer-properties.h" -#include "ui/icon-loader.h" -#include "ui/icon-names.h" -#include "ui/shortcuts.h" - -using Inkscape::DocumentUndo; -static bool temporarily_block_actions = false; - -ContextMenu::ContextMenu(SPDesktop *desktop, SPItem *item) : - _item(item), - MIGroup(), - MIParent(_("Go to parent")) +ContextMenu::ContextMenu(SPDesktop *desktop, SPItem *item, bool hide_layers_and_objects_menu_item) { set_name("ContextMenu"); - _object = static_cast(item); - _desktop = desktop; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - _show_icons = prefs->getInt("/theme/menuIcons_canvas", true); - - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_UNDO)); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_REDO)); - AddSeparator(); - - auto layer = Inkscape::LayerManager::asLayer(_object); - if (!layer) { - // don't bother adding copy/paste/cut when clicking on a layer; it won't work unless layer is *selected* - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_CUT)); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_COPY)); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_PASTE)); - AddSeparator(); - } - // when clicking an a current layer, show layer-specific duplicate action; - // layer has it's own "duplicate" action which varies from "selection duplicate" for better or worse - auto duplicate = layer ? SP_VERB_LAYER_DUPLICATE : SP_VERB_EDIT_DUPLICATE; - AppendItemFromVerb(Inkscape::Verb::get(duplicate)); - // layer-specific delete action - auto del = layer ? SP_VERB_LAYER_DELETE : SP_VERB_EDIT_DELETE; - AppendItemFromVerb(Inkscape::Verb::get(del)); - - positionOfLastDialog = 10; // 9 in front + 1 for the separator in the next if; used to position the dialog menu entries below each other - /* Item menu */ - if (item!=nullptr) { - AddSeparator(); - MakeObjectMenu(); - } - AddSeparator(); - /* Lock/Unock Hide/Unhide*/ - auto point_doc = _desktop->point() * _desktop->dt2doc(); - Geom::Rect b(point_doc, point_doc + Geom::Point(1, 1)); - std::vector< SPItem * > down_items = _desktop->getDocument()->getItemsPartiallyInBox( _desktop->dkey, b, true, true, true, true); - bool has_down_hidden = false; - bool has_down_locked = false; - for(auto & down_item : down_items){ - if(down_item->isHidden()) { - has_down_hidden = true; - } - if(down_item->isLocked()) { - has_down_locked = true; - } - } - Gtk::MenuItem* mi; - - mi = Gtk::manage(new Gtk::MenuItem(_("Hide Selected Objects"),true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::HideSelected)); - if (_desktop->selection->isEmpty()) { - mi->set_sensitive(false); - } - mi->show(); - append(*mi);//insert(*mi,positionOfLastDialog++); - - mi = Gtk::manage(new Gtk::MenuItem(_("Unhide Objects Below"),true)); - mi->signal_activate().connect(sigc::bind >(sigc::mem_fun(*this, &ContextMenu::UnHideBelow), down_items)); - if (!has_down_hidden) { - mi->set_sensitive(false); - } - mi->show(); - append(*mi);//insert(*mi,positionOfLastDialog++); - - mi = Gtk::manage(new Gtk::MenuItem(_("Lock Selected Objects"),true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::LockSelected)); - if (_desktop->selection->isEmpty()) { - mi->set_sensitive(false); - } - mi->show(); - append(*mi);//insert(*mi,positionOfLastDialog++); - - mi = Gtk::manage(new Gtk::MenuItem(_("Unlock Objects Below"),true)); - mi->signal_activate().connect(sigc::bind >(sigc::mem_fun(*this, &ContextMenu::UnLockBelow), down_items)); - if (!has_down_locked) { - mi->set_sensitive(false); - } - mi->show(); - append(*mi);//insert(*mi,positionOfLastDialog++); - - // Install CSS to shift icons into the space reserved for toggles (i.e. check and radio items). - signal_map().connect(sigc::bind(sigc::ptr_fun(shift_icons), this)); - - // Set the style and icon theme of the new menu based on the desktop - if (Gtk::Window *window = _desktop->getToplevel()) { - if (window->get_style_context()->has_class("dark")) { - get_style_context()->add_class("dark"); - } else { - get_style_context()->add_class("bright"); - } - if (prefs->getBool("/theme/symbolicIcons", false)) { - get_style_context()->add_class("symbolic"); - } else { - get_style_context()->add_class("regular"); - } - } -} - -ContextMenu::~ContextMenu(void) -= default; - -Gtk::SeparatorMenuItem* ContextMenu::AddSeparator() -{ - Gtk::SeparatorMenuItem* sep = Gtk::manage(new Gtk::SeparatorMenuItem()); - sep->show(); - append(*sep); - return sep; -} - -void ContextMenu::EnterGroup(Gtk::MenuItem* mi) -{ - _desktop->layerManager().setCurrentLayer(_MIGroup_group); - _desktop->selection->clear(); -} - -void ContextMenu::LeaveGroup() -{ - _desktop->layerManager().setCurrentLayer(_desktop->layerManager().currentLayer()->parent); -} - -void ContextMenu::LockSelected() -{ - auto itemlist = _desktop->selection->items(); - for(auto i=itemlist.begin();i!=itemlist.end(); ++i) { - (*i)->setLocked(true); - } -} - -void ContextMenu::HideSelected() -{ - auto itemlist =_desktop->selection->items(); - for(auto i=itemlist.begin();i!=itemlist.end(); ++i) { - (*i)->setHidden(true); - } -} - -void ContextMenu::UnLockBelow(std::vector items) -{ - _desktop->selection->clear(); - for(auto & item : items) { - if (item->isLocked()) { - item->setLocked(false); - _desktop->selection->add(item); - } - } -} - -void ContextMenu::UnHideBelow(std::vector items) -{ - _desktop->selection->clear(); - for(auto & item : items) { + // std::cout << "ContextMenu::ContextMenu: " << (item ? item->getId() : "no item") << std::endl; + action_group = Gio::SimpleActionGroup::create(); + insert_action_group("ctx", action_group); + auto document = desktop->getDocument(); + action_group->add_action("unhide-objects-below-cursor", sigc::bind(sigc::mem_fun(this, &ContextMenu::unhide_or_unlock), document, true)); + action_group->add_action("unlock-objects-below-cursor", sigc::bind(sigc::mem_fun(this, &ContextMenu::unhide_or_unlock), document, false)); + + auto gmenu = Gio::Menu::create(); // Main menu + auto gmenu_section = Gio::Menu::create(); // Section (used multiple times) + + auto layer = Inkscape::LayerManager::asLayer(item); // Layers have their own context menu in the Object and Layers dialog. + auto root = desktop->layerManager().currentRoot(); + + // "item" is the object that was under the mouse when right-clicked. It determines what is shown + // in the menu thus it makes the most sense that it is either selected or part of the current + // selection. + auto selection = desktop->selection; + if (item && !selection->includes(item)) { + selection->set(item); + } + + // Get a list of items under the cursor, used for unhiding and unlocking. + auto point_document = desktop->point() * desktop->dt2doc(); + Geom::Rect b(point_document, point_document + Geom::Point(1, 1)); // Seems strange to use a rect! + items_under_cursor = document->getItemsPartiallyInBox(desktop->dkey, b, true, true, true, true); + bool has_hidden_below_cursor = false; + bool has_locked_below_cursor = false; + for (auto item : items_under_cursor) { if (item->isHidden()) { - item->setHidden(false); - _desktop->selection->add(item); + has_hidden_below_cursor = true; } - } -} - -/* - * Some day when the right-click menus are ready to start working - * smarter with the verbs, we'll need to change this NULL being - * sent to sp_action_perform to something useful, or set some kind - * of global "right-clicked position" variable for actions to - * investigate when they're called. - */ -static void -context_menu_item_on_my_activate(void */*object*/, SPAction *action) -{ - if (!temporarily_block_actions) { - sp_action_perform(action, nullptr); - } -} - -static void -context_menu_item_on_my_select(void */*object*/, SPAction *action) -{ - sp_action_get_view(action)->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip); -} - -static void -context_menu_item_on_my_deselect(void */*object*/, SPAction *action) -{ - sp_action_get_view(action)->tipsMessageContext()->clear(); -} - - -// TODO: Update this to allow radio items to be used -void ContextMenu::AppendItemFromVerb(Inkscape::Verb *verb) -{ - SPAction *action; - SPDesktop *view = _desktop; - - if (verb->get_code() == SP_VERB_NONE) { - Gtk::MenuItem *item = AddSeparator(); - item->show(); - append(*item); - } else { - action = verb->get_action(Inkscape::ActionContext(view)); - if (!action) { - return; - } - // Create the menu item itself - auto const item = Gtk::manage(new Gtk::MenuItem()); - - // Now create the label and add it to the menu item (with mnemonic) - auto const label = Gtk::manage(new Gtk::AccelLabel(action->name, true)); - label->set_xalign(0.0); - Inkscape::Shortcuts::getInstance().add_accelerator(item, action->verb); - label->set_accel_widget(*item); - - // If there is an image associated with the action, then we can add it as an icon for the menu item - if (_show_icons && action->image) { - item->set_name("ImageMenuItem"); // custom name to identify our "ImageMenuItems" - auto const icon = Gtk::manage(sp_get_icon_image(action->image, Gtk::ICON_SIZE_MENU)); - - // create a box to hold icon and label as GtkMenuItem derives from GtkBin and can only hold one child - auto const box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); - box->pack_start(*icon, false, false, 0); - box->pack_start(*label, true, true, 0); - - item->add(*box); - } else { - item->add(*label); - } - - action->signal_set_sensitive.connect(sigc::mem_fun(*this, &ContextMenu::set_sensitive)); - action->signal_set_name.connect(sigc::mem_fun(*item, &ContextMenu::set_name)); - - if (!action->sensitive) { - item->set_sensitive(FALSE); + if (item->isLocked()) { + has_locked_below_cursor = true; } - - item->set_events(Gdk::KEY_PRESS_MASK); - item->signal_activate().connect(sigc::bind(sigc::ptr_fun(context_menu_item_on_my_activate),item,action)); - item->signal_select().connect(sigc::bind(sigc::ptr_fun(context_menu_item_on_my_select),item,action)); - item->signal_deselect().connect(sigc::bind(sigc::ptr_fun(context_menu_item_on_my_deselect),item,action)); - item->show_all(); - - append(*item); } -} + // std::cout << "Items below cursor: " << items_under_cursor.size() + // << " hidden: " << std::boolalpha << has_hidden_below_cursor + // << " locked: " << std::boolalpha << has_locked_below_cursor + // << std::endl; -void ContextMenu::MakeObjectMenu() -{ - if (SP_IS_ITEM(_object)) { - MakeItemMenu(Inkscape::LayerManager::asLayer(_object)); - } + // clang-tidy off - if (SP_IS_ITEM(_object)) { - MakeGroupMenu(SP_ITEM(_object)); - } - - if (SP_IS_ANCHOR(_object)) { - MakeAnchorMenu(); - } - - if (SP_IS_IMAGE(_object)) { - MakeImageMenu(); - } - - if (SP_IS_SHAPE(_object)) { - MakeShapeMenu(); - } - - if (SP_IS_TEXT(_object)) { - MakeTextMenu(); - } -} + // Undo/redo + // gmenu_section = Gio::Menu::create(); + // AppendItemFromAction(gmenu_section, "doc.undo", _("Undo"), "edit-undo"); + // AppendItemFromAction(gmenu_section, "doc.redo", _("Redo"), "edit-redo"); + // gmenu->append_section(gmenu_section); -void ContextMenu::MakeItemMenu(SPGroup* layer) -{ - Gtk::MenuItem* mi; - - /* Item dialog */ - mi = Gtk::manage(new Gtk::MenuItem(_("_Object Properties..."),true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ItemProperties)); - mi->show(); - append(*mi);//insert(*mi,positionOfLastDialog++); - - AddSeparator(); - - /* Select item */ - if (Inkscape::Verb::getbyid( "org.inkscape.follow_link" )) { - mi = Gtk::manage(new Gtk::MenuItem(_("_Select This"), true)); - if (_desktop->selection->includes(_item)) { - mi->set_sensitive(FALSE); - } else { - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ItemSelectThis)); - } - mi->show(); - append(*mi); - } - - // several options are not applicable to layers; adding them makes context menu unwieldy, - // so we need to be selective, or else it grows too much if (!layer) { - mi = Gtk::manage(new Gtk::MenuItem(_("Select Same"))); - mi->show(); - Gtk::Menu *select_same_submenu = Gtk::manage(new Gtk::Menu()); - if (_desktop->selection->isEmpty()) { - mi->set_sensitive(FALSE); - } - mi->set_submenu(*select_same_submenu); - append(*mi); - - /* Select same fill and stroke */ - mi = Gtk::manage(new Gtk::MenuItem(_("Fill and Stroke"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameFillStroke)); - mi->set_sensitive(!SP_IS_ANCHOR(_item)); - mi->show(); - select_same_submenu->append(*mi); - /* Select same fill color */ - mi = Gtk::manage(new Gtk::MenuItem(_("Fill Color"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameFillColor)); - mi->set_sensitive(!SP_IS_ANCHOR(_item)); - mi->show(); - select_same_submenu->append(*mi); + gmenu_section = Gio::Menu::create(); + AppendItemFromAction(gmenu_section, "app.cut", _("Cu_t"), "edit-cut"); + AppendItemFromAction(gmenu_section, "app.copy", _("_Copy"), "edit-copy"); + AppendItemFromAction(gmenu_section, "win.paste", _("_Paste"), "edit-paste"); + gmenu->append_section(gmenu_section); + + gmenu_section = Gio::Menu::create(); + AppendItemFromAction(gmenu_section, "app.duplicate", _("Duplic_ate"), "edit-duplicate"); + AppendItemFromAction(gmenu_section, "app.delete", _("_Delete"), "edit-delete"); + gmenu->append_section(gmenu_section); + + if (item) { + + // Dialogs + auto gmenu_dialogs = Gio::Menu::create(); + if (!hide_layers_and_objects_menu_item) { // Hidden when context menu is popped up in Layers and Objects dialog! + AppendItemFromAction(gmenu_dialogs, "win.dialog-open('Objects')", _("Layers and Objects..."), "dialog-objects" ); + } + AppendItemFromAction(gmenu_dialogs, "win.dialog-open('ObjectProperties')", _("_Object Properties..."), "dialog-object-properties" ); + + if (dynamic_cast(item) || dynamic_cast(item) || dynamic_cast(item)) { + AppendItemFromAction(gmenu_dialogs, "win.dialog-open('FillStroke')", _("_Fill and Stroke..."), "dialog-fill-and-stroke" ); + } + + // Image dialogs (mostly). + if (dynamic_cast(item)) { + AppendItemFromAction( gmenu_dialogs, "win.dialog-open('ObjectAttributes')", _("Image _Properties..."), "dialog-fill-and-stroke"); + AppendItemFromAction( gmenu_dialogs, "win.dialog-open('Trace')", _("_Trace Bitmap..."), "bitmap-trace" ); + auto image = dynamic_cast(item); + if (strncmp(image->href, "data", 4) == 0) { + // Image is embeded. + AppendItemFromAction( gmenu_dialogs, "app.org.inkscape.filter.extract_image", _("Extract Image..."), "" ); + } else { + // Image is linked. + AppendItemFromAction( gmenu_dialogs, "app.org.inkscape.filter.selected.embed_image", _("Embed Image"), "" ); + AppendItemFromAction( gmenu_dialogs, "app.element-image-edit", _("Edit Externally..."), "" ); + } + } + + // Text dialogs. + if (dynamic_cast(item)) { + AppendItemFromAction( gmenu_dialogs, "win.dialog-open('Text')", _("_Text and Font..."), "dialog-text-and-font" ); + AppendItemFromAction( gmenu_dialogs, "win.dialog-open('Spellcheck')", _("Check Spellin_g..."), "tools-check-spelling" ); + } + gmenu->append_section(gmenu_dialogs); // We might add to it later... + + if (!dynamic_cast(item)) { + // Item menu + + // Selection + gmenu_section = Gio::Menu::create(); + auto gmenu_submenu = Gio::Menu::create(); + AppendItemFromAction( gmenu_submenu, "win.select-same-fill-and-stroke", _("Fill _and Stroke"), "edit-select-same-fill-and-stroke"); + AppendItemFromAction( gmenu_submenu, "win.select-same-fill", _("_Fill Color"), "edit-select-same-fill" ); + AppendItemFromAction( gmenu_submenu, "win.select-same-stroke-color", _("_Stroke Color"), "edit-select-same-stroke-color" ); + AppendItemFromAction( gmenu_submenu, "win.select-same-stroke-style", _("Stroke St_yle"), "edit-select-same-stroke-style" ); + AppendItemFromAction( gmenu_submenu, "win.select-same-object-type", _("_Object Type"), "edit-select-same-object-type" ); + gmenu_section->append_submenu(_("Select Sa_me"), gmenu_submenu); + gmenu->append_section(gmenu_section); + + // Groups and Layers + gmenu_section = Gio::Menu::create(); + AppendItemFromAction( gmenu_section, "win.selection-move-to-layer", _("_Move to Layer..."), "" ); + AppendItemFromAction( gmenu_section, "app.selection-link", _("Create anchor (hyperlink)"), "" ); + AppendItemFromAction( gmenu_section, "app.selection-group", _("_Group"), "" ); + if (dynamic_cast(item)) { + AppendItemFromAction( gmenu_section, "app.selection-ungroup", _("_Ungroup"), "" ); + Glib::ustring label = Glib::ustring::compose(_("Enter group %1"), item->defaultLabel()); + AppendItemFromAction( gmenu_section, "win.selection-group-enter", label, "" ); + if (item->getParentGroup()->isLayer() || item->getParentGroup() == root) { + // A layer should be a child of root or another layer. + AppendItemFromAction( gmenu_section, "win.layer-from-group", _("Group to Layer"), "" ); + } + } + auto group = dynamic_cast(item->parent); + if (group && !group->isLayer()) { + AppendItemFromAction( gmenu_section, "win.selection-group-exit", _("Exit group"), "" ); + AppendItemFromAction( gmenu_section, "app.selection-ungroup-pop", _("_Pop selection out of group"), "" ); + } + gmenu->append_section(gmenu_section); + + // Clipping and Masking + gmenu_section = Gio::Menu::create(); + if (selection->size() > 1) { + AppendItemFromAction( gmenu_section, "app.object-set-clip", _("Set Cl_ip"), "" ); + } + if (item->getClipObject()) { + AppendItemFromAction( gmenu_section, "app.object-release-clip", _("Release C_lip"), "" ); + } else { + AppendItemFromAction( gmenu_section, "app.object-set-clip-group", _("Set Clip G_roup"), "" ); + } + if (selection->size() > 1) { + AppendItemFromAction( gmenu_section, "app.object-set-mask", _("Set Mask"), "" ); + } + if (item->getMaskObject()) { + AppendItemFromAction( gmenu_section, "app.object-release-mask", _("Release Mask"), "" ); + } + gmenu->append_section(gmenu_section); + + // Hide and Lock + gmenu_section = Gio::Menu::create(); + AppendItemFromAction( gmenu_section, "app.selection-hide", _("Hide Selected Objects"), "" ); + AppendItemFromAction( gmenu_section, "app.selection-lock", _("Lock Selected Objects"), "" ); + gmenu->append_section(gmenu_section); + + } else { + // Anchor menu + gmenu_section = Gio::Menu::create(); + AppendItemFromAction( gmenu_section, "win.dialog-open('ObjectAttributes')", _("Link _Properties..."), "" ); + AppendItemFromAction( gmenu_section, "app.element-a-open-link", _("_Open link in browser"), "" ); + AppendItemFromAction( gmenu_section, "app.selection-ungroup", _("_Remove Link"), "" ); + AppendItemFromAction( gmenu_section, "win.selection-group-enter", _("Enter Group"), "" ); + gmenu->append_section(gmenu_section); + } + } + + // Hidden or locked beneath cursor + gmenu_section = Gio::Menu::create(); + if (has_hidden_below_cursor) { + AppendItemFromAction( gmenu_section, "ctx.unhide-objects-below-cursor", _("Unhide Objects Below Cursor"), "" ); + } + if (has_locked_below_cursor) { + AppendItemFromAction( gmenu_section, "ctx.unlock-objects-below-cursor", _("Unlock Objects Below Cursor"), "" ); + } + gmenu->append_section(gmenu_section); - /* Select same stroke color */ - mi = Gtk::manage(new Gtk::MenuItem(_("Stroke Color"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameStrokeColor)); - mi->set_sensitive(!SP_IS_ANCHOR(_item)); - mi->show(); - select_same_submenu->append(*mi); - - /* Select same stroke style */ - mi = Gtk::manage(new Gtk::MenuItem(_("Stroke Style"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameStrokeStyle)); - mi->set_sensitive(!SP_IS_ANCHOR(_item)); - mi->show(); - select_same_submenu->append(*mi); - - /* Select same stroke style */ - mi = Gtk::manage(new Gtk::MenuItem(_("Object Type"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameObjectType)); - mi->set_sensitive(!SP_IS_ANCHOR(_item)); - mi->show(); - select_same_submenu->append(*mi); - } - - /* Move to layer */ - mi = Gtk::manage(new Gtk::MenuItem(_("_Move to Layer..."), true)); - if (_desktop->selection->isEmpty()) { - mi->set_sensitive(FALSE); } else { - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ItemMoveTo)); - } - mi->show(); - append(*mi); - - if (!layer) { - /* Create link */ - mi = Gtk::manage(new Gtk::MenuItem(_("Create _Link"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ItemCreateLink)); - mi->set_sensitive(!SP_IS_ANCHOR(_item)); - mi->show(); - append(*mi); - - bool ClipRefOK=false; - bool MaskRefOK=false; - if (_item && _item->getClipObject()) { - ClipRefOK = true; - } - if (_item && _item->getMaskObject()) { - MaskRefOK = true; - } - /* Set mask */ - mi = Gtk::manage(new Gtk::MenuItem(_("Set Mask"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SetMask)); - if (ClipRefOK || MaskRefOK) { - mi->set_sensitive(FALSE); - } else { - mi->set_sensitive(TRUE); - } - mi->show(); - append(*mi); + // Layers: Only used in "Layers and Objects" dialog. - /* Release mask */ - mi = Gtk::manage(new Gtk::MenuItem(_("Release Mask"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ReleaseMask)); - if (MaskRefOK) { - mi->set_sensitive(TRUE); - } else { - mi->set_sensitive(FALSE); - } - mi->show(); - append(*mi); + gmenu_section = Gio::Menu::create(); + AppendItemFromAction(gmenu_section, "win.layer-new", _("_Add Layer..."), "layer-new"); + AppendItemFromAction(gmenu_section, "win.layer-duplicate", _("D_uplicate Layer"), "layer-duplicate"); + AppendItemFromAction(gmenu_section, "win.layer-delete", _("_Delete Layer"), "layer-delete"); + AppendItemFromAction(gmenu_section, "win.layer-rename", _("Re_name Layer..."), "layer-rename"); + AppendItemFromAction(gmenu_section, "win.layer-to-group", _("Layer to _Group"), "dialog-objects"); + gmenu->append_section(gmenu_section); - /*SSet Clip Group */ - mi = Gtk::manage(new Gtk::MenuItem(_("Create Clip G_roup"),true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::CreateGroupClip)); - mi->set_sensitive(TRUE); - mi->show(); - append(*mi); + gmenu_section = Gio::Menu::create(); + AppendItemFromAction(gmenu_section, "win.layer-raise", _("_Raise Layer"), "layer-raise"); + AppendItemFromAction(gmenu_section, "win.layer-lower", _("_Lower Layer"), "layer-lower"); + gmenu->append_section(gmenu_section); - /* Set Clip */ - mi = Gtk::manage(new Gtk::MenuItem(_("Set Cl_ip"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SetClip)); - if (ClipRefOK || MaskRefOK) { - mi->set_sensitive(FALSE); - } else { - mi->set_sensitive(TRUE); - } - mi->show(); - append(*mi); + gmenu_section = Gio::Menu::create(); + AppendItemFromAction(gmenu_section, "win.layer-hide-toggle-others", _("_Hide/show other layers"), ""); + AppendItemFromAction(gmenu_section, "win.layer-hide-all", _("_Hide all layers"), ""); + AppendItemFromAction(gmenu_section, "win.layer-unhide-all", _("_Show all layers"), ""); + gmenu->append_section(gmenu_section); - /* Release Clip */ - mi = Gtk::manage(new Gtk::MenuItem(_("Release C_lip"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ReleaseClip)); - if (ClipRefOK) { - mi->set_sensitive(TRUE); - } else { - mi->set_sensitive(FALSE); - } - mi->show(); - append(*mi); + gmenu_section = Gio::Menu::create(); + AppendItemFromAction(gmenu_section, "win.layer-lock-toggle-others", _("_Lock/unlock other layers"), ""); + AppendItemFromAction(gmenu_section, "win.layer-lock-all", _("_Lock all layers"), ""); + AppendItemFromAction(gmenu_section, "win.layer-unlock-all", _("_Unlock all layers"), ""); + gmenu->append_section(gmenu_section); - /* Group */ - mi = Gtk::manage(new Gtk::MenuItem(_("_Group"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ActivateGroup)); - if (_desktop->selection->isEmpty()) { - mi->set_sensitive(FALSE); - } else { - mi->set_sensitive(TRUE); - } - mi->show(); - append(*mi); } -} - -void ContextMenu::SelectSameFillStroke() -{ - sp_select_same_fill_stroke_style(_desktop, true, true, true); -} - -void ContextMenu::SelectSameFillColor() -{ - sp_select_same_fill_stroke_style(_desktop, true, false, false); -} - -void ContextMenu::SelectSameStrokeColor() -{ - sp_select_same_fill_stroke_style(_desktop, false, true, false); -} + // clang-tidy on -void ContextMenu::SelectSameStrokeStyle() -{ - sp_select_same_fill_stroke_style(_desktop, false, false, true); -} - -void ContextMenu::SelectSameObjectType() -{ - sp_select_same_object_type(_desktop); -} + bind_model(gmenu, true); -void ContextMenu::ItemProperties() -{ - _desktop->selection->set(_item); - _desktop->getContainer()->new_dialog("ObjectProperties"); -} - -void ContextMenu::ItemSelectThis() -{ - _desktop->selection->set(_item); -} -void ContextMenu::ItemMoveTo() -{ - Inkscape::UI::Dialogs::LayerPropertiesDialog::showMove(_desktop, _desktop->layerManager().currentLayer()); -} - - - -void ContextMenu::ItemCreateLink() -{ - Inkscape::XML::Document *xml_doc = _desktop->doc()->getReprDoc(); - Inkscape::XML::Node *repr = xml_doc->createElement("svg:a"); - _item->parent->getRepr()->addChild(repr, _item->getRepr()); - SPObject *object = _item->document->getObjectByRepr(repr); - g_return_if_fail(SP_IS_ANCHOR(object)); - - const char *id = _item->getRepr()->attribute("id"); - Inkscape::XML::Node *child = _item->getRepr()->duplicate(xml_doc); - _item->deleteObject(false); - repr->addChild(child, nullptr); - child->setAttribute("id", id); - - Inkscape::GC::release(repr); - Inkscape::GC::release(child); - - Inkscape::DocumentUndo::done(object->document, _("Create link"), ""); - - _desktop->selection->set(SP_ITEM(object)); - _desktop->getContainer()->new_dialog("ObjectAttributes"); -} - -void ContextMenu::SetMask() -{ - _desktop->selection->setMask(false, false); -} - -void ContextMenu::ReleaseMask() -{ - Inkscape::LivePathEffect::sp_remove_powermask(_desktop->selection); - _desktop->selection->unsetMask(false); -} - -void ContextMenu::CreateGroupClip() -{ - _desktop->selection->setClipGroup(); -} - -void ContextMenu::SetClip() -{ - _desktop->selection->setMask(true, false); -} - - -void ContextMenu::ReleaseClip() -{ - Inkscape::LivePathEffect::sp_remove_powerclip(_desktop->selection); - _desktop->selection->unsetMask(true); -} - -void sp_group_layer_transform(SPDocument* document, SPGroup* group, SPGroup::LayerMode mode) { - if (!group) return; - - group->setLayerMode(mode); - group->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT); - if (document) { - DocumentUndo::done(document, mode == SPGroup::GROUP ? _("Layer to group") : _("Group to layer"), INKSCAPE_ICON("dialog-objects")); - } -} - -Glib::SignalProxy ContextMenu::append_item(const char* label, bool mnemonic) { - auto mi = Gtk::make_managed(label, mnemonic); - mi->show(); - append(*mi); - return mi->signal_activate(); -} - -void ContextMenu::fireAction(unsigned int code) { - if (Inkscape::Verb* verb = Inkscape::Verb::get(code)) { - if (SPAction* action = verb->get_action(Inkscape::ActionContext(_desktop))) { - sp_action_perform(action, nullptr); - } - } -} - -// group and layer menu -void ContextMenu::MakeGroupMenu(SPItem* item) { - auto group = dynamic_cast(item); - auto root = _desktop->layerManager().currentRoot(); - - if (group && group->isLayer()) { - // layer-specific commands - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_LAYER_NEW)); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_LAYER_RENAME)); - AddSeparator(); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_LAYER_SOLO)); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_LAYER_SHOW_ALL)); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_LAYER_HIDE_ALL)); - AddSeparator(); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_LAYER_LOCK_OTHERS)); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_LAYER_LOCK_ALL)); - AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_LAYER_UNLOCK_ALL)); - AddSeparator(); - - // transform layer into group - append_item(_("Layer to group"), false).connect([=]() { sp_group_layer_transform(_desktop->doc(), group, SPGroup::GROUP); }); - } - else if (group) { - /* Ungroup */ - append_item(_("_Ungroup"), true).connect(sigc::mem_fun(*this, &ContextMenu::ActivateUngroup)); - - if (item->getParentGroup()->isLayer() || item->getParentGroup() == root) { - // transform group into layer - append_item(_("Group to layer"), false).connect([=](){ sp_group_layer_transform(_desktop->doc(), group, SPGroup::LAYER); }); - } - - // enter group - auto layer = _desktop->layerManager().currentLayer(); - if (item != layer) { - MIGroup.set_label(Glib::ustring::compose(_("Enter group %1"), item->defaultLabel())); - _MIGroup_group = item; - MIGroup.signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &ContextMenu::EnterGroup),&MIGroup)); - MIGroup.show(); - append(MIGroup); - } - } - - auto layer = _desktop->layerManager().currentLayer(); - if (layer != root) { - if (layer->parent != root) { - MIParent.signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::LeaveGroup)); - MIParent.show(); - append(MIParent); - - /* Pop selection out of group */ - Gtk::MenuItem* miu = Gtk::manage(new Gtk::MenuItem(_("_Pop selection out of group"), true)); - miu->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ActivateUngroupPopSelection)); - miu->show(); - append(*miu); - } - } -} - -void ContextMenu::ActivateGroup() -{ - _desktop->selection->group(); -} - -void ContextMenu::ActivateUngroup() -{ - std::vector children; - - sp_item_group_ungroup(static_cast(_item), children); - _desktop->selection->setList(children); -} - -void ContextMenu::ActivateUngroupPopSelection() -{ - _desktop->selection->popFromGroup(); -} - - -void ContextMenu::MakeAnchorMenu() -{ - Gtk::MenuItem* mi; - - /* Link dialog */ - mi = Gtk::manage(new Gtk::MenuItem(_("Link _Properties..."), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::AnchorLinkProperties)); - mi->show(); - insert(*mi,positionOfLastDialog++); - - /* Select item */ - mi = Gtk::manage(new Gtk::MenuItem(_("_Follow Link"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::AnchorLinkFollow)); - mi->show(); - append(*mi); - - /* Reset transformations */ - mi = Gtk::manage(new Gtk::MenuItem(_("_Remove Link"), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::AnchorLinkRemove)); - mi->show(); - append(*mi); -} - -void ContextMenu::AnchorLinkProperties() -{ - _desktop->getContainer()->new_dialog("ObjectAttributes"); -} - -void ContextMenu::AnchorLinkFollow() -{ - - if (_desktop->selection->isEmpty()) { - _desktop->selection->set(_item); - } - // Opening the selected links with a python extension - Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.inkscape.follow_link" ); - if (verb) { - SPAction *action = verb->get_action(Inkscape::ActionContext(_desktop)); - if (action) { - sp_action_perform(action, nullptr); - } - } -} - -void ContextMenu::AnchorLinkRemove() -{ - std::vector children; - sp_item_group_ungroup(static_cast(_item), children, false); - Inkscape::DocumentUndo::done(_desktop->doc(), _("Remove link"), ""); -} - -void ContextMenu::MakeImageMenu () -{ - Gtk::MenuItem* mi; - Inkscape::XML::Node *ir = _object->getRepr(); - const gchar *href = ir->attribute("xlink:href"); - - /* Image properties */ - mi = Gtk::manage(new Gtk::MenuItem(_("Image _Properties..."), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageProperties)); - mi->show(); - insert(*mi,positionOfLastDialog++); - - /* Edit externally */ - mi = Gtk::manage(new Gtk::MenuItem(_("Edit Externally..."), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageEdit)); - mi->show(); - insert(*mi,positionOfLastDialog++); - if ( (!href) || ((strncmp(href, "data:", 5) == 0)) ) { - mi->set_sensitive( FALSE ); - } - - /* Trace Bitmap */ - mi = Gtk::manage(new Gtk::MenuItem(_("_Trace Bitmap..."), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageTraceBitmap)); - mi->show(); - insert(*mi,positionOfLastDialog++); - if (_desktop->selection->isEmpty()) { - mi->set_sensitive(FALSE); - } - - /* Embed image */ - if (Inkscape::Verb::getbyid( "org.inkscape.filter.selected.embed_image" )) { - mi = Gtk::manage(new Gtk::MenuItem(C_("Context menu", "Embed Image"))); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageEmbed)); - mi->show(); - insert(*mi,positionOfLastDialog++); - if ( (!href) || ((strncmp(href, "data:", 5) == 0)) ) { - mi->set_sensitive( FALSE ); - } - } - - /* Extract image */ - if (Inkscape::Verb::getbyid( "org.inkscape.filter.extract_image" )) { - mi = Gtk::manage(new Gtk::MenuItem(C_("Context menu", "Extract Image..."))); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageExtract)); - mi->show(); - insert(*mi,positionOfLastDialog++); - if ( (!href) || ((strncmp(href, "data:", 5) != 0)) ) { - mi->set_sensitive( FALSE ); - } - } -} - -void ContextMenu::ImageProperties() -{ - _desktop->getContainer()->new_dialog("ObjectAttributes"); -} + // Install CSS to shift icons into the space reserved for toggles (i.e. check and radio items). + signal_map().connect(sigc::bind(sigc::ptr_fun(shift_icons), this)); -Glib::ustring ContextMenu::getImageEditorName(bool is_svg) { + // Set the style and icon theme of the new menu based on the desktop Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - Glib::ustring value; - if (!is_svg) { - Glib::ustring choices = prefs->getString("/options/bitmapeditor/value"); - if (!choices.empty()) { - value = choices; - } - else { - value = "gimp"; - } - } else { - Glib::ustring choices = prefs->getString("/options/svgeditor/value"); - if (!choices.empty()) { - value = choices; - } - else { - value = "inkscape"; - } - } - return value; -} - -void ContextMenu::ImageEdit() -{ - if (_desktop->selection->isEmpty()) { - _desktop->selection->set(_item); - } - - GError* errThing = nullptr; - Glib::ustring bmpeditor = getImageEditorName(); - Glib::ustring cmdline = bmpeditor; - std::string name; - std::string fullname; - -#ifdef _WIN32 - // g_spawn_command_line_sync parsing is done according to Unix shell rules, - // not Windows command interpreter rules. Thus we need to enclose the - // executable path with single quotes. - int index = cmdline.find(".exe"); - if ( index < 0 ) index = cmdline.find(".bat"); - if ( index < 0 ) index = cmdline.find(".com"); - if ( index >= 0 ) { - Glib::ustring editorBin = cmdline.substr(0, index + 4).c_str(); - Glib::ustring args = cmdline.substr(index + 4, cmdline.length()).c_str(); - editorBin.insert(0, "'"); - editorBin.append("'"); - cmdline = editorBin; - cmdline.append(args); - } else { - // Enclose the whole command line if no executable path can be extracted. - cmdline.insert(0, "'"); - cmdline.append("'"); - } -#endif - - auto itemlist= _desktop->selection->items(); - for(auto i=itemlist.begin();i!=itemlist.end();++i){ - Inkscape::XML::Node *ir = (*i)->getRepr(); - const gchar *href = ir->attribute("xlink:href"); - - if (strncmp (href,"file:",5) == 0) { - // URI to filename conversion - name = Glib::filename_from_uri(href); + if (Gtk::Window *window = desktop->getToplevel()) { + if (window->get_style_context()->has_class("dark")) { + get_style_context()->add_class("dark"); } else { - name.append(href); + get_style_context()->add_class("bright"); } - - if (Glib::path_is_absolute(name)) { - fullname = name; - } else if (SP_ACTIVE_DOCUMENT->getDocumentBase()) { - fullname = Glib::build_filename(SP_ACTIVE_DOCUMENT->getDocumentBase(), name); + if (prefs->getBool("/theme/symbolicIcons", false)) { + get_style_context()->add_class("symbolic"); } else { - fullname = Glib::build_filename(Glib::get_current_dir(), name); - } - if (name.substr(name.find_last_of(".") + 1) == "SVG" || - name.substr(name.find_last_of(".") + 1) == "svg" ) - { - cmdline.erase(0, bmpeditor.length()); - Glib::ustring svgeditor = getImageEditorName(true); - cmdline = svgeditor.append(cmdline); + get_style_context()->add_class("regular"); } - cmdline.append(" '"); - cmdline.append(fullname.c_str()); - cmdline.append("'"); - } - - //g_warning("##Command line: %s\n", cmdline.c_str()); - - g_spawn_command_line_async(cmdline.c_str(), &errThing); - - if ( errThing ) { - g_warning("Problem launching editor (%d). %s", errThing->code, errThing->message); - (_desktop->messageStack())->flash(Inkscape::ERROR_MESSAGE, errThing->message); - g_error_free(errThing); - errThing = nullptr; } } -void ContextMenu::ImageTraceBitmap() -{ - _desktop->getContainer()->new_dialog("Trace"); -} - -void ContextMenu::ImageEmbed() +void +ContextMenu::AppendItemFromAction(Glib::RefPtr gmenu, Glib::ustring action, Glib::ustring label, Glib::ustring icon) { - if (_desktop->selection->isEmpty()) { - _desktop->selection->set(_item); - } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool show_icons = prefs->getInt("/theme/menuIcons_canvas", true); - Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.inkscape.filter.selected.embed_image" ); - if (verb) { - SPAction *action = verb->get_action(Inkscape::ActionContext(_desktop)); - if (action) { - sp_action_perform(action, nullptr); - } + auto menu_item = Gio::MenuItem::create(label, action); + if (icon != "" && show_icons) { + auto _icon = Gio::Icon::create(icon); + menu_item->set_icon(_icon); } + gmenu->append_item(menu_item); } -void ContextMenu::ImageExtract() +void +ContextMenu::unhide_or_unlock(SPDocument* document, bool unhide) { - if (_desktop->selection->isEmpty()) { - _desktop->selection->set(_item); - } - - Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.inkscape.filter.extract_image" ); - if (verb) { - SPAction *action = verb->get_action(Inkscape::ActionContext(_desktop)); - if (action) { - sp_action_perform(action, nullptr); + for (auto item : items_under_cursor) { + if (unhide) { + if (item->isHidden()) { + item->setHidden(false); + } + } else { + if (item->isLocked()) { + item->setLocked(false); + } } } -} - -void ContextMenu::MakeShapeMenu () -{ - Gtk::MenuItem* mi; - - /* Item dialog */ - mi = Gtk::manage(new Gtk::MenuItem(_("_Fill and Stroke..."), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::FillSettings)); - mi->show(); - insert(*mi,positionOfLastDialog++); -} - -void ContextMenu::FillSettings() -{ - if (_desktop->selection->isEmpty()) { - _desktop->selection->set(_item); - } - - _desktop->getContainer()->new_dialog("FillStroke"); -} - -void ContextMenu::MakeTextMenu () -{ - Gtk::MenuItem* mi; - - /* Fill and Stroke dialog */ - mi = Gtk::manage(new Gtk::MenuItem(_("_Fill and Stroke..."), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::FillSettings)); - mi->show(); - insert(*mi,positionOfLastDialog++); - - /* Edit Text dialog */ - mi = Gtk::manage(new Gtk::MenuItem(_("_Text and Font..."), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::TextSettings)); - mi->show(); - insert(*mi,positionOfLastDialog++); - -#if WITH_GSPELL - /* Spellcheck dialog */ - mi = Gtk::manage(new Gtk::MenuItem(_("Check Spellin_g..."), true)); - mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SpellcheckSettings)); - mi->show(); - insert(*mi,positionOfLastDialog++); -#endif -} - -void ContextMenu::TextSettings () -{ - if (_desktop->selection->isEmpty()) { - _desktop->selection->set(_item); - } - - _desktop->getContainer()->new_dialog("Text"); -} - -void ContextMenu::SpellcheckSettings () -{ -#if WITH_GSPELL - if (_desktop->selection->isEmpty()) { - _desktop->selection->set(_item); - } - _desktop->getContainer()->new_dialog("Spellcheck"); -#endif + // We wouldn't be here if we didn't make a change. + Inkscape::DocumentUndo::done(document, (unhide ? _("Unhid objects") : _("Unlocked objects")), ""); } /* diff --git a/src/ui/contextmenu.h b/src/ui/contextmenu.h index d4c67cc2a775ee42e3dd4d7b51a89aed5acac141..d34676015e9c328aa1f148b8537c676aab776de9 100644 --- a/src/ui/contextmenu.h +++ b/src/ui/contextmenu.h @@ -6,208 +6,38 @@ * Context menu * * Authors: - * Lauris Kaplinski - * Frank Felfe - * Abhishek Sharma - * Kris De Gussem + * Tavmjong Bah * - * Copyright (C) 2012 Kris De Gussem - * Copyright (C) 1999-2002 authors - * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2022 Tavmjong Bah * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include + #include +#include class SPDesktop; +class SPDocument; class SPItem; -class SPGroup; -class SPObject; - -namespace Gtk { -class SeparatorMenuItem; -} - -namespace Inkscape { -class Verb; -} /** * Implements the Inkscape context menu. - * - * For the context menu implementation, the ContextMenu class stores the object - * that was selected in a private data member. This should be fairly safe to do - * and a pointer to the SPItem as well as SPObject class are kept. - * All callbacks of the context menu entries are implemented as private - * functions. - * - * @todo add callbacks to destroy the context menu when it is closed (=key or mouse button pressed out of the scope of the context menu) */ class ContextMenu : public Gtk::Menu { - public: - /** - * The ContextMenu constructor contains all code to create and show the - * menu entries (aka child widgets). - * - * @param desktop pointer to the desktop the user is currently working on. - * @param item SPItem pointer to the object selected at the time the ContextMenu is created. - */ - ContextMenu(SPDesktop *desktop, SPItem *item); - ~ContextMenu() override; - - private: - SPItem *_item; // pointer to the object selected at the time the ContextMenu is created - SPObject *_object; // pointer to the object selected at the time the ContextMenu is created - SPDesktop *_desktop; //pointer to the desktop the user was currently working on at the time the ContextMenu is created - bool _show_icons; - int positionOfLastDialog; - - Gtk::MenuItem MIGroup; //menu entry to enter a group - SPObject *_MIGroup_group; // Group to enter when MIGroup is activated - Gtk::MenuItem MIParent; //menu entry to leave a group - - void fireAction(unsigned int code); - Glib::SignalProxy append_item(const char* label, bool mnemonic); +public: + ContextMenu(SPDesktop *desktop, SPItem *item, bool hide_layers_and_objects_menu_item = false); + ~ContextMenu() override = default; - /** - * auxiliary function that adds a separator line in the context menu - */ - Gtk::SeparatorMenuItem* AddSeparator(); - - /** - * Appends a custom menu UI from a verb. - * - * c++ified version of sp_ui_menu_append_item. - * @see sp_ui_menu_append_item_from_verb and synchronize/drop that function when c++ifying other code in interface.cpp - * - * @param show_icon True if an icon should be displayed before the menu item's label - * - */ - void AppendItemFromVerb(Inkscape::Verb *verb); - - /** - * main function which is responsible for creating the context sensitive menu items, - * calls subfunctions below to create the menu entry widgets. - */ - void MakeObjectMenu (); - /** - * creates menu entries for an SP_TYPE_ITEM object - */ - void MakeItemMenu(SPGroup* layer); - /** - * creates menu entries for a grouped object AND for regular items that can be popped out of groups - */ - void MakeGroupMenu(SPItem* item); - /** - * creates menu entries for an anchor object - */ - void MakeAnchorMenu (); - /** - * creates menu entries for a bitmap image object - */ - void MakeImageMenu (); - /** - * creates menu entries for a shape object - */ - void MakeShapeMenu (); - /** - * creates menu entries for a text object - */ - void MakeTextMenu (); - - void EnterGroup(Gtk::MenuItem* mi); - void LeaveGroup(); - void LockSelected(); - void HideSelected(); - void UnLockBelow(std::vector items); - void UnHideBelow(std::vector items); - ////////////////////////////////////////// - //callbacks for the context menu entries of an SP_TYPE_ITEM object - void ItemProperties(); - void ItemSelectThis(); - void ItemMoveTo(); - void SelectSameFillStroke(); - void SelectSameFillColor(); - void SelectSameStrokeColor(); - void SelectSameStrokeStyle(); - void SelectSameObjectType(); - void ItemCreateLink(); - void CreateGroupClip(); - void SetMask(); - void ReleaseMask(); - void SetClip(); - void ReleaseClip(); - ////////////////////////////////////////// - - - /** - * callback, is executed on clicking the anchor "Group" and "Ungroup" menu entry - */ - void ActivateUngroupPopSelection(); - void ActivateUngroup(); - void ActivateGroup(); - - void AnchorLinkProperties(); - /** - * placeholder for callback to be executed on clicking the anchor "Follow link" context menu entry - * @todo add code to follow link externally - */ - void AnchorLinkFollow(); - - /** - * callback, is executed on clicking the anchor "Link remove" menu entry - */ - void AnchorLinkRemove(); - - - /** - * callback, opens the image properties dialog and is executed on clicking the context menu entry with similar name - */ - void ImageProperties(); - - /** - * callback, is executed on clicking the image "Edit Externally" menu entry - */ - void ImageEdit(); - - /** - * auxiliary function that loads the external image editor name from the settings. - */ - Glib::ustring getImageEditorName(bool is_svg = false); - - /** - * callback, is executed on clicking the "Embed Image" menu entry - */ - void ImageEmbed(); - - /** - * callback, is executed on clicking the "Trace Bitmap" menu entry - */ - void ImageTraceBitmap(); +private: + void AppendItemFromAction(Glib::RefPtr gmenu, Glib::ustring action, Glib::ustring label, Glib::ustring icon = ""); - /** - * callback, is executed on clicking the "Extract Image" menu entry - */ - void ImageExtract(); - - - /** - * callback, is executed on clicking the "Fill and Stroke" menu entry - */ - void FillSettings(); - - - /** - * callback, is executed on clicking the "Text and Font" menu entry - */ - void TextSettings(); - - /** - * callback, is executed on clicking the "Check spelling" menu entry - */ - void SpellcheckSettings(); + // Used for unlock and unhide actions + Glib::RefPtr action_group; + std::vector items_under_cursor; + void unhide_or_unlock(SPDocument* document, bool unhide); }; #endif // SEEN_CONTEXT_MENU_H diff --git a/src/ui/dialog/objects.cpp b/src/ui/dialog/objects.cpp index 29ba641e63391a3d52a42f04638b36a6e9790ab6..3ace81f6f3fc8f48fc68be5c23581e31760e7015 100644 --- a/src/ui/dialog/objects.cpp +++ b/src/ui/dialog/objects.cpp @@ -1231,7 +1231,8 @@ bool ObjectsPanel::_handleButtonEvent(GdkEventButton* event) } if (context_menu) { - ContextMenu *menu = new ContextMenu(getDesktop(), item); + ContextMenu *menu = new ContextMenu(getDesktop(), item, true); // true == hide menu item for opening this dialog! + menu->attach_to_widget(*this); // So actions work! menu->show(); menu->popup_at_pointer(nullptr); } diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp index 28ef514f489e0178dfae087d082569220ea9982e..2790a51c2f56c7b90d846ebcde8806f2367a4a15 100644 --- a/src/ui/tools/tool-base.cpp +++ b/src/ui/tools/tool-base.cpp @@ -1310,6 +1310,7 @@ void sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event) } ContextMenu* menu = new ContextMenu(desktop, item); + menu->attach_to_widget(*(desktop->getCanvas())); // So actions work! menu->show(); switch (event->type) {