Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
inkscape-window.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/*
7 * Authors:
8 * Tavmjong Bah
9 *
10 * Copyright (C) 2018 Authors
11 *
12 * The contents of this file may be used under the GNU General Public License Version 2 or later.
13 * Read the file 'COPYING' for more information.
14 */
15
16#include "inkscape-window.h"
17
18#include <iostream>
19#include <gdkmm/surface.h>
20#include <gtkmm/box.h>
21#include <gtkmm/popovermenubar.h>
22#include <gtkmm/shortcutcontroller.h>
23#include <sigc++/functors/mem_fun.h>
24
25#include "desktop.h"
26#include "desktop-events.h" // Handle key events
27#include "document.h"
28#include "enums.h" // PREFS_WINDOW_GEOMETRY_NONE
30
39#include "actions/actions-node-align.h" // Node alignment.
41#include "actions/actions-paths.h" // TEMP
47#include "inkscape.h"
48#include "object/sp-namedview.h" // TODO Remove need for this!
49#include "ui/desktop/menubar.h"
53#include "ui/shortcuts.h"
54#include "ui/util.h"
56#include "util/enums.h"
57
61
63 : _app{InkscapeApplication::instance()}
64 , _document{desktop->getDocument()}
65 , _desktop{desktop}
66{
67 assert(_document);
68
69 set_name("InkscapeWindow");
70 set_show_menubar(true);
71 set_resizable(true);
72
73 _app->gtk_app()->add_window(*this);
74
75 // =================== Actions ===================
76
77 // After canvas has been constructed.. move to canvas proper.
78 add_actions_canvas_mode(this); // Actions to change canvas display mode.
79 add_actions_canvas_snapping(this); // Actions to toggle on/off snapping modes.
80 add_actions_canvas_transform(this); // Actions to transform canvas view.
81 add_actions_dialogs(this); // Actions to open dialogs.
82 add_actions_edit_window(this); // Actions to edit.
83 add_actions_file_window(this); // Actions for file actions which are desktop dependent.
84 add_actions_help_url(this); // Actions to help url.
85 add_actions_layer(this); // Actions for layer.
86 add_actions_node_align(this); // Actions to align and distribute nodes (requiring Node tool).
87 add_actions_page_tools(this); // Actions specific to pages tool and toolbar
88 add_actions_path(this); // Actions for paths. TEMP
89 add_actions_select_window(this); // Actions with desktop selection
90 add_actions_tools(this); // Actions to switch between tools.
91 add_actions_transform(this); // Actions for transforming against the screen zoom
92 add_actions_view_mode(this); // Actions to change how Inkscape canvas is displayed.
93 add_actions_view_window(this); // Actions to add/change window of Inkscape
94
95 // Add document action group to window and export to DBus.
97
98 auto connection = _app->gio_app()->get_dbus_connection();
99 if (connection) {
100 std::string document_action_group_name = _app->gio_app()->get_dbus_object_path() + "/document/" + std::to_string(get_id());
101 connection->export_action_group(document_action_group_name, _document->getActionGroup());
102 }
103
104 // This is called here (rather than in InkscapeApplication) solely to add win level action
105 // tooltips to the menu label-to-tooltip map.
106 build_menu();
107
108 // =============== Build interface ===============
109
110 // Desktop widget (=> MultiPaned) (After actions added as this initializes shortcuts via CommandDialog.)
111 _desktop_widget = Gtk::make_managed<SPDesktopWidget>(this);
112 set_child(*_desktop_widget);
113
115
116 // ================== Callbacks ==================
117 property_is_active().signal_changed().connect(sigc::mem_fun(*this, &InkscapeWindow::on_is_active_changed));
118 signal_close_request().connect(sigc::mem_fun(*this, &InkscapeWindow::on_close_request), false); // before
119 property_default_width ().signal_changed().connect(sigc::mem_fun(*this, &InkscapeWindow::on_size_changed));
120 property_default_height().signal_changed().connect(sigc::mem_fun(*this, &InkscapeWindow::on_size_changed));
121
122 // Show dialogs after the main window, otherwise dialogs may be associated as the main window of the program.
123 // Restore short-lived floating dialogs state if this is the first window being opened
124 bool include_short_lived = _app->get_number_of_windows() == 1;
125 DialogManager::singleton().restore_dialogs_state(_desktop_widget->getDialogContainer(), include_short_lived);
126
127 // ================= Shift Icons =================
128 // Note: The menu is defined at the app level but shifting icons requires actual widgets and
129 // must be done on the window level.
130 auto prefs = Inkscape::Preferences::get();
131 bool shift_icons = prefs->getInt("/theme/shiftIcons", true);
132 for (auto const child : Inkscape::UI::get_children(*this)) {
133 if (auto const menubar = dynamic_cast<Gtk::PopoverMenuBar *>(child)) {
134 bool const shifted = set_tooltips_and_shift_icons(*menubar, shift_icons);
135 if (shifted) shift_icons = false;
136 }
137 }
138
139 // ================== Shortcuts ==================
140 auto& shortcuts_instance = Inkscape::Shortcuts::getInstance();
141 _shortcut_controller = Gtk::ShortcutController::create(shortcuts_instance.get_liststore());
142 _shortcut_controller->set_scope(Gtk::ShortcutScope::LOCAL);
143 _shortcut_controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
144 add_controller(_shortcut_controller);
145
146 // Update shortcuts in menus (due to bug in Gtk4 where menus are not updated when liststore is changed).
147 // However, this will not remove a shortcut label if there is no longer a shortcut for menu item.
148 shortcuts_instance.connect_changed([this]() {
149 remove_controller(_shortcut_controller);
150 add_controller(_shortcut_controller);
151 // Todo: Trigger update_gui_text_recursive here rather than in preferences dialog.
152 });
153
154 // Add shortcuts to tooltips, etc. (but not menus).
155 shortcuts_instance.update_gui_text_recursive(this);
156}
157
159{
160 Gtk::ApplicationWindow::on_realize();
161
162 // Note: Toplevel only becomes non-null after realisation.
163 _toplevel_state_connection = get_toplevel()->property_state().signal_changed().connect(
164 sigc::mem_fun(*this, &InkscapeWindow::on_toplevel_state_changed)
165 );
166}
167
169
170// Change a document, leaving desktop/view the same. (Eventually move all code here.)
172{
173 if (!_app) {
174 std::cerr << "Inkscapewindow::change_document: app is nullptr!" << std::endl;
175 return;
176 }
177
178 _document = document;
181
183}
184
189static void retransientize_dialogs(Gtk::Window &parent)
190{
191 assert(!dynamic_cast<DialogWindow *>(&parent));
192
193 auto prefs = Preferences::get();
194 bool window_above = prefs->getInt("/options/transientpolicy/value", PREFS_DIALOGS_WINDOWS_NORMAL) != PREFS_DIALOGS_WINDOWS_NONE;
195
196 for (auto const &window : parent.get_application()->get_windows()) {
197 if (auto dialog_window = dynamic_cast<DialogWindow *>(window)) {
198 if (window_above) {
199 dialog_window->set_transient_for(parent);
200 } else {
201 dialog_window->unset_transient_for();
202 }
203 }
204 }
205}
206
207Glib::RefPtr<Gdk::Toplevel const>
209{
210 return std::dynamic_pointer_cast<Gdk::Toplevel const>(get_surface());
211}
212
213Gdk::Toplevel::State InkscapeWindow::get_toplevel_state() const
214{
215 if (auto const toplevel = get_toplevel()) {
216 return toplevel->get_state();
217 }
218 return {};
219}
220
222{
223 return Inkscape::Util::has_flag(get_toplevel_state(), Gdk::Toplevel::State::FULLSCREEN);
224}
225
227{
228 return Inkscape::Util::has_flag(get_toplevel_state(), Gdk::Toplevel::State::MAXIMIZED);
229}
230
232{
233 return Inkscape::Util::has_flag(get_toplevel_state(), Gdk::Toplevel::State::MINIMIZED);
234}
235
237{
238 if (isFullscreen()) {
239 unfullscreen();
240 } else {
241 fullscreen();
242 }
243}
244
246{
247 // The initial old state is empty {}, as is the new state if we do not have a toplevel anymore.
248 auto const new_toplevel_state = get_toplevel_state();
249 auto const changed_mask = _old_toplevel_state ^ new_toplevel_state;
250 _old_toplevel_state = new_toplevel_state;
251 if (_desktop) {
252 _desktop->onWindowStateChanged(changed_mask, new_toplevel_state);
253 }
254}
255
257{
258 _desktop_widget->onFocus(is_active());
259
260 if (!is_active()) {
261 return;
262 }
263
264 if (!_app) {
265 std::cerr << "Inkscapewindow::on_focus_in_event: app is nullptr!" << std::endl;
266 return;
267 }
268
269 _app->set_active_window(this);
275}
276
289
290// Called when a window is closed via the 'X' in the window bar.
292{
293 auto desktops = get_desktop_widget()->get_desktops();
294 for (auto desktop : desktops) {
295 if (!_app->destroyDesktop(desktop)) {
296 return true; // abort closing
297 }
298 }
299
300 // We are deleted by InkscapeApplication at this point, so return value doesn't matter.
301 return false;
302}
303
308{
309 // Store the desktop widget size on resize.
310 if (!_desktop || !get_realized()) {
311 return;
312 }
313
314 auto prefs = Preferences::get();
315 bool maxed = isMaximised();
316 bool full = isFullscreen();
317 prefs->setBool("/desktop/geometry/fullscreen", full);
318 prefs->setBool("/desktop/geometry/maximized", maxed);
319
320 // Don't save geom for maximized, fullscreen or minimised windows.
321 // It just tells you the current maximized size, which is not
322 // as useful as whatever value it had previously.
323 if (!_desktop->isMinimised() && !maxed && !full) {
324 // Get size is more accurate than frame extends for window size.
325 int w, h = 0;
326 get_default_size(w, h);
327 prefs->setInt("/desktop/geometry/width", w);
328 prefs->setInt("/desktop/geometry/height", h);
329
330 // Frame extends returns real positions, unlike get_position()
331 // TODO: GTK4: get_frame_extents() and Window.get_position() are gone.
332 // We will must add backend-specific code to get the position or give up
333#if 0
334 if (auto const surface = get_surface()) {
335 Gdk::Rectangle rect;
336 surface->get_frame_extents(rect);
337 prefs->setInt("/desktop/geometry/x", rect.get_x());
338 prefs->setInt("/desktop/geometry/y", rect.get_y());
339 }
340#endif
341 }
342}
343
345{
346 for (auto const &window : _app->gtk_app()->get_windows()) {
347 if (auto dialog_window = dynamic_cast<DialogWindow *>(window)) {
348 // Update the floating dialogs, reset them to the new desktop.
349 dialog_window->set_inkscape_window(this);
350 }
351 }
352
353 // Update the docked dialogs in this InkscapeWindow
355}
356
361{
362 auto doc_action_group = _document->getActionGroup();
363
364 insert_action_group("doc", doc_action_group);
365
366#ifdef __APPLE__
367 // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/5667
368 // Copy the document ("doc") actions to the window ("win") so that the
369 // application menu on macOS can handle them. The menu only handles the
370 // window actions (in gtk_application_impl_quartz_active_window_changed),
371 // not the ones attached with "insert_action_group".
372 for (auto const &action_name : doc_action_group->list_actions()) {
373 add_action(doc_action_group->lookup_action(action_name));
374 }
375#endif
376}
377
378/*
379 Local Variables:
380 mode:c++
381 c-file-style:"stroustrup"
382 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
383 indent-tabs-mode:nil
384 fill-column:99
385 End:
386*/
387// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
void add_actions_canvas_mode(InkscapeWindow *win)
const char * action_name
void add_actions_canvas_snapping(Gio::ActionMap *map)
void add_actions_canvas_transform(InkscapeWindow *win)
void add_actions_dialogs(InkscapeApplication *app)
void add_actions_edit_window(InkscapeWindow *win)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_file_window(InkscapeWindow *win)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_help_url(InkscapeWindow *win)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_layer(InkscapeWindow *win)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_node_align(InkscapeWindow *win)
void add_actions_page_tools(InkscapeWindow *win)
void add_actions_path(InkscapeApplication *app)
void add_actions_select_window(InkscapeWindow *win)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_tools(InkscapeWindow *win)
void add_actions_transform(InkscapeApplication *app)
void add_actions_view_mode(InkscapeWindow *win)
Authors: Sushant A A sushant.co19@gmail.com
void add_actions_view_window(InkscapeWindow *win)
Authors: Sushant A A sushant.co19@gmail.com
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
void set_active_desktop(SPDesktop *desktop)
void set_active_window(InkscapeWindow *window)
bool destroyDesktop(SPDesktop *desktop, bool keep_alive=false)
Destroy a window and close the document it contains.
void set_active_selection(Inkscape::Selection *selection)
Gio::Application * gio_app()
The Gio application instance, never NULL.
void set_active_document(SPDocument *document)
Gtk::Application * gtk_app()
The Gtk application instance, or NULL if running headless without display.
int get_number_of_windows() const
Return number of open Inkscape Windows (irrespective of number of documents)
bool isMinimised() const
InkscapeApplication * _app
SPDocument * _document
void setActiveTab(SPDesktop *desktop)
sigc::scoped_connection _toplevel_state_connection
bool on_close_request() override
void on_size_changed()
Configure is called when the widget's size, position or stack changes.
SPDesktopWidget * _desktop_widget
void on_realize() override
void add_document_actions()
Make document actions accessible from the window.
Gdk::Toplevel::State _old_toplevel_state
bool isMaximised() const
Glib::RefPtr< Gdk::Toplevel const > get_toplevel() const
InkscapeWindow(SPDesktop *desktop)
SPDesktopWidget * get_desktop_widget()
~InkscapeWindow() override
bool isFullscreen() const
Glib::RefPtr< Gtk::ShortcutController > _shortcut_controller
void change_document(SPDocument *document)
SPDesktop * _desktop
void on_toplevel_state_changed()
Gdk::Toplevel::State get_toplevel_state() const
static Preferences * get()
Access the singleton Preferences object.
static Shortcuts & getInstance(bool init=true)
Definition shortcuts.h:66
A widget that manages DialogNotebook's and other widgets inside a horizontal DialogMultipaned contain...
DialogWindow holds DialogContainer instances for undocked dialogs.
std::vector< SPDesktop * > const & get_desktops() const
void onFocus(bool has_focus)
Inkscape::UI::Dialog::DialogContainer * getDialogContainer()
void addDesktop(SPDesktop *desktop, int pos=-1)
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
void updateDialogs()
Definition desktop.cpp:1150
Inkscape::Selection * getSelection() const
Definition desktop.h:188
bool isMinimised() const
Definition desktop.cpp:914
void onWindowStateChanged(Gdk::Toplevel::State changed, Gdk::Toplevel::State new_toplevel_state)
onWindowStateChanged
Definition desktop.cpp:1097
Typed SVG document implementation.
Definition document.h:101
Glib::RefPtr< Gio::SimpleActionGroup > getActionGroup()
Definition document.h:371
const double w
Definition conic-4.cpp:19
A class to hold:
Editable view implementation.
A window for floating docks.
static char const *const parent
Definition dir-util.cpp:70
@ PREFS_DIALOGS_WINDOWS_NORMAL
Definition enums.h:142
@ PREFS_DIALOGS_WINDOWS_NONE
Definition enums.h:141
static void retransientize_dialogs(Gtk::Window &parent)
If "dialogs on top" is activated in the preferences, set parent as the new transient parent for all D...
Inkscape - An SVG editor.
bool set_tooltips_and_shift_icons(Gtk::Widget &menu, bool const shift_icons)
Go over a widget representing a menu, & set tooltips on its items from app label-to-tooltip map.
Go over a widget representing a menu, & set tooltips on its items from app label-to-tooltip map.
void build_menu()
Definition menubar.cpp:48
Desktop main menu bar code.
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Definition util.cpp:141
constexpr bool has_flag(T test, T flag)
Definition enums.h:128
Ocnode * child[8]
Definition quantize.cpp:33
SPDesktop * desktop