diff options
Diffstat (limited to 'src/ui/desktop/menubar.cpp')
-rw-r--r-- | src/ui/desktop/menubar.cpp | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/src/ui/desktop/menubar.cpp b/src/ui/desktop/menubar.cpp new file mode 100644 index 0000000..76cc3bf --- /dev/null +++ b/src/ui/desktop/menubar.cpp @@ -0,0 +1,332 @@ +// 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 "actions/actions-effect.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(); + enable_effect_actions(app, false); + 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; + +#ifdef __APPLE__ + // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/5667 + // Convert document actions to window actions + if (strncmp(detailed_action.c_str(), "doc.", 4) == 0) { + detailed_action = "win." + detailed_action.raw().substr(4); + } +#endif + + 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().raw() << 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 : |