summaryrefslogtreecommitdiffstats
path: root/src/ui/desktop
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/ui/desktop/README27
-rw-r--r--src/ui/desktop/document-check.cpp140
-rw-r--r--src/ui/desktop/document-check.h28
-rw-r--r--src/ui/desktop/menu-icon-shift.cpp153
-rw-r--r--src/ui/desktop/menu-icon-shift.h46
-rw-r--r--src/ui/desktop/menubar.cpp322
-rw-r--r--src/ui/desktop/menubar.h49
7 files changed, 765 insertions, 0 deletions
diff --git a/src/ui/desktop/README b/src/ui/desktop/README
new file mode 100644
index 0000000..e2932ca
--- /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
+ Palette (maybe turn into dialog).
+ Status bar
diff --git a/src/ui/desktop/document-check.cpp b/src/ui/desktop/document-check.cpp
new file mode 100644
index 0000000..6a8a809
--- /dev/null
+++ b/src/ui/desktop/document-check.cpp
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Check for data loss when closing a document window.
+ *
+ * Copyright (C) 2004-2021 Authors
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+/* Authors:
+ * MenTaLguY
+ * Link Mauve
+ * Thomas Holder
+ * Tavmjong Bah
+ */
+
+#include "document-check.h"
+
+#include <glibmm/i18n.h> // Internationalization
+#include <gtkmm.h>
+
+#include "inkscape-window.h"
+#include "object/sp-namedview.h"
+
+#include "file.h"
+#include "extension/system.h" // Inkscape::Extension::FILE...
+
+/** Check if closing document associated with window will cause data loss, and if so opens a dialog
+ * that gives user options to save or ignore.
+ *
+ * Returns true if document should remain open.
+ */
+bool
+document_check_for_data_loss(InkscapeWindow* window)
+{
+ auto document = window->get_document();
+
+ if (document->isModifiedSinceSave()) {
+ // Document has been modified!
+
+ Glib::ustring message = g_markup_printf_escaped(
+ _("<span weight=\"bold\" size=\"larger\">Save changes to document \"%s\" before closing?</span>\n\n"
+ "If you close without saving, your changes will be discarded."),
+ document->getDocumentName());
+
+ Gtk::MessageDialog dialog =
+ Gtk::MessageDialog(*window, message, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
+ dialog.property_destroy_with_parent() = true;
+
+ // Don't allow text to be selected (via tabbing).
+ Gtk::Container *ma = dialog.get_message_area();
+ std::vector<Gtk::Widget*> ma_labels = ma->get_children();
+ ma_labels[0]->set_can_focus(false);
+
+ dialog.add_button(_("Close _without saving"), Gtk::RESPONSE_NO);
+ dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
+ dialog.add_button(_("_Save"), Gtk::RESPONSE_YES);
+ dialog.set_default_response(Gtk::RESPONSE_YES);
+
+ int response = dialog.run();
+
+ switch (response) {
+ case GTK_RESPONSE_YES:
+ {
+ // Save document
+ sp_namedview_document_from_window(window->get_desktop()); // Save window geometry in document.
+ if (!sp_file_save_document(*window, document)) {
+ // Save dialog cancelled or save failed.
+ return true;
+ }
+ break;
+ }
+ case GTK_RESPONSE_NO:
+ break;
+ default: // cancel pressed, or dialog was closed
+ return true;
+ break;
+ }
+ }
+
+ // Check for data loss due to saving in lossy format.
+ bool allow_data_loss = false;
+ while (document->getReprRoot()->attribute("inkscape:dataloss") != nullptr && allow_data_loss == false) {
+ // This loop catches if the user saves to a lossy format when in the loop.
+
+ Glib::ustring message = g_markup_printf_escaped(
+ _("<span weight=\"bold\" size=\"larger\">The file \"%s\" was saved with a format that may cause data loss!</span>\n\n"
+ "Do you want to save this file as Inkscape SVG?"),
+ document->getDocumentName() ? document->getDocumentName() : "Unnamed");
+
+ Gtk::MessageDialog dialog =
+ Gtk::MessageDialog(*window, message, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
+ dialog.property_destroy_with_parent() = true;
+
+ // Don't allow text to be selected (via tabbing).
+ Gtk::Container *ma = dialog.get_message_area();
+ std::vector<Gtk::Widget*> ma_labels = ma->get_children();
+ ma_labels[0]->set_can_focus(false);
+
+ dialog.add_button(_("Close _without saving"), Gtk::RESPONSE_NO);
+ dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
+ dialog.add_button(_("_Save as Inkscape SVG"), Gtk::RESPONSE_YES);
+ dialog.set_default_response(Gtk::RESPONSE_YES);
+
+ int response = dialog.run();
+
+ switch (response) {
+ case GTK_RESPONSE_YES:
+ {
+ if (!sp_file_save_dialog(*window, document, Inkscape::Extension::FILE_SAVE_METHOD_INKSCAPE_SVG)) {
+ // Save dialog cancelled or save failed.
+ return TRUE;
+ }
+
+ break;
+ }
+ case GTK_RESPONSE_NO:
+ allow_data_loss = true;
+ break;
+ default: // cancel pressed, or dialog was closed
+ return true;
+ break;
+ }
+ }
+
+ return false;
+}
+
+
+/*
+ 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 :
diff --git a/src/ui/desktop/document-check.h b/src/ui/desktop/document-check.h
new file mode 100644
index 0000000..3fa1e5d
--- /dev/null
+++ b/src/ui/desktop/document-check.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Check for data loss when closing a document window.
+ *
+ * Copyright (C) 2021 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef DOCUMENT_CHECK_H
+
+class InkscapeWindow;
+
+bool document_check_for_data_loss(InkscapeWindow* window);
+
+#endif // DOCUMENT_CHECK_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 :
diff --git a/src/ui/desktop/menu-icon-shift.cpp b/src/ui/desktop/menu-icon-shift.cpp
new file mode 100644
index 0000000..0da36d5
--- /dev/null
+++ b/src/ui/desktop/menu-icon-shift.cpp
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Shift Gtk::MenuItems with icons to align with Toggle and Radio buttons.
+ */
+/*
+ * Authors:
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2020 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 "menu-icon-shift.h"
+
+#include <iostream>
+#include <gtkmm.h>
+
+#include "inkscape-application.h" // Action extra data
+
+// Could be used to update status bar.
+// bool on_enter_notify(GdkEventCrossing* crossing_event, Gtk::MenuItem* menuitem)
+// {
+// return false;
+// }
+
+/*
+ * Install CSS to shift icons into the space reserved for toggles (i.e. check and radio items).
+ * The CSS will apply to all menu icons but is updated as each menu is shown.
+ */
+void
+shift_icons(Gtk::MenuShell* menu)
+{
+ static Glib::RefPtr<Gtk::CssProvider> provider;
+ if (!provider) {
+ provider = Gtk::CssProvider::create();
+ auto const screen = Gdk::Screen::get_default();
+ Gtk::StyleContext::add_provider_for_screen(screen, provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ // Calculate required shift. We need an example!
+ // Search for Gtk::MenuItem -> Gtk::Box -> Gtk::Image
+ for (auto child : menu->get_children()) {
+
+ auto menuitem = dynamic_cast<Gtk::MenuItem *>(child);
+ if (menuitem) {
+
+ auto box = dynamic_cast<Gtk::Box *>(menuitem->get_child());
+ if (box) {
+
+ box->set_spacing(0); // Match ImageMenuItem
+
+ auto children = box->get_children();
+ if (children.size() == 2) { // Image + Label
+
+ auto image = dynamic_cast<Gtk::Image *>(box->get_children()[0]);
+ if (image) {
+
+ // OK, we have an example, do calculation.
+ auto allocation_menuitem = menuitem->get_allocation();
+ auto allocation_image = image->get_allocation();
+
+ int shift = -allocation_image.get_x();
+ if (menuitem->get_direction() == Gtk::TEXT_DIR_RTL) {
+ shift += (allocation_menuitem.get_width() - allocation_image.get_width());
+ }
+
+ static int current_shift = 0;
+ if (std::abs(current_shift - shift) > 2) {
+ // Only do this once per menu, and only if there is a large change.
+ current_shift = shift;
+ std::string css_str;
+ if (menuitem->get_direction() == Gtk::TEXT_DIR_RTL) {
+ css_str = "menuitem box image {margin-right:" + std::to_string(shift) + "px;}";
+ } else {
+ css_str = "menuitem box image {margin-left:" + std::to_string(shift) + "px;}";
+ }
+ provider->load_from_data(css_str);
+ }
+ }
+ }
+ }
+ }
+ }
+ // If we get here, it means there were no examples... but we don't care as there are no icons to shift anyway.
+}
+
+/*
+ * Find all submenus to add "shift_icons" callback. We need to do this for
+ * all submenus as some submenus are children of other submenus without icons.
+ */
+void
+shift_icons_recursive(Gtk::MenuShell *menu)
+{
+ if (menu) {
+
+ // Connect signal
+ menu->signal_map().connect(sigc::bind<Gtk::MenuShell *>(sigc::ptr_fun(&shift_icons), menu));
+
+ static auto app = InkscapeApplication::instance();
+ auto label_to_tooltip_map = app->get_menu_label_to_tooltip_map();
+
+ // Look for descendent menus.
+ auto children = menu->get_children(); // Should be Gtk::MenuItem's
+ for (auto child : children) {
+ auto menuitem = dynamic_cast<Gtk::MenuItem *>(child);
+ if (menuitem) {
+
+ // Use label as alternative way to figure out tooltip.
+ auto label = menuitem->get_label();
+ if (label.empty()) {
+ auto container = menuitem->get_child();
+ auto box = dynamic_cast<Gtk::Box *>(container);
+ if (box) {
+ std::vector<Gtk::Widget *> children = box->get_children();
+ if (children.size() == 2) {
+ auto label_widget = dynamic_cast<Gtk::Label *>(children[1]);
+ if (label_widget) {
+ label = label_widget->get_label();
+ }
+ }
+ }
+ }
+
+ auto it = label_to_tooltip_map.find(label);
+ if (it != label_to_tooltip_map.end()) {
+ menuitem->set_tooltip_text(it->second);
+ }
+
+ auto submenu = menuitem->get_submenu();
+ if (submenu) {
+ shift_icons_recursive(submenu);
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ 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/menu-icon-shift.h b/src/ui/desktop/menu-icon-shift.h
new file mode 100644
index 0000000..78462a8
--- /dev/null
+++ b/src/ui/desktop/menu-icon-shift.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_DESKTOP_MENU_ITEM_SHIFT_H
+#define SEEN_DESKTOP_MENU_ITEM_SHIFT_H
+
+/**
+ * @file
+ * Shift Gtk::MenuItems with icons to align with Toggle and Radio buttons.
+ */
+/*
+ * Authors:
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2020 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 MenuShell;
+}
+
+/**
+ * Call back to shift icons into place reserved for toggles (i.e. check and radio items).
+ */
+void shift_icons(Gtk::MenuShell *menu);
+
+/**
+ * Add callbacks recursively to menus.
+ */
+void shift_icons_recursive(Gtk::MenuShell *menu);
+
+#endif // SEEN_DESKTOP_MENU_ITEM_SHIFT_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/desktop/menubar.cpp b/src/ui/desktop/menubar.cpp
new file mode 100644
index 0000000..a686070
--- /dev/null
+++ b/src/ui/desktop/menubar.cpp
@@ -0,0 +1,322 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Desktop main menu bar code.
+ */
+/*
+ * Authors:
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Alex Valavanis <valavanisalex@gmail.com>
+ * Patrick Storz <eduard.braun2@gmx.de>
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ * Sushant A.A. <sushant.co19@gmail.com>
+ *
+ * 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 "menubar.h"
+
+#include <iostream>
+#include <iomanip>
+#include <map>
+#include <regex>
+
+#include <glibmm/i18n.h>
+
+#include "inkscape-application.h" // Open recent
+#include "preferences.h" // Use icons or not
+#include "io/resource.h" // UI File location
+
+// =================== Main Menu ================
+void
+build_menu()
+{
+ std::string filename = Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::UIS, "menus.ui");
+ auto refBuilder = Gtk::Builder::create();
+
+ try
+ {
+ refBuilder->add_from_file(filename);
+ }
+ catch (const Glib::Error& err)
+ {
+ std::cerr << "build_menu: failed to load Main menu from: "
+ << filename <<": "
+ << err.what().raw() << std::endl;
+ }
+
+ const auto object = refBuilder->get_object("menus");
+#if GTK_CHECK_VERSION(4, 0 ,0)
+ const auto gmenu = std::dynamic_pointer_cast<Gio::Menu>(object);
+#else
+ const auto gmenu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
+#endif
+
+ if (!gmenu) {
+ std::cerr << "build_menu: failed to build Main menu!" << std::endl;
+ } else {
+
+ static auto app = InkscapeApplication::instance();
+ std::map<Glib::ustring, Glib::ustring>& label_to_tooltip_map = app->get_menu_label_to_tooltip_map();
+ label_to_tooltip_map.clear();
+
+ { // Filters and Extensions
+
+ auto effects_object = refBuilder->get_object("effect-menu-effects");
+ auto filters_object = refBuilder->get_object("filter-menu-filters");
+ auto effects_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(effects_object);
+ auto filters_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(filters_object);
+
+ if (!filters_menu) {
+ std::cerr << "build_menu(): Couldn't find Filters menu entry!" << std::endl;
+ }
+ if (!effects_menu) {
+ std::cerr << "build_menu(): Couldn't find Extensions menu entry!" << std::endl;
+ }
+
+ std::map<Glib::ustring, Glib::RefPtr<Gio::Menu>> submenus;
+
+ for (auto &[ entry_id, submenu_name_list, entry_name ] : app->get_action_effect_data().give_all_data())
+ {
+ if (submenu_name_list.size() > 0) {
+
+ // Effect data is used for both filters menu and extensions menu... we need to
+ // add to correct menu. 'submenu_name_list' either starts with 'Effects' or 'Filters'.
+ // Note "Filters" is translated!
+ Glib::ustring path; // Only used as index to map of submenus.
+ auto top_menu = filters_menu;
+ if (submenu_name_list.front() == "Effects") {
+ top_menu = effects_menu;
+ path += "Effects";
+ } else {
+ path += "Filters";
+ }
+ submenu_name_list.pop_front();
+
+ if (top_menu) { // It's possible that the menu doesn't exist (Kid's Inkscape?)
+ auto current_menu = top_menu;
+ for (auto &submenu_name : submenu_name_list) {
+ path += submenu_name + "-";
+ auto it = submenus.find(path);
+ if (it == submenus.end()) {
+ auto new_gsubmenu = Gio::Menu::create();
+ submenus[path] = new_gsubmenu;
+ current_menu->append_submenu(submenu_name, new_gsubmenu);
+ current_menu = new_gsubmenu;
+ } else {
+ current_menu = it->second;
+ }
+ }
+ current_menu->append(entry_name, "app." + entry_id);
+ } else {
+ std::cerr << "build_menu(): menu doesn't exist!" << std::endl; // Warn for now.
+ }
+ }
+ }
+ }
+
+ // Recent file
+ auto recent_manager = Gtk::RecentManager::get_default();
+
+ auto sub_object = refBuilder->get_object("recent-files");
+ auto sub_gmenu = Glib::RefPtr<Gio::Menu>::cast_dynamic(sub_object);
+ auto recent_menu_quark = Glib::Quark("recent-manager");
+ sub_gmenu->set_data(recent_menu_quark, recent_manager.get()); // mark submenu, so we can find it
+
+ auto rebuild = [](Glib::RefPtr<Gio::Menu> submenu) {
+ auto recent_manager = Gtk::RecentManager::get_default();
+ submenu->remove_all();
+ int max_files = Inkscape::Preferences::get()->getInt("/options/maxrecentdocuments/value");
+ if (max_files <= 0) {
+ return;
+ }
+
+ auto recent_files = recent_manager->get_items(); // all recent files not necessarily inkscape only
+ // sort by "last modified" time, which puts the most recently opened files first
+ std::sort(begin(recent_files), end(recent_files),
+ [](Glib::RefPtr<Gtk::RecentInfo> a, Glib::RefPtr<Gtk::RecentInfo> b) -> bool {
+ return a->get_modified() > b->get_modified();
+ }
+ );
+
+ unsigned inserted_entries = 0;
+ for (auto const &recent_file : recent_files) {
+ // check if given was generated by inkscape
+ bool valid_file = recent_file->has_application(g_get_prgname()) ||
+ recent_file->has_application("org.inkscape.Inkscape") ||
+ recent_file->has_application("inkscape")
+#ifdef _WIN32
+ || recent_file->has_application("inkscape.exe")
+#endif
+ ;
+
+ // this is potentially expensive: local FS access (remote files are not checked)
+ valid_file = valid_file && recent_file->exists();
+
+ if (!valid_file) {
+ continue;
+ }
+
+ // Escape underscores to prevent them from being interpreted as accelerator mnemonics
+ std::regex underscore{"_"};
+ std::string const dunder{"__"};
+ std::string raw_name = recent_file->get_short_name();
+ Glib::ustring escaped = std::regex_replace(raw_name, underscore, dunder);
+
+ auto item { Gio::MenuItem::create(std::move(escaped), Glib::ustring()) };
+ auto target { Glib::Variant<Glib::ustring>::create(recent_file->get_uri_display()) };
+ // note: setting action and target separately rather than using convenience menu method append
+ // since some filename characters can result in invalid "direct action" string
+ item->set_action_and_target(Glib::ustring("app.file-open-window"), target);
+ submenu->append_item(item);
+ inserted_entries++;
+
+ if (--max_files == 0) {
+ break;
+ }
+ }
+
+ if (!inserted_entries) { // Create a placeholder with a non-existent action
+ auto nothing2c = Gio::MenuItem::create(_("No items found"), "app.nop");
+ submenu->append_item(nothing2c);
+ }
+ };
+
+ rebuild(sub_gmenu);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ auto useicons = static_cast<UseIcons>(prefs->getInt("/theme/menuIcons", 0));
+
+ // Remove all or some icons. Also create label to tooltip map.
+ auto gmenu_copy = Gio::Menu::create();
+ // menu gets recreated; keep track of new recent items submenu
+ rebuild_menu(gmenu, gmenu_copy, useicons, recent_menu_quark, sub_gmenu);
+ app->gtk_app()->set_menubar(gmenu_copy);
+
+ // rebuild recent items submenu when the list changes
+ recent_manager->signal_changed().connect([=](){ rebuild(sub_gmenu); });
+ }
+}
+
+
+/*
+ * Disable all or some menu icons.
+ *
+ * This is quite nasty:
+ *
+ * We must disable icons in the Gio::Menu as there is no way to pass
+ * the needed information to the children of Gtk::PopoverMenu and no
+ * way to set visibility via CSS.
+ *
+ * MenuItems are immutable and not copyable so you have to recreate
+ * the menu tree. The format for accessing MenuItem data is not the
+ * same as what you need to create a new MenuItem.
+ *
+ * NOTE: Input is a Gio::MenuModel, Output is a Gio::Menu!!
+ */
+#if GTK_CHECK_VERSION(4, 0, 0)
+void rebuild_menu (std::shared_ptr<Gio::MenuModel> menu, std::shared_ptr<Gio::Menu> menu_copy, UseIcons useIcons, Glib::Quark quark, Glib::RefPtr<Gio::Menu>& recent_files) {
+#else
+void rebuild_menu (Glib::RefPtr<Gio::MenuModel> menu, Glib::RefPtr<Gio::Menu> menu_copy, UseIcons useIcons, Glib::Quark quark, Glib::RefPtr<Gio::Menu>& recent_files) {
+#endif
+
+ static auto app = InkscapeApplication::instance();
+ auto& extra_data = app->get_action_extra_data();
+ auto& label_to_tooltip_map = app->get_menu_label_to_tooltip_map();
+
+ for (int i = 0; i < menu->get_n_items(); ++i) {
+
+ Glib::ustring label;
+ Glib::ustring action;
+ Glib::ustring target;
+ Glib::VariantBase icon;
+ Glib::ustring use_icon;
+ std::map<Glib::ustring, Glib::VariantBase> attributes;
+
+ auto attribute_iter = menu->iterate_item_attributes(i);
+ while (attribute_iter->next()) {
+
+ // Attributes we need to create MenuItem or set icon.
+ if (attribute_iter->get_name() == "label") {
+ // Convert label while preserving unicode translations
+ label = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string> >(attribute_iter->get_value()).get();
+ } else if (attribute_iter->get_name() == "action") {
+ action = attribute_iter->get_value().print();
+ action.erase(0, 1);
+ action.erase(action.size()-1, 1);
+ } else if (attribute_iter->get_name() == "target") {
+ target = attribute_iter->get_value().print();
+ } else if (attribute_iter->get_name() == "icon") {
+ icon = attribute_iter->get_value();
+ } else if (attribute_iter->get_name() == "use-icon") {
+ use_icon = attribute_iter->get_value().print();
+ } else {
+ // All the remaining attributes.
+ attributes[attribute_iter->get_name()] = attribute_iter->get_value();
+ }
+ }
+ Glib::ustring detailed_action = action;
+ if (target.size() > 0) {
+ detailed_action += "(" + target + ")";
+ }
+
+ auto tooltip = extra_data.get_tooltip_for_action(detailed_action);
+ label_to_tooltip_map[label] = tooltip;
+
+ // std::cout << " " << std::setw(30) << detailed_action
+ // << " label: " << std::setw(30) << label.c_str()
+ // << " use_icon (.ui): " << std::setw(6) << use_icon
+ // << " icon: " << (icon ? "yes" : "no ")
+ // << " useIcons: " << (int)useIcons
+ // << " use_icon.size(): " << use_icon.size()
+ // << " tooltip: " << tooltip.c_str()
+ // << std::endl;
+
+ auto menu_item = Gio::MenuItem::create(label, detailed_action);
+ if (icon &&
+ (useIcons == UseIcons::always ||
+ (useIcons == UseIcons::as_requested && use_icon.size() > 0))) {
+ menu_item->set_attribute_value("icon", icon);
+ }
+
+ // Add remaining attributes
+ for (auto const& [key, value] : attributes) {
+ menu_item->set_attribute_value(key, value);
+ }
+
+ // Add submenus
+ auto link_iter = menu->iterate_item_links(i);
+ while (link_iter->next()) {
+ auto submenu = Gio::Menu::create();
+ if (link_iter->get_name() == "submenu") {
+ menu_item->set_submenu(submenu);
+ if (link_iter->get_value()->get_data(quark)) {
+ recent_files = submenu;
+ }
+ } else if (link_iter->get_name() == "section") {
+ menu_item->set_section(submenu);
+ } else {
+ std::cerr << "rebuild_menu: Unknown link type: " << link_iter->get_name() << std::endl;
+ }
+ rebuild_menu (link_iter->get_value(), submenu, useIcons, quark, recent_files);
+ }
+
+ menu_copy->append_item(menu_item);
+ }
+}
+
+/*
+ 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 0000000..704f26f
--- /dev/null
+++ b/src/ui/desktop/menubar.h
@@ -0,0 +1,49 @@
+// 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
+ * Sushant A.A.
+ *
+ * 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 <gtkmm.h> // GTK_CHECK_VERSION
+
+void build_menu();
+
+enum class UseIcons {
+ never = -1, // Match existing preference numbering.
+ as_requested,
+ always,
+};
+
+// Rebuild menu with icons enabled or disabled. Recursive.
+#if GTK_CHECK_VERSION(4, 0, 0)
+void rebuild_menu (std::shared_ptr<Gio::MenuModel> menu, std::shared_ptr<Gio::Menu> menu_copy, UseIcons useIcons, Glib::Quark quark, Glib::RefPtr<Gio::Menu>& recent_files);
+#else
+void rebuild_menu (Glib::RefPtr<Gio::MenuModel> menu, Glib::RefPtr<Gio::Menu> menu_copy, UseIcons useIcons, Glib::Quark quark, Glib::RefPtr<Gio::Menu>& recent_files);
+#endif
+
+#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 :