diff --git a/po/POTFILES.in b/po/POTFILES.in index fcdd025e2292dac3aa52a105c1164c197e6bf7ad..17c86e8365660f6dc7ca61627b4e5e6c55e5fe14 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -235,6 +235,7 @@ src/ui/dialog/document-metadata.cpp src/ui/dialog/document-properties.cpp src/ui/dialog/export.cpp src/ui/dialog/extension-editor.cpp +src/ui/dialog/favorites.cpp src/ui/dialog/filedialogimpl-gtkmm.cpp src/ui/dialog/filedialogimpl-win32.cpp src/ui/dialog/fill-and-stroke.cpp diff --git a/share/icons/Tango/scalable/actions/dialog-favorites.svg b/share/icons/Tango/scalable/actions/dialog-favorites.svg new file mode 100644 index 0000000000000000000000000000000000000000..e041a141141d80d01d9ba43bd00225db3d4a970d --- /dev/null +++ b/share/icons/Tango/scalable/actions/dialog-favorites.svg @@ -0,0 +1,96 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/dialog-favorites.svg b/share/icons/hicolor/scalable/actions/dialog-favorites.svg new file mode 100644 index 0000000000000000000000000000000000000000..e041a141141d80d01d9ba43bd00225db3d4a970d --- /dev/null +++ b/share/icons/hicolor/scalable/actions/dialog-favorites.svg @@ -0,0 +1,96 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-filter-colorizable-drop-shadow.svg b/share/icons/hicolor/scalable/actions/favorites-filter-colorizable-drop-shadow.svg new file mode 100644 index 0000000000000000000000000000000000000000..0a227eacfd428df7c51c96d6c38f2c9d73d537b6 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-filter-colorizable-drop-shadow.svg @@ -0,0 +1,114 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-filter-fill-and-transparency-background.svg b/share/icons/hicolor/scalable/actions/favorites-filter-fill-and-transparency-background.svg new file mode 100644 index 0000000000000000000000000000000000000000..d455898b9188a8d328d3326551eecf7b019ebfe2 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-filter-fill-and-transparency-background.svg @@ -0,0 +1,118 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-filter-fill-and-transparency-flatten-transparency.svg b/share/icons/hicolor/scalable/actions/favorites-filter-fill-and-transparency-flatten-transparency.svg new file mode 100644 index 0000000000000000000000000000000000000000..996cb3d221757c5c730024fd71c68b82ee628826 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-filter-fill-and-transparency-flatten-transparency.svg @@ -0,0 +1,113 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-filter-fill-and-transparency-monochrome-transparency.svg b/share/icons/hicolor/scalable/actions/favorites-filter-fill-and-transparency-monochrome-transparency.svg new file mode 100644 index 0000000000000000000000000000000000000000..40d62eddc066d9b7baf9d841b23623c907544d40 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-filter-fill-and-transparency-monochrome-transparency.svg @@ -0,0 +1,95 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-filter-scatter-air-spray.svg b/share/icons/hicolor/scalable/actions/favorites-filter-scatter-air-spray.svg new file mode 100644 index 0000000000000000000000000000000000000000..9385b7db7b49bff3f7fd83b79f6a03e1b3274241 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-filter-scatter-air-spray.svg @@ -0,0 +1,189 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-filter-scatter-cubes.svg b/share/icons/hicolor/scalable/actions/favorites-filter-scatter-cubes.svg new file mode 100644 index 0000000000000000000000000000000000000000..15f11094945d248a25876e529a842e9ab22d4872 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-filter-scatter-cubes.svg @@ -0,0 +1,123 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-filter-scatter-leaves.svg b/share/icons/hicolor/scalable/actions/favorites-filter-scatter-leaves.svg new file mode 100644 index 0000000000000000000000000000000000000000..b61d466ce7d067c1a8cc4176bd2efc14fcd76a54 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-filter-scatter-leaves.svg @@ -0,0 +1,98 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-filter-scatter-pointillism.svg b/share/icons/hicolor/scalable/actions/favorites-filter-scatter-pointillism.svg new file mode 100644 index 0000000000000000000000000000000000000000..3824aa038574b89d4d7302a50138be1f0f0f63d4 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-filter-scatter-pointillism.svg @@ -0,0 +1,703 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-filters.svg b/share/icons/hicolor/scalable/actions/favorites-filters.svg new file mode 100644 index 0000000000000000000000000000000000000000..9dd0d24fb20eb6148c1878665470847bce6211e2 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-filters.svg @@ -0,0 +1,123 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-others-convert-to-text.svg b/share/icons/hicolor/scalable/actions/favorites-others-convert-to-text.svg new file mode 100644 index 0000000000000000000000000000000000000000..cc5b784ba213fedb5eceaef36ada79893d308bcb --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-others-convert-to-text.svg @@ -0,0 +1,132 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + txt + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-others-object-release-clip.svg b/share/icons/hicolor/scalable/actions/favorites-others-object-release-clip.svg new file mode 100644 index 0000000000000000000000000000000000000000..7e5e8760b15e65ea859ea1bdedbce8d3d9cea592 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-others-object-release-clip.svg @@ -0,0 +1,112 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-others-object-release-mask.svg b/share/icons/hicolor/scalable/actions/favorites-others-object-release-mask.svg new file mode 100644 index 0000000000000000000000000000000000000000..affec866353bb31d3a2f852cd21e1c359f39d777 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-others-object-release-mask.svg @@ -0,0 +1,109 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-others-object-set-clip.svg b/share/icons/hicolor/scalable/actions/favorites-others-object-set-clip.svg new file mode 100644 index 0000000000000000000000000000000000000000..c7f88b2d30dd48cc04d2c13e122f19732fcabd58 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-others-object-set-clip.svg @@ -0,0 +1,106 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-others-object-set-mask.svg b/share/icons/hicolor/scalable/actions/favorites-others-object-set-mask.svg new file mode 100644 index 0000000000000000000000000000000000000000..546d6b07063bd8660462f1c12f77c76d9813ccaf --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-others-object-set-mask.svg @@ -0,0 +1,103 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/favorites-others.svg b/share/icons/hicolor/scalable/actions/favorites-others.svg new file mode 100644 index 0000000000000000000000000000000000000000..f1945a237bc6b9596d912fdc3afd09f0273adc1e --- /dev/null +++ b/share/icons/hicolor/scalable/actions/favorites-others.svg @@ -0,0 +1,105 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/symbolic/actions/dialog-favorites-symbolic.svg b/share/icons/hicolor/symbolic/actions/dialog-favorites-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..cfa350d03ab7314baa71dbb89dfe1fb58b89a767 --- /dev/null +++ b/share/icons/hicolor/symbolic/actions/dialog-favorites-symbolic.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/share/icons/hicolor/symbolic/actions/icons.html b/share/icons/hicolor/symbolic/actions/icons.html index 9c804260d89c6c2abf5b19d0a4695f38db26f0e9..3c3e1a2d6377d4c20e49eda89ceb74e6e74647e8 100644 --- a/share/icons/hicolor/symbolic/actions/icons.html +++ b/share/icons/hicolor/symbolic/actions/icons.html @@ -226,6 +226,7 @@
  • X Transform... (dialog-transform)
  • X Align and Distribute... (dialog-align-and-distribute)
  • +
  • X Favorites... (dialog-favorites)
  • X Arrange... (dialog-rows-and-columns)
  • diff --git a/share/pixmaps/symbolic_icons.svg b/share/pixmaps/symbolic_icons.svg index 12d035d95087a525d0befdc1cd339efc77d2a346..f0e6755e3ac375268e17fd174106360b81b8ad01 100644 --- a/share/pixmaps/symbolic_icons.svg +++ b/share/pixmaps/symbolic_icons.svg @@ -721,6 +721,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/ui/menus.xml b/share/ui/menus.xml index e6e7e28ea0aadab949c43b22ff78ad766f2f7a68..983558574c80c89e5afe2da52fa47a0236344ee4 100644 --- a/share/ui/menus.xml +++ b/share/ui/menus.xml @@ -149,6 +149,7 @@ + diff --git a/share/ui/toolbar-commands.ui b/share/ui/toolbar-commands.ui index f060e8e9d9b5ffd74b9e56da6f057b0a83877697..1e1b875c5fbc9d46fd234138bb878e6842e16529 100644 --- a/share/ui/toolbar-commands.ui +++ b/share/ui/toolbar-commands.ui @@ -32,6 +32,7 @@ + diff --git a/src/desktop.cpp b/src/desktop.cpp index ebc3e559135f2e5a1f951b9ad2e480ed94f5386e..0630f01d89721556d8e0ae029115f35ee831dddb 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -1997,6 +1997,7 @@ SPDesktop::show_dialogs() mapVerbPreference.insert(std::make_pair ("FillAndStroke", "/dialogs/fillstroke") ); mapVerbPreference.insert(std::make_pair ("ExtensionEditor", "/dialogs/extensioneditor") ); mapVerbPreference.insert(std::make_pair ("AlignAndDistribute", "/dialogs/align") ); + mapVerbPreference.insert(std::make_pair ("Favorites", "/dialogs/favorites") ); mapVerbPreference.insert(std::make_pair ("DocumentMetadata", "/dialogs/documentmetadata") ); mapVerbPreference.insert(std::make_pair ("DocumentProperties", "/dialogs/documentoptions") ); mapVerbPreference.insert(std::make_pair ("FilterEffectsDialog", "/dialogs/filtereffects") ); @@ -2082,4 +2083,3 @@ SPDesktop::show_dialogs() End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : - diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 885bcc34d8bec521362dae62b54c0cd5ae377b71..f8b9e8a10b0d8a3c98f33fcf19bc97a5703afdb9 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -86,6 +86,7 @@ set(ui_SRC dialog/aboutbox.cpp dialog/align-and-distribute.cpp + dialog/favorites.cpp dialog/calligraphic-profile-rename.cpp dialog/clonetiler.cpp dialog/color-item.cpp diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp index dcfa6fe9a82668f9171e0059fa8cc2990809b108..bb9adb0f4acfa6a5f29eda969a50639c9d7b352a 100644 --- a/src/ui/dialog/dialog-manager.cpp +++ b/src/ui/dialog/dialog-manager.cpp @@ -18,6 +18,7 @@ #include "ui/dialog/prototype.h" #include "ui/dialog/align-and-distribute.h" +#include "ui/dialog/favorites.h" #include "ui/dialog/document-metadata.h" #include "ui/dialog/document-properties.h" #include "ui/dialog/extension-editor.h" @@ -104,6 +105,7 @@ DialogManager::DialogManager() { if (dialogs_type == FLOATING) { registerFactory("Prototype", &create); registerFactory("AlignAndDistribute", &create); + registerFactory("Favorites", &create); registerFactory("DocumentMetadata", &create); registerFactory("DocumentProperties", &create); registerFactory("ExtensionEditor", &create); @@ -146,6 +148,7 @@ DialogManager::DialogManager() { registerFactory("Prototype", &create); registerFactory("AlignAndDistribute", &create); + registerFactory("Favorites", &create); registerFactory("DocumentMetadata", &create); registerFactory("DocumentProperties", &create); registerFactory("ExtensionEditor", &create); diff --git a/src/ui/dialog/favorites.cpp b/src/ui/dialog/favorites.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9908607088025badf14dbfe0d80e4e31935e33b1 --- /dev/null +++ b/src/ui/dialog/favorites.cpp @@ -0,0 +1,809 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Favorites dialog + *//* + * Authors: + * Juanjo Antolinez + * + * Copyright (C) 1999-2004, 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "favorites.h" + +#include "inkscape.h" +#include "verbs.h" +#include "include/gtkmm_version.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +#include "helper/action-context.h" +#include "helper/action.h" + +#include + +namespace Inkscape { +namespace UI { +namespace Dialog { + + +/** @brief Managed objects factory. + + Depending on the gtkmm version uses Gtk::manage or the newer function + Gtk::make_managed. Newer is preferred as follows modern C++ practices + of avoiding the use of the @c new operator. + + @tparam T Type of object to instanciate + @tparam T_Args Types of the multiple T's constructor parameters + @param args T's constructor parameters + @return A pointer to a newly created managed object of type T. + Its constructor was called with arguments @c args. +*/ +template +T* make_managed(T_Args&&... args) +{ +#if GTKMM_CHECK_VERSION(3,24,0) + return Gtk::make_managed(std::forward(args)...); +#else + return Gtk::manage(new T(std::forward(args)...)); +#endif +} + + +namespace CustomizeFavorites { + +// TODO: Implement as a separated file (.JSON, .YML, .XML...) +const std::map Action::ACTIONS{ + // TODO: take back actions related to extensions when current code changes + // in the project regarding how extensions are managed get settled + + // filters + { + "org.inkscape.effect.filter.f190", + Action{ + "org.inkscape.effect.filter.f190", + "Fill and transparency / Background", + INKSCAPE_ICON("favorites-filter-fill-and-transparency-background"), + "Fill and transparency / Background", + ActionGroup::Filter + } + }, + { + "org.inkscape.effect.filter.f191", + Action{ + "org.inkscape.effect.filter.f191", + "Fill and transparency / Flatten Transparency", + INKSCAPE_ICON("favorites-filter-fill-and-transparency-flatten-transparency"), + "Fill and transparency / Flatten Transparency", + ActionGroup::Filter + } + }, + { + "org.inkscape.effect.filter.f152", + Action{ + "org.inkscape.effect.filter.f152", + "Fill and transparency / Monochrome Transparency", + INKSCAPE_ICON("favorites-filter-fill-and-transparency-monochrome-transparency"), + "Fill and transparency / Monochrome Transparency", + ActionGroup::Filter + } + }, + { + "org.inkscape.effect.filter.f074", + Action{ + "org.inkscape.effect.filter.f074", + "Scatter / Air Spray", + INKSCAPE_ICON("favorites-filter-scatter-air-spray"), + "Scatter / Air Spray", + ActionGroup::Filter + } + }, + { + "org.inkscape.effect.filter.f065", + Action{ + "org.inkscape.effect.filter.f065", + "Scatter / Cubes", + INKSCAPE_ICON("favorites-filter-scatter-cubes"), + "Scatter / Cubes", + ActionGroup::Filter + } + }, + { + "org.inkscape.effect.filter.f045", + Action{ + "org.inkscape.effect.filter.f045", + "Scatter / Leaves", + INKSCAPE_ICON("favorites-filter-scatter-leaves"), + "Scatter / Leaves", + ActionGroup::Filter + } + }, + { + "org.inkscape.effect.filter.f188", + Action{ + "org.inkscape.effect.filter.f188", + "Scatter / Pointillism", + INKSCAPE_ICON("favorites-filter-scatter-pointillism"), + "Scatter / Pointillism", + ActionGroup::Filter + } + }, + { + "org.inkscape.effect.filter.ColorDropShadow", + Action{ + "org.inkscape.effect.filter.ColorDropShadow", + "Shadows / Colorizable Drop shadow", + INKSCAPE_ICON("favorites-filter-colorizable-drop-shadow"), + "Colorizable Drop shadow", + ActionGroup::Filter + } + }, + + // other + { + "ObjectFlowtextToText", + Action{ + "ObjectFlowtextToText", + "Text / Convert to Text", + INKSCAPE_ICON("favorites-others-convert-to-text"), + "Convert to Text", + ActionGroup::Other + } + }, + { + "ObjectSetClipPath", + Action{ + "ObjectSetClipPath", + "Object / Set clip", + INKSCAPE_ICON("favorites-others-object-set-clip"), + "Set clip", + ActionGroup::Other + } + }, + { + "ObjectUnSetClipPath", + Action{ + "ObjectUnSetClipPath", + "Object / Release clip", + INKSCAPE_ICON("favorites-others-object-release-clip"), + "Release clip", + ActionGroup::Other + } + }, + { + "ObjectSetMask", + Action{ + "ObjectSetMask", + "Object / Set mask", + INKSCAPE_ICON("favorites-others-object-set-mask"), + "Set mask", + ActionGroup::Other + } + }, + { + "ObjectUnSetMask", + Action{ + "ObjectUnSetMask", + "Object / Release mask", + INKSCAPE_ICON("favorites-others-object-release-mask"), + "Release mask", + ActionGroup::Other + } + }, +}; + +// DlgCustomize definition + +DlgCustomize::DlgCustomize(Gtk::Window& parent, std::vector favoriteActionIds) +: Gtk::Dialog(_("Customize favorites"), parent, true) +, _boxMain{new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)} +, _boxAvailable{new Gtk::Box(Gtk::ORIENTATION_VERTICAL)} +, _boxMove{new Gtk::Box(Gtk::ORIENTATION_VERTICAL)} +, _boxFavorites{new Gtk::Box(Gtk::ORIENTATION_VERTICAL)} +, _boxArrange{new Gtk::Box(Gtk::ORIENTATION_VERTICAL)} +, _scrAvailableActions{new Gtk::ScrolledWindow()} +, _scrFavoriteActions{new Gtk::ScrolledWindow()} +, _lblAvailableActions{new Gtk::Label(_("Available actions:"))} +, _lblFavoriteActions{new Gtk::Label(_("Favorite actions:"))} +, _btnChooseAction{new Gtk::Button()} +, _btnDismissAction{new Gtk::Button()} +, _btnArrangeUp{new Gtk::Button()} +, _btnArrangeDown{new Gtk::Button()} +, _tvwAvailableActions{new Gtk::TreeView()} +, _tvwFavoriteActions{new Gtk::TreeView()} { + + buildUI(); + loadAvailableActions(); + chooseInitialActions(favoriteActionIds); +} + +inline decltype(Action::id) DlgCustomize::getRowId(RowIterator& iter) const { + auto row = *iter; + return row[_columnsAction.id]; +} + +inline decltype(Action::name) DlgCustomize::getRowName(RowIterator& iter) const { + auto row = *iter; + return row[_columnsAction.name]; +} + +inline decltype(Action::group) DlgCustomize::getRowGroup(RowIterator& iter) const { + auto row = *iter; + return row[_columnsAction.group]; +} + +void DlgCustomize::setRowAction(Action action, const RowIterator& iter) const { + auto row = *iter; + row[_columnsAction.id] = action.id; + row[_columnsAction.name] = action.name; + row[_columnsAction.icon] = action.icon; + row[_columnsAction.tip] = action.tip; + row[_columnsAction.group] = action.group; +} + +Action DlgCustomize::getRowAction(const RowIterator& iter) const { + auto row = *iter; + auto id = row[_columnsAction.id]; + auto name = row[_columnsAction.name]; + auto icon = row[_columnsAction.icon]; + auto tip = row[_columnsAction.tip]; + auto group = row[_columnsAction.group]; + + return Action{id, name, icon, tip, group}; +} + +RowIterator DlgCustomize::findRow(const Glib::ustring id, Gtk::TreeNodeChildren children) const { + + RowIterator iterId{}; + + for (auto iter=children.begin(); iter != children.end(); iter++) { + if (id == getRowId(iter)) { + iterId = iter; + break; + } + } + return iterId; // might be uninitilized if row was not found (will never happen with current implementation of DlgCustomize); must be handled by callee +} + +RowIterator DlgCustomize::findRowAvailable(const Glib::ustring id) const +{ + + RowIterator iterId{}; + + for (const auto& rowParent : _treeStoreAvailableActions->children()) { + iterId = findRow(id, rowParent->children()); + + if (iterId) break; + } + + return iterId; // might be uninitilized if row was not found (will never happen with current implementation of DlgCustomize); must be handled by callee +} + +inline RowIterator DlgCustomize::findRowFavorite(const Glib::ustring id) const +{ + return findRow(id, _listStoreFavoriteActions->children()); +} + +template +void DlgCustomize::appendAction(Action action, T store) +{ + auto iter = store->append(); + setRowAction(action, iter); +} + +void DlgCustomize::appendActionAvailable(Action action) +{ + appendAction(action, _treeStoreAvailableActions); +} + +void DlgCustomize::appendActionFavorite(Action action) +{ + appendAction(action, _listStoreFavoriteActions); +} + +void DlgCustomize::insertBeforeRowAvailable(Action action, RowIterator& iterInsertBeforeThisRow) +{ + auto iter = _treeStoreAvailableActions->insert(iterInsertBeforeThisRow); + setRowAction(action, iter); +} + +void DlgCustomize::appendRowAvailable(Action action, RowIterator& iterGroupParentRow) +{ + auto iter = _treeStoreAvailableActions->append(iterGroupParentRow->children()); + setRowAction(action, iter); +} + +void DlgCustomize::insertActionAvailable(Action action) +{ + + auto iterParentRows = _treeStoreAvailableActions->children(); + + for (auto iterParent=iterParentRows.begin(); iterParent != iterParentRows.end(); iterParent++) { + auto groupParent = getRowGroup(iterParent); + + if (action.group == groupParent) { + auto rowParent = *iterParent; + auto iterChildRows = rowParent->children(); + + for (auto iter=iterChildRows.begin(); iter != iterChildRows.end(); iter++ ) { + auto nameChild = getRowName(iter); + + // child rows in this store are sorted by action name + if (action.name.compare(nameChild) < 0) { + insertBeforeRowAvailable(action, iter); + return; + } + } + // name doesn't come before any other + appendRowAvailable(action, iterParent); + return; + } + } +} + +void DlgCustomize::chooseInitialActions(std::vector favoriteActionIds) +{ + for (const auto& id : favoriteActionIds) chooseAction(id); +} + +void DlgCustomize::chooseAction(const Glib::ustring& id) +{ + auto iter = findRowAvailable(id); + if (iter) { + appendActionFavorite(getRowAction(iter)); + _treeStoreAvailableActions->erase(iter); + } +} + +void DlgCustomize::dismissAction(const Glib::ustring& id) +{ + auto iter = findRowFavorite(id); + if (iter) { + insertActionAvailable(getRowAction(iter)); + _listStoreFavoriteActions->erase(iter); + } +} + +void DlgCustomize::arrangeUp(const Glib::ustring& id) +{ + auto iterId = findRowFavorite(id); + auto action = getRowAction(iterId); + auto iterPrev = std::prev(iterId); + + if (iterId && iterPrev) { + // erase ID row + _listStoreFavoriteActions->erase(iterId); + + // insert ID row before PREVIOUS row + auto iterNew = _listStoreFavoriteActions->insert(iterPrev); + setRowAction(action, iterNew); + + // select this NEW row + auto selection = _tvwFavoriteActions->get_selection(); + selection->select(iterNew); + } +} + +void DlgCustomize::arrangeDown(const Glib::ustring& id) +{ + auto iterId = findRowFavorite(id); + auto action = getRowAction(iterId); + auto iterNext = std::next(iterId); + + if (iterId && iterNext) { + // erase ID row + _listStoreFavoriteActions->erase(iterId); + + // insert ID row after NEXT row + auto iterNew = _listStoreFavoriteActions->insert_after(iterNext); + setRowAction(action, iterNew); + + // select this NEW row + auto selection = _tvwFavoriteActions->get_selection(); + selection->select(iterNew); + } +} + +void DlgCustomize::loadAvailableActions() +{ + // parent rows + + // TODO: take back actions related to extensions when current code changes + // in the project regarding how extensions are managed get settled + + appendActionAvailable(Action{"F", "Filters", "favorites-filters", "", ActionGroup::Filter}); + appendActionAvailable(Action{"O", "Others", "favorites-others", "", ActionGroup::Other}); + + for (const auto& entryAction : Action::ACTIONS) { + auto action = entryAction.second; + insertActionAvailable(action); + } + + _tvwAvailableActions->expand_all(); +} + +void DlgCustomize::buildUI() +{ + const int VIEW_WIDTH = 380, VIEW_HEIGHT = 480; + + set_position(Gtk::WIN_POS_CENTER); + + // content area + _boxAvailable->pack_start(*_lblAvailableActions, false, false); + + _scrAvailableActions->set_min_content_width(VIEW_WIDTH); + _scrAvailableActions->set_min_content_height(VIEW_HEIGHT); + + _scrAvailableActions->add(*_tvwAvailableActions); + _boxAvailable->pack_start(*_scrAvailableActions, true, true); + _boxMain->pack_start(*_boxAvailable, true, true); + + /* icon name is the name of a file located under /usr/share/icons or + any directory in the search path of the current theme */ + + _btnChooseAction->set_image_from_icon_name("go-next"); + _btnDismissAction->set_image_from_icon_name("go-previous"); + + auto boxMoveSep01 = make_managed(); + _boxMove->pack_start(*boxMoveSep01, true, false); + auto boxMoveSep02 = make_managed(); + _boxMove->pack_start(*boxMoveSep02, true, false); + auto boxMoveSep12 = make_managed(); + _boxMove->pack_end(*boxMoveSep12, true, false); + auto boxMoveSep11 = make_managed(); + _boxMove->pack_end(*boxMoveSep11, true, false); + + _boxMove->pack_start(*_btnChooseAction, true, false); + _boxMove->pack_start(*_btnDismissAction, true, false); + _boxMain->pack_start(*_boxMove, false, false); + + _boxFavorites->pack_start(*_lblFavoriteActions, false, false); + + _scrFavoriteActions->set_min_content_width(VIEW_WIDTH); + _scrFavoriteActions->set_min_content_height(VIEW_HEIGHT); + + _scrFavoriteActions->add(*_tvwFavoriteActions); + _boxFavorites->pack_start(*_scrFavoriteActions, true, true); + _boxMain->pack_start(*_boxFavorites, true, true); + + /* icon name is the name of a file located under /usr/share/icons or + any directory in the search path of the current theme */ + + _btnArrangeUp->set_image_from_icon_name("go-up"); + _btnArrangeDown->set_image_from_icon_name("go-down"); + + auto boxArrangeSep01 = make_managed(); + _boxArrange->pack_start(*boxArrangeSep01, true, false); + auto boxArrangeSep02 = make_managed(); + _boxArrange->pack_start(*boxArrangeSep02, true, false); + auto boxArrangeSep12 = make_managed(); + _boxArrange->pack_end(*boxArrangeSep12, true, false); + auto boxArrangeSep11 = make_managed(); + _boxArrange->pack_end(*boxArrangeSep11, true, false); + + _boxArrange->pack_start(*_btnArrangeUp, true, false); + _boxArrange->pack_start(*_btnArrangeDown, true, false); + _boxMain->pack_start(*_boxArrange, false, false); + + get_content_area()->add(*_boxMain); + + // response buttons + add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); + add_button(_("_OK"), Gtk::RESPONSE_OK); + + // tree views + // Available actions + _treeStoreAvailableActions = Gtk::TreeStore::create(_columnsAction); + _tvwAvailableActions->set_model(_treeStoreAvailableActions); + + // action.icon + action.name + auto /*Gtk::TreeView::Column* */ colAvailable = make_managed(_("Action")); + auto iconRendererAvailable = make_managed(); + + colAvailable->pack_start(*iconRendererAvailable, /* expand= */ false); + // IMPORTANT! next statement must go always AFTER "pack_start" call + colAvailable->add_attribute(iconRendererAvailable->property_icon_name(), _columnsAction.icon); + + colAvailable->pack_start(_columnsAction.name); + + _tvwAvailableActions->append_column(*colAvailable); + + // Favorite actions + _listStoreFavoriteActions = Gtk::ListStore::create(_columnsAction); + _tvwFavoriteActions->set_model(_listStoreFavoriteActions); + + // action.icon + action.name + auto /*Gtk::TreeView::Column* */ colFavorites = make_managed(_("Action")); + auto iconRendererFavorites = make_managed(); + + colFavorites->pack_start(*iconRendererFavorites, /* expand= */ false); + // IMPORTANT! next statement must go always AFTER "pack_start" call + colFavorites->add_attribute(iconRendererFavorites->property_icon_name(), _columnsAction.icon); + + colFavorites->pack_start(_columnsAction.name); + + _tvwFavoriteActions->append_column(*colFavorites); + + // signal handlers + _btnChooseAction->signal_clicked().connect(sigc::mem_fun(*this, &DlgCustomize::onBtnChooseActionClicked)); + _btnDismissAction->signal_clicked().connect(sigc::mem_fun(*this, &DlgCustomize::onBtnDismissActionClicked)); + _btnArrangeUp->signal_clicked().connect(sigc::mem_fun(*this, &DlgCustomize::onBtnArrangeUpClicked)); + _btnArrangeDown->signal_clicked().connect(sigc::mem_fun(*this, &DlgCustomize::onBtnArrangeDownClicked)); + + show_all_children(); +} + +void DlgCustomize::onBtnChooseActionClicked() +{ + auto selection = _tvwAvailableActions->get_selection(); + auto iterSelected = selection->get_selected(); + if (iterSelected) chooseAction(getRowId(iterSelected)); +} + +void DlgCustomize::onBtnDismissActionClicked() +{ + auto selection = _tvwFavoriteActions->get_selection(); + auto iterSelected = selection->get_selected(); + if (iterSelected) dismissAction(getRowId(iterSelected)); +} + +void DlgCustomize::onBtnArrangeUpClicked() +{ + auto selection = _tvwFavoriteActions->get_selection(); + auto iterSelected = selection->get_selected(); + if (iterSelected) arrangeUp(getRowId(iterSelected)); +} + +void DlgCustomize::onBtnArrangeDownClicked() +{ + auto selection = _tvwFavoriteActions->get_selection(); + auto iterSelected = selection->get_selected(); + if (iterSelected) arrangeDown(getRowId(iterSelected)); +} + +std::vector DlgCustomize::getFavoriteActions() const +{ + std::vector favoriteActions{}; + for (const auto& row : _listStoreFavoriteActions->children()) { + favoriteActions.push_back(getRowAction(row)); + } + + return favoriteActions; +} + +} // namespace CustomizeFavorites + + +// Favorites definition + +Favorites::Favorites() +: UI::Widget::Panel("/dialogs/favorites", SP_VERB_DIALOG_FAVORITES) +, _gridActionButtons{} +, _boxGlue{} +, _mnuCustomize{} +, ADD_ICON_NAME{"list-add"} +, XML_PREFS_GROUPID{"id"} +, XML_PREFS_ACTION_KEY_PREFIX{"action_"} +, PREFS_FAVORITE_ACTIONS_PATH{"/dialogs/favorites/actions"} +{ + buildUI(); +} + +void Favorites::buildUI() +{ + buildActionButtons(); + + // arrange dialog contents + _gridActionButtons.set_valign(Gtk::ALIGN_START); + _gridActionButtons.set_halign(Gtk::ALIGN_START); + + _boxGlue.pack_start(_gridActionButtons, true, true); + _boxGlue.signal_button_release_event().connect(sigc::mem_fun(*this, &Favorites::on_button_release_event)); + _boxGlue.signal_size_allocate().connect(sigc::mem_fun(*this, &Favorites::on_size_allocate)); + + auto /*Gtk::Box* */ contents = _getContents(); + contents->set_spacing(0); + contents->pack_start(_boxGlue, true, true); + + // customize popup menu + auto mnuItemCustomize = make_managed(_("Customize...")); + mnuItemCustomize->show(); + mnuItemCustomize->signal_activate().connect(sigc::mem_fun(*this, &Favorites::onMenuCustomizeActivate)); + _mnuCustomize.add(*mnuItemCustomize); + + show_all_children(); +} + +void Favorites::removeActionButtons() +{ + // widget is a button actually + for (auto childWidget : _gridActionButtons.get_children()) { + _gridActionButtons.remove(*childWidget); + + if (childWidget) { + auto button = reinterpret_cast(childWidget); + + // delete the icon + if (button) { + auto icon = button->get_child(); + if (icon) delete icon; + } + + delete childWidget; + } + } +} + +void Favorites::buildActionButtons() +{ + const auto& ACTIONS{CustomizeFavorites::Action::ACTIONS}; + + // clear buttons + removeActionButtons(); + + // build buttons from preferences + int nButtonsBuilt{0}; + for (const auto& actionId : getPreferencesFavoriteActions()) { + + auto iterAction = ACTIONS.find(actionId); + if (iterAction == ACTIONS.end()) continue; + + auto action = iterAction->second; + + auto icon = Gtk::manage(sp_get_icon_image(INKSCAPE_ICON(action.icon), Gtk::ICON_SIZE_LARGE_TOOLBAR)); + auto button = make_managed(); + button->set_relief(Gtk::RELIEF_NONE); + button->set_tooltip_text(action.tip); + button->add(*icon); + button->set_valign(Gtk::ALIGN_START); + button->set_halign(Gtk::ALIGN_START); + + button->signal_clicked().connect( + sigc::bind( + sigc::mem_fun(*this, &Favorites::onActionButtonClicked), + action.id + ) + ); + + addActionButton(*button); + nButtonsBuilt++; + } + + // Initial state and whenever no favorites are chosen + // A "+" button is added to allow user choose favorite actions + if (! nButtonsBuilt) { + auto icon = Gtk::manage(sp_get_icon_image(INKSCAPE_ICON(ADD_ICON_NAME), Gtk::ICON_SIZE_LARGE_TOOLBAR)); + auto button = make_managed(); + button->set_relief(Gtk::RELIEF_NONE); + button->add(*icon); + + button->signal_clicked().connect(sigc::mem_fun(*this, &Favorites::onMenuCustomizeActivate)); + + _gridActionButtons.add(*button); + } + + _gridActionButtons.show_all_children(); +} + +void Favorites::addActionButton(Gtk::Button& button) +{ + int nbuttons = _gridActionButtons.get_children().size(); + + // IMPORTANT! the following formulas assume nbuttons = nbuttons + 1 (the new button to add) + int rowi = nbuttons / _optimalNumberOfColumns; + int coli = nbuttons - rowi*_optimalNumberOfColumns; + +#if GTKMM_CHECK_VERSION(3,24,0) + _gridActionButtons.attach(button, coli, rowi); +#else + _gridActionButtons.attach(button, coli, rowi, 1, 1); +#endif +} + +void Favorites::performAction(const Glib::ustring actionId) +{ + auto /*Inkscape::Verb* */ verb = Inkscape::Verb::getbyid(actionId.c_str()); + auto /*SPAction* */ action = verb->get_action(Inkscape::ActionContext(SP_ACTIVE_DESKTOP)); + sp_action_perform(action, nullptr); +} + +bool Favorites::on_button_release_event(GdkEventButton* buttonEvent) +{ + // right button click + if ((buttonEvent->type == GDK_BUTTON_RELEASE) && (buttonEvent->button == 3)) { + +#if GTKMM_CHECK_VERSION(3,22,0) + _mnuCustomize.popup_at_pointer(nullptr); +#else + _mnuCustomize.popup(buttonEvent->button, buttonEvent->time); +#endif + + return true; + } + + return false; // event was not handled +} + +std::vector Favorites::getPreferencesFavoriteActions() +{ + // get current preferences + auto /*Inkscape::Preferences* */prefs = Inkscape::Preferences::get(); + auto /*std::vector*/ entries = prefs->getAllEntries(PREFS_FAVORITE_ACTIONS_PATH); + + std::map prefsEntriesMap{}; + for (const auto& entry : entries) { + if (entry.getEntryName() == XML_PREFS_GROUPID) continue; + + std::string skey = entry.getEntryName().substr(XML_PREFS_ACTION_KEY_PREFIX.length()); // get the XXX in "action_XXX" + int key = std::stoi(skey); + Glib::ustring value = entry.getString(); + + prefsEntriesMap[key] = value; + } + + // sorted map (by key) to vector + std::vector sortedFavoriteActionIds{}; + for (const auto& keyValue : prefsEntriesMap) { + sortedFavoriteActionIds.push_back(keyValue.second); + } + + return sortedFavoriteActionIds; +} + +void Favorites::onMenuCustomizeActivate() +{ + // open customize dialog + CustomizeFavorites::DlgCustomize dlgCustomize{*INKSCAPE.active_desktop()->getToplevel(), getPreferencesFavoriteActions()}; + int userResponse = dlgCustomize.run(); + if (userResponse == Gtk::RESPONSE_CANCEL) return; + + auto /*std::vector*/ actions = dlgCustomize.getFavoriteActions(); + + // erase current favorite actions + auto /*Inkscape::Preferences* */prefs = Inkscape::Preferences::get(); + prefs->remove(PREFS_FAVORITE_ACTIONS_PATH); + + // set the new ones + for (size_t i=0, actionsSize=actions.size(); isetString(oss.str(), actions[i].id); + } + + prefs->save(); + + // refresh UI + buildActionButtons(); +} + +void Favorites::onActionButtonClicked(const Glib::ustring& actionId) +{ + performAction(actionId); +} + +void Favorites::on_size_allocate(Gdk::Rectangle& allocation) +{ + Panel::on_size_allocate(allocation); + + auto buttons = _gridActionButtons.get_children(); + if (buttons.size() > 0) { + + int max_ncols = allocation.get_width() / buttons[0]->get_width(); // no column spacing + _optimalNumberOfColumns = max_ncols; + } +} + +} // 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 : diff --git a/src/ui/dialog/favorites.h b/src/ui/dialog/favorites.h new file mode 100644 index 0000000000000000000000000000000000000000..6b456be57fae7ffc3a52c2cc0365fb81beeb6339 --- /dev/null +++ b/src/ui/dialog/favorites.h @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Favorites dialog + *//* + * A dialog that contains actions the user has chosen. Actions are customizable. + * Actions available include most of the commands available under Extensions and + * Filters menus. Also, other actions are available, those are a selected list + * of commands accesible from other menus. The initial set of these other actions + * has been crafted based on certain criteria, mainly, frequent use and no shortcut + * available for them. + * + * Authors: + * Juanjo Antolinez + * + * Copyright (C) 1999-2004, 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_DIALOG_FAVORITES_H +#define INKSCAPE_UI_DIALOG_FAVORITES_H + +#include "ui/widget/panel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Inkscape { +namespace UI { +namespace Dialog { + +namespace CustomizeFavorites { + +/// Iterator pointing to a TreeView's row +using RowIterator = Gtk::TreeModel::Children::iterator; + +/// Groups of actions, to visually separate available actions when customizing +/// the favorites contents +enum class ActionGroup { Extension, Filter, Other }; + +/** @brief A type representing an action or command in Inkscape. + + Only information required in this context. + Additionally contains the list of all actions available so far to select as favorites. +*/ +struct Action { + Glib::ustring id; ///< Identifier of the action + Glib::ustring name; ///< A short name; used to fill tree views for the user to pick an action + Glib::ustring icon; ///< Icon name; icon's filename is "${icon}.svg" + Glib::ustring tip; ///< Tip text + ActionGroup group; ///< Group or classification for the action. @see ActionGroup + + /** @brief List (as a map) of all actions available so far + + It is an ordered map whose key (@c first data member) is the action ID and + its @c second data member is the whole action. + + Used for listing all actions available and searching for all action data + using the action ID as the key. + */ + static const std::map ACTIONS; // map key type can't by Glib::ustring + + /** Operator << for converting this action into a string or printing + an action's contents using @c std::cout or @c std::cerr + + @param stream An output stream + @param action This action + */ + friend std::ostream& operator<< (std::ostream& stream, const Action& action) { + Glib::ustring id{action.id}, name{action.name}, icon{action.icon}, tip{action.tip}; + Glib::ustring group{action.group == ActionGroup::Extension ? "Extension" : (action.group == ActionGroup::Filter ? "Filter" : "Other")}; + + stream << "id: [" << id << "]\n"; + stream << "name: [" << name << "]\n"; + stream << "icon: [" << icon << "]\n"; + stream << "tip: [" << tip << "]\n"; + stream << "group: [" << group << "]\n"; + + return stream; + } +}; + +/** @brief Dialog box to customize favorite actions. + + Two-panel window: available actions on the left and chosen (favorite) actions + on the right. + Also allows to arrange favorites as pleased. + + @see Gtk::Dialog + */ +class DlgCustomize : public Gtk::Dialog { +public: + /// Constructor for this dialog box + /// @param parent window object to this dialog belongs. + /// @param favoriteActionIds vector of action IDs to be initially chosen as favorites. + DlgCustomize(Gtk::Window& parent, std::vector favoriteActionIds); + + ~DlgCustomize() override = default; + + /// Returns the list of actions chosen by the user as her favorites + /// @return A @c std::vector of actions, user's favorites + std::vector getFavoriteActions() const; + +protected: + /** @brief Model shared by both tree views: available and favorite actions + + The model represents an action, as both tree views, available and + favorite, contain actions for the user to pick + */ + class ActionsModelColumns : public Gtk::TreeModelColumnRecord { + public: + ActionsModelColumns() { + add(id); + add(name); + add(icon); + add(tip); + add(group); + } + + Gtk::TreeModelColumn id; ///< Action's ID. @see Action + Gtk::TreeModelColumn name; ///< Action's name. @see Action + Gtk::TreeModelColumn icon; ///< Action's icon. @see Action + Gtk::TreeModelColumn tip; ///< Action's tip. @see Action + Gtk::TreeModelColumn group; ///< Action's group. @see Action + }; + + ActionsModelColumns _columnsAction; ///< Data member representing the model + Glib::RefPtr _treeStoreAvailableActions; ///< Store of available actions. Used by _tvwAvailableActions + Glib::RefPtr _listStoreFavoriteActions; ///< Store of favorite actions. Used by _tvwFavoriteActions + + // widgets + std::shared_ptr + _boxMain, ///< Contains all widgets within this dialog + _boxAvailable, ///< Contains widgets regarding available actions + _boxMove, ///< Contains widgets regarding choosing and dismissing actions + _boxFavorites, ///< Contains widgets regarding favorite actions + _boxArrange; ///< Contains widgets regarding to arranging favorites + + std::shared_ptr + _scrAvailableActions, ///< Scroll for the available actions TreeView + _scrFavoriteActions; ///< Scroll for the favorite actions TreeView + + std::shared_ptr + _lblAvailableActions, ///< Label (as title) close to the available actions TreeView + _lblFavoriteActions; ///< Label (as title) close to the favorite actions TreeView + + std::shared_ptr + _btnChooseAction, ///< User pushes this button in order to choose an available action to make it favorite + _btnDismissAction, ///< User pushes this button in order dismiss a favorite action (and make it available again) + _btnArrangeUp, ///< User pushes this button in order move up an action in the favorite actions TreeView + _btnArrangeDown; ///< User pushes this button in order move down an action in the favorite actions TreeView + + std::shared_ptr + _tvwAvailableActions, ///< Tree of available actions + _tvwFavoriteActions; ///< List of favorite actions + + // helper function members + + /// Builds the user interface of this dialog + void buildUI(); + /// Loads available actions tree + void loadAvailableActions(); + /// Loads favorite actions list with the argument provided + /// @param favoriteActionIds A @c std::vector of action IDs + void chooseInitialActions(std::vector favoriteActionIds); + /// Sets a TreeView's row columns data out of an action + /// @param action An action, whether available or favorite + /// @param iter An iterator pointing to a TreeView's row + void setRowAction(Action action, const RowIterator& iter) const; + /// Gets an action out of a TreeView's row columns data + /// @param iter An iterator pointing to a TreeView's row + /// @return An action created out of the data stored in this TreeView's row + Action getRowAction(const RowIterator& iter) const; + + /** @brief Gets the action's ID of a TreeView's row + + Same as `getRowAction().id` + @see getRowAction + + @param iter An iterator pointing to a TreeView's row + @return Row's action ID. + */ + inline decltype(Action::id) getRowId(RowIterator& iter) const; + + /** @brief Gets the action's name of a TreeView's row + + Same as `getRowAction().name` + @see getRowAction + + @param iter An iterator pointing to a TreeView's row + @return Row's action name. + */ + inline decltype(Action::name) getRowName(RowIterator& iter) const; + + /** @brief Gets the action's group of a TreeView's row + + Same as `getRowAction().group` + @see getRowAction + + @param iter An iterator pointing to a TreeView's row + @return Row's action group. + */ + inline decltype(Action::group) getRowGroup(RowIterator& iter) const; + + /** @brief Finds a row with a given ID + + Although technically this function could return the value of an uninitilized + local variable in practice this won't happen in this Favorites implementation + as ID provided as argument is always an existing one. + + @param id The given action ID to look for. + @param children The set of rows where the desired row is found. + @return An iterator pointing to the found row. + */ + RowIterator findRow(const Glib::ustring id, Gtk::TreeNodeChildren children) const; + + /** @brief Finds a row in the available actions tree view with a given ID + + Although technically this function could return the value of an uninitilized + local variable in practice this won't happen in this Favorites implementation + as ID provided as argument is always an existing one. + + @param id The given action ID to look for. + @return An iterator pointing to the found row. + */ + RowIterator findRowAvailable(const Glib::ustring id) const; + + /** @brief Finds a row in the favorite actions tree view with a given ID + + Although technically this function could return the value of an uninitilized + local variable in practice this won't happen in this Favorites implementation + as ID provided as argument is always an existing one. + + @param id The given action ID to look for. + @return An iterator pointing to the found row. + */ + inline RowIterator findRowFavorite(const Glib::ustring id) const; + + // member functions to manipulate stores + + /** Appends an action to an store + + @tparam T Type of store (TreeStore or ListStore) + @param action Action to append + @param store Store to append this action to + */ + template + void appendAction(Action action, T store); + + /** Inserts an action into the available actions store + + @param action Action to insert + */ + void insertActionAvailable(Action action); + + /** Appends an action to the available actions store + + @param action Action to append + */ + void appendActionAvailable(Action action); + + /** Appends an action to the favorite actions store + + @param action Action to append + */ + void appendActionFavorite(Action action); + + // member functions to manipulate TreeViews + + /** Appends a child row to the available actions TreeView + + @param action Action to append + @param iterGroupParentRow Iterator pointing to the parent row in the available actions TreeView + */ + void appendRowAvailable(Action action, RowIterator& iterGroupParentRow); + + /** @brief Inserts a child row into the available actions TreeView + + The new row gets inserted before the given sibling row + + @param action Action to append + @param iterInsertBeforeThisRow Iterator pointing to a row in the available actions TreeView + */ + void insertBeforeRowAvailable(Action action, RowIterator& iterInsertBeforeThisRow); + + /** Chooses an action + + A.k.a. moving an action from available to favorites + + @param id ID of the action to choose + */ + void chooseAction(const Glib::ustring& id); + + /** Dismisses an action + + A.k.a. moving back an action from favorites to available + + @param id ID of the action to dismiss + */ + void dismissAction(const Glib::ustring& id); + + /** Arranges up an action in the favorites TreeView + + @param id ID of the action to arrange + */ + void arrangeUp(const Glib::ustring& id); + + /** Arranges down an action in the favorites TreeView + + @param id ID of the action to arrange + */ + void arrangeDown(const Glib::ustring& id); + + // signals + + /// When button choose action is clicked + void onBtnChooseActionClicked(); + /// When button dismiss action is clicked + void onBtnDismissActionClicked(); + /// When button arrange up favorite action is clicked + void onBtnArrangeUpClicked(); + /// When button arrange down favorite action is clicked + void onBtnArrangeDownClicked(); + +private: + DlgCustomize(DlgCustomize const &d) = delete; // no copy + DlgCustomize& operator=(DlgCustomize const &d) = delete; // no assign +}; + +} // namespace CustomizeFavorites + + +/** @brief Dialog where favorite actions are represented by buttons. + + This dialog is available on the main menu View > Favorites... + The first time it is opened, a "+" button is the only widget in the dialog. + This "+" button allows the user to open a dialog box where she can customize + which actions and in which order she want to have in the Favorites dialog. +*/ +class Favorites : public Widget::Panel { +public: + Favorites(); + + ~Favorites() override = default; + + /** @brief Creates an instance of this dialog. + + Used by the dialog manager to instanciate this class. + */ + static Favorites &getInstance() { return *new Favorites(); } + +protected: + // helper member functions + + /// Builds the user interface of this dialog. + void buildUI(); + /// Builds the buttons that represent the favorite actions. Called when the + /// dialog is presented and each time the user customizes her favorites. + void buildActionButtons(); + /// Removes all action buttons from the grid. This function should be + /// called before @c buildActionButtons + void removeActionButtons(); + + /** @brief Add an action button to the grid. + + Places the button on the right position (left, top) based on the optimal + number of columns. @see _optimalNumberOfColumns + + @param button An action button to be added to the grid. + */ + void addActionButton(Gtk::Button& button); + + /** @brief Gets current favorite actions from the user's preferences registry. + + Favorite actions are stored in the @c preferences.xml file, with all other + user's preferences. This allows favorite actions list to persist when the + user exits Inkscape. + + @return A @c std::vector of the favorite actions IDs. + */ + std::vector getPreferencesFavoriteActions(); + + /** @brief Executes a favorite action. + + @param actionId ID of the action to be performed. + */ + void performAction(const Glib::ustring actionId); + + // signal handlers + + /** @brief When dialog changes its size. + + Optimal number of columns is updated accordingly. + + @see _optimalNumberOfColumns + */ + void on_size_allocate(Gdk::Rectangle& allocation) override; + /// When mouse right click over any action button + bool on_button_release_event(GdkEventButton* buttonEvent) override; + /// When activate customize menu item on the customize pop-up menu + void onMenuCustomizeActivate(); + /// When any action button is clicked + /// @param actionId ID of the action corresponding to the button clicked on. + void onActionButtonClicked(const Glib::ustring& actionId); + + // widget members + + Gtk::Grid _gridActionButtons; ///< Grid that holds the action buttons + Gtk::Box _boxGlue; ///< Box that glues the grid and the dialog contents + Gtk::Menu _mnuCustomize; ///< Pop-up menu that allows the user to customize her favorites + +private: + Favorites(Favorites const &d) = delete; // no copy + Favorites& operator=(Favorites const &d) = delete; // no assign + + // constants + + const Glib::ustring + ADD_ICON_NAME, ///< Name of the icon used to add to the "+" button, shown when favorite actions list is empty + XML_PREFS_GROUPID, ///< Name of the ID property of a group within the @c preferences.xml file + XML_PREFS_ACTION_KEY_PREFIX, ///< Prefix used to name each key of an action in the @c preferences.xml file + PREFS_FAVORITE_ACTIONS_PATH; ///< Path where favorite actions are saved in the @c preferences.xml file + + // state + + /// Optimal number of columns of the action buttons grid. Used by @c addActionButton + /// to set each button in the right position according to the space available + int _optimalNumberOfColumns = 8; +}; + + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_DIALOG_FAVORITES_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/verbs.cpp b/src/verbs.cpp index bfdae4e0bf7a1b953e42a4f3e4517d7013b8cc48..bc32300c5faf6fc84419dae32f5d12411a1f3840 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -2174,6 +2174,9 @@ void DialogVerb::perform(SPAction *action, void *data) case SP_VERB_DIALOG_ALIGN_DISTRIBUTE: dt->_dlg_mgr->showDialog("AlignAndDistribute"); break; + case SP_VERB_DIALOG_FAVORITES: + dt->_dlg_mgr->showDialog("Favorites"); + break; case SP_VERB_DIALOG_SPRAY_OPTION: dt->_dlg_mgr->showDialog("SprayOptionClass"); break; @@ -3102,6 +3105,8 @@ Verb *Verb::_base_verbs[] = { N_("Precisely control objects' transformations"), INKSCAPE_ICON("dialog-transform")), new DialogVerb(SP_VERB_DIALOG_ALIGN_DISTRIBUTE, "DialogAlignDistribute", N_("_Align and Distribute..."), N_("Align and distribute objects"), INKSCAPE_ICON("dialog-align-and-distribute")), + new DialogVerb(SP_VERB_DIALOG_FAVORITES, "DialogFavorites", N_("_Favorites..."), + N_("Favorite commands"), INKSCAPE_ICON("dialog-favorites")), new DialogVerb(SP_VERB_DIALOG_SPRAY_OPTION, "DialogSprayOption", N_("_Spray options..."), N_("Some options for the spray"), INKSCAPE_ICON("dialog-spray-options")), new DialogVerb(SP_VERB_DIALOG_UNDO_HISTORY, "DialogUndoHistory", N_("Undo _History..."), N_("Undo History"), diff --git a/src/verbs.h b/src/verbs.h index b22a9eebec1f407c03eea996d40c21c1b8d508da..9e52229f9e85d9d8db8c3e7800e61b1c02b43a73 100644 --- a/src/verbs.h +++ b/src/verbs.h @@ -318,6 +318,7 @@ enum { SP_VERB_DIALOG_SYMBOLS, SP_VERB_DIALOG_TRANSFORM, SP_VERB_DIALOG_ALIGN_DISTRIBUTE, + SP_VERB_DIALOG_FAVORITES, SP_VERB_DIALOG_SPRAY_OPTION, SP_VERB_DIALOG_UNDO_HISTORY, SP_VERB_DIALOG_TEXT, diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp index d3daa892f90cba5cd4b12189869e5ec54c415ba6..192551d4d4ec3ec0df1ffb7d7d70d40c4f0c693b 100644 --- a/src/widgets/toolbox.cpp +++ b/src/widgets/toolbox.cpp @@ -304,6 +304,7 @@ static Glib::RefPtr create_or_fetch_actions( SPDesktop* deskto //SP_VERB_EDIT_TILE, //SP_VERB_EDIT_UNTILE, SP_VERB_DIALOG_ALIGN_DISTRIBUTE, + SP_VERB_DIALOG_FAVORITES, SP_VERB_DIALOG_DISPLAY, SP_VERB_DIALOG_FILL_STROKE, SP_VERB_DIALOG_NAMEDVIEW,