summaryrefslogtreecommitdiffstats
path: root/src/ui/contextmenu.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
commitc853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch)
tree7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/ui/contextmenu.cpp
parentInitial commit. (diff)
downloadinkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz
inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/ui/contextmenu.cpp')
-rw-r--r--src/ui/contextmenu.cpp364
1 files changed, 364 insertions, 0 deletions
diff --git a/src/ui/contextmenu.cpp b/src/ui/contextmenu.cpp
new file mode 100644
index 0000000..707923e
--- /dev/null
+++ b/src/ui/contextmenu.cpp
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Context menu
+ */
+/* Authors:
+ * Tavmjong Bah
+ *
+ * Rewrite of code authored by:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Frank Felfe <innerspace@iname.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2022 Tavmjong Bah
+ * Copyright (C) 2012 Kris De Gussem
+ * Copyright (C) 2010 authors
+ * Copyright (C) 1999-2005 authors
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "contextmenu.h"
+
+#include <glibmm/i18n.h>
+
+#include "desktop.h"
+#include "document.h"
+#include "layer-manager.h"
+#include "page-manager.h"
+#include "selection.h"
+
+#include "object/sp-anchor.h"
+#include "object/sp-image.h"
+#include "object/sp-page.h"
+#include "object/sp-shape.h"
+#include "object/sp-text.h"
+
+#include "ui/desktop/menu-icon-shift.h"
+#include "ui/util.h"
+
+ContextMenu::ContextMenu(SPDesktop *desktop, SPObject *object, bool hide_layers_and_objects_menu_item)
+{
+ set_name("ContextMenu");
+
+ auto item = cast<SPItem>(object);
+
+ // 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<SPDocument*, bool>(sigc::mem_fun(*this, &ContextMenu::unhide_or_unlock), document, true));
+ action_group->add_action("unlock-objects-below-cursor", sigc::bind<SPDocument*, bool>(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();
+
+ // 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()) {
+ has_hidden_below_cursor = true;
+ }
+ if (item->isLocked()) {
+ has_locked_below_cursor = true;
+ }
+ }
+ // 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;
+
+ // clang-tidy off
+
+ // 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);
+
+ if (auto page = cast<SPPage>(object)) {
+ auto &page_manager = document->getPageManager();
+ page_manager.selectPage(page);
+
+ gmenu_section = Gio::Menu::create();
+ AppendItemFromAction(gmenu_section, "doc.page-new", _("_New Page"), "pages-add");
+ gmenu->append_section(gmenu_section);
+
+ gmenu_section = Gio::Menu::create();
+ AppendItemFromAction(gmenu_section, "doc.page-delete", _("_Delete Page"), "pages-remove");
+ AppendItemFromAction(gmenu_section, "doc.page-move-backward", _("Move Page _Backward"), "pages-order-backwards");
+ AppendItemFromAction(gmenu_section, "doc.page-move-forward", _("Move Page _Forward"), "pages-order-forwards");
+ gmenu->append_section(gmenu_section);
+
+ } else if (!layer) {
+ // "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->getSelection();
+ if (object && !selection->includes(object)) {
+ selection->set(object);
+ }
+
+ if (!item) {
+ // Even when there's no item, we should still have the Paste action on top
+ // (see https://gitlab.com/inkscape/inkscape/-/issues/4150)
+ gmenu->append_section(create_clipboard_actions(true));
+
+ gmenu_section = Gio::Menu::create();
+ AppendItemFromAction(gmenu_section, "win.dialog-open('DocumentProperties')", _("Document Properties..."), "document-properties");
+ gmenu->append_section(gmenu_section);
+ } else {
+ // When an item is selected, show all three of Cut, Copy and Paste.
+ gmenu->append_section(create_clipboard_actions());
+
+ gmenu_section = Gio::Menu::create();
+ AppendItemFromAction(gmenu_section, "app.duplicate", _("Duplic_ate"), "edit-duplicate");
+ AppendItemFromAction(gmenu_section, "app.clone", _("_Clone"), "edit-clone");
+ AppendItemFromAction(gmenu_section, "app.delete-selection", _("_Delete"), "edit-delete");
+ gmenu->append_section(gmenu_section);
+
+ // 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 (is<SPShape>(item) || is<SPText>(item) || is<SPGroup>(item)) {
+ AppendItemFromAction(gmenu_dialogs, "win.dialog-open('FillStroke')", _("_Fill and Stroke..."), "dialog-fill-and-stroke" );
+ }
+
+ // Image dialogs (mostly).
+ if (auto image = cast<SPImage>(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" );
+
+ if (image->getClipObject()) {
+ AppendItemFromAction( gmenu_dialogs, "app.element-image-crop", _("Crop Image to Clip"), "" );
+ }
+ if (strncmp(image->href, "data", 4) == 0) {
+ // Image is embedded.
+ 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 (is<SPText>(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 (!is<SPAnchor>(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 (is<SPGroup>(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 = cast<SPGroup>(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);
+
+ } else {
+ // Layers: Only used in "Layers and Objects" dialog.
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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);
+
+ }
+ // clang-tidy on
+
+ bind_model(gmenu, true);
+
+ // Do not install this CSS provider; it messes up menus with icons (like popup menu with all dialogs).
+ // It doesn't work well with context menu either, introducing disturbing visual glitch
+ // where menu shifts upon opening.
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getInt("/theme/shiftIcons", true)) {
+ get_style_context()->add_class("shifticonmenu");
+ 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");
+ }
+ }
+}
+
+/** @brief Create a menu section containing the standard editing actions:
+ * Cut, Copy, Paste.
+ *
+ * @param paste_only If true, only the Paste action will be included.
+ * @return A new menu containing the requested actions.
+ */
+Glib::RefPtr<Gio::Menu> ContextMenu::create_clipboard_actions(bool paste_only)
+{
+ auto result = Gio::Menu::create();
+ if (!paste_only) {
+ AppendItemFromAction(result, "app.cut", _("Cu_t"), "edit-cut");
+ AppendItemFromAction(result, "app.copy", _("_Copy"), "edit-copy");
+ }
+ AppendItemFromAction(result, "win.paste", _("_Paste"), "edit-paste");
+ return result;
+}
+
+void
+ContextMenu::AppendItemFromAction(Glib::RefPtr<Gio::Menu> gmenu, Glib::ustring action, Glib::ustring label, Glib::ustring icon)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool show_icons = prefs->getInt("/theme/menuIcons", true);
+
+ 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::unhide_or_unlock(SPDocument* document, bool unhide)
+{
+ for (auto item : items_under_cursor) {
+ if (unhide) {
+ if (item->isHidden()) {
+ item->setHidden(false);
+ }
+ } else {
+ if (item->isLocked()) {
+ item->setLocked(false);
+ }
+ }
+ }
+
+ // We wouldn't be here if we didn't make a change.
+ Inkscape::DocumentUndo::done(document, (unhide ? _("Unhid objects") : _("Unlocked objects")), "");
+}
+
+/*
+ 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 :