diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/ui/contextmenu.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-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.cpp | 364 |
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 : |