// 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 :