From cca66b9ec4e494c1d919bff0f71a820d8afab1fa Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:24:48 +0200 Subject: Adding upstream version 1.2.2. Signed-off-by: Daniel Baumann --- src/actions/CMakeLists.txt | 12 + src/actions/README | 25 + src/actions/actions-base.cpp | 252 +++++++++ src/actions/actions-base.h | 18 + src/actions/actions-canvas-mode.cpp | 324 ++++++++++++ src/actions/actions-canvas-mode.h | 29 ++ src/actions/actions-canvas-snapping.cpp | 412 +++++++++++++++ src/actions/actions-canvas-snapping.h | 40 ++ src/actions/actions-canvas-transform.cpp | 286 +++++++++++ src/actions/actions-canvas-transform.h | 30 ++ src/actions/actions-dialogs.cpp | 162 ++++++ src/actions/actions-dialogs.h | 32 ++ src/actions/actions-edit-document.cpp | 94 ++++ src/actions/actions-edit-document.h | 19 + src/actions/actions-edit-window.cpp | 74 +++ src/actions/actions-edit-window.h | 19 + src/actions/actions-edit.cpp | 344 +++++++++++++ src/actions/actions-edit.h | 19 + src/actions/actions-effect-data.cpp | 63 +++ src/actions/actions-effect-data.h | 49 ++ src/actions/actions-effect.cpp | 83 +++ src/actions/actions-effect.h | 19 + src/actions/actions-element-a.cpp | 88 ++++ src/actions/actions-element-a.h | 29 ++ src/actions/actions-element-image.cpp | 155 ++++++ src/actions/actions-element-image.h | 29 ++ src/actions/actions-extra-data.cpp | 84 +++ src/actions/actions-extra-data.h | 78 +++ src/actions/actions-file-window.cpp | 165 ++++++ src/actions/actions-file-window.h | 19 + src/actions/actions-file.cpp | 147 ++++++ src/actions/actions-file.h | 29 ++ src/actions/actions-help-url.cpp | 153 ++++++ src/actions/actions-help-url.h | 19 + src/actions/actions-helper.cpp | 45 ++ src/actions/actions-helper.h | 34 ++ src/actions/actions-hide-lock.cpp | 237 +++++++++ src/actions/actions-hide-lock.h | 19 + src/actions/actions-hint-data.cpp | 47 ++ src/actions/actions-hint-data.h | 36 ++ src/actions/actions-layer.cpp | 539 +++++++++++++++++++ src/actions/actions-layer.h | 19 + src/actions/actions-node-align.cpp | 136 +++++ src/actions/actions-node-align.h | 29 ++ src/actions/actions-object-align.cpp | 852 +++++++++++++++++++++++++++++++ src/actions/actions-object-align.h | 29 ++ src/actions/actions-object.cpp | 345 +++++++++++++ src/actions/actions-object.h | 29 ++ src/actions/actions-output.cpp | 363 +++++++++++++ src/actions/actions-output.h | 29 ++ src/actions/actions-pages.cpp | 113 ++++ src/actions/actions-pages.h | 29 ++ src/actions/actions-paths.cpp | 266 ++++++++++ src/actions/actions-paths.h | 31 ++ src/actions/actions-selection-object.cpp | 196 +++++++ src/actions/actions-selection-object.h | 19 + src/actions/actions-selection-window.cpp | 149 ++++++ src/actions/actions-selection-window.h | 19 + src/actions/actions-selection.cpp | 284 +++++++++++ src/actions/actions-selection.h | 29 ++ src/actions/actions-text.cpp | 104 ++++ src/actions/actions-text.h | 19 + src/actions/actions-tools.cpp | 413 +++++++++++++++ src/actions/actions-tools.h | 81 +++ src/actions/actions-transform.cpp | 170 ++++++ src/actions/actions-transform.h | 29 ++ src/actions/actions-tutorial.cpp | 107 ++++ src/actions/actions-tutorial.h | 19 + src/actions/actions-undo-document.cpp | 115 +++++ src/actions/actions-undo-document.h | 23 + src/actions/actions-view-mode.cpp | 372 ++++++++++++++ src/actions/actions-view-mode.h | 20 + src/actions/actions-view-window.cpp | 80 +++ src/actions/actions-view-window.h | 30 ++ src/actions/actions-window.cpp | 82 +++ src/actions/actions-window.h | 29 ++ 76 files changed, 9016 insertions(+) create mode 100644 src/actions/CMakeLists.txt create mode 100644 src/actions/README create mode 100644 src/actions/actions-base.cpp create mode 100644 src/actions/actions-base.h create mode 100644 src/actions/actions-canvas-mode.cpp create mode 100644 src/actions/actions-canvas-mode.h create mode 100644 src/actions/actions-canvas-snapping.cpp create mode 100644 src/actions/actions-canvas-snapping.h create mode 100644 src/actions/actions-canvas-transform.cpp create mode 100644 src/actions/actions-canvas-transform.h create mode 100644 src/actions/actions-dialogs.cpp create mode 100644 src/actions/actions-dialogs.h create mode 100644 src/actions/actions-edit-document.cpp create mode 100644 src/actions/actions-edit-document.h create mode 100644 src/actions/actions-edit-window.cpp create mode 100644 src/actions/actions-edit-window.h create mode 100644 src/actions/actions-edit.cpp create mode 100644 src/actions/actions-edit.h create mode 100644 src/actions/actions-effect-data.cpp create mode 100644 src/actions/actions-effect-data.h create mode 100644 src/actions/actions-effect.cpp create mode 100644 src/actions/actions-effect.h create mode 100644 src/actions/actions-element-a.cpp create mode 100644 src/actions/actions-element-a.h create mode 100644 src/actions/actions-element-image.cpp create mode 100644 src/actions/actions-element-image.h create mode 100644 src/actions/actions-extra-data.cpp create mode 100644 src/actions/actions-extra-data.h create mode 100644 src/actions/actions-file-window.cpp create mode 100644 src/actions/actions-file-window.h create mode 100644 src/actions/actions-file.cpp create mode 100644 src/actions/actions-file.h create mode 100644 src/actions/actions-help-url.cpp create mode 100644 src/actions/actions-help-url.h create mode 100644 src/actions/actions-helper.cpp create mode 100644 src/actions/actions-helper.h create mode 100644 src/actions/actions-hide-lock.cpp create mode 100644 src/actions/actions-hide-lock.h create mode 100644 src/actions/actions-hint-data.cpp create mode 100644 src/actions/actions-hint-data.h create mode 100644 src/actions/actions-layer.cpp create mode 100644 src/actions/actions-layer.h create mode 100644 src/actions/actions-node-align.cpp create mode 100644 src/actions/actions-node-align.h create mode 100644 src/actions/actions-object-align.cpp create mode 100644 src/actions/actions-object-align.h create mode 100644 src/actions/actions-object.cpp create mode 100644 src/actions/actions-object.h create mode 100644 src/actions/actions-output.cpp create mode 100644 src/actions/actions-output.h create mode 100644 src/actions/actions-pages.cpp create mode 100644 src/actions/actions-pages.h create mode 100644 src/actions/actions-paths.cpp create mode 100644 src/actions/actions-paths.h create mode 100644 src/actions/actions-selection-object.cpp create mode 100644 src/actions/actions-selection-object.h create mode 100644 src/actions/actions-selection-window.cpp create mode 100644 src/actions/actions-selection-window.h create mode 100644 src/actions/actions-selection.cpp create mode 100644 src/actions/actions-selection.h create mode 100644 src/actions/actions-text.cpp create mode 100644 src/actions/actions-text.h create mode 100644 src/actions/actions-tools.cpp create mode 100644 src/actions/actions-tools.h create mode 100644 src/actions/actions-transform.cpp create mode 100644 src/actions/actions-transform.h create mode 100644 src/actions/actions-tutorial.cpp create mode 100644 src/actions/actions-tutorial.h create mode 100644 src/actions/actions-undo-document.cpp create mode 100644 src/actions/actions-undo-document.h create mode 100644 src/actions/actions-view-mode.cpp create mode 100644 src/actions/actions-view-mode.h create mode 100644 src/actions/actions-view-window.cpp create mode 100644 src/actions/actions-view-window.h create mode 100644 src/actions/actions-window.cpp create mode 100644 src/actions/actions-window.h (limited to 'src/actions') 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 + +#include // Not ! To eventually allow a headless version! +#include + +#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(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> 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(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(sigc::ptr_fun(&query_x), app) ); + gapp->add_action( "query-y", sigc::bind(sigc::ptr_fun(&query_y), app) ); + gapp->add_action( "query-width", sigc::bind(sigc::ptr_fun(&query_width), app) ); + gapp->add_action( "query-height", sigc::bind(sigc::ptr_fun(&query_height), app) ); + gapp->add_action( "query-all", sigc::bind(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 + +#include // Not ! To eventually allow a headless version! +#include + +#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 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::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::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::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::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::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::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> 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(sigc::ptr_fun(&canvas_display_mode), win), display_mode); + win->add_action( "canvas-display-mode-cycle", sigc::bind(sigc::ptr_fun(&canvas_display_mode_cycle), win)); + win->add_action( "canvas-display-mode-toggle", sigc::bind(sigc::ptr_fun(&canvas_display_mode_toggle), win)); + win->add_action_radio_integer ("canvas-split-mode", sigc::bind(sigc::ptr_fun(&canvas_split_mode), win), (int)Inkscape::SplitMode::NORMAL); + win->add_action_bool( "canvas-color-mode", sigc::bind(sigc::ptr_fun(&canvas_color_mode_toggle), win)); + win->add_action_bool( "canvas-color-manage", sigc::bind(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 +#include +#include + +#include // Not ! To eventually allow a headless version! +#include + +#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 SnapVector; +typedef std::unordered_map 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> 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 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 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::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> 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 + +#include // Not ! To eventually allow a headless version! +#include + +#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(dt->event_context) || + dynamic_cast(dt->event_context) ) { + + // Zoom around end of unfinished path. + std::optional zoom_to = + dynamic_cast(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::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> 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(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_IN)); + win->add_action( "canvas-zoom-out", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_OUT)); + win->add_action( "canvas-zoom-1-1", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_1_1)); + win->add_action( "canvas-zoom-1-2", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_1_2)); + win->add_action( "canvas-zoom-2-1", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_2_1)); + win->add_action( "canvas-zoom-selection", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_SELECTION)); + win->add_action( "canvas-zoom-drawing", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_DRAWING)); + win->add_action( "canvas-zoom-page", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_PAGE)); + win->add_action( "canvas-zoom-page-width", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_PAGE_WIDTH)); + win->add_action( "canvas-zoom-center-page",sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_CENTER_PAGE)); + win->add_action( "canvas-zoom-prev", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_PREV)); + win->add_action( "canvas-zoom-next", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ZOOM_NEXT)); + + win->add_action( "canvas-rotate-cw", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ROTATE_CW)); + win->add_action( "canvas-rotate-ccw", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ROTATE_CCW)); + win->add_action( "canvas-rotate-reset", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_ROTATE_RESET)); + + win->add_action( "canvas-flip-horizontal", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_FLIP_HORIZONTAL)); + win->add_action( "canvas-flip-vertical", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_FLIP_VERTICAL)); + win->add_action( "canvas-flip-reset", sigc::bind(sigc::ptr_fun(&canvas_transform), win, INK_CANVAS_FLIP_RESET)); + + win->add_action_bool( "canvas-rotate-lock",sigc::bind(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 +#include + +#include // Not ! To eventually allow a headless version! +#include + +#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> 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 s = Glib::VariantBase::cast_dynamic >(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::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(sigc::ptr_fun(&dialog_open), win)); + win->add_action( "dialog-toggle", sigc::bind(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::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 + +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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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> 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 map = document->getActionGroup(); + + // clang-format off + map->add_action( "create-guides-around-page", sigc::bind(sigc::ptr_fun(&create_guides_around_page), document)); + map->add_action( "delete-all-guides", sigc::bind(sigc::ptr_fun(&delete_all_guides), document)); + map->add_action( "fit-canvas-to-drawing", sigc::bind(sigc::ptr_fun(&fit_canvas_drawing), document)); + map->add_action_bool( "lock-all-guides", sigc::bind(sigc::ptr_fun(&lock_all_guides), document)); + map->add_action_bool( "show-all-guides", sigc::bind(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 + * + * 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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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> 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(sigc::ptr_fun(&paste), win)); + win->add_action( "paste-in-place", sigc::bind(sigc::ptr_fun(&paste_in_place), win)); + win->add_action( "path-effect-parameter-next", sigc::bind(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 + * + * 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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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(desktop->event_context)) { + text_tool->deleteSelected(); + return; + } + if (auto node_tool = dynamic_cast(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> 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(sigc::ptr_fun(&object_to_pattern), app)); + gapp->add_action( "pattern-to-object", sigc::bind(sigc::ptr_fun(&pattern_to_object), app)); + gapp->add_action( "object-to-marker", sigc::bind(sigc::ptr_fun(&object_to_marker), app)); + gapp->add_action( "object-to-guides", sigc::bind(sigc::ptr_fun(&object_to_guides), app)); + gapp->add_action( "cut", sigc::bind(sigc::ptr_fun(&cut), app)); + gapp->add_action( "copy", sigc::bind(sigc::ptr_fun(©), app)); + gapp->add_action( "paste-style", sigc::bind(sigc::ptr_fun(&paste_style), app)); + gapp->add_action( "paste-size", sigc::bind(sigc::ptr_fun(&paste_size), app)); + gapp->add_action( "paste-width", sigc::bind(sigc::ptr_fun(&paste_width), app)); + gapp->add_action( "paste-height", sigc::bind(sigc::ptr_fun(&paste_height), app)); + gapp->add_action( "paste-size-separately", sigc::bind(sigc::ptr_fun(&paste_size_separately), app)); + gapp->add_action( "paste-width-separately", sigc::bind(sigc::ptr_fun(&paste_width_separately), app)); + gapp->add_action( "paste-height-separately", sigc::bind(sigc::ptr_fun(&paste_height_separately), app)); + gapp->add_action( "duplicate", sigc::bind(sigc::ptr_fun(&duplicate), app)); + gapp->add_action( "clone", sigc::bind(sigc::ptr_fun(&clone), app)); + gapp->add_action( "clone-unlink", sigc::bind(sigc::ptr_fun(&clone_unlink), app)); + gapp->add_action( "clone-unlink-recursively", sigc::bind(sigc::ptr_fun(&clone_unlink_recursively), app)); + gapp->add_action( "clone-link", sigc::bind(sigc::ptr_fun(&clone_link), app)); + gapp->add_action( "select-original", sigc::bind(sigc::ptr_fun(&select_original), app)); + gapp->add_action( "clone-link-lpe", sigc::bind(sigc::ptr_fun(&clone_link_lpe), app)); + gapp->add_action( "delete", sigc::bind(sigc::ptr_fun(&edit_delete), app)); + gapp->add_action( "delete-selection", sigc::bind(sigc::ptr_fun(&edit_delete_selection), app)); + gapp->add_action( "paste-path-effect", sigc::bind(sigc::ptr_fun(&paste_path_effect), app)); + gapp->add_action( "remove-path-effect", sigc::bind(sigc::ptr_fun(&remove_path_effect), app)); + gapp->add_action( "swap-fill-and-stroke", sigc::bind(sigc::ptr_fun(&swap_fill_and_stroke), app)); + gapp->add_action( "fit-canvas-to-selection", sigc::bind(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 + * + * 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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + + +#include "actions-effect-data.h" + +#include +#include +#include + +#include + +std::vector +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 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 + * + * 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 +#include +#include + +#include + +class InkActionEffectData +{ +public: + InkActionEffectData() = default; + + typedef std::tuple, Glib::ustring> datum; + + // Get Vector + std::vector give_all_data(); + + // Add Data + void add_data(std::string effect_id, std::list effect_submenu_vector, Glib::ustring const &effect_name) ; + +private: + std::vector 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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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> 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(sigc::ptr_fun(&edit_remove_filter), app)); + gapp->add_action( "last-effect", sigc::bind(sigc::ptr_fun(&last_effect), app)); + gapp->add_action( "last-effect-pref", sigc::bind(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 + * + * 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 (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 + +#include // Not ! To eventually allow a headless version! +#include + +#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(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> 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(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 (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 . + * + * 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 + +#include // Not ! To eventually allow a headless version! +#include // OK, we lied. We pop-up an message dialog if external editor not found and if we have a GUI. +#include + +#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(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.\nNote: Path to editor can be set in Preferences dialog."); + 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> 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(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 . + * + * 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 + +#include + +std::vector +InkActionExtraData::get_actions() +{ + std::vector 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> &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 +#include +#include +#include +#include + +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 get_actions(); + + void add_data(std::vector> &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 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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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> 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 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(sigc::ptr_fun(&document_new), win)); + win->add_action( "document-dialog-templates", sigc::bind(sigc::ptr_fun(&document_dialog_templates), win)); + win->add_action( "document-open", sigc::bind(sigc::ptr_fun(&document_open), win)); + win->add_action( "document-revert", sigc::bind(sigc::ptr_fun(&document_revert), win)); + win->add_action( "document-save", sigc::bind(sigc::ptr_fun(&document_save), win)); + win->add_action( "document-save-as", sigc::bind(sigc::ptr_fun(&document_save_as), win)); + win->add_action( "document-save-copy", sigc::bind(sigc::ptr_fun(&document_save_copy), win)); + win->add_action( "document-save-template", sigc::bind(sigc::ptr_fun(&document_save_template), win)); + win->add_action( "document-import", sigc::bind(sigc::ptr_fun(&document_import), win)); + win->add_action( "document-print", sigc::bind(sigc::ptr_fun(&document_print), win)); + win->add_action( "document-cleanup", sigc::bind(sigc::ptr_fun(&document_cleanup), win)); + win->add_action( "document-close", sigc::bind(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 + * + * 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 + +#include // Not ! To eventually allow a headless version! +#include + +#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 s = Glib::VariantBase::cast_dynamic >(value); + + Glib::RefPtr 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 s = Glib::VariantBase::cast_dynamic >(value); + Glib::RefPtr 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 s = Glib::VariantBase::cast_dynamic >(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> 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> 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(sigc::ptr_fun(&file_open), app)); + gapp->add_action_with_parameter( "file-new", String, sigc::bind(sigc::ptr_fun(&file_new), app)); + gapp->add_action_with_parameter( "file-open-window", String, sigc::bind(sigc::ptr_fun(&file_open_with_window), app)); + gapp->add_action( "file-close", sigc::bind(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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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> 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(sigc::ptr_fun(&help_url_ask_question), win, lang)); + win->add_action( "help-url-man", sigc::bind(sigc::ptr_fun(&help_url_man), win, lang,branch)); + win->add_action( "help-url-faq", sigc::bind(sigc::ptr_fun(&help_url_faq), win, lang)); + win->add_action( "help-url-keys", sigc::bind(sigc::ptr_fun(&help_url_keys), win, lang, branch)); + win->add_action( "help-url-release-notes", sigc::bind(sigc::ptr_fun(&help_url_release_notes), win, lang, version, development_version)); + win->add_action( "help-url-report-bug", sigc::bind(sigc::ptr_fun(&help_url_report_bug), win, lang)); + win->add_action( "help-url-manual", sigc::bind(sigc::ptr_fun(&help_url_manual), win)); + win->add_action( "help-url-donate", sigc::bind(sigc::ptr_fun(&help_url_donate), win, lang, version)); + win->add_action( "help-url-svg11-spec", sigc::bind(sigc::ptr_fun(&help_url_svg11_spec), win)); + win->add_action( "help-url-svg2-spec", sigc::bind(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 + * + * 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 + * 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 // Not ! To eventually allow a headless version! +#include + +#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(&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> 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( sigc::ptr_fun(&hide_lock_unhide_all), app)); + gapp->add_action( "unlock-all", sigc::bind( sigc::ptr_fun(&hide_lock_unlock_all), app)); + + gapp->add_action( "selection-hide", sigc::bind(sigc::ptr_fun(&hide_lock_hide_selected), app, true )); + gapp->add_action( "selection-unhide", sigc::bind(sigc::ptr_fun(&hide_lock_hide_selected), app, false)); + gapp->add_action( "selection-unhide-below", sigc::bind( sigc::ptr_fun(&hide_lock_unhide_below), app)); + + gapp->add_action( "selection-lock", sigc::bind(sigc::ptr_fun(&hide_lock_lock_selected), app, true )); + gapp->add_action( "selection-unlock", sigc::bind(sigc::ptr_fun(&hide_lock_lock_selected), app, false)); + gapp->add_action( "selection-unlock-below", sigc::bind( 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 + * + * 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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "actions-hint-data.h" +#include + +std::vector +InkActionHintData::get_actions() +{ + // To get all the Placeholder hints + std::vector 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> &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 + * + * 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 +#include +#include +#include +#include + +class InkActionHintData +{ +public: + InkActionHintData() = default ; + + std::vector get_actions(); + + void add_data(std::vector> &raw_data); + + Glib::ustring get_tooltip_hint_for_action(Glib::ustring const &action_name, bool translated = true); + +private: + std::map 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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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 element with a special Inkscape attribute (Inkscape:groupMode) set to + * "layer". It is typically directly placed in the 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 %s."), 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 %s."), 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 %s."), 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 %s."), 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 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(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 items(selection->items().begin(), selection->items().end()); + if (items.size() == 1 && dynamic_cast(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 items(selection->items().begin(), selection->items().end()); + if (items.size() == 1 && dynamic_cast(items[0]->parent) ) { + // Only one item selected and the parent is a group! + selection->set(items[0]->parent); + } else { + selection->clear(); + } +} + +std::vector> 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(sigc::ptr_fun(&layer_new), win)); + win->add_action("layer-duplicate", sigc::bind(sigc::ptr_fun(&layer_duplicate), win)); + win->add_action("layer-delete", sigc::bind(sigc::ptr_fun(&layer_delete), win)); + win->add_action("layer-rename", sigc::bind(sigc::ptr_fun(&layer_rename), win)); + + win->add_action("layer-hide-all", sigc::bind(sigc::ptr_fun(&layer_hide_all), win)); + win->add_action("layer-unhide-all", sigc::bind(sigc::ptr_fun(&layer_unhide_all), win)); + win->add_action("layer-hide-toggle", sigc::bind(sigc::ptr_fun(&layer_hide_toggle), win)); + win->add_action("layer-hide-toggle-others", sigc::bind(sigc::ptr_fun(&layer_hide_toggle_others), win)); + + win->add_action("layer-lock-all", sigc::bind(sigc::ptr_fun(&layer_lock_all), win)); + win->add_action("layer-unlock-all", sigc::bind(sigc::ptr_fun(&layer_unlock_all), win)); + win->add_action("layer-lock-toggle", sigc::bind(sigc::ptr_fun(&layer_lock_toggle), win)); + win->add_action("layer-lock-toggle-others", sigc::bind(sigc::ptr_fun(&layer_lock_toggle_others), win)); + + win->add_action("layer-previous", sigc::bind(sigc::ptr_fun(&layer_previous), win)); + win->add_action("layer-next", sigc::bind(sigc::ptr_fun(&layer_next), win)); + + win->add_action("selection-move-to-layer-above", sigc::bind(sigc::ptr_fun(&selection_move_to_layer_above), win)); + win->add_action("selection-move-to-layer-below", sigc::bind(sigc::ptr_fun(&selection_move_to_layer_below), win)); + win->add_action("selection-move-to-layer", sigc::bind(sigc::ptr_fun(&selection_move_to_layer), win)); + + win->add_action("layer-top", sigc::bind(sigc::ptr_fun(&layer_top), win)); + win->add_action("layer-raise", sigc::bind(sigc::ptr_fun(&layer_raise), win)); + win->add_action("layer-lower", sigc::bind(sigc::ptr_fun(&layer_lower), win)); + win->add_action("layer-bottom", sigc::bind(sigc::ptr_fun(&layer_bottom), win)); + + win->add_action("layer-to-group", sigc::bind(sigc::ptr_fun(&layer_to_group), win)); + win->add_action("layer-from-group", sigc::bind(sigc::ptr_fun(&layer_from_group), win)); + + win->add_action("selection-group-enter", sigc::bind(sigc::ptr_fun(&group_enter), win)); + win->add_action("selection-group-exit", sigc::bind(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 + * + * 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 +#include + +#include // Not ! To eventually allow a headless version! +#include + +#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(tool); + if (node_tool) { + } else { + std::cerr << "node_align: tool is not Node tool!" << std::endl; + return; + } + + Glib::Variant s = Glib::VariantBase::cast_dynamic >(value); + std::vector 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(tool); + if (node_tool) { + } else { + std::cerr << "node_distribute: tool is not Node tool!" << std::endl; + return; + } + + node_tool->_multipath->distributeNodes(direction); +} + +std::vector> 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> 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(sigc::ptr_fun(&node_align), win, Geom::X)); + win->add_action_with_parameter( "node-align-vertical", String, sigc::bind(sigc::ptr_fun(&node_align), win, Geom::Y)); + win->add_action( "node-distribute-horizontal", sigc::bind(sigc::ptr_fun(&node_distribute), win, Geom::X)); + win->add_action( "node-distribute-vertical", sigc::bind(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 +#include + +#include // Not ! To eventually allow a headless version! +#include + +#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::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 s = Glib::VariantBase::cast_dynamic >(value); + std::vector 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 selected; + std::vector bools; + for (auto itemlist = list.begin(); itemlist != list.end(); ++itemlist) { + SPItem *item = dynamic_cast(*itemlist); + if (total == 2) { + SPLPEItem *lpeitem = dynamic_cast(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 s = Glib::VariantBase::cast_dynamic >(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 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 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 s = Glib::VariantBase::cast_dynamic >(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 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(item) || dynamic_cast(item)) { + Inkscape::Text::Layout const *layout = te_get_layout(item); + std::optional 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 s = Glib::VariantBase::cast_dynamic >(value); + std::vector 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(focus) || dynamic_cast(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(item) || dynamic_cast(item)) { + Inkscape::Text::Layout const *layout = te_get_layout(item); + std::optional 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 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 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::max(); + double max = std::numeric_limits::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 s = Glib::VariantBase::cast_dynamic >(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 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 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>>(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> 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> hint_data_object_align = +{ + // clang-format off + {"app.object-align", N_("Enter anchoralignmentoptional 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 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(sigc::ptr_fun(&object_align_on_canvas), app), on_canvas); + gapp->add_action_with_parameter( "object-align", String, sigc::bind(sigc::ptr_fun(&object_align), app)); + gapp->add_action_with_parameter( "object-align-text", String, sigc::bind(sigc::ptr_fun(&object_align_text), app)); + gapp->add_action_with_parameter( "object-distribute", String, sigc::bind(sigc::ptr_fun(&object_distribute), app)); + gapp->add_action_with_parameter( "object-distribute-text", String, sigc::bind(sigc::ptr_fun(&object_distribute_text), app)); + gapp->add_action_with_parameter( "object-rearrange", String, sigc::bind(sigc::ptr_fun(&object_rearrange), app)); + gapp->add_action_with_parameter( "object-remove-overlaps", Tuple_DD, sigc::bind(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 + +#include // Not ! To eventually allow a headless version! +#include + +#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 s = Glib::VariantBase::cast_dynamic >(value); + + std::vector 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 s = Glib::VariantBase::cast_dynamic >(value); + + std::vector 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> 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> 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(sigc::ptr_fun(&object_set_attribute), app)); + gapp->add_action_with_parameter( "object-set-property", String, sigc::bind(sigc::ptr_fun(&object_set_property), app)); + + gapp->add_action( "object-unlink-clones", sigc::bind(sigc::ptr_fun(&object_unlink_clones), app)); + gapp->add_action( "object-to-path", sigc::bind(sigc::ptr_fun(&object_to_path), app)); + gapp->add_action( "object-stroke-to-path", sigc::bind(sigc::ptr_fun(&object_stroke_to_path), app)); + + gapp->add_action( "object-set-clip", sigc::bind(sigc::ptr_fun(&object_clip_set), app)); + gapp->add_action( "object-set-inverse-clip", sigc::bind(sigc::ptr_fun(&object_clip_set_inverse), app)); + gapp->add_action( "object-release-clip", sigc::bind(sigc::ptr_fun(&object_clip_release), app)); + gapp->add_action( "object-set-clip-group", sigc::bind(sigc::ptr_fun(&object_clip_set_group), app)); + gapp->add_action( "object-set-mask", sigc::bind(sigc::ptr_fun(&object_mask_set), app)); + gapp->add_action( "object-set-inverse-mask", sigc::bind(sigc::ptr_fun(&object_mask_set_inverse), app)); + gapp->add_action( "object-release-mask", sigc::bind(sigc::ptr_fun(&object_mask_release), app)); + + gapp->add_action( "object-rotate-90-cw", sigc::bind(sigc::ptr_fun(&object_rotate_90_cw), app)); + gapp->add_action( "object-rotate-90-ccw", sigc::bind(sigc::ptr_fun(&object_rotate_90_ccw), app)); + gapp->add_action( "object-flip-horizontal", sigc::bind(sigc::ptr_fun(&object_flip_horizontal), app)); + gapp->add_action( "object-flip-vertical", sigc::bind(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 + +#include // Not ! To eventually allow a headless version! +#include + +#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 s = Glib::VariantBase::cast_dynamic >(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 s = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 s = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 i = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 i = Glib::VariantBase::cast_dynamic >(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 i = Glib::VariantBase::cast_dynamic >(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 s = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 d = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 i = Glib::VariantBase::cast_dynamic >(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 s = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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 s = Glib::VariantBase::cast_dynamic >(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 d = Glib::VariantBase::cast_dynamic >(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 s = Glib::VariantBase::cast_dynamic >(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 b = Glib::VariantBase::cast_dynamic >(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> 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> 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(sigc::ptr_fun(&export_type), app)); + gapp->add_action_with_parameter( "export-filename", String, sigc::bind(sigc::ptr_fun(&export_filename), app)); // MAY NOT WORK DUE TO std::string + gapp->add_action_with_parameter( "export-overwrite", Bool, sigc::bind(sigc::ptr_fun(&export_overwrite), app)); + + gapp->add_action_with_parameter( "export-area", String, sigc::bind(sigc::ptr_fun(&export_area), app)); + gapp->add_action_with_parameter( "export-area-drawing", Bool, sigc::bind(sigc::ptr_fun(&export_area_drawing), app)); + gapp->add_action_with_parameter( "export-area-page", Bool, sigc::bind(sigc::ptr_fun(&export_area_page), app)); + gapp->add_action_with_parameter( "export-margin", Int, sigc::bind(sigc::ptr_fun(&export_margin), app)); + gapp->add_action_with_parameter( "export-area-snap", Bool, sigc::bind(sigc::ptr_fun(&export_area_snap), app)); + gapp->add_action_with_parameter( "export-width", Int, sigc::bind(sigc::ptr_fun(&export_width), app)); + gapp->add_action_with_parameter( "export-height", Int, sigc::bind(sigc::ptr_fun(&export_height), app)); + + gapp->add_action_with_parameter( "export-id", String, sigc::bind(sigc::ptr_fun(&export_id), app)); + gapp->add_action_with_parameter( "export-id-only", Bool, sigc::bind(sigc::ptr_fun(&export_id_only), app)); + + gapp->add_action_with_parameter( "export-plain-svg", Bool, sigc::bind(sigc::ptr_fun(&export_plain_svg), app)); + gapp->add_action_with_parameter( "export-dpi", Double, sigc::bind(sigc::ptr_fun(&export_dpi), app)); + gapp->add_action_with_parameter( "export-ignore-filters", Bool, sigc::bind(sigc::ptr_fun(&export_plain_svg), app)); + gapp->add_action_with_parameter( "export-text-to-path", Bool, sigc::bind(sigc::ptr_fun(&export_text_to_path), app)); + gapp->add_action_with_parameter( "export-ps-level", Int, sigc::bind(sigc::ptr_fun(&export_ps_level), app)); + gapp->add_action_with_parameter( "export-pdf-version", String, sigc::bind(sigc::ptr_fun(&export_pdf_level), app)); + gapp->add_action_with_parameter( "export-latex", Bool, sigc::bind(sigc::ptr_fun(&export_latex), app)); + gapp->add_action_with_parameter( "export-use-hints", Bool, sigc::bind(sigc::ptr_fun(&export_use_hints), app)); + gapp->add_action_with_parameter( "export-background", String, sigc::bind(sigc::ptr_fun(&export_background), app)); + gapp->add_action_with_parameter( "export-background-opacity",Double, sigc::bind(sigc::ptr_fun(&export_background_opacity), app)); + gapp->add_action_with_parameter( "export-png-color-mode", String, sigc::bind(sigc::ptr_fun(&export_png_color_mode), app)); + gapp->add_action_with_parameter( "export-png-use-dithering", Bool, sigc::bind(sigc::ptr_fun(&export_png_use_dithering), app)); + + // Extra + gapp->add_action( "export-do", sigc::bind(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 + +#include +#include + +#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> 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(sigc::ptr_fun(&page_new), doc)); + group->add_action("page-delete", sigc::bind(sigc::ptr_fun(&page_delete), doc)); + group->add_action("page-move-backward", sigc::bind(sigc::ptr_fun(&page_backward), doc)); + group->add_action("page-move-forward", sigc::bind(sigc::ptr_fun(&page_forward), doc)); + group->add_action_bool("page-move-objects", sigc::bind(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 + +#include // Not ! To eventually allow a headless version! +#include + +#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 d = Glib::VariantBase::cast_dynamic >(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 d = Glib::VariantBase::cast_dynamic >(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> 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(sigc::ptr_fun(&object_path_union), app)); + gapp->add_action( "path-difference", sigc::bind(sigc::ptr_fun(&select_path_difference), app)); + gapp->add_action( "path-intersection", sigc::bind(sigc::ptr_fun(&select_path_intersection), app)); + gapp->add_action( "path-exclusion", sigc::bind(sigc::ptr_fun(&select_path_exclusion), app)); + gapp->add_action( "path-division", sigc::bind(sigc::ptr_fun(&select_path_division), app)); + gapp->add_action( "path-cut", sigc::bind(sigc::ptr_fun(&select_path_cut), app)); + gapp->add_action( "path-combine", sigc::bind(sigc::ptr_fun(&select_path_combine), app)); + gapp->add_action( "path-break-apart", sigc::bind(sigc::ptr_fun(&select_path_break_apart), app)); + gapp->add_action( "path-split", sigc::bind(sigc::ptr_fun(&select_path_split), app)); + gapp->add_action( "path-fill-between-paths", sigc::bind(sigc::ptr_fun(&fill_between_paths), app)); + gapp->add_action( "path-simplify", sigc::bind(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(sigc::ptr_fun(&select_path_inset), win)); + win->add_action( "path-offset", sigc::bind(sigc::ptr_fun(&select_path_offset), win)); + win->add_action_with_parameter( "path-inset-screen", Double, sigc::bind(sigc::ptr_fun(&select_path_inset_screen), win)); + win->add_action_with_parameter( "path-offset-screen", Double, sigc::bind(sigc::ptr_fun(&select_path_offset_screen), win)); + win->add_action( "path-offset-dynamic", sigc::bind(sigc::ptr_fun(&select_path_offset_dynamic), win)); + win->add_action( "path-offset-linked", sigc::bind(sigc::ptr_fun(&select_path_offset_linked), win)); + win->add_action( "path-reverse", sigc::bind(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 + * + * 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 +#include + +#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 + 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> 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(sigc::ptr_fun(&select_object_group), app)); + gapp->add_action( "selection-ungroup", sigc::bind(sigc::ptr_fun(&select_object_ungroup), app)); + gapp->add_action( "selection-ungroup-pop", sigc::bind(sigc::ptr_fun(&select_object_ungroup_pop), app)); + gapp->add_action( "selection-link", sigc::bind(sigc::ptr_fun(&select_object_link), app)); + + gapp->add_action( "selection-top", sigc::bind(sigc::ptr_fun(&selection_top), app)); + gapp->add_action( "selection-raise", sigc::bind(sigc::ptr_fun(&selection_raise), app)); + gapp->add_action( "selection-lower", sigc::bind(sigc::ptr_fun(&selection_lower), app)); + gapp->add_action( "selection-bottom", sigc::bind(sigc::ptr_fun(&selection_bottom), app)); + + gapp->add_action( "selection-stack-up", sigc::bind(sigc::ptr_fun(&selection_stack_up), app)); + gapp->add_action( "selection-stack-down", sigc::bind(sigc::ptr_fun(&selection_stack_down), app)); + + gapp->add_action( "selection-make-bitmap-copy", sigc::bind(sigc::ptr_fun(&selection_make_bitmap_copy), app)); + gapp->add_action( "page-fit-to-selection", sigc::bind(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 + * + * 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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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> 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(sigc::ptr_fun(&select_all), win)); + win->add_action( "select-all-layers", sigc::bind(sigc::ptr_fun(&select_all_layers), win)); + win->add_action( "select-same-fill-and-stroke", sigc::bind(sigc::ptr_fun(&select_same_fill_and_stroke), win)); + win->add_action( "select-same-fill", sigc::bind(sigc::ptr_fun(&select_same_fill), win)); + win->add_action( "select-same-stroke-color", sigc::bind(sigc::ptr_fun(&select_same_stroke_color), win)); + win->add_action( "select-same-stroke-style", sigc::bind(sigc::ptr_fun(&select_same_stroke_style), win)); + win->add_action( "select-same-object-type", sigc::bind(sigc::ptr_fun(&select_same_object_type), win)); + win->add_action( "select-invert", sigc::bind(sigc::ptr_fun(&select_invert), win)); + win->add_action( "select-none", sigc::bind(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 + * + * 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 + +#include // Not ! To eventually allow a headless version! +#include + +#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 &objects, SPObject *object, Glib::ustring &condition) +{ + for (auto &o : object->childList(false)) { + if (dynamic_cast(o)) { + SPGroup *group = dynamic_cast(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 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 objects; + get_all_items_recursive(objects, document->getRoot(), condition); + + // Get current selection. + std::vector 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), [¤t](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> 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(sigc::ptr_fun(&select_clear), app) ); + gapp->add_action_radio_string( "select", sigc::bind(sigc::ptr_fun(&select_by_id), app), "null"); // Backwards compatible. + gapp->add_action_radio_string( "unselect", sigc::bind(sigc::ptr_fun(&unselect_by_id), app), "null"); // Match select. + gapp->add_action_radio_string( "select-by-id", sigc::bind(sigc::ptr_fun(&select_by_id), app), "null"); + gapp->add_action_radio_string( "unselect-by-id", sigc::bind(sigc::ptr_fun(&unselect_by_id), app), "null"); + gapp->add_action_radio_string( "select-by-class", sigc::bind(sigc::ptr_fun(&select_by_class), app), "null"); + gapp->add_action_radio_string( "select-by-element", sigc::bind(sigc::ptr_fun(&select_by_element), app), "null"); + gapp->add_action_radio_string( "select-by-selector", sigc::bind(sigc::ptr_fun(&select_by_selector), app), "null"); + gapp->add_action_radio_string( "select-all", sigc::bind(sigc::ptr_fun(&select_all), app), "null"); + gapp->add_action_radio_string( "select-invert", sigc::bind(sigc::ptr_fun(&select_invert), app), "null"); + gapp->add_action( "select-list", sigc::bind(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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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> 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 + * + * 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 +#include + +#include // Not ! To eventually allow a headless version! +#include + +#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 const &get_tool_data() +{ + static std::map 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 const &get_tool_msg() +{ + static std::map tool_msg; + + if (tool_msg.empty()) { + tool_msg = { + // clang-format off + {"Select", _("Click to Select and Transform objects, Drag to select many objects.") }, + {"Node", _("Modify selected path points (nodes) directly.") }, + {"Rect", _("Drag to create a rectangle. Drag controls to round corners and resize. Click to select.") }, + {"Arc", _("Drag to create an ellipse. Drag controls to make an arc or segment. Click to select.") }, + {"Star", _("Drag to create a star. Drag controls to edit the star shape. Click to select.") }, + {"3DBox", _("Drag to create a 3D box. Drag controls to resize in perspective. Click to select (with Ctrl+Alt for single faces).") }, + {"Spiral", _("Drag to create a spiral. Drag controls to edit the spiral shape. Click to select.") }, + {"Marker", _("Click a shape to start editing its markers. Drag controls to change orientation, scale, and position.") }, + {"Pencil", _("Drag to create a freehand line. Shift appends to selected path, Alt activates sketch mode.") }, + {"Pen", _("Click or click and drag to start a path; with Shift to append to selected path. Ctrl+click to create single dots (straight line modes only).") }, + {"Calligraphic", _("Drag to draw a calligraphic stroke; with Ctrl to track a guide path. Arrow keys adjust width (left/right) and angle (up/down).") }, + {"Text", _("Click to select or create text, drag to create flowed text; then type.") }, + {"Gradient", _("Drag or double click to create a gradient on selected objects, drag handles to adjust gradients.") }, + {"Mesh", _("Drag or double click to create a mesh on selected objects, drag handles to adjust meshes.") }, + {"Zoom", _("Click or drag around an area to zoom in, Shift+click to zoom out.") }, + {"Measure", _("Drag to measure the dimensions of objects.") }, + {"Dropper", _("Click to set fill, Shift+click to set stroke; drag to average color in area; with Alt to pick inverse color; Ctrl+C to copy the color under mouse to clipboard") }, + {"Tweak", _("To tweak a path by pushing, select it and drag over it.") }, + {"Spray", _("Drag, click or click and scroll to spray the selected objects.") }, + {"Connector", _("Click and drag between shapes to create a connector.") }, + {"PaintBucket", _("Click to paint a bounded area, Shift+click to union the new fill with the current selection, Ctrl+click to change the clicked object's fill and stroke to the current setting.") }, + {"Eraser", _("Drag 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::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(item)) { + tool_switch("Rect", win); + } else if (dynamic_cast(item)) { + tool_switch("Arc", win); + } else if (dynamic_cast(item)) { + tool_switch("Star", win); + } else if (dynamic_cast(item)) { + tool_switch("3DBox", win); + } else if (dynamic_cast(item)) { + tool_switch("Spiral", win); + } else if (dynamic_cast(item)) { + tool_switch("Marker", win); + } else if (dynamic_cast(item)) { + if (Inkscape::UI::Tools::cc_item_is_connector(item)) { + tool_switch("Connector", win); + } + else { + tool_switch("Node", win); + } + } else if (dynamic_cast(item) || dynamic_cast(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(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::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(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::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> 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(sigc::ptr_fun(&tool_switch), win), "Select"); + win->add_action( "tool-toggle", sigc::bind(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 +#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 + +#include // Not ! To eventually allow a headless version! +#include + +#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 s = Glib::VariantBase::cast_dynamic >(value); + + std::vector 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 d = Glib::VariantBase::cast_dynamic >(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 d = Glib::VariantBase::cast_dynamic >(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 d = Glib::VariantBase::cast_dynamic >(value); + auto selection = app->get_active_selection(); + selection->scaleGrow(d.get()); +} + +void +transform_grow_step(const Glib::VariantBase& value, InkscapeApplication *app) +{ + Glib::Variant d = Glib::VariantBase::cast_dynamic >(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 d = Glib::VariantBase::cast_dynamic >(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> 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> 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(sigc::ptr_fun(&transform_translate), app)); + gapp->add_action_with_parameter( "transform-rotate", Double, sigc::bind(sigc::ptr_fun(&transform_rotate), app)); + gapp->add_action_with_parameter( "transform-scale", Double, sigc::bind(sigc::ptr_fun(&transform_scale), app)); + gapp->add_action_with_parameter( "transform-grow", Double, sigc::bind(sigc::ptr_fun(&transform_grow), app)); + gapp->add_action_with_parameter( "transform-grow-step", Double, sigc::bind(sigc::ptr_fun(&transform_grow_step), app)); + gapp->add_action_with_parameter( "transform-grow-screen", Double, sigc::bind(sigc::ptr_fun(&transform_grow_screen), app)); + gapp->add_action( "transform-remove", sigc::bind(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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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> 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(sigc::ptr_fun(&help_open_tutorial), "tutorial-basic")); + gapp->add_action( "tutorial-shapes", sigc::bind(sigc::ptr_fun(&help_open_tutorial), "tutorial-shapes")); + gapp->add_action( "tutorial-advanced", sigc::bind(sigc::ptr_fun(&help_open_tutorial), "tutorial-advanced")); + gapp->add_action( "tutorial-tracing", sigc::bind(sigc::ptr_fun(&help_open_tutorial), "tutorial-tracing")); + gapp->add_action( "tutorial-tracing-pixelart", sigc::bind(sigc::ptr_fun(&help_open_tutorial), "tutorial-tracing-pixelart")); + gapp->add_action( "tutorial-calligraphy", sigc::bind(sigc::ptr_fun(&help_open_tutorial), "tutorial-calligraphy")); + gapp->add_action( "tutorial-interpolate", sigc::bind(sigc::ptr_fun(&help_open_tutorial), "tutorial-interpolate")); + gapp->add_action( "tutorial-design", sigc::bind(sigc::ptr_fun(&help_open_tutorial), "tutorial-elements")); + gapp->add_action( "tutorial-tips", sigc::bind(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 + * + * 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 +#include + +#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> 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(sigc::ptr_fun(&undo), document)); + group->add_action( "redo", sigc::bind(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> 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 + * Tavmjong Bah + * + * Copyright (C) 2021, 2022 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#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::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::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> 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(sigc::ptr_fun(&canvas_show_grid_toggle), win)); + + win->add_action_bool( "canvas-commands-bar", sigc::bind(sigc::ptr_fun(&canvas_commands_bar_toggle), win), commands_toggle); + win->add_action_bool( "canvas-snap-controls-bar", sigc::bind(sigc::ptr_fun(&canvas_snap_controls_bar_toggle), win), snaptoolbox_toggle); + win->add_action_bool( "canvas-tool-control-bar", sigc::bind(sigc::ptr_fun(&canvas_tool_control_bar_toggle), win), toppanel_toggle); + win->add_action_bool( "canvas-toolbox", sigc::bind(sigc::ptr_fun(&canvas_toolbox_toggle), win), toolbox_toggle); + win->add_action_bool( "canvas-rulers", sigc::bind(sigc::ptr_fun(&canvas_rulers_toggle), win), rulers_toggle); + win->add_action_bool( "canvas-scroll-bars", sigc::bind(sigc::ptr_fun(&canvas_scroll_bars), win), scrollbars_toggle); + win->add_action_bool( "canvas-palette", sigc::bind(sigc::ptr_fun(&canvas_palette_toggle), win), palette_toggle); + win->add_action_bool( "canvas-statusbar", sigc::bind(sigc::ptr_fun(&canvas_statusbar_toggle), win), statusbar_toggle); + + win->add_action_bool ( "canvas-interface-mode", sigc::bind(sigc::ptr_fun(&canvas_interface_mode), win), interface_mode); + win->add_action( "view-fullscreen", sigc::bind(sigc::ptr_fun(&view_fullscreen), win)); + + win->add_action( "view-full-screen-focus", sigc::bind(sigc::ptr_fun(&view_full_screen_focus), win)); + win->add_action( "view-focus-toggle", sigc::bind(sigc::ptr_fun(&view_focus_toggle), win)); + + win->add_action( "canvas-command-palette", sigc::bind(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 + * + * 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 + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "actions-view-window.h" + +#include +#include + +#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> 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(sigc::ptr_fun(&window_new), win)); + win->add_action( "window-previous", sigc::bind(sigc::ptr_fun(&window_previous), win)); + win->add_action( "window-next", sigc::bind(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 + * + * 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 + +#include // Not ! To eventually allow a headless version! +#include + +#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> 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(sigc::ptr_fun(&window_open), app)); + gapp->add_action( "window-close", sigc::bind(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 : -- cgit v1.2.3