summaryrefslogtreecommitdiffstats
path: root/src/actions
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/actions
parentInitial commit. (diff)
downloadinkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz
inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/actions/CMakeLists.txt12
-rw-r--r--src/actions/README25
-rw-r--r--src/actions/actions-base.cpp252
-rw-r--r--src/actions/actions-base.h18
-rw-r--r--src/actions/actions-canvas-mode.cpp324
-rw-r--r--src/actions/actions-canvas-mode.h29
-rw-r--r--src/actions/actions-canvas-snapping.cpp412
-rw-r--r--src/actions/actions-canvas-snapping.h40
-rw-r--r--src/actions/actions-canvas-transform.cpp286
-rw-r--r--src/actions/actions-canvas-transform.h30
-rw-r--r--src/actions/actions-dialogs.cpp162
-rw-r--r--src/actions/actions-dialogs.h32
-rw-r--r--src/actions/actions-edit-document.cpp94
-rw-r--r--src/actions/actions-edit-document.h19
-rw-r--r--src/actions/actions-edit-window.cpp74
-rw-r--r--src/actions/actions-edit-window.h19
-rw-r--r--src/actions/actions-edit.cpp344
-rw-r--r--src/actions/actions-edit.h19
-rw-r--r--src/actions/actions-effect-data.cpp63
-rw-r--r--src/actions/actions-effect-data.h49
-rw-r--r--src/actions/actions-effect.cpp83
-rw-r--r--src/actions/actions-effect.h19
-rw-r--r--src/actions/actions-element-a.cpp88
-rw-r--r--src/actions/actions-element-a.h29
-rw-r--r--src/actions/actions-element-image.cpp155
-rw-r--r--src/actions/actions-element-image.h29
-rw-r--r--src/actions/actions-extra-data.cpp84
-rw-r--r--src/actions/actions-extra-data.h78
-rw-r--r--src/actions/actions-file-window.cpp165
-rw-r--r--src/actions/actions-file-window.h19
-rw-r--r--src/actions/actions-file.cpp147
-rw-r--r--src/actions/actions-file.h29
-rw-r--r--src/actions/actions-help-url.cpp153
-rw-r--r--src/actions/actions-help-url.h19
-rw-r--r--src/actions/actions-helper.cpp45
-rw-r--r--src/actions/actions-helper.h34
-rw-r--r--src/actions/actions-hide-lock.cpp237
-rw-r--r--src/actions/actions-hide-lock.h19
-rw-r--r--src/actions/actions-hint-data.cpp47
-rw-r--r--src/actions/actions-hint-data.h36
-rw-r--r--src/actions/actions-layer.cpp539
-rw-r--r--src/actions/actions-layer.h19
-rw-r--r--src/actions/actions-node-align.cpp136
-rw-r--r--src/actions/actions-node-align.h29
-rw-r--r--src/actions/actions-object-align.cpp852
-rw-r--r--src/actions/actions-object-align.h29
-rw-r--r--src/actions/actions-object.cpp345
-rw-r--r--src/actions/actions-object.h29
-rw-r--r--src/actions/actions-output.cpp363
-rw-r--r--src/actions/actions-output.h29
-rw-r--r--src/actions/actions-pages.cpp113
-rw-r--r--src/actions/actions-pages.h29
-rw-r--r--src/actions/actions-paths.cpp266
-rw-r--r--src/actions/actions-paths.h31
-rw-r--r--src/actions/actions-selection-object.cpp196
-rw-r--r--src/actions/actions-selection-object.h19
-rw-r--r--src/actions/actions-selection-window.cpp149
-rw-r--r--src/actions/actions-selection-window.h19
-rw-r--r--src/actions/actions-selection.cpp284
-rw-r--r--src/actions/actions-selection.h29
-rw-r--r--src/actions/actions-text.cpp104
-rw-r--r--src/actions/actions-text.h19
-rw-r--r--src/actions/actions-tools.cpp413
-rw-r--r--src/actions/actions-tools.h81
-rw-r--r--src/actions/actions-transform.cpp170
-rw-r--r--src/actions/actions-transform.h29
-rw-r--r--src/actions/actions-tutorial.cpp107
-rw-r--r--src/actions/actions-tutorial.h19
-rw-r--r--src/actions/actions-undo-document.cpp115
-rw-r--r--src/actions/actions-undo-document.h23
-rw-r--r--src/actions/actions-view-mode.cpp372
-rw-r--r--src/actions/actions-view-mode.h20
-rw-r--r--src/actions/actions-view-window.cpp80
-rw-r--r--src/actions/actions-view-window.h30
-rw-r--r--src/actions/actions-window.cpp82
-rw-r--r--src/actions/actions-window.h29
76 files changed, 9016 insertions, 0 deletions
diff --git a/src/actions/CMakeLists.txt b/src/actions/CMakeLists.txt
new file mode 100644
index 0000000..d5b8339
--- /dev/null
+++ b/src/actions/CMakeLists.txt
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(actions_SRC
+ # actions-base.cpp
+ # actions-selection.cpp
+
+ # HEADERS
+ # actions-base.h
+ # actions-selection.h
+ )
+
+add_inkscape_source("${actions_SRC}")
diff --git a/src/actions/README b/src/actions/README
new file mode 100644
index 0000000..ec2ce4a
--- /dev/null
+++ b/src/actions/README
@@ -0,0 +1,25 @@
+This directory contains Gio::Actions.
+
+"A GAction (Gio::Action) is a representation of a single
+user-interesting action in an application."
+(https://wiki.gnome.org/HowDoI/GAction)
+
+In more layman terms, it provides a uniform interface for calling
+functions with an optional single parameter that is not tied to the
+GUI.
+
+Actions provide two operations:
+
+* activation (which results in calling the function),
+* state change (if the action supports a state).
+
+While actions are defined independent of the GUI (unlike GtkAction),
+they can be used by "actionable widgets" (menus, buttons, etc.) by
+simply referring to them by name. They can also be remotely activated
+by D-Bus and GNotifications.
+
+To do:
+
+* Convert 'verbs' to Gio::Actions.
+* Update command line to use actions.
+* Update GUI to use actions.
diff --git a/src/actions/actions-base.cpp b/src/actions/actions-base.cpp
new file mode 100644
index 0000000..156be4b
--- /dev/null
+++ b/src/actions/actions-base.cpp
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions tied to the application and independent of GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-base.h"
+#include "actions-helper.h"
+
+#include "actions/actions-extra-data.h"
+#include "inkscape-application.h"
+
+#include "inkscape.h" // Inkscape::Application
+#include "inkscape-version-info.h"// Inkscape version
+#include "path-prefix.h" // Extension directory
+#include "selection.h" // Selection
+#include "object/sp-root.h" // query_all()
+#include "file.h" // dpi convert method
+#include "io/resource.h"
+
+void
+print_inkscape_version()
+{
+ std::cout << Inkscape::inkscape_version() << std::endl;
+}
+
+void
+print_debug_info()
+{
+ std::cout << Inkscape::debug_info() << std::endl;
+}
+
+void
+print_system_data_directory()
+{
+ std::cout << Glib::build_filename(get_inkscape_datadir(), "inkscape") << std::endl;
+}
+
+void
+print_user_data_directory()
+{
+ std::cout << Inkscape::IO::Resource::profile_path("") << std::endl;
+}
+
+// Helper function for query_x(), query_y(), query_width(), and query_height().
+void
+query_dimension(InkscapeApplication* app, bool extent, Geom::Dim2 const axis)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+
+ if (selection->isEmpty()) {
+ selection->add(document->getRoot());
+ }
+
+ bool first = true;
+ auto items = selection->items();
+ for (auto item : items) {
+ if (!first) {
+ std::cout << ",";
+ }
+ first = false;
+ Geom::OptRect area = item->documentVisualBounds();
+ if (area) {
+ if (extent) {
+ std::cout << area->dimensions()[axis];
+ } else {
+ std::cout << area->min()[axis];
+ }
+ } else {
+ std::cout << "0";
+ }
+ }
+ std::cout << std::endl;
+}
+
+void
+query_x(InkscapeApplication* app)
+{
+ query_dimension(app, false, Geom::X);
+}
+
+void
+query_y(InkscapeApplication* app)
+{
+ query_dimension(app, false, Geom::Y);
+}
+
+void
+query_width(InkscapeApplication* app)
+{
+ query_dimension(app, true, Geom::X);
+}
+
+void
+query_height(InkscapeApplication* app)
+{
+ query_dimension(app, true, Geom::Y);
+}
+
+// Helper for query_all()
+void
+query_all_recurse (SPObject *o)
+{
+ SPItem *item = dynamic_cast<SPItem*>(o);
+ if (item && item->getId()) {
+ Geom::OptRect area = item->documentVisualBounds();
+ if (area) {
+ // clang-format off
+ std::cout << item->getId() << ","
+ << area->min()[Geom::X] << ","
+ << area->min()[Geom::Y] << ","
+ << area->dimensions()[Geom::X] << ","
+ << area->dimensions()[Geom::Y] << std::endl;
+ // clang-format on
+ }
+
+ for (auto& child: o->children) {
+ query_all_recurse (&child);
+ }
+ }
+}
+
+void
+query_all(InkscapeApplication* app)
+{
+ SPDocument* doc = app->get_active_document();
+ if (!doc) {
+ std::cerr << "query_all: no document!" << std::endl;
+ return;
+ }
+
+ SPObject *o = doc->getRoot();
+ if (o) {
+ query_all_recurse(o);
+ }
+}
+
+void
+pdf_page(int page)
+{
+ INKSCAPE.set_pdf_page(page);
+}
+
+void
+convert_dpi_method(Glib::ustring method)
+{
+ if (method == "none") {
+ sp_file_convert_dpi_method_commandline = FILE_DPI_UNCHANGED;
+ } else if (method == "scale-viewbox") {
+ sp_file_convert_dpi_method_commandline = FILE_DPI_VIEWBOX_SCALED;
+ } else if (method == "scale-document") {
+ sp_file_convert_dpi_method_commandline = FILE_DPI_DOCUMENT_SCALED;
+ } else {
+ std::cerr << "dpi_convert_method: invalid option" << std::endl;
+ }
+}
+
+void
+no_convert_baseline()
+{
+ sp_no_convert_text_baseline_spacing = true;
+}
+
+void
+vacuum_defs(InkscapeApplication* app)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+ document->vacuumDocument();
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_base =
+{
+ // clang-format off
+ {"app.inkscape-version", N_("Inkscape Version"), "Base", N_("Print Inkscape version and exit") },
+ {"app.debug-info", N_("Debug Info"), "Base", N_("Print debugging information and exit") },
+ {"app.system-data-directory", N_("System Directory"), "Base", N_("Print system data directory and exit") },
+ {"app.user-data-directory", N_("User Directory"), "Base", N_("Print user data directory and exit") },
+ {"app.action-list", N_("List Actions"), "Base", N_("Print a list of actions and exit") },
+ {"app.vacuum-defs", N_("Clean up Document"), "Base", N_("Remove unused definitions (gradients, etc.)") },
+ {"app.quit", N_("Quit"), "Base", N_("Quit Inkscape, check for data loss") },
+ {"app.quit-immediate", N_("Quit Immediately"), "Base", N_("Immediately quit Inkscape, no check for data loss") },
+
+ {"app.open-page", N_("Import Page Number"), "Import", N_("Select PDF page number to import") },
+ {"app.convert-dpi-method", N_("Import DPI Method"), "Import", N_("Set DPI conversion method for legacy Inkscape files")},
+ {"app.no-convert-baseline", N_("No Import Baseline Conversion"), "Import", N_("Do not convert text baselines in legacy Inkscape files")},
+
+ {"app.query-x", N_("Query X"), "Query", N_("Query 'x' value(s) of selected objects") },
+ {"app.query-y", N_("Query Y"), "Query", N_("Query 'y' value(s) of selected objects") },
+ {"app.query-width", N_("Query Width"), "Query", N_("Query 'width' value(s) of object(s)") },
+ {"app.query-height", N_("Query Height"), "Query", N_("Query 'height' value(s) of object(s)") },
+ {"app.query-all", N_("Query All"), "Query", N_("Query 'x', 'y', 'width', and 'height'") }
+ // clang-format on
+};
+
+void
+add_actions_base(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // Note: "radio" actions are just an easy way to set type without using templating.
+ // clang-format off
+ gapp->add_action( "inkscape-version", sigc::ptr_fun(&print_inkscape_version) );
+ gapp->add_action( "debug-info", sigc::ptr_fun(&print_debug_info) );
+ gapp->add_action( "system-data-directory", sigc::ptr_fun(&print_system_data_directory) );
+ gapp->add_action( "user-data-directory", sigc::ptr_fun(&print_user_data_directory) );
+ gapp->add_action( "action-list", sigc::mem_fun(app, &InkscapeApplication::print_action_list) );
+ gapp->add_action( "vacuum-defs", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&vacuum_defs), app) );
+ gapp->add_action( "quit", sigc::mem_fun(app, &InkscapeApplication::on_quit) );
+ gapp->add_action( "quit-immediate", sigc::mem_fun(app, &InkscapeApplication::on_quit_immediate) );
+
+ gapp->add_action_radio_integer( "open-page", sigc::ptr_fun(&pdf_page), 0);
+ gapp->add_action_radio_string( "convert-dpi-method", sigc::ptr_fun(&convert_dpi_method), "none");
+ gapp->add_action( "no-convert-baseline", sigc::ptr_fun(&no_convert_baseline) );
+
+
+ gapp->add_action( "query-x", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&query_x), app) );
+ gapp->add_action( "query-y", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&query_y), app) );
+ gapp->add_action( "query-width", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&query_width), app) );
+ gapp->add_action( "query-height", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&query_height), app) );
+ gapp->add_action( "query-all", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&query_all), app) );
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_base);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-base.h b/src/actions/actions-base.h
new file mode 100644
index 0000000..a520788
--- /dev/null
+++ b/src/actions/actions-base.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_BASE_H
+#define INK_ACTIONS_BASE_H
+
+class InkscapeApplication;
+
+void add_actions_base(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_BASE_H
diff --git a/src/actions/actions-canvas-mode.cpp b/src/actions/actions-canvas-mode.cpp
new file mode 100644
index 0000000..829253e
--- /dev/null
+++ b/src/actions/actions-canvas-mode.cpp
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for changing the canvas display mode. Tied to a particular InkscapeWindow.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "ui/interface.h"
+#include "ui/view/view.h"
+
+#include "actions-canvas-mode.h"
+
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+
+#include "display/rendermode.h"
+#include "display/drawing.h" // Setting gray scale parameters.
+#include "display/control/canvas-item-drawing.h"
+
+#include "ui/widget/canvas.h"
+
+// TODO: Use action state rather than set variable in Canvas (via Desktop).
+// TODO: Move functions from Desktop to Canvas.
+// TODO: Canvas actions should belong to canvas (not window)!
+
+/**
+ * Helper function to set display mode.
+ */
+void
+canvas_set_display_mode(Inkscape::RenderMode value, InkscapeWindow *win, Glib::RefPtr<Gio::SimpleAction> saction)
+{
+ g_assert(value != Inkscape::RenderMode::size);
+ saction->change_state((int)value);
+
+ // Save value as a preference
+ Inkscape::Preferences *pref = Inkscape::Preferences::get();
+ pref->setInt("/options/displaymode", (int)value);
+
+ SPDesktop* dt = win->get_desktop();
+ auto canvas = dt->getCanvas();
+ canvas->set_render_mode(Inkscape::RenderMode(value));
+}
+
+/**
+ * Set display mode.
+ */
+void
+canvas_display_mode(int value, InkscapeWindow *win)
+{
+ if (value < 0 || value >= (int)Inkscape::RenderMode::size) {
+ std::cerr << "canvas_display_mode: value out of bound! : " << value << std::endl;
+ return;
+ }
+
+ auto action = win->lookup_action("canvas-display-mode");
+ if (!action) {
+ std::cerr << "canvas_display_mode: action 'canvas-display-mode' missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "canvas_display_mode: action 'canvas-display-mode' not SimpleAction!" << std::endl;
+ return;
+ }
+
+ canvas_set_display_mode(Inkscape::RenderMode(value), win, saction);
+}
+
+/**
+ * Cycle between values.
+ */
+void
+canvas_display_mode_cycle(InkscapeWindow *win)
+{
+ auto action = win->lookup_action("canvas-display-mode");
+ if (!action) {
+ std::cerr << "canvas_display_mode_cycle: action 'canvas-display-mode' missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "canvas_display_mode_cycle: action 'canvas-display-mode' not SimpleAction!" << std::endl;
+ return;
+ }
+
+ int value = -1;
+ saction->get_state(value);
+ value++;
+ value %= (int)Inkscape::RenderMode::size;
+
+ canvas_set_display_mode((Inkscape::RenderMode)value, win, saction);
+}
+
+
+/**
+ * Toggle between normal and last set other value.
+ */
+void
+canvas_display_mode_toggle(InkscapeWindow *win)
+{
+ auto action = win->lookup_action("canvas-display-mode");
+ if (!action) {
+ std::cerr << "canvas_display_mode_toggle: action 'canvas-display-mode' missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "canvas_display_mode_toogle: action 'canvas-display-mode' not SimpleAction!" << std::endl;
+ return;
+ }
+
+ static Inkscape::RenderMode old_value = Inkscape::RenderMode::OUTLINE;
+
+ int value = -1;
+ saction->get_state(value);
+ if (value == (int)Inkscape::RenderMode::NORMAL) {
+ canvas_set_display_mode(old_value, win, saction);
+ } else {
+ old_value = Inkscape::RenderMode(value);
+ canvas_set_display_mode(Inkscape::RenderMode::NORMAL, win, saction);
+ }
+}
+
+
+/**
+ * Set split mode.
+ */
+void
+canvas_split_mode(int value, InkscapeWindow *win)
+{
+ if (value < 0 || value >= (int)Inkscape::SplitMode::size) {
+ std::cerr << "canvas_split_mode: value out of bound! : " << value << std::endl;
+ return;
+ }
+
+ auto action = win->lookup_action("canvas-split-mode");
+ if (!action) {
+ std::cerr << "canvas_split_mode: action 'canvas-split-mode' missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "canvas_split_mode: action 'canvas-split-mode' not SimpleAction!" << std::endl;
+ return;
+ }
+
+ // If split mode is already set to the requested mode, turn it off.
+ int old_value = -1;
+ saction->get_state(old_value);
+ if (value == old_value) {
+ value = (int)Inkscape::SplitMode::NORMAL;
+ }
+
+ saction->change_state(value);
+
+ SPDesktop* dt = win->get_desktop();
+ auto canvas = dt->getCanvas();
+ canvas->set_split_mode(Inkscape::SplitMode(value));
+}
+
+/**
+ * Set gray scale for canvas.
+ */
+void
+canvas_color_mode_gray(InkscapeWindow *win)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ gdouble r = prefs->getDoubleLimited("/options/rendering/grayscale/red-factor", 0.21, 0.0, 1.0);
+ gdouble g = prefs->getDoubleLimited("/options/rendering/grayscale/green-factor", 0.72, 0.0, 1.0);
+ gdouble b = prefs->getDoubleLimited("/options/rendering/grayscale/blue-factor", 0.072, 0.0, 1.0);
+ gdouble grayscale_value_matrix[20] =
+ { r, g, b, 0, 0,
+ r, g, b, 0, 0,
+ r, g, b, 0, 0,
+ 0, 0, 0, 1, 0 };
+ SPDesktop* dt = win->get_desktop();
+ dt->getCanvasDrawing()->get_drawing()->setGrayscaleMatrix(grayscale_value_matrix);
+}
+
+/**
+ * Toggle Gray scale on/off.
+ */
+void
+canvas_color_mode_toggle(InkscapeWindow *win)
+{
+ auto action = win->lookup_action("canvas-color-mode");
+ if (!action) {
+ std::cerr << "canvas_color_mode_toggle: action missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "canvas_color_mode_toggle: action not SimpleAction!" << std::endl;
+ return;
+ }
+
+ bool state = false;
+ saction->get_state(state);
+ state = !state;
+ saction->change_state(state);
+
+ if (state) {
+ // Set gray scale parameters.
+ canvas_color_mode_gray(win);
+ }
+
+ SPDesktop* dt = win->get_desktop();
+ auto canvas = dt->getCanvas();
+ canvas->set_color_mode(state ? Inkscape::ColorMode::GRAYSCALE : Inkscape::ColorMode::NORMAL);
+}
+
+
+/**
+ * Toggle Color management on/off.
+ */
+void
+canvas_color_manage_toggle(InkscapeWindow *win)
+{
+ auto action = win->lookup_action("canvas-color-manage");
+ if (!action) {
+ std::cerr << "canvas_color_manage_toggle: action missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "canvas_color_manage_toggle: action not SimpleAction!" << std::endl;
+ return;
+ }
+
+ bool state = false;
+ saction->get_state(state);
+ state = !state;
+ saction->change_state(state);
+
+ // Save value as a preference
+ Inkscape::Preferences *pref = Inkscape::Preferences::get();
+ pref->setBool("/options/displayprofile/enable", state);
+
+ SPDesktop* dt = win->get_desktop();
+ auto canvas = dt->getCanvas();
+ canvas->set_cms_active(state);
+ canvas->redraw_all();
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_canvas_mode =
+{
+ // clang-format off
+ {"win.canvas-display-mode(0)", N_("Display Mode: Normal"), "Canvas Display", N_("Use normal rendering mode") },
+ {"win.canvas-display-mode(1)", N_("Display Mode: Outline"), "Canvas Display", N_("Show only object outlines") },
+ {"win.canvas-display-mode(2)", N_("Display Mode: No Filters"), "Canvas Display", N_("Do not render filters (for speed)") },
+ {"win.canvas-display-mode(3)", N_("Display Mode: Hairlines"), "Canvas Display", N_("Render thin lines visibly") },
+ {"win.canvas-display-mode-cycle", N_("Display Mode Cycle"), "Canvas Display", N_("Cycle through display modes") },
+ {"win.canvas-display-mode-toggle", N_("Display Mode Toggle"), "Canvas Display", N_("Toggle between normal and last non-normal mode") },
+
+ {"win.canvas-split-mode(0)", N_("Split Mode: Normal"), "Canvas Display", N_("Do not split canvas") },
+ {"win.canvas-split-mode(1)", N_("Split Mode: Split"), "Canvas Display", N_("Render part of the canvas in outline mode") },
+ {"win.canvas-split-mode(2)", N_("Split Mode: X-Ray"), "Canvas Display", N_("Render a circular area in outline mode") },
+
+ {"win.canvas-color-mode", N_("Color Mode"), "Canvas Display", N_("Toggle between normal and grayscale modes") },
+ {"win.canvas-color-manage", N_("Color Managed Mode"), "Canvas Display", N_("Toggle between normal and color managed modes") }
+ // clang-format on
+};
+
+void
+add_actions_canvas_mode(InkscapeWindow* win)
+{
+ // Sync action with desktop variables. TODO: Remove!
+ auto prefs = Inkscape::Preferences::get();
+
+ // Initial States of Actions
+ int display_mode = prefs->getIntLimited("/options/displaymode", 0, 0, 4); // Default, minimum, maximum
+ bool color_manage = prefs->getBool("/options/displayprofile/enable");
+
+ SPDesktop* dt = win->get_desktop();
+ if (dt) {
+ auto canvas = dt->getCanvas();
+ canvas->set_render_mode(Inkscape::RenderMode(display_mode));
+ canvas->set_cms_active(color_manage);
+ } else {
+ std::cerr << "add_actions_canvas_mode: no desktop!" << std::endl;
+ }
+
+ // clang-format off
+ win->add_action_radio_integer ("canvas-display-mode", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_display_mode), win), display_mode);
+ win->add_action( "canvas-display-mode-cycle", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_display_mode_cycle), win));
+ win->add_action( "canvas-display-mode-toggle", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_display_mode_toggle), win));
+ win->add_action_radio_integer ("canvas-split-mode", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_split_mode), win), (int)Inkscape::SplitMode::NORMAL);
+ win->add_action_bool( "canvas-color-mode", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_color_mode_toggle), win));
+ win->add_action_bool( "canvas-color-manage", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_color_manage_toggle), win), color_manage);
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_canvas_mode: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_canvas_mode);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-canvas-mode.h b/src/actions/actions-canvas-mode.h
new file mode 100644
index 0000000..2d17109
--- /dev/null
+++ b/src/actions/actions-canvas-mode.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for changing the canvas display mode. Tied to a particular InkscapeWindow.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_CANVAS_MODE_H
+#define INK_ACTIONS_CANVAS_MODE_H
+
+class InkscapeWindow;
+
+void add_actions_canvas_mode(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_CANVAS_MODE_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-canvas-snapping.cpp b/src/actions/actions-canvas-snapping.cpp
new file mode 100644
index 0000000..460bc95
--- /dev/null
+++ b/src/actions/actions-canvas-snapping.cpp
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for toggling snapping preferences. Not tied to a particular document.
+ *
+ * Copyright (C) 2019 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+#include <unordered_map>
+#include <vector>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-canvas-snapping.h"
+#include "actions/actions-extra-data.h"
+#include "inkscape-application.h"
+#include "inkscape.h"
+#include "inkscape-window.h"
+#include "desktop.h"
+#include "snap-preferences.h"
+
+using namespace Inkscape;
+
+void set_actions_canvas_snapping(Gio::ActionMap& map);
+
+// There are two snapping lists that must be connected:
+// 1. The Inkscape::SNAPTARGET value: e.g. Inkscape::SNAPTARGET_BBOX_CATEGORY.
+// 2. The Gio::Action name: e.g. "snap-bbox"
+
+struct SnapInfo {
+ Glib::ustring action_name; // action name without "doc." prefix
+ SnapTargetType type; // corresponding snapping type
+ bool set; // this is default for when "simple snapping" is ON and also initial value when preferences are deleted
+};
+
+typedef std::vector<SnapInfo> SnapVector;
+typedef std::unordered_map<SnapTargetType, Glib::ustring> SnapMap;
+
+SnapVector snap_bbox = {
+ { "snap-bbox", SNAPTARGET_BBOX_CATEGORY, true },
+ { "snap-bbox-edge", SNAPTARGET_BBOX_EDGE, true },
+ { "snap-bbox-corner", SNAPTARGET_BBOX_CORNER, true },
+ { "snap-bbox-edge-midpoint", SNAPTARGET_BBOX_EDGE_MIDPOINT, false },
+ { "snap-bbox-center", SNAPTARGET_BBOX_MIDPOINT, false },
+};
+
+SnapVector snap_node = {
+ { "snap-node-category", SNAPTARGET_NODE_CATEGORY, true },
+ { "snap-path", SNAPTARGET_PATH, true },
+ { "snap-path-intersection", SNAPTARGET_PATH_INTERSECTION, false }, // Note: OFF by default, as it is extremely slow in large documents!
+ { "snap-node-cusp", SNAPTARGET_NODE_CUSP, true },
+ { "snap-node-smooth", SNAPTARGET_NODE_SMOOTH, true },
+ { "snap-line-midpoint", SNAPTARGET_LINE_MIDPOINT, true },
+ { "snap-line-tangential", SNAPTARGET_PATH_TANGENTIAL, false },
+ { "snap-line-perpendicular", SNAPTARGET_PATH_PERPENDICULAR, false },
+};
+
+SnapVector snap_alignment = {
+ { "snap-alignment", SNAPTARGET_ALIGNMENT_CATEGORY, true },
+ { "snap-alignment-self", SNAPTARGET_ALIGNMENT_HANDLE, false },
+ // separate category:
+ { "snap-distribution", SNAPTARGET_DISTRIBUTION_CATEGORY, true },
+};
+
+SnapVector snap_all_the_rest = {
+ { "snap-others", SNAPTARGET_OTHERS_CATEGORY, true },
+ { "snap-object-midpoint", SNAPTARGET_OBJECT_MIDPOINT, false },
+ { "snap-rotation-center", SNAPTARGET_ROTATION_CENTER, false },
+ { "snap-text-baseline", SNAPTARGET_TEXT_BASELINE, true },
+ { "snap-path-mask", SNAPTARGET_PATH_MASK, true },
+ { "snap-path-clip", SNAPTARGET_PATH_CLIP, true },
+
+ { "snap-page-border", SNAPTARGET_PAGE_BORDER, true },
+ { "snap-grid", SNAPTARGET_GRID, true },
+ { "snap-guide", SNAPTARGET_GUIDE, true },
+};
+
+const struct {const char* action_name; SimpleSnap option; bool set;} simple_snap_options[] = {
+ { "simple-snap-bbox", SimpleSnap::BBox, true },
+ { "simple-snap-nodes", SimpleSnap::Nodes, true },
+ { "simple-snap-alignment", SimpleSnap::Alignment, false }
+};
+
+const SnapMap& get_snap_map() {
+ static SnapMap map;
+ if (map.empty()) {
+ for (auto&& snap : snap_bbox) { map[snap.type] = snap.action_name; }
+ for (auto&& snap : snap_node) { map[snap.type] = snap.action_name; }
+ for (auto&& snap : snap_alignment) { map[snap.type] = snap.action_name; }
+ for (auto&& snap : snap_all_the_rest) { map[snap.type] = snap.action_name; }
+ }
+ return map;
+}
+
+const SnapVector& get_snap_vect() {
+ static SnapVector vect;
+ if (vect.empty()) {
+ for (auto v : {&snap_bbox, &snap_node, &snap_alignment, &snap_all_the_rest}) {
+ vect.insert(vect.end(), v->begin(), v->end());
+ }
+ }
+ return vect;
+}
+
+const Glib::ustring snap_pref_path = "/options/snapping/";
+const Glib::ustring global_toggle = "snap-global-toggle";
+
+// global and single location of snapping preferences
+SnapPreferences& get_snapping_preferences() {
+ static SnapPreferences preferences;
+ static bool initialized = false;
+
+ if (!initialized) {
+ // after starting up restore all snapping preferences:
+ auto prefs = Preferences::get();
+ for (auto&& info : get_snap_vect()) {
+ bool enabled = prefs->getBool(snap_pref_path + info.action_name, info.set);
+ preferences.setTargetSnappable(info.type, enabled);
+ }
+ for (auto&& info : simple_snap_options) {
+ bool enabled = prefs->getBool(snap_pref_path + info.action_name, info.set);
+ preferences.set_simple_snap(info.option, enabled);
+ }
+
+ auto simple = prefs->getEntry("/toolbox/simplesnap");
+ if (!simple.isValid()) {
+ // first time up after creating preferences; apply "simple" snapping defaults
+ prefs->setBool(simple.getPath(), true);
+ transition_to_simple_snapping();
+ }
+
+ auto enabled = prefs->getEntry(snap_pref_path + global_toggle);
+ preferences.setSnapEnabledGlobally(enabled.getBool());
+
+ initialized = true;
+ }
+
+ return preferences;
+}
+
+void store_snapping_action(const Glib::ustring& action_name, bool enabled) {
+ Preferences::get()->setBool(snap_pref_path + action_name, enabled);
+}
+
+// Turn requested snapping type on or off:
+// * type - snap target
+// * enabled - true to turn it on, false to turn it off
+//
+void set_canvas_snapping(SnapTargetType type, bool enabled) {
+ get_snapping_preferences().setTargetSnappable(type, enabled);
+
+ auto it = get_snap_map().find(type);
+ if (it == get_snap_map().end()) {
+ g_warning("No action for snap target type %d", int(type));
+ }
+ else {
+ auto&& action_name = it->second;
+ store_snapping_action(action_name, enabled);
+ }
+}
+
+void update_actions(Gio::ActionMap& map) {
+ // Some actions depend on others... we need to update everything!
+ set_actions_canvas_snapping(map);
+}
+
+static void canvas_snapping_toggle(Gio::ActionMap& map, SnapTargetType type) {
+ bool enabled = get_snapping_preferences().isSnapButtonEnabled(type);
+ set_canvas_snapping(type, !enabled);
+ update_actions(map);
+}
+
+void set_simple_snap(SimpleSnap option, bool value) {
+ const SnapVector* vect = nullptr;
+ switch (option) {
+ case SimpleSnap::BBox:
+ vect = &snap_bbox;
+ break;
+ case SimpleSnap::Nodes:
+ vect = &snap_node;
+ break;
+ case SimpleSnap::Alignment:
+ vect = &snap_alignment;
+ break;
+ case SimpleSnap::Rest:
+ vect = &snap_all_the_rest;
+ break;
+ default:
+ std::cerr << "missing case statement in " << __func__ << std::endl;
+ break;
+ }
+
+ if (vect) {
+ for (auto&& info : *vect) {
+ bool enable = value && info.set;
+ set_canvas_snapping(info.type, enable);
+ }
+
+ Glib::ustring action_name;
+ for (auto&& info : simple_snap_options) {
+ if (info.option == option) {
+ action_name = info.action_name;
+ break;
+ }
+ }
+ // simple snap option 'Rest' does not have an action; only save other ones
+ if (!action_name.empty()) {
+ get_snapping_preferences().set_simple_snap(option, value);
+ Preferences::get()->setBool(snap_pref_path + action_name, value);
+ }
+ }
+}
+
+void toggle_simple_snap_option(Gio::ActionMap& map, SimpleSnap option) {
+ // toggle desired option
+ bool enabled = !get_snapping_preferences().get_simple_snap(option);
+ set_simple_snap(option, enabled);
+
+ // reset others not visible / not exposed to their "simple" defaults
+ for (auto&& info : snap_all_the_rest) {
+ set_canvas_snapping(info.type, info.set);
+ }
+
+ update_actions(map);
+}
+
+void apply_simple_snap_defaults(Gio::ActionMap& map) {
+ set_simple_snap(SimpleSnap::BBox, true);
+ set_simple_snap(SimpleSnap::Nodes, true);
+ set_simple_snap(SimpleSnap::Alignment, false);
+ set_simple_snap(SimpleSnap::Rest, true);
+ update_actions(map);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_canvas_snapping =
+{
+ {"win.snap-global-toggle", N_("Snapping"), "Snap", N_("Toggle snapping on/off") },
+
+ {"win.snap-alignment", N_("Snap Objects that Align"), "Snap", N_("Toggle alignment snapping") },
+ {"win.snap-alignment-self", N_("Snap Nodes that Align"), "Snap", N_("Toggle alignment snapping to nodes in the same path")},
+
+ {"win.snap-distribution", N_("Snap Objects at Equal Distances"), "Snap", N_("Toggle snapping objects at equal distances")},
+
+ {"win.snap-bbox", N_("Snap Bounding Boxes"), "Snap", N_("Toggle snapping to bounding boxes (global)") },
+ {"win.snap-bbox-edge", N_("Snap Bounding Box Edges"), "Snap", N_("Toggle snapping to bounding-box edges") },
+ {"win.snap-bbox-corner", N_("Snap Bounding Box Corners"), "Snap", N_("Toggle snapping to bounding-box corners") },
+ {"win.snap-bbox-edge-midpoint", N_("Snap Bounding Box Edge Midpoints"), "Snap", N_("Toggle snapping to bounding-box edge mid-points") },
+ {"win.snap-bbox-center", N_("Snap Bounding Box Centers"), "Snap", N_("Toggle snapping to bounding-box centers") },
+
+ {"win.snap-node-category", N_("Snap Nodes"), "Snap", N_("Toggle snapping to nodes (global)") },
+ {"win.snap-path", N_("Snap Paths"), "Snap", N_("Toggle snapping to paths") },
+ {"win.snap-path-intersection", N_("Snap Path Intersections"), "Snap", N_("Toggle snapping to path intersections") },
+ {"win.snap-node-cusp", N_("Snap Cusp Nodes"), "Snap", N_("Toggle snapping to cusp nodes, including rectangle corners")},
+ {"win.snap-node-smooth", N_("Snap Smooth Node"), "Snap", N_("Toggle snapping to smooth nodes, including quadrant points of ellipses")},
+ {"win.snap-line-midpoint", N_("Snap Line Midpoints"), "Snap", N_("Toggle snapping to midpoints of lines") },
+ {"win.snap-line-perpendicular", N_("Snap Perpendicular Lines"), "Snap", N_("Toggle snapping to perpendicular lines") },
+ {"win.snap-line-tangential", N_("Snap Tangential Lines"), "Snap", N_("Toggle snapping to tangential lines") },
+
+ {"win.snap-others", N_("Snap Others"), "Snap", N_("Toggle snapping to misc. points (global)") },
+ {"win.snap-object-midpoint", N_("Snap Object Midpoint"), "Snap", N_("Toggle snapping to object midpoint") },
+ {"win.snap-rotation-center", N_("Snap Rotation Center"), "Snap", N_("Toggle snapping to object rotation center") },
+ {"win.snap-text-baseline", N_("Snap Text Baselines"), "Snap", N_("Toggle snapping to text baseline and text anchors") },
+
+ {"win.snap-page-border", N_("Snap Page Border"), "Snap", N_("Toggle snapping to page border") },
+ {"win.snap-grid", N_("Snap Grids"), "Snap", N_("Toggle snapping to grids") },
+ {"win.snap-guide", N_("Snap Guide Lines"), "Snap", N_("Toggle snapping to guide lines") },
+
+ {"win.snap-path-mask", N_("Snap Mask Paths"), "Snap", N_("Toggle snapping to mask paths") },
+ {"win.snap-path-clip", N_("Snap Clip Paths"), "Snap", N_("Toggle snapping to clip paths") },
+
+ {"win.simple-snap-bbox", N_("Simple Snap Bounding Box"), "Snap", N_("Toggle snapping to bounding boxes") },
+ {"win.simple-snap-nodes", N_("Simple Snap Nodes"), "Snap", N_("Toggle snapping to nodes") },
+ {"win.simple-snap-alignment", N_("Simple Snap Alignment"), "Snap", N_("Toggle alignment snapping") },
+};
+
+void add_actions_canvas_snapping(Gio::ActionMap* map) {
+ assert(map != nullptr);
+
+ map->add_action_bool(global_toggle, [=]() {
+ auto& pref = get_snapping_preferences();
+ bool enabled = !pref.getSnapEnabledGlobally();
+ pref.setSnapEnabledGlobally(enabled);
+ store_snapping_action(global_toggle, enabled);
+ update_actions(*map);
+ });
+
+ for (auto&& info : get_snap_vect()) {
+ map->add_action_bool(info.action_name, [=](){ canvas_snapping_toggle(*map, info.type); });
+ }
+
+ // Simple snapping popover
+ for (auto&& info : simple_snap_options) {
+ map->add_action_bool(info.action_name, [=](){ toggle_simple_snap_option(*map, info.option); });
+ }
+
+ // Check if there is already an application instance (GUI or non-GUI).
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_canvas_snapping: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_canvas_snapping);
+
+ update_actions(*map);
+}
+
+
+void
+set_actions_canvas_snapping_helper(Gio::ActionMap& map, Glib::ustring action_name, bool state, bool enabled)
+{
+ // Glib::RefPtr<Gio::SimpleAction> saction = map->lookup_action(action_name); NOT POSSIBLE!
+
+ // We can't enable/disable action directly! (Gio::Action can "get" enabled value but can not
+ // "set" it! We need to cast to Gio::SimpleAction)
+ Glib::RefPtr<Gio::Action> action = map.lookup_action(action_name);
+ if (!action) {
+ std::cerr << "set_actions_canvas_snapping_helper: action " << action_name << " missing!" << std::endl;
+ return;
+ }
+
+ auto simple = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!simple) {
+ std::cerr << "set_actions_canvas_snapping_helper: action " << action_name << " not SimpleAction!" << std::endl;
+ return;
+ }
+
+ simple->change_state(state);
+ simple->set_enabled(enabled);
+}
+
+void set_actions_canvas_snapping(Gio::ActionMap& map) {
+ auto& snapprefs = get_snapping_preferences();
+ bool global = snapprefs.getSnapEnabledGlobally();
+ bool alignment = snapprefs.isTargetSnappable(SNAPTARGET_ALIGNMENT_CATEGORY);
+ bool distribution = snapprefs.isTargetSnappable(SNAPTARGET_DISTRIBUTION_CATEGORY);
+ bool bbox = snapprefs.isTargetSnappable(SNAPTARGET_BBOX_CATEGORY);
+ bool node = snapprefs.isTargetSnappable(SNAPTARGET_NODE_CATEGORY);
+ bool other = snapprefs.isTargetSnappable(SNAPTARGET_OTHERS_CATEGORY);
+
+ struct { const char* action; bool state; bool enabled; } snap_options[] = {
+ { "snap-global-toggle", global, true }, // Always enabled
+
+ { "snap-alignment", alignment, global },
+ { "snap-alignment-self", snapprefs.isSnapButtonEnabled(SNAPTARGET_ALIGNMENT_HANDLE), global && alignment },
+
+ { "snap-distribution", distribution, global },
+
+ { "snap-bbox", bbox, global },
+ { "snap-bbox-edge", snapprefs.isSnapButtonEnabled(SNAPTARGET_BBOX_EDGE), global && bbox },
+ { "snap-bbox-corner", snapprefs.isSnapButtonEnabled(SNAPTARGET_BBOX_CORNER), global && bbox },
+ { "snap-bbox-edge-midpoint", snapprefs.isSnapButtonEnabled(SNAPTARGET_BBOX_EDGE_MIDPOINT), global && bbox },
+ { "snap-bbox-center", snapprefs.isSnapButtonEnabled(SNAPTARGET_BBOX_MIDPOINT), global && bbox },
+
+ { "snap-node-category", node, global },
+ { "snap-path", snapprefs.isSnapButtonEnabled(SNAPTARGET_PATH), global && node },
+ { "snap-path-intersection", snapprefs.isSnapButtonEnabled(SNAPTARGET_PATH_INTERSECTION), global && node },
+ { "snap-node-cusp", snapprefs.isSnapButtonEnabled(SNAPTARGET_NODE_CUSP), global && node },
+ { "snap-node-smooth", snapprefs.isSnapButtonEnabled(SNAPTARGET_NODE_SMOOTH), global && node },
+ { "snap-line-midpoint", snapprefs.isSnapButtonEnabled(SNAPTARGET_LINE_MIDPOINT), global && node },
+ { "snap-line-tangential", snapprefs.isSnapButtonEnabled(SNAPTARGET_PATH_TANGENTIAL), global && node },
+ { "snap-line-perpendicular", snapprefs.isSnapButtonEnabled(SNAPTARGET_PATH_PERPENDICULAR), global && node },
+
+ { "snap-others", other, global },
+ { "snap-object-midpoint", snapprefs.isSnapButtonEnabled(SNAPTARGET_OBJECT_MIDPOINT), global && other },
+ { "snap-rotation-center", snapprefs.isSnapButtonEnabled(SNAPTARGET_ROTATION_CENTER), global && other },
+ { "snap-text-baseline", snapprefs.isSnapButtonEnabled(SNAPTARGET_TEXT_BASELINE), global && other },
+
+ { "snap-page-border", snapprefs.isSnapButtonEnabled(SNAPTARGET_PAGE_BORDER), global },
+ { "snap-grid", snapprefs.isSnapButtonEnabled(SNAPTARGET_GRID), global },
+ { "snap-guide", snapprefs.isSnapButtonEnabled(SNAPTARGET_GUIDE), global },
+
+ { "snap-path-clip", snapprefs.isSnapButtonEnabled(SNAPTARGET_PATH_CLIP), global },
+ { "snap-path-mask", snapprefs.isSnapButtonEnabled(SNAPTARGET_PATH_MASK), global },
+
+ { "simple-snap-bbox", bbox, global },
+ { "simple-snap-nodes", node, global },
+ { "simple-snap-alignment", alignment, global },
+ };
+
+ for (auto&& snap : snap_options) {
+ set_actions_canvas_snapping_helper(map, snap.action, snap.state, snap.enabled);
+ }
+}
+
+/**
+ * Simple snapping groups existing "advanced" options into three easy to understand choices (bounding box, nodes, alignment snap).
+ * Behind the scene the same snapping properties to used. When entering "simple" mode those snapping properties need to be set
+ * to the correct default values; advanced mode affords complete freedom in selecting them, simple mode restricts them.
+ */
+void transition_to_simple_snapping() {
+ if (auto* dt = SP_ACTIVE_DESKTOP) {
+ if (Gio::ActionMap* map = dt->getInkscapeWindow()) {
+ apply_simple_snap_defaults(*map);
+ }
+ }
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-canvas-snapping.h b/src/actions/actions-canvas-snapping.h
new file mode 100644
index 0000000..9b958d2
--- /dev/null
+++ b/src/actions/actions-canvas-snapping.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for toggling snapping preferences. Tied to a particular document.
+ *
+ * As preferences are stored per document, changes should be propagated to any window with same document.
+ *
+ * Copyright (C) 2019 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_CANVAS_SNAPPING_H
+#define INK_ACTIONS_CANVAS_SNAPPING_H
+
+class SPDocument;
+namespace Inkscape {
+ class SnapPreferences;
+}
+
+void add_actions_canvas_snapping(Gio::ActionMap* map);
+
+std::vector<std::vector<Glib::ustring>> get_extra_data_canvas_snapping();
+
+Inkscape::SnapPreferences& get_snapping_preferences();
+
+void transition_to_simple_snapping();
+
+#endif // INK_ACTIONS_CANVAS_SNAPPING_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-canvas-transform.cpp b/src/actions/actions-canvas-transform.cpp
new file mode 100644
index 0000000..da9cec2
--- /dev/null
+++ b/src/actions/actions-canvas-transform.cpp
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for transforming the canvas view. Tied to a particular InkscapeWindow.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-canvas-transform.h"
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "desktop.h"
+
+#include "object/sp-namedview.h"
+#include "page-manager.h"
+
+#include "ui/tools/freehand-base.h" // SP_DRAW_CONTEXT
+#include "ui/tools/pen-tool.h"
+#include "ui/tools/pencil-tool.h"
+
+#include "ui/widget/canvas.h" // Canvas area
+
+enum {
+ INK_CANVAS_ZOOM_IN,
+ INK_CANVAS_ZOOM_OUT,
+ INK_CANVAS_ZOOM_1_1,
+ INK_CANVAS_ZOOM_1_2,
+ INK_CANVAS_ZOOM_2_1,
+ INK_CANVAS_ZOOM_SELECTION,
+ INK_CANVAS_ZOOM_DRAWING,
+ INK_CANVAS_ZOOM_PAGE,
+ INK_CANVAS_ZOOM_PAGE_WIDTH,
+ INK_CANVAS_ZOOM_CENTER_PAGE,
+ INK_CANVAS_ZOOM_PREV,
+ INK_CANVAS_ZOOM_NEXT,
+
+ INK_CANVAS_ROTATE_CW,
+ INK_CANVAS_ROTATE_CCW,
+ INK_CANVAS_ROTATE_RESET,
+ INK_CANVAS_FLIP_HORIZONTAL,
+ INK_CANVAS_FLIP_VERTICAL,
+ INK_CANVAS_FLIP_RESET
+};
+
+static void
+canvas_zoom_helper(SPDesktop* dt, const Geom::Point& midpoint, double zoom_factor)
+{
+ if (dynamic_cast<Inkscape::UI::Tools::PencilTool *>(dt->event_context) ||
+ dynamic_cast<Inkscape::UI::Tools::PenTool *>(dt->event_context) ) {
+
+ // Zoom around end of unfinished path.
+ std::optional<Geom::Point> zoom_to =
+ dynamic_cast<Inkscape::UI::Tools::FreehandBase*>(dt->event_context)->red_curve_get_last_point();
+ if (zoom_to) {
+ dt->zoom_relative(*zoom_to, zoom_factor);
+ return;
+ }
+ }
+
+ dt->zoom_relative(midpoint, zoom_factor, false);
+}
+
+void
+canvas_transform(InkscapeWindow *win, const int& option)
+{
+ SPDesktop* dt = win->get_desktop();
+ SPDocument *doc = dt->getDocument();
+
+ // The following might be better done elsewhere:
+
+ // Get preference dependent parameters
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double zoom_inc =
+ prefs->getDoubleLimited("/options/zoomincrement/value", M_SQRT2, 1.01, 10);
+ double rotate_inc =
+ prefs->getDoubleLimited("/options/rotateincrement/value", 15, 1, 90, "°");
+ rotate_inc *= M_PI/180.0;
+
+ // Get document dependent parameters
+ Geom::Rect const canvas = dt->getCanvas()->get_area_world();
+ Geom::Point midpoint = dt->w2d(canvas.midpoint()); // Midpoint of drawing on canvas.
+
+ switch (option) {
+ case INK_CANVAS_ZOOM_IN:
+ canvas_zoom_helper( dt, midpoint, zoom_inc);
+ break;
+
+ case INK_CANVAS_ZOOM_OUT:
+ canvas_zoom_helper( dt, midpoint, 1.0 / zoom_inc); // zoom_inc > 1
+ break;
+
+ case INK_CANVAS_ZOOM_1_1:
+ dt->zoom_realworld( midpoint, 1.0 );
+ break;
+
+ case INK_CANVAS_ZOOM_1_2:
+ dt->zoom_realworld( midpoint, 0.5 );
+ break;
+
+ case INK_CANVAS_ZOOM_2_1:
+ dt->zoom_realworld( midpoint, 2.0 );
+ break;
+
+ case INK_CANVAS_ZOOM_SELECTION:
+ dt->zoom_selection();
+ break;
+
+ case INK_CANVAS_ZOOM_DRAWING:
+ dt->zoom_drawing();
+ break;
+
+ case INK_CANVAS_ZOOM_PAGE:
+ doc->getPageManager().zoomToSelectedPage(dt, false);
+ break;
+
+ case INK_CANVAS_ZOOM_PAGE_WIDTH:
+ doc->getPageManager().zoomToSelectedPage(dt, true);
+ break;
+
+ case INK_CANVAS_ZOOM_CENTER_PAGE:
+ doc->getPageManager().centerToSelectedPage(dt);
+ break;
+
+ case INK_CANVAS_ZOOM_PREV:
+ dt->prev_transform();
+ break;
+
+ case INK_CANVAS_ZOOM_NEXT:
+ dt->next_transform(); // Is this only zoom? Yes!
+ break;
+
+ case INK_CANVAS_ROTATE_CW:
+ dt->rotate_relative_center_point (midpoint, rotate_inc);
+ break;
+
+ case INK_CANVAS_ROTATE_CCW:
+ dt->rotate_relative_center_point (midpoint, -rotate_inc);
+ break;
+
+ case INK_CANVAS_ROTATE_RESET:
+ dt->rotate_absolute_center_point (midpoint, 0);
+ break;
+
+ case INK_CANVAS_FLIP_HORIZONTAL:
+ dt->flip_relative_center_point (midpoint, SPDesktop::FLIP_HORIZONTAL);
+ break;
+
+ case INK_CANVAS_FLIP_VERTICAL:
+ dt->flip_relative_center_point (midpoint, SPDesktop::FLIP_VERTICAL);
+ break;
+
+ case INK_CANVAS_FLIP_RESET:
+ dt->flip_absolute_center_point (midpoint, SPDesktop::FLIP_NONE);
+ break;
+
+ default:
+ std::cerr << "canvas_zoom: unhandled action value!" << std::endl;
+ }
+}
+
+/**
+ * Toggle rotate lock.
+ */
+void
+canvas_rotate_lock(InkscapeWindow *win)
+{
+ auto action = win->lookup_action("canvas-rotate-lock");
+ if (!action) {
+ std::cerr << "canvas_rotate_lock: action missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "canvas_rotate_lock: action not SimpleAction!" << std::endl;
+ return;
+ }
+
+ bool state = false;
+ saction->get_state(state);
+ state = !state;
+ saction->change_state(state);
+
+ // Save value as a preference
+ Inkscape::Preferences *pref = Inkscape::Preferences::get();
+ pref->setBool("/options/rotationlock", state);
+ std::cout << "rotate_lock: set to: " << state << std::endl;
+
+ SPDesktop* dt = win->get_desktop();
+ dt->set_rotation_lock(state);
+}
+
+
+std::vector<std::vector<Glib::ustring>> raw_data_canvas_transform =
+{
+ // clang-format off
+ {"win.canvas-zoom-in", N_("Zoom In"), "Canvas Geometry", N_("Zoom in") },
+ {"win.canvas-zoom-out", N_("Zoom Out"), "Canvas Geometry", N_("Zoom out") },
+ {"win.canvas-zoom-1-1", N_("Zoom 1:1"), "Canvas Geometry", N_("Zoom to 1:1") },
+ {"win.canvas-zoom-1-2", N_("Zoom 1:2"), "Canvas Geometry", N_("Zoom to 1:2") },
+ {"win.canvas-zoom-2-1", N_("Zoom 2:1"), "Canvas Geometry", N_("Zoom to 2:1") },
+ {"win.canvas-zoom-selection", N_("Zoom Selection"), "Canvas Geometry", N_("Zoom to fit selection in window") },
+ {"win.canvas-zoom-drawing", N_("Zoom Drawing"), "Canvas Geometry", N_("Zoom to fit drawing in window") },
+ {"win.canvas-zoom-page", N_("Zoom Page"), "Canvas Geometry", N_("Zoom to fit page in window") },
+ {"win.canvas-zoom-page-width", N_("Zoom Page Width"), "Canvas Geometry", N_("Zoom to fit page width in window") },
+ {"win.canvas-zoom-center-page", N_("Zoom Center Page"), "Canvas Geometry", N_("Center page in window") },
+ {"win.canvas-zoom-prev", N_("Zoom Prev"), "Canvas Geometry", N_("Go back to previous zoom (from the history of zooms)")},
+ {"win.canvas-zoom-next", N_("Zoom Next"), "Canvas Geometry", N_("Go to next zoom (from the history of zooms)")},
+
+ {"win.canvas-rotate-cw", N_("Rotate Clockwise"), "Canvas Geometry", N_("Rotate canvas clockwise") },
+ {"win.canvas-rotate-ccw", N_("Rotate Counter-CW"), "Canvas Geometry", N_("Rotate canvas counter-clockwise") },
+ {"win.canvas-rotate-reset", N_("Reset Rotation"), "Canvas Geometry", N_("Reset canvas rotation") },
+
+ {"win.canvas-flip-horizontal", N_("Flip Horizontal"), "Canvas Geometry", N_("Flip canvas horizontally") },
+ {"win.canvas-flip-vertical", N_("Flip Vertical"), "Canvas Geometry", N_("Flip canvas vertically") },
+ {"win.canvas-flip-reset", N_("Reset Flipping"), "Canvas Geometry", N_("Reset canvas flipping") },
+
+ {"win.canvas-rotate-lock", N_("Lock Rotation"), "Canvas Geometry", N_("Lock canvas rotation") },
+ // clang-format on
+};
+
+void
+add_actions_canvas_transform(InkscapeWindow* win)
+{
+ auto prefs = Inkscape::Preferences::get();
+
+ bool rotate_lock = prefs->getBool("/options/rotationlock");
+
+ SPDesktop* dt = win->get_desktop();
+ if (dt) {
+ dt->set_rotation_lock(rotate_lock);
+ } else {
+ std::cerr << "add_actions_canvas_transform: no desktop!" << std::endl;
+ }
+
+ // clang-format off
+ win->add_action( "canvas-zoom-in", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_IN));
+ win->add_action( "canvas-zoom-out", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_OUT));
+ win->add_action( "canvas-zoom-1-1", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_1_1));
+ win->add_action( "canvas-zoom-1-2", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_1_2));
+ win->add_action( "canvas-zoom-2-1", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_2_1));
+ win->add_action( "canvas-zoom-selection", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_SELECTION));
+ win->add_action( "canvas-zoom-drawing", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_DRAWING));
+ win->add_action( "canvas-zoom-page", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_PAGE));
+ win->add_action( "canvas-zoom-page-width", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_PAGE_WIDTH));
+ win->add_action( "canvas-zoom-center-page",sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_CENTER_PAGE));
+ win->add_action( "canvas-zoom-prev", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_PREV));
+ win->add_action( "canvas-zoom-next", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_NEXT));
+
+ win->add_action( "canvas-rotate-cw", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ROTATE_CW));
+ win->add_action( "canvas-rotate-ccw", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ROTATE_CCW));
+ win->add_action( "canvas-rotate-reset", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ROTATE_RESET));
+
+ win->add_action( "canvas-flip-horizontal", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_FLIP_HORIZONTAL));
+ win->add_action( "canvas-flip-vertical", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_FLIP_VERTICAL));
+ win->add_action( "canvas-flip-reset", sigc::bind<InkscapeWindow*, int>(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_FLIP_RESET));
+
+ win->add_action_bool( "canvas-rotate-lock",sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_rotate_lock), win), rotate_lock);
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_canvas_transform: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_canvas_transform);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-canvas-transform.h b/src/actions/actions-canvas-transform.h
new file mode 100644
index 0000000..ad3538a
--- /dev/null
+++ b/src/actions/actions-canvas-transform.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for transforming the canvas view. Tied to a particular InkscapeWindow.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_CANVAS_TRANSFORM_H
+#define INK_ACTIONS_CANVAS_TRANSFORM_H
+
+class InkscapeWindow;
+class InkscapeApplication;
+
+void add_actions_canvas_transform(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_CANVAS_TRANSFORM_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-dialogs.cpp b/src/actions/actions-dialogs.cpp
new file mode 100644
index 0000000..0a2d391
--- /dev/null
+++ b/src/actions/actions-dialogs.cpp
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for switching tools.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+#include <map>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "config.h" // #ifdef WITH_GSPELL
+
+#include "actions-dialogs.h"
+
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+
+#include "ui/dialog/dialog-container.h"
+#include "ui/dialog/dialog-data.h"
+#include "ui/icon-names.h"
+
+// Note the "AttrDialog" is now part of the "XMLDialog" and the "Style" dialog is part of the "Selectors" dialog.
+// Also note that the "AttrDialog" does not correspond to SP_VERB_DIALOG_ATTR!!!!! (That would be the "ObjectAttributes" dialog.)
+
+std::vector<std::vector<Glib::ustring>> raw_data_dialogs =
+{
+ // clang-format off
+ {"win.dialog-open('AlignDistribute')", N_("Open Align and Distribute"), "Dialog", N_("Align and distribute objects") },
+ {"win.dialog-open('CloneTiler')", N_("Open Clone Tiler"), "Dialog", N_("Create multiple clones of selected object, arranging them into a pattern or scattering") },
+ {"win.dialog-open('DocumentProperties')", N_("Open Document Properties"), "Dialog", N_("Edit properties of this document (to be saved with the document)") },
+ {"win.dialog-open('Export')", N_("Open Export"), "Dialog", N_("Export this document or a selection as a PNG image") },
+ {"win.dialog-open('FillStroke')", N_("Open Fill and Stroke"), "Dialog", N_("Edit objects' colors, gradients, arrowheads, and other fill and stroke properties...") },
+ {"win.dialog-open('FilterEffects')", N_("Open Filter Effects"), "Dialog", N_("Manage, edit, and apply SVG filters") },
+ {"win.dialog-open('Find')", N_("Open Find"), "Dialog", N_("Find objects in document") },
+ {"win.dialog-open('Glyphs')", N_("Open Glyphs"), "Dialog", N_("Select Unicode characters from a palette") },
+ {"win.dialog-open('IconPreview')", N_("Open Icon Preview"), "Dialog", N_("Preview Icon") },
+ {"win.dialog-open('Input')", N_("Open Input"), "Dialog", N_("Configure extended input devices, such as a graphics tablet") },
+ {"win.dialog-open('LivePathEffect')", N_("Open Live Path Effect"), "Dialog", N_("Manage, edit, and apply path effects") },
+ {"win.dialog-open('Memory')", N_("Open Memory"), "Dialog", N_("View memory use") },
+ {"win.dialog-open('Messages')", N_("Open Messages"), "Dialog", N_("View debug messages") },
+ {"win.dialog-open('ObjectAttributes')", N_("Open Object Attributes"), "Dialog", N_("Edit the object attributes (context dependent)...") },
+ {"win.dialog-open('ObjectProperties')", N_("Open Object Properties"), "Dialog", N_("Edit the ID, locked and visible status, and other object properties") },
+ {"win.dialog-open('Objects')", N_("Open Objects"), "Dialog", N_("View Objects") },
+ {"win.dialog-open('PaintServers')", N_("Open Paint Servers"), "Dialog", N_("Select paint server from a collection") },
+ {"win.dialog-open('Preferences')", N_("Open Preferences"), "Dialog", N_("Edit global Inkscape preferences") },
+ {"win.dialog-open('Selectors')", N_("Open Selectors"), "Dialog", N_("View and edit CSS selectors and styles") },
+ {"win.dialog-open('SVGFonts')", N_("Open SVG Fonts"), "Dialog", N_("Edit SVG fonts") },
+ {"win.dialog-open('Swatches')", N_("Open Swatches"), "Dialog", N_("Select colors from a swatches palette") /* TRANSLATORS: "Swatches" -> color samples */ },
+ {"win.dialog-open('Symbols')", N_("Open Symbols"), "Dialog", N_("Select symbol from a symbols palette") },
+ {"win.dialog-open('Text')", N_("Open Text"), "Dialog", N_("View and select font family, font size and other text properties") },
+ {"win.dialog-open('Trace')", N_("Open Trace"), "Dialog", N_("Create one or more paths from a bitmap by tracing it") },
+ {"win.dialog-open('Transform')", N_("Open Transform"), "Dialog", N_("Precisely control objects' transformations") },
+ {"win.dialog-open('UndoHistory')", N_("Open Undo History"), "Dialog", N_("Undo History") },
+ {"win.dialog-open('XMLEditor')", N_("Open XML Editor"), "Dialog", N_("View and edit the XML tree of the document") },
+ {"app.preferences", N_("Open Preferences"), "Dialog", N_("Edit global Inkscape preferences") },
+#if WITH_GSPELL
+ {"win.dialog-open('Spellcheck')", N_("Open Spellcheck"), "Dialog", N_("Check spelling of text in document") },
+#endif
+#if DEBUG
+ {"win.dialog-open('Prototype')", N_("Open Prototype"), "Dialog", N_("Prototype Dialog") },
+#endif
+
+ {"win.dialog-toggle", N_("Toggle all dialogs"), "Dialog", N_("Show or hide all dialogs") },
+ // clang-format on
+};
+
+/**
+ * Open dialog.
+ */
+void
+dialog_open(const Glib::VariantBase& value, InkscapeWindow *win)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ auto dialog = s.get();
+
+ auto const &dialog_data = get_dialog_data();
+ auto dialog_it = dialog_data.find(dialog);
+ if (dialog_it == dialog_data.end()) {
+ std::cerr << "dialog_open: invalid dialog name: " << dialog << std::endl;
+ return;
+ }
+
+ SPDesktop* dt = win->get_desktop();
+ if (!dt) {
+ std::cerr << "dialog_toggle: no desktop!" << std::endl;
+ return;
+ }
+
+ Inkscape::UI::Dialog::DialogContainer *container = dt->getContainer();
+ container->new_dialog(dialog);
+}
+
+/**
+ * Toggle between showing and hiding dialogs.
+ */
+void
+dialog_toggle(InkscapeWindow *win)
+{
+ SPDesktop* dt = win->get_desktop();
+ if (!dt) {
+ std::cerr << "dialog_toggle: no desktop!" << std::endl;
+ return;
+ }
+
+ // Keep track of state?
+ // auto action = win->lookup_action("dialog-toggle");
+ // if (!action) {
+ // std::cerr << "dialog_toggle: action 'dialog-toggle' missing!" << std::endl;
+ // return;
+ // }
+
+ // auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ // if (!saction) {
+ // std::cerr << "dialog_toogle: action 'dialog_switch' not SimpleAction!" << std::endl;
+ // return;
+ // }
+
+ // saction->get_state();
+
+ Inkscape::UI::Dialog::DialogContainer *container = dt->getContainer();
+ container->toggle_dialogs();
+}
+
+void
+add_actions_dialogs(InkscapeWindow* win)
+{
+ Glib::VariantType String(Glib::VARIANT_TYPE_STRING);
+
+ // clang-format off
+ win->add_action_with_parameter( "dialog-open", String, sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&dialog_open), win));
+ win->add_action( "dialog-toggle", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&dialog_toggle), win));
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_dialog: no app!" << std::endl;
+ return;
+ }
+
+ // macOS automatically uses app.preferences in the application menu
+ auto gapp = app->gio_app();
+ gapp->add_action("preferences", sigc::track_obj([=] { dialog_open(Glib::Variant<Glib::ustring>::create("Preferences"), win); }, *win));
+
+ app->get_action_extra_data().add_data(raw_data_dialogs);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-dialogs.h b/src/actions/actions-dialogs.h
new file mode 100644
index 0000000..f820222
--- /dev/null
+++ b/src/actions/actions-dialogs.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for dialogs.
+ *
+ * Copyright (C) 2021 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_DIALOGS_H
+#define INK_ACTIONS_DIALOGS_H
+
+#include <glibmm.h>
+
+class InkscapeWindow;
+
+// Standard function to add actions.
+void add_actions_dialogs(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_DIALOGS_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-edit-document.cpp b/src/actions/actions-edit-document.cpp
new file mode 100644
index 0000000..74a69d8
--- /dev/null
+++ b/src/actions/actions-edit-document.cpp
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions Related to Editing which require document
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-edit-document.h"
+#include "actions/actions-extra-data.h"
+#include "inkscape-application.h"
+#include "document.h"
+#include "inkscape.h"
+#include "selection-chemistry.h"
+#include "object/sp-guide.h"
+#include "object/sp-namedview.h"
+
+void
+create_guides_around_page(SPDocument* document)
+{
+ // Create Guides Around the Page
+ sp_guide_create_guides_around_page(document);
+}
+
+void
+lock_all_guides(SPDocument *document)
+{
+ document->getNamedView()->toggleLockGuides();
+}
+
+void
+show_all_guides(SPDocument *document)
+{
+ document->getNamedView()->toggleShowGuides();
+}
+
+void
+delete_all_guides(SPDocument* document)
+{
+ // Delete All Guides
+ sp_guide_delete_all_guides(document);
+}
+
+void
+fit_canvas_drawing(SPDocument* document)
+{
+ // Fit Page to Drawing
+ if (fit_canvas_to_drawing(document)) {
+ Inkscape::DocumentUndo::done(document, _("Fit Page to Drawing"), "");
+ }
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_edit_document =
+{
+
+ // clang-format off
+ {"doc.create-guides-around-page", N_("Create Guides Around the Page"), "Edit Document", N_("Create four guides aligned with the page borders")},
+ {"doc.lock-all-guides", N_("Lock All Guides"), "Edit Document", N_("Toggle lock of all guides in the document")},
+ {"doc.show-all-guides", N_("Show All Guides"), "Edit Document", N_("Toggle visibility of all guides in the document")},
+ {"doc.delete-all-guides", N_("Delete All Guides"), "Edit Document", N_("Delete all the guides in the document")},
+ {"doc.fit-canvas-to-drawing", N_("Fit Page to Drawing"), "Edit Document", N_("Fit the page to the drawing")}
+ // clang-format on
+};
+
+void
+add_actions_edit_document(SPDocument* document)
+{
+
+ Glib::RefPtr<Gio::SimpleActionGroup> map = document->getActionGroup();
+
+ // clang-format off
+ map->add_action( "create-guides-around-page", sigc::bind<SPDocument*>(sigc::ptr_fun(&create_guides_around_page), document));
+ map->add_action( "delete-all-guides", sigc::bind<SPDocument*>(sigc::ptr_fun(&delete_all_guides), document));
+ map->add_action( "fit-canvas-to-drawing", sigc::bind<SPDocument*>(sigc::ptr_fun(&fit_canvas_drawing), document));
+ map->add_action_bool( "lock-all-guides", sigc::bind<SPDocument*>(sigc::ptr_fun(&lock_all_guides), document));
+ map->add_action_bool( "show-all-guides", sigc::bind<SPDocument*>(sigc::ptr_fun(&show_all_guides), document));
+ // clang-format on
+
+ // Check if there is already an application instance (GUI or non-GUI).
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_edit_document: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_edit_document);
+}
diff --git a/src/actions/actions-edit-document.h b/src/actions/actions-edit-document.h
new file mode 100644
index 0000000..e5d354c
--- /dev/null
+++ b/src/actions/actions-edit-document.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_EDIT_DOCUMENT_H
+#define INK_ACTIONS_EDIT_DOCUMENT_H
+
+class SPDocument;
+
+void add_actions_edit_document(SPDocument* document);
+
+#endif // INK_ACTIONS_EDIT_DOCUMENT_H \ No newline at end of file
diff --git a/src/actions/actions-edit-window.cpp b/src/actions/actions-edit-window.cpp
new file mode 100644
index 0000000..7c26736
--- /dev/null
+++ b/src/actions/actions-edit-window.cpp
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions for Editing an object which require desktop
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-edit-window.h"
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "desktop.h"
+#include "selection-chemistry.h"
+
+void
+paste(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Paste
+ sp_selection_paste(dt, false);
+}
+
+void
+paste_in_place(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Paste In Place
+ sp_selection_paste(dt, true);
+}
+
+void
+path_effect_parameter_next(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Next path effect parameter
+ sp_selection_next_patheffect_param(dt);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_edit_window =
+{
+ // clang-format off
+ {"win.paste", N_("Paste"), "Edit", N_("Paste objects from clipboard to mouse point, or paste text")},
+ {"win.paste-in-place", N_("Paste In Place"), "Edit", N_("Paste objects from clipboard to the original position of the copied objects")},
+ {"win.path-effect-parameter-next", N_("Next path effect parameter"), "Edit", N_("Show next editable path effect parameter")}
+ // clang-format on
+};
+
+void
+add_actions_edit_window(InkscapeWindow* win)
+{
+ // clang-format off
+ win->add_action( "paste", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&paste), win));
+ win->add_action( "paste-in-place", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&paste_in_place), win));
+ win->add_action( "path-effect-parameter-next", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&path_effect_parameter_next), win));
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_edit_window: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_edit_window);
+}
diff --git a/src/actions/actions-edit-window.h b/src/actions/actions-edit-window.h
new file mode 100644
index 0000000..8bdda98
--- /dev/null
+++ b/src/actions/actions-edit-window.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_EDIT_WINDOW_H
+#define INK_ACTIONS_EDIT_WINDOW_H
+
+class InkscapeWindow;
+
+void add_actions_edit_window(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_EDIT_WINDOW_H \ No newline at end of file
diff --git a/src/actions/actions-edit.cpp b/src/actions/actions-edit.cpp
new file mode 100644
index 0000000..84afabc
--- /dev/null
+++ b/src/actions/actions-edit.cpp
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions for Editing an object
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-edit.h"
+#include "actions-helper.h"
+#include "inkscape-application.h"
+#include "selection-chemistry.h"
+#include "object/sp-guide.h"
+
+#include "ui/tools/text-tool.h"
+#include "ui/tools/node-tool.h"
+
+void
+object_to_pattern(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Objects to Pattern
+ selection->tile();
+}
+
+void
+pattern_to_object(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Pattern to Objects
+ selection->untile();
+}
+
+void
+object_to_marker(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Objects to Marker
+ selection->toMarker();
+}
+
+void
+object_to_guides(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Objects to Guides
+ selection->toGuides();
+}
+
+void
+cut(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Cut
+ selection->cut();
+}
+
+void
+copy(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Copy
+ selection->copy();
+}
+
+void
+paste_style(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Paste Style
+ selection->pasteStyle();
+}
+
+void
+paste_size(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Paste Size
+ selection->pasteSize(true,true);
+}
+
+void
+paste_width(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Paste Width
+ selection->pasteSize(true, false);
+}
+
+void
+paste_height(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Paste Height
+ selection->pasteSize(false, true);
+}
+
+void
+paste_size_separately(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Paste Size Separately
+ selection->pasteSizeSeparately(true, true);
+}
+
+void
+paste_width_separately(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Paste Width Separately
+ selection->pasteSizeSeparately(true, false);
+}
+
+void
+paste_height_separately(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Paste Height Separately
+ selection->pasteSizeSeparately(false, true);
+}
+
+void
+duplicate(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Duplicate
+ selection->duplicate();
+}
+
+void
+clone(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Create Clone
+ selection->clone();
+}
+
+void
+clone_unlink(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Unlink Clone
+ selection->unlink();
+}
+
+void
+clone_unlink_recursively(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Unlink Clones recursively
+ selection->unlinkRecursive(false, true);
+}
+
+void
+clone_link(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Relink to Copied
+ selection->relink();
+}
+
+void
+select_original(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Select Original
+ selection->cloneOriginal();
+}
+
+void
+clone_link_lpe(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Clone original path (LPE)
+ selection->cloneOriginalPathLPE();
+}
+
+void
+edit_delete(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // For text and node too special handling.
+ if (auto desktop = selection->desktop()) {
+ if (auto text_tool = dynamic_cast<Inkscape::UI::Tools::TextTool*>(desktop->event_context)) {
+ text_tool->deleteSelected();
+ return;
+ }
+ if (auto node_tool = dynamic_cast<Inkscape::UI::Tools::NodeTool *>(desktop->event_context)) {
+ // This means we delete items is no nodes are selected.
+ if (node_tool->_selected_nodes) {
+ node_tool->deleteSelected();
+ return;
+ }
+ }
+ }
+
+ // Delete select objects only.
+ selection->deleteItems();
+}
+
+void
+edit_delete_selection(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->deleteItems();
+}
+
+void
+paste_path_effect(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Paste Path Effect
+ selection->pastePathEffect();
+}
+
+void
+remove_path_effect(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Remove Path Effect
+ selection->removeLPE();
+}
+
+void
+swap_fill_and_stroke(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Swap fill and Stroke
+ selection->swapFillStroke();
+}
+
+void
+fit_canvas_to_selection(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Fit Page to Selection
+ selection->fitCanvas(true);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_edit =
+{
+ // clang-format off
+ {"app.object-to-pattern", N_("Objects to Pattern"), "Edit", N_("Convert selection to a rectangle with tiled pattern fill")},
+ {"app.pattern-to-object", N_("Pattern to Objects"), "Edit", N_("Extract objects from a tiled pattern fill")},
+ {"app.object-to-marker", N_("Objects to Marker"), "Edit", N_("Convert selection to a line marker")},
+ {"app.object-to-guides", N_("Objects to Guides"), "Edit", N_("Convert selected objects to a collection of guidelines aligned with their edges")},
+ {"app.cut", N_("Cut"), "Edit", N_("Cut selection to clipboard")},
+ {"app.copy", N_("Copy"), "Edit", N_("Copy selection to clipboard")},
+ {"app.paste-style", N_("Paste Style"), "Edit", N_("Apply the style of the copied object to selection")},
+ {"app.paste-size", N_("Paste Size"), "Edit", N_("Scale selection to match the size of the copied object")},
+ {"app.paste-width", N_("Paste Width"), "Edit", N_("Scale selection horizontally to match the width of the copied object")},
+ {"app.paste-height", N_("Paste Height"), "Edit", N_("Scale selection vertically to match the height of the copied object")},
+ {"app.paste-size-separately", N_("Paste Size Separately"), "Edit", N_("Scale each selected object to match the size of the copied object")},
+ {"app.paste-width-separately", N_("Paste Width Separately"), "Edit", N_("Scale each selected object horizontally to match the width of the copied object")},
+ {"app.paste-height-separately", N_("Paste Height Separately"), "Edit", N_("Scale each selected object vertically to match the height of the copied object")},
+ {"app.duplicate", N_("Duplicate"), "Edit", N_("Duplicate Selected Objects")},
+ {"app.clone", N_("Create Clone"), "Edit", N_("Create a clone (a copy linked to the original) of selected object")},
+ {"app.clone-unlink", N_("Unlink Clone"), "Edit", N_("Cut the selected clones' links to the originals, turning them into standalone objects")},
+ {"app.clone-unlink-recursively", N_("Unlink Clones recursively"), "Edit", N_("Unlink all clones in the selection, even if they are in groups.")},
+ {"app.clone-link", N_("Relink to Copied"), "Edit", N_("Relink the selected clones to the object currently on the clipboard")},
+ {"app.select-original", N_("Select Original"), "Edit", N_("Select the object to which the selected clone is linked")},
+ {"app.clone-link-lpe", N_("Clone original path (LPE)"), "Edit", N_("Creates a new path, applies the Clone original LPE, and refers it to the selected path")},
+ {"app.delete", N_("Delete"), "Edit", N_("Delete selected items, nodes or text.")},
+ {"app.delete-selection", N_("Delete Items"), "Edit", N_("Delete selected items")},
+ {"app.paste-path-effect", N_("Paste Path Effect"), "Edit", N_("Apply the path effect of the copied object to selection")},
+ {"app.remove-path-effect", N_("Remove Path Effect"), "Edit", N_("Remove any path effects from selected objects")},
+ {"app.swap-fill-and-stroke", N_("Swap fill and stroke"), "Edit", N_("Swap fill and stroke of an object")},
+ {"app.fit-canvas-to-selection", N_("Fit Page to Selection"), "Edit", N_("Fit the page to the current selection")}
+ // clang-format on
+};
+
+void
+add_actions_edit(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "object-to-pattern", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_to_pattern), app));
+ gapp->add_action( "pattern-to-object", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&pattern_to_object), app));
+ gapp->add_action( "object-to-marker", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_to_marker), app));
+ gapp->add_action( "object-to-guides", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_to_guides), app));
+ gapp->add_action( "cut", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&cut), app));
+ gapp->add_action( "copy", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&copy), app));
+ gapp->add_action( "paste-style", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&paste_style), app));
+ gapp->add_action( "paste-size", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&paste_size), app));
+ gapp->add_action( "paste-width", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&paste_width), app));
+ gapp->add_action( "paste-height", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&paste_height), app));
+ gapp->add_action( "paste-size-separately", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&paste_size_separately), app));
+ gapp->add_action( "paste-width-separately", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&paste_width_separately), app));
+ gapp->add_action( "paste-height-separately", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&paste_height_separately), app));
+ gapp->add_action( "duplicate", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&duplicate), app));
+ gapp->add_action( "clone", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&clone), app));
+ gapp->add_action( "clone-unlink", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&clone_unlink), app));
+ gapp->add_action( "clone-unlink-recursively", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&clone_unlink_recursively), app));
+ gapp->add_action( "clone-link", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&clone_link), app));
+ gapp->add_action( "select-original", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_original), app));
+ gapp->add_action( "clone-link-lpe", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&clone_link_lpe), app));
+ gapp->add_action( "delete", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&edit_delete), app));
+ gapp->add_action( "delete-selection", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&edit_delete_selection), app));
+ gapp->add_action( "paste-path-effect", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&paste_path_effect), app));
+ gapp->add_action( "remove-path-effect", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&remove_path_effect), app));
+ gapp->add_action( "swap-fill-and-stroke", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&swap_fill_and_stroke), app));
+ gapp->add_action( "fit-canvas-to-selection", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&fit_canvas_to_selection), app));
+ // clang-format on
+
+ if (!app) {
+ std::cerr << "add_actions_edit: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_edit);
+}
diff --git a/src/actions/actions-edit.h b/src/actions/actions-edit.h
new file mode 100644
index 0000000..bec0650
--- /dev/null
+++ b/src/actions/actions-edit.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_EDIT_H
+#define INK_ACTIONS_EDIT_H
+
+class InkscapeApplication;
+
+void add_actions_edit(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_EDIT_H \ No newline at end of file
diff --git a/src/actions/actions-effect-data.cpp b/src/actions/actions-effect-data.cpp
new file mode 100644
index 0000000..337e1fb
--- /dev/null
+++ b/src/actions/actions-effect-data.cpp
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Effect Data to store data related to creating of
+ * Filters and Effect manubar
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "actions-effect-data.h"
+
+#include <iostream>
+#include <algorithm>
+#include <tuple>
+
+#include <glibmm/i18n.h>
+
+std::vector<InkActionEffectData::datum>
+InkActionEffectData::give_all_data()
+{
+ // Sort by menu tree and effect name.
+ std::sort(data.begin(), data.end(), [](datum a, datum b) {
+ auto a_list = std::get<1>(a);
+ auto b_list = std::get<1>(b);
+ auto a_it = a_list.begin();
+ auto b_it = b_list.begin();
+ while (a_it != a_list.end() && b_it != b_list.end()) {
+ if (*a_it < *b_it) return true;
+ if (*a_it > *b_it) return false;
+ a_it++;
+ b_it++;
+ }
+ if (a_it != a_list.end()) return *a_it < std::get<2>(b); // Compare menu name with effect name.
+ if (b_it != b_list.end()) return *b_it > std::get<2>(a); // Compare menu name with effect name.
+ return std::get<2>(a) < std::get<2>(b); // Same menu, order by effect name.
+ });
+
+ return data;
+}
+
+void
+InkActionEffectData::add_data ( std::string effect_id, std::list<Glib::ustring> effect_submenu_name,
+ Glib::ustring const &effect_name )
+{
+ data.emplace_back(effect_id, effect_submenu_name, effect_name);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-effect-data.h b/src/actions/actions-effect-data.h
new file mode 100644
index 0000000..e36f5d8
--- /dev/null
+++ b/src/actions/actions-effect-data.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_EFFECT_DATA_H
+#define INK_ACTIONS_EFFECT_DATA_H
+
+#include <list>
+#include <vector>
+#include <utility>
+
+#include <glibmm/ustring.h>
+
+class InkActionEffectData
+{
+public:
+ InkActionEffectData() = default;
+
+ typedef std::tuple<std::string, std::list<Glib::ustring>, Glib::ustring> datum;
+
+ // Get Vector
+ std::vector<datum> give_all_data();
+
+ // Add Data
+ void add_data(std::string effect_id, std::list<Glib::ustring> effect_submenu_vector, Glib::ustring const &effect_name) ;
+
+private:
+ std::vector<datum> data;
+};
+
+#endif // INK_ACTIONS_EFFECT_DATA_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-effect.cpp b/src/actions/actions-effect.cpp
new file mode 100644
index 0000000..2956967
--- /dev/null
+++ b/src/actions/actions-effect.cpp
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions for Filters and Extension menu items
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-effect.h"
+#include "actions-helper.h"
+#include "inkscape-application.h"
+
+#include "extension/effect.h"
+
+void
+edit_remove_filter(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Remove Filter
+ selection->removeFilter();
+}
+
+void
+last_effect(InkscapeApplication *app)
+{
+ Inkscape::Extension::Effect *effect = Inkscape::Extension::Effect::get_last_effect();
+
+ if (effect == nullptr) {
+ return;
+ }
+
+ // Last Effect
+ effect->effect(InkscapeApplication::instance()->get_active_view());
+}
+
+void
+last_effect_pref(InkscapeApplication *app)
+{
+ Inkscape::Extension::Effect *effect = Inkscape::Extension::Effect::get_last_effect();
+
+ if (effect == nullptr) {
+ return;
+ }
+
+ // Last Effect Pref
+ effect->prefs(InkscapeApplication::instance()->get_active_view());
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_effect =
+{
+ // clang-format off
+ {"app.edit-remove-filter", N_("Remove Filters"), "Filter", N_("Remove any filters from selected objects")},
+ {"app.last-effect", N_("Previous Extension"), "Extension", N_("Repeat the last extension with the same settings")},
+ {"app.last-effect-pref", N_("Previous Extension Settings"), "Extension", N_("Repeat the last extension with new settings")}
+ // clang-format on
+};
+
+void
+add_actions_effect(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "edit-remove-filter", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&edit_remove_filter), app));
+ gapp->add_action( "last-effect", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&last_effect), app));
+ gapp->add_action( "last-effect-pref", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&last_effect_pref), app));
+ // clang-format on
+
+ if (!app) {
+ std::cerr << "add_actions_edit: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_effect);
+}
diff --git a/src/actions/actions-effect.h b/src/actions/actions-effect.h
new file mode 100644
index 0000000..8ee1c14
--- /dev/null
+++ b/src/actions/actions-effect.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_EFFECT_H
+#define INK_ACTIONS_EFFECT_H
+
+class InkscapeApplication;
+
+void add_actions_effect(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_EFFECT_H \ No newline at end of file
diff --git a/src/actions/actions-element-a.cpp b/src/actions/actions-element-a.cpp
new file mode 100644
index 0000000..d35a053
--- /dev/null
+++ b/src/actions/actions-element-a.cpp
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for use with <a> (for anchor or hyper link).
+ *
+ * Copyright (C) 2022 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "actions-element-a.h"
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "preferences.h"
+
+#include "selection.h" // Selection
+#include "object/sp-anchor.h"
+#include "ui/dialog/dialog-container.h"
+
+// void anchor_add(InkscapeApplication *app)
+// {
+// auto selection = app->get_active_selection();
+// auto anchor = selection->group(1);
+// selection->set(anchor);
+
+// if (app->get_active_window()) {
+// app->get_active_window()->get_desktop()->getContainer()->new_dialog("ObjectAttributes");
+// }
+// }
+
+// XML not modified. Requires GUI.
+void anchor_open_link(InkscapeApplication* app)
+{
+ auto window = app->get_active_window();
+ if (window) {
+ auto selection = app->get_active_selection();
+ for (auto item : selection->items()) {
+ auto anchor = dynamic_cast<SPAnchor *>(item);
+ if (anchor) {
+ const char* href = anchor->href;
+ if (href) {
+ try {
+ window->show_uri(href, GDK_CURRENT_TIME);
+ } catch (const Glib::Error &e) {
+ std::cerr << "anchor_open_link: cannot open " << href << " " << e.what().raw() << std::endl;
+ }
+ }
+ }
+ }
+ }
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_element_a =
+{
+ // clang-format off
+ {"app.element-a-open-link", N_("Open link"), "Anchor", N_("Add an anchor to an object.") },
+ // clang-format on
+};
+
+void
+add_actions_element_a(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "element-a-open-link", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&anchor_open_link), app));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_element_a);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-element-a.h b/src/actions/actions-element-a.h
new file mode 100644
index 0000000..9285a11
--- /dev/null
+++ b/src/actions/actions-element-a.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for use with <a> (for anchor or hyper link).
+ *
+ * Copyright (C) 2022 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_ELEMENT_A_H
+#define INK_ACTIONS_ELEMENT_A_H
+
+class InkscapeApplication;
+
+void add_actions_element_a(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_ELEMENT_A_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-element-image.cpp b/src/actions/actions-element-image.cpp
new file mode 100644
index 0000000..9f05224
--- /dev/null
+++ b/src/actions/actions-element-image.cpp
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for use with <image>.
+ *
+ * Copyright (C) 2022 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "actions-element-image.h"
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <gtkmm.h> // OK, we lied. We pop-up an message dialog if external editor not found and if we have a GUI.
+#include <glibmm/i18n.h>
+
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "preferences.h"
+
+#include "selection.h" // Selection
+#include "object/sp-image.h"
+
+Glib::ustring image_get_editor_name(bool is_svg)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ Glib::ustring editor;
+ if (is_svg) {
+ editor = prefs->getString("/options/svgeditor/value", "inkscape");
+ } else {
+ editor = prefs->getString("/options/bitmapeditor/value", "gimp");
+ }
+ return editor;
+}
+
+// Note that edits are external to Inkscape and thus we cannot undo them!
+void image_edit(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ if (selection->isEmpty()) {
+ // Nothing to do.
+ return;
+ }
+
+ auto document = selection->document();
+
+ for (auto item : selection->items()) {
+ auto image = dynamic_cast<SPImage *>(item);
+ if (image) {
+
+ Inkscape::XML::Node *node = item->getRepr();
+ const gchar *href = node->attribute("xlink:href");
+ if (!href) {
+ std::cerr << "image_edit: no xlink:href" << std::endl;
+ continue;
+ }
+
+ if (strncmp (href, "data", 4) == 0) {
+ std::cerr << "image_edit: cannot edit embedded image" << std::endl;
+ continue;
+ }
+
+ // Find filename.
+ std::string filename = href;
+ if (strncmp (href, "file", 4) == 0) {
+ filename = Glib::filename_from_uri(href);
+ }
+
+ if (Glib::path_is_absolute(filename)) {
+ // Do nothing
+ } else if (document->getDocumentBase()) {
+ filename = Glib::build_filename(document->getDocumentBase(), filename);
+ } else {
+ filename = Glib::build_filename(Glib::get_current_dir(), filename);
+ }
+
+ // Bitmap or SVG?
+ bool is_svg = false;
+ if (filename.substr(filename.find_last_of(".") + 1) == "SVG" ||
+ filename.substr(filename.find_last_of(".") + 1) == "svg") {
+ is_svg = true;
+ }
+
+ // Get editor.
+ auto editor = image_get_editor_name(is_svg);
+
+#ifdef _WIN32
+ // Parsing is done according to Unix shell rules, need to enclose editor path by single
+ // quotes (everything before file extension).
+ int index = editor.find(".exe");
+ if (index < 0) index = editor.find(".bat");
+ if (index < 0) index = editor.find(".com");
+ if (index < 0) index = editor.length();
+
+ editor.insert(index, "'");
+ editor.insert(0, "'");
+#endif
+ Glib::ustring command = editor + " '" + filename + "'";
+
+ GError* error = nullptr;
+ g_spawn_command_line_async(command.c_str(), &error);
+ if (error) {
+ Glib::ustring message = _("Failed to edit external image.\n<small>Note: Path to editor can be set in Preferences dialog.</small>");
+ Glib::ustring message2 = Glib::ustring::compose(_("System error message: %1"), error->message);
+ auto window = app->get_active_window();
+ if (window) {
+ Gtk::MessageDialog dialog(*window, message, true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK);
+ dialog.property_destroy_with_parent() = true;
+ dialog.set_name("SetEditorDialog");
+ dialog.set_title(_("External Edit Image:"));
+ dialog.set_secondary_text(message2);
+ dialog.run();
+ } else {
+ std::cerr << "image_edit: " << message << std::endl;
+ }
+ g_error_free(error);
+ error = nullptr;
+ }
+ }
+ }
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_element_image =
+{
+ // clang-format off
+ {"app.element-image-edit", N_("Edit externally"), "Image", N_("Edit image externally (image must be selected and not embedded).") },
+ // clang-format on
+};
+
+void
+add_actions_element_image(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "element-image-edit", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&image_edit), app));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_element_image);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-element-image.h b/src/actions/actions-element-image.h
new file mode 100644
index 0000000..32a71a5
--- /dev/null
+++ b/src/actions/actions-element-image.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for use with <image>.
+ *
+ * Copyright (C) 2022 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_ELEMENT_IMAGE_H
+#define INK_ACTIONS_ELEMENT_IMAGE_H
+
+class InkscapeApplication;
+
+void add_actions_element_image(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_ELEMENT_IMAGE_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-extra-data.cpp b/src/actions/actions-extra-data.cpp
new file mode 100644
index 0000000..b84b3d4
--- /dev/null
+++ b/src/actions/actions-extra-data.cpp
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Extra data associated with actions: Label, Section, Tooltip.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * Extra data is indexed by "detailed action names", that is an action
+ * with prefix and value (if statefull). For example:
+ * "win.canvas-display-mode(1)"
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "actions-extra-data.h"
+
+#include <iostream>
+
+#include <glibmm/i18n.h>
+
+std::vector<Glib::ustring>
+InkActionExtraData::get_actions()
+{
+ std::vector<Glib::ustring> action_names;
+ for (auto datum : data) {
+ action_names.emplace_back(datum.first);
+ }
+ return action_names;
+}
+
+Glib::ustring
+InkActionExtraData::get_label_for_action(Glib::ustring const &action_name, bool translated)
+{
+ Glib::ustring value;
+ auto search = data.find(action_name);
+ if (search != data.end()) {
+ value = translated ? _(search->second.get_label().c_str())
+ : search->second.get_label();
+ }
+ return value;
+}
+
+// TODO: Section should be translatable, too
+Glib::ustring
+InkActionExtraData::get_section_for_action(Glib::ustring const &action_name) {
+
+ Glib::ustring value;
+ auto search = data.find(action_name);
+ if (search != data.end()) {
+ value = search->second.get_section();
+ }
+ return value;
+}
+
+Glib::ustring
+InkActionExtraData::get_tooltip_for_action(Glib::ustring const &action_name, bool translated) {
+
+ Glib::ustring value;
+ auto search = data.find(action_name);
+ if (search != data.end()) {
+ value = translated ? _(search->second.get_tooltip().c_str())
+ : search->second.get_tooltip();
+ }
+ return value;
+}
+
+void
+InkActionExtraData::add_data(std::vector<std::vector<Glib::ustring>> &raw_data) {
+ for (auto raw : raw_data) {
+ InkActionExtraDatum datum(raw[1], raw[2], raw[3]);
+ data.emplace(raw[0], datum);
+ }
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-extra-data.h b/src/actions/actions-extra-data.h
new file mode 100644
index 0000000..bbe1e8c
--- /dev/null
+++ b/src/actions/actions-extra-data.h
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Extra data associated with actions: Label, Section, Tooltip.
+ *
+ * Extra data is indexed by "detailed action names", that is an action
+ * with prefix and value (if statefull). For example:
+ * "win.canvas-display-mode(1)"
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_EXTRA_DATA_H
+#define INK_ACTIONS_EXTRA_DATA_H
+
+#include <glibmm/ustring.h>
+#include <glibmm/varianttype.h>
+#include <map>
+#include <utility>
+#include <vector>
+
+enum class ParamType
+{
+ INTEGER,
+ DOUBLE,
+ STRING,
+};
+
+class InkActionExtraDatum {
+public:
+ InkActionExtraDatum(Glib::ustring& label, Glib::ustring& section, Glib::ustring& tooltip)
+ : action_label(label)
+ , action_section(section)
+ , action_tooltip(tooltip)
+ {
+ }
+
+ Glib::ustring get_label() { return action_label; }
+ Glib::ustring get_section() { return action_section; }
+ Glib::ustring get_tooltip() { return action_tooltip; }
+
+private:
+ Glib::ustring action_label;
+ Glib::ustring action_section;
+ Glib::ustring action_tooltip;
+};
+
+class InkActionExtraData
+{
+public:
+ InkActionExtraData() = default;
+
+ std::vector<Glib::ustring> get_actions();
+
+ void add_data(std::vector<std::vector<Glib::ustring>> &raw_data);
+
+ Glib::ustring get_label_for_action(Glib::ustring const &action_name, bool translated = true);
+ Glib::ustring get_section_for_action(Glib::ustring const &action_name);
+ Glib::ustring get_tooltip_for_action(Glib::ustring const &action_name, bool translated = true);
+
+private:
+ std::map<Glib::ustring, InkActionExtraDatum> data;
+};
+
+#endif // INK_ACTIONS_EXTRA_DATA_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-file-window.cpp b/src/actions/actions-file-window.cpp
new file mode 100644
index 0000000..28fe640
--- /dev/null
+++ b/src/actions/actions-file-window.cpp
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions for opening, saving, etc. files which (mostly) open a dialog or an Inkscape window.
+ * Used by menu items under the "File" submenu.
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-file-window.h"
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "desktop.h"
+#include "file.h"
+#include "ui/dialog/save-template-dialog.h"
+#include "ui/dialog/new-from-template.h"
+
+void
+document_new(InkscapeWindow* win)
+{
+ sp_file_new_default();
+}
+
+void
+document_dialog_templates(InkscapeWindow* win)
+{
+ Inkscape::UI::NewFromTemplate::load_new_from_template();
+}
+
+void
+document_open(InkscapeWindow* win)
+{
+ // Open File Dialog
+ sp_file_open_dialog(*win, nullptr, nullptr);
+}
+
+void
+document_revert(InkscapeWindow* win)
+{
+ sp_file_revert_dialog();
+}
+
+void
+document_save(InkscapeWindow* win)
+{
+ // Save File
+ sp_file_save(*win, nullptr, nullptr);
+}
+
+void
+document_save_as(InkscapeWindow* win)
+{
+ // Save File As
+ sp_file_save_as(*win, nullptr, nullptr);
+}
+
+void
+document_save_copy(InkscapeWindow* win)
+{
+ // Save A copy
+ sp_file_save_a_copy(*win, nullptr, nullptr);
+}
+
+void
+document_save_template(InkscapeWindow* win)
+{
+ // Save As Template
+ Inkscape::UI::Dialog::SaveTemplate::save_document_as_template(*win);
+}
+
+void
+document_import(InkscapeWindow* win)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ prefs->setBool("/options/onimport", true);
+ sp_file_import(*win);
+ prefs->setBool("/options/onimport", false);
+}
+
+void
+document_print(InkscapeWindow* win)
+{
+ // Print File
+ sp_file_print(*win);
+}
+
+void
+document_cleanup(InkscapeWindow* win)
+{
+ // Cleanup Up Document
+ sp_file_vacuum(win->get_document());
+}
+
+// Close window, checking for data loss. If it's the last window, keep open with new document.
+void
+document_close(InkscapeWindow* win)
+{
+ // Close
+ auto app = InkscapeApplication::instance();
+ app->destroy_window(win, true); // true == keep alive last window
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_dialog_window =
+{
+ // clang-format off
+ {"win.document-new", N_("New"), "Window-File", N_("Create new document from the default template")},
+ {"win.document-dialog-templates", N_("New from Template"), "Window-File", N_("Create new project from template")},
+ {"win.document-open", N_("Open File Dialog"), "Window-File", N_("Open an existing document")},
+ {"win.document-revert", N_("Revert"), "Window-File", N_("Revert to the last saved version of document (changes will be lost)")},
+ {"win.document-save", N_("Save"), "Window-File", N_("Save document")},
+ {"win.document-save-as", N_("Save As"), "Window-File", N_("Save document under a new name")},
+ {"win.document-save-copy", N_("Save a Copy"), "Window-File", N_("Save a copy of the document under a new name")},
+ {"win.document-save-template", N_("Save Template"), "Window-File", N_("Save a copy of the document as template")},
+ {"win.document-import", N_("Import"), "Window-File", N_("Import a bitmap or SVG image into this document")},
+ {"win.document-print", N_("Print"), "Window-File", N_("Print document")},
+ {"win.document-cleanup", N_("Clean Up Document"), "Window-File", N_("Remove unused definitions (such as gradients or clipping paths) from the <defs> of the document")},
+ {"win.document-close", N_("Close"), "Window-File", N_("Close window (unless last window)")},
+ // clang-format on
+};
+
+void
+add_actions_file_window(InkscapeWindow* win)
+{
+ // clang-format off
+ win->add_action( "document-new", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_new), win));
+ win->add_action( "document-dialog-templates", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_dialog_templates), win));
+ win->add_action( "document-open", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_open), win));
+ win->add_action( "document-revert", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_revert), win));
+ win->add_action( "document-save", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_save), win));
+ win->add_action( "document-save-as", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_save_as), win));
+ win->add_action( "document-save-copy", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_save_copy), win));
+ win->add_action( "document-save-template", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_save_template), win));
+ win->add_action( "document-import", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_import), win));
+ win->add_action( "document-print", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_print), win));
+ win->add_action( "document-cleanup", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_cleanup), win));
+ win->add_action( "document-close", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&document_close), win));
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_file_window: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_dialog_window);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-file-window.h b/src/actions/actions-file-window.h
new file mode 100644
index 0000000..550ab6b
--- /dev/null
+++ b/src/actions/actions-file-window.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_FILE_WINDOW_H
+#define INK_ACTIONS_FILE_WINDOW_H
+
+class InkscapeWindow;
+
+void add_actions_file_window(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_FILE_WINDOW_H \ No newline at end of file
diff --git a/src/actions/actions-file.cpp b/src/actions/actions-file.cpp
new file mode 100644
index 0000000..b25ebba
--- /dev/null
+++ b/src/actions/actions-file.cpp
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for file handling tied to the application and without GUI.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-file.h"
+#include "actions-helper.h"
+#include "inkscape-application.h"
+
+#include "inkscape.h" // Inkscape::Application
+
+// Actions for file handling (should be integrated with file dialog).
+
+void
+file_open(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+
+ Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(s.get());
+ if (!file->query_exists()) {
+ std::cerr << "file_open: file '" << s.get() << "' does not exist." << std::endl;
+ return;
+ }
+ SPDocument *document = app->document_open(file);
+ INKSCAPE.add_document(document);
+
+ app->set_active_document(document);
+ app->set_active_selection(document->getSelection());
+ app->set_active_view(nullptr);
+
+ document->ensureUpToDate();
+}
+
+void
+file_open_with_window(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(s.get());
+ if (!file->query_exists()) {
+ std::cerr << "file_open: file '" << s.get() << "' does not exist." << std::endl;
+ return;
+ }
+ app->create_window(file);
+}
+
+
+void
+file_new(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+
+ SPDocument *document = app->document_new(s.get());
+ INKSCAPE.add_document(document);
+
+ app->set_active_document(document);
+ app->set_active_selection(document->getSelection());
+ app->set_active_view(nullptr); // No desktop (yet).
+
+ document->ensureUpToDate();
+}
+
+// Need to create a document_revert that doesn't depend on windows.
+// void
+// file_revert(InkscapeApplication *app)
+// {
+// app->document_revert(app->get_current_document());
+// }
+
+// No checks for dataloss are performed. Useful for scripts.
+void
+file_close(InkscapeApplication *app)
+{
+ SPDocument *document = app->get_active_document();
+ app->document_close(document);
+
+ app->set_active_document(nullptr);
+ app->set_active_selection(nullptr);
+ app->set_active_view(nullptr);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_file =
+{
+ // clang-format off
+ {"app.file-open", N_("File Open"), "File", N_("Open file") },
+ {"app.file-new", N_("File New"), "File", N_("Open new document using template") },
+ {"app.file-close", N_("File Close"), "File", N_("Close active document") },
+ {"app.file-open-window", N_("File Open Window"), "File", N_("Open file window") }
+ // clang-format on
+};
+
+std::vector<std::vector<Glib::ustring>> hint_data_file =
+{
+ // clang-format off
+ {"app.file-open", N_("Enter file name")},
+ {"app.file-new", N_("Enter file name")},
+ {"app.file-open-window", N_("Enter file name")}
+ // clang-format on
+};
+
+void
+add_actions_file(InkscapeApplication* app)
+{
+ Glib::VariantType Bool( Glib::VARIANT_TYPE_BOOL);
+ Glib::VariantType Int( Glib::VARIANT_TYPE_INT32);
+ Glib::VariantType Double(Glib::VARIANT_TYPE_DOUBLE);
+ Glib::VariantType String(Glib::VARIANT_TYPE_STRING);
+ Glib::VariantType BString(Glib::VARIANT_TYPE_BYTESTRING);
+
+ // Debian 9 has 2.50.0
+#if GLIB_CHECK_VERSION(2, 52, 0)
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action_with_parameter( "file-open", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&file_open), app));
+ gapp->add_action_with_parameter( "file-new", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&file_new), app));
+ gapp->add_action_with_parameter( "file-open-window", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&file_open_with_window), app));
+ gapp->add_action( "file-close", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&file_close), app));
+ // clang-format on
+#else
+ std::cerr << "add_actions: Some actions require Glibmm 2.52, compiled with: " << glib_major_version << "." << glib_minor_version << std::endl;
+#endif
+
+ app->get_action_extra_data().add_data(raw_data_file);
+ app->get_action_hint_data().add_data(hint_data_file);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-file.h b/src/actions/actions-file.h
new file mode 100644
index 0000000..ea0b92f
--- /dev/null
+++ b/src/actions/actions-file.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for file handling tied to the application and without GUI.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_FILE_H
+#define INK_ACTIONS_FILE_H
+
+class InkscapeApplication;
+
+void add_actions_file(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_FILE_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-help-url.cpp b/src/actions/actions-help-url.cpp
new file mode 100644
index 0000000..732c7cc
--- /dev/null
+++ b/src/actions/actions-help-url.cpp
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions for Help Url
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-help-url.h"
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "desktop.h"
+#include "inkscape-version.h"
+
+/** Open an URL in the the default application
+ *
+ * See documentation of gtk_show_uri_on_window() for details
+ *
+ * @param url URL to be opened
+ * @param window Parent window for which the URL is opened
+ */
+// TODO: Do we really need a window reference here? It's the way recommended by gtk, though.
+void help_open_url(const Glib::ustring &url, Gtk::Window *window)
+{
+ try {
+ window->show_uri(url, GDK_CURRENT_TIME);
+ } catch (const Glib::Error &e) {
+ g_warning("Unable to show '%s': %s", url.c_str(), e.what().c_str());
+ }
+}
+
+void
+help_url_ask_question(InkscapeWindow* win, const char *lang)
+{
+ Glib::ustring url = Glib::ustring::compose("https://inkscape.org/%1/community/", lang);
+ help_open_url(url, win);
+}
+
+void
+help_url_man(InkscapeWindow* win, const char *lang, const Glib::ustring branch)
+{
+ Glib::ustring url = Glib::ustring::compose("https://inkscape.org/%1/doc/inkscape-man-%2.html", lang, branch);
+ help_open_url(url, win);
+}
+
+void
+help_url_faq(InkscapeWindow* win, const char *lang)
+{
+ Glib::ustring url = Glib::ustring::compose("https://inkscape.org/%1/learn/faq/", lang);
+ help_open_url(url, win);
+}
+
+void
+help_url_keys(InkscapeWindow* win, const char *lang, const Glib::ustring branch)
+{
+ Glib::ustring url = Glib::ustring::compose("https://inkscape.org/%1/doc/keys-%2.html", lang, branch);
+ help_open_url(url, win);
+}
+
+void
+help_url_release_notes(InkscapeWindow* win, const char *lang, const char *version, const bool development_version)
+{
+ Glib::ustring url = Glib::ustring::compose("https://inkscape.org/%1/release/inkscape-%2", lang, development_version ? "master" : version);
+ help_open_url(url, win);
+}
+
+void
+help_url_report_bug(InkscapeWindow* win, const char *lang)
+{
+ Glib::ustring url = Glib::ustring::compose("https://inkscape.org/%1/contribute/report-bugs/", lang);
+ help_open_url(url, win);
+}
+
+void
+help_url_manual(InkscapeWindow* win)
+{
+ Glib::ustring url = "http://tavmjong.free.fr/INKSCAPE/MANUAL/html/index.php";
+ help_open_url(url, win);
+}
+
+void
+help_url_donate(InkscapeWindow* win, const char *lang, const char *version)
+{
+ Glib::ustring url = Glib::ustring::compose("https://inkscape.org/%1/donate#lang=%1&version=%2", lang, version);
+ help_open_url(url, win);
+}
+
+void
+help_url_svg11_spec(InkscapeWindow* win)
+{
+ Glib::ustring url = "http://www.w3.org/TR/SVG11/";
+ help_open_url(url, win);
+}
+
+void
+help_url_svg2_spec(InkscapeWindow* win)
+{
+ Glib::ustring url = "http://www.w3.org/TR/SVG2/";
+ help_open_url(url, win);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_help_url =
+{
+ // clang-format off
+ { "win.help-url-ask-question", N_("Ask Us a Question"), "Help Url", N_("Ask Us a Question") },
+ { "win.help-url-man", N_("Command Line Options"), "Help Url", N_("Command Line Options")},
+ { "win.help-url-faq", N_("FAQ"), "Help Url", N_("FAQ")},
+ { "win.help-url-keys", N_("Keys and Mouse Reference"), "Help Url", N_("Keys and Mouse Reference")},
+ { "win.help-url-release-notes", N_("New in This Version"), "Help Url", N_("New in This Version")},
+ { "win.help-url-report-bug", N_("Report a Bug"), "Help Url", N_("Report a Bug")},
+ { "win.help-url-manual", N_("Inkscape Manual"), "Help Url", N_("Inkscape Manual")},
+ { "win.help-url-donate", N_("Donate"), "Help Url", N_("Donate to Inkscape")},
+ { "win.help-url-svg11-spec", N_("SVG 1.1 Specification"), "Help Url", N_("SVG 1.1 Specification")},
+ { "win.help-url-svg2-spec", N_("SVG 2 Specification"), "Help Url", N_("SVG 2 Specification")}
+ // clang-format on
+};
+
+void
+add_actions_help_url(InkscapeWindow* win)
+{
+ const char *lang = _("en"); // TODO: strip /en/ for English version?
+ const char *version = Inkscape::version_string_without_revision;
+ const bool development_version = g_str_has_suffix(version, "-dev"); // this detection is not perfect but should be close enough
+ const Glib::ustring branch = development_version ? "master" : Glib::ustring::compose("%1.%2.x", Inkscape::version_major, Inkscape::version_minor);
+
+ // clang-format off
+ win->add_action( "help-url-ask-question", sigc::bind<InkscapeWindow*, const char*>(sigc::ptr_fun(&help_url_ask_question), win, lang));
+ win->add_action( "help-url-man", sigc::bind<InkscapeWindow*, const char*, const Glib::ustring>(sigc::ptr_fun(&help_url_man), win, lang,branch));
+ win->add_action( "help-url-faq", sigc::bind<InkscapeWindow*, const char*>(sigc::ptr_fun(&help_url_faq), win, lang));
+ win->add_action( "help-url-keys", sigc::bind<InkscapeWindow*, const char*, const Glib::ustring>(sigc::ptr_fun(&help_url_keys), win, lang, branch));
+ win->add_action( "help-url-release-notes", sigc::bind<InkscapeWindow*, const char*, const char*, const bool>(sigc::ptr_fun(&help_url_release_notes), win, lang, version, development_version));
+ win->add_action( "help-url-report-bug", sigc::bind<InkscapeWindow*, const char*>(sigc::ptr_fun(&help_url_report_bug), win, lang));
+ win->add_action( "help-url-manual", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&help_url_manual), win));
+ win->add_action( "help-url-donate", sigc::bind<InkscapeWindow*, const char*, const char*>(sigc::ptr_fun(&help_url_donate), win, lang, version));
+ win->add_action( "help-url-svg11-spec", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&help_url_svg11_spec), win));
+ win->add_action( "help-url-svg2-spec", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&help_url_svg2_spec), win));
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_help_url: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_help_url);
+}
diff --git a/src/actions/actions-help-url.h b/src/actions/actions-help-url.h
new file mode 100644
index 0000000..eef77e5
--- /dev/null
+++ b/src/actions/actions-help-url.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_HELP_URL_H
+#define INK_ACTIONS_HELP_URL_H
+
+class InkscapeWindow;
+
+void add_actions_help_url(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_HELP_URL_H \ No newline at end of file
diff --git a/src/actions/actions-helper.cpp b/src/actions/actions-helper.cpp
new file mode 100644
index 0000000..7dab91a
--- /dev/null
+++ b/src/actions/actions-helper.cpp
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for selection tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ * TODO: REMOVE THIS FILE It's really not necessary.
+ */
+
+#include "inkscape-application.h"
+#include "inkscape.h"
+#include "selection.h"
+
+// Helper function: returns true if both document and selection found. Maybe this should
+// work on current view. Or better, application could return the selection of the current view.
+bool
+get_document_and_selection(InkscapeApplication* app, SPDocument** document, Inkscape::Selection** selection)
+{
+ *document = app->get_active_document();
+ if (!(*document)) {
+ std::cerr << "get_document_and_selection: No document!" << std::endl;
+ return false;
+ }
+
+ *selection = app->get_active_selection();
+ if (!*selection) {
+ std::cerr << "get_document_and_selection: No selection!" << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-helper.h b/src/actions/actions-helper.h
new file mode 100644
index 0000000..da65e10
--- /dev/null
+++ b/src/actions/actions-helper.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for selection tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_HELPER_H
+#define INK_ACTIONS_HELPER_H
+
+class InkscapeApplication;
+class SPDocument;
+namespace Inkscape {
+ class Selection;
+}
+
+bool get_document_and_selection(InkscapeApplication* app, SPDocument** document, Inkscape::Selection** selection);
+
+
+#endif // INK_ACTIONS_HELPER_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-hide-lock.cpp b/src/actions/actions-hide-lock.cpp
new file mode 100644
index 0000000..01ccef9
--- /dev/null
+++ b/src/actions/actions-hide-lock.cpp
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions related to hide and lock
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ * Tavmjong Bah
+ *
+ * Copyright (C) 2021-2022 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "actions-hide-lock.h"
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "inkscape-application.h"
+#include "document-undo.h"
+
+#include "object/sp-root.h"
+
+// Helper to unlock/unhide everything. (Could also be used to lock/hide everything but that isn't very useful.)
+static bool
+hide_lock_recurse(bool (*f)(SPItem*, bool), SPItem *item, bool hide_or_lock)
+{
+ bool changed = false;
+
+ if (f(item, hide_or_lock)) {
+ changed = true;
+ }
+
+ for (auto& child : item->children) {
+ auto item = dynamic_cast<SPItem*>(&child);
+ if (item && hide_lock_recurse(f, item, hide_or_lock)) {
+ changed = true;
+ }
+ }
+
+ return changed;
+}
+
+// Helper to hide/unhide one item.
+bool
+hide_lock_hide(SPItem* item, bool hide)
+{
+ bool changed = false;
+ if (item->isHidden() != hide) {
+ item->setHidden(hide);
+ changed = true;
+ }
+ return changed;
+}
+
+// Helper to lock/unlock one item.
+bool
+hide_lock_lock(SPItem* item, bool lock)
+{
+ bool changed = false;
+ if (item->isLocked() != lock) {
+ item->setLocked(lock);
+ changed = true;
+ }
+ return changed;
+}
+
+// Unhide all
+void
+hide_lock_unhide_all(InkscapeApplication* app)
+{
+ auto document = app->get_active_document();
+ auto root = document->getRoot();
+
+ bool changed = hide_lock_recurse(&hide_lock_hide, root, false); // Unhide
+
+ if (changed) {
+ Inkscape::DocumentUndo::done(document, _("Unhid all objects in the current layer"), "");
+ }
+}
+
+// Unlock all
+void
+hide_lock_unlock_all(InkscapeApplication* app)
+{
+ auto document = app->get_active_document();
+ auto root = document->getRoot();
+
+ bool changed = hide_lock_recurse(&hide_lock_lock, root, false); // Unlock
+
+ if (changed) {
+ Inkscape::DocumentUndo::done(document, _("Unlocked all objects in the current layer"), "");
+ }
+}
+
+// Unhide selected items and their descendents.
+void
+hide_lock_unhide_below(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ if (!selection) {
+ std::cerr << "hide_lock_unhide_below: no selection!" << std::endl;
+ return;
+ }
+
+ bool changed = false;
+ for (auto item : selection->items()) {
+ if (hide_lock_recurse(&hide_lock_hide, item, false)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ auto document = app->get_active_document();
+ Inkscape::DocumentUndo::done(document, _("Unhid selected items and their descendents."), "");
+ }
+}
+
+// Unlock selected items and their descendents.
+void
+hide_lock_unlock_below(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ if (!selection) {
+ std::cerr << "hide_lock_unhide_below: no selection!" << std::endl;
+ return;
+ }
+
+ bool changed = false;
+ for (auto item : selection->items()) {
+ if (hide_lock_recurse(&hide_lock_lock, item, false)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ auto document = app->get_active_document();
+ Inkscape::DocumentUndo::done(document, _("Unlocked selected items and their descendents."), "");
+ }
+}
+
+// Hide/unhide selected items.
+void
+hide_lock_hide_selected(InkscapeApplication* app, bool hide)
+{
+ auto selection = app->get_active_selection();
+ if (!selection) {
+ std::cerr << "hide_lock_hide_selected: no selection!" << std::endl;
+ return;
+ }
+
+ bool changed = false;
+ for (auto item : selection->items()) {
+ if (hide_lock_hide(item, hide)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ auto document = app->get_active_document();
+ Inkscape::DocumentUndo::done(document, (hide ? _("Hid selected items.") : _("Unhid selected items.")), "");
+ selection->clear();
+ }
+}
+
+// Lock/Unlock selected items.
+void
+hide_lock_lock_selected(InkscapeApplication* app, bool lock)
+{
+ auto selection = app->get_active_selection();
+ if (!selection) {
+ std::cerr << "hide_lock_lock_selected: no selection!" << std::endl;
+ return;
+ }
+
+ bool changed = false;
+ for (auto item : selection->items()) {
+ if (hide_lock_lock(item, lock)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ auto document = app->get_active_document();
+ Inkscape::DocumentUndo::done(document, (lock ? _("Locked selected items.") : _("Unlocked selected items.")), "");
+ selection->clear();
+ }
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_hide_lock =
+{
+ // clang-format off
+ {"app.unhide-all", N_("Unhide All"), "Hide and Lock", N_("Unhide all objects") },
+ {"app.unlock-all", N_("Unlock All"), "Hide and Lock", N_("Unlock all objects") },
+
+ {"app.selection-hide", N_("Hide selection"), "Hide and Lock", N_("Hide all selected objects") },
+ {"app.selection-unhide", N_("Unhide selection"), "Hide and Lock", N_("Unhide all selected objects") },
+ {"app.selection-unhide-below", N_("Unhide descendents"), "Hide and Lock", N_("Unhide all items inside selected objects") },
+
+ {"app.selection-lock", N_("Lock selection"), "Hide and Lock", N_("Lock all selected objects") },
+ {"app.selection-unlock", N_("Unlock selection"), "Hide and Lock", N_("Unlock all selected objects") },
+ {"app.selection-unlock-below", N_("Unlock descendents"), "Hide and Lock", N_("Unlock all items inside selected objects") },
+ // clang-format on
+};
+
+void
+add_actions_hide_lock(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "unhide-all", sigc::bind<InkscapeApplication*>( sigc::ptr_fun(&hide_lock_unhide_all), app));
+ gapp->add_action( "unlock-all", sigc::bind<InkscapeApplication*>( sigc::ptr_fun(&hide_lock_unlock_all), app));
+
+ gapp->add_action( "selection-hide", sigc::bind<InkscapeApplication*, bool>(sigc::ptr_fun(&hide_lock_hide_selected), app, true ));
+ gapp->add_action( "selection-unhide", sigc::bind<InkscapeApplication*, bool>(sigc::ptr_fun(&hide_lock_hide_selected), app, false));
+ gapp->add_action( "selection-unhide-below", sigc::bind<InkscapeApplication*>( sigc::ptr_fun(&hide_lock_unhide_below), app));
+
+ gapp->add_action( "selection-lock", sigc::bind<InkscapeApplication*, bool>(sigc::ptr_fun(&hide_lock_lock_selected), app, true ));
+ gapp->add_action( "selection-unlock", sigc::bind<InkscapeApplication*, bool>(sigc::ptr_fun(&hide_lock_lock_selected), app, false));
+ gapp->add_action( "selection-unlock-below", sigc::bind<InkscapeApplication*>( sigc::ptr_fun(&hide_lock_unlock_below), app));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_hide_lock);
+}
+
+/*
+ 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/actions/actions-hide-lock.h b/src/actions/actions-hide-lock.h
new file mode 100644
index 0000000..457de14
--- /dev/null
+++ b/src/actions/actions-hide-lock.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_HIDE_LOCK_H
+#define INK_ACTIONS_HIDE_LOCK_H
+
+class InkscapeApplication;
+
+void add_actions_hide_lock(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_HIDE_LOCK_H
diff --git a/src/actions/actions-hint-data.cpp b/src/actions/actions-hint-data.cpp
new file mode 100644
index 0000000..23cf368
--- /dev/null
+++ b/src/actions/actions-hint-data.cpp
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Command Palette input placeholder hint data
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "actions-hint-data.h"
+#include <glibmm/i18n.h>
+
+std::vector<Glib::ustring>
+InkActionHintData::get_actions()
+{
+ // To get all the Placeholder hints
+ std::vector<Glib::ustring> action_names;
+ for (auto hint : data) {
+ action_names.emplace_back(hint.first);
+ }
+ return action_names;
+}
+
+Glib::ustring
+InkActionHintData::get_tooltip_hint_for_action(Glib::ustring const &action_name, bool translated)
+{
+ // Hint for a particular Action
+ Glib::ustring value;
+ auto search = data.find(action_name);
+ if (search != data.end()) {
+ value = translated ? _(search->second.c_str()) : search->second;
+ }
+ return value;
+}
+
+void
+InkActionHintData::add_data(std::vector<std::vector<Glib::ustring>> &hint_data)
+{
+ for (auto hint : hint_data) {
+ // Action Name , Hint
+ data.emplace(hint[0], hint[1]);
+ }
+} \ No newline at end of file
diff --git a/src/actions/actions-hint-data.h b/src/actions/actions-hint-data.h
new file mode 100644
index 0000000..8961d39
--- /dev/null
+++ b/src/actions/actions-hint-data.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_HINT_DATA_H
+#define INK_ACTIONS_HINT_DATA_H
+
+#include <glibmm/ustring.h>
+#include <glibmm/varianttype.h>
+#include <map>
+#include <utility>
+#include <vector>
+
+class InkActionHintData
+{
+public:
+ InkActionHintData() = default ;
+
+ std::vector<Glib::ustring> get_actions();
+
+ void add_data(std::vector<std::vector<Glib::ustring>> &raw_data);
+
+ Glib::ustring get_tooltip_hint_for_action(Glib::ustring const &action_name, bool translated = true);
+
+private:
+ std::map<Glib::ustring, Glib::ustring> data;
+};
+
+#endif // INK_ACTIONS_HINT_DATA_H \ No newline at end of file
diff --git a/src/actions/actions-layer.cpp b/src/actions/actions-layer.cpp
new file mode 100644
index 0000000..0d0484d
--- /dev/null
+++ b/src/actions/actions-layer.cpp
@@ -0,0 +1,539 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions for Layers.
+ *
+ * These all require a window. To do: remove this requirement.
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-layer.h"
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "desktop.h"
+#include "ui/dialog/layer-properties.h"
+#include "message-stack.h"
+#include "ui/icon-names.h"
+#include "document-undo.h"
+
+/*
+ * A layer is a group <g> element with a special Inkscape attribute (Inkscape:groupMode) set to
+ * "layer". It is typically directly placed in the <svg> element but it is also possible to put
+ * inside any other layer (a "layer" inside a normal group is considered a group). The GUI tracks
+ * which is the "Current" layer. The "Current" layer is set when a new selection initiated
+ * (i.e. when not adding objects to a previous selection), when it is chosen in the "Layers and
+ * Objects" dialog, when using the previous/next layer menu items, and when moving objects to
+ * adjacent layers.
+ */
+
+void
+layer_new(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // New Layer
+ Inkscape::UI::Dialogs::LayerPropertiesDialog::showCreate(dt, dt->layerManager().currentLayer());
+}
+
+void
+layer_duplicate (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ if (!dt->layerManager().isRoot()) {
+
+ dt->selection->duplicate(true, true); // This requires the selection to be a layer!
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Duplicate layer"), INKSCAPE_ICON("layer-duplicate"));
+ dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Duplicated layer."));
+
+ } else {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ }
+}
+
+void
+layer_delete (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ auto root = dt->layerManager().currentRoot();
+
+ if (!dt->layerManager().isRoot()) {
+
+ dt->getSelection()->clear();
+ SPObject *old_layer = dt->layerManager().currentLayer();
+ SPObject *old_parent = old_layer->parent;
+ SPObject *old_parent_parent = (old_parent != nullptr) ? old_parent->parent : nullptr;
+
+ SPObject *survivor = Inkscape::previous_layer(root, old_layer);
+ if (survivor != nullptr && survivor->parent == old_layer) {
+ while (survivor != nullptr &&
+ survivor->parent != old_parent &&
+ survivor->parent != old_parent_parent)
+ {
+ survivor = Inkscape::previous_layer(root, survivor);
+ }
+ }
+
+ if (survivor == nullptr || (survivor->parent != old_parent && survivor->parent != old_layer)) {
+ survivor = Inkscape::next_layer(root, old_layer);
+ while (survivor != nullptr &&
+ survivor != old_parent &&
+ survivor->parent != old_parent)
+ {
+ survivor = Inkscape::next_layer(root, survivor);
+ }
+ }
+
+ // Deleting the old layer before switching layers is a hack to trigger the
+ // listeners of the deletion event (as happens when old_layer is deleted using the
+ // xml editor). See
+ // http://sourceforge.net/tracker/index.php?func=detail&aid=1339397&group_id=93438&atid=604306
+ //
+ old_layer->deleteObject();
+
+ if (survivor) {
+ dt->layerManager().setCurrentLayer(survivor);
+ }
+
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Delete layer"), INKSCAPE_ICON("layer-delete"));
+ dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Deleted layer."));
+
+ } else {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ }
+}
+
+void
+layer_rename (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Rename Layer
+ Inkscape::UI::Dialogs::LayerPropertiesDialog::showRename(dt, dt->layerManager().currentLayer());
+}
+
+void
+layer_hide_all (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ dt->layerManager().toggleHideAllLayers(true);
+ Inkscape::DocumentUndo::maybeDone(dt->getDocument(), "layer:hideall", _("Hide all layers"), "");
+}
+
+void
+layer_unhide_all (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ dt->layerManager().toggleHideAllLayers(false);
+ Inkscape::DocumentUndo::maybeDone(dt->getDocument(), "layer:showall", _("Show all layers"), "");
+}
+
+void
+layer_hide_toggle (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ auto layer = dt->layerManager().currentLayer();
+
+ if (!layer || dt->layerManager().isRoot()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ } else {
+ layer->setHidden(!layer->isHidden());
+ }
+}
+
+void
+layer_hide_toggle_others (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ auto layer = dt->layerManager().currentLayer();
+
+ if (!layer || dt->layerManager().isRoot()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ } else {
+ dt->layerManager().toggleLayerSolo( layer ); // Weird name!
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Hide other layers"), "");
+ }
+}
+
+void
+layer_lock_all (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ dt->layerManager().toggleLockAllLayers(true);
+ Inkscape::DocumentUndo::maybeDone(dt->getDocument(), "layer:lockall", _("Lock all layers"), "");
+}
+
+void
+layer_unlock_all (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ dt->layerManager().toggleLockAllLayers(false);
+ Inkscape::DocumentUndo::maybeDone(dt->getDocument(), "layer:unlockall", _("Unlock all layers"), "");
+}
+
+void
+layer_lock_toggle (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ auto layer = dt->layerManager().currentLayer();
+
+ if (!layer || dt->layerManager().isRoot()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ } else {
+ layer->setLocked(!layer->isLocked());
+ }
+}
+
+void
+layer_lock_toggle_others (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ auto layer = dt->layerManager().currentLayer();
+
+ if (!layer || dt->layerManager().isRoot()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ } else {
+ dt->layerManager().toggleLockOtherLayers( layer );
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Lock other layers"), "");
+ }
+}
+
+void
+layer_previous (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ SPObject *next = Inkscape::next_layer(dt->layerManager().currentRoot(), dt->layerManager().currentLayer());
+ if (next) {
+ dt->layerManager().setCurrentLayer(next);
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Switch to next layer"), INKSCAPE_ICON("layer-previous"));
+ dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Switched to next layer."));
+ } else {
+ dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Cannot go past last layer."));
+ }
+}
+
+void
+layer_next (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ SPObject *prev=Inkscape::previous_layer(dt->layerManager().currentRoot(), dt->layerManager().currentLayer());
+ if (prev) {
+ dt->layerManager().setCurrentLayer(prev);
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Switch to previous layer") ,INKSCAPE_ICON("layer-next"));
+ dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Switched to previous layer."));
+ } else {
+ dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Cannot go before first layer."));
+ }
+}
+
+void
+selection_move_to_layer_above (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Layer Rise
+ dt->selection->toNextLayer();
+}
+
+void
+selection_move_to_layer_below (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Layer Lower
+ dt->selection->toPrevLayer();
+}
+
+void
+selection_move_to_layer (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Selection move to layer
+ Inkscape::UI::Dialogs::LayerPropertiesDialog::showMove(dt, dt->layerManager().currentLayer());
+}
+
+void
+layer_top (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ if (dt->layerManager().isRoot()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ return;
+ }
+
+ SPItem *layer = dt->layerManager().currentLayer();
+ g_return_if_fail(layer != nullptr);
+ SPObject *old_pos = layer->getNext();
+ layer->raiseToTop();
+
+ if (layer->getNext() != old_pos) {
+
+ char const * message = g_strdup_printf(_("Raised layer <b>%s</b>."), layer->defaultLabel());
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Layer to top"), INKSCAPE_ICON("layer-top"));
+ dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
+ g_free((void *) message);
+
+ } else {
+ dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Cannot move layer any further."));
+ }
+}
+
+void
+layer_raise (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ if (dt->layerManager().isRoot()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ return;
+ }
+
+ SPItem *layer = dt->layerManager().currentLayer();
+ g_return_if_fail(layer != nullptr);
+
+ SPObject *old_pos = layer->getNext();
+
+
+ layer->raiseOne();
+
+
+ if (layer->getNext() != old_pos) {
+
+ char const * message = g_strdup_printf(_("Raised layer <b>%s</b>."), layer->defaultLabel());
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Raise layer"), INKSCAPE_ICON("layer-raise"));
+ dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
+ g_free((void *) message);
+
+ } else {
+ dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Cannot move layer any further."));
+ }
+}
+
+void
+layer_lower (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ if (dt->layerManager().isRoot()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ return;
+ }
+
+ SPItem *layer = dt->layerManager().currentLayer();
+ g_return_if_fail(layer != nullptr);
+ SPObject *old_pos = layer->getNext();
+ layer->lowerOne();
+
+ if (layer->getNext() != old_pos) {
+
+ char const * message = g_strdup_printf(_("Lowered layer <b>%s</b>."), layer->defaultLabel());
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Lower layer"), INKSCAPE_ICON("layer-lower"));
+ dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
+ g_free((void *) message);
+
+ } else {
+ dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Cannot move layer any further."));
+ }
+}
+
+void
+layer_bottom (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ if (dt->layerManager().isRoot()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ return;
+ }
+
+ SPItem *layer = dt->layerManager().currentLayer();
+ g_return_if_fail(layer != nullptr);
+ SPObject *old_pos = layer->getNext();
+ layer->lowerToBottom();
+
+ if (layer->getNext() != old_pos) {
+
+ char const * message = g_strdup_printf(_("Lowered layer <b>%s</b>."), layer->defaultLabel());
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Layer to bottom"), INKSCAPE_ICON("layer-bottom"));
+ dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
+ g_free((void *) message);
+
+ } else {
+ dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Cannot move layer any further."));
+ }
+}
+
+void
+layer_to_group (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ auto layer = dt->layerManager().currentLayer();
+
+ if (!layer || dt->layerManager().isRoot()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No current layer."));
+ return;
+ }
+
+ layer->setLayerMode(SPGroup::GROUP);
+ layer->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Layer to group"), INKSCAPE_ICON("dialog-objects"));
+}
+
+void
+layer_from_group (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ auto selection = dt->getSelection();
+
+ std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
+ if (items.size() != 1) {
+ std::cerr << "layer_to_group: only one selected item allowed!" << std::endl;
+ return;
+ }
+
+ auto group = dynamic_cast<SPGroup*>(items[0]);
+ if (group && group->isLayer()) {
+ dt->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Group already layer."));
+ return;
+ }
+
+ group->setLayerMode(SPGroup::LAYER);
+ group->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
+ Inkscape::DocumentUndo::done(dt->getDocument(), _("Group to layer"), INKSCAPE_ICON("dialog-objects"));
+}
+
+// Does not change XML.
+void
+group_enter (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ auto selection = dt->selection;
+
+ std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
+ if (items.size() == 1 && dynamic_cast<SPGroup*>(items[0])) {
+ // Only one item and it is a group!
+ dt->layerManager().setCurrentLayer(items[0]);
+ selection->clear();
+ }
+}
+
+// Does not change XML.
+void
+group_exit (InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+ auto selection = dt->selection;
+
+ auto parent = dt->layerManager().currentLayer()->parent;
+ dt->layerManager().setCurrentLayer(parent);
+
+ std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
+ if (items.size() == 1 && dynamic_cast<SPGroup*>(items[0]->parent) ) {
+ // Only one item selected and the parent is a group!
+ selection->set(items[0]->parent);
+ } else {
+ selection->clear();
+ }
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_layer =
+{
+ // clang-format off
+ {"win.layer-new", N_("Add Layer"), "Layers", N_("Create a new layer")},
+ {"win.layer-duplicate", N_("Duplicate Current Layer"), "Layers", N_("Duplicate the current layer")},
+ {"win.layer-delete", N_("Delete Current Layer"), "Layers", N_("Delete the current layer")},
+ {"win.layer-rename", N_("Rename Layer"), "Layers", N_("Rename the current layer")},
+
+ {"win.layer-toggle-hide", N_("Show/Hide Current Layer"), "Layers", N_("Toggle visibility of current layer")},
+ {"win.layer-toggle-lock", N_("Lock/Unlock Current Layer"), "Layers", N_("Toggle lock on current layer")},
+
+ {"win.layer-previous", N_("Switch to Layer Above"), "Layers", N_("Switch to the layer above the current")},
+ {"win.layer-next", N_("Switch to Layer Below"), "Layers", N_("Switch to the layer below the current")},
+
+ {"win.selection-move-to-layer-above", N_("Move Selection to Layer Above"), "Layers", N_("Move selection to the layer above the current")},
+ {"win.selection-move-to-layer-below", N_("Move Selection to Layer Below"), "Layers", N_("Move selection to the layer below the current")},
+ {"win.selection-move-to-layer", N_("Move Selection to Layer..."), "Layers", N_("Move selection to layer")},
+
+ {"win.layer-top", N_("Layer to Top"), "Layers", N_("Raise the current layer to the top")},
+ {"win.layer-raise", N_("Raise Layer"), "Layers", N_("Raise the current layer")},
+ {"win.layer-lower", N_("Lower Layer"), "Layers", N_("Lower the current layer")},
+ {"win.layer-bottom", N_("Layer to Bottom"), "Layers", N_("Lower the current layer to the bottom")},
+
+ {"win.layer-to-group", N_("Layer to Group"), "Layers", N_("Convert the current layer to a group")},
+ {"win.layer-from-group", N_("Layer from Group"), "Layers", N_("Convert the group to a layer")},
+
+ // These use Layer technology even if they don't act on layers.
+ {"win.selection-group-enter", N_("Enter Group"), "Select", N_("Enter group")},
+ {"win.selection-group-exit", N_("Exit Group"), "Select", N_("Exit group")},
+ // clang-format on
+};
+
+void
+add_actions_layer(InkscapeWindow* win)
+{
+ // clang-format off
+ win->add_action("layer-new", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_new), win));
+ win->add_action("layer-duplicate", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_duplicate), win));
+ win->add_action("layer-delete", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_delete), win));
+ win->add_action("layer-rename", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_rename), win));
+
+ win->add_action("layer-hide-all", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_hide_all), win));
+ win->add_action("layer-unhide-all", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_unhide_all), win));
+ win->add_action("layer-hide-toggle", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_hide_toggle), win));
+ win->add_action("layer-hide-toggle-others", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_hide_toggle_others), win));
+
+ win->add_action("layer-lock-all", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_lock_all), win));
+ win->add_action("layer-unlock-all", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_unlock_all), win));
+ win->add_action("layer-lock-toggle", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_lock_toggle), win));
+ win->add_action("layer-lock-toggle-others", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_lock_toggle_others), win));
+
+ win->add_action("layer-previous", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_previous), win));
+ win->add_action("layer-next", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_next), win));
+
+ win->add_action("selection-move-to-layer-above", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&selection_move_to_layer_above), win));
+ win->add_action("selection-move-to-layer-below", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&selection_move_to_layer_below), win));
+ win->add_action("selection-move-to-layer", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&selection_move_to_layer), win));
+
+ win->add_action("layer-top", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_top), win));
+ win->add_action("layer-raise", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_raise), win));
+ win->add_action("layer-lower", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_lower), win));
+ win->add_action("layer-bottom", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_bottom), win));
+
+ win->add_action("layer-to-group", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_to_group), win));
+ win->add_action("layer-from-group", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&layer_from_group), win));
+
+ win->add_action("selection-group-enter", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&group_enter), win));
+ win->add_action("selection-group-exit", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&group_exit), win));
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_layer: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_layer);
+}
+
+/*
+ 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/actions/actions-layer.h b/src/actions/actions-layer.h
new file mode 100644
index 0000000..c6771e1
--- /dev/null
+++ b/src/actions/actions-layer.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_LAYER_H
+#define INK_ACTIONS_LAYER_H
+
+class InkscapeWindow;
+
+void add_actions_layer(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_LAYER_H \ No newline at end of file
diff --git a/src/actions/actions-node-align.cpp b/src/actions/actions-node-align.cpp
new file mode 100644
index 0000000..89e446a
--- /dev/null
+++ b/src/actions/actions-node-align.cpp
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for aligning and distributing objects without GUI.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * Some code and ideas from src/ui/dialogs/align-and-distribute.cpp
+ * Authors: Bryce Harrington
+ * Martin Owens
+ * John Smith
+ * Patrick Storz
+ * Jabier Arraiza
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ * To do: Remove GUI dependency!
+ */
+
+#include "actions-node-align.h"
+
+#include <iostream>
+#include <limits>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include <2geom/coord.h>
+
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "ui/tool/node-types.h"
+#include "ui/tool/multi-path-manipulator.h" // Node align/distribute
+#include "ui/tools/node-tool.h" // Node align/distribute
+
+using Inkscape::UI::AlignTargetNode;
+
+void
+node_align(const Glib::VariantBase& value, InkscapeWindow* win, Geom::Dim2 direction)
+{
+ auto tool = win->get_desktop()->getEventContext();
+ auto node_tool = dynamic_cast<Inkscape::UI::Tools::NodeTool*>(tool);
+ if (node_tool) {
+ } else {
+ std::cerr << "node_align: tool is not Node tool!" << std::endl;
+ return;
+ }
+
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(" ", s.get());
+ if (tokens.size() > 1) {
+ std::cerr << "node_align: too many arguments!" << std::endl;
+ return;
+ }
+
+ // clang-format off
+ auto target = AlignTargetNode::MID_NODE;
+ if (tokens.size() == 1) {
+ std::string token = tokens[0];
+ if (token == "pref") {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ token = prefs->getString("/dialogs/align/nodes-align-to", "first");
+ }
+ if (token == "last" ) target = AlignTargetNode::LAST_NODE;
+ else if (token == "first" ) target = AlignTargetNode::FIRST_NODE;
+ else if (token == "middle" ) target = AlignTargetNode::MID_NODE;
+ else if (token == "min" ) target = AlignTargetNode::MIN_NODE;
+ else if (token == "max" ) target = AlignTargetNode::MAX_NODE;
+ }
+ // clang-format on
+ node_tool->_multipath->alignNodes(direction, target);
+}
+
+void
+node_distribute(InkscapeWindow* win, Geom::Dim2 direction)
+{
+ auto tool = win->get_desktop()->getEventContext();
+ auto node_tool = dynamic_cast<Inkscape::UI::Tools::NodeTool*>(tool);
+ if (node_tool) {
+ } else {
+ std::cerr << "node_distribute: tool is not Node tool!" << std::endl;
+ return;
+ }
+
+ node_tool->_multipath->distributeNodes(direction);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_node_align =
+{
+ // clang-format off
+ {"win.node-align-horizontal", N_("Align nodes horizontally"), "Node", N_("Align selected nodes horizontally; usage [last|first|middle|min|max|pref]" )},
+ {"win.node-align-vertical", N_("Align nodes vertically"), "Node", N_("Align selected nodes vertically; usage [last|first|middle|min|max|pref]" )},
+ {"win.node-distribute-horizontal", N_("Distribute nodes horizontally"), "Node", N_("Distribute selected nodes horizontally." )},
+ {"win.node-distribute-vertical", N_("Distribute nodes vertically"), "Node", N_("Distribute selected nodes vertically." )}
+ // clang-format on
+};
+
+std::vector<std::vector<Glib::ustring>> hint_data_node_align =
+{
+ // clang-format off
+ {"app.node-align-horizontal", N_("Enter string for alignment anchor, one of: first/last/middle/min/max")},
+ {"app.node-align-vertical", N_("Enter string for alignment anchor, one of: first/last/middle/min/max")},
+ // clang-format on
+};
+
+// These are window actions as the require the node tool to be active and nodes to be selected.
+void
+add_actions_node_align(InkscapeWindow* win)
+{
+ Glib::VariantType String(Glib::VARIANT_TYPE_STRING);
+
+ // clang-format off
+ win->add_action_with_parameter( "node-align-horizontal", String, sigc::bind<InkscapeWindow*, Geom::Dim2>(sigc::ptr_fun(&node_align), win, Geom::X));
+ win->add_action_with_parameter( "node-align-vertical", String, sigc::bind<InkscapeWindow*, Geom::Dim2>(sigc::ptr_fun(&node_align), win, Geom::Y));
+ win->add_action( "node-distribute-horizontal", sigc::bind<InkscapeWindow*, Geom::Dim2>(sigc::ptr_fun(&node_distribute), win, Geom::X));
+ win->add_action( "node-distribute-vertical", sigc::bind<InkscapeWindow*, Geom::Dim2>(sigc::ptr_fun(&node_distribute), win, Geom::Y));
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_node_align: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_node_align);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-node-align.h b/src/actions/actions-node-align.h
new file mode 100644
index 0000000..ebb87c3
--- /dev/null
+++ b/src/actions/actions-node-align.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for aligning and distributing nodes. Requires Node tool.
+ *
+ * Copyright (C) 2021 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_NODE_ALIGN_H
+#define INK_ACTIONS_NODE_ALIGN_H
+
+class InkscapeWindow;
+
+void add_actions_node_align(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_NODe_ALIGN_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-object-align.cpp b/src/actions/actions-object-align.cpp
new file mode 100644
index 0000000..bedaaeb
--- /dev/null
+++ b/src/actions/actions-object-align.cpp
@@ -0,0 +1,852 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for aligning and distributing objects without GUI.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * Some code and ideas from src/ui/dialogs/align-and-distribute.cpp
+ * Authors: Bryce Harrington
+ * Martin Owens
+ * John Smith
+ * Patrick Storz
+ * Jabier Arraiza
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "actions-object-align.h"
+
+#include <iostream>
+#include <limits>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "document-undo.h"
+#include "enums.h" // Clones
+#include "filter-chemistry.h" // LPE bool
+#include "inkscape-application.h"
+#include "inkscape.h" // Inkscape::Application - preferences
+#include "text-editing.h"
+
+#include "object/sp-text.h"
+#include "object/sp-flowtext.h"
+
+#include "object/algorithms/graphlayout.h" // Graph layout objects.
+#include "object/algorithms/removeoverlap.h" // Remove overlaps between objects.
+#include "object/algorithms/unclump.h" // Rearrange objects.
+#include "object/algorithms/bboxsort.h" // Sort based on bounding box.
+
+#include "live_effects/effect-enum.h"
+#include "live_effects/effect.h"
+
+#include "object/sp-root.h" // "Desktop Bounds"
+
+#include "ui/icon-names.h" // Icon macro used in undo.
+
+enum class ObjectAlignTarget {
+ LAST,
+ FIRST,
+ BIGGEST,
+ SMALLEST,
+ PAGE,
+ DRAWING,
+ SELECTION
+};
+
+void
+object_align_on_canvas(InkscapeApplication *app)
+{
+ // Get Action
+ auto *gapp = app->gio_app();
+ auto action = gapp->lookup_action("object-align-on-canvas");
+ if (!action) {
+ std::cerr << "object_align_on_canvas: action missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "object_align_on_canvas: action not SimpleAction!" << std::endl;
+ return;
+ }
+
+ // Toggle state
+ bool state = false;
+ saction->get_state(state);
+ state = !state;
+ saction->change_state(state);
+
+ // Toggle action
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/dialogs/align/oncanvas", state);
+}
+
+void
+object_align(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(" ", s.get());
+
+ // Find out if we are using an anchor.
+ bool anchor = std::find(tokens.begin(), tokens.end(), "anchor") != tokens.end();
+
+ // Default values:
+ auto target = ObjectAlignTarget::SELECTION;
+
+ bool group = false;
+ double mx0 = 0;
+ double mx1 = 0;
+ double my0 = 0;
+ double my1 = 0;
+ double sx0 = 0;
+ double sx1 = 0;
+ double sy0 = 0;
+ double sy1 = 0;
+
+ // Preference request allows alignment action to remember for key-presses
+ if (std::find(tokens.begin(), tokens.end(), "pref") != tokens.end()) {
+ group = prefs->getBool("/dialogs/align/sel-as-groups", false);
+ tokens.push_back(prefs->getString("/dialogs/align/objects-align-to", "selection"));
+ }
+
+ // clang-format off
+ for (auto const &token : tokens) {
+
+ // Target
+ if (token == "last" ) target = ObjectAlignTarget::LAST;
+ else if (token == "first" ) target = ObjectAlignTarget::FIRST;
+ else if (token == "biggest" ) target = ObjectAlignTarget::BIGGEST;
+ else if (token == "smallest" ) target = ObjectAlignTarget::SMALLEST;
+ else if (token == "page" ) target = ObjectAlignTarget::PAGE;
+ else if (token == "drawing" ) target = ObjectAlignTarget::DRAWING;
+ else if (token == "selection") target = ObjectAlignTarget::SELECTION;
+
+ // Group
+ else if (token == "group") group = true;
+
+ // Position
+ if (!anchor) {
+ if (token == "left" ) { mx0 = 1.0; mx1 = 0.0; sx0 = 1.0; sx1 = 0.0; }
+ else if (token == "hcenter" ) { mx0 = 0.5; mx1 = 0.5; sx0 = 0.5; sx1 = 0.5; }
+ else if (token == "right" ) { mx0 = 0.0; mx1 = 1.0; sx0 = 0.0; sx1 = 1.0; }
+
+ else if (token == "top" ) { my0 = 1.0; my1 = 0.0; sy0 = 1.0; sy1 = 0.0; }
+ else if (token == "vcenter" ) { my0 = 0.5; my1 = 0.5; sy0 = 0.5; sy1 = 0.5; }
+ else if (token == "bottom" ) { my0 = 0.0; my1 = 1.0; sy0 = 0.0; sy1 = 1.0; }
+ } else {
+ if (token == "left" ) { mx0 = 0.0; mx1 = 1.0; sx0 = 1.0; sx1 = 0.0; }
+ else if (token == "hcenter" ) std::cerr << "'anchor' cannot be used with 'hcenter'" << std::endl;
+ else if (token == "right" ) { mx0 = 1.0; mx1 = 0.0; sx0 = 0.0; sx1 = 1.0; }
+
+ else if (token == "top" ) { my0 = 0.0; my1 = 1.0; sy0 = 1.0; sy1 = 0.0; }
+ else if (token == "vcenter" ) std::cerr << "'anchor' cannot be used with 'vcenter'" << std::endl;
+ else if (token == "bottom" ) { my0 = 1.0; my1 = 0.0; sy0 = 0.0; sy1 = 1.0; }
+ }
+ }
+ // clang-format on
+
+ auto selection = app->get_active_selection();
+
+ // We should not have to do this!
+ auto document = app->get_active_document();
+ selection->setDocument(document);
+
+ // We force unselect operand in bool LPE. TODO: See if we can use "selected" from below.
+ auto list = selection->items();
+ std::size_t total = std::distance(list.begin(), list.end());
+ std::vector<SPItem *> selected;
+ std::vector<Inkscape::LivePathEffect::Effect *> bools;
+ for (auto itemlist = list.begin(); itemlist != list.end(); ++itemlist) {
+ SPItem *item = dynamic_cast<SPItem *>(*itemlist);
+ if (total == 2) {
+ SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(item);
+ if (lpeitem) {
+ for (auto lpe : lpeitem->getPathEffectsOfType(Inkscape::LivePathEffect::EffectType::BOOL_OP)) {
+ if (!g_strcmp0(lpe->getRepr()->attribute("is_visible"), "true")) {
+ lpe->getRepr()->setAttribute("is_visible", "false");
+ bools.emplace_back(lpe);
+ item->document->ensureUpToDate();
+ }
+ }
+ }
+ }
+ if (!(item && has_hidder_filter(item) && total > 2)) {
+ selected.emplace_back(item);
+ }
+ }
+
+ if (selected.empty()) return;
+
+ // Find alignment rectangle. This can come from:
+ // - The bounding box of an object
+ // - The bounding box of a group of objects
+ // - The bounding box of the page, drawing, or selection.
+ SPItem *focus = nullptr;
+ Geom::OptRect b = Geom::OptRect();
+ Inkscape::Selection::CompareSize direction = (mx0 != 0.0 || mx1 != 0.0) ? Inkscape::Selection::VERTICAL : Inkscape::Selection::HORIZONTAL;
+
+ switch (target) {
+ case ObjectAlignTarget::LAST:
+ focus = selected.back();
+ break;
+ case ObjectAlignTarget::FIRST:
+ focus = selected.front();
+ break;
+ case ObjectAlignTarget::BIGGEST:
+ focus = selection->largestItem(direction);
+ break;
+ case ObjectAlignTarget::SMALLEST:
+ focus = selection->smallestItem(direction);
+ break;
+ case ObjectAlignTarget::PAGE:
+ b = document->pageBounds();
+ break;
+ case ObjectAlignTarget::DRAWING:
+ b = document->getRoot()->desktopPreferredBounds();
+ break;
+ case ObjectAlignTarget::SELECTION:
+ b = selection->preferredBounds();
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ };
+
+ if (focus) {
+ b = focus->desktopPreferredBounds();
+ }
+
+ g_return_if_fail(b);
+
+ if (auto desktop = selection->desktop(); desktop && !desktop->is_yaxisdown()) {
+ std::swap(my0, my1);
+ std::swap(sy0, sy1);
+ }
+
+ // Generate the move point from the selected bounding box
+ Geom::Point mp = Geom::Point(mx0 * b->min()[Geom::X] + mx1 * b->max()[Geom::X],
+ my0 * b->min()[Geom::Y] + my1 * b->max()[Geom::Y]);
+
+ if (group) {
+ if (focus) {
+ // Use bounding box of all selected elements except the "focused" element.
+ Inkscape::ObjectSet copy(document);
+ copy.add(selection->objects().begin(), selection->objects().end());
+ copy.remove(focus);
+ b = copy.preferredBounds();
+ } else {
+ // Use bounding box of all selected elements.
+ b = selection->preferredBounds();
+ }
+ }
+
+ // Move each item in the selected list separately.
+ bool changed = false;
+ for (auto item : selected) {
+ document->ensureUpToDate();
+
+ if (!group) {
+ b = (item)->desktopPreferredBounds();
+ }
+
+ if (b && (!focus || (item) != focus)) {
+ Geom::Point const sp(sx0 * b->min()[Geom::X] + sx1 * b->max()[Geom::X],
+ sy0 * b->min()[Geom::Y] + sy1 * b->max()[Geom::Y]);
+ Geom::Point const mp_rel( mp - sp );
+ if (LInfty(mp_rel) > 1e-9) {
+ item->move_rel(Geom::Translate(mp_rel));
+ changed = true;
+ }
+ }
+ }
+
+ if (changed) {
+ Inkscape::DocumentUndo::done(document, _("Align"), INKSCAPE_ICON("dialog-align-and-distribute"));
+ }
+}
+
+void
+object_distribute(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ auto token = s.get();
+
+ auto selection = app->get_active_selection();
+
+ // We should not have to do this!
+ auto document = app->get_active_document();
+ selection->setDocument(document);
+
+ std::vector<SPItem*> selected(selection->items().begin(), selection->items().end());
+ if (selected.size() < 2) {
+ return;
+ }
+
+ // clang-format off
+ double a = 0.0;
+ double b = 0.0;
+ bool gap = false;
+ auto orientation = Geom::X;
+ if (token == "hgap" ) { gap = true; orientation = Geom::X; a = 0.5, b = 0.5; }
+ else if (token == "left" ) { gap = false; orientation = Geom::X; a = 1.0, b = 0.0; }
+ else if (token == "hcenter" ) { gap = false; orientation = Geom::X; a = 0.5, b = 0.5; }
+ else if (token == "right" ) { gap = false; orientation = Geom::X; a = 0.0, b = 1.0; }
+ else if (token == "vgap" ) { gap = true; orientation = Geom::Y; a = 0.5, b = 0.5; }
+ else if (token == "top" ) { gap = false; orientation = Geom::Y; a = 1.0, b = 0.0; }
+ else if (token == "vcenter" ) { gap = false; orientation = Geom::Y; a = 0.5, b = 0.5; }
+ else if (token == "bottom" ) { gap = false; orientation = Geom::Y; a = 0.0, b = 1.0; }
+ // clang-format on
+
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int prefs_bbox = prefs->getBool("/tools/bounding_box");
+
+ // Make a list of objects, sorted by anchors.
+ std::vector<BBoxSort> sorted;
+ for (auto item : selected) {
+ Geom::OptRect bbox = !prefs_bbox ? (item)->desktopVisualBounds() : (item)->desktopGeometricBounds();
+ if (bbox) {
+ sorted.emplace_back(item, *bbox, orientation, a, b);
+ }
+ }
+ std::stable_sort(sorted.begin(), sorted.end());
+
+ // See comment in ActionAlign above (MISSING).
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ bool changed = false;
+ if (gap) {
+ // Evenly spaced.
+
+ // Overall bboxes span.
+ double dist = (sorted.back().bbox.max()[orientation] - sorted.front().bbox.min()[orientation]);
+
+ // Space eaten by bboxes.
+ double span = 0.0;
+ for (auto bbox : sorted) {
+ span += bbox.bbox[orientation].extent();
+ }
+
+ // New distance between each bbox.
+ double step = (dist - span) / (sorted.size() - 1);
+ double pos = sorted.front().bbox.min()[orientation];
+ for (auto bbox : sorted) {
+
+ // Don't move if we are really close.
+ if (!Geom::are_near(pos, bbox.bbox.min()[orientation], 1e-6)) {
+
+ // Compute translation.
+ Geom::Point t(0.0, 0.0);
+ t[orientation] = pos - bbox.bbox.min()[orientation];
+
+ // Translate
+ bbox.item->move_rel(Geom::Translate(t));
+ changed = true;
+ }
+
+ pos += bbox.bbox[orientation].extent();
+ pos += step;
+ }
+
+ } else {
+
+ // Overall anchor span.
+ double dist = sorted.back().anchor - sorted.front().anchor;
+
+ // Distance between anchors.
+ double step = dist / (sorted.size() - 1);
+
+ for (unsigned int i = 0; i < sorted.size() ; i++) {
+ BBoxSort & it(sorted[i]);
+
+ // New anchor position.
+ double pos = sorted.front().anchor + i * step;
+
+ // Don't move if we are really close.
+ if (!Geom::are_near(pos, it.anchor, 1e-6)) {
+
+ // Compute translation.
+ Geom::Point t(0.0, 0.0);
+ t[orientation] = pos - it.anchor;
+
+ // Translate
+ it.item->move_rel(Geom::Translate(t));
+ changed = true;
+ }
+ }
+ }
+
+ // Restore compensation setting.
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ if (changed) {
+ Inkscape::DocumentUndo::done( document, _("Distribute"), INKSCAPE_ICON("dialog-align-and-distribute"));
+ }
+}
+
+class Baseline
+{
+public:
+ Baseline(SPItem *item, Geom::Point base, Geom::Dim2 orientation)
+ : _item (item)
+ , _base (base)
+ , _orientation (orientation)
+ {}
+ SPItem *_item = nullptr;
+ Geom::Point _base;
+ Geom::Dim2 _orientation;
+};
+
+static bool operator< (const Baseline &a, const Baseline &b)
+{
+ return (a._base[a._orientation] < b._base[b._orientation]);
+}
+
+void
+object_distribute_text(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ auto token = s.get();
+
+ Geom::Dim2 orientation = Geom::Dim2::X;
+ if (token.find("vertical") != Glib::ustring::npos) {
+ orientation = Geom::Dim2::Y;
+ }
+
+ auto selection = app->get_active_selection();
+ if (selection->size() < 2) {
+ return;
+ }
+
+ // We should not have to do this!
+ auto document = app->get_active_document();
+ selection->setDocument(document);
+
+ std::vector<Baseline> baselines;
+ Geom::Point b_min = Geom::Point ( HUGE_VAL, HUGE_VAL);
+ Geom::Point b_max = Geom::Point (-HUGE_VAL, -HUGE_VAL);
+
+ for (auto item : selection->items()) {
+ if (dynamic_cast<SPText *>(item) || dynamic_cast<SPFlowtext *>(item)) {
+ Inkscape::Text::Layout const *layout = te_get_layout(item);
+ std::optional<Geom::Point> pt = layout->baselineAnchorPoint();
+ if (pt) {
+ Geom::Point base = *pt * item->i2dt_affine();
+ if (base[Geom::X] < b_min[Geom::X]) b_min[Geom::X] = base[Geom::X];
+ if (base[Geom::Y] < b_min[Geom::Y]) b_min[Geom::Y] = base[Geom::Y];
+ if (base[Geom::X] > b_max[Geom::X]) b_max[Geom::X] = base[Geom::X];
+ if (base[Geom::Y] > b_max[Geom::Y]) b_max[Geom::Y] = base[Geom::Y];
+ baselines.emplace_back(Baseline(item, base, orientation));
+ }
+ }
+ }
+
+ if (baselines.size() < 2) {
+ return;
+ }
+
+ std::stable_sort(baselines.begin(), baselines.end());
+
+ double step = (b_max[orientation] - b_min[orientation])/(baselines.size() - 1);
+ int i = 0;
+ for (auto& baseline : baselines) {
+ Geom::Point t(0.0, 0.0);
+ t[orientation] = b_min[orientation] + (step * i) - baseline._base[orientation];
+ baseline._item->move_rel(Geom::Translate(t));
+ ++i;
+ }
+
+ Inkscape::DocumentUndo::done( document, _("Distribute"), INKSCAPE_ICON("dialog-align-and-distribute"));
+}
+
+void
+object_align_text(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(" ", s.get());
+
+ // Defaults
+ auto target = ObjectAlignTarget::SELECTION;
+ auto orientation = Geom::Dim2::X;
+ auto direction = Inkscape::Selection::HORIZONTAL;
+
+ // Preference request allows alignment action to remember for key-presses
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (std::find(tokens.begin(), tokens.end(), "pref") != tokens.end()) {
+ tokens.push_back(prefs->getString("/dialogs/align/objects-align-to", "selection"));
+ }
+
+ for (auto const token : tokens) {
+ // Target
+ if (token == "last" ) target = ObjectAlignTarget::LAST;
+ else if (token == "first" ) target = ObjectAlignTarget::FIRST;
+ else if (token == "biggest" ) target = ObjectAlignTarget::BIGGEST;
+ else if (token == "smallest" ) target = ObjectAlignTarget::SMALLEST;
+ else if (token == "page" ) target = ObjectAlignTarget::PAGE;
+ else if (token == "drawing" ) target = ObjectAlignTarget::DRAWING;
+ else if (token == "selection") target = ObjectAlignTarget::SELECTION;
+
+ // Direction
+ if (token == "vertical" ) {
+ orientation = Geom::Dim2::Y;
+ direction = Inkscape::Selection::VERTICAL;
+ }
+ }
+
+ auto selection = app->get_active_selection();
+
+ // We should not have to do this!
+ auto document = app->get_active_document();
+ selection->setDocument(document);
+
+ // Find alignment rectangle. This can come from:
+ // - The bounding box of an object
+ // - The bounding box of a group of objects
+ // - The bounding box of the page, drawing, or selection.
+ SPItem *focus = nullptr;
+ Geom::OptRect b = Geom::OptRect();
+
+ switch (target) {
+ case ObjectAlignTarget::LAST:
+ focus = selection->items().back();
+ break;
+ case ObjectAlignTarget::FIRST:
+ focus = selection->items().front();
+ break;
+ case ObjectAlignTarget::BIGGEST:
+ focus = selection->largestItem(direction);
+ break;
+ case ObjectAlignTarget::SMALLEST:
+ focus = selection->smallestItem(direction);
+ break;
+ case ObjectAlignTarget::PAGE:
+ b = document->pageBounds();
+ break;
+ case ObjectAlignTarget::DRAWING:
+ b = document->getRoot()->desktopPreferredBounds();
+ break;
+ case ObjectAlignTarget::SELECTION:
+ b = selection->preferredBounds();
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ };
+
+ Geom::Point ref_point;
+ if (focus) {
+ if (dynamic_cast<SPText *>(focus) || dynamic_cast<SPFlowtext *>(focus)) {
+ ref_point = *(te_get_layout(focus)->baselineAnchorPoint())*(focus->i2dt_affine());
+ } else {
+ ref_point = focus->desktopPreferredBounds()->min();
+ }
+ } else {
+ ref_point = b->min();
+ }
+
+ for (auto item : selection->items()) {
+ if (dynamic_cast<SPText *>(item) || dynamic_cast<SPFlowtext *>(item)) {
+ Inkscape::Text::Layout const *layout = te_get_layout(item);
+ std::optional<Geom::Point> pt = layout->baselineAnchorPoint();
+ if (pt) {
+ Geom::Point base = *pt * (item)->i2dt_affine();
+ Geom::Point t(0.0, 0.0);
+ t[orientation] = ref_point[orientation] - base[orientation];
+ item->move_rel(Geom::Translate(t));
+ }
+ }
+ }
+
+ Inkscape::DocumentUndo::done( document, _("Align"), INKSCAPE_ICON("dialog-align-and-distribute"));
+}
+
+/* --------------- Rearrange ----------------- */
+
+class RotateCompare
+{
+public:
+ RotateCompare(Geom::Point& center) : center(center) {}
+
+ bool operator()(const SPItem* a, const SPItem* b) {
+ Geom::Point point_a = a->getCenter() - (center);
+ Geom::Point point_b = b->getCenter() - (center);
+
+ // Sort according to angle.
+ double angle_a = Geom::atan2(point_a);
+ double angle_b = Geom::atan2(point_b);
+ if (angle_a != angle_b) return (angle_a < angle_b);
+
+ // Sort by distance
+ return point_a.length() < point_b.length();
+ }
+
+private:
+ Geom::Point center;
+};
+
+enum SortOrder {
+ SelectionOrder,
+ ZOrder,
+ Rotate
+};
+
+static bool PositionCompare(const SPItem* a, const SPItem* b) {
+ return sp_item_repr_compare_position(a, b) < 0;
+}
+
+void exchange(Inkscape::Selection* selection, SortOrder order)
+{
+ std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
+
+ // Reorder items.
+ switch (order) {
+ case SelectionOrder:
+ break;
+ case ZOrder:
+ std::sort(items.begin(), items.end(), PositionCompare);
+ break;
+ case Rotate:
+ auto center = selection->center();
+ if (center) {
+ std::sort(items.begin(), items.end(), RotateCompare(*center));
+ }
+ break;
+ }
+
+ // Move items.
+ Geom::Point p1 = items.back()->getCenter();
+ for (SPItem *item : items) {
+ Geom::Point p2 = item->getCenter();
+ Geom::Point delta = p1 - p2;
+ item->move_rel(Geom::Translate(delta));
+ p1 = p2;
+ }
+}
+
+/*
+ * The algorithm keeps the size of the bounding box of the centers of all items constant. This
+ * ensures there is no growth or shrinking or drift of the overall area of the items on sequential
+ * randomizations.
+ */
+void randomize(Inkscape::Selection* selection)
+{
+ std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
+
+ // Do 'x' and 'y' independently.
+ for (int i = 0; i < 2; i++) {
+
+ // First, find maximum and minimum centers.
+ double min = std::numeric_limits<double>::max();
+ double max = std::numeric_limits<double>::min();
+
+ for (auto item : items) {
+ double center = item->getCenter()[i];
+ if (min > center) {
+ min = center;
+ }
+ if (max < center) {
+ max = center;
+ }
+ }
+
+
+ // Second, assign minimum/maximum values to two different items randomly.
+ int nitems = items.size();
+ int imin = rand() % nitems;
+ int imax = rand() % nitems;
+ while (imin == imax) {
+ imax = rand() % nitems;
+ }
+
+
+ // Third, find new positions of item centers.
+ int index = 0;
+ for (auto item : items) {
+ double z = 0.0;
+ if (index == imin) {
+ z = min;
+ } else if (index == imax) {
+ z = max;
+ } else {
+ z = g_random_double_range(min, max);
+ }
+
+ double delta = z - item->getCenter()[i];
+ Geom::Point t;
+ t[i] = delta;
+ item->move_rel(Geom::Translate(t));
+
+ ++index;
+ }
+ }
+}
+
+
+void
+object_rearrange(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ auto token = s.get();
+
+ auto selection = app->get_active_selection();
+
+ // We should not have to do this!
+ auto document = app->get_active_document();
+ selection->setDocument(document);
+
+ std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
+ if (items.size() < 2) {
+ return;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ // clang-format off
+ if (token == "graph" ) { graphlayout(items); }
+ else if (token == "exchange" ) { exchange(selection, SortOrder::SelectionOrder); }
+ else if (token == "exchangez" ) { exchange(selection, SortOrder::ZOrder); }
+ else if (token == "rotate" ) { exchange(selection, SortOrder::Rotate); }
+ else if (token == "randomize" ) { randomize(selection); }
+ else if (token == "unclump" ) { unclump(items); }
+ else {
+ std::cerr << "object_rearrange: unhandled argument: " << token << std::endl;
+ }
+ // clang-format on
+
+ // Restore compensation setting.
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ Inkscape::DocumentUndo::done( document, _("Rearrange"), INKSCAPE_ICON("dialog-align-and-distribute"));
+}
+
+
+void
+object_remove_overlaps(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // We should not have to do this!
+ auto document = app->get_active_document();
+ selection->setDocument(document);
+
+ std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
+ if (items.size() < 2) {
+ return;
+ }
+
+ // We used tuple so as not to convert from double to string and back again (from Align and Distribute dialog).
+ if (value.get_type_string() != "(dd)") {
+ std::cerr << "object_remove_overlaps: wrong variant type: " << value.get_type_string() << " (should be '(dd)')" << std::endl;
+ }
+
+ auto tuple = Glib::VariantBase::cast_dynamic<Glib::Variant<std::tuple<double, double>>>(value);
+ auto [hgap, vgap] = tuple.get();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ removeoverlap(items, hgap, vgap);
+
+ // Restore compensation setting.
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ Inkscape::DocumentUndo::done( document, _("Remove overlaps"), INKSCAPE_ICON("dialog-align-and-distribute"));
+}
+
+
+std::vector<std::vector<Glib::ustring>> raw_data_object_align =
+{
+ // clang-format off
+ {"app.object-align-on-canvas", N_("Enable on-canvas alignment"), "Object", N_("Enable on-canvas alignment handles." )},
+
+ {"app.object-align", N_("Align objects"), "Object", N_("Align selected objects; usage: [[left|hcenter|right] || [top|vcenter|bottom]] [last|first|biggest|smallest|page|drawing|selection|pref]? group? anchor?")},
+
+ {"app.object-align('left pref')", N_("Align to left edge"), "Object", N_("Align selection horizontally to left edge." )},
+ {"app.object-align('hcenter pref')", N_("Align to horizontal center"), "Object", N_("Align selection horizontally to the center." )},
+ {"app.object-align('right pref')", N_("Align to right edge"), "Object", N_("Align selection horizontally to right edge." )},
+ {"app.object-align('top pref')", N_("Align to top edge"), "Object", N_("Align selection vertically to top edge." )},
+ {"app.object-align('bottom pref')", N_("Align to bottom edge"), "Object", N_("Align selection vertically to bottom edge." )},
+ {"app.object-align('vcenter pref')", N_("Align to vertical center"), "Object", N_("Align selection vertically to the center." )},
+ {"app.object-align('hcenter vcenter pref')", N_("Align to center"), "Object", N_("Align selection to the center." )},
+ {"app.object-align-text", N_("Align text objects"), "Object", N_("Align selected text alignment points; usage: [[vertical | horizontal] [last|first|biggest|smallest|page|drawing|selection]?" )},
+
+ {"app.object-distribute", N_("Distribute objects"), "Object", N_("Distribute selected objects; usage: [hgap | left | hcenter | right | vgap | top | vcenter | bottom]" )},
+ {"app.object-distribute('hgap')", N_("Even horizontal gaps"), "Object", N_("Distribute horizontally with even horizontal gaps." )},
+ {"app.object-distribute('left')", N_("Even left edges"), "Object", N_("Distribute horizontally with even spacing between left edges." )},
+ {"app.object-distribute('hcenter')", N_("Even horizontal centers"), "Object", N_("Distribute horizontally with even spacing between centers." )},
+ {"app.object-distribute('right')", N_("Even right edges"), "Object", N_("Distribute horizontally with even spacing between right edges." )},
+ {"app.object-distribute('vgap')", N_("Even vertical gaps"), "Object", N_("Distribute vertically with even vertical gaps." )},
+ {"app.object-distribute('top')", N_("Even top edges"), "Object", N_("Distribute vertically with even spacing between top edges." )},
+ {"app.object-distribute('vcenter')", N_("Even vertical centers"), "Object", N_("Distribute vertically with even spacing between centers." )},
+ {"app.object-distribute('bottom')", N_("Even bottom edges"), "Object", N_("Distribute vertically with even spacing between bottom edges." )},
+
+ {"app.object-distribute-text", N_("Distribute text objects"), "Object", N_("Distribute text alignment points; usage [vertical | horizontal]" )},
+ {"app.object-distribute-text('horizontal')", N_("Distribute text objects"), "Object", N_("Distribute text alignment points horizontally" )},
+ {"app.object-distribute-text('vertical')", N_("Distribute text objects"), "Object", N_("Distribute text alignment points vertically" )},
+
+ {"app.object-rearrange", N_("Rearrange objects"), "Object", N_("Rearrange selected objects; usage: [graph | exchange | exchangez | rotate | randomize | unclump]" )},
+ {"app.object-rearrange('graph')", N_("Rearrange as graph"), "Object", N_("Nicely arrange selected connector network." )},
+ {"app.object-rearrange('exchange')", N_("Exchange in selection order"), "Object", N_("Exchange positions of selected objects - selection order." )},
+ {"app.object-rearrange('exchangez')", N_("Exchange in z-order"), "Object", N_("Exchange positions of selected objects - stacking order." )},
+ {"app.object-rearrange('rotate')", N_("Exchange around center"), "Object", N_("Exchange positions of selected objects - rotate around center point." )},
+ {"app.object-rearrange('randomize')", N_("Random exchange"), "Object", N_("Randomize centers in both dimensions." )},
+ {"app.object-rearrange('unclump')", N_("Unclump"), "Object", N_("Unclump objects: try to equalize edge-to-edge distances." )},
+
+ {"app.object-remove-overlaps", N_("Remove overlaps"), "Object", N_("Remove overlaps between objects: requires two comma separated numbers (horizontal and vertical gaps)." )},
+ // clang-format on
+};
+
+std::vector<std::vector<Glib::ustring>> hint_data_object_align =
+{
+ // clang-format off
+ {"app.object-align", N_("Enter anchor<space>alignment<space>optional second alignment. Possible anchors: last, first, biggest, smallest, page, drawing, selection, pref; possible alignments: left, hcenter, right, top, vcenter, bottom.")},
+ {"app.object-distribute", N_("Enter distribution type. Possible values: left, hcenter, right, top, vcenter, bottom, hgap, vgap.") },
+ {"app.object-rearrange", N_("Enter arrange method. Possible values: graph, exchange, exchangez, rotate, randomize, unclump.") },
+ {"app.object-remove-overlaps", N_("Enter two comma-separated numbers: horizontal,vertical") },
+ // clang-format on
+};
+
+void
+add_actions_object_align(InkscapeApplication* app)
+{
+ Glib::VariantType String(Glib::VARIANT_TYPE_STRING);
+ std::vector<Glib::VariantType> dd = {Glib::VARIANT_TYPE_DOUBLE, Glib::VARIANT_TYPE_DOUBLE};
+ Glib::VariantType Tuple_DD = Glib::VariantType::create_tuple(dd);
+
+ auto *gapp = app->gio_app();
+
+ auto prefs = Inkscape::Preferences::get();
+ bool on_canvas = prefs->getBool("/dialogs/align/oncanvas");
+
+ // clang-format off
+ gapp->add_action_bool( "object-align-on-canvas", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_align_on_canvas), app), on_canvas);
+ gapp->add_action_with_parameter( "object-align", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_align), app));
+ gapp->add_action_with_parameter( "object-align-text", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_align_text), app));
+ gapp->add_action_with_parameter( "object-distribute", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_distribute), app));
+ gapp->add_action_with_parameter( "object-distribute-text", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_distribute_text), app));
+ gapp->add_action_with_parameter( "object-rearrange", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_rearrange), app));
+ gapp->add_action_with_parameter( "object-remove-overlaps", Tuple_DD, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_remove_overlaps), app));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_object_align);
+ app->get_action_hint_data().add_data(hint_data_object_align);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-object-align.h b/src/actions/actions-object-align.h
new file mode 100644
index 0000000..6da938c
--- /dev/null
+++ b/src/actions/actions-object-align.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for aligning and distributing objects without GUI.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_OBJECT_ALIGN_H
+#define INK_ACTIONS_OBJECT_ALIGN_H
+
+class InkscapeApplication;
+
+void add_actions_object_align(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_OBJECT_ALIGN_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-object.cpp b/src/actions/actions-object.cpp
new file mode 100644
index 0000000..72d9e31
--- /dev/null
+++ b/src/actions/actions-object.cpp
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for working with objects without GUI.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-object.h"
+#include "actions-helper.h"
+#include "document-undo.h"
+#include "inkscape-application.h"
+
+#include "inkscape.h" // Inkscape::Application
+#include "selection.h" // Selection
+#include "path/path-simplify.h"
+
+#include "live_effects/lpe-powerclip.h"
+#include "live_effects/lpe-powermask.h"
+#include "ui/icon-names.h"
+
+// No sanity checking is done... should probably add.
+void
+object_set_attribute(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", s.get());
+ if (tokens.size() != 2) {
+ std::cerr << "action:object_set_attribute: requires 'attribute name, attribute value'" << std::endl;
+ return;
+ }
+
+ auto selection = app->get_active_selection();
+ if (selection->isEmpty()) {
+ std::cerr << "action:object_set_attribute: selection empty!" << std::endl;
+ return;
+ }
+
+ // Should this be a selection member function?
+ auto items = selection->items();
+ for (auto i = items.begin(); i != items.end(); ++i) {
+ Inkscape::XML::Node *repr = (*i)->getRepr();
+ repr->setAttribute(tokens[0], tokens[1]);
+ }
+
+ // Needed to update repr (is this the best way?).
+ Inkscape::DocumentUndo::done(app->get_active_document(), "ActionObjectSetAttribute", "");
+}
+
+
+// No sanity checking is done... should probably add.
+void
+object_set_property(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", s.get());
+ if (tokens.size() != 2) {
+ std::cerr << "action:object_set_property: requires 'property name, property value'" << std::endl;
+ return;
+ }
+
+ auto selection = app->get_active_selection();
+ if (selection->isEmpty()) {
+ std::cerr << "action:object_set_property: selection empty!" << std::endl;
+ return;
+ }
+
+ // Should this be a selection member function?
+ auto items = selection->items();
+ for (auto i = items.begin(); i != items.end(); ++i) {
+ Inkscape::XML::Node *repr = (*i)->getRepr();
+ SPCSSAttr *css = sp_repr_css_attr(repr, "style");
+ sp_repr_css_set_property(css, tokens[0].c_str(), tokens[1].c_str());
+ sp_repr_css_set(repr, css, "style");
+ sp_repr_css_attr_unref(css);
+ }
+
+ // Needed to update repr (is this the best way?).
+ Inkscape::DocumentUndo::done(app->get_active_document(), "ActionObjectSetProperty", "");
+}
+
+
+void
+object_unlink_clones(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // We should not have to do this!
+ auto document = app->get_active_document();
+ selection->setDocument(document);
+
+ selection->unlink();
+}
+
+void
+object_clip_set(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Object Clip Set
+ selection->setMask(true, false);
+}
+
+void
+object_clip_set_inverse(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Object Clip Set Inverse
+ selection->setMask(true, false);
+ Inkscape::LivePathEffect::sp_inverse_powerclip(app->get_active_selection());
+ Inkscape::DocumentUndo::done(app->get_active_document(), _("Set Inverse Clip(LPE)"), "");
+}
+
+void
+object_clip_release(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Object Clip Release
+ Inkscape::LivePathEffect::sp_remove_powerclip(app->get_active_selection());
+ selection->unsetMask(true);
+ Inkscape::DocumentUndo::done(app->get_active_document(), _("Release clipping path"), "");
+}
+
+void
+object_clip_set_group(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+ selection->setClipGroup();
+ // Undo added in setClipGroup().
+}
+
+void
+object_mask_set(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Object Mask Set
+ selection->setMask(false, false);
+ // Undo added in setMask().
+}
+
+void
+object_mask_set_inverse(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Object Mask Set Inverse
+ selection->setMask(false, false);
+ Inkscape::LivePathEffect::sp_inverse_powermask(app->get_active_selection());
+ Inkscape::DocumentUndo::done(app->get_active_document(), _("Set Inverse Mask (LPE)"), "");
+}
+
+void
+object_mask_release(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Object Mask Release
+ Inkscape::LivePathEffect::sp_remove_powermask(app->get_active_selection());
+ selection->unsetMask(false);
+ Inkscape::DocumentUndo::done(app->get_active_document(), _("Release mask"), "");
+}
+
+void
+object_rotate_90_cw(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Object Rotate 90
+ selection->rotate90(false);
+}
+
+void
+object_rotate_90_ccw(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Object Rotate 90 CCW
+ selection->rotate90(true);
+}
+
+void
+object_flip_horizontal(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ Geom::OptRect bbox = selection->visualBounds();
+ if (!bbox) {
+ return;
+ }
+
+ // Get center
+ Geom::Point center;
+ if (selection->center()) {
+ center = *selection->center();
+ } else {
+ center = bbox->midpoint();
+ }
+
+ // Object Flip Horizontal
+ selection->setScaleRelative(center, Geom::Scale(-1.0, 1.0));
+ Inkscape::DocumentUndo::done(app->get_active_document(), _("Flip horizontally"), INKSCAPE_ICON("object-flip-horizontal"));
+}
+
+void
+object_flip_vertical(InkscapeApplication *app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ Geom::OptRect bbox = selection->visualBounds();
+ if (!bbox) {
+ return;
+ }
+
+ // Get center
+ Geom::Point center;
+ if (selection->center()) {
+ center = *selection->center();
+ } else {
+ center = bbox->midpoint();
+ }
+
+ // Object Flip Vertical
+ selection->setScaleRelative(center, Geom::Scale(1.0, -1.0));
+ Inkscape::DocumentUndo::done(app->get_active_document(), _("Flip vertically"), INKSCAPE_ICON("object-flip-vertical"));
+}
+
+
+void
+object_to_path(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // We should not have to do this!
+ auto document = app->get_active_document();
+ selection->setDocument(document);
+
+ selection->toCurves(); // TODO: Rename toPaths()
+}
+
+
+void
+object_stroke_to_path(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // We should not have to do this!
+ auto document = app->get_active_document();
+ selection->setDocument(document);
+
+ selection->strokesToPaths();
+}
+
+
+std::vector<std::vector<Glib::ustring>> raw_data_object =
+{
+ // clang-format off
+ {"app.object-set-attribute", N_("Set Attribute"), "Object", N_("Set or update an attribute of selected objects; usage: object-set-attribute:attribute name, attribute value;")},
+ {"app.object-set-property", N_("Set Property"), "Object", N_("Set or update a property on selected objects; usage: object-set-property:property name, property value;")},
+
+ {"app.object-unlink-clones", N_("Unlink Clones"), "Object", N_("Unlink clones and symbols")},
+ {"app.object-to-path", N_("Object To Path"), "Object", N_("Convert shapes to paths")},
+ {"app.object-stroke-to-path", N_("Stroke to Path"), "Object", N_("Convert strokes to paths")},
+
+ {"app.object-set-clip", N_("Object Clip Set"), "Object", N_("Apply clipping path to selection (using the topmost object as clipping path)")},
+ {"app.object-set-inverse-clip", N_("Object Clip Set Inverse"), "Object", N_("Apply inverse clipping path to selection (Power Clip LPE)")},
+ {"app.object-release-clip", N_("Object Clip Release"), "Object", N_("Remove clipping path from selection")},
+ {"app.object-set-clip-group", N_("Object Clip Set Group"), "Object", N_("Create a self-clipping group to which objects (not contributing to the clip-path) can be added")},
+ {"app.object-set-mask", N_("Object Mask Set"), "Object", N_("Apply mask to selection (using the topmost object as mask)")},
+ {"app.object-set-inverse-mask", N_("Object Mask Set Inverse"), "Object", N_("Apply inverse mask to selection (Power Mask LPE)")},
+ {"app.object-release-mask", N_("Object Mask Release"), "Object", N_("Remove mask from selection")},
+
+ {"app.object-rotate-90-cw", N_("Object Rotate 90"), "Object", N_("Rotate selection 90° clockwise")},
+ {"app.object-rotate-90-ccw", N_("Object Rotate 90 CCW"), "Object", N_("Rotate selection 90° counter-clockwise")},
+ {"app.object-flip-horizontal", N_("Object Flip Horizontal"), "Object", N_("Flip selected objects horizontally")},
+ {"app.object-flip-vertical", N_("Object Flip Vertical"), "Object", N_("Flip selected objects vertically")}
+ // clang-format on
+};
+
+std::vector<std::vector<Glib::ustring>> hint_data_object =
+{
+ // clang-format off
+ {"app.object-set-attribute", N_("Enter comma-separated string for attribute name, attribute value") },
+ {"app.object-set-property", N_("Enter comma-separated string for property name, property value") }
+ // clang-format on
+};
+
+void
+add_actions_object(InkscapeApplication* app)
+{
+ Glib::VariantType Bool( Glib::VARIANT_TYPE_BOOL);
+ Glib::VariantType Int( Glib::VARIANT_TYPE_INT32);
+ Glib::VariantType Double(Glib::VARIANT_TYPE_DOUBLE);
+ Glib::VariantType String(Glib::VARIANT_TYPE_STRING);
+
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action_with_parameter( "object-set-attribute", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_set_attribute), app));
+ gapp->add_action_with_parameter( "object-set-property", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_set_property), app));
+
+ gapp->add_action( "object-unlink-clones", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_unlink_clones), app));
+ gapp->add_action( "object-to-path", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_to_path), app));
+ gapp->add_action( "object-stroke-to-path", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_stroke_to_path), app));
+
+ gapp->add_action( "object-set-clip", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_clip_set), app));
+ gapp->add_action( "object-set-inverse-clip", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_clip_set_inverse), app));
+ gapp->add_action( "object-release-clip", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_clip_release), app));
+ gapp->add_action( "object-set-clip-group", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_clip_set_group), app));
+ gapp->add_action( "object-set-mask", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_mask_set), app));
+ gapp->add_action( "object-set-inverse-mask", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_mask_set_inverse), app));
+ gapp->add_action( "object-release-mask", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_mask_release), app));
+
+ gapp->add_action( "object-rotate-90-cw", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_rotate_90_cw), app));
+ gapp->add_action( "object-rotate-90-ccw", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_rotate_90_ccw), app));
+ gapp->add_action( "object-flip-horizontal", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_flip_horizontal), app));
+ gapp->add_action( "object-flip-vertical", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_flip_vertical), app));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_object);
+ app->get_action_hint_data().add_data(hint_data_object);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-object.h b/src/actions/actions-object.h
new file mode 100644
index 0000000..be03f7c
--- /dev/null
+++ b/src/actions/actions-object.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for working with objects not tied to GUI.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_OBJECT_H
+#define INK_ACTIONS_OBJECT_H
+
+class InkscapeApplication;
+
+void add_actions_object(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_OBJECT_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-output.cpp b/src/actions/actions-output.cpp
new file mode 100644
index 0000000..e521fbd
--- /dev/null
+++ b/src/actions/actions-output.cpp
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for output tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-output.h"
+#include "actions-helper.h"
+#include "inkscape-application.h"
+
+#include "inkscape.h" // Inkscape::Application
+
+// Actions for command line output (should be integrated with file dialog).
+
+// These actions are currently stateless and result in changes to an instance of the
+// InkFileExportCmd class owned by the application.
+
+void
+export_type(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ app->file_export()->export_type = s.get();
+ // std::cout << "export-type: " << s.get() << std::endl;
+}
+
+void
+export_filename(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<std::string> s = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string> >(value);
+ app->file_export()->export_filename = s.get();
+ // std::cout << "export-filename: " << s.get() << std::endl;
+}
+
+void
+export_overwrite(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_overwrite = b.get();
+ // std::cout << "export-overwrite: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_area(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<std::string> s = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string> >(value);
+ app->file_export()->export_area = s.get();
+ // std::cout << "export-area: " << s.get() << std::endl;
+}
+
+void
+export_area_drawing(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_area_drawing = b.get();
+ // std::cout << "export-area-drawing: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_area_page(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_area_page = b.get();
+ // std::cout << "export-area-page: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_margin(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<int> i = Glib::VariantBase::cast_dynamic<Glib::Variant<int> >(value);
+ app->file_export()->export_margin = i.get();
+ // std::cout << "export-margin: " << i.get() << std::endl;
+}
+
+void
+export_area_snap(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_area_snap = b.get();
+ // std::cout << "export-area-snap: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_width(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<int> i = Glib::VariantBase::cast_dynamic<Glib::Variant<int> >(value);
+ app->file_export()->export_width = i.get();
+ // std::cout << "export-width: " << i.get() << std::endl;
+}
+
+void
+export_height(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<int> i = Glib::VariantBase::cast_dynamic<Glib::Variant<int> >(value);
+ app->file_export()->export_height = i.get();
+ // std::cout << "export-height: " << i.get() << std::endl;
+}
+
+void
+export_id(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<std::string> s = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string> >(value);
+ app->file_export()->export_id = s.get();
+ // std::cout << "export-id: " << s.get() << std::endl;
+}
+
+void
+export_id_only(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_id_only = b.get();
+ // std::cout << "export-id-only: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_plain_svg(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_plain_svg = b.get();
+ // std::cout << "export-plain-svg: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_dpi(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<double> d = Glib::VariantBase::cast_dynamic<Glib::Variant<double> >(value);
+ app->file_export()->export_dpi = d.get();
+ // std::cout << "export-dpi: " << d.get() << std::endl;
+}
+
+void
+export_ignore_filters(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_ignore_filters = b.get();
+ // std::cout << "export-ignore-filters: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_text_to_path(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_text_to_path = b.get();
+ // std::cout << "export-text-to-path: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_ps_level(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<int> i = Glib::VariantBase::cast_dynamic<Glib::Variant<int> >(value);
+ app->file_export()->export_ps_level = i.get();
+ // std::cout << "export-ps-level: " << i.get() << std::endl;
+}
+
+void
+export_pdf_level(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+ app->file_export()->export_pdf_level = s.get();
+ // std::cout << "export-pdf-level" << s.get() << std::endl;
+}
+
+void
+export_latex(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_latex = b.get();
+ // std::cout << "export-latex: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_use_hints(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_use_hints = b.get();
+ // std::cout << "export-use-hints: " << std::boolalpha << b.get() << std::endl;
+}
+
+void
+export_background(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<std::string> s = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string> >(value);
+ app->file_export()->export_background = s.get();
+ // std::cout << "export-background: " << s.get() << std::endl;
+}
+
+void
+export_background_opacity(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<double> d = Glib::VariantBase::cast_dynamic<Glib::Variant<double> >(value);
+ app->file_export()->export_background_opacity = d.get();
+ // std::cout << d.get() << std::endl;
+}
+
+void
+export_png_color_mode(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<std::string> s = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string> >(value);
+ app->file_export()->export_png_color_mode = s.get();
+ // std::cout << s.get() << std::endl;
+}
+
+void
+export_png_use_dithering(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<bool> b = Glib::VariantBase::cast_dynamic<Glib::Variant<bool> >(value);
+ app->file_export()->export_png_use_dithering = b.get();
+ // std::cout << s.get() << std::endl;
+}
+
+void
+export_do(InkscapeApplication *app)
+{
+ SPDocument* document = app->get_active_document();
+ std::string filename;
+ if (document->getDocumentFilename()) {
+ filename = document->getDocumentFilename();
+ }
+ app->file_export()->do_export(document, filename);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_output =
+{
+ // clang-format off
+ {"app.export-type", N_("Export Type"), "Export", N_("Set export file type") },
+ {"app.export-filename", N_("Export File Name"), "Export", N_("Set export file name") },
+ {"app.export-overwrite", N_("Export Overwrite"), "Export", N_("Allow to overwrite existing files during export") },
+
+ {"app.export-area", N_("Export Area"), "Export", N_("Set export area") },
+ {"app.export-area-drawing", N_("Export Area Drawing"), "Export", N_("Export drawing area") },
+ {"app.export-area-page", N_("Export Area Page"), "Export", N_("Export page area") },
+ {"app.export-margin", N_("Export Margin"), "Export", N_("Set additional export margin") },
+ {"app.export-area-snap", N_("Export Area Snap"), "Export", N_("Snap export area to integer values") },
+ {"app.export-width", N_("Export Width"), "Export", N_("Set export width") },
+ {"app.export-height", N_("Export Height"), "Export", N_("Set export height") },
+
+ {"app.export-id", N_("Export ID"), "Export", N_("Export selected ID(s)") },
+ {"app.export-id-only", N_("Export ID Only"), "Export", N_("Hide any objects not given in export-id option") },
+
+ {"app.export-plain-svg", N_("Export Plain SVG"), "Export", N_("Export as plain SVG") },
+ {"app.export-dpi", N_("Export DPI"), "Export", N_("Set export DPI") },
+ {"app.export-ignore-filters", N_("Export Ignore Filters"), "Export", N_("Export without filters to avoid rasterization for PDF, PS, EPS")},
+ {"app.export-text-to-path", N_("Export Text to Path"), "Export", N_("Convert texts to paths in the exported file") },
+ {"app.export-ps-level", N_("Export PS Level"), "Export", N_("Set PostScript level") },
+ {"app.export-pdf-version", N_("Export PDF Version"), "Export", N_("Set PDF version") },
+ {"app.export-latex", N_("Export LaTeX"), "Export", N_("Export LaTeX") },
+ {"app.export-use-hints", N_("Export Use Hints"), "Export", N_("Export using saved hints") },
+ {"app.export-background", N_("Export Background"), "Export", N_("Include background color in exported file") },
+ {"app.export-background-opacity", N_("Export Background Opacity"), "Export", N_("Include background opacity in exported file") },
+ {"app.export-png-color-mode", N_("Export PNG Color Mode"), "Export", N_("Set color mode for PNG export") },
+ {"app.export-png-use-dithering", N_("Export PNG Dithering"), "Export", N_("Set dithering for PNG export") },
+
+ {"app.export-do", N_("Do Export"), "Export", N_("Do export") }
+ // clang-format on
+};
+
+std::vector<std::vector<Glib::ustring>> hint_data_output =
+{
+ // clang-format off
+ {"app.export-type", N_("Enter string for the file type") },
+ {"app.export-filename", N_("Enter string for the file name") },
+ {"app.export-overwrite", N_("Enter 1/0 for Yes/No to overwrite exported file") },
+
+ {"app.export-area", N_("Enter string for export area, formatted like x0:y0:x1:y1") },
+ {"app.export-area-drawing", N_("Enter 1/0 for Yes/No to export drawing area") },
+ {"app.export-area-page", N_("Enter 1/0 for Yes/No to export page area") },
+ {"app.export-margin", N_("Enter integer number for margin") },
+ {"app.export-area-snap", N_("Enter 1/0 for Yes/No to snap the export area") },
+ {"app.export-width", N_("Enter integer number for width") },
+ {"app.export-height", N_("Enter integer number for height") },
+
+ {"app.export-id", N_("Enter string for export ID") },
+ {"app.export-id-only", N_("Enter 1/0 for Yes/No to export only given ID") },
+
+ {"app.export-plain-svg", N_("Enter 1/0 for Yes/No to export plain SVG") },
+ {"app.export-dpi", N_("Enter integer number for export DPI") },
+ {"app.export-ignore-filters", N_("Enter 1/0 for Yes/No to export ignoring filters") },
+ {"app.export-text-to-path", N_("Enter 1/0 for Yes/No to convert text to path on export") },
+ {"app.export-ps-level", N_("Enter integer number 2 or 3 for PS Level") },
+ {"app.export-pdf-version", N_("Enter string for PDF Version, e.g. 1.4 or 1.5") },
+ {"app.export-latex", N_("Enter 1/0 for Yes/No to export to PDF and LaTeX") },
+ {"app.export-use-hints", N_("Enter 1/0 for Yes/No to use export hints from document") },
+ {"app.export-background", N_("Enter string for background color, e.g. #ff007f or rgb(255, 0, 128)") },
+ {"app.export-background-opacity", N_("Enter number for background opacity, either between 0.0 and 1.0, or 1 up to 255") },
+ {"app.export-png-color-mode", N_("Enter string for PNG Color Mode, one of Gray_1/Gray_2/Gray_4/Gray_8/Gray_16/RGB_8/RGB_16/GrayAlpha_8/GrayAlpha_16/RGBA_8/RGBA_16")},
+ {"app.export-png-use-dithering", N_("Enter 1/0 for Yes/No to use dithering") }
+ // clang-format on
+};
+
+
+void
+add_actions_output(InkscapeApplication* app)
+{
+ Glib::VariantType Bool( Glib::VARIANT_TYPE_BOOL);
+ Glib::VariantType Int( Glib::VARIANT_TYPE_INT32);
+ Glib::VariantType Double(Glib::VARIANT_TYPE_DOUBLE);
+ Glib::VariantType String(Glib::VARIANT_TYPE_STRING);
+ Glib::VariantType BString(Glib::VARIANT_TYPE_BYTESTRING);
+
+ // Debian 9 has 2.50.0
+#if GLIB_CHECK_VERSION(2, 52, 0)
+ auto *gapp = app->gio_app();
+
+ // Matches command line options
+ // clang-format off
+ gapp->add_action_with_parameter( "export-type", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_type), app));
+ gapp->add_action_with_parameter( "export-filename", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_filename), app)); // MAY NOT WORK DUE TO std::string
+ gapp->add_action_with_parameter( "export-overwrite", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_overwrite), app));
+
+ gapp->add_action_with_parameter( "export-area", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_area), app));
+ gapp->add_action_with_parameter( "export-area-drawing", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_area_drawing), app));
+ gapp->add_action_with_parameter( "export-area-page", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_area_page), app));
+ gapp->add_action_with_parameter( "export-margin", Int, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_margin), app));
+ gapp->add_action_with_parameter( "export-area-snap", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_area_snap), app));
+ gapp->add_action_with_parameter( "export-width", Int, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_width), app));
+ gapp->add_action_with_parameter( "export-height", Int, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_height), app));
+
+ gapp->add_action_with_parameter( "export-id", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_id), app));
+ gapp->add_action_with_parameter( "export-id-only", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_id_only), app));
+
+ gapp->add_action_with_parameter( "export-plain-svg", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_plain_svg), app));
+ gapp->add_action_with_parameter( "export-dpi", Double, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_dpi), app));
+ gapp->add_action_with_parameter( "export-ignore-filters", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_plain_svg), app));
+ gapp->add_action_with_parameter( "export-text-to-path", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_text_to_path), app));
+ gapp->add_action_with_parameter( "export-ps-level", Int, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_ps_level), app));
+ gapp->add_action_with_parameter( "export-pdf-version", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_pdf_level), app));
+ gapp->add_action_with_parameter( "export-latex", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_latex), app));
+ gapp->add_action_with_parameter( "export-use-hints", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_use_hints), app));
+ gapp->add_action_with_parameter( "export-background", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_background), app));
+ gapp->add_action_with_parameter( "export-background-opacity",Double, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_background_opacity), app));
+ gapp->add_action_with_parameter( "export-png-color-mode", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_png_color_mode), app));
+ gapp->add_action_with_parameter( "export-png-use-dithering", Bool, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_png_use_dithering), app));
+
+ // Extra
+ gapp->add_action( "export-do", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&export_do), app));
+ // clang-format on
+#else
+ std::cerr << "add_actions: Some actions require Glibmm 2.52, compiled with: " << glib_major_version << "." << glib_minor_version << std::endl;
+#endif
+
+ app->get_action_extra_data().add_data(raw_data_output);
+ app->get_action_hint_data().add_data(hint_data_output);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-output.h b/src/actions/actions-output.h
new file mode 100644
index 0000000..fffe184
--- /dev/null
+++ b/src/actions/actions-output.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for output tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_OUTPUT_H
+#define INK_ACTIONS_OUTPUT_H
+
+class InkscapeApplication;
+
+void add_actions_output(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_OUTPUT_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-pages.cpp b/src/actions/actions-pages.cpp
new file mode 100644
index 0000000..50bd820
--- /dev/null
+++ b/src/actions/actions-pages.cpp
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for pages, mostly for the toolbar.
+ *
+ * Copyright (C) 2021 Martin Owens
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-pages.h"
+#include "inkscape-application.h"
+#include "document-undo.h"
+
+#include "page-manager.h"
+#include "object/sp-page.h"
+#include "ui/icon-names.h"
+
+void page_new(SPDocument *document)
+{
+ document->getPageManager().selectPage(document->getPageManager().newPage());
+ Inkscape::DocumentUndo::done(document, "New Automatic Page", INKSCAPE_ICON("tool-pages"));
+}
+
+void page_delete(SPDocument *document)
+{
+ // Delete page's content if move_objects is checked.
+ document->getPageManager().deletePage(document->getPageManager().move_objects());
+ Inkscape::DocumentUndo::done(document, "Delete Page", INKSCAPE_ICON("tool-pages"));
+}
+
+void page_backward(SPDocument *document)
+{
+ auto &page_manager = document->getPageManager();
+ if (auto page = page_manager.getSelected()) {
+ if (page->setPageIndex(page->getPageIndex() - 1, page_manager.move_objects())) {
+ Inkscape::DocumentUndo::done(document, "Shift Page Backwards", INKSCAPE_ICON("tool-pages"));
+ }
+ }
+}
+
+void page_forward(SPDocument *document)
+{
+ auto &page_manager = document->getPageManager();
+ if (auto page = page_manager.getSelected()) {
+ if (page->setPageIndex(page->getPageIndex() + 1, page_manager.move_objects())) {
+ Inkscape::DocumentUndo::done(document, "Shift Page Forewards", INKSCAPE_ICON("tool-pages"));
+ }
+ }
+}
+
+void set_move_objects(SPDocument *doc)
+{
+ if (auto action = doc->getActionGroup()->lookup_action("page-move-objects")) {
+ bool active = false;
+ action->get_state(active);
+ active = !active; // toggle
+ action->change_state(active);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/pages/move_objects", active);
+ } else {
+ g_warning("Can't find page-move-objects action group!");
+ }
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_actions =
+{
+ // clang-format off
+ {"doc.page-new", N_("New Page"), "Page", N_("Create a new page") },
+ {"doc.page-delete", N_("Delete Page"), "Page", N_("Delete the selected page") },
+ {"doc.page-move-objects", N_("Move Objects with Page"), "Page", N_("Move overlapping objects as the page is moved.") },
+ {"doc.page-move-backward", N_("Move Before Previous"), "Page", N_("Move page backwards in the page order") },
+ {"doc.page-move-forward", N_("Move After Next"), "Page", N_("Move page forwards in the page order") },
+ // clang-format on
+};
+
+void add_actions_pages(SPDocument* doc)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ auto group = doc->getActionGroup();
+ group->add_action("page-new", sigc::bind<SPDocument*>(sigc::ptr_fun(&page_new), doc));
+ group->add_action("page-delete", sigc::bind<SPDocument*>(sigc::ptr_fun(&page_delete), doc));
+ group->add_action("page-move-backward", sigc::bind<SPDocument*>(sigc::ptr_fun(&page_backward), doc));
+ group->add_action("page-move-forward", sigc::bind<SPDocument*>(sigc::ptr_fun(&page_forward), doc));
+ group->add_action_bool("page-move-objects", sigc::bind<SPDocument*>(sigc::ptr_fun(&set_move_objects), doc),
+ prefs->getBool("/tools/pages/move_objects", true));
+
+ // Note: This will only work for the first ux to load, possible problem.
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_pages: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_actions);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-pages.h b/src/actions/actions-pages.h
new file mode 100644
index 0000000..c00cccf
--- /dev/null
+++ b/src/actions/actions-pages.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for pages
+ *
+ * Copyright (C) 2021 Martin Owens
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_PAGES_H
+#define INK_ACTIONS_PAGES_H
+
+class SPDocument;
+
+void add_actions_pages(SPDocument* doc);
+
+#endif // INK_ACTIONS_PAGES_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-paths.cpp b/src/actions/actions-paths.cpp
new file mode 100644
index 0000000..1eaef3d
--- /dev/null
+++ b/src/actions/actions-paths.cpp
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for Path Actions
+ *
+ * Copyright (C) 2021 Sushant A.A.
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-paths.h"
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "selection.h" // Selection
+#include "selection-chemistry.h" // SelectionHelper
+#include "path/path-offset.h"
+
+void
+object_path_union(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->removeLPESRecursive(true);
+ selection->unlinkRecursive(true);
+ selection->pathUnion();
+}
+
+void
+select_path_difference(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->removeLPESRecursive(true);
+ selection->unlinkRecursive(true);
+ selection->pathDiff();
+}
+
+void
+select_path_intersection(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->removeLPESRecursive(true);
+ selection->unlinkRecursive(true);
+ selection->pathIntersect();
+}
+
+void
+select_path_exclusion(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->removeLPESRecursive(true);
+ selection->unlinkRecursive(true);
+ selection->pathSymDiff();
+}
+
+void
+select_path_division(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->removeLPESRecursive(true);
+ selection->unlinkRecursive(true);
+ selection->pathCut();
+}
+
+void
+select_path_cut(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->removeLPESRecursive(true);
+ selection->unlinkRecursive(true);
+ selection->pathSlice();
+}
+
+void
+select_path_combine(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->unlinkRecursive(true);
+ selection->combine();
+}
+
+void
+select_path_break_apart(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->breakApart();
+}
+
+void
+select_path_split(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->breakApart(false, false);
+}
+
+void
+fill_between_paths(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->fillBetweenMany();
+}
+
+void
+select_path_simplify(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->simplifyPaths();
+}
+
+void
+select_path_inset(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Inset selected paths
+ dt->selection->removeLPESRecursive(true);
+ dt->selection->unlinkRecursive(true);
+ sp_selected_path_inset(dt);
+}
+
+void
+select_path_offset(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Offset selected paths
+ dt->selection->removeLPESRecursive(true);
+ dt->selection->unlinkRecursive(true);
+ sp_selected_path_offset(dt);
+}
+
+void
+select_path_inset_screen(const Glib::VariantBase& value, InkscapeWindow *win)
+{
+ Glib::Variant<double> d = Glib::VariantBase::cast_dynamic<Glib::Variant<double> >(value);
+
+ SPDesktop* dt = win->get_desktop();
+
+ // Offset selected paths
+ dt->selection->removeLPESRecursive(true);
+ dt->selection->unlinkRecursive(true);
+ sp_selected_path_inset_screen(dt, d.get());
+}
+
+void
+select_path_offset_screen(const Glib::VariantBase& value, InkscapeWindow *win)
+{
+ Glib::Variant<double> d = Glib::VariantBase::cast_dynamic<Glib::Variant<double> >(value);
+
+ SPDesktop* dt = win->get_desktop();
+
+ // Offset selected paths
+ dt->selection->removeLPESRecursive(true);
+ dt->selection->unlinkRecursive(true);
+ sp_selected_path_offset_screen(dt, d.get());
+}
+
+void
+select_path_offset_dynamic(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Dynamic Offset
+ dt->selection->removeLPESRecursive(true);
+ dt->selection->unlinkRecursive(true);
+ sp_selected_path_create_offset_object_zero(dt);
+}
+
+void
+select_path_offset_linked(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Linked Offset
+ dt->selection->removeLPESRecursive(true);
+ dt->selection->unlinkRecursive(true);
+ sp_selected_path_create_updating_offset_object_zero(dt);
+}
+
+void
+select_path_reverse(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Reverse
+ Inkscape::SelectionHelper::reverse(dt);
+}
+
+
+std::vector<std::vector<Glib::ustring>> raw_data_path =
+{
+ // clang-format offs
+ {"app.path-union", N_("Union"), "Path", N_("Create union of selected paths")},
+ {"app.path-difference", N_("Difference"), "Path", N_("Create difference of selected paths (bottom minus top)")},
+ {"app.path-intersection", N_("Intersection"), "Path", N_("Create intersection of selected paths")},
+ {"app.path-exclusion", N_("Exclusion"), "Path", N_("Create exclusive OR of selected paths (those parts that belong to only one path)")},
+ {"app.path-division", N_("Division"), "Path", N_("Cut the bottom path into pieces")},
+ {"app.path-cut", N_("Cut Path"), "Path", N_("Cut the bottom path's stroke into pieces, removing fill")},
+ {"app.path-combine", N_("Combine"), "Path", N_("Combine several paths into one")},
+ {"app.path-break-apart", N_("Break Apart"), "Path", N_("Break selected paths into subpaths")},
+ {"app.path-split", N_("Split Apart"), "Path", N_("Split selected paths into non-overlapping sections")},
+ {"app.path-fill-between-paths", N_("Fill between paths"), "Path", N_("Create a fill object using the selected paths")},
+ {"app.path-simplify", N_("Simplify"), "Path", N_("Simplify selected paths (remove extra nodes)")},
+
+ {"win.path-inset", N_("Inset"), "Path", N_("Inset selected paths")},
+ {"win.path-offset", N_("Offset"), "Path", N_("Offset selected paths")},
+ {"win.path-offset-dynamic", N_("Dynamic Offset"), "Path", N_("Create a dynamic offset object")},
+ {"win.path-offset-linked", N_("Linked Offset"), "Path", N_("Create a dynamic offset object linked to the original path")},
+ {"win.path-reverse", N_("Reverse"), "Path", N_("Reverse the direction of selected paths (useful for flipping markers)")},
+ {"win.path-inset-screen", N_("Inset Screen"), "Path", N_("Inset selected paths by screen pixels")},
+ {"win.path-offset-screen", N_("Offset Screen"), "Path", N_("Offset selected paths by screen pixels")},
+ // clang-format on
+};
+
+void
+add_actions_path(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "path-union", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&object_path_union), app));
+ gapp->add_action( "path-difference", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_path_difference), app));
+ gapp->add_action( "path-intersection", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_path_intersection), app));
+ gapp->add_action( "path-exclusion", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_path_exclusion), app));
+ gapp->add_action( "path-division", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_path_division), app));
+ gapp->add_action( "path-cut", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_path_cut), app));
+ gapp->add_action( "path-combine", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_path_combine), app));
+ gapp->add_action( "path-break-apart", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_path_break_apart), app));
+ gapp->add_action( "path-split", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_path_split), app));
+ gapp->add_action( "path-fill-between-paths", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&fill_between_paths), app));
+ gapp->add_action( "path-simplify", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_path_simplify), app));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_path);
+}
+
+// TODO: Remove desktop dependencies and convert to app actions.
+void
+add_actions_path(InkscapeWindow* win)
+{
+ Glib::VariantType Double(Glib::VARIANT_TYPE_DOUBLE);
+
+ // clang-format off
+ win->add_action( "path-inset", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_path_inset), win));
+ win->add_action( "path-offset", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_path_offset), win));
+ win->add_action_with_parameter( "path-inset-screen", Double, sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_path_inset_screen), win));
+ win->add_action_with_parameter( "path-offset-screen", Double, sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_path_offset_screen), win));
+ win->add_action( "path-offset-dynamic", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_path_offset_dynamic), win));
+ win->add_action( "path-offset-linked", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_path_offset_linked), win));
+ win->add_action( "path-reverse", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_path_reverse), win));
+ // clang-format on
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-paths.h b/src/actions/actions-paths.h
new file mode 100644
index 0000000..f5c7886
--- /dev/null
+++ b/src/actions/actions-paths.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for selection tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_PATH_H
+#define INK_ACTIONS_PATH_H
+
+class InkscapeApplication;
+class InkscapeWindow;
+
+void add_actions_path(InkscapeApplication* app);
+void add_actions_path(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_PATH_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-selection-object.cpp b/src/actions/actions-selection-object.cpp
new file mode 100644
index 0000000..8d254a7
--- /dev/null
+++ b/src/actions/actions-selection-object.cpp
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions related to manipulation a selection of objects which don't require desktop.
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/*
+ * Note: Actions must be app level as different windows can have different selections
+ * and selections must also work from the command line (without GUI).
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-selection-object.h"
+#include "actions-helper.h"
+#include "inkscape-application.h"
+#include "inkscape-window.h" // Anchor
+#include "inkscape.h"
+#include "page-manager.h"
+#include "preferences.h" // Scaling factor
+#include "selection.h"
+
+#include "object/sp-namedview.h"
+
+#include "ui/dialog/dialog-container.h" // Used by select_object_link() to open dialog to add hyperlink.
+#include "ui/icon-names.h"
+
+void
+select_object_group(InkscapeApplication* app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Group
+ selection->group();
+}
+
+void
+select_object_ungroup(InkscapeApplication* app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Ungroup
+ selection->ungroup();
+}
+
+void
+select_object_ungroup_pop(InkscapeApplication* app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Pop Selected Objects out of Group
+ selection->popFromGroup();
+}
+
+void
+select_object_link(InkscapeApplication* app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Group with <a>
+ auto anchor = selection->group(1);
+ selection->set(anchor);
+
+ // Open dialog to set link.
+ if (app->get_active_window()) {
+ app->get_active_window()->get_desktop()->getContainer()->new_dialog("ObjectAttributes");
+ }
+}
+
+void
+selection_top(InkscapeApplication* app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Raise to Top
+ selection->raiseToTop();
+}
+
+void
+selection_raise(InkscapeApplication* app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Raise
+ selection->raise();
+}
+
+void
+selection_lower(InkscapeApplication* app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Lower
+ selection->lower();
+}
+
+void
+selection_bottom(InkscapeApplication* app)
+{
+ Inkscape::Selection *selection = app->get_active_selection();
+
+ // Lower to Bottom
+ selection->lowerToBottom();
+}
+
+void
+selection_stack_up(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->stackUp();
+}
+
+void
+selection_stack_down(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->stackDown();
+}
+
+void
+selection_make_bitmap_copy(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+
+ // Make a Bitmap Copy
+ selection->createBitmapCopy();
+}
+
+void
+page_fit_to_selection(InkscapeApplication *app)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+
+ document->getPageManager().fitToSelection(selection);
+ Inkscape::DocumentUndo::done(document, _("Resize page to fit"), INKSCAPE_ICON("tool-pages"));
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_selection_object =
+{
+ // clang-format off
+ { "app.selection-group", N_("Group"), "Select", N_("Group selected objects")},
+ { "app.selection-ungroup", N_("Ungroup"), "Select", N_("Ungroup selected objects")},
+ { "app.selection-ungroup-pop", N_("Pop Selected Objects out of Group"), "Select", N_("Pop selected objects out of group")},
+ { "app.selection-link", N_("Link"), "Select", N_("Add an anchor to selected objects")},
+
+ { "app.selection-top", N_("Raise to Top"), "Select", N_("Raise selection to top")},
+ { "app.selection-raise", N_("Raise"), "Select", N_("Raise selection one step")},
+ { "app.selection-lower", N_("Lower"), "Select", N_("Lower selection one step")},
+ { "app.selection-bottom", N_("Lower to Bottom"), "Select", N_("Lower selection to bottom")},
+
+ { "app.selection-stack-up", N_("Move up the Stack"), "Select", N_("Move the selection up in the stack order")},
+ { "app.selection-stack-down", N_("Move down the Stack"), "Select", N_("Move the selection down in the stack order")},
+
+ { "app.selection-make-bitmap-copy", N_("Make a Bitmap Copy"), "Select", N_("Export selection to a bitmap and insert it into document")},
+ { "app.page-fit-to-selection", N_("Resize Page to Selection"), "Page", N_("Fit the page to the current selection or the drawing if there is no selection")}
+ // clang-format on
+};
+
+void
+add_actions_selection_object(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ // See actions-layer.cpp for "enter-group" and "exit-group".
+ gapp->add_action( "selection-group", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_object_group), app));
+ gapp->add_action( "selection-ungroup", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_object_ungroup), app));
+ gapp->add_action( "selection-ungroup-pop", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_object_ungroup_pop), app));
+ gapp->add_action( "selection-link", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_object_link), app));
+
+ gapp->add_action( "selection-top", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&selection_top), app));
+ gapp->add_action( "selection-raise", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&selection_raise), app));
+ gapp->add_action( "selection-lower", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&selection_lower), app));
+ gapp->add_action( "selection-bottom", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&selection_bottom), app));
+
+ gapp->add_action( "selection-stack-up", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&selection_stack_up), app));
+ gapp->add_action( "selection-stack-down", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&selection_stack_down), app));
+
+ gapp->add_action( "selection-make-bitmap-copy", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&selection_make_bitmap_copy), app));
+ gapp->add_action( "page-fit-to-selection", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&page_fit_to_selection), app));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_selection_object);
+}
diff --git a/src/actions/actions-selection-object.h b/src/actions/actions-selection-object.h
new file mode 100644
index 0000000..c67b9d2
--- /dev/null
+++ b/src/actions/actions-selection-object.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_SELECTION_OBJECT_H
+#define INK_ACTIONS_SELECTION_OBJECT_H
+
+class InkscapeApplication;
+
+void add_actions_selection_object(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_SELECTION_OBJECT_H \ No newline at end of file
diff --git a/src/actions/actions-selection-window.cpp b/src/actions/actions-selection-window.cpp
new file mode 100644
index 0000000..c847984
--- /dev/null
+++ b/src/actions/actions-selection-window.cpp
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions related to selection which require desktop.
+ *
+ * These are linked to the desktop as they operate differently
+ * depending on if the node tool is in use or not.
+ *
+ * To do: Rewrite select_same_fill_and_stroke to remove desktop dependency.
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-selection-window.h"
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "desktop.h"
+#include "ui/dialog/dialog-container.h"
+#include "actions/actions-tools.h"
+#include "selection-chemistry.h"
+
+void
+select_all(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Select All
+ Inkscape::SelectionHelper::selectAll(dt);
+}
+
+void
+select_all_layers(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Select All in All Layers
+ Inkscape::SelectionHelper::selectAllInAll(dt);
+}
+
+void
+select_same_fill_and_stroke(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Fill and Stroke
+ Inkscape::SelectionHelper::selectSameFillStroke(dt);
+}
+
+void
+select_same_fill(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Fill Color
+ Inkscape::SelectionHelper::selectSameFillColor(dt);
+}
+
+void
+select_same_stroke_color(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Stroke Color
+ Inkscape::SelectionHelper::selectSameStrokeColor(dt);
+}
+
+void
+select_same_stroke_style(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Stroke Style
+ Inkscape::SelectionHelper::selectSameStrokeStyle(dt);
+}
+
+void
+select_same_object_type(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Object Type
+ Inkscape::SelectionHelper::selectSameObjectType(dt);
+}
+
+void
+select_invert(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Invert Selection
+ Inkscape::SelectionHelper::invert(dt);
+}
+
+void
+select_none(InkscapeWindow* win)
+{
+ SPDesktop* dt = win->get_desktop();
+
+ // Deselect
+ Inkscape::SelectionHelper::selectNone(dt);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_selection_dekstop_data =
+{
+ // clang-format off
+ {"win.select-all", N_("Select All"), "Select", N_("Select all objects or all nodes")},
+ {"win.select-all-layers", N_("Select All in All Layers"), "Select", N_("Select all objects in all visible and unlocked layers")},
+ {"win.select-same-fill-and-stroke", N_("Fill and Stroke"), "Select", N_("Select all objects with the same fill and stroke as the selected objects")},
+ {"win.select-same-fill", N_("Fill Color"), "Select", N_("Select all objects with the same fill as the selected objects")},
+ {"win.select-same-stroke-color", N_("Stroke Color"), "Select", N_("Select all objects with the same stroke as the selected objects")},
+ {"win.select-same-stroke-style", N_("Stroke Style"), "Select", N_("Select all objects with the same stroke style (width, dash, markers) as the selected objects")},
+ {"win.select-same-object-type", N_("Object Type"), "Select", N_("Select all objects with the same object type (rect, arc, text, path, bitmap etc) as the selected objects")},
+ {"win.select-invert", N_("Invert Selection"), "Select", N_("Invert selection (unselect what is selected and select everything else)")},
+ {"win.select-none", N_("Deselect"), "Select", N_("Deselect any selected objects or nodes")},
+ // DO NOT ADD select-next or select-previous here as their default keys conflict with Gtk's widget navigation.
+
+ // clang-format on
+};
+
+void
+add_actions_select_window(InkscapeWindow* win)
+{
+ // clang-format off
+ win->add_action( "select-all", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_all), win));
+ win->add_action( "select-all-layers", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_all_layers), win));
+ win->add_action( "select-same-fill-and-stroke", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_same_fill_and_stroke), win));
+ win->add_action( "select-same-fill", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_same_fill), win));
+ win->add_action( "select-same-stroke-color", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_same_stroke_color), win));
+ win->add_action( "select-same-stroke-style", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_same_stroke_style), win));
+ win->add_action( "select-same-object-type", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_same_object_type), win));
+ win->add_action( "select-invert", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_invert), win));
+ win->add_action( "select-none", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&select_none), win));
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_edit: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_selection_dekstop_data);
+}
diff --git a/src/actions/actions-selection-window.h b/src/actions/actions-selection-window.h
new file mode 100644
index 0000000..767406a
--- /dev/null
+++ b/src/actions/actions-selection-window.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_SELECTION_WINDOW_H
+#define INK_ACTIONS_SELECTION_WINDOW_H
+
+class InkscapeWindow;
+
+void add_actions_select_window(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_SELECTION_WINDOW_H \ No newline at end of file
diff --git a/src/actions/actions-selection.cpp b/src/actions/actions-selection.cpp
new file mode 100644
index 0000000..f599224
--- /dev/null
+++ b/src/actions/actions-selection.cpp
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for to change selection, tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-selection.h"
+#include "actions-helper.h"
+#include "inkscape-application.h"
+
+#include "inkscape.h" // Inkscape::Application
+#include "selection.h" // Selection
+
+#include "object/sp-root.h" // select_all: document->getRoot();
+#include "object/sp-item-group.h" // select_all
+
+void
+select_clear(InkscapeApplication* app)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+ selection->clear();
+}
+
+void
+select_by_id(Glib::ustring ids, InkscapeApplication* app)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+
+ auto tokens = Glib::Regex::split_simple("\\s*,\\s*", ids);
+ for (auto id : tokens) {
+ SPObject* object = document->getObjectById(id);
+ if (object) {
+ selection->add(object);
+ } else {
+ std::cerr << "select_by_id: Did not find object with id: " << id << std::endl;
+ }
+ }
+}
+
+void
+unselect_by_id(Glib::ustring ids, InkscapeApplication* app)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+
+ auto tokens = Glib::Regex::split_simple("\\s*,\\s*", ids);
+ for (auto id : tokens) {
+ SPObject* object = document->getObjectById(id);
+ if (object) {
+ selection->remove(object);
+ } else {
+ std::cerr << "unselect_by_id: Did not find object with id: " << id << std::endl;
+ }
+ }
+}
+
+void
+select_by_class(Glib::ustring klass, InkscapeApplication* app)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+
+ auto objects = document->getObjectsByClass(klass);
+ selection->add(objects.begin(), objects.end());
+}
+
+void
+select_by_element(Glib::ustring element, InkscapeApplication* app)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+ auto objects = document->getObjectsByElement(element);
+ selection->add(objects.begin(), objects.end());
+}
+
+void
+select_by_selector(Glib::ustring selector, InkscapeApplication* app)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+
+ auto objects = document->getObjectsBySelector(selector);
+ selection->add(objects.begin(), objects.end());
+}
+
+
+// Helper
+void
+get_all_items_recursive(std::vector<SPObject *> &objects, SPObject *object, Glib::ustring &condition)
+{
+ for (auto &o : object->childList(false)) {
+ if (dynamic_cast<SPItem *>(o)) {
+ SPGroup *group = dynamic_cast<SPGroup *>(o);
+ if (condition == "layers") {
+ if (group && group->layerMode() == SPGroup::LAYER) {
+ objects.emplace_back(o);
+ continue; // Layers cannot contain layers.
+ }
+ } else if (condition == "no-layers") {
+ if (group && group->layerMode() == SPGroup::LAYER) {
+ // recurse one level
+ } else {
+ objects.emplace_back(o);
+ continue;
+ }
+ } else if (condition == "groups") {
+ if (group) {
+ objects.emplace_back(o);
+ }
+ } else if (condition == "all") {
+ objects.emplace_back(o);
+ } else {
+ // no-groups, default
+ if (!group) {
+ objects.emplace_back(o);
+ continue; // Non-groups cannot contain items.
+ }
+ }
+ get_all_items_recursive(objects, o, condition);
+ }
+ }
+}
+
+
+/*
+ * 'layers': All layers.
+ * 'groups': All groups (including layers).
+ * 'no-layers': All top level objects in all layers (matches GUI "Select All in All Layers").
+ * 'no-groups': All objects other than groups (and layers).
+ * 'all': All objects including groups and their descendents.
+ *
+ * Note: GUI "Select All" requires knowledge of selected layer, which is a desktop property.
+ */
+void
+select_all(Glib::ustring condition, InkscapeApplication* app)
+{
+ if (condition != "" && condition != "layers" && condition != "no-layers" &&
+ condition != "groups" && condition != "no-groups" && condition != "all") {
+ std::cerr << "select_all: allowed options are '', 'all', 'layers', 'no-layers', 'groups', and 'no-groups'" << std::endl;
+ return;
+ }
+
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+
+ std::vector<SPObject *> objects;
+ get_all_items_recursive(objects, document->getRoot(), condition);
+
+ selection->setList(objects);
+}
+
+/* See above for conditions. */
+void
+select_invert(Glib::ustring condition, InkscapeApplication* app)
+{
+ if (condition != "" && condition != "layers" && condition != "no-layers" &&
+ condition != "groups" && condition != "no-groups" && condition != "all") {
+ std::cerr << "select_all: allowed options are '', 'all', 'layers', 'no-layers', 'groups', and 'no-groups'" << std::endl;
+ return;
+ }
+
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+
+ // Find all objects that match condition.
+ std::vector<SPObject *> objects;
+ get_all_items_recursive(objects, document->getRoot(), condition);
+
+ // Get current selection.
+ std::vector<SPObject *> current(selection->items().begin(), selection->items().end());
+
+ // Remove current selection from object vector (using "erase remove_if idiom").
+ objects.erase(
+ std::remove_if(std::begin(objects), std::end(objects), [&current](const SPObject *x)
+ {
+ return (std::find(current.begin(), current.end(), x) != current.end());
+ }), objects.end());
+
+ // Set selection to object vector.
+ selection->setList(objects);
+}
+
+
+// Debug... print selected items
+void
+select_list(InkscapeApplication* app)
+{
+ SPDocument* document = nullptr;
+ Inkscape::Selection* selection = nullptr;
+ if (!get_document_and_selection(app, &document, &selection)) {
+ return;
+ }
+
+ auto items = selection->items();
+ for (auto i = items.begin(); i != items.end(); ++i) {
+ std::cout << **i << std::endl;
+ }
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_selection =
+{
+ // clang-format offs
+ {"app.select-clear", N_("Clear Selection"), "Select", N_("Clear selection")},
+ {"app.select", N_("Select"), "Select", N_("Select by ID (deprecated)")},
+ {"app.unselect", N_("Deselect"), "Select", N_("Deselect by ID (deprecated)")},
+ {"app.select-by-id", N_("Select by ID"), "Select", N_("Select by ID")},
+ {"app.unselect-by-id", N_("Deselect by ID"), "Select", N_("Deselect by ID")},
+ {"app.select-by-class", N_("Select by Class"), "Select", N_("Select by class")},
+ {"app.select-by-element", N_("Select by Element"), "Select", N_("Select by SVG element (e.g. 'rect')")},
+ {"app.select-by-selector", N_("Select by Selector"), "Select", N_("Select by CSS selector")},
+ {"app.select-all", N_("Select All Objects"), "Select", N_("Select all; options: 'all' (every object including groups), 'layers', 'no-layers' (top level objects in layers), 'groups' (all groups including layers), 'no-groups' (all objects other than groups and layers, default)")},
+ {"app.select-invert", N_("Invert Selection"), "Select", N_("Invert selection; options: 'all', 'layers', 'no-layers', 'groups', 'no-groups' (default)")},
+ {"app.select-list", N_("List Selection"), "Select", N_("Print a list of objects in current selection")},
+ // clang-format on
+};
+
+void
+add_actions_selection(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "select-clear", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_clear), app) );
+ gapp->add_action_radio_string( "select", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_by_id), app), "null"); // Backwards compatible.
+ gapp->add_action_radio_string( "unselect", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&unselect_by_id), app), "null"); // Match select.
+ gapp->add_action_radio_string( "select-by-id", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_by_id), app), "null");
+ gapp->add_action_radio_string( "unselect-by-id", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&unselect_by_id), app), "null");
+ gapp->add_action_radio_string( "select-by-class", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_by_class), app), "null");
+ gapp->add_action_radio_string( "select-by-element", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_by_element), app), "null");
+ gapp->add_action_radio_string( "select-by-selector", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_by_selector), app), "null");
+ gapp->add_action_radio_string( "select-all", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_all), app), "null");
+ gapp->add_action_radio_string( "select-invert", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_invert), app), "null");
+ gapp->add_action( "select-list", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&select_list), app) );
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_selection);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-selection.h b/src/actions/actions-selection.h
new file mode 100644
index 0000000..1d5a311
--- /dev/null
+++ b/src/actions/actions-selection.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for selection tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_SELECTION_H
+#define INK_ACTIONS_SELECTION_H
+
+class InkscapeApplication;
+
+void add_actions_selection(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_SELECTION_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-text.cpp b/src/actions/actions-text.cpp
new file mode 100644
index 0000000..2ba72b7
--- /dev/null
+++ b/src/actions/actions-text.cpp
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions Related to Text
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-text.h"
+#include "actions/actions-extra-data.h"
+#include "inkscape-application.h"
+#include "text-chemistry.h"
+
+void
+selection_text_put_on_path()
+{
+ text_put_on_path();
+}
+
+void
+selection_text_remove_from_path()
+{
+ text_remove_from_path();
+}
+
+void
+text_flow_into_frame()
+{
+ text_flow_into_shape();
+}
+
+void
+text_flow_subtract_frame()
+{
+ text_flow_shape_subtract();
+}
+
+void
+select_text_unflow()
+{
+ text_unflow();
+}
+
+void
+text_convert_to_regular()
+{
+ flowtext_to_text();
+}
+
+void
+text_unkern()
+{
+ text_remove_all_kerns();
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_text =
+{
+ // clang-format off
+ {"app.text-put-on-path", N_("Put on Path"), "Text", N_("Put text on path")},
+ {"app.text-remove-from-path", N_("Remove from Path"), "Text", N_("Remove text from path")},
+ {"app.text-flow-into-frame", N_("Flow into Frame"), "Text", N_("Put text into a frame (path or shape), creating a flowed text linked to the frame object")},
+ {"app.text-flow-subtract-frame", N_("Set Subtraction Frames"), "Text", N_("Flow text around a frame (path or shape), only available for SVG 2.0 Flow text.")},
+ {"app.text-unflow", N_("Unflow"), "Text", N_("Remove text from frame (creates a single-line text object)")},
+ {"app.text-convert-to-regular", N_("Convert to Text"), "Text", N_("Convert flowed text to regular text object (preserves appearance)")},
+ {"app.text-unkern", N_("Remove Manual Kerns"), "Text", N_("Remove all manual kerns and glyph rotations from a text object")}
+ // clang-format on
+};
+
+void
+add_actions_text(InkscapeApplication* app)
+{
+ auto gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "text-put-on-path", sigc::ptr_fun(selection_text_put_on_path));
+ gapp->add_action( "text-remove-from-path", sigc::ptr_fun(selection_text_remove_from_path));
+ gapp->add_action( "text-flow-into-frame", sigc::ptr_fun(text_flow_into_frame));
+ gapp->add_action( "text-flow-subtract-frame", sigc::ptr_fun(text_flow_subtract_frame));
+ gapp->add_action( "text-unflow", sigc::ptr_fun(select_text_unflow));
+ gapp->add_action( "text-convert-to-regular", sigc::ptr_fun(text_convert_to_regular));
+ gapp->add_action( "text-unkern", sigc::ptr_fun(text_unkern));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_text);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-text.h b/src/actions/actions-text.h
new file mode 100644
index 0000000..226bb8c
--- /dev/null
+++ b/src/actions/actions-text.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_TEXT_H
+#define INK_ACTIONS_TEXT_H
+
+class InkscapeApplication;
+
+void add_actions_text(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_TEXT_H
diff --git a/src/actions/actions-tools.cpp b/src/actions/actions-tools.cpp
new file mode 100644
index 0000000..c6214c7
--- /dev/null
+++ b/src/actions/actions-tools.cpp
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for switching tools.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+#include <map>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-tools.h"
+
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "inkscape.h"
+#include "message-context.h"
+
+#include "object/box3d.h"
+#include "object/sp-ellipse.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-offset.h"
+#include "object/sp-path.h"
+#include "object/sp-rect.h"
+#include "object/sp-spiral.h"
+#include "object/sp-star.h"
+#include "object/sp-text.h"
+#include "object/sp-marker.h"
+
+#include "ui/dialog/dialog-container.h"
+#include "ui/dialog/dialog-manager.h"
+#include "ui/dialog/inkscape-preferences.h"
+#include "ui/tools/connector-tool.h"
+#include "ui/tools/text-tool.h"
+
+class ToolData {
+public:
+ int tool = TOOLS_INVALID; // TODO: Switch to named enum
+ int pref = TOOLS_INVALID;
+ Glib::ustring pref_path;
+};
+
+static std::map<Glib::ustring, ToolData> const &get_tool_data()
+{
+ static std::map<Glib::ustring, ToolData> tool_data;
+
+ if (tool_data.empty()) {
+ tool_data = {
+ // clang-format off
+ {"Select", {TOOLS_SELECT, PREFS_PAGE_TOOLS_SELECTOR, "/tools/select", }},
+ {"Node", {TOOLS_NODES, PREFS_PAGE_TOOLS_NODE, "/tools/nodes", }},
+ {"Marker", {TOOLS_MARKER, PREFS_PAGE_TOOLS,/*No Page*/ "/tools/marker", }},
+ {"Rect", {TOOLS_SHAPES_RECT, PREFS_PAGE_TOOLS_SHAPES_RECT, "/tools/shapes/rect", }},
+ {"Arc", {TOOLS_SHAPES_ARC, PREFS_PAGE_TOOLS_SHAPES_ELLIPSE, "/tools/shapes/arc", }},
+ {"Star", {TOOLS_SHAPES_STAR, PREFS_PAGE_TOOLS_SHAPES_STAR, "/tools/shapes/star", }},
+ {"3DBox", {TOOLS_SHAPES_3DBOX, PREFS_PAGE_TOOLS_SHAPES_3DBOX, "/tools/shapes/3dbox", }},
+ {"Spiral", {TOOLS_SHAPES_SPIRAL, PREFS_PAGE_TOOLS_SHAPES_SPIRAL, "/tools/shapes/spiral", }},
+ {"Pencil", {TOOLS_FREEHAND_PENCIL, PREFS_PAGE_TOOLS_PENCIL, "/tools/freehand/pencil", }},
+ {"Pen", {TOOLS_FREEHAND_PEN, PREFS_PAGE_TOOLS_PEN, "/tools/freehand/pen", }},
+ {"Calligraphic", {TOOLS_CALLIGRAPHIC, PREFS_PAGE_TOOLS_CALLIGRAPHY, "/tools/calligraphic", }},
+ {"Text", {TOOLS_TEXT, PREFS_PAGE_TOOLS_TEXT, "/tools/text", }},
+ {"Gradient", {TOOLS_GRADIENT, PREFS_PAGE_TOOLS_GRADIENT, "/tools/gradient", }},
+ {"Mesh", {TOOLS_MESH, PREFS_PAGE_TOOLS, /* No Page */ "/tools/mesh", }},
+ {"Zoom", {TOOLS_ZOOM, PREFS_PAGE_TOOLS_ZOOM, "/tools/zoom", }},
+ {"Measure", {TOOLS_MEASURE, PREFS_PAGE_TOOLS_MEASURE, "/tools/measure", }},
+ {"Dropper", {TOOLS_DROPPER, PREFS_PAGE_TOOLS_DROPPER, "/tools/dropper", }},
+ {"Tweak", {TOOLS_TWEAK, PREFS_PAGE_TOOLS_TWEAK, "/tools/tweak", }},
+ {"Spray", {TOOLS_SPRAY, PREFS_PAGE_TOOLS_SPRAY, "/tools/spray", }},
+ {"Connector", {TOOLS_CONNECTOR, PREFS_PAGE_TOOLS_CONNECTOR, "/tools/connector", }},
+ {"PaintBucket", {TOOLS_PAINTBUCKET, PREFS_PAGE_TOOLS_PAINTBUCKET, "/tools/paintbucket", }},
+ {"Eraser", {TOOLS_ERASER, PREFS_PAGE_TOOLS_ERASER, "/tools/eraser", }},
+ {"LPETool", {TOOLS_LPETOOL, PREFS_PAGE_TOOLS, /* No Page */ "/tools/lpetool", }},
+ {"Pages", {TOOLS_PAGES, PREFS_PAGE_TOOLS, "/tools/pages", }}
+ // clang-format on
+ };
+ }
+ return tool_data;
+}
+
+static std::map<Glib::ustring, Glib::ustring> const &get_tool_msg()
+{
+ static std::map<Glib::ustring, Glib::ustring> tool_msg;
+
+ if (tool_msg.empty()) {
+ tool_msg = {
+ // clang-format off
+ {"Select", _("<b>Click</b> to Select and Transform objects, <b>Drag</b> to select many objects.") },
+ {"Node", _("Modify selected path points (nodes) directly.") },
+ {"Rect", _("<b>Drag</b> to create a rectangle. <b>Drag controls</b> to round corners and resize. <b>Click</b> to select.") },
+ {"Arc", _("<b>Drag</b> to create an ellipse. <b>Drag controls</b> to make an arc or segment. <b>Click</b> to select.") },
+ {"Star", _("<b>Drag</b> to create a star. <b>Drag controls</b> to edit the star shape. <b>Click</b> to select.") },
+ {"3DBox", _("<b>Drag</b> to create a 3D box. <b>Drag controls</b> to resize in perspective. <b>Click</b> to select (with <b>Ctrl+Alt</b> for single faces).") },
+ {"Spiral", _("<b>Drag</b> to create a spiral. <b>Drag controls</b> to edit the spiral shape. <b>Click</b> to select.") },
+ {"Marker", _("<b>Click</b> a shape to start editing its markers. <b>Drag controls</b> to change orientation, scale, and position.") },
+ {"Pencil", _("<b>Drag</b> to create a freehand line. <b>Shift</b> appends to selected path, <b>Alt</b> activates sketch mode.") },
+ {"Pen", _("<b>Click</b> or <b>click and drag</b> to start a path; with <b>Shift</b> to append to selected path. <b>Ctrl+click</b> to create single dots (straight line modes only).") },
+ {"Calligraphic", _("<b>Drag</b> to draw a calligraphic stroke; with <b>Ctrl</b> to track a guide path. <b>Arrow keys</b> adjust width (left/right) and angle (up/down).") },
+ {"Text", _("<b>Click</b> to select or create text, <b>drag</b> to create flowed text; then type.") },
+ {"Gradient", _("<b>Drag</b> or <b>double click</b> to create a gradient on selected objects, <b>drag handles</b> to adjust gradients.") },
+ {"Mesh", _("<b>Drag</b> or <b>double click</b> to create a mesh on selected objects, <b>drag handles</b> to adjust meshes.") },
+ {"Zoom", _("<b>Click</b> or <b>drag around an area</b> to zoom in, <b>Shift+click</b> to zoom out.") },
+ {"Measure", _("<b>Drag</b> to measure the dimensions of objects.") },
+ {"Dropper", _("<b>Click</b> to set fill, <b>Shift+click</b> to set stroke; <b>drag</b> to average color in area; with <b>Alt</b> to pick inverse color; <b>Ctrl+C</b> to copy the color under mouse to clipboard") },
+ {"Tweak", _("To tweak a path by pushing, select it and drag over it.") },
+ {"Spray", _("<b>Drag</b>, <b>click</b> or <b>click and scroll</b> to spray the selected objects.") },
+ {"Connector", _("<b>Click and drag</b> between shapes to create a connector.") },
+ {"PaintBucket", _("<b>Click</b> to paint a bounded area, <b>Shift+click</b> to union the new fill with the current selection, <b>Ctrl+click</b> to change the clicked object's fill and stroke to the current setting.") },
+ {"Eraser", _("<b>Drag</b> to erase.") },
+ {"LPETool", _("Choose a subtool from the toolbar") },
+ {"Pages", _("Create and manage pages.")}
+ // clang-format on
+ };
+ }
+ return tool_msg;
+}
+
+Glib::ustring
+get_active_tool(InkscapeWindow *win)
+{
+ Glib::ustring state;
+
+ auto action = win->lookup_action("tool-switch");
+ if (!action) {
+ std::cerr << "git_active_tool: action 'tool-switch' missing!" << std::endl;
+ return state;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "git_active_tool: action 'tool-switch' not SimpleAction!" << std::endl;
+ return state;
+ }
+
+ saction->get_state(state);
+
+ return state;
+}
+
+int
+get_active_tool_enum(InkscapeWindow *win)
+{
+ return get_tool_data().at(get_active_tool(win)).tool;
+}
+
+void tool_switch(Glib::ustring const &tool, InkscapeWindow *win);
+
+void
+set_active_tool(InkscapeWindow *win, Glib::ustring const &tool)
+{
+ // Seems silly to have a function to just flip argument order... but it's consistent with other
+ // external functions.
+ tool_switch(tool, win);
+}
+
+void
+open_tool_preferences(InkscapeWindow *win, Glib::ustring const &tool)
+{
+ tool_preferences(tool, win);
+}
+
+/**
+ * Set tool to appropriate one to edit 'item'.
+ */
+void
+set_active_tool(InkscapeWindow *win, SPItem *item, Geom::Point const p)
+{
+ if (dynamic_cast<SPRect *>(item)) {
+ tool_switch("Rect", win);
+ } else if (dynamic_cast<SPGenericEllipse *>(item)) {
+ tool_switch("Arc", win);
+ } else if (dynamic_cast<SPStar *>(item)) {
+ tool_switch("Star", win);
+ } else if (dynamic_cast<SPBox3D *>(item)) {
+ tool_switch("3DBox", win);
+ } else if (dynamic_cast<SPSpiral *>(item)) {
+ tool_switch("Spiral", win);
+ } else if (dynamic_cast<SPMarker *>(item)) {
+ tool_switch("Marker", win);
+ } else if (dynamic_cast<SPPath *>(item)) {
+ if (Inkscape::UI::Tools::cc_item_is_connector(item)) {
+ tool_switch("Connector", win);
+ }
+ else {
+ tool_switch("Node", win);
+ }
+ } else if (dynamic_cast<SPText *>(item) || dynamic_cast<SPFlowtext *>(item)) {
+ tool_switch("Text", win);
+ SPDesktop* dt = win->get_desktop();
+ if (!dt) {
+ std::cerr << "set_active_tool: no desktop!" << std::endl;
+ return;
+ }
+ sp_text_context_place_cursor_at (SP_TEXT_CONTEXT(dt->event_context), item, p);
+ } else if (dynamic_cast<SPOffset *>(item)) {
+ tool_switch("Node", win);
+ }
+}
+
+/**
+ * Set display mode. Callback for 'tool-switch' action.
+ */
+void
+tool_switch(Glib::ustring const &tool, InkscapeWindow *win)
+{
+ auto const &tool_data = get_tool_data();
+ // Valid tool?
+ auto tool_it = tool_data.find(tool);
+ if (tool_it == tool_data.end()) {
+ std::cerr << "tool-switch: invalid tool name: " << tool << std::endl;
+ return;
+ }
+
+ // Have desktop?
+ SPDesktop* dt = win->get_desktop();
+ if (!dt) {
+ std::cerr << "tool_switch: no desktop!" << std::endl;
+ return;
+ }
+
+ auto action = win->lookup_action("tool-switch");
+ if (!action) {
+ std::cerr << "tool-switch: action 'tool-switch' missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "tool-switch: action 'tool-switch' not SimpleAction!" << std::endl;
+ return;
+ }
+
+ // Update button states.
+ saction->set_enabled(false); // Avoid infinite loop when called by tool_toogle().
+ saction->change_state(tool);
+ saction->set_enabled(true);
+
+ // Switch to new tool. TODO: Clean this up. This should be one window function.
+ // Setting tool via preference path is a bit strange.
+ dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, get_tool_msg().at(tool).c_str());
+ dt->setEventContext(tool_data.at(tool).pref_path);
+}
+
+/**
+ * Open preferences page for tool. Could be turned into actions if need be.
+ */
+void
+tool_preferences(Glib::ustring const &tool, InkscapeWindow *win)
+{
+ auto const &tool_data = get_tool_data();
+ // Valid tool?
+ auto tool_it = tool_data.find(tool);
+ if (tool_it == tool_data.end()) {
+ std::cerr << "tool-preferences: invalid tool name: " << tool << std::endl;
+ return;
+ }
+
+ // Have desktop?
+ SPDesktop* dt = win->get_desktop();
+ if (!dt) {
+ std::cerr << "tool-preferences: no desktop!" << std::endl;
+ return;
+ }
+
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setInt("/dialogs/preferences/page", tool_it->second.pref);
+ Inkscape::UI::Dialog::DialogContainer* container = dt->getContainer();
+
+ // Create dialog if it doesn't exist (also sets page if dialog not already in opened tab).
+ container->new_floating_dialog("Preferences");
+
+ // Find dialog and explicitly set page (in case not set in previous line).
+ auto dialog = Inkscape::UI::Dialog::DialogManager::singleton().find_floating_dialog("Preferences");
+ if (dialog) {
+ auto pref_dialog = dynamic_cast<Inkscape::UI::Dialog::InkscapePreferences *>(dialog);
+ if (pref_dialog) {
+ pref_dialog->showPage(); // Switch to page indicated in preferences file (set above).
+ }
+ }
+}
+
+/**
+ * Toggle between "Selector" and last used tool.
+ */
+void
+tool_toggle(InkscapeWindow *win)
+{
+ SPDesktop* dt = win->get_desktop();
+ if (!dt) {
+ std::cerr << "tool_toggle: no desktop!" << std::endl;
+ return;
+ }
+
+ auto action = win->lookup_action("tool-switch");
+ if (!action) {
+ std::cerr << "tool_toggle: action 'tool_switch' missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "tool_toogle: action 'tool_switch' not SimpleAction!" << std::endl;
+ return;
+ }
+
+ static Glib::ustring old_tool = "Select";
+
+ Glib::ustring tool;
+ saction->get_state(tool);
+ if (tool == "Select") {
+ tool = old_tool;
+ } else {
+ old_tool = tool;
+ tool = "Select";
+ }
+
+ tool_switch(tool, win);
+}
+
+Glib::ustring get_active_tool(SPDesktop *desktop)
+{
+ InkscapeWindow* win = desktop->getInkscapeWindow();
+ return get_active_tool(win);
+}
+
+int get_active_tool_enum(SPDesktop *desktop)
+{
+ InkscapeWindow* win = desktop->getInkscapeWindow();
+ return get_active_tool_enum(win);
+}
+
+void set_active_tool(SPDesktop *desktop, Glib::ustring const &tool)
+{
+ InkscapeWindow* win = desktop->getInkscapeWindow();
+ set_active_tool(win, tool);
+}
+
+void set_active_tool(SPDesktop *desktop, SPItem *item, Geom::Point const p)
+{
+ InkscapeWindow* win = desktop->getInkscapeWindow();
+ set_active_tool(win, item, p);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_tools =
+{
+ // clang-format off
+ {"win.tool-switch('Select')", N_("Select Tool"), "Tool Switch", N_("Select and transform objects") },
+ {"win.tool-switch('Node')", N_("Node Tool"), "Tool Switch", N_("Edit paths by nodes") },
+
+ {"win.tool-switch('Rect')", N_("Rectangle Tool"), "Tool Switch", N_("Create rectangles and squares") },
+ {"win.tool-switch('Arc')", N_("Ellipse/Arc Tool"), "Tool Switch", N_("Create circles, ellipses and arcs") },
+ {"win.tool-switch('Star')", N_("Star/Polygon Tool"), "Tool Switch", N_("Create stars and polygons") },
+ {"win.tool-switch('3DBox')", N_("3D Box Tool"), "Tool Switch", N_("Create 3D Boxes") },
+ {"win.tool-switch('Spiral')", N_("Spiral Tool"), "Tool Switch", N_("Create spirals") },
+ {"win.tool-switch('Marker')", N_("Marker Tool"), "Tool Switch", N_("Edit markers") },
+
+ {"win.tool-switch('Pen')", N_("Pen Tool"), "Tool Switch", N_("Draw Bezier curves and straight lines") },
+ {"win.tool-switch('Pencil')", N_("Pencil Tool"), "Tool Switch", N_("Draw freehand lines") },
+ {"win.tool-switch('Calligraphic')", N_("Calligraphy Tool"), "Tool Switch", N_("Draw calligraphic or brush strokes") },
+ {"win.tool-switch('Text')", N_("Text Tool"), "Tool Switch", N_("Create and edit text objects") },
+
+ {"win.tool-switch('Gradient')", N_("Gradient Tool"), "Tool Switch", N_("Create and edit gradients") },
+ {"win.tool-switch('Mesh')", N_("Mesh Tool"), "Tool Switch", N_("Create and edit meshes") },
+ {"win.tool-switch('Dropper')", N_("Dropper Tool"), "Tool Switch", N_("Pick colors from image") },
+ {"win.tool-switch('PaintBucket')", N_("Paint Bucket Tool"), "Tool Switch", N_("Fill bounded areas") },
+
+ {"win.tool-switch('Tweak')", N_("Tweak Tool"), "Tool Switch", N_("Tweak objects by sculpting or painting") },
+ {"win.tool-switch('Spray')", N_("Spray Tool"), "Tool Switch", N_("Spray copies or clones of objects") },
+ {"win.tool-switch('Eraser')", N_("Eraser Tool"), "Tool Switch", N_("Erase objects or paths") },
+ {"win.tool-switch('Connector')", N_("Connector Tool"), "Tool Switch", N_("Create diagram connectors") },
+ {"win.tool-switch('LPETool')", N_("LPE Tool"), "Tool Switch", N_("Do geometric constructions") },
+
+ {"win.tool-switch('Zoom')", N_("Zoom Tool"), "Tool Switch", N_("Zoom in or out") },
+ {"win.tool-switch('Measure')", N_("Measure Tool"), "Tool Switch", N_("Measure objects") },
+ {"win.tool-switch('Pages')", N_("Pages Tool"), "Tool Switch", N_("Create and edit document pages") },
+
+ {"win.tool-toggle", N_("Toggle Tool"), "Tool Switch", N_("Toggle between Select tool and last used tool") },
+ // clang-format on
+};
+
+
+void
+add_actions_tools(InkscapeWindow* win)
+{
+ // clang-format off
+ win->add_action_radio_string ( "tool-switch", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&tool_switch), win), "Select");
+ win->add_action( "tool-toggle", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&tool_toggle), win) );
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_tools: no app!" << std::endl;
+ return;
+ }
+
+ app->get_action_extra_data().add_data(raw_data_tools);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-tools.h b/src/actions/actions-tools.h
new file mode 100644
index 0000000..2e07421
--- /dev/null
+++ b/src/actions/actions-tools.h
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for switching tools. Also includes functions to set and get active tool.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_TOOLS_H
+#define INK_ACTIONS_TOOLS_H
+
+#include <glibmm.h>
+#include <2geom/point.h>
+
+enum tools_enum {
+ TOOLS_INVALID,
+ TOOLS_SELECT,
+ TOOLS_NODES,
+ TOOLS_MARKER,
+ TOOLS_TWEAK,
+ TOOLS_SPRAY,
+ TOOLS_SHAPES_RECT,
+ TOOLS_SHAPES_3DBOX,
+ TOOLS_SHAPES_ARC,
+ TOOLS_SHAPES_STAR,
+ TOOLS_SHAPES_SPIRAL,
+ TOOLS_FREEHAND_PENCIL,
+ TOOLS_FREEHAND_PEN,
+ TOOLS_CALLIGRAPHIC,
+ TOOLS_TEXT,
+ TOOLS_GRADIENT,
+ TOOLS_MESH,
+ TOOLS_ZOOM,
+ TOOLS_MEASURE,
+ TOOLS_DROPPER,
+ TOOLS_CONNECTOR,
+ TOOLS_PAINTBUCKET,
+ TOOLS_ERASER,
+ TOOLS_LPETOOL,
+ TOOLS_PAGES
+};
+
+class InkscapeWindow;
+class SPDesktop;
+class SPItem;
+
+Glib::ustring get_active_tool(InkscapeWindow *win);
+int get_active_tool_enum(InkscapeWindow *win);
+
+void set_active_tool(InkscapeWindow* win, Glib::ustring const &tool);
+void set_active_tool(InkscapeWindow* win, SPItem *item, Geom::Point const p);
+
+void open_tool_preferences(InkscapeWindow* win, Glib::ustring const &tool);
+
+// Deprecated: Long term goal to remove SPDesktop.
+Glib::ustring get_active_tool(SPDesktop *desktop);
+int get_active_tool_enum(SPDesktop *desktop);
+
+void set_active_tool(SPDesktop *desktop, Glib::ustring const &tool);
+void set_active_tool(SPDesktop *desktop, SPItem *item, Geom::Point const p);
+
+void tool_preferences(Glib::ustring const &tool, InkscapeWindow *win);
+
+// Standard function to add actions.
+void add_actions_tools(InkscapeWindow* win);
+
+
+#endif // INK_ACTIONS_TOOLS_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-transform.cpp b/src/actions/actions-transform.cpp
new file mode 100644
index 0000000..c97c202
--- /dev/null
+++ b/src/actions/actions-transform.cpp
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for selection tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-transform.h"
+#include "actions-helper.h"
+#include "document-undo.h"
+#include "inkscape-application.h"
+
+#include "inkscape.h" // Inkscape::Application
+#include "selection.h" // Selection
+
+void
+transform_translate(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
+
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", s.get());
+ if (tokens.size() != 2) {
+ std::cerr << "action:transform_translate: requires two comma separated numbers" << std::endl;
+ return;
+ }
+ double dx = 0;
+ double dy = 0;
+
+ try {
+ dx = std::stod(tokens[0]);
+ dy = std::stod(tokens[1]);
+ } catch (...) {
+ std::cerr << "action:transform-move: invalid arguments" << std::endl;
+ return;
+ }
+
+ auto selection = app->get_active_selection();
+ selection->move(dx, dy);
+
+ // Needed to update repr (is this the best way?).
+ Inkscape::DocumentUndo::done(app->get_active_document(), "ActionTransformTranslate", "");
+}
+
+void
+transform_rotate(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<double> d = Glib::VariantBase::cast_dynamic<Glib::Variant<double> >(value);
+ auto selection = app->get_active_selection();
+
+ selection->rotate(d.get());
+
+ // Needed to update repr (is this the best way?).
+ Inkscape::DocumentUndo::done(app->get_active_document(), "ActionTransformRotate", "");
+}
+
+void
+transform_scale(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<double> d = Glib::VariantBase::cast_dynamic<Glib::Variant<double> >(value);
+ auto selection = app->get_active_selection();
+ selection->scale(d.get());
+
+ // Needed to update repr (is this the best way?).
+ Inkscape::DocumentUndo::done(app->get_active_document(), "ActionTransformScale", "");
+}
+
+void
+transform_grow(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<double> d = Glib::VariantBase::cast_dynamic<Glib::Variant<double> >(value);
+ auto selection = app->get_active_selection();
+ selection->scaleGrow(d.get());
+}
+
+void
+transform_grow_step(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<double> d = Glib::VariantBase::cast_dynamic<Glib::Variant<double> >(value);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ auto selection = app->get_active_selection();
+ selection->scaleGrow(d.get() * prefs->getDoubleLimited("/options/defaultscale/value", 2, 0, 1000));
+}
+
+void
+transform_grow_screen(const Glib::VariantBase& value, InkscapeApplication *app)
+{
+ Glib::Variant<double> d = Glib::VariantBase::cast_dynamic<Glib::Variant<double> >(value);
+ auto selection = app->get_active_selection();
+ selection->scaleScreen(d.get());
+}
+
+void
+transform_remove(InkscapeApplication *app)
+{
+ auto selection = app->get_active_selection();
+ selection->removeTransform();
+
+ // Needed to update repr (is this the best way?).
+ Inkscape::DocumentUndo::done(app->get_active_document(), "ActionTransformRemoveTransform", "");
+}
+
+// SHOULD REALLY BE DOC LEVEL ACTIONS
+std::vector<std::vector<Glib::ustring>> raw_data_transform =
+{
+ // clang-format off
+ {"app.transform-translate", N_("Translate"), "Transform", N_("Translate selected objects (dx,dy)")},
+ {"app.transform-rotate", N_("Rotate"), "Transform", N_("Rotate selected objects by degrees")},
+ {"app.transform-scale", N_("Scale"), "Transform", N_("Scale selected objects by scale factor")},
+ {"app.transform-grow", N_("Grow/Shrink"), "Transform", N_("Grow/shrink selected objects")},
+ {"app.transform-grow-step", N_("Grow/Shrink Step"), "Transform", N_("Grow/shrink selected objects by multiple of step value")},
+ {"app.transform-grow-screen", N_("Grow/Shrink Screen"), "Transform", N_("Grow/shrink selected objects relative to zoom level")},
+ {"app.transform-remove", N_("Remove Transforms"), "Transform", N_("Remove any transforms from selected objects")},
+ // clang-format on
+};
+
+std::vector<std::vector<Glib::ustring>> hint_data_transform =
+{
+ // clang-format off
+ {"app.transform-translate", N_("Enter two comma-separated numbers, e.g. 50,-2.5")},
+ {"app.transform-rotate", N_("Enter angle (in degrees) for clockwise rotation")},
+ {"app.transform-scale", N_("Enter scaling factor, e.g. 1.5")},
+ {"app.transform-grow", N_("Enter positive or negative number to grow/shrink selection")},
+ {"app.transform-grow-step", N_("Enter positive or negative number to grow or shrink selection relative to preference step value")},
+ {"app.transform-grow-screen", N_("Enter positive or negative number to grow or shrink selection relative to zoom level")},
+ // clang-format on
+};
+
+void
+add_actions_transform(InkscapeApplication* app)
+{
+ Glib::VariantType Bool( Glib::VARIANT_TYPE_BOOL);
+ Glib::VariantType Int( Glib::VARIANT_TYPE_INT32);
+ Glib::VariantType Double(Glib::VARIANT_TYPE_DOUBLE);
+ Glib::VariantType String(Glib::VARIANT_TYPE_STRING);
+
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action_with_parameter( "transform-translate", String, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&transform_translate), app));
+ gapp->add_action_with_parameter( "transform-rotate", Double, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&transform_rotate), app));
+ gapp->add_action_with_parameter( "transform-scale", Double, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&transform_scale), app));
+ gapp->add_action_with_parameter( "transform-grow", Double, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&transform_grow), app));
+ gapp->add_action_with_parameter( "transform-grow-step", Double, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&transform_grow_step), app));
+ gapp->add_action_with_parameter( "transform-grow-screen", Double, sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&transform_grow_screen), app));
+ gapp->add_action( "transform-remove", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&transform_remove), app));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_transform);
+ app->get_action_hint_data().add_data(hint_data_transform);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-transform.h b/src/actions/actions-transform.h
new file mode 100644
index 0000000..9b1b0dc
--- /dev/null
+++ b/src/actions/actions-transform.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for selection tied to the application and without GUI.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_TRANSFORM_H
+#define INK_ACTIONS_TRANSFORM_H
+
+class InkscapeApplication;
+
+void add_actions_transform(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_TRANSFORM_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-tutorial.cpp b/src/actions/actions-tutorial.cpp
new file mode 100644
index 0000000..30e519f
--- /dev/null
+++ b/src/actions/actions-tutorial.cpp
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Tutorial Actions
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-tutorial.h"
+#include "actions/actions-extra-data.h"
+#include "document.h"
+#include "inkscape-application.h"
+#include "ui/interface.h"
+#include "ui/dialog/about.h"
+#include "io/resource.h"
+
+using Inkscape::IO::Resource::UIS;
+
+
+void help_about()
+{
+ Inkscape::UI::Dialog::AboutDialog::show_about();
+}
+
+void help_open_tutorial(Glib::ustring name)
+{
+ Glib::ustring filename = name + ".svg";
+
+ filename = Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::TUTORIALS, filename.c_str(), true);
+ if (!filename.empty()) {
+ auto *app = InkscapeApplication::instance();
+ SPDocument* doc = app->document_new(filename);
+ app->window_open(doc);
+ } else {
+ // TRANSLATORS: Please don't translate link unless the page exists in your language. Add your language code to
+ // the link this way: https://inkscape.org/[lang]/learn/tutorials/
+ sp_ui_error_dialog(_("The tutorial files are not installed.\nFor Linux, you may need to install "
+ "'inkscape-tutorials'; for Windows, please re-run the setup and select 'Tutorials'.\nThe "
+ "tutorials can also be found online at https://inkscape.org/en/learn/tutorials/"));
+ }
+}
+
+void
+help_about_inkscape()
+{
+ help_about();
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_tutorial =
+{
+ // clang-format off
+ { "app.tutorial-basic", N_("Inkscape: Basic"), "Tutorial", N_("Getting started with Inkscape")},
+ { "app.tutorial-shapes", N_("Inkscape: Shapes"), "Tutorial", N_("Using shape tools to create and edit shapes")},
+ { "app.tutorial-advanced", N_("Inkscape: Advanced"), "Tutorial", N_("Advanced Inkscape topics")},
+ { "app.tutorial-tracing", N_("Inkscape: Tracing"), "Tutorial", N_("Using bitmap tracing"),},
+ { "app.tutorial-tracing-pixelart", N_("Inkscape: Tracing Pixel Art"), "Tutorial", N_("Using Trace Pixel Art dialog")},
+ { "app.tutorial-calligraphy", N_("Inkscape: Calligraphy"), "Tutorial", N_("Using the Calligraphy pen tool")},
+ { "app.tutorial-interpolate", N_("Inkscape: Interpolate"), "Tutorial", N_("Using the interpolate extension")},
+ { "app.tutorial-design", N_("Elements of Design"), "Tutorial", N_("Principles of design in the tutorial form")},
+ { "app.tutorial-tips", N_("Tips and Tricks"), "Tutorial", N_("Miscellaneous tips and tricks")},
+ { "app.about", N_("About Inkscape"), "Tutorial", N_("Inkscape version, authors, license")}
+ // clang-format on
+};
+
+void
+add_actions_tutorial(InkscapeApplication* app)
+{
+ if (!app->gtk_app()) {
+ return;
+ }
+
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "tutorial-basic", sigc::bind<Glib::ustring>(sigc::ptr_fun(&help_open_tutorial), "tutorial-basic"));
+ gapp->add_action( "tutorial-shapes", sigc::bind<Glib::ustring>(sigc::ptr_fun(&help_open_tutorial), "tutorial-shapes"));
+ gapp->add_action( "tutorial-advanced", sigc::bind<Glib::ustring>(sigc::ptr_fun(&help_open_tutorial), "tutorial-advanced"));
+ gapp->add_action( "tutorial-tracing", sigc::bind<Glib::ustring>(sigc::ptr_fun(&help_open_tutorial), "tutorial-tracing"));
+ gapp->add_action( "tutorial-tracing-pixelart", sigc::bind<Glib::ustring>(sigc::ptr_fun(&help_open_tutorial), "tutorial-tracing-pixelart"));
+ gapp->add_action( "tutorial-calligraphy", sigc::bind<Glib::ustring>(sigc::ptr_fun(&help_open_tutorial), "tutorial-calligraphy"));
+ gapp->add_action( "tutorial-interpolate", sigc::bind<Glib::ustring>(sigc::ptr_fun(&help_open_tutorial), "tutorial-interpolate"));
+ gapp->add_action( "tutorial-design", sigc::bind<Glib::ustring>(sigc::ptr_fun(&help_open_tutorial), "tutorial-elements"));
+ gapp->add_action( "tutorial-tips", sigc::bind<Glib::ustring>(sigc::ptr_fun(&help_open_tutorial), "tutorial-tips"));
+ gapp->add_action( "about", sigc::ptr_fun(&help_about_inkscape));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_tutorial);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-tutorial.h b/src/actions/actions-tutorial.h
new file mode 100644
index 0000000..04df40b
--- /dev/null
+++ b/src/actions/actions-tutorial.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_TUTORIAL_H
+#define INK_ACTIONS_TUTORIAL_H
+
+class InkscapeApplication;
+
+void add_actions_tutorial(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_TUTORIAL_H
diff --git a/src/actions/actions-undo-document.cpp b/src/actions/actions-undo-document.cpp
new file mode 100644
index 0000000..12e5b7e
--- /dev/null
+++ b/src/actions/actions-undo-document.cpp
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions for Undo/Redo tied to document.
+ *
+ * Authors:
+ * Tavmjong Bah
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-undo-document.h"
+
+#include "document.h"
+#include "document-undo.h"
+#include "inkscape-application.h"
+
+// ifdef out for headless operation!
+#include "desktop.h"
+#include "inkscape-window.h"
+#include "ui/widget/canvas.h"
+
+void
+undo(SPDocument* document)
+{
+ auto app = InkscapeApplication::instance();
+ auto win = app->get_active_window();
+ if (win) {
+ // Could be in headless mode.
+ auto desktop = win->get_desktop();
+ // No undo while dragging, too dangerous.
+ if (desktop->getCanvas()->is_dragging()) {
+ return;
+ }
+ }
+
+ Inkscape::DocumentUndo::undo(document);
+}
+
+void
+redo(SPDocument* document)
+{
+ auto app = InkscapeApplication::instance();
+ auto win = app->get_active_window();
+ if (win) {
+ // Could be in headless mode.
+ auto desktop = win->get_desktop();
+ // No redo while dragging, too dangerous.
+ if (desktop->getCanvas()->is_dragging()) {
+ return;
+ }
+ }
+
+ Inkscape::DocumentUndo::redo(document);
+}
+
+
+std::vector<std::vector<Glib::ustring>> raw_data_undo_document =
+{
+ // clang-format off
+ {"doc.undo", N_("Undo"), "Edit Document", N_("Undo last action")},
+ {"doc.redo", N_("Redo"), "Edit Document", N_("Do again the last undone action")},
+ // clang-format on
+};
+
+void
+add_actions_undo_document(SPDocument* document)
+{
+ auto group = document->getActionGroup();
+
+ // clang-format off
+ group->add_action( "undo", sigc::bind<SPDocument*>(sigc::ptr_fun(&undo), document));
+ group->add_action( "redo", sigc::bind<SPDocument*>(sigc::ptr_fun(&redo), document));
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_undo: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_undo_document);
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_undo_app =
+{
+ // clang-format off
+ {"app.undo", N_("Undo"), "Edit Document", N_("Undo last action")},
+ {"app.redo", N_("Redo"), "Edit Document", N_("Do again the last undone action")},
+ // clang-format on
+};
+
+void
+add_actions_undo_app(InkscapeApplication* app)
+{
+ auto gapp = app->gio_app();
+ gapp->add_action( "undo", [=]{ undo(app->get_active_window()->get_desktop()->getDocument()); });
+ gapp->add_action( "redo", [=]{ redo(app->get_active_window()->get_desktop()->getDocument()); });
+ app->get_action_extra_data().add_data(raw_data_undo_app);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-undo-document.h b/src/actions/actions-undo-document.h
new file mode 100644
index 0000000..cdf6c8e
--- /dev/null
+++ b/src/actions/actions-undo-document.h
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions for Undo/Redo tied to document.
+ *
+ * Authors:
+ * Tavmjong Bah
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_DOCUMENT_UNDO_H
+#define INK_ACTIONS_DOCUMENT_UNDO_H
+
+class SPDocument;
+class InkscapeApplication;
+
+void add_actions_undo_document(SPDocument* document);
+void add_actions_undo_app(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_DOCUMENT_UNDO_H
diff --git a/src/actions/actions-view-mode.cpp b/src/actions/actions-view-mode.cpp
new file mode 100644
index 0000000..ad1330b
--- /dev/null
+++ b/src/actions/actions-view-mode.cpp
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Actions related to View mode
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ * Tavmjong Bah
+ *
+ * Copyright (C) 2021, 2022 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "actions-view-mode.h"
+
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+
+#include "object/sp-namedview.h"
+
+#include "ui/monitor.h" // Monitor aspect ratio
+#include "ui/widget/canvas.h"
+
+#include "widgets/desktop-widget.h"
+
+// Helper function to set state.
+void
+canvas_set_state(InkscapeWindow *win, Glib::ustring action_name, bool state)
+{
+ // Get Action
+ auto action = win->lookup_action(action_name);
+ if (!action) {
+ std::cerr << "canvas_set_state: " << action_name << " action missing!" << std::endl;
+ return;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "canvas_set_state: " << action_name << " not SimpleAction!" << std::endl;
+ return;
+ }
+
+ // Set State
+ saction->change_state(state);
+}
+
+// Helper function to toggle state.
+bool
+canvas_toggle_state(InkscapeWindow *win, Glib::ustring action_name)
+{
+ // Get Action
+ auto action = win->lookup_action(action_name);
+ if (!action) {
+ std::cerr << "canvas_toggle_state: " << action_name << " action missing!" << std::endl;
+ return false;
+ }
+
+ auto saction = Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(action);
+ if (!saction) {
+ std::cerr << "canvas_toggle_state: " << action_name << " not SimpleAction!" << std::endl;
+ return false;
+ }
+
+ // Toggle State
+ bool state = false;
+ saction->get_state(state);
+ state = !state;
+ saction->change_state(state);
+
+ return state;
+}
+
+void
+canvas_show_grid_toggle(InkscapeWindow *win)
+{
+ // Toggle State
+ canvas_toggle_state(win, "canvas-show-grid");
+
+ // Do Action
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleGrids();
+}
+
+void
+canvas_commands_bar_toggle(InkscapeWindow *win)
+{
+ // Toggle State
+ canvas_toggle_state(win, "canvas-commands-bar");
+
+ // Do Action
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleToolbar("commands");
+}
+
+void
+canvas_snap_controls_bar_toggle(InkscapeWindow *win)
+{
+ // Toggle State
+ canvas_toggle_state(win, "canvas-snap-controls-bar");
+
+ // Do Action
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleToolbar("snaptoolbox");
+}
+
+void
+canvas_tool_control_bar_toggle(InkscapeWindow *win)
+{
+ // Toggle State
+ canvas_toggle_state(win, "canvas-tool-control-bar");
+
+ // Do Action
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleToolbar("toppanel");
+}
+
+void
+canvas_toolbox_toggle(InkscapeWindow *win)
+{
+ // Toggle State
+ canvas_toggle_state(win, "canvas-toolbox");
+
+ // Do Action
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleToolbar("toolbox");
+}
+
+void
+canvas_rulers_toggle(InkscapeWindow *win)
+{
+ // Toggle State
+ canvas_toggle_state(win, "canvas-rulers");
+
+ // Do Action
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleToolbar("rulers");
+}
+
+void
+canvas_scroll_bars(InkscapeWindow *win)
+{
+ // Toggle State
+ canvas_toggle_state(win, "canvas-scroll-bars");
+
+ // Do Action
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleToolbar("scrollbars");
+}
+
+void
+canvas_palette_toggle(InkscapeWindow *win)
+{
+ // Toggle State
+ canvas_toggle_state(win, "canvas-palette");
+
+ // Do Action
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleToolbar("panels");
+}
+
+void
+canvas_statusbar_toggle(InkscapeWindow *win)
+{
+ // Toggle State
+ canvas_toggle_state(win, "canvas-statusbar");
+
+ // Do Action
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleToolbar("statusbar");
+}
+
+void
+canvas_interface_mode(InkscapeWindow *win)
+{
+ // Toggle State
+ bool state = canvas_toggle_state(win, "canvas-interface-mode");
+
+ // Save to preferences
+ auto prefs = Inkscape::Preferences::get();
+ Glib::ustring pref_root = "/window/";
+ auto desktop = win->get_desktop();
+ if (desktop && desktop->is_focusMode()) {
+ pref_root = "/focus/";
+ } else if (desktop && desktop->is_fullscreen()) {
+ pref_root = "/fullscreen/";
+ }
+ prefs->setBool(pref_root + "interface_mode", state);
+
+ // Update Interface
+ auto desktop_widget = win->get_desktop_widget();
+ desktop_widget->layoutWidgets();
+}
+
+void
+view_fullscreen(InkscapeWindow *win)
+{
+ SPDesktop* dt = win->get_desktop();
+ dt->fullscreen();
+}
+
+void
+view_full_screen_focus(InkscapeWindow *win)
+{
+ SPDesktop* dt = win->get_desktop();
+ dt->fullscreen();
+ dt->focusMode(!dt->is_fullscreen());
+}
+
+void
+view_focus_toggle(InkscapeWindow *win)
+{
+ SPDesktop* dt = win->get_desktop();
+ dt->focusMode(!dt->is_focusMode());
+}
+
+void
+canvas_command_palette(InkscapeWindow *win)
+{
+ SPDesktop* dt = win->get_desktop();
+ dt->toggleCommandPalette();
+}
+
+/*
+ * Sets state of actions (and thus toggle buttons).
+ * Needed when changing window mode (fullscreen, normal, focus).
+ */
+void
+view_set_gui(InkscapeWindow* win)
+{
+ auto prefs = Inkscape::Preferences::get();
+ SPDesktop* desktop = win->get_desktop();
+
+ if (!desktop) {
+ std::cerr << "canvas_set_gui: no desktop!" << std::endl;
+ return;
+ }
+
+ Glib::ustring pref_root = "/window/";
+ if (desktop && desktop->is_focusMode()) {
+ pref_root = "/focus/";
+ } else if (desktop && desktop->is_fullscreen()) {
+ pref_root = "/fullscreen/";
+ }
+
+ // clang-format off
+
+ // Current States of Actions
+ bool commands_state = prefs->getBool(pref_root + "commands/state", true);
+ bool snaptoolbox_state = prefs->getBool(pref_root + "snaptoolbox/state", true);
+ bool toppanel_state = prefs->getBool(pref_root + "toppanel/state", true);
+ bool toolbox_state = prefs->getBool(pref_root + "toolbox/state", true);
+ bool palette_state = prefs->getBool(pref_root + "panels/state", true);
+ bool statusbar_state = prefs->getBool(pref_root + "statusbar/state", true);
+ bool scrollbars_state = prefs->getBool(pref_root + "scrollbars/state", true);
+ bool rulers_state = prefs->getBool(pref_root + "rulers/state", true);
+ bool interface_mode = prefs->getInt(pref_root + "interface_mode", false);
+
+ canvas_set_state(win, "canvas-commands-bar", commands_state);
+ canvas_set_state(win, "canvas-snap-controls-bar", snaptoolbox_state);
+ canvas_set_state(win, "canvas-tool-control-bar", toppanel_state);
+ canvas_set_state(win, "canvas-toolbox", toolbox_state);
+ canvas_set_state(win, "canvas-rulers", rulers_state);
+ canvas_set_state(win, "canvas-scroll-bars", scrollbars_state);
+ canvas_set_state(win, "canvas-palette", palette_state);
+ canvas_set_state(win, "canvas-statusbar", statusbar_state);
+ // clang-format on
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_view_mode =
+{
+ // clang-format off
+ {"win.canvas-show-grid", N_("Page Grid"), "Canvas Display", N_("Show or hide the page grid")},
+
+ {"win.canvas-commands-bar", N_("Commands Bar"), "Canvas Display", N_("Show or hide the Commands bar (under the menu)")},
+ {"win.canvas-snap-controls-bar", N_("Snap Controls Bar"), "Canvas Display", N_("Show or hide the snapping controls")},
+ {"win.canvas-tool-control-bar", N_("Tool Controls Bar"), "Canvas Display", N_("Show or hide the Tool Controls bar")},
+ {"win.canvas-toolbox", N_("Toolbox"), "Canvas Display", N_("Show or hide the main toolbox (on the left)")},
+ {"win.canvas-rulers", N_("Rulers"), "Canvas Display", N_("Show or hide the canvas rulers")},
+ {"win.canvas-scroll-bars", N_("Scroll bars"), "Canvas Display", N_("Show or hide the canvas scrollbars")},
+ {"win.canvas-palette", N_("Palette"), "Canvas Display", N_("Show or hide the color palette")},
+ {"win.canvas-statusbar", N_("Statusbar"), "Canvas Display", N_("Show or hide the statusbar (at the bottom of the window)")},
+
+ {"win.canvas-command-palette", N_("Command Palette"), "Canvas Display", N_("Show or hide the on-canvas command palette")},
+ {"win.view-fullscreen", N_("Fullscreen"), "Canvas Display", N_("Stretch this document window to full screen")},
+
+ {"win.view-full-screen-focus", N_("Fullscreen & Focus Mode"), "Canvas Display", N_("Stretch this document window to full screen")},
+ {"win.view-focus-toggle", N_("Focus Mode"), "Canvas Display", N_("Remove excess toolbars to focus on drawing")},
+
+ {"win.canvas-interface-mode", N_("Interface Mode"), "Canvas Display", N_("Toggle wide or narrow screen setup")},
+ // clang-format on
+};
+
+void
+add_actions_view_mode(InkscapeWindow* win)
+{
+ auto prefs = Inkscape::Preferences::get();
+ SPDesktop* desktop = win->get_desktop();
+
+ if (!desktop) {
+ std::cerr << "add_actions_view_mode: no desktop!" << std::endl;
+ }
+
+ Glib::ustring pref_root = "/window/";
+ if (desktop && desktop->is_focusMode()) {
+ pref_root = "/focus/";
+ } else if (desktop && desktop->is_fullscreen()) {
+ pref_root = "/fullscreen/";
+ }
+
+ // Initial States of Actions
+
+ // If interface_mode unset, use screen aspect ratio.
+ Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_primary();
+ double const width = monitor_geometry.get_width();
+ double const height = monitor_geometry.get_height();
+ bool widescreen = (height > 0 && width/height > 1.65);
+ widescreen = prefs->getInt(pref_root + "task/taskset", widescreen ? 2 : 0) == 2; // legacy
+
+ // clang-format off
+ bool commands_toggle = prefs->getBool(pref_root + "commands/state", true);
+ bool snaptoolbox_toggle = prefs->getBool(pref_root + "snaptoolbox/state", true);
+ bool toppanel_toggle = prefs->getBool(pref_root + "toppanel/state", true);
+ bool toolbox_toggle = prefs->getBool(pref_root + "toolbox/state", true);
+ bool rulers_toggle = prefs->getBool(pref_root + "rulers/state", true);
+ bool scrollbars_toggle = prefs->getBool(pref_root + "scrollbars/state", true);
+ bool palette_toggle = prefs->getBool(pref_root + "panels/state", true);
+ bool statusbar_toggle = prefs->getBool(pref_root + "statusbar/state", true);
+
+ bool interface_mode = prefs->getBool(pref_root + "interface_mode", widescreen);
+
+ win->add_action_bool( "canvas-show-grid", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_show_grid_toggle), win));
+
+ win->add_action_bool( "canvas-commands-bar", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_commands_bar_toggle), win), commands_toggle);
+ win->add_action_bool( "canvas-snap-controls-bar", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_snap_controls_bar_toggle), win), snaptoolbox_toggle);
+ win->add_action_bool( "canvas-tool-control-bar", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_tool_control_bar_toggle), win), toppanel_toggle);
+ win->add_action_bool( "canvas-toolbox", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_toolbox_toggle), win), toolbox_toggle);
+ win->add_action_bool( "canvas-rulers", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_rulers_toggle), win), rulers_toggle);
+ win->add_action_bool( "canvas-scroll-bars", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_scroll_bars), win), scrollbars_toggle);
+ win->add_action_bool( "canvas-palette", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_palette_toggle), win), palette_toggle);
+ win->add_action_bool( "canvas-statusbar", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_statusbar_toggle), win), statusbar_toggle);
+
+ win->add_action_bool ( "canvas-interface-mode", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_interface_mode), win), interface_mode);
+ win->add_action( "view-fullscreen", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&view_fullscreen), win));
+
+ win->add_action( "view-full-screen-focus", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&view_full_screen_focus), win));
+ win->add_action( "view-focus-toggle", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&view_focus_toggle), win));
+
+ win->add_action( "canvas-command-palette", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&canvas_command_palette), win));
+ // clang-format on
+
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_view_mode: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_view_mode);
+}
+
+/*
+ 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/actions/actions-view-mode.h b/src/actions/actions-view-mode.h
new file mode 100644
index 0000000..f14b336
--- /dev/null
+++ b/src/actions/actions-view-mode.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_VIEW_MODE_H
+#define INK_ACTIONS_VIEW_MODE_H
+
+class InkscapeWindow;
+
+void add_actions_view_mode(InkscapeWindow* win);
+void view_set_gui(InkscapeWindow* win);
+
+#endif // INK_ACTIONS_VIEW_MODE_H
diff --git a/src/actions/actions-view-window.cpp b/src/actions/actions-view-window.cpp
new file mode 100644
index 0000000..c7492ca
--- /dev/null
+++ b/src/actions/actions-view-window.cpp
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Gio::Actions for window handling that are not useful from the command line (thus tied to window map).
+ * Found under the "View" menu.
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "actions-view-window.h"
+
+#include <giomm.h>
+#include <glibmm/i18n.h>
+
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+#include "inkscape.h" // previous/next window
+#include "actions/actions-extra-data.h"
+#include "ui/interface.h" // sp_ui_new_view()
+
+void
+window_previous(InkscapeWindow* win)
+{
+ INKSCAPE.switch_desktops_prev();
+}
+
+void
+window_next(InkscapeWindow* win)
+{
+ INKSCAPE.switch_desktops_next();
+}
+
+void
+window_new(InkscapeWindow* win)
+{
+ sp_ui_new_view();
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_view_window =
+{
+ // clang-format off
+ {"win.window-new", N_("Duplicate Window"), "View", N_("Open a new window with the same document")},
+ {"win.window-previous", N_("Previous Window"), "View", N_("Switch to the previous document window")},
+ {"win.window-next", N_("Next Window"), "View", N_("Switch to the next document window")},
+ // clang-format on
+};
+
+void
+add_actions_view_window(InkscapeWindow* win)
+{
+ // clang-format off
+ win->add_action( "window-new", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&window_new), win));
+ win->add_action( "window-previous", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&window_previous), win));
+ win->add_action( "window-next", sigc::bind<InkscapeWindow*>(sigc::ptr_fun(&window_next), win));
+ // clang-format on
+
+ // Check if there is already an application instance (GUI or non-GUI).
+ auto app = InkscapeApplication::instance();
+ if (!app) {
+ std::cerr << "add_actions_view_window: no app!" << std::endl;
+ return;
+ }
+ app->get_action_extra_data().add_data(raw_data_view_window);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-view-window.h b/src/actions/actions-view-window.h
new file mode 100644
index 0000000..23c9187
--- /dev/null
+++ b/src/actions/actions-view-window.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Authors:
+ * Sushant A A <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_ACTIONS_VIEW_WINDOW_H
+#define INK_ACTIONS_VIEW_WINDOW_H
+
+class InkscapeWindow;
+
+void add_actions_view_window(InkscapeWindow *);
+
+#endif // INK_ACTIONS_VIEW_WINDOW_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-window.cpp b/src/actions/actions-window.cpp
new file mode 100644
index 0000000..0fb787d
--- /dev/null
+++ b/src/actions/actions-window.cpp
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for window handling tied to the application and with GUI.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include <iostream>
+
+#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
+#include <glibmm/i18n.h>
+
+#include "actions-window.h"
+#include "actions-helper.h"
+#include "inkscape-application.h"
+#include "inkscape-window.h"
+
+#include "inkscape.h" // Inkscape::Application
+
+// Actions for window handling (should be integrated with file dialog).
+
+class InkscapeWindow;
+
+// Open a window for current document
+void
+window_open(InkscapeApplication *app)
+{
+ SPDocument *document = app->get_active_document();
+ if (document) {
+ InkscapeWindow* window = app->get_active_window();
+ if (window && window->get_document() && window->get_document()->getVirgin()) {
+ // We have a window with an untouched template document, use this window.
+ app->document_swap (window, document);
+ } else {
+ app->window_open(document);
+ }
+ } else {
+ std::cerr << "window_open(): failed to find document!" << std::endl;
+ }
+}
+
+void
+window_close(InkscapeApplication *app)
+{
+ app->window_close_active();
+}
+
+std::vector<std::vector<Glib::ustring>> raw_data_window =
+{
+ // clang-format off
+ {"app.window-open", N_("Window Open"), "Window", N_("Open a window for the active document; GUI only") },
+ {"app.window-close", N_("Window Close"), "Window", N_("Close the active window, does not check for data loss") }
+ // clang-format on
+};
+
+void
+add_actions_window(InkscapeApplication* app)
+{
+ auto *gapp = app->gio_app();
+
+ // clang-format off
+ gapp->add_action( "window-open", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&window_open), app));
+ gapp->add_action( "window-close", sigc::bind<InkscapeApplication*>(sigc::ptr_fun(&window_close), app));
+ // clang-format on
+
+ app->get_action_extra_data().add_data(raw_data_window);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/actions/actions-window.h b/src/actions/actions-window.h
new file mode 100644
index 0000000..9f4d9b9
--- /dev/null
+++ b/src/actions/actions-window.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gio::Actions for window handling tied to the application and with GUI.
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_ACTIONS_WINDOW_H
+#define INK_ACTIONS_WINDOW_H
+
+class InkscapeApplication;
+
+void add_actions_window(InkscapeApplication* app);
+
+#endif // INK_ACTIONS_WINDOW_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 :