Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
selection.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Per-desktop selection container
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * MenTaLguY <mental@rydia.net>
8 * bulia byak <buliabyak@users.sf.net>
9 * Andrius R. <knutux@gmail.com>
10 * Abhishek Sharma
11 * Adrian Boguszewski
12 *
13 * Copyright (C) 2016 Adrian Boguszewski
14 * Copyright (C) 2006 Andrius R.
15 * Copyright (C) 2004-2005 MenTaLguY
16 * Copyright (C) 1999-2002 Lauris Kaplinski
17 * Copyright (C) 2001-2002 Ximian, Inc.
18 *
19 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
20 */
21
22#include "selection.h"
23
24#include <cmath>
25#include <glibmm/i18n.h>
26
27#include "desktop.h"
28#include "document-undo.h"
29#include "document.h"
30#include "inkscape.h"
31#include "layer-manager.h"
32#include "page-manager.h"
33
34#include "object/sp-defs.h"
35#include "object/sp-page.h"
36#include "object/sp-shape.h"
37#include "ui/icon-names.h"
40#include "ui/tools/node-tool.h"
41
42static constexpr auto SP_SELECTION_UPDATE_PRIORITY = G_PRIORITY_HIGH_IDLE + 1;
43
44namespace Inkscape {
45
49
51 : ObjectSet(document)
52{}
53
55 if (_idle) {
56 g_source_remove(_idle);
57 _idle = 0;
58 }
59}
60
61/* Handler for selected objects "modified" signal */
62
63void Selection::_schedule_modified(SPObject */*obj*/, guint flags) {
64 if (!this->_idle) {
65 /* Request handling to be run in _idle loop */
66 this->_idle = g_idle_add_full(SP_SELECTION_UPDATE_PRIORITY, GSourceFunc(&Selection::_emit_modified), this, nullptr);
67 }
68
69 /* Collect all flags */
70 this->_flags |= flags;
71}
72
74{
75 /* force new handler to be created if requested before we return */
76 selection->_idle = 0;
77 guint flags = selection->_flags;
78 selection->_flags = 0;
79
80 selection->_emitModified(flags);
81
82 /* drop this handler */
83 return FALSE;
84}
85
86void Selection::_emitModified(guint flags)
87{
88 _modified_signal.emit(this, flags);
89
90 if (!_desktop || isEmpty()) {
91 return;
92 }
93
94 auto &pm = _document->getPageManager();
95
96 // If the selected items have been moved to a new page...
97 if (auto item = singleItem()) {
98 pm.selectPage(item, false);
99 } else {
100 SPPage *page = pm.getPageFor(firstItem(), true);
101 for (auto this_item : this->items()) {
102 if (page != pm.getPageFor(this_item, true)) {
103 return;
104 }
105 }
106 pm.selectPage(page);
107 }
108}
109
110void Selection::_emitChanged(bool persist_selection_context)
111{
113 if (persist_selection_context) {
114 if (nullptr == _selection_context) {
118 }
119 } else {
121 }
122
126 if (_document && _desktop) {
127 if (auto item = singleItem()) {
128 if (_change_layer) {
129 auto layer = _desktop->layerManager().layerForObject(item);
130 if (layer && layer != _selection_context) {
132 }
133 }
134 if (_change_page) {
135 // This could be more complex if we want to be smarter.
137 }
138 }
140 }
141
142 _changed_signal.emit(this);
143}
144
146{
147 if (!_selection_context || _selection_context != obj) {
148 return;
149 }
150
151 _context_release_connection.disconnect();
152
154 _selection_context = nullptr;
155}
156
164
165std::vector<Inkscape::SnapCandidatePoint> Selection::getSnapPoints(SnapPreferences const *snapprefs) const {
166 std::vector<Inkscape::SnapCandidatePoint> p;
167
168 if (snapprefs != nullptr){
169 SnapPreferences snapprefs_dummy = *snapprefs; // create a local copy of the snapping prefs
170 snapprefs_dummy.setTargetSnappable(Inkscape::SNAPTARGET_ROTATION_CENTER, false); // locally disable snapping to the item center
171 auto items = const_cast<Selection *>(this)->items();
172 for (auto iter = items.begin(); iter != items.end(); ++iter) {
173 SPItem *this_item = *iter;
174 this_item->getSnappoints(p, &snapprefs_dummy);
175
176 //Include the transformation origin for snapping
177 //For a selection or group only the overall center is considered, not for each item individually
179 p.emplace_back(this_item->getCenter(), SNAPSOURCE_ROTATION_CENTER);
180 }
181 }
182 }
183
184 return p;
185}
186
187void Selection::setAnchor(double x, double y, bool set)
188{
189 constexpr double epsilon = 1e-12;
190 auto const pt = Geom::Point{x, y};
191 if (Geom::LInfty(anchor - pt) > epsilon || set != has_anchor) {
192 anchor = pt;
193 has_anchor = set;
194 _emitModified(SP_OBJECT_MODIFIED_FLAG);
195
196 // This allows each anchored-event to have it's own maybeDone
198 }
199}
200
201void Selection::scaleAnchored(double amount, bool fixed)
202{
203 if (Geom::OptRect bbox = visualBounds()) {
204 // Scale the amount by the size to get the final scale amount
205 if (fixed) {
206 double const max_len = bbox->maxExtent();
207 if (max_len + amount <= 1e-3) {
208 return;
209 }
210 amount = 1.0 + amount / max_len;
211 }
212
213 auto center = has_anchor ? bbox->min() + bbox->dimensions() * Geom::Scale(anchor) : bbox->midpoint();
214 scaleRelative(center, Geom::Scale(amount, amount));
215
217 ((amount > 0) ? "selector:grow:larger" : "selector:grow:smaller" ),
218 ((amount > 0) ? _("Grow") : _("Shrink")), INKSCAPE_ICON("tool-pointer"));
219 }
220}
221
222void Selection::rotateAnchored(double angle_degrees, double zoom)
223{
224 if (Geom::OptRect bbox = visualBounds()) {
225 auto actionkey = document()->action_key();
226
227 auto mid = center() ? *center() : bbox->midpoint();
228 auto center = has_anchor ? bbox->min() + bbox->dimensions() * Geom::Scale(anchor) : mid;
229
230 // Remember the center for previous rotations with the same undo action
231 if (has_anchor && (actionkey == "selector:rotate:ccw" || actionkey == "selector:rotate:cw")) {
233 }
234
235 if (auto d = desktop()) {
236 angle_degrees = d->yaxisdir() ? angle_degrees : -angle_degrees;
237 }
238
239 if (zoom != 1.0) {
240 Geom::Point m = bbox->midpoint();
241 unsigned i = 0;
242 if (center[Geom::X] < m[Geom::X]) {
243 i = 1;
244 }
245 if (center[Geom::Y] < m[Geom::Y]) {
246 i = 3 - i;
247 }
248
249 double const r = Geom::L2(bbox->corner(i) - center);
250 angle_degrees = 180 * atan2(angle_degrees / zoom, r) / M_PI;
251 }
252
253 rotateRelative(center, angle_degrees);
254
255 // Remember the rotation anchor for multiple rotation events.
257
258 if (angle_degrees == 90.0) {
259 DocumentUndo::maybeDone(document(), "selector:rotate:cw", _("Rotate 90\xc2\xb0 CW"), INKSCAPE_ICON("object-rotate-right"));
260 } else if (angle_degrees == -90.0) {
261 DocumentUndo::maybeDone(document(), "selector:rotate:ccw", _("Rotate 90\xc2\xb0 CCW"), INKSCAPE_ICON("object-rotate-left"));
262 } else {
264 ( ( angle_degrees > 0 )? "selector:rotate:ccw": "selector:rotate:cw" ),
265 _("Rotate"), INKSCAPE_ICON("tool-pointer"));
266 }
267 }
268}
269
270
272 g_return_val_if_fail(repr != nullptr, NULL);
273 auto object = _document->getObjectByRepr(repr);
274 assert(object == _document->getObjectById(repr->attribute("id")));
275 return object;
276}
277
279 auto items = this->items();
280 std::set<SPObject*> layers;
281 for (auto iter = items.begin(); iter != items.end(); ++iter) {
282 SPObject *layer = _desktop->layerManager().layerForObject(*iter);
283 layers.insert(layer);
284 }
285
286 return layers.size();
287}
288
290 auto items = this->items();
291 std::set<SPObject*> parents;
292 for (auto iter = items.begin(); iter != items.end(); ++iter) {
293 SPObject *parent = (*iter)->parent;
294 parents.insert(parent);
295 }
296 return parents.size();
297}
298
300 _modified_connections[object] = object->connectModified(sigc::mem_fun(*this, &Selection::_schedule_modified));
301}
302
304 _modified_connections.erase(object);
305}
306
308{
309 SelectionState state;
310
311 // Get IDs of selected objects
312 for (auto const * const item : items()) {
313 if (auto id = item->getId()) {
314 state.selected_ids.emplace_back(id);
315 }
316 }
317
318 // If node tool is active, get selected nodes
319 if (SPDesktop *desktop = this->desktop()) {
320 if (auto tool = dynamic_cast<Inkscape::UI::Tools::NodeTool *>(desktop->getTool())) {
321 for (auto const point : tool->_selected_nodes->_points_list) {
322 auto const node = dynamic_cast<Inkscape::UI::Node const *>(point);
323 if (!node)
324 continue;
325
326 auto const &nodeList = node->nodeList();
327 auto const &subpathList = nodeList.subpathList();
328
329 // Find subpath index
330 int sp = 0;
331 bool found_sp = false;
332 for (auto i = subpathList.begin(), e = subpathList.end(); i != e; ++i, ++sp) {
333 if (&**i == &nodeList) {
334 found_sp = true;
335 break;
336 }
337 }
338
339 // Find node index
340 int nl = 0;
341 bool found_nl = false;
342 for (auto j = nodeList.begin(), e = nodeList.end(); j != e; ++j, ++nl) {
343 if (&*j == node) {
344 found_nl = true;
345 break;
346 }
347 }
348
349 if (!(found_nl && found_sp)) {
350 g_warning("Something went wrong while trying to get node info. Please report a bug.");
351 continue;
352 }
353
354 if (auto id = subpathList.pm().item()->getId()) {
355 state.selected_nodes.emplace_back(id, sp, nl);
356 }
357 }
358 }
359 }
360
361 return state;
362}
363
365{
366 SPDesktop *desktop = this->desktop();
367 SPDocument *document = SP_ACTIVE_DOCUMENT;
368 SPDefs * defs = document->getDefs();
369 Inkscape::UI::Tools::NodeTool *tool = nullptr;
370 if (desktop) {
371 if (auto nt = dynamic_cast<Inkscape::UI::Tools::NodeTool*>(desktop->getTool())) {
372 tool = nt;
373 }
374 }
375
376 // update selection
377 std::vector<SPItem *> new_selection;
378 for (auto const &selected_id : state.selected_ids) {
379 auto const item = cast<SPItem>(document->getObjectById(selected_id.c_str()));
380 if (item && !defs->isAncestorOf(item)) {
381 new_selection.push_back(item);
382 }
383 }
384 if (size())
385 clear();
386 add(new_selection.begin(), new_selection.end());
387 new_selection.clear();
388
389 if (!tool) return;
390
391 auto const cps = tool->_selected_nodes;
392 cps->selectAll();
393 auto const point = !cps->_points_list.empty() ? cps->_points_list.front() : nullptr;
394 cps->clear();
395 if (!point) return;
396
397 auto const node = dynamic_cast<Inkscape::UI::Node const *>(point);
398 if (!node) return;
399
400 auto const &sp = node->nodeList().subpathList();
401 for (auto const &node_state : state.selected_nodes) {
402 int sp_count = 0;
403 for (auto j = sp.begin(); j != sp.end(); ++j, ++sp_count) {
404 if (sp_count != node_state.subpath_index)
405 continue;
406
407 int nt_count = 0;
408 for (auto k = (*j)->begin(); k != (*j)->end(); ++k, ++nt_count) {
409 if (nt_count == node_state.node_index) {
410 cps->insert(k.ptr());
411 break;
412 }
413 }
414 break;
415 }
416 }
417}
418
419} // namespace Inkscape
420
421/*
422 Local Variables:
423 mode:c++
424 c-file-style:"stroustrup"
425 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
426 indent-tabs-mode:nil
427 fill-column:99
428 End:
429*/
430// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
uint64_t page
Definition canvas.cpp:171
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
Scaling from the origin.
Definition transforms.h:150
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void resetKey(SPDocument *document)
SPObject * layerForObject(SPObject *object)
Return layer that contains object.
void setCurrentLayer(SPObject *object, bool clear=false)
Sets the current layer of the desktop.
SPGroup * currentLayer() const
Returns current top layer.
SPDesktop * desktop()
Returns the desktop the selection is bound to.
Definition object-set.h:390
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
void rotateRelative(const Geom::Point &, double)
SPDocument * _document
Definition object-set.h:531
std::optional< Geom::Point > center() const
Returns the rotation/skew center of the selection.
void clear()
Unselects all selected objects.
virtual void _emitChanged(bool persist_selection_context=false)
bool isEmpty()
Returns true if no items are selected.
int size()
Returns size of the selection.
SPDesktop * _desktop
Definition object-set.h:530
void scaleRelative(const Geom::Point &, const Geom::Scale &)
SPDocument * document()
Returns the document the selection is bound to.
Definition object-set.h:397
SPItem * firstItem() const
Returns the first selected item, returns nullptr if no items selected.
SPItem * singleItem()
Returns a single selected item.
Geom::OptRect visualBounds() const
bool selectPage(SPPage *page)
Set the given page as the selected page.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
SPObject * _selection_context
Definition selection.h:278
void _releaseSignals(SPObject *object) override
sigc::scoped_connection _context_release_connection
Definition selection.h:284
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
Definition selection.h:107
sigc::signal< void(Selection *, unsigned)> _modified_signal
Definition selection.h:287
std::vector< Inkscape::SnapCandidatePoint > getSnapPoints(SnapPreferences const *snapprefs) const
Compute the list of points in the selection that are to be considered for snapping from.
void _releaseContext(SPObject *obj)
Releases an active layer object that is being removed.
void setAnchor(double x, double y, bool set=true)
Set the anchor point of the selection, used for telling it how transforms should be anchored against.
std::unordered_map< SPObject *, sigc::scoped_connection > _modified_connections
Definition selection.h:283
SPObject * activeContext()
Returns active layer for selection (currentLayer or its parent).
void _connectSignals(SPObject *object) override
size_t numberOfLayers()
Returns the number of layers in which there are selected objects.
Geom::Point _previous_rotate_anchor
Definition selection.h:289
void _emitChanged(bool persist_selection_context=false) override
Issues changed selection signal.
static int _emit_modified(Selection *selection)
Issues modification notification signals.
Definition selection.cpp:73
void rotateAnchored(double angle_degrees, double zoom=1.0)
Rotate the selection, anchoring it against the center, or a selected anchor.
SelectionState getState()
Returns the current selection state including selected objects and nodes.
void scaleAnchored(double amount, bool fixed=true)
Scale the selection, anchoring it against the center, or a selected anchor.
void _emitModified(unsigned int flags)
Issues modified selection signal.
Definition selection.cpp:86
void setState(SelectionState const &state)
Restores a selection state previously obtained from getState()
size_t numberOfParents()
Returns the number of parents to which the selected objects belong.
void _schedule_modified(SPObject *obj, unsigned int flags)
Schedules an item modification signal to be sent.
Definition selection.cpp:63
Selection(SPDesktop *desktop)
Constructs an selection object, bound to a particular layer model.
Definition selection.cpp:46
sigc::signal< void(Selection *)> _changed_signal
Definition selection.h:286
~Selection() override
Definition selection.cpp:54
SPObject * _objectForXMLNode(XML::Node *repr) const
returns the SPObject corresponding to an xml node (if any).
Geom::Point anchor
Definition selection.h:201
Storing of snapping preferences.
void setTargetSnappable(Inkscape::SnapTargetType const target, bool enabled)
bool isTargetSnappable(Inkscape::SnapTargetType const target) const
void selectAll()
Select all points that this selection can contain.
Inkscape::UI::ControlPointSelection * _selected_nodes
Definition node-tool.h:50
Interface for refcounted XML nodes.
Definition node.h:80
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
To do: update description of desktop.
Definition desktop.h:149
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
Inkscape::LayerManager & layerManager()
Definition desktop.h:287
Typed SVG document implementation.
Definition document.h:101
SPObject * getObjectById(std::string const &id) const
Glib::ustring const & action_key() const
Definition document.h:362
Inkscape::PageManager & getPageManager()
Definition document.h:162
SPDefs * getDefs()
Return the main defs object for the document.
Definition document.cpp:246
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Point getCenter(bool ensure_uptodate=true) const
Definition sp-item.cpp:384
void getSnappoints(std::vector< Inkscape::SnapCandidatePoint > &p, Inkscape::SnapPreferences const *snapprefs=nullptr) const
Definition sp-item.cpp:1121
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
char const * getId() const
Returns the objects current ID string.
sigc::connection connectRelease(sigc::slot< void(SPObject *)> slot)
Connects to the release request signal.
Definition sp-object.h:237
SPObject * parent
Definition sp-object.h:189
bool isAncestorOf(SPObject const *object) const
True if object is non-NULL and this is some in/direct parent of object.
sigc::connection connectModified(sigc::slot< void(SPObject *, unsigned int)> slot)
Connects to the modification notification signal.
Definition sp-object.h:705
Control point selection - stores a set of control points and applies transformations to them.
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
SPItem * item
Inkscape::XML::Node * node
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
Coord LInfty(Point const &p)
Helper class to stream background task notifications as a series of messages.
@ SNAPSOURCE_ROTATION_CENTER
Definition snap-enums.h:52
@ SNAPTARGET_ROTATION_CENTER
Definition snap-enums.h:117
New node tool with support for multiple path editing.
Path manipulator - a component that edits a single path on-canvas.
static constexpr auto SP_SELECTION_UPDATE_PRIORITY
Definition selection.cpp:42
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
SPPage – a page object.
Complete state of a selection, including selected objects and nodes.
Definition selection.h:57
std::vector< std::string > selected_ids
Definition selection.h:58
std::vector< PathNodeState > selected_nodes
Definition selection.h:59
SPDesktop * desktop