Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
arc-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * Mitsuru Oka
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * bulia byak <buliabyak@users.sf.net>
10 * Johan Engelen <johan@shouraizou.nl>
11 * Abhishek Sharma
12 * Jon A. Cruz <jon@joncruz.org>
13 *
14 * Copyright (C) 2000-2006 Authors
15 * Copyright (C) 2000-2001 Ximian, Inc.
16 *
17 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
18 */
19
20#include "arc-tool.h"
21
22#include <glibmm/i18n.h>
23#include <gdk/gdkkeysyms.h>
24
25#include "context-fns.h"
26#include "desktop-style.h"
27#include "desktop.h"
28#include "document-undo.h"
29#include "document.h"
30#include "message-context.h"
31#include "preferences.h"
32#include "selection.h"
33#include "snap.h"
34
35#include "object/sp-ellipse.h"
36#include "object/sp-namedview.h"
37
38#include "ui/icon-names.h"
39#include "ui/modifiers.h"
40#include "ui/shape-editor.h"
41#include "ui/tools/tool-base.h"
43
44#include "util/units.h"
45
47
48namespace Inkscape::UI::Tools {
49
51 : ToolBase(desktop, "/tools/shapes/arc", "arc.svg")
52{
54
55 this->shape_editor = new ShapeEditor(desktop);
56
58 if (item) {
59 this->shape_editor->set_item(item);
60 }
61
62 this->sel_changed_connection.disconnect();
63 this->sel_changed_connection = selection->connectChanged(
64 sigc::mem_fun(*this, &ArcTool::selection_changed)
65 );
66
68 if (prefs->getBool("/tools/shapes/selcue")) {
69 this->enableSelectionCue();
70 }
71
72 if (prefs->getBool("/tools/shapes/gradientdrag")) {
73 this->enableGrDrag();
74 }
75}
76
78{
80 this->finishItem();
81 this->sel_changed_connection.disconnect();
82
83 this->enableGrDrag(false);
84
85 this->sel_changed_connection.disconnect();
86
87 delete this->shape_editor;
88 this->shape_editor = nullptr;
89
90 /* fixme: This is necessary because we do not grab */
91 if (this->arc) {
92 this->finishItem();
93 }
94}
95
101 this->shape_editor->unset_item();
102 this->shape_editor->set_item(selection->singleItem());
103}
104
106{
107 inspect_event(event,
108 [&] (ButtonPressEvent const &event) {
109 if (event.num_press == 1 && event.button == 1) {
110 setup_for_drag_start(event);
111 }
112 // motion and release are always on root (why?)
113 },
114 [&] (CanvasEvent const &event) {}
115 );
116
117 return ToolBase::item_handler(item, event);
118}
119
121{
122 auto selection = _desktop->getSelection();
123 auto prefs = Preferences::get();
124
125 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
126
127 bool ret = false;
128
129 inspect_event(event,
130 [&] (ButtonPressEvent const &event) {
131 if (event.num_press == 1 && event.button == 1) {
132 dragging = true;
133
134 setup_for_drag_start(event);
135 center = _desktop->w2d(event.pos);
136
137 // Snap center.
138 auto &m = _desktop->getNamedView()->snap_manager;
139 m.setup(_desktop);
140 m.freeSnapReturnByRef(center, SNAPSOURCE_NODE_HANDLE);
141
142 grabCanvasEvents();
143
144 ret = true;
145 m.unSetup();
146 }
147 },
148 [&] (MotionEvent const &event) {
149 if (dragging && (event.modifiers & GDK_BUTTON1_MASK)) {
150 if (!checkDragMoved(event.pos)) {
151 return;
152 }
153
154 auto const motion_dt = _desktop->w2d(event.pos);
155 drag(motion_dt, event.modifiers);
156
157 gobble_motion_events(GDK_BUTTON1_MASK);
158
159 ret = true;
160 } else if (!sp_event_context_knot_mouseover()) {
161 auto &m = _desktop->getNamedView()->snap_manager;
162 m.setup(_desktop);
163
164 auto const motion_dt = _desktop->w2d(event.pos);
165 m.preSnap(SnapCandidatePoint(motion_dt, SNAPSOURCE_NODE_HANDLE));
166 m.unSetup();
167 }
168 },
169 [&] (ButtonReleaseEvent const &event) {
170 xyp = {};
171 if (dragging && event.button == 1) {
172 dragging = false;
174
175 if (arc) {
176 // we've been dragging, finish the arc
177 finishItem();
178 } else if (item_to_select) {
179 // no dragging, select clicked item if any
180 if (event.modifiers & GDK_SHIFT_MASK) {
181 selection->toggle(item_to_select);
182 } else if (!selection->includes(item_to_select)) {
183 selection->set(item_to_select);
184 }
185 } else {
186 // click in an empty space
187 selection->clear();
188 }
189
190 xyp = {};
191 item_to_select = nullptr;
192 ret = true;
193 }
195 },
196 [&] (KeyPressEvent const &event) {
197 switch (get_latin_keyval(event)) {
198 case GDK_KEY_Alt_L:
199 case GDK_KEY_Alt_R:
200 case GDK_KEY_Control_L:
201 case GDK_KEY_Control_R:
202 case GDK_KEY_Shift_L:
203 case GDK_KEY_Shift_R:
204 case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
205 case GDK_KEY_Meta_R:
206 if (!dragging) {
208 _("<b>Ctrl</b>: make circle or integer-ratio ellipse, snap arc/segment angle"),
209 _("<b>Shift</b>: draw around the starting point"),
210 _("<b>Alt</b>: snap ellipse to mouse pointer"));
211 }
212 break;
213
214 case GDK_KEY_x:
215 case GDK_KEY_X:
216 if (mod_alt_only(event)) {
217 _desktop->setToolboxFocusTo("arc-rx");
218 ret = true;
219 }
220 break;
221
222 case GDK_KEY_Escape:
223 if (dragging) {
224 dragging = false;
226 // if drawing, cancel, otherwise pass it up for deselecting
227 cancel();
228 ret = true;
229 }
230 break;
231
232 case GDK_KEY_space:
233 if (dragging) {
235 dragging = false;
237
238 if (!within_tolerance) {
239 // we've been dragging, finish the arc
240 finishItem();
241 }
242 // do not return true, so that space would work switching to selector
243 }
244 break;
245
246 case GDK_KEY_Delete:
247 case GDK_KEY_KP_Delete:
248 case GDK_KEY_BackSpace:
249 ret = deleteSelectedDrag(mod_ctrl_only(event));
250 break;
251
252 default:
253 break;
254 }
255 },
256 [&] (KeyReleaseEvent const &event) {
257 switch (event.keyval) {
258 case GDK_KEY_Alt_L:
259 case GDK_KEY_Alt_R:
260 case GDK_KEY_Control_L:
261 case GDK_KEY_Control_R:
262 case GDK_KEY_Shift_L:
263 case GDK_KEY_Shift_R:
264 case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
265 case GDK_KEY_Meta_R:
267 break;
268
269 default:
270 break;
271 }
272 },
273 [&] (CanvasEvent const &event) {}
274 );
275
276 return ret || ToolBase::root_handler(event);
277}
278
279void ArcTool::drag(Geom::Point const &pt, unsigned state)
280{
281 if (!this->arc) {
283 return;
284 }
285
286 // Create object
288 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
289 repr->setAttribute("sodipodi:type", "arc");
290
291 // Set style
292 sp_desktop_apply_style_tool(_desktop, repr, "/tools/shapes/arc", false);
293
294 auto layer = currentLayer();
295 this->arc = cast<SPGenericEllipse>(layer->appendChildRepr(repr));
297 this->arc->transform = layer->i2doc_affine().inverse();
298 this->arc->updateRepr();
299 }
300
302 // Third is weirdly wrong, surely incrememnts should do something else.
305
306 Geom::Rect r = Inkscape::snap_rectangular_box(_desktop, arc.get(), pt, this->center, state);
307
308 Geom::Point dir = r.dimensions() / 2;
309
310
311 if (circle_edge) {
312 /* With Alt let the ellipse pass through the mouse pointer */
313 Geom::Point c = r.midpoint();
314
315 if (!confine) {
316 if (fabs(dir[Geom::X]) > 1E-6 && fabs(dir[Geom::Y]) > 1E-6) {
317 Geom::Affine const i2d ( (this->arc)->i2dt_affine() );
318 Geom::Point new_dir = pt * i2d - c;
319 new_dir[Geom::X] *= dir[Geom::Y] / dir[Geom::X];
320 double lambda = new_dir.length() / dir[Geom::Y];
321 r = Geom::Rect (c - lambda*dir, c + lambda*dir);
322 }
323 } else {
324 /* with Alt+Ctrl we generate a perfect circle, shift decides if initial click is centre or end of diameter */
325 if (off_center) {
326 r = Geom::Rect((2 * center) - pt, pt);
327 } else {
328 r = Geom::Rect(center, pt);
329 }
330 dir = r.dimensions()/2;
331 c = r.midpoint();
332 double l = dir.length();
333 Geom::Point d (l, l);
334 r = Geom::Rect (c - d, c + d);
335 }
336 }
337
338 this->arc->position_set(
339 r.midpoint()[Geom::X], r.midpoint()[Geom::Y],
340 r.dimensions()[Geom::X] / 2, r.dimensions()[Geom::Y] / 2);
341
342 double rdimx = r.dimensions()[Geom::X];
343 double rdimy = r.dimensions()[Geom::Y];
344
347 Glib::ustring xs = rdimx_q.string(_desktop->getNamedView()->display_units);
348 Glib::ustring ys = rdimy_q.string(_desktop->getNamedView()->display_units);
349
350 if (state & GDK_CONTROL_MASK) {
351 int ratio_x, ratio_y;
352 bool is_golden_ratio = false;
353
354 if (fabs (rdimx) > fabs (rdimy)) {
355 if (fabs(rdimx / rdimy - goldenratio) < 1e-6) {
356 is_golden_ratio = true;
357 }
358
359 ratio_x = (int) rint (rdimx / rdimy);
360 ratio_y = 1;
361 } else {
362 if (fabs(rdimy / rdimx - goldenratio) < 1e-6) {
363 is_golden_ratio = true;
364 }
365
366 ratio_x = 1;
367 ratio_y = (int) rint (rdimy / rdimx);
368 }
369
370 if (!is_golden_ratio) {
372 _("<b>Ellipse</b>: %s &#215; %s (constrained to ratio %d:%d); with <b>Shift</b> to draw around the starting point"),
373 xs.c_str(), ys.c_str(), ratio_x, ratio_y);
374 } else {
375 if (ratio_y == 1) {
377 _("<b>Ellipse</b>: %s &#215; %s (constrained to golden ratio 1.618 : 1); with <b>Shift</b> to draw around the starting point"),
378 xs.c_str(), ys.c_str());
379 } else {
381 _("<b>Ellipse</b>: %s &#215; %s (constrained to golden ratio 1 : 1.618); with <b>Shift</b> to draw around the starting point"),
382 xs.c_str(), ys.c_str());
383 }
384 }
385 } else {
386 this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Ellipse</b>: %s &#215; %s; with <b>Ctrl</b> to make circle, integer-ratio, or golden-ratio ellipse; with <b>Shift</b> to draw around the starting point"), xs.c_str(), ys.c_str());
387 }
388}
389
391{
392 message_context->clear();
393
394 if (arc) {
395 if (this->arc->rx.computed == 0 || this->arc->ry.computed == 0) {
396 this->cancel(); // Don't allow the creating of zero sized arc, for example when the start and and point snap to the snap grid point
397 return;
398 }
399
400 this->arc->updateRepr();
401 this->arc->doWriteTransform(this->arc->transform, nullptr, true);
402 // update while creating inside a LPE group
403 sp_lpe_item_update_patheffect(this->arc.get(), true, true);
405
406 DocumentUndo::done(_desktop->getDocument(), _("Create ellipse"), INKSCAPE_ICON("draw-ellipse"));
407
408 this->arc = nullptr;
409 }
410}
411
413{
416
417 if (arc) {
418 arc->deleteObject();
419 }
420
421 within_tolerance = false;
422 xyp = {};
423 item_to_select = nullptr;
424
426}
427
428} // namespace Inkscape::UI::Tools
429
430/*
431 Local Variables:
432 mode:c++
433 c-file-style:"stroustrup"
434 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
435 indent-tabs-mode:nil
436 fill-column:99
437 End:
438*/
439// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
CPoint midpoint() const
Get the point in the geometric center of the rectangle.
CPoint dimensions() const
Get rectangle's width and height as a point.
Two-dimensional point that doubles as a vector.
Definition point.h:66
Coord length() const
Compute the distance from origin.
Definition point.h:118
Axis aligned, non-empty rectangle.
Definition rect.h:92
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void cancel(SPDocument *document)
void clear()
removes our current message from the stack
bool active(int button_state) const
Test if this modifier is currently active.
static Modifier * get(Type index)
A function to turn an enum index into a modifier object.
Definition modifiers.h:213
void clear()
Unselects all selected objects.
SPItem * singleItem()
Returns a single selected item.
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
T * get() const
Definition weakptr.h:26
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
Definition selection.h:118
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
Definition selection.h:179
Class to store data for points which are snap candidates, either as a source or as a target.
void set_item(SPItem *item)
void unset_item(bool keep_knotholder=false)
sigc::connection sel_changed_connection
Definition arc-tool.h:47
SPWeakPtr< SPGenericEllipse > arc
Definition arc-tool.h:43
bool item_handler(SPItem *item, CanvasEvent const &event) override
Handles item specific events.
Definition arc-tool.cpp:105
bool root_handler(CanvasEvent const &event) override
Definition arc-tool.cpp:120
ArcTool(SPDesktop *desktop)
Definition arc-tool.cpp:50
void drag(Geom::Point const &pt, unsigned state)
Definition arc-tool.cpp:279
void selection_changed(Selection *selection)
Callback that processes the "changed" signal on the selection; destroys old and creates new knotholde...
Definition arc-tool.cpp:100
Base class for Event processors.
Definition tool-base.h:107
bool sp_event_context_knot_mouseover() const
Returns true if we're hovering above a knot (needed because we don't want to pre-snap in that case).
void ungrabCanvasEvents()
Ungrab events from the Canvas Catchall.
SPGroup * currentLayer() const
bool within_tolerance
are we still within tolerance of origin
Definition tool-base.h:148
bool dragging
are we dragging?
Definition tool-base.h:146
std::unique_ptr< MessageContext > message_context
Definition tool-base.h:193
SPItem * item_to_select
the item where mouse_press occurred, to be selected if this is a click not drag
Definition tool-base.h:152
Geom::IntPoint xyp
where drag started
Definition tool-base.h:145
virtual bool root_handler(CanvasEvent const &event)
bool checkDragMoved(Geom::Point const &pos)
Analyse the current position and return true once it has moved farther than tolerance from the drag o...
virtual bool item_handler(SPItem *item, CanvasEvent const &event)
Handles item specific events.
void enableGrDrag(bool enable=true)
void enableSelectionCue(bool enable=true)
Enables/disables the ToolBase's SelCue.
void discard_delayed_snap_event()
If a delayed snap event has been scheduled, this function will cancel it.
MessageContext * defaultMessageContext() const
Definition tool-base.h:123
bool deleteSelectedDrag(bool just_one)
Delete a selected GrDrag point.
Glib::ustring string(Unit const *u) const
Return a printable string of the value in the specified unit.
Definition units.cpp:515
Interface for refcounted XML nodes.
Definition node.h:80
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
void setToolboxFocusTo(char const *label)
Definition desktop.cpp:1128
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
SPDocument * doc() const
Definition desktop.h:159
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
void position_set(double x, double y, double rx, double ry)
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine transform
Definition sp-item.h:138
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1665
SnapManager snap_manager
Inkscape::Util::Unit const * display_units
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
float computed
Definition svg-length.h:50
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
constexpr double goldenratio
Definition context-fns.h:19
double c[8][4]
void sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, Glib::ustring const &tool_path, bool with_text)
Apply the desktop's current style or the tool style to repr.
Editable view implementation.
TODO: insert short description here.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
SPItem * item
Interface for locally managing a current status message.
static R & release(R &r)
Decrements the reference count of a anchored object.
void sp_event_show_modifier_tip(MessageContext *message_context, KeyEvent const &event, char const *ctrl_tip, char const *shift_tip, char const *alt_tip)
Show tool context specific modifier tip.
void gobble_motion_events(unsigned mask)
Definition tool-base.h:244
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_NODE_HANDLE
Definition snap-enums.h:43
bool mod_ctrl_only(unsigned modifiers)
Geom::Rect snap_rectangular_box(SPDesktop const *desktop, SPItem *item, Geom::Point const &pt, Geom::Point const &center, int state)
bool mod_alt_only(unsigned modifiers)
@ IMMEDIATE_MESSAGE
Definition message.h:27
bool have_viable_layer(SPDesktop *desktop, MessageContext *message)
Check to see if the current layer is both unhidden and unlocked.
Singleton class to access the preferences file in a convenient way.
Inkscape::ShapeEditor This is a container class which contains a knotholder for shapes.
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
A mouse button (left/right/middle) is pressed.
A mouse button (left/right/middle) is released.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
A key has been pressed.
A key has been released.
Movement of the mouse pointer.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
SPDesktop * desktop