1 /** \file
2 * Per-desktop selection container
3 *
4 * Authors:
5 * Lauris Kaplinski <lauris@kaplinski.com>
6 * MenTaLguY <mental@rydia.net>
7 * bulia byak <buliabyak@users.sf.net>
8 * Andrius R. <knutux@gmail.com>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 2006 Andrius R.
12 * Copyright (C) 2004-2005 MenTaLguY
13 * Copyright (C) 1999-2002 Lauris Kaplinski
14 * Copyright (C) 2001-2002 Ximian, Inc.
15 *
16 * Released under GNU GPL, read the file 'COPYING' for more information
17 */
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 #include "macros.h"
23 #include "inkscape-private.h"
24 #include "desktop.h"
25 #include "desktop-handles.h"
26 #include "document.h"
27 #include "selection.h"
28 #include "helper/recthull.h"
29 #include "xml/repr.h"
31 #include "sp-shape.h"
32 #include "sp-path.h"
33 #include "sp-item-group.h"
34 #include "box3d.h"
35 #include "box3d.h"
36 #include "persp3d.h"
38 #include <sigc++/functors/mem_fun.h>
40 #define SP_SELECTION_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE + 1)
42 namespace Inkscape {
44 Selection::Selection(SPDesktop *desktop) :
45 _objs(NULL),
46 _reprs(NULL),
47 _items(NULL),
48 _desktop(desktop),
49 _selection_context(NULL),
50 _flags(0),
51 _idle(0)
52 {
53 }
55 Selection::~Selection() {
56 _clear();
57 _desktop = NULL;
58 if (_idle) {
59 g_source_remove(_idle);
60 _idle = 0;
61 }
62 }
64 /* Handler for selected objects "modified" signal */
66 void Selection::_schedule_modified(SPObject */*obj*/, guint flags) {
67 if (!this->_idle) {
68 /* Request handling to be run in _idle loop */
69 this->_idle = g_idle_add_full(SP_SELECTION_UPDATE_PRIORITY, GSourceFunc(&Selection::_emit_modified), this, NULL);
70 }
72 /* Collect all flags */
73 this->_flags |= flags;
74 }
76 gboolean
77 Selection::_emit_modified(Selection *selection)
78 {
79 /* force new handler to be created if requested before we return */
80 selection->_idle = 0;
81 guint flags = selection->_flags;
82 selection->_flags = 0;
84 selection->_emitModified(flags);
86 /* drop this handler */
87 return FALSE;
88 }
90 void Selection::_emitModified(guint flags) {
91 inkscape_selection_modified(this, flags);
92 _modified_signal.emit(this, flags);
93 }
95 void Selection::_emitChanged(bool persist_selection_context/* = false */) {
96 if (persist_selection_context) {
97 if (NULL == _selection_context) {
98 _selection_context = desktop()->currentLayer();
99 sp_object_ref(_selection_context, NULL);
100 _context_release_connection = _selection_context->connectRelease(sigc::mem_fun(*this, &Selection::_releaseContext));
101 }
102 } else {
103 _releaseContext(_selection_context);
104 }
106 inkscape_selection_changed(this);
107 _changed_signal.emit(this);
108 }
110 void
111 Selection::_releaseContext(SPObject *obj)
112 {
113 if (NULL == _selection_context || _selection_context != obj)
114 return;
116 _context_release_connection.disconnect();
118 sp_object_unref(_selection_context, NULL);
119 _selection_context = NULL;
120 }
122 void Selection::_invalidateCachedLists() {
123 g_slist_free(_items);
124 _items = NULL;
126 g_slist_free(_reprs);
127 _reprs = NULL;
128 }
130 void Selection::_clear() {
131 _invalidateCachedLists();
132 while (_objs) {
133 SPObject *obj=reinterpret_cast<SPObject *>(_objs->data);
134 _remove(obj);
135 }
136 }
138 SPObject *Selection::activeContext() {
139 if (NULL != _selection_context)
140 return _selection_context;
141 return desktop()->currentLayer();
142 }
144 bool Selection::includes(SPObject *obj) const {
145 if (obj == NULL)
146 return FALSE;
148 g_return_val_if_fail(SP_IS_OBJECT(obj), FALSE);
150 return ( g_slist_find(_objs, obj) != NULL );
151 }
153 void Selection::add(SPObject *obj, bool persist_selection_context/* = false */) {
154 g_return_if_fail(obj != NULL);
155 g_return_if_fail(SP_IS_OBJECT(obj));
157 if (includes(obj)) {
158 return;
159 }
161 _invalidateCachedLists();
162 _add(obj);
163 _emitChanged(persist_selection_context);
164 }
166 void Selection::add_3D_boxes_recursively(SPObject *obj) {
167 std::list<SPBox3D *> boxes = box3d_extract_boxes(obj);
169 for (std::list<SPBox3D *>::iterator i = boxes.begin(); i != boxes.end(); ++i) {
170 SPBox3D *box = *i;
171 _3dboxes.push_back(box);
172 }
173 }
175 void Selection::_add(SPObject *obj) {
176 // unselect any of the item's ancestors and descendants which may be selected
177 // (to prevent double-selection)
178 _removeObjectDescendants(obj);
179 _removeObjectAncestors(obj);
181 _objs = g_slist_prepend(_objs, obj);
183 add_3D_boxes_recursively(obj);
185 _release_connections[obj] = obj->connectRelease(sigc::mem_fun(*this, (void (Selection::*)(SPObject *))&Selection::remove));
186 _modified_connections[obj] = obj->connectModified(sigc::mem_fun(*this, &Selection::_schedule_modified));
187 }
189 void Selection::set(SPObject *object, bool persist_selection_context) {
190 _clear();
191 add(object, persist_selection_context);
192 }
194 void Selection::toggle(SPObject *obj) {
195 if (includes (obj)) {
196 remove (obj);
197 } else {
198 add(obj);
199 }
200 }
202 void Selection::remove(SPObject *obj) {
203 g_return_if_fail(obj != NULL);
204 g_return_if_fail(SP_IS_OBJECT(obj));
205 g_return_if_fail(includes(obj));
207 _invalidateCachedLists();
208 _remove(obj);
209 _emitChanged();
210 }
212 void Selection::remove_3D_boxes_recursively(SPObject *obj) {
213 std::list<SPBox3D *> boxes = box3d_extract_boxes(obj);
215 for (std::list<SPBox3D *>::iterator i = boxes.begin(); i != boxes.end(); ++i) {
216 SPBox3D *box = *i;
217 std::list<SPBox3D *>::iterator b = std::find(_3dboxes.begin(), _3dboxes.end(), box);
218 if (b == _3dboxes.end()) {
219 g_print ("Warning! Trying to remove unselected box from selection.\n");
220 return;
221 }
222 _3dboxes.erase(b);
223 }
224 }
226 void Selection::_remove(SPObject *obj) {
227 _modified_connections[obj].disconnect();
228 _modified_connections.erase(obj);
230 _release_connections[obj].disconnect();
231 _release_connections.erase(obj);
233 remove_3D_boxes_recursively(obj);
235 _objs = g_slist_remove(_objs, obj);
236 }
238 void Selection::setList(GSList const *list) {
239 _clear();
241 if ( list != NULL ) {
242 for ( GSList const *iter = list ; iter != NULL ; iter = iter->next ) {
243 _add(reinterpret_cast<SPObject *>(iter->data));
244 }
245 }
247 _emitChanged();
248 }
250 void Selection::addList(GSList const *list) {
252 if (list == NULL)
253 return;
255 _invalidateCachedLists();
257 for ( GSList const *iter = list ; iter != NULL ; iter = iter->next ) {
258 SPObject *obj = reinterpret_cast<SPObject *>(iter->data);
259 if (includes(obj)) {
260 continue;
261 }
262 _add (obj);
263 }
265 _emitChanged();
266 }
268 void Selection::setReprList(GSList const *list) {
269 _clear();
271 for ( GSList const *iter = list ; iter != NULL ; iter = iter->next ) {
272 SPObject *obj=_objectForXMLNode(reinterpret_cast<Inkscape::XML::Node *>(iter->data));
273 if (obj) {
274 _add(obj);
275 }
276 }
278 _emitChanged();
279 }
281 void Selection::clear() {
282 _clear();
283 _emitChanged();
284 }
286 GSList const *Selection::list() {
287 return _objs;
288 }
290 GSList const *Selection::itemList() {
291 if (_items) {
292 return _items;
293 }
295 for ( GSList const *iter=_objs ; iter != NULL ; iter = iter->next ) {
296 SPObject *obj=reinterpret_cast<SPObject *>(iter->data);
297 if (SP_IS_ITEM(obj)) {
298 _items = g_slist_prepend(_items, SP_ITEM(obj));
299 }
300 }
301 _items = g_slist_reverse(_items);
303 return _items;
304 }
306 GSList const *Selection::reprList() {
307 if (_reprs) { return _reprs; }
309 for ( GSList const *iter=itemList() ; iter != NULL ; iter = iter->next ) {
310 SPObject *obj=reinterpret_cast<SPObject *>(iter->data);
311 _reprs = g_slist_prepend(_reprs, SP_OBJECT_REPR(obj));
312 }
313 _reprs = g_slist_reverse(_reprs);
315 return _reprs;
316 }
318 std::list<Persp3D *> const Selection::perspList() {
319 std::list<Persp3D *> pl;
320 for (std::list<SPBox3D *>::iterator i = _3dboxes.begin(); i != _3dboxes.end(); ++i) {
321 Persp3D *persp = box3d_get_perspective(*i);
322 if (std::find(pl.begin(), pl.end(), persp) == pl.end())
323 pl.push_back(persp);
324 }
325 return pl;
326 }
328 std::list<SPBox3D *> const Selection::box3DList(Persp3D *persp) {
329 std::list<SPBox3D *> boxes;
330 if (persp) {
331 SPBox3D *box;
332 for (std::list<SPBox3D *>::iterator i = _3dboxes.begin(); i != _3dboxes.end(); ++i) {
333 box = *i;
334 if (persp == box3d_get_perspective(box))
335 boxes.push_back(box);
336 }
337 } else {
338 boxes = _3dboxes;
339 }
340 return boxes;
341 }
343 SPObject *Selection::single() {
344 if ( _objs != NULL && _objs->next == NULL ) {
345 return reinterpret_cast<SPObject *>(_objs->data);
346 } else {
347 return NULL;
348 }
349 }
351 SPItem *Selection::singleItem() {
352 GSList const *items=itemList();
353 if ( items != NULL && items->next == NULL ) {
354 return reinterpret_cast<SPItem *>(items->data);
355 } else {
356 return NULL;
357 }
358 }
360 Inkscape::XML::Node *Selection::singleRepr() {
361 SPObject *obj=single();
362 return obj ? SP_OBJECT_REPR(obj) : NULL;
363 }
365 NRRect *Selection::bounds(NRRect *bbox, SPItem::BBoxType type) const
366 {
367 g_return_val_if_fail (bbox != NULL, NULL);
368 *bbox = NRRect(bounds(type));
369 return bbox;
370 }
372 Geom::OptRect Selection::bounds(SPItem::BBoxType type) const
373 {
374 GSList const *items = const_cast<Selection *>(this)->itemList();
376 Geom::OptRect bbox;
377 for ( GSList const *i = items ; i != NULL ; i = i->next ) {
378 bbox = unify(bbox, SP_ITEM(i->data)->getBboxDesktop(type));
379 }
380 return bbox;
381 }
383 NRRect *Selection::boundsInDocument(NRRect *bbox, SPItem::BBoxType type) const {
384 g_return_val_if_fail (bbox != NULL, NULL);
386 GSList const *items=const_cast<Selection *>(this)->itemList();
387 if (!items) {
388 bbox->x0 = bbox->y0 = bbox->x1 = bbox->y1 = 0.0;
389 return bbox;
390 }
392 bbox->x0 = bbox->y0 = 1e18;
393 bbox->x1 = bbox->y1 = -1e18;
395 for ( GSList const *iter=items ; iter != NULL ; iter = iter->next ) {
396 SPItem *item=SP_ITEM(iter->data);
397 Geom::Matrix i2doc(item->i2doc_affine());
398 item->invoke_bbox( bbox, i2doc, FALSE, type);
399 }
401 return bbox;
402 }
404 Geom::OptRect Selection::boundsInDocument(SPItem::BBoxType type) const {
405 NRRect r;
406 return to_2geom(boundsInDocument(&r, type)->upgrade());
407 }
409 /** Extract the position of the center from the first selected object */
410 // If we have a selection of multiple items, then the center of the first item
411 // will be returned; this is also the case in SelTrans::centerRequest()
412 boost::optional<Geom::Point> Selection::center() const {
413 GSList *items = (GSList *) const_cast<Selection *>(this)->itemList();
414 Geom::Point center;
415 if (items) {
416 SPItem *first = reinterpret_cast<SPItem*>(g_slist_last(items)->data); // from the first item in selection
417 if (first->isCenterSet()) { // only if set explicitly
418 return first->getCenter();
419 }
420 }
421 Geom::OptRect bbox = bounds();
422 if (bbox) {
423 return bounds()->midpoint();
424 } else {
425 return boost::optional<Geom::Point>();
426 }
427 }
429 /**
430 * Compute the list of points in the selection that are to be considered for snapping from.
431 */
432 std::vector<Inkscape::SnapCandidatePoint> Selection::getSnapPoints(SnapPreferences const *snapprefs) const {
433 GSList const *items = const_cast<Selection *>(this)->itemList();
435 SnapPreferences snapprefs_dummy = *snapprefs; // create a local copy of the snapping prefs
436 snapprefs_dummy.setIncludeItemCenter(false); // locally disable snapping to the item center
437 snapprefs_dummy.setSnapToItemNode(true); // consider any type of nodes as a snap source
438 snapprefs_dummy.setSnapSmoothNodes(true); // i.e. disregard the smooth / cusp node preference
439 std::vector<Inkscape::SnapCandidatePoint> p;
440 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
441 SPItem *this_item = SP_ITEM(iter->data);
442 this_item->getSnappoints(p, &snapprefs_dummy);
444 //Include the transformation origin for snapping
445 //For a selection or group only the overall origin is considered
446 if (snapprefs != NULL && snapprefs->getIncludeItemCenter()) {
447 p.push_back(Inkscape::SnapCandidatePoint(this_item->getCenter(), SNAPSOURCE_ROTATION_CENTER));
448 }
449 }
451 return p;
452 }
453 // TODO: both getSnapPoints and getSnapPointsConvexHull are called, subsequently. Can we do this more efficient?
454 // Why do we need to include the transformation center in one case and not the other?
455 std::vector<Inkscape::SnapCandidatePoint> Selection::getSnapPointsConvexHull(SnapPreferences const *snapprefs) const {
456 GSList const *items = const_cast<Selection *>(this)->itemList();
458 SnapPreferences snapprefs_dummy = *snapprefs; // create a local copy of the snapping prefs
459 snapprefs_dummy.setSnapToItemNode(true); // consider any type of nodes as a snap source
460 snapprefs_dummy.setSnapSmoothNodes(true); // i.e. disregard the smooth / cusp node preference
462 std::vector<Inkscape::SnapCandidatePoint> p;
463 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
464 SP_ITEM(iter->data)->getSnappoints(p, &snapprefs_dummy);
465 }
467 std::vector<Inkscape::SnapCandidatePoint> pHull;
468 if (!p.empty()) {
469 std::vector<Inkscape::SnapCandidatePoint>::iterator i;
470 Geom::RectHull cvh((p.front()).getPoint());
471 for (i = p.begin(); i != p.end(); i++) {
472 // these are the points we get back
473 cvh.add((*i).getPoint());
474 }
476 Geom::OptRect rHull = cvh.bounds();
477 if (rHull) {
478 for ( unsigned i = 0 ; i < 4 ; ++i ) {
479 pHull.push_back(Inkscape::SnapCandidatePoint(rHull->corner(i), SNAPSOURCE_CONVEX_HULL_CORNER));
480 }
481 }
482 }
484 return pHull;
485 }
487 void Selection::_removeObjectDescendants(SPObject *obj) {
488 GSList *iter, *next;
489 for ( iter = _objs ; iter ; iter = next ) {
490 next = iter->next;
491 SPObject *sel_obj=reinterpret_cast<SPObject *>(iter->data);
492 SPObject *parent=SP_OBJECT_PARENT(sel_obj);
493 while (parent) {
494 if ( parent == obj ) {
495 _remove(sel_obj);
496 break;
497 }
498 parent = SP_OBJECT_PARENT(parent);
499 }
500 }
501 }
503 void Selection::_removeObjectAncestors(SPObject *obj) {
504 SPObject *parent=SP_OBJECT_PARENT(obj);
505 while (parent) {
506 if (includes(parent)) {
507 _remove(parent);
508 }
509 parent = SP_OBJECT_PARENT(parent);
510 }
511 }
513 SPObject *Selection::_objectForXMLNode(Inkscape::XML::Node *repr) const {
514 g_return_val_if_fail(repr != NULL, NULL);
515 gchar const *id = repr->attribute("id");
516 g_return_val_if_fail(id != NULL, NULL);
517 SPObject *object=sp_desktop_document(_desktop)->getObjectById(id);
518 g_return_val_if_fail(object != NULL, NULL);
519 return object;
520 }
522 guint Selection::numberOfLayers() {
523 GSList const *items = const_cast<Selection *>(this)->itemList();
524 GSList *layers = NULL;
525 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
526 SPObject *layer = desktop()->layerForObject(SP_OBJECT(iter->data));
527 if (g_slist_find (layers, layer) == NULL) {
528 layers = g_slist_prepend (layers, layer);
529 }
530 }
531 guint ret = g_slist_length (layers);
532 g_slist_free (layers);
533 return ret;
534 }
536 guint Selection::numberOfParents() {
537 GSList const *items = const_cast<Selection *>(this)->itemList();
538 GSList *parents = NULL;
539 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
540 SPObject *parent = SP_OBJECT_PARENT(iter->data);
541 if (g_slist_find (parents, parent) == NULL) {
542 parents = g_slist_prepend (parents, parent);
543 }
544 }
545 guint ret = g_slist_length (parents);
546 g_slist_free (parents);
547 return ret;
548 }
550 }
552 /*
553 Local Variables:
554 mode:c++
555 c-file-style:"stroustrup"
556 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
557 indent-tabs-mode:nil
558 fill-column:99
559 End:
560 */
561 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :