Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
rect-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Rectangle drawing context
4 *
5 * Author:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
12 * Copyright (C) 2000-2005 authors
13 * Copyright (C) 2000-2001 Ximian, Inc.
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#include <cstring>
19#include <string>
20
21#include <gdk/gdkkeysyms.h>
22#include <glibmm/i18n.h>
23
24#include "context-fns.h"
25#include "desktop-style.h"
26#include "desktop.h"
27#include "document-undo.h"
28#include "document.h"
29#include "message-context.h"
30#include "selection-chemistry.h"
31#include "selection.h"
32
33#include "object/sp-rect.h"
34#include "object/sp-namedview.h"
35
36#include "ui/icon-names.h"
37#include "ui/shape-editor.h"
38#include "ui/tools/rect-tool.h"
40
41#include "util/units.h"
42
44
45namespace Inkscape::UI::Tools {
46
48 : ToolBase(desktop, "/tools/shapes/rect", "rect.svg")
49 , rx(0)
50 , ry(0)
51{
52 this->shape_editor = new ShapeEditor(desktop);
53
55 if (item) {
56 this->shape_editor->set_item(item);
57 }
58
59 this->sel_changed_connection.disconnect();
61 sigc::mem_fun(*this, &RectTool::selection_changed)
62 );
63
64 sp_event_context_read(this, "rx");
65 sp_event_context_read(this, "ry");
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
79
80 this->finishItem();
81 this->enableGrDrag(false);
82
83 this->sel_changed_connection.disconnect();
84
85 delete this->shape_editor;
86 this->shape_editor = nullptr;
87
88 /* fixme: This is necessary because we do not grab */
89 if (this->rect) {
90 this->finishItem();
91 }
92}
93
99 this->shape_editor->unset_item();
100 this->shape_editor->set_item(selection->singleItem());
101}
102
104 /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like
105 * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */
106 Glib::ustring name = val.getEntryName();
107
108 if ( name == "rx" ) {
109 this->rx = val.getDoubleLimited(); // prevents NaN and +/-Inf from messing up
110 } else if ( name == "ry" ) {
111 this->ry = val.getDoubleLimited();
112 }
113}
114
116{
117 inspect_event(event,
118 [&] (ButtonPressEvent const &event) {
119 if (event.num_press == 1 && event.button == 1) {
120 setup_for_drag_start(event);
121 }
122 },
123 [&] (CanvasEvent const &event) {}
124 );
125
126 return ToolBase::item_handler(item, event);
127}
128
130{
131 auto selection = _desktop->getSelection();
132 auto prefs = Inkscape::Preferences::get();
133
134 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
135
136 bool ret = false;
137
138 inspect_event(event,
139 [&] (ButtonPressEvent const &event) {
140 if (event.num_press == 1 && event.button == 1) {
141
142 auto const button_w = event.pos;
143
144 // Save drag origin
145 saveDragOrigin(button_w);
146 dragging = true;
147
148 // Remember clicked item, disregarding groups, honoring Alt.
149 item_to_select = sp_event_context_find_item (_desktop, button_w, event.modifiers & GDK_ALT_MASK, true);
150 // Postion center
151 auto button_dt = _desktop->w2d(button_w);
152 center = button_dt;
153
154 // Snap center
155 auto &m = _desktop->getNamedView()->snap_manager;
156 m.setup(_desktop);
157 m.freeSnapReturnByRef(button_dt, SNAPSOURCE_NODE_HANDLE);
158 m.unSetup();
159 center = button_dt;
160
161 grabCanvasEvents();
162 ret = true;
163 }
164 },
165 [&] (MotionEvent const &event) {
166 if (dragging && (event.modifiers & GDK_BUTTON1_MASK)) {
167 if (!checkDragMoved(event.pos)) {
168 return;
169 }
170
171 auto const motion_dt = _desktop->w2d(event.pos);
172 drag(motion_dt, event.modifiers); // This will also handle the snapping.
173
174 gobble_motion_events(GDK_BUTTON1_MASK);
175
176 ret = true;
177 } else if (!sp_event_context_knot_mouseover()) {
178 auto &m = _desktop->getNamedView()->snap_manager;
179 m.setup(_desktop);
180
181 auto const motion_dt = _desktop->w2d(event.pos);
182 m.preSnap(SnapCandidatePoint(motion_dt, SNAPSOURCE_NODE_HANDLE));
183 m.unSetup();
184 }
185 },
186 [&] (ButtonReleaseEvent const &event) {
187 xyp = {};
188 if (dragging && event.button == 1) {
189 dragging = false;
191
192 if (rect) {
193 // We've been dragging, finish the rect.
194 finishItem();
195 } else if (item_to_select) {
196 // No dragging, select clicked item if any.
197 if (event.modifiers & GDK_SHIFT_MASK) {
198 selection->toggle(item_to_select);
199 } else if (!selection->includes(item_to_select)) {
200 selection->set(item_to_select);
201 }
202 } else {
203 // Click in an empty space.
204 selection->clear();
205 }
206
207 item_to_select = nullptr;
208 ret = true;
209 }
211 },
212 [&] (KeyPressEvent const &event) {
213 switch (get_latin_keyval (event)) {
214 case GDK_KEY_Alt_L:
215 case GDK_KEY_Alt_R:
216 case GDK_KEY_Control_L:
217 case GDK_KEY_Control_R:
218 case GDK_KEY_Shift_L:
219 case GDK_KEY_Shift_R:
220 case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
221 case GDK_KEY_Meta_R:
222 if (!dragging){
224 _("<b>Ctrl</b>: make square or integer-ratio rect, lock a rounded corner circular"),
225 _("<b>Shift</b>: draw around the starting point"),
226 _("<b>Alt</b>: use with Ctrl to make square"));
227 }
228 break;
229 case GDK_KEY_x:
230 case GDK_KEY_X:
231 if (mod_alt_only(event)) {
232 _desktop->setToolboxFocusTo("rect-width");
233 ret = true;
234 }
235 break;
236
237 case GDK_KEY_g:
238 case GDK_KEY_G:
239 if (mod_shift_only(event)) {
241 ret = true;
242 }
243 break;
244
245 case GDK_KEY_Escape:
246 if (dragging) {
247 dragging = false;
249 // if drawing, cancel, otherwise pass it up for deselecting
250 cancel();
251 ret = true;
252 }
253 break;
254
255 case GDK_KEY_space:
256 if (dragging) {
258 dragging = false;
260
261 if (!within_tolerance) {
262 // we've been dragging, finish the rect
263 finishItem();
264 }
265 // do not return true, so that space would work switching to selector
266 }
267 break;
268
269 case GDK_KEY_Delete:
270 case GDK_KEY_KP_Delete:
271 case GDK_KEY_BackSpace:
272 ret = deleteSelectedDrag(mod_ctrl_only(event));
273 break;
274
275 default:
276 break;
277 }
278 },
279 [&] (KeyReleaseEvent const &event) {
280 switch (get_latin_keyval(event)) {
281 case GDK_KEY_Alt_L:
282 case GDK_KEY_Alt_R:
283 case GDK_KEY_Control_L:
284 case GDK_KEY_Control_R:
285 case GDK_KEY_Shift_L:
286 case GDK_KEY_Shift_R:
287 case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
288 case GDK_KEY_Meta_R:
290 break;
291 default:
292 break;
293 }
294 },
295 [&] (CanvasEvent const &event) {}
296 );
297
298 return ret || ToolBase::root_handler(event);
299}
300
301void RectTool::drag(Geom::Point const pt, unsigned state) {
302 if (!this->rect) {
304 return;
305 }
306
307 // Create object
309 Inkscape::XML::Node *repr = xml_doc->createElement("svg:rect");
310
311 // Set style
312 sp_desktop_apply_style_tool(_desktop, repr, "/tools/shapes/rect", false);
313
314 this->rect = cast<SPRect>(currentLayer()->appendChildRepr(repr));
316
318 this->rect->updateRepr();
319 }
320
321 Geom::Rect const r = Inkscape::snap_rectangular_box(_desktop, rect.get(), pt, this->center, state);
322
323 this->rect->setPosition(r.min()[Geom::X], r.min()[Geom::Y], r.dimensions()[Geom::X], r.dimensions()[Geom::Y]);
324
325 if (this->rx != 0.0) {
326 this->rect->setRx(true, this->rx);
327 }
328
329 if (this->ry != 0.0) {
330 if (this->rx == 0.0) {
331 this->rect->setRy(true, CLAMP(this->ry, 0, MIN(r.dimensions()[Geom::X], r.dimensions()[Geom::Y])/2));
332 } else {
333 this->rect->setRy(true, CLAMP(this->ry, 0, r.dimensions()[Geom::Y]));
334 }
335 }
336
337 // status text
338 double rdimx = r.dimensions()[Geom::X];
339 double rdimy = r.dimensions()[Geom::Y];
340
343 Glib::ustring xs = rdimx_q.string(_desktop->getNamedView()->display_units);
344 Glib::ustring ys = rdimy_q.string(_desktop->getNamedView()->display_units);
345
346 if (state & GDK_CONTROL_MASK) {
347 int ratio_x, ratio_y;
348 bool is_golden_ratio = false;
349
350 if (fabs (rdimx) > fabs (rdimy)) {
351 if (fabs(rdimx / rdimy - goldenratio) < 1e-6) {
352 is_golden_ratio = true;
353 }
354
355 ratio_x = (int) rint (rdimx / rdimy);
356 ratio_y = 1;
357 } else {
358 if (fabs(rdimy / rdimx - goldenratio) < 1e-6) {
359 is_golden_ratio = true;
360 }
361
362 ratio_x = 1;
363 ratio_y = (int) rint (rdimy / rdimx);
364 }
365
366 if (!is_golden_ratio) {
368 _("<b>Rectangle</b>: %s &#215; %s (constrained to ratio %d:%d); with <b>Shift</b> to draw around the starting point"),
369 xs.c_str(), ys.c_str(), ratio_x, ratio_y);
370 } else {
371 if (ratio_y == 1) {
373 _("<b>Rectangle</b>: %s &#215; %s (constrained to golden ratio 1.618 : 1); with <b>Shift</b> to draw around the starting point"),
374 xs.c_str(), ys.c_str());
375 } else {
377 _("<b>Rectangle</b>: %s &#215; %s (constrained to golden ratio 1 : 1.618); with <b>Shift</b> to draw around the starting point"),
378 xs.c_str(), ys.c_str());
379 }
380 }
381 } else {
383 _("<b>Rectangle</b>: %s &#215; %s; with <b>Ctrl</b> to make square, integer-ratio, or golden-ratio rectangle; with <b>Shift</b> to draw around the starting point"),
384 xs.c_str(), ys.c_str());
385 }
386}
387
389 this->message_context->clear();
390
391 if (rect) {
392 if (this->rect->width.computed == 0 || this->rect->height.computed == 0) {
393 this->cancel(); // Don't allow the creating of zero sized rectangle, for example when the start and and point snap to the snap grid point
394 return;
395 }
396
397 this->rect->updateRepr();
398 this->rect->doWriteTransform(this->rect->transform, nullptr, true);
399 // update while creating inside a LPE group
400 sp_lpe_item_update_patheffect(this->rect.get(), true, true);
402
403 DocumentUndo::done(_desktop->getDocument(), _("Create rectangle"), INKSCAPE_ICON("draw-rectangle"));
404
405 this->rect = nullptr;
406 }
407}
408
412
413 if (rect) {
415 }
416
417 this->within_tolerance = false;
418 xyp = {};
419 this->item_to_select = nullptr;
420
422}
423
424} // namespace Inkscape::UI::Tools
425
426/*
427 Local Variables:
428 mode:c++
429 c-file-style:"stroustrup"
430 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
431 indent-tabs-mode:nil
432 fill-column:99
433 End:
434*/
435// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint dimensions() const
Get rectangle's width and height as a point.
Two-dimensional point that doubles as a vector.
Definition point.h:66
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
void clear()
Unselects all selected objects.
SPItem * singleItem()
Returns a single selected item.
Data type representing a typeless value of a preference.
Glib::ustring getEntryName() const
Get the last component of the preference's path.
double getDoubleLimited(double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="") const
Interpret the preference as a limited floating point value.
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)
SPWeakPtr< SPRect > rect
Definition rect-tool.h:42
sigc::connection sel_changed_connection
Definition rect-tool.h:48
void drag(Geom::Point const pt, unsigned state)
void selection_changed(Selection *selection)
Callback that processes the "changed" signal on the selection; destroys old and creates new knotholde...
Definition rect-tool.cpp:98
RectTool(SPDesktop *desktop)
Definition rect-tool.cpp:47
bool root_handler(CanvasEvent const &event) override
bool item_handler(SPItem *item, CanvasEvent const &event) override
Handles item specific events.
void set(Preferences::Entry const &val) override
Called by our pref_observer if a preference has been changed.
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
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
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine transform
Definition sp-item.h:138
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1823
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.
void setRx(bool set, double value)
Definition sp-rect.cpp:361
SVGLength width
Definition sp-rect.h:78
void setRy(bool set, double value)
Definition sp-rect.cpp:371
void setPosition(double x, double y, double width, double height)
Definition sp-rect.cpp:352
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
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 sp_event_context_read(ToolBase *tool, char const *key)
Calls virtual set() function of ToolBase.
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)
bool mod_shift_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.
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
Glib::ustring name
Definition toolbars.cpp:55