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 while (i != NULL) {
307 bbox = NR::Rect::union_bounds(bbox, sp_item_bbox_desktop(SP_ITEM(i->data)));
308 i = i->next;
309 }
311 return bbox;
312 }
314 NRRect *Selection::boundsInDocument(NRRect *bbox) const {
315 g_return_val_if_fail (bbox != NULL, NULL);
317 GSList const *items=const_cast<Selection *>(this)->itemList();
318 if (!items) {
319 bbox->x0 = bbox->y0 = bbox->x1 = bbox->y1 = 0.0;
320 return bbox;
321 }
323 bbox->x0 = bbox->y0 = 1e18;
324 bbox->x1 = bbox->y1 = -1e18;
326 for ( GSList const *iter=items ; iter != NULL ; iter = iter->next ) {
327 SPItem *item=SP_ITEM(iter->data);
328 NR::Matrix const i2doc(sp_item_i2doc_affine(item));
329 sp_item_invoke_bbox(item, bbox, i2doc, FALSE);
330 }
332 return bbox;
333 }
335 NR::Rect Selection::boundsInDocument() const {
336 NRRect r;
337 return NR::Rect(*boundsInDocument(&r));
338 }
340 /**
341 * Compute the list of points in the selection that are to be considered for snapping.
342 */
343 std::vector<NR::Point> Selection::getSnapPoints() const {
344 GSList const *items = const_cast<Selection *>(this)->itemList();
345 std::vector<NR::Point> p;
346 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
347 sp_item_snappoints(SP_ITEM(iter->data), SnapPointsIter(p));
348 }
350 return p;
351 }
353 std::vector<NR::Point> Selection::getSnapPointsConvexHull() const {
354 GSList const *items = const_cast<Selection *>(this)->itemList();
355 std::vector<NR::Point> p;
356 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
357 sp_item_snappoints(SP_ITEM(iter->data), SnapPointsIter(p));
358 }
360 std::vector<NR::Point>::iterator i;
361 NR::ConvexHull cvh(*(p.begin()));
362 for (i = p.begin(); i != p.end(); i++) {
363 // these are the points we get back
364 cvh.add(*i);
365 }
367 NR::Rect rHull = cvh.bounds();
368 std::vector<NR::Point> pHull(4);
369 pHull[0] = rHull.corner(0);
370 pHull[1] = rHull.corner(1);
371 pHull[2] = rHull.corner(2);
372 pHull[3] = rHull.corner(3);
374 return pHull;
375 }
377 std::vector<NR::Point> Selection::getBBoxPoints() const {
378 GSList const *items = const_cast<Selection *>(this)->itemList();
379 std::vector<NR::Point> p;
380 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
381 NR::Rect b = sp_item_bbox_desktop(SP_ITEM(iter->data));
382 p.push_back(b.min());
383 p.push_back(b.max());
384 }
386 return p;
387 }
389 void Selection::_removeObjectDescendants(SPObject *obj) {
390 GSList *iter, *next;
391 for ( iter = _objs ; iter ; iter = next ) {
392 next = iter->next;
393 SPObject *sel_obj=reinterpret_cast<SPObject *>(iter->data);
394 SPObject *parent=SP_OBJECT_PARENT(sel_obj);
395 while (parent) {
396 if ( parent == obj ) {
397 _remove(sel_obj);
398 break;
399 }
400 parent = SP_OBJECT_PARENT(parent);
401 }
402 }
403 }
405 void Selection::_removeObjectAncestors(SPObject *obj) {
406 SPObject *parent=SP_OBJECT_PARENT(obj);
407 while (parent) {
408 if (includes(parent)) {
409 _remove(parent);
410 }
411 parent = SP_OBJECT_PARENT(parent);
412 }
413 }
415 SPObject *Selection::_objectForXMLNode(Inkscape::XML::Node *repr) const {
416 g_return_val_if_fail(repr != NULL, NULL);
417 gchar const *id = repr->attribute("id");
418 g_return_val_if_fail(id != NULL, NULL);
419 SPObject *object=SP_DT_DOCUMENT(_desktop)->getObjectById(id);
420 g_return_val_if_fail(object != NULL, NULL);
421 return object;
422 }
424 guint Selection::numberOfLayers() {
425 GSList const *items = const_cast<Selection *>(this)->itemList();
426 GSList *layers = NULL;
427 for (GSList const *iter = items; iter != NULL; iter = iter->next) {
428 SPObject *layer = desktop()->layerForObject(SP_OBJECT(iter->data));
429 if (g_slist_find (layers, layer) == NULL) {
430 layers = g_slist_prepend (layers, layer);
431 }
432 }
433 guint ret = g_slist_length (layers);
434 g_slist_free (layers);
435 return ret;
436 }
438 }
440 /*
441 Local Variables:
442 mode:c++
443 c-file-style:"stroustrup"
444 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
445 indent-tabs-mode:nil
446 fill-column:99
447 End:
448 */
449 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :