From 35a96bde514a8897f6f0fcc41c5833bf63df2e2a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 18:29:01 +0200 Subject: Adding upstream version 1.0.2. Signed-off-by: Daniel Baumann --- src/ui/tools/lpe-tool.cpp | 494 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100644 src/ui/tools/lpe-tool.cpp (limited to 'src/ui/tools/lpe-tool.cpp') diff --git a/src/ui/tools/lpe-tool.cpp b/src/ui/tools/lpe-tool.cpp new file mode 100644 index 0000000..d1e7e18 --- /dev/null +++ b/src/ui/tools/lpe-tool.cpp @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LPEToolContext: a context for a generic tool composed of subtools that are given by LPEs + * + * Authors: + * Maximilian Albert + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2008 Maximilian Albert + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#include <2geom/sbasis-geometric.h> + +#include "desktop.h" +#include "document.h" +#include "message-context.h" +#include "message-stack.h" +#include "selection.h" + +#include "display/curve.h" +#include "display/canvas-bpath.h" +#include "display/canvas-text.h" + +#include "object/sp-path.h" + +#include "ui/pixmaps/cursor-crosshairs.xpm" + +#include "util/units.h" + +#include "ui/toolbar/lpe-toolbar.h" +#include "ui/tools/lpe-tool.h" +#include "ui/shape-editor.h" + +using Inkscape::Util::unit_table; +using Inkscape::UI::Tools::PenTool; + +const int num_subtools = 8; + +SubtoolEntry lpesubtools[] = { + // this must be here to account for the "all inactive" action + {Inkscape::LivePathEffect::INVALID_LPE, "draw-geometry-inactive"}, + {Inkscape::LivePathEffect::LINE_SEGMENT, "draw-geometry-line-segment"}, + {Inkscape::LivePathEffect::CIRCLE_3PTS, "draw-geometry-circle-from-three-points"}, + {Inkscape::LivePathEffect::CIRCLE_WITH_RADIUS, "draw-geometry-circle-from-radius"}, + {Inkscape::LivePathEffect::PARALLEL, "draw-geometry-line-parallel"}, + {Inkscape::LivePathEffect::PERP_BISECTOR, "draw-geometry-line-perpendicular"}, + {Inkscape::LivePathEffect::ANGLE_BISECTOR, "draw-geometry-angle-bisector"}, + {Inkscape::LivePathEffect::MIRROR_SYMMETRY, "draw-geometry-mirror"} +}; + +namespace Inkscape { +namespace UI { +namespace Tools { + +void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data); + +const std::string& LpeTool::getPrefsPath() { + return LpeTool::prefsPath; +} + +const std::string LpeTool::prefsPath = "/tools/lpetool"; + +LpeTool::LpeTool() + : PenTool(cursor_crosshairs_xpm) + , shape_editor(nullptr) + , canvas_bbox(nullptr) + , mode(Inkscape::LivePathEffect::BEND_PATH) +// TODO: pointer? + , measuring_items(new std::map) +{ +} + +LpeTool::~LpeTool() { + delete this->shape_editor; + this->shape_editor = nullptr; + + if (this->canvas_bbox) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(this->canvas_bbox)); + this->canvas_bbox = nullptr; + } + + lpetool_delete_measuring_items(this); + delete this->measuring_items; + this->measuring_items = nullptr; + + this->sel_changed_connection.disconnect(); +} + +void LpeTool::setup() { + PenTool::setup(); + + Inkscape::Selection *selection = this->desktop->getSelection(); + SPItem *item = selection->singleItem(); + + this->sel_changed_connection.disconnect(); + this->sel_changed_connection = + selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_lpetool_context_selection_changed), (gpointer)this)); + + this->shape_editor = new ShapeEditor(this->desktop); + + lpetool_context_switch_mode(this, Inkscape::LivePathEffect::INVALID_LPE); + lpetool_context_reset_limiting_bbox(this); + lpetool_create_measuring_items(this); + +// TODO temp force: + this->enableSelectionCue(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (item) { + this->shape_editor->set_item(item); + } + + if (prefs->getBool("/tools/lpetool/selcue")) { + this->enableSelectionCue(); + } +} + +/** + * Callback that processes the "changed" signal on the selection; + * destroys old and creates new nodepath and reassigns listeners to the new selected item's repr. + */ +void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data) +{ + LpeTool *lc = SP_LPETOOL_CONTEXT(data); + + lc->shape_editor->unset_item(); + SPItem *item = selection->singleItem(); + lc->shape_editor->set_item(item); +} + +void LpeTool::set(const Inkscape::Preferences::Entry& val) { + if (val.getEntryName() == "mode") { + Inkscape::Preferences::get()->setString("/tools/geometric/mode", "drag"); + SP_PEN_CONTEXT(this)->mode = PenTool::MODE_DRAG; + } +} + +bool LpeTool::item_handler(SPItem* item, GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + { + // select the clicked item but do nothing else + Inkscape::Selection * const selection = this->desktop->getSelection(); + selection->clear(); + selection->add(item); + ret = TRUE; + break; + } + case GDK_BUTTON_RELEASE: + // TODO: do we need to catch this or can we pass it on to the parent handler? + ret = TRUE; + break; + default: + break; + } + + if (!ret) { + ret = PenTool::item_handler(item, event); + } + + return ret; +} + +bool LpeTool::root_handler(GdkEvent* event) { + Inkscape::Selection *selection = desktop->getSelection(); + + bool ret = false; + + if (this->hasWaitingLPE()) { + // quit when we are waiting for a LPE to be applied + //ret = ((ToolBaseClass *) sp_lpetool_context_parent_class)->root_handler(event_context, event); + return PenTool::root_handler(event); + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + if (this->mode == Inkscape::LivePathEffect::INVALID_LPE) { + // don't do anything for now if we are inactive (except clearing the selection + // since this was a click into empty space) + selection->clear(); + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Choose a construction tool from the toolbar.")); + ret = true; + break; + } + + // save drag origin + this->xp = (gint) event->button.x; + this->yp = (gint) event->button.y; + this->within_tolerance = true; + + using namespace Inkscape::LivePathEffect; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int mode = prefs->getInt("/tools/lpetool/mode"); + EffectType type = lpesubtools[mode].type; + + //bool over_stroke = lc->shape_editor->is_over_stroke(Geom::Point(event->button.x, event->button.y), true); + + this->waitForLPEMouseClicks(type, Inkscape::LivePathEffect::Effect::acceptsNumClicks(type)); + + // we pass the mouse click on to pen tool as the first click which it should collect + //ret = ((ToolBaseClass *) sp_lpetool_context_parent_class)->root_handler(event_context, event); + ret = PenTool::root_handler(event); + } + break; + + + case GDK_BUTTON_RELEASE: + { + /** + break; + **/ + } + + case GDK_KEY_PRESS: + /** + switch (get_latin_keyval (&event->key)) { + } + break; + **/ + + case GDK_KEY_RELEASE: + /** + switch (get_latin_keyval(&event->key)) { + case GDK_Control_L: + case GDK_Control_R: + dc->_message_context->clear(); + break; + default: + break; + } + **/ + + default: + break; + } + + if (!ret) { + ret = PenTool::root_handler(event); + } + + return ret; +} + +/* + * Finds the index in the list of geometric subtools corresponding to the given LPE type. + * Returns -1 if no subtool is found. + */ +int +lpetool_mode_to_index(Inkscape::LivePathEffect::EffectType const type) { + for (int i = 0; i < num_subtools; ++i) { + if (lpesubtools[i].type == type) { + return i; + } + } + return -1; +} + +/* + * Checks whether an item has a construction applied as LPE and if so returns the index in + * lpesubtools of this construction + */ +int lpetool_item_has_construction(LpeTool */*lc*/, SPItem *item) +{ + if (!SP_IS_LPE_ITEM(item)) { + return -1; + } + + Inkscape::LivePathEffect::Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); + if (!lpe) { + return -1; + } + return lpetool_mode_to_index(lpe->effectType()); +} + +/* + * Attempts to perform the construction of the given type (i.e., to apply the corresponding LPE) to + * a single selected item. Returns whether we succeeded. + */ +bool +lpetool_try_construction(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type) +{ + Inkscape::Selection *selection = lc->desktop->getSelection(); + SPItem *item = selection->singleItem(); + + // TODO: should we check whether type represents a valid geometric construction? + if (item && SP_IS_LPE_ITEM(item) && Inkscape::LivePathEffect::Effect::acceptsNumClicks(type) == 0) { + Inkscape::LivePathEffect::Effect::createAndApply(type, lc->desktop->getDocument(), item); + return true; + } + return false; +} + +void +lpetool_context_switch_mode(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type) +{ + int index = lpetool_mode_to_index(type); + if (index != -1) { + lc->mode = type; + auto tb = dynamic_cast(lc->desktop->get_toolbar_by_name("LPEToolToolbar")); + + if(tb) { + tb->set_mode(index); + } else { + std::cerr << "Could not access LPE toolbar" << std::endl; + } + } else { + g_warning ("Invalid mode selected: %d", type); + return; + } +} + +void +lpetool_get_limiting_bbox_corners(SPDocument *document, Geom::Point &A, Geom::Point &B) { + Geom::Coord w = document->getWidth().value("px"); + Geom::Coord h = document->getHeight().value("px"); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + double ulx = prefs->getDouble("/tools/lpetool/bbox_upperleftx", 0); + double uly = prefs->getDouble("/tools/lpetool/bbox_upperlefty", 0); + double lrx = prefs->getDouble("/tools/lpetool/bbox_lowerrightx", w); + double lry = prefs->getDouble("/tools/lpetool/bbox_lowerrighty", h); + + A = Geom::Point(ulx, uly); + B = Geom::Point(lrx, lry); +} + +/* + * Reads the limiting bounding box from preferences and draws it on the screen + */ +// TODO: Note that currently the bbox is not user-settable; we simply use the page borders +void +lpetool_context_reset_limiting_bbox(LpeTool *lc) +{ + if (lc->canvas_bbox) { + sp_canvas_item_destroy(lc->canvas_bbox); + lc->canvas_bbox = nullptr; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (!prefs->getBool("/tools/lpetool/show_bbox", true)) + return; + + SPDocument *document = lc->desktop->getDocument(); + + Geom::Point A, B; + lpetool_get_limiting_bbox_corners(document, A, B); + Geom::Affine doc2dt(lc->desktop->doc2dt()); + A *= doc2dt; + B *= doc2dt; + + Geom::Rect rect(A, B); + SPCurve *curve = SPCurve::new_from_rect(rect); + + lc->canvas_bbox = sp_canvas_bpath_new (lc->desktop->getControls(), curve); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(lc->canvas_bbox), 0x0000ffff, 0.8, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT, 5, 5); +} + +static void +set_pos_and_anchor(SPCanvasText *canvas_text, const Geom::Piecewise > &pwd2, + const double t, const double length, bool /*use_curvature*/ = false) +{ + using namespace Geom; + + Piecewise > pwd2_reparam = arc_length_parametrization(pwd2, 2 , 0.1); + double t_reparam = pwd2_reparam.cuts.back() * t; + Point pos = pwd2_reparam.valueAt(t_reparam); + Point dir = unit_vector(derivative(pwd2_reparam).valueAt(t_reparam)); + Point n = -rot90(dir); + double angle = Geom::angle_between(dir, Point(1,0)); + + sp_canvastext_set_coords(canvas_text, pos + n * length); + sp_canvastext_set_anchor_manually(canvas_text, std::sin(angle), -std::cos(angle)); +} + +void +lpetool_create_measuring_items(LpeTool *lc, Inkscape::Selection *selection) +{ + if (!selection) { + selection = lc->desktop->getSelection(); + } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool show = prefs->getBool("/tools/lpetool/show_measuring_info", true); + + SPPath *path; + SPCurve *curve; + SPCanvasText *canvas_text; + SPCanvasGroup *tmpgrp = lc->desktop->getTempGroup(); + gchar *arc_length; + double lengthval; + auto items= selection->items(); + for(auto i=items.begin();i!=items.end();++i){ + if (SP_IS_PATH(*i)) { + path = SP_PATH(*i); + curve = path->getCurve(); + Geom::Piecewise > pwd2 = paths_to_pw(curve->get_pathvector()); + canvas_text = (SPCanvasText *) sp_canvastext_new(tmpgrp, lc->desktop, Geom::Point(0,0), ""); + if (!show) + sp_canvas_item_hide(SP_CANVAS_ITEM(canvas_text)); + + Inkscape::Util::Unit const * unit = nullptr; + if (prefs->getString("/tools/lpetool/unit").compare("")) { + unit = unit_table.getUnit(prefs->getString("/tools/lpetool/unit")); + } else { + unit = unit_table.getUnit("px"); + } + + lengthval = Geom::length(pwd2); + lengthval = Inkscape::Util::Quantity::convert(lengthval, "px", unit); + arc_length = g_strdup_printf("%.2f %s", lengthval, unit->abbr.c_str()); + sp_canvastext_set_text (canvas_text, arc_length); + set_pos_and_anchor(canvas_text, pwd2, 0.5, 10); + // TODO: must we free arc_length? + (*lc->measuring_items)[path] = SP_CANVAS_ITEM(canvas_text); + } + } +} + +void +lpetool_delete_measuring_items(LpeTool *lc) +{ + std::map::iterator i; + for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) { + sp_canvas_item_destroy(i->second); + } + lc->measuring_items->clear(); +} + +void +lpetool_update_measuring_items(LpeTool *lc) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + for ( std::map::iterator i = lc->measuring_items->begin(); + i != lc->measuring_items->end(); + ++i ) + { + SPPath *path = i->first; + SPCurve *curve = path->getCurve(); + Geom::Piecewise > pwd2 = Geom::paths_to_pw(curve->get_pathvector()); + Inkscape::Util::Unit const * unit = nullptr; + if (prefs->getString("/tools/lpetool/unit").compare("")) { + unit = unit_table.getUnit(prefs->getString("/tools/lpetool/unit")); + } else { + unit = unit_table.getUnit("px"); + } + double lengthval = Geom::length(pwd2); + lengthval = Inkscape::Util::Quantity::convert(lengthval, "px", unit); + gchar *arc_length = g_strdup_printf("%.2f %s", lengthval, unit->abbr.c_str()); + sp_canvastext_set_text (SP_CANVASTEXT(i->second), arc_length); + set_pos_and_anchor(SP_CANVASTEXT(i->second), pwd2, 0.5, 10); + // TODO: must we free arc_length? + } +} + +void +lpetool_show_measuring_info(LpeTool *lc, bool show) +{ + std::map::iterator i; + for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) { + if (show) { + sp_canvas_item_show(i->second); + } else { + sp_canvas_item_hide(i->second); + } + } +} + +} +} +} + +/* + 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 : -- cgit v1.2.3