From 2c971690777ca2dca936245569bae9dfb76513e8 Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Fri, 25 Jan 2019 10:36:53 +0100 Subject: Rewrite main menu bar code: Convert to GTKMM. General cleanup. Move to dedicated files. --- src/ui/CMakeLists.txt | 4 + src/ui/desktop/README | 27 ++ src/ui/desktop/menubar.cpp | 513 +++++++++++++++++++++++++++++++++++++ src/ui/desktop/menubar.h | 45 ++++ src/ui/interface.cpp | 613 +-------------------------------------------- src/ui/interface.h | 12 - 6 files changed, 590 insertions(+), 624 deletions(-) create mode 100644 src/ui/desktop/README create mode 100644 src/ui/desktop/menubar.cpp create mode 100644 src/ui/desktop/menubar.h (limited to 'src/ui') diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 7125a4aa2..46978d87a 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -21,6 +21,8 @@ set(ui_SRC cache/svg_preview_cache.cpp + desktop/menubar.cpp + tool/control-point-selection.cpp tool/control-point.cpp tool/curve-drag-point.cpp @@ -239,6 +241,8 @@ set(ui_SRC cache/svg_preview_cache.h + desktop/menubar.cpp + dialog/aboutbox.h dialog/align-and-distribute.h dialog/arrange-tab.h diff --git a/src/ui/desktop/README b/src/ui/desktop/README new file mode 100644 index 000000000..af0799694 --- /dev/null +++ b/src/ui/desktop/README @@ -0,0 +1,27 @@ + + +This directory contains code related to the Inkscape desktop, that is +code that is directly used by the InkscapeWindow class and in linking +the desktop to the canvas. It should not contain basic widgets, +dialogs, toolbars, etc. + +To do: + +* widgets/desktop-widget.h/cpp should disappear with code ending up in either + InkscapeWindow.h/cpp or desktop.h/cpp (or in new files). + +* ui/view/view-widget.h/cpp should disappear ('view' should be member of window) + +* desktop.h/cpp should only contain code that links the desktop to the canvas. + +* Convert GUI to use actions where possible. + +* Future Structure: + Main menu bar (menubar.h/.cpp) + Tool bar + Multipaned widget containing + Dialogs + Tools + Canvas + Palatte (maybe turn into dialog). + Status bar diff --git a/src/ui/desktop/menubar.cpp b/src/ui/desktop/menubar.cpp new file mode 100644 index 000000000..38d2a5ad3 --- /dev/null +++ b/src/ui/desktop/menubar.cpp @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Desktop main menu bar code. + */ +/* + * Authors: + * Tavmjong Bah + * Alex Valavanis + * Patrick Storz + * Krzysztof KosiƄski + * Kris De Gussem + * + * 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 +#include + +#include + +#include "inkscape.h" +#include "file.h" // sp_file_open +#include "message-context.h" +#include "shortcuts.h" + +#include "helper/action.h" +#include "helper/action-context.h" + +#include "object/sp-namedview.h" + +#include "ui/contextmenu.h" // Shift to make room for icons +#include "ui/icon-loader.h" +#include "ui/view/view.h" +#include "ui/uxmanager.h" // To Do: Convert to actions + +// ================= Common ==================== + +// Sets tip +static void +select_action(SPAction *action) +{ + sp_action_get_view(action)->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip); +} + +// Clears tip +static void +deselect_action(SPAction *action) +{ + sp_action_get_view(action)->tipsMessageContext()->clear(); +} + +// Trigger action +static void +item_activate(Gtk::MenuItem* menuitem, SPAction* action) +{ + sp_action_perform(action, nullptr); +} + +// Change label name (used in the Undo/Redo menu items). +static void +set_name(Glib::ustring const &name, Gtk::MenuItem* menuitem) +{ + if (menuitem) { + Gtk::Widget* widget = menuitem->get_child(); + + // Label is either child of menuitem + Gtk::Label* label = dynamic_cast(widget); + + // Or wrapped inside a box which is a child of menuitem (if with icon). + if (!label) { + Gtk::Box* box = dynamic_cast(widget); + if (box) { + std::vector children = box->get_children(); + for (auto child: children) { + label = dynamic_cast(child); + if (label) break; + } + } + } + + if (label) { + label->set_markup_with_mnemonic(name); + } else { + std::cerr << "set_name: could not find label!" << std::endl; + } + } +} + +/* Install CSS to shift icons into the space reserved for toggles (i.e. check and radio items). + * + * TODO: This code already exists as a C++ version in the class ContextMenu so we can simply wrap + * it here. In future ContextMenu and the (to be created) class for the menu bar should then + * be derived from one common base class. + * + * TODO: This code is called everytime a menu is opened. We can certainly find a way to call it once. + */ +static void +shift_icons(Gtk::Menu* menu) +{ + ContextMenu *contextmenu = static_cast(menu); + contextmenu->ShiftIcons(); +} + +// ================= MenuItem ==================== + +Gtk::MenuItem* +build_menu_item_from_verb(SPAction* action, + bool show_icon, + bool radio = false, + Gtk::RadioMenuItem::Group *group = nullptr) +{ + Gtk::MenuItem* menuitem = nullptr; + + if (radio) { + menuitem = Gtk::manage(new Gtk::RadioMenuItem(*group)); + } else { + menuitem = Gtk::manage(new Gtk::MenuItem()); + } + + Gtk::AccelLabel* label = Gtk::manage(new Gtk::AccelLabel(action->name, true)); + label->set_alignment(0.0, 0.5); + label->set_accel_widget(*menuitem); + sp_shortcut_add_accelerator((GtkWidget*)menuitem->gobj(), sp_shortcut_get_primary(action->verb)); + + // If there is an image associated with the action, we can add it as an icon for the menu item. + if (show_icon && action->image) { + menuitem->set_name("ImageMenuItem"); // custom name to identify our "ImageMenuItems" + Gtk::Image* image = Gtk::manage(sp_get_icon_image(action->image, Gtk::ICON_SIZE_MENU)); + + // Create a box to hold icon and label as Gtk::MenuItem derives from GtkBin and can + // only hold one child + Gtk::Box *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); + box->pack_start(*image, false, false, 0); + box->pack_start(*label, true, true, 0); + menuitem->add(*box); + } else { + menuitem->add(*label); + } + + menuitem->signal_activate().connect( + sigc::bind(sigc::ptr_fun(&item_activate), menuitem, action)); + menuitem->signal_select().connect( sigc::bind(sigc::ptr_fun(&select_action), action)); + menuitem->signal_deselect().connect(sigc::bind(sigc::ptr_fun(&deselect_action), action)); + + action->signal_set_sensitive.connect( + sigc::bind<0>(sigc::ptr_fun(>k_widget_set_sensitive), (GtkWidget*)menuitem->gobj())); + action->signal_set_name.connect( + sigc::bind(sigc::ptr_fun(&set_name), menuitem)); + + return menuitem; +} + +// =============== CheckMenuItem ================== + +static bool +getStateFromPref(SPDesktop* dt, Glib::ustring item) +{ + Glib::ustring pref_path; + + if (dt->is_focusMode()) { + pref_path = "/focus/"; + } else if (dt->is_fullscreen()) { + pref_path = "/fullscreen/"; + } else { + pref_path = "/window/"; + } + + pref_path += item; + pref_path += "/state"; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + return prefs->getBool(pref_path, false); +} + +// I wonder if this can be done without hard coding names. +static void +checkitem_update(Gtk::CheckMenuItem* menuitem, SPAction* action) +{ + bool active = false; + if (action && action->id) { + Glib::ustring id = action->id; + SPDesktop* dt = static_cast(sp_action_get_view(action)); + + if (id == "ToggleGrid") { + active = dt->gridsEnabled(); + + } else if (id == "EditGuidesToggleLock") { + active = dt->namedview->lockguides; + + } else if (id == "ToggleGuides") { + active = dt->namedview->getGuides(); + + } else if (id == "ViewCmsToggle") { + active = dt->colorProfAdjustEnabled(); + + } else if (id == "ViewSplitModeToggle") { + active = dt->splitMode(); + + } else if (id == "ViewXRayToggle") { + active = dt->xrayMode(); + + } else if (id == "ToggleCommandsToolbar") { + active = getStateFromPref(dt, "commands"); + + } else if (id == "ToggleSnapToolbar") { + active = getStateFromPref(dt, "snaptoolbox"); + + } else if (id == "ToggleToolToolbar") { + active = getStateFromPref(dt, "toppanel"); + + } else if (id == "ToggleToolbox") { + active = getStateFromPref(dt, "toolbox"); + + } else if (id == "ToggleRulers") { + active = getStateFromPref(dt, "rulers"); + + } else if (id == "ToggleScrollbars") { + active = getStateFromPref(dt, "scrollbars"); + + } else if (id == "TogglePalette") { + active = getStateFromPref(dt, "panels"); // Rename? + + } else if (id == "ToggleStatusbar") { + active = getStateFromPref(dt, "statusbar"); + + } else { + std::cerr << "checkitem_update: unhandled item: " << id << std::endl; + } + + menuitem->set_active(active); + } else { + std::cerr << "checkitem_update: unknown action" << std::endl; + } +} + +static Gtk::CheckMenuItem* +build_menu_check_item_from_verb(SPAction* action) +{ + // This does not work for some reason! + // Gtk::CheckMenuItem* menuitem = Gtk::manage(new Gtk::CheckMenuItem(action->name, true)); + // sp_shortcut_add_accelerator(GTK_WIDGET(menuitem->gobj()), sp_shortcut_get_primary(action->verb)); + + GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(action->name); + sp_shortcut_add_accelerator(item, sp_shortcut_get_primary(action->verb)); + Gtk::CheckMenuItem* menuitem = Gtk::manage(Glib::wrap(GTK_CHECK_MENU_ITEM(item))); + + // Set initial state before connecting signals. + checkitem_update(menuitem, action); + + menuitem->signal_toggled().connect( + sigc::bind(sigc::ptr_fun(&item_activate), menuitem, action)); + menuitem->signal_select().connect( sigc::bind(sigc::ptr_fun(&select_action), action)); + menuitem->signal_deselect().connect(sigc::bind(sigc::ptr_fun(&deselect_action), action)); + + return menuitem; +} + +// ================= Tasks Submenu ============== +static void +task_activated(SPDesktop* dt, int number) +{ + Inkscape::UI::UXManager::getInstance()->setTask(dt, number); +} + +// Sets tip +static void +select_task(SPDesktop* dt, Glib::ustring tip) +{ + dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, tip.c_str()); +} + +// Clears tip +static void +deselect_task(SPDesktop* dt) +{ + dt->tipsMessageContext()->clear(); +} + +static void +add_tasks(Gtk::MenuShell* menu, SPDesktop* dt) +{ + const Glib::ustring data[3][2] = { + { C_("Interface setup", "Default"), _("Default interface setup") }, + { C_("Interface setup", "Custom"), _("Setup for custom task") }, + { C_("Interface setup", "Wide"), _("Setup for widescreen work") } + }; + + int active = Inkscape::UI::UXManager::getInstance()->getDefaultTask(dt); + + Gtk::RadioMenuItem::Group group; + + for (unsigned int i = 0; i < 3; ++i) { + + Gtk::RadioMenuItem* menuitem = Gtk::manage(new Gtk::RadioMenuItem(group, data[i][0])); + if (menuitem) { + if (active == i) { + menuitem->set_active(); + } + + menuitem->signal_toggled().connect( + sigc::bind(sigc::ptr_fun(&task_activated), dt, i)); + menuitem->signal_select().connect( + sigc::bind(sigc::ptr_fun(&select_task), dt, data[i][1])); + menuitem->signal_deselect().connect( + sigc::bind(sigc::ptr_fun(&deselect_task),dt)); + + menu->append(*menuitem); + } + } +} + + +static void +sp_recent_open(Gtk::RecentChooser* recentchooser) +{ + Glib::ustring uri = recentchooser->get_current_uri(); + + Glib::RefPtr file = Gio::File::create_for_uri(uri); + + // To do: change sp_file_open to use Gio::File. + // To do: get rid of sp_file_open + sp_file_open(file->get_parse_name(), nullptr); +} + +// =================== Main Menu ================ +// Recursively build menu and submenus. +void +build_menu(Gtk::MenuShell* menu, Inkscape::XML::Node* xml, Inkscape::UI::View::View* view) +{ + if (menu == nullptr) { + std::cerr << "build_menu: menu is nullptr" << std::endl; + return; + } + + if (xml == nullptr) { + std::cerr << "build_menu: xml is nullptr" << std::endl; + return; + } + + // Do we want icons???? + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int show_icon_pref = prefs->getInt("/theme/menuIcons", 0); + static bool show_icon = false; + if (show_icon_pref == 1) { + show_icon = true; + } else if ( show_icon_pref == -1) { + show_icon = false; + } + + Gtk::RadioMenuItem::Group group; + + for (auto menu_ptr = xml; menu_ptr != nullptr; menu_ptr = menu_ptr->next()) { + + if (show_icon_pref == 0) { + // We set according to file, value is inherited into sub-menus. + const char *str = menu_ptr->attribute("show-icons"); + if (str) { + Glib::ustring ustr = str; + if (ustr == "true") { + show_icon = true; + } else if (ustr == "false") { + show_icon = false; + } else { + std::cerr << "build_menu: invalid value for 'show-icon' (use 'true' or 'false')." + << ustr << std::endl; + } + } + } + + if (menu_ptr->name()) { + Glib::ustring name = menu_ptr->name(); + + if (name == "inkscape") { + build_menu(menu, menu_ptr->firstChild(), view); + continue; + } + + if (name == "submenu") { + Gtk::MenuItem* menuitem = nullptr; + if (menu_ptr->attribute("_name") != nullptr) { + menuitem = Gtk::manage(new Gtk::MenuItem(_(menu_ptr->attribute("_name")), true)); + } else { + menuitem = Gtk::manage(new Gtk::MenuItem( menu_ptr->attribute("name"), true)); + } + Gtk::Menu* submenu = Gtk::manage(new Gtk::Menu()); + build_menu(submenu, menu_ptr->firstChild(), view); + menuitem->set_submenu(*submenu); + menu->append(*menuitem); + + submenu->signal_map().connect( + sigc::bind(sigc::ptr_fun(&shift_icons), submenu)); + + continue; + } + + if (name == "verb") { + if (menu_ptr->attribute("verb-id") != nullptr) { + Glib::ustring verb_name = menu_ptr->attribute("verb-id"); + + Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name.c_str()); + if (verb != nullptr && verb->get_code() != SP_VERB_NONE) { + + SPAction* action = verb->get_action(Inkscape::ActionContext(view)); + + if (menu_ptr->attribute("check") != nullptr) { + + Gtk::CheckMenuItem* menuitem = build_menu_check_item_from_verb(action); + if (menuitem) { + menu->append(*menuitem); + } + + } else if (menu_ptr->attribute("radio") != nullptr) { + + Gtk::MenuItem* menuitem = build_menu_item_from_verb(action, show_icon, true, &group); + if (menuitem) { + if (menu_ptr->attribute("default") != nullptr) { + auto radiomenuitem = dynamic_cast(menuitem); + if (radiomenuitem) { + radiomenuitem->set_active(true); + } + } + menu->append(*menuitem); + } + + } else { + + Gtk::MenuItem* menuitem = build_menu_item_from_verb(action, show_icon); + if (menuitem) { + menu->append(*menuitem); + } + } + } else { + std::cerr << "build_menu: no verb with id: " << verb_name << std::endl; + } + } + continue; + } + + // This is used only for wide-screen vs non-wide-screen displays. + // The code should be rewritten to use actions like everything else here. + if (name == "task-checkboxes") { + add_tasks(menu, static_cast(view)); + continue; + } + + if (name == "recent-file-list") { + + // Filter recent files to those already opened in Inkscape. + Glib::RefPtr recentfilter = Gtk::RecentFilter::create(); + recentfilter->add_application(g_get_prgname()); + + Gtk::RecentChooserMenu* recentchoosermenu = Gtk::manage(new Gtk::RecentChooserMenu()); + int max = Inkscape::Preferences::get()->getInt("/options/maxrecentdocuments/value"); + recentchoosermenu->set_limit(max); + recentchoosermenu->set_sort_type(Gtk::RECENT_SORT_MRU); // Sort most recent first. + recentchoosermenu->set_show_tips(true); + recentchoosermenu->set_show_not_found(false); + recentchoosermenu->add_filter(recentfilter); + recentchoosermenu->signal_item_activated().connect( + sigc::bind(sigc::ptr_fun(&sp_recent_open), recentchoosermenu)); + + Gtk::MenuItem* menuitem = Gtk::manage(new Gtk::MenuItem(_("Open _Recent"))); + menuitem->set_submenu(*recentchoosermenu); + menu->append(*menuitem); + continue; + } + + if (name == "separator") { + Gtk::MenuItem* menuitem = Gtk::manage(new Gtk::SeparatorMenuItem()); + menu->append(*menuitem); + continue; + } + + // Comments and items handled elsewhere. + if (name == "comment" || + name == "filters-list" || + name == "effects-list" ) { + continue; + } + + std::cerr << "build_menu: unhandled option: " << name << std::endl; + + } else { + std::cerr << "build_menu: xml node has no name!" << std::endl; + } + } + +} + +Gtk::MenuBar* +build_menubar(Inkscape::UI::View::View* view) +{ + Gtk::MenuBar* menubar = Gtk::manage(new Gtk::MenuBar()); + build_menu(menubar, INKSCAPE.get_menus()->parent(), view); + return menubar; +} + + +/* + 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 : diff --git a/src/ui/desktop/menubar.h b/src/ui/desktop/menubar.h new file mode 100644 index 000000000..cf50c7f88 --- /dev/null +++ b/src/ui/desktop/menubar.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_DESKTOP_MENUBAR_H +#define SEEN_DESKTOP_MENUBAR_H + +/** + * @file + * Desktop main menu bar code. + */ +/* + * 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. + * + */ + +namespace Gtk { + class MenuBar; +} + +namespace Inkscape { +namespace UI { +namespace View { + class View; +} +} +} + +Gtk::MenuBar* build_menubar(Inkscape::UI::View::View* view); + +#endif // SEEN_DESKTOP_MENUBAR_H + +/* + 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 : diff --git a/src/ui/interface.cpp b/src/ui/interface.cpp index 496a03bae..45736da52 100644 --- a/src/ui/interface.cpp +++ b/src/ui/interface.cpp @@ -20,27 +20,15 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#include -#include -#include - -#include "desktop-style.h" #include "desktop.h" -#include "document-undo.h" #include "document.h" #include "enums.h" #include "file.h" -#include "gradient-drag.h" #include "inkscape.h" #include "inkscape-window.h" -#include "message-context.h" -#include "message-stack.h" #include "preferences.h" -#include "selection-chemistry.h" #include "shortcuts.h" -#include "display/sp-canvas.h" - #include "extension/db.h" #include "extension/effect.h" #include "extension/find_extension_by_mime.h" @@ -48,48 +36,23 @@ #include "helper/action.h" -#include "ui/icon-loader.h" - #include "io/sys.h" -#include "object/sp-anchor.h" -#include "object/sp-clippath.h" -#include "object/sp-flowtext.h" -#include "object/sp-image.h" -#include "object/sp-item-group.h" -#include "object/sp-mask.h" #include "object/sp-namedview.h" #include "object/sp-root.h" -#include "object/sp-shape.h" -#include "object/sp-text.h" -#include "style.h" -#include "svg/svg-color.h" - -#include "ui/clipboard.h" -#include "ui/contextmenu.h" #include "ui/dialog-events.h" #include "ui/dialog/dialog-manager.h" #include "ui/dialog/inkscape-preferences.h" #include "ui/dialog/layer-properties.h" #include "ui/interface.h" -#include "ui/monitor.h" -#include "ui/tools/tool-base.h" -#include "ui/uxmanager.h" + #include "ui/view/svg-view-widget.h" #include "widgets/desktop-widget.h" -#include "widgets/ege-paint-def.h" - -using Inkscape::DocumentUndo; - -static bool temporarily_block_actions = false; static void sp_ui_import_one_file(char const *filename); static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused); -static void sp_ui_menu_item_set_name(GtkWidget *data, - Glib::ustring const &name); -static void sp_recent_open(GtkRecentChooser *, gpointer); void sp_ui_new_view() @@ -204,47 +167,6 @@ sp_ui_close_all() return TRUE; } -/* - * Some day when the right-click menus are ready to start working - * smarter with the verbs, we'll need to change this NULL being - * sent to sp_action_perform to something useful, or set some kind - * of global "right-clicked position" variable for actions to - * investigate when they're called. - */ -static void -sp_ui_menu_activate(void */*object*/, SPAction *action) -{ - if (!temporarily_block_actions) { - sp_action_perform(action, nullptr); - } -} - -static void -sp_ui_menu_select_action(void */*object*/, SPAction *action) -{ - sp_action_get_view(action)->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip); -} - -static void -sp_ui_menu_deselect_action(void */*object*/, SPAction *action) -{ - sp_action_get_view(action)->tipsMessageContext()->clear(); -} - -static void -sp_ui_menu_select(gpointer object, gpointer tip) -{ - Inkscape::UI::View::View *view = static_cast (g_object_get_data(G_OBJECT(object), "view")); - view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip); -} - -static void -sp_ui_menu_deselect(gpointer object) -{ - Inkscape::UI::View::View *view = static_cast (g_object_get_data(G_OBJECT(object), "view")); - view->tipsMessageContext()->clear(); -} - void sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c) @@ -274,114 +196,6 @@ sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c) } } -/* install CSS to shift icons into the space reserved for toggles (i.e. check and radio items) - * - * TODO: This code already exists as a C++ version in the class ContextMenu so we can simply wrap it here. - * In future ContextMenu and the (to be created) class for the menu bar should then be derived from one common base class. - */ -void shift_icons(GtkWidget *menu, gpointer /* user_data */) -{ - ContextMenu *contextmenu = static_cast(Glib::wrap(menu)); - contextmenu->ShiftIcons(); -} - -/** - * Appends a custom menu UI from a verb. - * - * @see ContextMenu::AppendItemFromVerb for a c++ified alternative. Consider dropping sp_ui_menu_append_item_from_verb when c++ifying interface.cpp. - * - * @param menu The menu to which the item will be appended - * @param verb The verb from which the item's label, action and icon (optionally) will be read - * @param view - * @param show_icon True if an icon should be displayed before the menu item's label - * @param radio True if a radio button should be displayed next to the menu item - * @param group The radio button group that the item should belong to - * - * @details The show_icon flag should be used very sparingly because menu icons are not recommended - * any longer under the GNOME HIG. Also, note that the text appears after the icon, and - * so will be indented relative to "normal" menu items. As such, menus will look best if - * all the items with icons are grouped together between a pair of separators. - */ -static GtkWidget *sp_ui_menu_append_item_from_verb(GtkMenu *menu, - Inkscape::Verb *verb, - Inkscape::UI::View::View *view, - bool show_icon = false, - bool radio = false, - Gtk::RadioMenuItem::Group *group = nullptr) -{ - Gtk::Widget *item; - - // Just create a menu separator if this isn't a real action. Otherwise, create a real menu item - if (verb->get_code() == SP_VERB_NONE) { - item = new Gtk::SeparatorMenuItem(); - } else { - SPAction *action = verb->get_action(Inkscape::ActionContext(view)); - - if (!action) return nullptr; - - // Create the menu item itself, either as a radio menu item, or just - // a regular menu item depending on whether the "radio" flag is set - if (radio) { - item = new Gtk::RadioMenuItem(*group); - } else { - item = new Gtk::MenuItem(); - } - - // Now create the label and add it to the menu item - GtkWidget *label = gtk_accel_label_new(action->name); - gtk_label_set_markup_with_mnemonic( GTK_LABEL(label), action->name); - -#if GTK_CHECK_VERSION(3,16,0) - gtk_label_set_xalign(GTK_LABEL(label), 0.0); -#else - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); -#endif - sp_shortcut_add_accelerator(item->gobj(), sp_shortcut_get_primary(verb)); - gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), item->gobj()); - - // If there is an image associated with the action, then we can add it as an icon for the menu item. - if (show_icon && action->image) { - item->set_name("ImageMenuItem"); // custom name to identify our "ImageMenuItems" - GtkWidget *icon = sp_get_icon_image(action->image, GTK_ICON_SIZE_MENU); - - // create a box to hold icon and label as GtkMenuItem derives from GtkBin and can only hold one child - GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_box_pack_start(GTK_BOX(box), icon, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); - - gtk_container_add(GTK_CONTAINER(item->gobj()), box); - } else { - gtk_container_add(GTK_CONTAINER(item->gobj()), label); - } - - action->signal_set_sensitive.connect( - sigc::bind<0>( - sigc::ptr_fun(>k_widget_set_sensitive), - item->gobj())); - action->signal_set_name.connect( - sigc::bind<0>( - sigc::ptr_fun(&sp_ui_menu_item_set_name), - item->gobj())); - - if (!action->sensitive) { - item->set_sensitive(false); - } - - gtk_widget_set_events(item->gobj(), GDK_KEY_PRESS_MASK); - - g_object_set_data(G_OBJECT(item->gobj()), "view", (gpointer) view); - g_signal_connect( G_OBJECT(item->gobj()), "activate", G_CALLBACK(sp_ui_menu_activate), action ); - g_signal_connect( G_OBJECT(item->gobj()), "select", G_CALLBACK(sp_ui_menu_select_action), action ); - g_signal_connect( G_OBJECT(item->gobj()), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action ); - } - - item->show_all(); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item->gobj()); - - return item->gobj(); - -} // end of sp_ui_menu_append_item_from_verb - Glib::ustring getLayoutPrefPath( Inkscape::UI::View::View *view ) { @@ -398,402 +212,6 @@ Glib::ustring getLayoutPrefPath( Inkscape::UI::View::View *view ) return prefPath; } -static void -checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data) -{ - gchar const *pref = (gchar const *) user_data; - Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); - SPAction *action = (SPAction *) g_object_get_data(G_OBJECT(menuitem), "action"); - - if (action) { - - sp_ui_menu_activate(menuitem, action); - - } else if (pref) { - // All check menu items should have actions now, but just in case - Glib::ustring pref_path = getLayoutPrefPath( view ); - pref_path += pref; - pref_path += "/state"; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - gboolean checked = gtk_check_menu_item_get_active(menuitem); - prefs->setBool(pref_path, checked); - - reinterpret_cast(view)->layoutWidget(); - } -} - -static bool getViewStateFromPref(Inkscape::UI::View::View *view, gchar const *pref) -{ - Glib::ustring pref_path = getLayoutPrefPath( view ); - pref_path += pref; - pref_path += "/state"; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - return prefs->getBool(pref_path, true); -} - -static gboolean checkitem_update(GtkWidget *widget, cairo_t * /*cr*/, gpointer user_data) -{ - GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget); - - gchar const *pref = (gchar const *) user_data; - Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); - SPAction *action = (SPAction *) g_object_get_data(G_OBJECT(menuitem), "action"); - SPDesktop *dt = static_cast(view); - - bool ison = false; - if (action) { - - if (!strcmp(action->id, "ToggleGrid")) { - ison = dt->gridsEnabled(); - } else if (!strcmp(action->id, "EditGuidesToggleLock")) { - ison = dt->namedview->lockguides; - } else if (!strcmp(action->id, "ToggleGuides")) { - ison = dt->namedview->getGuides(); - } else if (!strcmp(action->id, "ViewCmsToggle")) { - ison = dt->colorProfAdjustEnabled(); - } else if (!strcmp(action->id, "ViewSplitModeToggle")) { - ison = dt->splitMode(); - } else if (!strcmp(action->id, "ViewXRayToggle")) { - ison = dt->xrayMode(); - } else { - ison = getViewStateFromPref(view, pref); - } - } else if (pref) { - // The Show/Hide menu items without actions - ison = getViewStateFromPref(view, pref); - } - - g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data); - gtk_check_menu_item_set_active(menuitem, ison); - g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data); - - return FALSE; -} - - -static void taskToggled(GtkCheckMenuItem *menuitem, gpointer userData) -{ - if ( gtk_check_menu_item_get_active(menuitem) ) { - gint taskNum = GPOINTER_TO_INT(userData); - taskNum = (taskNum < 0) ? 0 : (taskNum > 2) ? 2 : taskNum; - - Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); - - // note: this will change once more options are in the task set support: - Inkscape::UI::UXManager::getInstance()->setTask( dynamic_cast(view), taskNum ); - } -} - - -/** - * Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline) and Color display mode. - */ -static gboolean update_view_menu(GtkWidget *widget, cairo_t * /*cr*/, gpointer user_data) -{ - SPAction *action = (SPAction *) user_data; - g_assert(action->id != nullptr); - - Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view"); - SPDesktop *dt = static_cast(view); - Inkscape::RenderMode mode = dt->getMode(); - Inkscape::ColorMode colormode = dt->getColorMode(); - - bool new_state = false; - if (!strcmp(action->id, "ViewModeNormal")) { - new_state = mode == Inkscape::RENDERMODE_NORMAL; - } else if (!strcmp(action->id, "ViewModeNoFilters")) { - new_state = mode == Inkscape::RENDERMODE_NO_FILTERS; - } else if (!strcmp(action->id, "ViewModeOutline")) { - new_state = mode == Inkscape::RENDERMODE_OUTLINE; - } else if (!strcmp(action->id, "ViewModeVisibleHairlines")) { - new_state = mode == Inkscape::RENDERMODE_VISIBLE_HAIRLINES; - } else if (!strcmp(action->id, "ViewColorModeNormal")) { - new_state = colormode == Inkscape::COLORMODE_NORMAL; - } else if (!strcmp(action->id, "ViewColorModeGrayscale")) { - new_state = colormode == Inkscape::COLORMODE_GRAYSCALE; - } else if (!strcmp(action->id, "ViewColorModePrintColorsPreview")) { - new_state = colormode == Inkscape::COLORMODE_PRINT_COLORS_PREVIEW; - } else { - g_warning("update_view_menu does not handle this verb"); - } - - if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated - if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) { - // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second - // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled. - // This emission however should not invoke any actions, hence we block it here: - temporarily_block_actions = true; - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE); - temporarily_block_actions = false; - } - } - - return FALSE; -} - -static void -sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *pref, Inkscape::Verb *verb) -{ - unsigned int shortcut = sp_shortcut_get_primary(verb); - SPAction *action = verb->get_action(Inkscape::ActionContext(view)); - GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(action->name); - - sp_shortcut_add_accelerator(item, shortcut); - - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - - g_object_set_data(G_OBJECT(item), "view", (gpointer) view); - g_object_set_data(G_OBJECT(item), "action", (gpointer) action); - - g_signal_connect( G_OBJECT(item), "toggled", (GCallback) checkitem_toggled, (void *) pref); - g_signal_connect( G_OBJECT(item), "draw", (GCallback) checkitem_update, (void *) pref); - - checkitem_update(item, nullptr, (void *)pref); - - g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action ); - g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action ); -} - -static void -sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/) -{ - // dealing with the bizarre filename convention in Inkscape for now - gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu)); - gchar *local_fn = g_filename_from_uri(uri, nullptr, nullptr); - gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, nullptr, nullptr, nullptr); - sp_file_open(utf8_fn, nullptr); - g_free(utf8_fn); - g_free(local_fn); - g_free(uri); -} - -static void -sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view) -{ - //sp_ui_menu_append_check_item_from_verb(m, view, "menu", 0); - sp_ui_menu_append_check_item_from_verb(m, view, "commands", - Inkscape::Verb::get(SP_VERB_TOGGLE_COMMANDS_TOOLBAR)); - sp_ui_menu_append_check_item_from_verb(m, view,"snaptoolbox", - Inkscape::Verb::get(SP_VERB_TOGGLE_SNAP_TOOLBAR)); - sp_ui_menu_append_check_item_from_verb(m, view, "toppanel", - Inkscape::Verb::get(SP_VERB_TOGGLE_TOOL_TOOLBAR)); - sp_ui_menu_append_check_item_from_verb(m, view, "toolbox", - Inkscape::Verb::get(SP_VERB_TOGGLE_TOOLBOX)); - sp_ui_menu_append_check_item_from_verb(m, view, "rulers", - Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS)); - sp_ui_menu_append_check_item_from_verb(m, view, "scrollbars", - Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS)); - sp_ui_menu_append_check_item_from_verb(m, view, "panels", - Inkscape::Verb::get(SP_VERB_TOGGLE_PALETTE)); - sp_ui_menu_append_check_item_from_verb(m, view, "statusbar", - Inkscape::Verb::get(SP_VERB_TOGGLE_STATUSBAR)); -} - - -static void addTaskMenuItems(GtkMenu *menu, Inkscape::UI::View::View *view) -{ - gchar const* data[] = { - C_("Interface setup", "Default"), _("Default interface setup"), - C_("Interface setup", "Custom"), _("Setup for custom task"), - C_("Interface setup", "Wide"), _("Setup for widescreen work"), - nullptr, nullptr - }; - - Gtk::RadioMenuItem::Group group; - int count = 0; - gint active = Inkscape::UI::UXManager::getInstance()->getDefaultTask( dynamic_cast(view) ); - for (gchar const **strs = data; strs[0]; strs += 2, count++) - { - Gtk::RadioMenuItem *item = new Gtk::RadioMenuItem(group,Glib::ustring(strs[0])); - if ( count == active ) - { - item->set_active(); - } - - g_object_set_data( G_OBJECT(item->gobj()), "view", view ); - g_signal_connect( G_OBJECT(item->gobj()), "toggled", reinterpret_cast(taskToggled), GINT_TO_POINTER(count) ); - g_signal_connect( G_OBJECT(item->gobj()), "select", G_CALLBACK(sp_ui_menu_select), const_cast(strs[1]) ); - g_signal_connect( G_OBJECT(item->gobj()), "deselect", G_CALLBACK(sp_ui_menu_deselect), 0 ); - - item->show(); - gtk_menu_shell_append( GTK_MENU_SHELL(menu), GTK_WIDGET(item->gobj()) ); - } -} - - -/** - * Observer that updates the recent list's max document count. - */ -class MaxRecentObserver : public Inkscape::Preferences::Observer { -public: - MaxRecentObserver(GtkWidget *recent_menu) : - Observer("/options/maxrecentdocuments/value"), - _rm(recent_menu) - {} - void notify(Inkscape::Preferences::Entry const &e) override { - gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt()); - // hack: the recent menu doesn't repopulate after changing the limit, so we force it - g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed"); - } -private: - GtkWidget *_rm; -}; - -/** - * This function turns XML into a menu. - * - * This function is realitively simple as it just goes through the XML - * and parses the individual elements. In the case of a submenu, it - * just calls itself recursively. Because it is only reasonable to have - * a couple of submenus, it is unlikely this will go more than two or - * three times. - * - * In the case of an unrecognized verb, a menu item is made to identify - * the verb that is missing, and display that. The menu item is also made - * insensitive. - * - * @param menus This is the XML that defines the menu - * @param menu Menu to be added to - * @param view The View that this menu is being built for - * @param show_icons Whether to show icons (can be overridden by the current XML::Node and preferences) - */ -static void sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view, - bool show_icons = true) -{ - if (menus == nullptr) return; - if (menu == nullptr) return; - Gtk::RadioMenuItem::Group group; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - int show_icons_pref = prefs->getInt("/theme/menuIcons", 0); - - for (Inkscape::XML::Node *menu_pntr = menus; menu_pntr != nullptr; menu_pntr = menu_pntr->next()) { - - // Check if the "show-icons" attribute is set, and set the flag here accordingly - bool show_icons_curr = show_icons; - if (show_icons_pref == 1) { // enable all icons - show_icons_curr = true; - } else if (show_icons_pref == -1) { // disable all icons - show_icons_curr = false; - } else { // read from XML file or inherit (=keep) previous value if unset - const char *str = menu_pntr->attribute("show-icons"); - if (str) { - if (!g_ascii_strcasecmp(str, "yes") || !g_ascii_strcasecmp(str, "true") || !g_ascii_strcasecmp(str, "1")) { - show_icons_curr = true; - } else if (!g_ascii_strcasecmp(str, "no") || !g_ascii_strcasecmp(str, "false") || !g_ascii_strcasecmp(str, "0")) { - show_icons_curr = false; - } else { - g_warning("Invalid value for attribute 'show-icons': '%s'", str); - } - } - } - - if (!strcmp(menu_pntr->name(), "inkscape")) { - sp_ui_build_dyn_menus(menu_pntr->firstChild(), menu, view, show_icons_curr); - } - if (!strcmp(menu_pntr->name(), "submenu")) { - GtkWidget *mitem; - if (menu_pntr->attribute("_name") != nullptr) { - mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("_name"))); - } else { - mitem = gtk_menu_item_new_with_mnemonic(menu_pntr->attribute("name")); - } - GtkWidget *submenu = gtk_menu_new(); - sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view, show_icons_curr); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem); - g_signal_connect(submenu, "map", G_CALLBACK(shift_icons), NULL); - continue; - } - if (!strcmp(menu_pntr->name(), "verb")) { - gchar const *verb_name = menu_pntr->attribute("verb-id"); - Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name); - - if (verb != nullptr) { - if (menu_pntr->attribute("radio") != nullptr) { - GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, false, true, &group); - if (menu_pntr->attribute("default") != nullptr) { - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); - } - if (verb->get_code() != SP_VERB_NONE) { - SPAction *action = verb->get_action(Inkscape::ActionContext(view)); - g_signal_connect( G_OBJECT(item), "draw", (GCallback) update_view_menu, (void *) action); - } - } else if (menu_pntr->attribute("check") != nullptr) { - if (verb->get_code() != SP_VERB_NONE) { - SPAction *action = verb->get_action(Inkscape::ActionContext(view)); - sp_ui_menu_append_check_item_from_verb(GTK_MENU(menu), view, nullptr, verb); - } - } else { - sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view, show_icons_curr); - Gtk::RadioMenuItem::Group group2; - group = group2; - } - } else { - gchar string[120]; - g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name); - string[119] = '\0'; /* may not be terminated */ - GtkWidget *item = gtk_menu_item_new_with_label(string); - gtk_widget_set_sensitive(item, false); - gtk_widget_show(item); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - } - continue; - } - if (!strcmp(menu_pntr->name(), "separator")) { - GtkWidget *item = gtk_separator_menu_item_new(); - gtk_widget_show(item); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - continue; - } - - if (!strcmp(menu_pntr->name(), "recent-file-list")) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - // create recent files menu - int max_recent = prefs->getInt("/options/maxrecentdocuments/value"); - GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default()); - gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent); - // sort most recently used documents first to preserve previous behavior - gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU); - g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) nullptr); - - // add filter to only open files added by Inkscape - GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new(); - gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname()); - gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter); - - gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER(recent_menu), TRUE); - gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER(recent_menu), FALSE); - - GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent")); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu); - - gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(recent_item)); - // this will just sit and update the list's item count - static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu); - prefs->addObserver(*mro); - continue; - } - if (!strcmp(menu_pntr->name(), "objects-checkboxes")) { - sp_ui_checkboxes_menus(GTK_MENU(menu), view); - continue; - } - if (!strcmp(menu_pntr->name(), "task-checkboxes")) { - addTaskMenuItems(GTK_MENU(menu), view); - continue; - } - } -} - -GtkWidget *sp_ui_main_menubar(Inkscape::UI::View::View *view) -{ - GtkWidget *mbar = gtk_menu_bar_new(); - sp_ui_build_dyn_menus(INKSCAPE.get_menus()->parent(), mbar, view); - return mbar; -} void sp_ui_import_files(gchar *buffer) @@ -883,35 +301,6 @@ sp_ui_overwrite_file(gchar const *filename) return return_value; } -static void -sp_ui_menu_item_set_name(GtkWidget *data, Glib::ustring const &name) -{ - if (data || GTK_IS_BIN (data)) { - void *child = gtk_bin_get_child (GTK_BIN (data)); - // child is either - // - a GtkLabel (if the menu has no accel key or icon) - // - a GtkBox (if item has an accel key or image) - // in which case we need to find the GtkLabel in the box - // - something else we do not expect (yet) - if (child != nullptr) { - if (GTK_IS_LABEL(child)) { - gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str()); - } else if (GTK_IS_BOX(child)) { - std::vector children = Glib::wrap(GTK_CONTAINER(child))->get_children(); - for (auto child: children) { - Gtk::Label *label = dynamic_cast(child); - if (label) { - label->set_markup_with_mnemonic(name); - break; - } - } - } else { - // sp_ui_menu_append_item_from_verb might have learned to set a menu item in yet another way... - g_assert_not_reached(); - } - } - } -} /* Local Variables: mode:c++ diff --git a/src/ui/interface.h b/src/ui/interface.h index eb75fe7d6..67cb0f393 100644 --- a/src/ui/interface.h +++ b/src/ui/interface.h @@ -54,18 +54,6 @@ void sp_ui_import_files(gchar *buffer); */ unsigned int sp_ui_close_all (); -/** - * Build the main tool bar. - * - * Currently the main tool bar is built as a dynamic XML menu using - * \c sp_ui_build_dyn_menus. This function builds the bar, and then - * pass it to get items attached to it. - * - * @param view View to build the bar for - */ -GtkWidget *sp_ui_main_menubar (Inkscape::UI::View::View *view); - -void sp_menu_append_recent_documents (GtkWidget *menu); void sp_ui_dialog_title_string (Inkscape::Verb * verb, char* c); Glib::ustring getLayoutPrefPath( Inkscape::UI::View::View *view ); -- cgit v1.2.3