// SPDX-License-Identifier: GPL-2.0-or-later /** @file * @brief A window for floating dialogs. * * Authors: see git history * Tavmjong Bah * * Copyright (c) 2018 Tavmjong Bah, Authors * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "ui/dialog/dialog-window.h" #include #include #include #include #include #include "enums.h" #include "inkscape-application.h" #include "inkscape-window.h" #include "inkscape.h" #include "preferences.h" #include "ui/dialog/dialog-base.h" #include "ui/dialog/dialog-container.h" #include "ui/dialog/dialog-manager.h" #include "ui/dialog/dialog-multipaned.h" #include "ui/dialog/dialog-notebook.h" #include "ui/shortcuts.h" #include "ui/util.h" // Sizing constants const int MINIMUM_WINDOW_WIDTH = 210; const int MINIMUM_WINDOW_HEIGHT = 320; const int INITIAL_WINDOW_WIDTH = 360; const int INITIAL_WINDOW_HEIGHT = 520; const int WINDOW_DROPZONE_SIZE = 10; const int WINDOW_DROPZONE_SIZE_LARGE = 16; const int NOTEBOOK_TAB_HEIGHT = 36; namespace Inkscape { namespace UI { namespace Dialog { class DialogNotebook; class DialogContainer; DialogWindow::~DialogWindow() {} // Create a dialog window and move page from old notebook. DialogWindow::DialogWindow(InkscapeWindow *inkscape_window, Gtk::Widget *page) : Gtk::Window() , _app(InkscapeApplication::instance()) , _inkscape_window(inkscape_window) , _title(_("Dialog Window")) { g_assert(_app != nullptr); g_assert(_inkscape_window != nullptr); // ============ Initialization =============== // Setting the window type Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool window_above = true; if (prefs) { window_above = prefs->getInt("/options/transientpolicy/value", PREFS_DIALOGS_WINDOWS_NORMAL) != PREFS_DIALOGS_WINDOWS_NONE; } set_type_hint(Gdk::WINDOW_TYPE_HINT_DIALOG); set_transient_for(*inkscape_window); // Add the dialog window to our app _app->gtk_app()->add_window(*this); this->signal_delete_event().connect([=](GdkEventAny *) { DialogManager::singleton().store_state(*this); delete this; return true; }); auto win_action_group = dynamic_cast(inkscape_window); if (win_action_group) { // Must use C API as C++ API takes a RefPtr which we can't get (easily). gtk_widget_insert_action_group(GTK_WIDGET(this->gobj()), "win", win_action_group->gobj()); } else { std::cerr << "DialogWindow::DialogWindow: Can't find InkscapeWindow Gio:ActionGroup!" << std::endl; } insert_action_group("doc", inkscape_window->get_document()->getActionGroup()); // ============ Theming: icons ============== // Set the style and icon theme of the new menu based on the desktop if (auto desktop = SP_ACTIVE_DESKTOP) { if (Gtk::Window *window = desktop->getToplevel()) { if (window->get_style_context()->has_class("dark")) { get_style_context()->add_class("dark"); get_style_context()->remove_class("bright"); } else { get_style_context()->add_class("bright"); get_style_context()->remove_class("dark"); } if (prefs->getBool("/theme/symbolicIcons", false)) { get_style_context()->add_class("symbolic"); get_style_context()->remove_class("regular"); } else { get_style_context()->add_class("regular"); get_style_context()->remove_class("symbolic"); } } } // ================ Window ================== set_title(_title); set_name(_title); int window_width = INITIAL_WINDOW_WIDTH; int window_height = INITIAL_WINDOW_HEIGHT; // =============== Outer Box ================ Gtk::Box *box_outer = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); add(*box_outer); // =============== Container ================ _container = Gtk::manage(new DialogContainer(inkscape_window)); DialogMultipaned *columns = _container->get_columns(); auto drop_size = Inkscape::Preferences::get()->getBool("/options/dockingzone/value", true) ? WINDOW_DROPZONE_SIZE / 2 : WINDOW_DROPZONE_SIZE; columns->set_dropzone_sizes(drop_size, drop_size); box_outer->pack_end(*_container); // If there is no page, create an empty Dialogwindow to be populated later if (page) { // ============= Initial Column ============= DialogMultipaned *column = _container->create_column(); columns->append(column); // ============== New Notebook ============== DialogNotebook *dialog_notebook = Gtk::manage(new DialogNotebook(_container)); column->append(dialog_notebook); column->set_dropzone_sizes(drop_size, drop_size); dialog_notebook->move_page(*page); // Set window title DialogBase *dialog = dynamic_cast(page); if (dialog) { _title = dialog->get_name(); set_title(_title); } // Set window size considering what the dialog needs Gtk::Requisition minimum_size, natural_size; dialog->get_preferred_size(minimum_size, natural_size); int overhead = 2 * (drop_size + dialog->property_margin().get_value()); int width = natural_size.width + overhead; int height = natural_size.height + overhead + NOTEBOOK_TAB_HEIGHT; window_width = std::max(width, window_width); window_height = std::max(height, window_height); } // Set window sizing set_size_request(MINIMUM_WINDOW_WIDTH, MINIMUM_WINDOW_HEIGHT); set_default_size(window_width, window_height); if (page) { update_dialogs(); } // window is created hidden; don't show it now, its size needs to be restored } /** * Change InkscapeWindow that DialogWindow is linked to. */ void DialogWindow::set_inkscape_window(InkscapeWindow* inkscape_window) { if (!inkscape_window) { std::cerr << "DialogWindow::set_inkscape_window: no inkscape_window!" << std::endl; return; } _inkscape_window = inkscape_window; update_dialogs(); } /** * Update all dialogs that are owned by the DialogWindow's _container. */ void DialogWindow::update_dialogs() { g_assert(_app != nullptr); g_assert(_container != nullptr); g_assert(_inkscape_window != nullptr); _container->set_inkscape_window(_inkscape_window); _container->update_dialogs(); // Updating dialogs is not using the _app reference here. // Set window title. const std::multimap *dialogs = _container->get_dialogs(); if (dialogs->size() > 1) { _title = "Multiple dialogs"; } else if (dialogs->size() == 1) { _title = dialogs->begin()->second->get_name(); } else { // Should not happen... but does on closing a window! // std::cerr << "DialogWindow::update_dialogs(): No dialogs!" << std::endl; _title = ""; } auto document_name = _inkscape_window->get_document()->getDocumentName(); if (document_name) { set_title(_title + " - " + Glib::ustring(document_name)); } } /** * Update window width and height in order to fit all dialogs inisde its container. * * The intended use of this function is at initialization. */ void DialogWindow::update_window_size_to_fit_children() { // Declare variables int pos_x = 0, pos_y = 0; int width = 0, height = 0; int overhead = 0, baseline; Gtk::Allocation allocation; Gtk::Requisition minimum_size, natural_size; // Read needed data get_position(pos_x, pos_y); get_allocated_size(allocation, baseline); const std::multimap *dialogs = _container->get_dialogs(); // Get largest sizes for dialogs for (auto dialog : *dialogs) { dialog.second->get_preferred_size(minimum_size, natural_size); width = std::max(natural_size.width, width); height = std::max(natural_size.height, height); overhead = std::max(overhead, dialog.second->property_margin().get_value()); } // Compute sizes including overhead overhead = 2 * (WINDOW_DROPZONE_SIZE_LARGE + overhead); width = width + overhead; height = height + overhead + NOTEBOOK_TAB_HEIGHT; // If sizes are lower then current, don't change them if (allocation.get_width() >= width && allocation.get_height() >= height) { return; } // Compute largest sizes on both axis width = std::max(width, allocation.get_width()); height = std::max(height, allocation.get_height()); // Compute new positions to keep window centered pos_x = pos_x - (width - allocation.get_width()) / 2; pos_y = pos_y - (height - allocation.get_height()) / 2; // Keep window inside the screen pos_x = std::max(pos_x, 0); pos_y = std::max(pos_y, 0); // Resize window move(pos_x, pos_y); resize(width, height); } // mimic InkscapeWindow handling of shortcuts to make them work with active floating dialog window bool DialogWindow::on_key_press_event(GdkEventKey *key_event) { auto focus = get_focus(); if (focus) { if (focus->event(reinterpret_cast(key_event))) { return true; } } // Pass key event to this window or to app (via this window). if (Gtk::Window::on_key_press_event(key_event)) { return true; } // Pass key event to active InkscapeWindow to handle win (and app) level shortcuts. if (auto win = _app->get_active_window(); win && win->on_key_press_event(key_event)) { return true; } return false; } } // namespace Dialog } // namespace UI } // namespace Inkscape /* 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 :