// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * Inkscape - An SVG editor. */ /* * Authors: * Tavmjong Bah * * Copyright (C) 2018 Authors * * The contents of this file may be used under the GNU General Public License Version 2 or later. * Read the file 'COPYING' for more information. * */ #include "inkscape-window.h" #include "inkscape.h" // SP_ACTIVE_DESKTOP #include "desktop-events.h" // Handle key events #include "enums.h" // PREFS_WINDOW_GEOMETRY_NONE #include "inkscape-application.h" #include "actions/actions-canvas-mode.h" #include "actions/actions-canvas-snapping.h" #include "actions/actions-canvas-transform.h" #include "actions/actions-dialogs.h" #include "actions/actions-edit-window.h" #include "actions/actions-file-window.h" #include "actions/actions-help-url.h" #include "actions/actions-layer.h" #include "actions/actions-node-align.h" // Node alignment. #include "actions/actions-paths.h" // TEMP #include "actions/actions-selection-window.h" #include "actions/actions-tools.h" #include "actions/actions-view-mode.h" #include "actions/actions-view-window.h" #include "actions/actions-pages.h" #include "object/sp-namedview.h" // TODO Remove need for this! #include "ui/dialog/dialog-container.h" #include "ui/dialog/dialog-manager.h" #include "ui/dialog/dialog-window.h" #include "ui/drag-and-drop.h" // Move to canvas? #include "ui/interface.h" // main menu, sp_ui_close_view() #include "ui/monitor.h" // get_monitor_geometry_at_point() #include "ui/desktop/menubar.h" #include "ui/desktop/menu-icon-shift.h" #include "ui/drag-and-drop.h" #include "ui/event-debug.h" #include "ui/shortcuts.h" #include "widgets/desktop-widget.h" #include "ui/util.h" #include "ui/widget/canvas.h" using Inkscape::UI::Dialog::DialogManager; using Inkscape::UI::Dialog::DialogContainer; using Inkscape::UI::Dialog::DialogWindow; static gboolean _resize_children(Gtk::Window *win) { Inkscape::UI::resize_widget_children(win); return false; } InkscapeWindow::InkscapeWindow(SPDocument* document) : _document(document) { if (!_document) { std::cerr << "InkscapeWindow::InkscapeWindow: null document!" << std::endl; return; } _app = InkscapeApplication::instance(); _app->gtk_app()->add_window(*this); set_resizable(true); // =============== Build interface =============== // Main box _mainbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); _mainbox->set_name("DesktopMainBox"); _mainbox->show(); add(*_mainbox); // Desktop widget (=> MultiPaned) _desktop_widget = new SPDesktopWidget(this, _document); _desktop_widget->window = this; _desktop_widget->show(); _desktop = _desktop_widget->desktop; // =================== Actions =================== // After canvas has been constructed.. move to canvas proper. add_actions_canvas_mode(this); // Actions to change canvas display mode. add_actions_canvas_snapping(this); // Actions to toggle on/off snapping modes. add_actions_canvas_transform(this); // Actions to transform canvas view. add_actions_dialogs(this); // Actions to open dialogs. 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_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 add_actions_select_window(this); // Actions with desktop selection add_actions_tools(this); // Actions to switch between tools. add_actions_view_mode(this); // Actions to change how Inkscape canvas is displayed. add_actions_view_window(this); // Actions to add/change window of Inkscape add_actions_page_tools(this); // Actions specific to pages tool and toolbar // Add document action group to window and export to DBus. add_document_actions(); auto connection = _app->gio_app()->get_dbus_connection(); if (connection) { std::string document_action_group_name = _app->gio_app()->get_dbus_object_path() + "/document/" + std::to_string(get_id()); connection->export_action_group(document_action_group_name, document->getActionGroup()); } // This is called here (rather than in InkscapeApplication) solely to add win level action // tooltips to the menu label-to-tooltip map. build_menu(); // ========== Drag and Drop of Documents ========= ink_drag_setup(_desktop_widget); // Pallet // Status bar // The main section _mainbox->pack_start(*Gtk::manage(_desktop_widget), true, true); // ================== Callbacks ================== signal_window_state_event().connect(sigc::mem_fun(*_desktop, &SPDesktop::onWindowStateEvent)); signal_focus_in_event().connect( sigc::mem_fun(*_desktop_widget, &SPDesktopWidget::onFocusInEvent)); // ================ Window Options =============== setup_view(); // Show dialogs after the main window, otherwise dialogs may be associated as the main window of the program. // Restore short-lived floating dialogs state if this is the first window being opened bool include_short_lived = _app->get_number_of_windows() == 0; DialogManager::singleton().restore_dialogs_state(_desktop->getContainer(), include_short_lived); // This pokes the window to request the right size for the dialogs once loaded. g_idle_add(GSourceFunc(&_resize_children), this); // ================= Shift Icons ================= // Note: The menu is defined at the app level but shifting icons requires actual widgets and // must be done on the window level. Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getInt("/theme/shiftIcons", true)) { bool shifted = false; for (auto child : get_children()) { auto menubar = dynamic_cast(child); if (menubar) { menubar->get_style_context()->add_class("shifticonmenu"); if (!shifted) { shifted = shift_icons(menubar); } } } } // ========= Update text for Accellerators ======= Inkscape::Shortcuts::getInstance().update_gui_text_recursive(this); } InkscapeWindow::~InkscapeWindow() { g_idle_remove_by_data(this); } // Change a document, leaving desktop/view the same. (Eventually move all code here.) void InkscapeWindow::change_document(SPDocument* document) { if (!_app) { std::cerr << "Inkscapewindow::change_document: app is nullptr!" << std::endl; return; } _document = document; _app->set_active_document(_document); add_document_actions(); setup_view(); update_dialogs(); } // Sets up the window and view according to user preferences and of the just loaded document void InkscapeWindow::setup_view() { // Make sure the GdkWindow is fully initialized before resizing/moving // (ensures the monitor it'll be shown on is known) realize(); // Resize the window to match the document properties sp_namedview_window_from_document(_desktop); // This should probably be a member function here. // Must show before setting zoom and view! (crashes otherwise) // // Showing after resizing/moving allows the window manager to correct an invalid size/position of the window // TODO: This does *not* work when called from 'change_document()', i.e. when the window is already visible. // This can result in off-screen windows! We previously worked around this by hiding and re-showing // the window, but a call to hide() causes Inkscape to just exit since the migration to Gtk::Application show(); _desktop->schedule_zoom_from_document(); sp_namedview_update_layers_from_document(_desktop); SPNamedView *nv = _desktop->namedview; if (nv && nv->lockguides) { nv->setLockGuides(true); } } bool InkscapeWindow::on_key_press_event(GdkEventKey* event) { #ifdef EVENT_DEBUG ui_dump_event(reinterpret_cast(event), "\nInkscapeWindow::on_key_press_event"); #endif // Key press and release events are normally sent first to Gtk::Window for processing as // accelerators and menomics before bubbling up from the "grab" or "focus" widget (unlike other // events which always bubble up). This would means that key combinations used for accelerators // won't reach the focus widget (and our tool event handlers). As we use single keys for // accelerators, we wouldn't even be able to type text! We can get around this by sending key // events first to the focus widget. // // See https://developer.gnome.org/gtk3/stable/chap-input-handling.html (Event Propagation) auto focus = get_focus(); if (focus) { if (focus->event(reinterpret_cast(event))) { return true; } } // Try to find action to call; calling it here makes it higher priority than dialog mnemonics; // this is needed because GTK tries to activate widgets with matching mnemonics first, // even if they are invisible (!) and/or disabled. That cripples some Alt+key shortcuts when // we open and dock some dialogs, whether they are visible or not. // On macOS situation is even worse, as dialogs can steal many common