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 *
9 * Copyright (C) 2004-2005 MenTaLguY
10 * Copyright (C) 1999-2002 Lauris Kaplinski
11 * Copyright (C) 2001-2002 Ximian, Inc.
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
19 #include "macros.h"
20 #include "inkscape-private.h"
21 #include "desktop.h"
22 #include "desktop-handles.h"
23 #include "document.h"
24 #include "selection.h"
25 #include "xml/repr.h"
27 #include "sp-shape.h"
30 #define SP_SELECTION_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE + 1)
32 namespace Inkscape {
34 Selection::Selection(SPDesktop *desktop) :
35 _objs(NULL),
36 _reprs(NULL),
37 _items(NULL),
38 _desktop(desktop),
39 _flags(0),
40 _idle(0)
41 {
42 }
44 Selection::~Selection() {
45 _clear();
46 _desktop = NULL;
47 if (_idle) {
48 g_source_remove(_idle);
49 _idle = 0;
50 }
51 }
53 void
54 Selection::_release(SPObject *obj, Selection *selection)
55 {
56 selection->remove(obj);
57 }
59 /* Handler for selected objects "modified" signal */
61 void
62 Selection::_schedule_modified(SPObject *obj, guint flags, Selection *selection)
63 {
64 if (!selection->_idle) {
65 /* Request handling to be run in _idle loop */
66 selection->_idle = g_idle_add_full(SP_SELECTION_UPDATE_PRIORITY, GSourceFunc(&Selection::_emit_modified), selection, NULL);
67 }
69 /* Collect all flags */
70 selection->_flags |= flags;
71 }
73 gboolean
74 Selection::_emit_modified(Selection *selection)
75 {
76 /* force new handler to be created if requested before we return */
77 selection->_idle = 0;
78 guint flags = selection->_flags;
79 selection->_flags = 0;
81 selection->_emitModified(flags);
83 /* drop this handler */
84 return FALSE;
85 }
87 void Selection::_emitModified(guint flags) {
88 inkscape_selection_modified(this, flags);
89 _modified_signal.emit(this, flags);
90 }
92 void Selection::_emitChanged() {
93 inkscape_selection_changed(this);
94 _changed_signal.emit(this);
95 }
97 void Selection::_invalidateCachedLists() {
98 g_slist_free(_items);
99 _items = NULL;
101 g_slist_free(_reprs);
102 _reprs = NULL;
103 }
105 void Selection::_clear() {
106 _invalidateCachedLists();
107 while (_objs) {
108 SPObject *obj=reinterpret_cast<SPObject *>(_objs->data);
109 sp_signal_disconnect_by_data(obj, this);
110 _objs = g_slist_remove(_objs, obj);
111 }
112 }
114 bool Selection::includes(SPObject *obj) const {
115 if (obj == NULL)
116 return FALSE;
118 g_return_val_if_fail(SP_IS_OBJECT(obj), FALSE);
120 return ( g_slist_find(_objs, obj) != NULL );
121 }
123 void Selection::add(SPObject *obj) {
124 g_return_if_fail(obj != NULL);
125 g_return_if_fail(SP_IS_OBJECT(obj));
127 if (includes(obj)) {
128 return;
129 }
131 _invalidateCachedLists();
132 _add(obj);
133 _emitChanged();
134 }
136 void Selection::_add(SPObject *obj) {
137 // unselect any of the item's ancestors and descendants which may be selected
138 // (to prevent double-selection)
139 _removeObjectDescendants(obj);
140 _removeObjectAncestors(obj);
142 _objs = g_slist_prepend(_objs, obj);
143 g_signal_connect(G_OBJECT(obj), "release",
144 G_CALLBACK(&Selection::_release), this);
145 g_signal_connect(G_OBJECT(obj), "modified",
146 G_CALLBACK(&Selection::_schedule_modified), this);
148 /*
149 if (!SP_IS_SHAPE(obj)) {
150 printf("This is not a shape\n");
151 }
152 */
153 }
155 void Selection::set(SPObject *object) {
156 _clear();
157 add(object);
158 }
160 void Selection::toggle(SPObject *obj) {
161 if (includes (obj)) {
162 remove (obj);
163 } else {
164 add(obj);
165 }
166 }
168 void Selection::remove(SPObject *obj) {
169 g_return_if_fail(obj != NULL);
170 g_return_if_fail(SP_IS_OBJECT(obj));
171 g_return_if_fail(includes(obj));
173 _invalidateCachedLists();
174 _remove(obj);
175 _emitChanged();
176 }
178 void Selection::_remove(SPObject *obj) {
179 sp_signal_disconnect_by_data(obj, this);
180 _objs = g_slist_remove(_objs, obj);
181 }
183 void Selection::setList(GSList const *list) {
184 _clear();
186 if ( list != NULL ) {
187 for ( GSList const *iter = list ; iter != NULL ; iter = iter->next ) {
188 _add(reinterpret_cast<SPObject *>(iter->data));
189 }
190 }
192 _emitChanged();
193 }
195 void Selection::addList(GSList const *list) {
197 if (list == NULL)
198 return;
200 _invalidateCachedLists();
202 for ( GSList const *iter = list ; iter != NULL ; iter = iter->next ) {
203 SPObject *obj = reinterpret_cast<SPObject *>(iter->data);
204 if (includes(obj)) {
205 continue;
206 }
207 _add (obj);
208 }
210 _emitChanged();
211 }
213 void Selection::setReprList(GSList const *list) {
214 _clear();
216 for ( GSList const *iter = list ; iter != NULL ; iter = iter->next ) {
217 SPObject *obj=_objectForXMLNode(reinterpret_cast<Inkscape::XML::Node *>(iter->data));
218 if (obj) {
219 _add(obj);
220 }
221 }
223 _emitChanged();
224 }
226 void Selection::clear() {
227 _clear();
228 _emitChanged();
229 }
231 GSList const *Selection::list() {
232 return _objs;
233 }
235 GSList const *Selection::itemList() {
236 if (_items) {
237 return _items;
238 }
240 for ( GSList const *iter=_objs ; iter != NULL ; iter = iter->next ) {
241 SPObject *obj=reinterpret_cast<SPObject *>(iter->data);
242 if (SP_IS_ITEM(obj)) {
243 _items = g_slist_prepend(_items, SP_ITEM(obj));
244 }
245 }
246 _items = g_slist_reverse(_items);
248 return _items;
249 }
251 GSList const *Selection::reprList() {
252 if (_reprs) { return _reprs; }
254 for ( GSList const *iter=itemList() ; iter != NULL ; iter = iter->next ) {
255 SPObject *obj=reinterpret_cast<SPObject *>(iter->data);
256 _reprs = g_slist_prepend(_reprs, SP_OBJECT_REPR(obj));
257 }
258 _reprs = g_slist_reverse(_reprs);
260 return _reprs;
261 }
263 SPObject *Selection::single() {
264 if ( _objs != NULL && _objs->next == NULL ) {
265 return reinterpret_cast<SPObject *>(_objs->data);
266 } else {
267 return NULL;
268 }
269 }
271 SPItem *Selection::singleItem() {
272 GSList const *items=itemList();
273 if ( items != NULL && items->next == NULL ) {
274 return reinterpret_cast<SPItem *>(items->data);
275 } else {
276 return NULL;
277 }
278 }
280 Inkscape::XML::Node *Selection::singleRepr() {
281 SPObject *obj=single();
282 return obj ? SP_OBJECT_REPR(obj) : NULL;
283 }
285 NRRect *Selection::bounds(NRRect *bbox) const
286 {
287 g_return_val_if_fail (bbox != NULL, NULL);
288 NR::Rect const b = bounds();
289 bbox->x0 = b.min()[NR::X];
290 bbox->y0 = b.min()[NR::Y];
291 bbox->x1 = b.max()[NR::X];
292 bbox->y1 = b.max()[NR::Y];
293 return bbox;
294 }
296 NR::Rect Selection::bounds() const
297 {
298 GSList const *items = const_cast<Selection *>(this)->itemList();
299 if (!items) {
300 return NR::Rect(NR::Point(0, 0), NR::Point(0, 0));
301 }
303 GSList const *i = items;
304 NR::Rect bbox = sp_item_bbox_desktop(SP_ITEM(i->data));
306 GSList const *i_start = i;
307 while (i != NULL) {
308 if (i != i_start)
309 bbox = NR::Rect::union_bounds(bbox, sp_item_bbox_desktop(SP_ITEM(i->data)));
310 i = i->next;
311 }
313 return bbox;
314 }
316 NRRect *Selection::boundsInDocument(NRRect *bbox) const {
317 g_return_val_if_fail (bbox != NULL, NULL);
319 GSList const *items=const_cast<Selection *>(this)->itemList();
320 if (!items) {
321 bbox->x0 = bbox->y0 = bbox->x1 = bbox->y1 = 0.0;
322 return bbox;
323 }
325 bbox->x0 = bbox->y0 = 1e18;
326 bbox->x1 = bbox->y1 = -1e18;
328 for ( GSList const *iter=items ; iter != NULL ; iter = iter->next ) {
329 SPItem *item=SP_ITEM(iter->data);
330 NR::Matrix const i2doc(sp_item_i2doc_affine(item));
331 sp_item_invoke_bbox(item, bbox, i2doc, FALSE);
332 }
334 return bbox;
335 }
337 NR::Rect Selection::boundsInDocument() const {
338 NRRect r;
339 return NR::Rect(*boundsInDocument(&r));
340 }
342 /** Extract the position of the center from the first selected object */
343 NR::Point Selection::center() const {
344 GSList *items = (GSList *) const_cast<Selection *>(this)->itemList();
345 NR::Point center;
346 if (items) {
347 SPItem *first = reinterpret_cast<SPItem*>(g_slist_last(items)->data); // from the first item in selection
348 if (first->isCenterSet()) { // only if set explicitly
349 center = first->getCenter();
350 } else {
351 center = bounds().midpoint();
352 }
353 } else {
354 center = bounds().midpoint();
355 }
356 return center;
357 }
359 /**
360 * Compute the list of points in the selection that are to be considered for snapping.
361 */
362 std::vector<NR::Point> Selection::getSnapPoints() const {
363 GSList const *items = const_cast<Selection *>(this)->itemList();
364 std::vector<NR::Point> p;
365 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
366 sp_item_snappoints(SP_ITEM(iter->data), SnapPointsIter(p));
367 }
369 return p;
370 }
372 std::vector<NR::Point> Selection::getSnapPointsConvexHull() const {
373 GSList const *items = const_cast<Selection *>(this)->itemList();
374 std::vector<NR::Point> p;
375 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
376 sp_item_snappoints(SP_ITEM(iter->data), SnapPointsIter(p));
377 }
379 std::vector<NR::Point>::iterator i;
380 NR::ConvexHull cvh(*(p.begin()));
381 for (i = p.begin(); i != p.end(); i++) {
382 // these are the points we get back
383 cvh.add(*i);
384 }
386 NR::Rect rHull = cvh.bounds();
387 std::vector<NR::Point> pHull(4);
388 pHull[0] = rHull.corner(0);
389 pHull[1] = rHull.corner(1);
390 pHull[2] = rHull.corner(2);
391 pHull[3] = rHull.corner(3);
393 return pHull;
394 }
396 std::vector<NR::Point> Selection::getBBoxPoints() const {
397 GSList const *items = const_cast<Selection *>(this)->itemList();
398 std::vector<NR::Point> p;
399 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
400 NR::Rect b = sp_item_bbox_desktop(SP_ITEM(iter->data));
401 p.push_back(b.min());
402 p.push_back(b.max());
403 }
405 return p;
406 }
408 void Selection::_removeObjectDescendants(SPObject *obj) {
409 GSList *iter, *next;
410 for ( iter = _objs ; iter ; iter = next ) {
411 next = iter->next;
412 SPObject *sel_obj=reinterpret_cast<SPObject *>(iter->data);
413 SPObject *parent=SP_OBJECT_PARENT(sel_obj);
414 while (parent) {
415 if ( parent == obj ) {
416 _remove(sel_obj);
417 break;
418 }
419 parent = SP_OBJECT_PARENT(parent);
420 }
421 }
422 }
424 void Selection::_removeObjectAncestors(SPObject *obj) {
425 SPObject *parent=SP_OBJECT_PARENT(obj);
426 while (parent) {
427 if (includes(parent)) {
428 _remove(parent);
429 }
430 parent = SP_OBJECT_PARENT(parent);
431 }
432 }
434 SPObject *Selection::_objectForXMLNode(Inkscape::XML::Node *repr) const {
435 g_return_val_if_fail(repr != NULL, NULL);
436 gchar const *id = repr->attribute("id");
437 g_return_val_if_fail(id != NULL, NULL);
438 SPObject *object=SP_DT_DOCUMENT(_desktop)->getObjectById(id);
439 g_return_val_if_fail(object != NULL, NULL);
440 return object;
441 }
443 guint Selection::numberOfLayers() {
444 GSList const *items = const_cast<Selection *>(this)->itemList();
445 GSList *layers = NULL;
446 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
447 SPObject *layer = desktop()->layerForObject(SP_OBJECT(iter->data));
448 if (g_slist_find (layers, layer) == NULL) {
449 layers = g_slist_prepend (layers, layer);
450 }
451 }
452 guint ret = g_slist_length (layers);
453 g_slist_free (layers);
454 return ret;
455 }
457 guint Selection::numberOfParents() {
458 GSList const *items = const_cast<Selection *>(this)->itemList();
459 GSList *parents = NULL;
460 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
461 SPObject *parent = SP_OBJECT_PARENT(iter->data);
462 if (g_slist_find (parents, parent) == NULL) {
463 parents = g_slist_prepend (parents, parent);
464 }
465 }
466 guint ret = g_slist_length (parents);
467 g_slist_free (parents);
468 return ret;
469 }
471 }
473 /*
474 Local Variables:
475 mode:c++
476 c-file-style:"stroustrup"
477 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
478 indent-tabs-mode:nil
479 fill-column:99
480 End:
481 */
482 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :