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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
+
+
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 @@
Transform... (dialog-transform)
Align and Distribute... (dialog-align-and-distribute)
+
Favorites... (dialog-favorites)
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