1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
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 :
|