1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * A simple panel for objects (originally developed for Ponyscape, an Inkscape derivative)
4 *
5 * Authors:
6 * Theodore Janeczko
7 * Tweaked by Liam P White for use in Inkscape
8 * Tavmjong Bah
9 *
10 * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
11 * Tavmjong Bah 2017
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16 #include "objects.h"
17
18 #include <gtkmm/icontheme.h>
19 #include <gtkmm/imagemenuitem.h>
20 #include <gtkmm/separatormenuitem.h>
21 #include <glibmm/main.h>
22
23 #include "desktop-style.h"
24 #include "desktop.h"
25 #include "document-undo.h"
26 #include "document.h"
27 #include "filter-chemistry.h"
28 #include "inkscape.h"
29 #include "layer-manager.h"
30 #include "verbs.h"
31
32 #include "helper/action.h"
33 #include "ui/icon-loader.h"
34
35 #include "include/gtkmm_version.h"
36
37 #include "object/filters/blend.h"
38 #include "object/filters/gaussian-blur.h"
39 #include "object/sp-clippath.h"
40 #include "object/sp-mask.h"
41 #include "object/sp-root.h"
42 #include "object/sp-shape.h"
43 #include "style.h"
44
45 #include "ui/dialog-events.h"
46 #include "ui/icon-names.h"
47 #include "ui/selected-color.h"
48 #include "ui/shortcuts.h"
49 #include "ui/desktop/menu-icon-shift.h"
50 #include "ui/tools-switch.h"
51 #include "ui/tools/node-tool.h"
52
53 #include "ui/widget/canvas.h"
54 #include "ui/widget/clipmaskicon.h"
55 #include "ui/widget/color-notebook.h"
56 #include "ui/widget/highlight-picker.h"
57 #include "ui/widget/imagetoggler.h"
58 #include "ui/widget/insertordericon.h"
59 #include "ui/widget/layertypeicon.h"
60
61 #include "xml/node-observer.h"
62
63 //#define DUMP_LAYERS 1
64
65 namespace Inkscape {
66 namespace UI {
67 namespace Dialog {
68
69 using Inkscape::XML::Node;
70
71 /**
72 * Gets an instance of the Objects panel
73 */
getInstance()74 ObjectsPanel& ObjectsPanel::getInstance()
75 {
76 return *new ObjectsPanel();
77 }
78
79 /**
80 * Column enumeration
81 */
82 enum {
83 COL_VISIBLE = 1,
84 COL_LOCKED,
85 COL_TYPE,
86 // COL_INSERTORDER,
87 COL_CLIPMASK,
88 COL_HIGHLIGHT
89 };
90
91 /**
92 * Button enumeration
93 */
94 enum {
95 BUTTON_NEW = 0,
96 BUTTON_RENAME,
97 BUTTON_TOP,
98 BUTTON_BOTTOM,
99 BUTTON_UP,
100 BUTTON_DOWN,
101 BUTTON_DUPLICATE,
102 BUTTON_DELETE,
103 BUTTON_SOLO,
104 BUTTON_SHOW_ALL,
105 BUTTON_HIDE_ALL,
106 BUTTON_LOCK_OTHERS,
107 BUTTON_LOCK_ALL,
108 BUTTON_UNLOCK_ALL,
109 BUTTON_SETCLIP,
110 BUTTON_CLIPGROUP,
111 // BUTTON_SETINVCLIP,
112 BUTTON_UNSETCLIP,
113 BUTTON_SETMASK,
114 BUTTON_UNSETMASK,
115 BUTTON_GROUP,
116 BUTTON_UNGROUP,
117 BUTTON_COLLAPSE_ALL,
118 DRAGNDROP,
119 UPDATE_TREE
120 };
121
122 /**
123 * Xml node observer for observing objects in the document
124 */
125 class ObjectsPanel::ObjectWatcher : public Inkscape::XML::NodeObserver {
126 public:
127 /**
128 * Creates a new object watcher
129 * @param pnl The panel to which the object watcher belongs
130 * @param obj The object to watch
131 */
ObjectWatcher(ObjectsPanel * pnl,SPObject * obj)132 ObjectWatcher(ObjectsPanel* pnl, SPObject* obj) :
133 _pnl(pnl),
134 _obj(obj),
135 _repr(obj->getRepr()),
136 _highlightAttr(g_quark_from_string("inkscape:highlight-color")),
137 _lockedAttr(g_quark_from_string("sodipodi:insensitive")),
138 _labelAttr(g_quark_from_string("inkscape:label")),
139 _groupAttr(g_quark_from_string("inkscape:groupmode")),
140 _styleAttr(g_quark_from_string("style")),
141 _clipAttr(g_quark_from_string("clip-path")),
142 _maskAttr(g_quark_from_string("mask"))
143 {
144 _repr->addObserver(*this);
145 }
146
~ObjectWatcher()147 ~ObjectWatcher() override {
148 _repr->removeObserver(*this);
149 }
150
notifyChildAdded(Node &,Node &,Node *)151 void notifyChildAdded( Node &/*node*/, Node &/*child*/, Node */*prev*/ ) override
152 {
153 if ( _pnl && _obj ) {
154 _pnl->_objectsChangedWrapper( _obj );
155 }
156 }
notifyChildRemoved(Node &,Node &,Node *)157 void notifyChildRemoved( Node &/*node*/, Node &/*child*/, Node */*prev*/ ) override
158 {
159 if ( _pnl && _obj ) {
160 _pnl->_objectsChangedWrapper( _obj );
161 }
162 }
notifyChildOrderChanged(Node &,Node &,Node *,Node *)163 void notifyChildOrderChanged( Node &/*node*/, Node &/*child*/, Node */*old_prev*/, Node */*new_prev*/ ) override
164 {
165 if ( _pnl && _obj ) {
166 _pnl->_objectsChangedWrapper( _obj );
167 }
168 }
notifyContentChanged(Node &,Util::ptr_shared,Util::ptr_shared)169 void notifyContentChanged( Node &/*node*/, Util::ptr_shared /*old_content*/, Util::ptr_shared /*new_content*/ ) override {}
notifyAttributeChanged(Node & node,GQuark name,Util::ptr_shared,Util::ptr_shared)170 void notifyAttributeChanged( Node &node, GQuark name, Util::ptr_shared /*old_value*/, Util::ptr_shared /*new_value*/ ) override {
171 /* Weird things happen on undo! we get notified about the child being removed, but after that we still get
172 * notified for attributes being changed on this XML node! In that case the corresponding SPObject might already
173 * have been deleted and the pointer to might be invalid, leading to a segfault if we're not carefull.
174 * So after we initiated the update of the treeview using _objectsChangedWrapper() in notifyChildRemoved(), the
175 * _pending_update flag is set, and we will no longer process any notifyAttributeChanged()
176 * Reproducing the crash: new document -> open objects panel -> draw freehand line -> undo -> segfault (but only
177 * if we don't check for _pending_update) */
178 if ( _pnl && (!_pnl->_pending_update) && _obj ) {
179 if ( name == _lockedAttr || name == _labelAttr || name == _highlightAttr || name == _groupAttr || name == _styleAttr || name == _clipAttr || name == _maskAttr ) {
180 _pnl->_updateObject(_obj, name == _highlightAttr);
181 if ( name == _styleAttr ) {
182 _pnl->_updateComposite();
183 }
184 }
185 }
186 }
187
188 /**
189 * Objects panel to which this watcher belongs
190 */
191 ObjectsPanel* _pnl;
192
193 /**
194 * The object that is being observed
195 */
196 SPObject* _obj;
197
198 /**
199 * The xml representation of the object that is being observed
200 */
201 Inkscape::XML::Node* _repr;
202
203 /* These are quarks which define the attributes that we are observing */
204 GQuark _highlightAttr;
205 GQuark _lockedAttr;
206 GQuark _labelAttr;
207 GQuark _groupAttr;
208 GQuark _styleAttr;
209 GQuark _clipAttr;
210 GQuark _maskAttr;
211 };
212
213 class ObjectsPanel::InternalUIBounce
214 {
215 public:
216 int _actionCode;
217 sigc::connection _signal;
218 };
219
220 class ObjectsPanel::ModelColumns : public Gtk::TreeModel::ColumnRecord
221 {
222 public:
223
ModelColumns()224 ModelColumns()
225 {
226 add(_colObject);
227 add(_colVisible);
228 add(_colLocked);
229 add(_colLabel);
230 add(_colType);
231 add(_colHighlight);
232 add(_colClipMask);
233 add(_colPrevSelectionState);
234 //add(_colInsertOrder);
235 }
236 ~ModelColumns() override = default;
237
238 Gtk::TreeModelColumn<SPItem*> _colObject;
239 Gtk::TreeModelColumn<Glib::ustring> _colLabel;
240 Gtk::TreeModelColumn<bool> _colVisible;
241 Gtk::TreeModelColumn<bool> _colLocked;
242 Gtk::TreeModelColumn<int> _colType;
243 Gtk::TreeModelColumn<guint32> _colHighlight;
244 Gtk::TreeModelColumn<int> _colClipMask;
245 Gtk::TreeModelColumn<bool> _colPrevSelectionState;
246 //Gtk::TreeModelColumn<int> _colInsertOrder;
247 };
248
249 /**
250 * Stylizes a button using the given icon name and tooltip
251 */
_styleButton(Gtk::Button & btn,char const * iconName,char const * tooltip)252 void ObjectsPanel::_styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip)
253 {
254 auto child = Glib::wrap(sp_get_icon_image(iconName, GTK_ICON_SIZE_SMALL_TOOLBAR));
255 child->show();
256 btn.add(*child);
257 btn.set_relief(Gtk::RELIEF_NONE);
258 btn.set_tooltip_text(tooltip);
259 }
260
261 /**
262 * Adds an item to the pop-up (right-click) menu
263 * @param desktop The active destktop
264 * @param code Action code
265 * @param id Button id for callback function
266 * @return The generated menu item
267 */
_addPopupItem(SPDesktop * desktop,unsigned int code,int id)268 Gtk::MenuItem& ObjectsPanel::_addPopupItem( SPDesktop *desktop, unsigned int code, int id )
269 {
270 Verb *verb = Verb::get( code );
271 g_assert(verb);
272 SPAction *action = verb->get_action(Inkscape::ActionContext(desktop));
273
274 Gtk::MenuItem* item = Gtk::manage(new Gtk::MenuItem());
275
276 Gtk::Label *label = Gtk::manage(new Gtk::Label(action->name, true));
277 label->set_xalign(0.0);
278
279 if (_show_contextmenu_icons && action->image) {
280 item->set_name("ImageMenuItem"); // custom name to identify our "ImageMenuItems"
281 Gtk::Image *icon = Gtk::manage(sp_get_icon_image(action->image, Gtk::ICON_SIZE_MENU));
282
283 // Create a box to hold icon and label as Gtk::MenuItem derives from GtkBin and can only hold one child
284 Gtk::Box *box = Gtk::manage(new Gtk::Box());
285 box->pack_start(*icon, false, false, 0);
286 box->pack_start(*label, true, true, 0);
287 item->add(*box);
288 } else {
289 item->add(*label);
290 }
291
292 item->signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &ObjectsPanel::_takeAction), id));
293 _popupMenu.append(*item);
294
295 return *item;
296 }
297
298 /**
299 * Attach a watcher to the XML node of an item, which will signal us in case of changes to that item or node
300 * @param item The item of which the XML node is to be watched
301 */
_addWatcher(SPItem * item)302 void ObjectsPanel::_addWatcher(SPItem *item) {
303 bool used = true; // Any newly created watcher is obviously being used
304 auto iter = _objectWatchers.find(item);
305 if (iter == _objectWatchers.end()) { // If not found then watcher doesn't exist yet
306 ObjectsPanel::ObjectWatcher *w = new ObjectsPanel::ObjectWatcher(this, item);
307 _objectWatchers.emplace(item, std::make_pair(w, used));
308 } else { // Found; no need to create a new watcher; just flag it as "in use"
309 (*iter).second.second = used;
310 }
311 }
312
313 /**
314 * Delete the watchers, which signal us in case of changes to the item being watched
315 * @param only_unused Only delete those watchers that are no longer in use
316 */
_removeWatchers(bool only_unused=false)317 void ObjectsPanel::_removeWatchers(bool only_unused = false) {
318 // Delete all watchers (optionally only those which are not in use)
319 auto iter = _objectWatchers.begin();
320 while (iter != _objectWatchers.end()) {
321 bool used = (*iter).second.second;
322 bool delete_watcher = (!only_unused) || (only_unused && !used);
323 if ( delete_watcher ) {
324 ObjectsPanel::ObjectWatcher *w = (*iter).second.first;
325 delete w;
326 iter = _objectWatchers.erase(iter);
327 } else {
328 // It must be in use, so the used "field" should be set to true;
329 // However, when _removeWatchers is being called, we will already have processed the complete queue ...
330 g_assert(_tree_update_queue.empty());
331 // .. and we can preemptively flag it as unused for the processing of the next queue
332 (*iter).second.second = false; // It will be set to true again by _addWatcher, if in use
333 iter++;
334 }
335 }
336 }
337 /**
338 * Call function for asynchronous invocation of _objectsChanged
339 */
_objectsChangedWrapper(SPObject *)340 void ObjectsPanel::_objectsChangedWrapper(SPObject */*obj*/) {
341 // We used to call _objectsChanged with a reference to _obj,
342 // but since _obj wasn't used, I'm dropping that for now
343 _takeAction(UPDATE_TREE);
344 }
345
346 /**
347 * Callback function for when an object changes. Essentially refreshes the entire tree
348 * @param obj Object which was changed (currently not used as the entire tree is recreated)
349 */
_objectsChanged(SPObject *)350 void ObjectsPanel::_objectsChanged(SPObject */*obj*/)
351 {
352 if (_desktop) {
353 //Get the current document's root and use that to enumerate the tree
354 SPDocument* document = _desktop->doc();
355 SPRoot* root = document->getRoot();
356 if ( root ) {
357 _selectedConnection.block(); // Will be unblocked after the queue has been processed fully
358 _documentChangedCurrentLayer.block();
359
360 //Clear the tree store
361 _store->clear(); // This will increment it's stamp, making all old iterators
362 _tree_cache.clear(); // invalid. So we will also clear our own cache, as well
363 _tree_update_queue.clear(); // as any remaining update queue
364
365 // Temporarily detach the TreeStore from the TreeView to slightly reduce flickering, and to speed up
366 // Note: if we truly want to eliminate the flickering, we should implement double buffering on the _store,
367 // but maybe this is a bit too much effort/bloat for too little gain?
368 _tree.unset_model();
369
370 //Add all items recursively; we will do this asynchronously, by first filling a queue, which is rather fast
371 _queueObject( root, nullptr );
372 //However, the processing of this queue is slow, so this is done at a low priority and in small chunks. Using
373 //only small chunks keeps Inkscape responsive, for example while using the spray tool. After processing each
374 //of the chunks, Inkscape will check if there are other tasks with a high priority, for example when user is
375 //spraying. If so, the sprayed objects will be added first, and the whole updating will be restarted before
376 //it even finished.
377 _paths_to_be_expanded.clear();
378 _processQueue_sig.disconnect(); // Might be needed in case objectsChanged is called directly, and not through objectsChangedWrapper()
379 _processQueue_sig = Glib::signal_timeout().connect( sigc::mem_fun(*this, &ObjectsPanel::_processQueue), 0, Glib::PRIORITY_DEFAULT_IDLE+100);
380 }
381 }
382 }
383
384 /**
385 * Recursively adds the children of the given item to the tree
386 * @param obj Root object to add to the tree
387 * @param parentRow Parent tree row (or NULL if adding to tree root)
388 */
_queueObject(SPObject * obj,Gtk::TreeModel::Row * parentRow)389 void ObjectsPanel::_queueObject(SPObject* obj, Gtk::TreeModel::Row* parentRow)
390 {
391 bool already_expanded = false;
392
393 for(auto& child: obj->children) {
394 if (SP_IS_ITEM(&child)) {
395 //Add the item to the tree, basically only creating an empty row in the tree view
396 Gtk::TreeModel::iterator iter = parentRow ? _store->prepend(parentRow->children()) : _store->prepend();
397
398 //Add the item to a queue, so we can fill in the data in each row asynchronously
399 //at a later stage. See the comments in _objectsChanged() for more details
400 bool expand = SP_IS_GROUP(obj) && SP_GROUP(obj)->expanded() && (not already_expanded);
401 _tree_update_queue.emplace_back(SP_ITEM(&child), iter, expand);
402
403 already_expanded = expand || already_expanded; // We need to expand only a single child in each group
404
405 //If the item is a group, recursively add its children
406 if (SP_IS_GROUP(&child)) {
407 Gtk::TreeModel::Row row = *iter;
408 _queueObject(&child, &row);
409 }
410 }
411 }
412 }
413
414 /**
415 * Walks through the queue in small chunks, and fills in the rows in the tree view accordingly
416 * @return False if the queue has been fully emptied
417 */
_processQueue()418 bool ObjectsPanel::_processQueue() {
419 auto *desktop = getDesktop();
420 if (!desktop) {
421 return false;
422 }
423
424 auto queue_iter = _tree_update_queue.begin();
425 auto queue_end = _tree_update_queue.end();
426 int count = 0;
427
428 while (queue_iter != queue_end) {
429 //The queue is a list of tuples; expand the tuples
430 SPItem *item = std::get<0>(*queue_iter);
431 Gtk::TreeModel::iterator iter = std::get<1>(*queue_iter);
432 bool expanded = std::get<2>(*queue_iter);
433 //Add the object to the tree view and tree cache
434 _addObjectToTree(item, *iter, expanded);
435 _tree_cache.emplace(item, *iter);
436
437 /* Update the watchers; No watcher shall be deleted before the processing of the queue has
438 * finished; we need to keep watching for items that might have been deleted while the queue,
439 * which is being processed on idle, was not yet empty. This is because when an item is deleted, the
440 * queue is still holding a pointer to it. The NotifyChildRemoved method of the watcher will stop the
441 * processing of the queue and prevent a segmentation fault, but only if there is a watcher in place*/
442 _addWatcher(item);
443
444 queue_iter = _tree_update_queue.erase(queue_iter);
445 count++;
446 if (count == 100 && (!_tree_update_queue.empty())) {
447 return true; // we have not yet reached the end of the queue, so return true to keep the timeout signal alive
448 }
449 }
450
451 //We have reached the end of the queue, and it is safe to remove any watchers
452 _removeWatchers(true); // ... but only remove those that are no longer in use
453
454 // Now we can bring the tree view back to life safely
455 _tree.set_model(_store); // Attach the store again to the tree view this sets search columns as -1
456 _tree.set_search_column(_model->_colLabel);//set search column again
457
458 // Expand the tree; this is kept outside of _addObjectToTree() and _processQueue() to allow
459 // temporarily detaching the store from the tree, which slightly reduces flickering
460 for (auto path: _paths_to_be_expanded) {
461 _tree.expand_to_path(path);
462 _tree.collapse_row(path);
463 }
464
465 _blockAllSignals(false);
466 _objectsSelected(desktop->selection); //Set the tree selection; will also invoke _checkTreeSelection()
467 _pending_update = false;
468 return false; // Return false to kill the timeout signal that kept calling _processQueue
469 }
470
471 /**
472 * Fills in the details of an item in the already existing row of the tree view
473 * @param item Item of which the name, visibility, lock status, etc, will be filled in
474 * @param row Row where the item is residing
475 * @param expanded True if the item is part of a group that is shown as expanded in the tree view
476 */
_addObjectToTree(SPItem * item,const Gtk::TreeModel::Row & row,bool expanded)477 void ObjectsPanel::_addObjectToTree(SPItem* item, const Gtk::TreeModel::Row &row, bool expanded)
478 {
479 SPGroup * group = SP_IS_GROUP(item) ? SP_GROUP(item) : nullptr;
480
481 row[_model->_colObject] = item;
482 gchar const * label = item->label() ? item->label() : item->getId();
483 row[_model->_colLabel] = label ? label : item->defaultLabel();
484 row[_model->_colVisible] = !item->isHidden();
485 row[_model->_colLocked] = !item->isSensitive();
486 row[_model->_colType] = group ? (group->layerMode() == SPGroup::LAYER ? 2 : 1) : 0;
487 row[_model->_colHighlight] = item->isHighlightSet() ? item->highlight_color() : item->highlight_color() & 0xffffff00;
488 row[_model->_colClipMask] = item ? (
489 (item->getClipObject() ? 1 : 0) |
490 (item->getMaskObject() ? 2 : 0)
491 ) : 0;
492 row[_model->_colPrevSelectionState] = false;
493 //row[_model->_colInsertOrder] = group ? (group->insertBottom() ? 2 : 1) : 0;
494
495 //If our parent object is a group and it's expanded, expand the tree
496 if (expanded) {
497 _paths_to_be_expanded.emplace_back(_store->get_path(row));
498 }
499 }
500
501 /**
502 * Updates an item in the tree and optionally recursively updates the item's children
503 * @param obj The item to update in the tree
504 * @param recurse Whether to recurse through the item's children
505 */
_updateObject(SPObject * obj,bool recurse)506 void ObjectsPanel::_updateObject( SPObject *obj, bool recurse ) {
507 Gtk::TreeModel::iterator tree_iter;
508 if (_findInTreeCache(SP_ITEM(obj), tree_iter)) {
509 Gtk::TreeModel::Row row = *tree_iter;
510
511 //We found our item in the tree; now update it!
512 SPItem * item = SP_IS_ITEM(obj) ? SP_ITEM(obj) : nullptr;
513 SPGroup * group = SP_IS_GROUP(obj) ? SP_GROUP(obj) : nullptr;
514
515 gchar const * label = obj->label() ? obj->label() : obj->getId();
516 row[_model->_colLabel] = label ? label : obj->defaultLabel();
517 row[_model->_colVisible] = item ? !item->isHidden() : false;
518 row[_model->_colLocked] = item ? !item->isSensitive() : false;
519 row[_model->_colType] = group ? (group->layerMode() == SPGroup::LAYER ? 2 : 1) : 0;
520 row[_model->_colHighlight] = item ? (item->isHighlightSet() ? item->highlight_color() : item->highlight_color() & 0xffffff00) : 0;
521 row[_model->_colClipMask] = item ? (
522 (item->getClipObject() ? 1 : 0) |
523 (item->getMaskObject() ? 2 : 0)
524 ) : 0;
525 //row[_model->_colInsertOrder] = group ? (group->insertBottom() ? 2 : 1) : 0;
526
527 if (recurse){
528 for (auto& iter: obj->children) {
529 _updateObject(&iter, recurse);
530 }
531 }
532 }
533 }
534
535 /**
536 * Updates the composite controls for the selected item
537 */
_updateComposite()538 void ObjectsPanel::_updateComposite() {
539 if (!_blockCompositeUpdate)
540 {
541 //Set the default values
542 bool setValues = true;
543
544 //Get/set the values
545 _tree.get_selection()->selected_foreach_iter(sigc::bind<bool *>(sigc::mem_fun(*this, &ObjectsPanel::_compositingChanged), &setValues));
546
547 }
548 }
549
550 /**
551 * Sets the compositing values for the first selected item in the tree
552 * @param iter Current tree item
553 * @param setValues Whether to set the compositing values
554 */
_compositingChanged(const Gtk::TreeModel::iterator & iter,bool * setValues)555 void ObjectsPanel::_compositingChanged( const Gtk::TreeModel::iterator& iter, bool *setValues )
556 {
557 if (iter) {
558 Gtk::TreeModel::Row row = *iter;
559 SPItem *item = row[_model->_colObject];
560 if (*setValues)
561 {
562 _setCompositingValues(item);
563 *setValues = false;
564 }
565 }
566 }
567
568 /**
569 * Occurs when the current desktop selection changes
570 * @param sel The current selection
571 */
_objectsSelected(Selection * sel)572 void ObjectsPanel::_objectsSelected( Selection *sel ) {
573
574 bool setOpacity = true;
575 _selectedConnection.block();
576
577 _tree.get_selection()->unselect_all();
578 _store->foreach_iter(sigc::mem_fun(*this, &ObjectsPanel::_clearPrevSelectionState));
579
580 SPItem *item = nullptr;
581 auto items = sel->items();
582 for(auto i=items.begin(); i!=items.end(); ++i){
583 item = *i;
584 if (setOpacity)
585 {
586 _setCompositingValues(item);
587 setOpacity = false;
588 }
589 _updateObjectSelected(item, (*i)==items.back(), false);
590 }
591 if (!item) {
592 if (_desktop->currentLayer() && SP_IS_ITEM(_desktop->currentLayer())) {
593 item = SP_ITEM(_desktop->currentLayer());
594 _setCompositingValues(item);
595 _updateObjectSelected(item, false, true);
596 }
597 }
598 _selectedConnection.unblock();
599 _checkTreeSelection();
600 }
601
602 /**
603 * Helper function for setting the compositing values
604 * @param item Item to use for setting the compositing values
605 */
_setCompositingValues(SPItem * item)606 void ObjectsPanel::_setCompositingValues(SPItem *item)
607 {
608 // Block the connections to avoid interference
609 _isolationConnection.block();
610 _opacityConnection.block();
611 _blendConnection.block();
612 _blurConnection.block();
613
614 // Set the isolation
615 auto isolation = item->style->isolation.set ? item->style->isolation.value : SP_CSS_ISOLATION_AUTO;
616 _filter_modifier.set_isolation_mode(isolation, true);
617 // Set the opacity
618 double opacity = (item->style->opacity.set ? SP_SCALE24_TO_FLOAT(item->style->opacity.value) : 1);
619 opacity *= 100; // Display in percent.
620 _filter_modifier.set_opacity_value(opacity);
621 // Set the blend mode
622 if (item->style->isolation.value == SP_CSS_ISOLATION_ISOLATE) {
623 _filter_modifier.set_blend_mode(SP_CSS_BLEND_NORMAL, true);
624 } else {
625 _filter_modifier.set_blend_mode(item->style->mix_blend_mode.value, true);
626 }
627 if (_filter_modifier.get_blend_mode() == SP_CSS_BLEND_NORMAL) {
628 if (!item->style->mix_blend_mode.set && item->style->filter.set && item->style->getFilter()) {
629 auto blend = filter_get_legacy_blend(item);
630 _filter_modifier.set_blend_mode(blend, true);
631 }
632 }
633 SPGaussianBlur *spblur = nullptr;
634 if (item->style->getFilter()) {
635 for (auto& primitive_obj: item->style->getFilter()->children) {
636 if (!SP_IS_FILTER_PRIMITIVE(&primitive_obj)) {
637 break;
638 }
639 if (SP_IS_GAUSSIANBLUR(&primitive_obj) && !spblur) {
640 //Get the blur value
641 spblur = SP_GAUSSIANBLUR(&primitive_obj);
642 }
643 }
644 }
645
646 //Set the blur value
647 double blur_value = 0;
648 if (spblur) {
649 Geom::OptRect bbox = item->bounds(SPItem::GEOMETRIC_BBOX); // calculating the bbox is expensive; only do this if we have an spblur in the first place
650 if (bbox) {
651 double perimeter = bbox->dimensions()[Geom::X] + bbox->dimensions()[Geom::Y]; // fixme: this is only half the perimeter, is that correct?
652 blur_value = spblur->stdDeviation.getNumber() * 400 / perimeter;
653 }
654 }
655 _filter_modifier.set_blur_value(blur_value);
656
657 //Unblock connections
658 _isolationConnection.unblock();
659 _blurConnection.unblock();
660 _blendConnection.unblock();
661 _opacityConnection.unblock();
662 }
663
664 // See the comment in objects.h for _tree_cache
665 /**
666 * Find the specified item in the tree cache
667 * @param iter Current tree item
668 * @param tree_iter Tree_iter will point to the row in which the tree item was found
669 * @return True if found
670 */
_findInTreeCache(SPItem * item,Gtk::TreeModel::iterator & tree_iter)671 bool ObjectsPanel::_findInTreeCache(SPItem* item, Gtk::TreeModel::iterator &tree_iter) {
672 if (not item) {
673 return false;
674 }
675
676 try {
677 tree_iter = _tree_cache.at(item);
678 }
679 catch (std::out_of_range) {
680 // Apparently, item cannot be found in the tree_cache, which could mean that
681 // - the tree and/or tree_cache are out-dated or in the process of being updated.
682 // - a layer is selected, which is not visible in the objects panel (see _objectsSelected())
683 // Anyway, this doesn't seem all that critical, so no warnings; just return false
684 return false;
685 }
686
687 /* If the row in the tree has been deleted, and an old tree_cache is being used, then we will
688 * get a segmentation fault crash somewhere here; so make sure iters don't linger around!
689 * We can only check the validity as done below, but this is rather slow according to the
690 * documentation (adds 0.25 s for a 2k long tree). But better safe than sorry
691 */
692 if (not _store->iter_is_valid(tree_iter)) {
693 g_critical("Invalid iterator to Gtk::tree in objects panel; just prevented a segfault!");
694 return false;
695 }
696
697 return true;
698 }
699
700
701 /**
702 * Find the specified item in the tree store and (de)select it, optionally scrolling to the item
703 * @param item Item to select in the tree
704 * @param scrollto Whether to scroll to the item
705 * @param expand If true, the path in the tree towards item will be expanded
706 */
_updateObjectSelected(SPItem * item,bool scrollto,bool expand)707 void ObjectsPanel::_updateObjectSelected(SPItem* item, bool scrollto, bool expand)
708 {
709 Gtk::TreeModel::iterator tree_iter;
710 if (_findInTreeCache(item, tree_iter)) {
711 Gtk::TreeModel::Row row = *tree_iter;
712
713 //We found the item! Expand to the path and select it in the tree.
714 Gtk::TreePath path = _store->get_path(tree_iter);
715 _tree.expand_to_path( path );
716 if (!expand)
717 // but don't expand itself, just the path
718 _tree.collapse_row(path);
719
720 Glib::RefPtr<Gtk::TreeSelection> select = _tree.get_selection();
721
722 select->select(tree_iter);
723 row[_model->_colPrevSelectionState] = true;
724 if (scrollto) {
725 //Scroll to the item in the tree
726 _tree.scroll_to_row(path, 0.5);
727 }
728 }
729 }
730
731 /**
732 * Pushes the current tree selection to the canvas
733 */
_pushTreeSelectionToCurrent()734 void ObjectsPanel::_pushTreeSelectionToCurrent()
735 {
736 if ( _desktop && _desktop->currentRoot() ) {
737 //block connections for selection and compositing values to prevent interference
738 _selectionChangedConnection.block();
739 _documentChangedCurrentLayer.block();
740 //Clear the selection and then iterate over the tree selection, pushing each item to the desktop
741 _desktop->selection->clear();
742 if (_tree.get_selection()->count_selected_rows() == 0) {
743 _store->foreach_iter(sigc::mem_fun(*this, &ObjectsPanel::_clearPrevSelectionState));
744 }
745 bool setOpacity = true;
746 bool first_pass = true;
747 _store->foreach_iter(sigc::bind<bool *>(sigc::mem_fun(*this, &ObjectsPanel::_selectItemCallback), &setOpacity, &first_pass));
748 first_pass = false;
749 _store->foreach_iter(sigc::bind<bool *>(sigc::mem_fun(*this, &ObjectsPanel::_selectItemCallback), &setOpacity, &first_pass));
750
751 //unblock connections, unless we were already blocking them beforehand
752 _selectionChangedConnection.unblock();
753 _documentChangedCurrentLayer.unblock();
754
755 _checkTreeSelection();
756 }
757 }
758
759 /**
760 * Helper function for pushing the current tree selection to the current desktop
761 * @param iter Current tree item
762 * @param setCompositingValues Whether to set the compositing values
763 */
_selectItemCallback(const Gtk::TreeModel::iterator & iter,bool * setCompositingValues,bool * first_pass)764 bool ObjectsPanel::_selectItemCallback(const Gtk::TreeModel::iterator& iter, bool *setCompositingValues, bool *first_pass)
765 {
766 Gtk::TreeModel::Row row = *iter;
767 bool selected = _tree.get_selection()->is_selected(iter);
768 if (selected) { // All items selected in the treeview will be added to the current selection
769 /* Adding/removing only the items that were selected or deselected since the previous call to _pushTreeSelectionToCurrent()
770 * is very slow on large documents, because _desktop->selection->remove(item) needs to traverse the whole ObjectSet to find
771 * the item to be removed. When all N objects are selected in a document, clearing the whole selection would require O(N^2)
772 * That's why we simply clear the complete selection using _desktop->selection->clear(), and re-add all items one by one.
773 * This is much faster.
774 */
775
776 /* On the first pass, we will add only the items that were selected before too. Then, on the second pass, we will add the
777 * newly selected items such that the last selected items will be actually last. This is needed for example when the user
778 * wants to align relative to the last selected item.
779 */
780 if (*first_pass == row[_model->_colPrevSelectionState]) {
781 SPItem *item = row[_model->_colObject];
782 if (!SP_IS_GROUP(item) || SP_GROUP(item)->layerMode() != SPGroup::LAYER) {
783 //If the item is not a layer, then select it and set the current layer to its parent (if it's the first item)
784 if (_desktop->selection->isEmpty()) {
785 _desktop->setCurrentLayer(item->parent);
786 }
787 _desktop->selection->add(item);
788 } else {
789 //If the item is a layer, set the current layer
790 if (_desktop->selection->isEmpty()) {
791 _desktop->setCurrentLayer(item);
792 }
793 }
794 if (*setCompositingValues) {
795 //Only set the compositing values for the first item <-- TODO: We have this comment here, but this has not actually been implemented?
796 _setCompositingValues(item);
797 *setCompositingValues = false;
798 }
799 }
800 }
801
802 if (not *first_pass) {
803 row[_model->_colPrevSelectionState] = selected;
804 }
805
806 return false;
807 }
808
_clearPrevSelectionState(const Gtk::TreeModel::iterator & iter)809 bool ObjectsPanel::_clearPrevSelectionState( const Gtk::TreeModel::iterator& iter) {
810 Gtk::TreeModel::Row row = *iter;
811 row[_model->_colPrevSelectionState] = false;
812 return false;
813 }
814
815 /**
816 * Handles button sensitivity
817 */
_checkTreeSelection()818 void ObjectsPanel::_checkTreeSelection()
819 {
820 bool sensitive = _tree.get_selection()->count_selected_rows() > 0;
821 //TODO: top/bottom sensitivity
822 bool sensitiveNonTop = true;
823 bool sensitiveNonBottom = true;
824
825 for (auto & it : _watching) {
826 it->set_sensitive( sensitive );
827 }
828 for (auto & it : _watchingNonTop) {
829 it->set_sensitive( sensitiveNonTop );
830 }
831 for (auto & it : _watchingNonBottom) {
832 it->set_sensitive( sensitiveNonBottom );
833 }
834
835 _tree.set_reorderable(sensitive); // Reorderable means that we allow drag-and-drop, but we only allow that when at least one row is selected
836 }
837
838 /**
839 * Sets visibility of items in the tree
840 * @param iter Current item in the tree
841 * @param visible Whether the item should be visible or not
842 */
_setVisibleIter(const Gtk::TreeModel::iterator & iter,const bool visible)843 void ObjectsPanel::_setVisibleIter( const Gtk::TreeModel::iterator& iter, const bool visible )
844 {
845 Gtk::TreeModel::Row row = *iter;
846 SPItem* item = row[_model->_colObject];
847 if (item)
848 {
849 item->setHidden( !visible );
850 row[_model->_colVisible] = visible;
851 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
852 }
853 }
854
855 /**
856 * Sets sensitivity of items in the tree
857 * @param iter Current item in the tree
858 * @param locked Whether the item should be locked
859 */
_setLockedIter(const Gtk::TreeModel::iterator & iter,const bool locked)860 void ObjectsPanel::_setLockedIter( const Gtk::TreeModel::iterator& iter, const bool locked )
861 {
862 Gtk::TreeModel::Row row = *iter;
863 SPItem* item = row[_model->_colObject];
864 if (item)
865 {
866 item->setLocked( locked );
867 row[_model->_colLocked] = locked;
868 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
869 }
870 }
871
872 /**
873 * Handles keyboard events
874 * @param event Keyboard event passed in from GDK
875 * @return Whether the event should be eaten (om nom nom)
876 */
_handleKeyEvent(GdkEventKey * event)877 bool ObjectsPanel::_handleKeyEvent(GdkEventKey *event)
878 {
879 if (!_desktop)
880 return false;
881
882 Gtk::AccelKey shortcut = Inkscape::Shortcuts::get_from_event(event);
883
884 switch (shortcut.get_key()) {
885 // how to get users key binding for the action “start-interactive-search” ??
886 // ctrl+f is just the default
887 case GDK_KEY_f:
888 if (shortcut.get_mod() | Gdk::CONTROL_MASK) return false;
889 break;
890 // shall we slurp ctrl+w to close panel?
891
892 // defocus:
893 case GDK_KEY_Escape:
894 if (_desktop->canvas) {
895 _desktop->canvas->grab_focus();
896 return true;
897 }
898 break;
899 }
900
901 // invoke user defined shortcuts first
902 bool done = Inkscape::Shortcuts::getInstance().invoke_verb(event, _desktop);
903 if (done) {
904 return true;
905 }
906
907 // handle events for the treeview
908 // bool empty = _desktop->selection->isEmpty();
909
910 switch (Inkscape::UI::Tools::get_latin_keyval(event)) {
911 case GDK_KEY_Return:
912 case GDK_KEY_KP_Enter:
913 {
914 Gtk::TreeModel::Path path;
915 Gtk::TreeViewColumn *focus_column = nullptr;
916
917 _tree.get_cursor(path, focus_column);
918 if (focus_column == _name_column && !_text_renderer->property_editable()) {
919 //Rename item
920 _text_renderer->property_editable() = true;
921 _tree.set_cursor(path, *_name_column, true);
922 grab_focus();
923 return true;
924 }
925 return false;
926 break;
927 }
928 }
929 return false;
930 }
931
932 /**
933 * Handles mouse events
934 * @param event Mouse event from GDK
935 * @return whether to eat the event (om nom nom)
936 */
_handleButtonEvent(GdkEventButton * event)937 bool ObjectsPanel::_handleButtonEvent(GdkEventButton* event)
938 {
939 static unsigned doubleclick = 0;
940 static bool overVisible = false;
941
942 //Right mouse button was clicked, launch the pop-up menu
943 if ( (event->type == GDK_BUTTON_PRESS) && (event->button == 3) ) {
944 Gtk::TreeModel::Path path;
945 int x = static_cast<int>(event->x);
946 int y = static_cast<int>(event->y);
947 if ( _tree.get_path_at_pos( x, y, path ) ) {
948 _checkTreeSelection();
949 _popupMenu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
950
951 if (_tree.get_selection()->is_selected(path)) {
952 return true;
953 }
954 }
955 }
956
957 //Left mouse button was pressed! In order to handle multiple item drag & drop,
958 //we need to defer selection by setting the select function so that the tree doesn't
959 //automatically select anything. In order to handle multiple item icon clicking,
960 //we need to eat the event. There might be a better way to do both of these...
961 if ( (event->type == GDK_BUTTON_PRESS) && (event->button == 1)) {
962 overVisible = false;
963 Gtk::TreeModel::Path path;
964 Gtk::TreeViewColumn* col = nullptr;
965 int x = static_cast<int>(event->x);
966 int y = static_cast<int>(event->y);
967 int x2 = 0;
968 int y2 = 0;
969 if ( _tree.get_path_at_pos( x, y, path, col, x2, y2 ) ) {
970 if (col == _tree.get_column(COL_VISIBLE-1)) {
971 //Click on visible column, eat this event to keep row selection
972 overVisible = true;
973 return true;
974 } else if (col == _tree.get_column(COL_LOCKED-1) ||
975 col == _tree.get_column(COL_TYPE-1) ||
976 //col == _tree.get_column(COL_INSERTORDER - 1) ||
977 col == _tree.get_column(COL_HIGHLIGHT-1)) {
978 //Click on an icon column, eat this event to keep row selection
979 return true;
980 } else if ( !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) & _tree.get_selection()->is_selected(path) ) {
981 //Click on a selected item with no modifiers, defer selection to the mouse-up by
982 //setting the select function to _noSelection
983 _tree.get_selection()->set_select_function(sigc::mem_fun(*this, &ObjectsPanel::_noSelection));
984 _defer_target = path;
985 }
986 }
987 }
988
989 //Restore the selection function to allow tree selection on mouse button release
990 if ( event->type == GDK_BUTTON_RELEASE) {
991 _tree.get_selection()->set_select_function(sigc::mem_fun(*this, &ObjectsPanel::_rowSelectFunction));
992 }
993
994 //CellRenderers do not have good support for dealing with multiple items, so
995 //we handle all events on them here
996 if ( (event->type == GDK_BUTTON_RELEASE) && (event->button == 1)) {
997
998 Gtk::TreeModel::Path path;
999 Gtk::TreeViewColumn* col = nullptr;
1000 int x = static_cast<int>(event->x);
1001 int y = static_cast<int>(event->y);
1002 int x2 = 0;
1003 int y2 = 0;
1004 if ( _tree.get_path_at_pos( x, y, path, col, x2, y2 ) ) {
1005 if (_defer_target) {
1006 //We had deferred a selection target, select it here (assuming no drag & drop)
1007 if (_defer_target == path && !(event->x == 0 && event->y == 0))
1008 {
1009 _tree.set_cursor(path, *col, false);
1010 }
1011 _defer_target = Gtk::TreeModel::Path();
1012 }
1013 else {
1014 if (event->state & GDK_SHIFT_MASK) {
1015 // Shift left click on the visible/lock columns toggles "solo" mode
1016 if (col == _tree.get_column(COL_VISIBLE - 1)) {
1017 _takeAction(BUTTON_SOLO);
1018 } else if (col == _tree.get_column(COL_LOCKED - 1)) {
1019 _takeAction(BUTTON_LOCK_OTHERS);
1020 }
1021 } else if (event->state & GDK_MOD1_MASK) {
1022 // Alt+left click on the visible/lock columns toggles "solo" mode and preserves selection
1023 Gtk::TreeModel::iterator iter = _store->get_iter(path);
1024 if (_store->iter_is_valid(iter)) {
1025 Gtk::TreeModel::Row row = *iter;
1026 SPItem *item = row[_model->_colObject];
1027 if (col == _tree.get_column(COL_VISIBLE - 1)) {
1028 _desktop->toggleLayerSolo( item );
1029 DocumentUndo::maybeDone(_desktop->doc(), "layer:solo", SP_VERB_LAYER_SOLO, _("Toggle layer solo"));
1030 } else if (col == _tree.get_column(COL_LOCKED - 1)) {
1031 _desktop->toggleLockOtherLayers( item );
1032 DocumentUndo::maybeDone(_desktop->doc(), "layer:lockothers", SP_VERB_LAYER_LOCK_OTHERS, _("Lock other layers"));
1033 }
1034 }
1035 } else {
1036 Gtk::TreeModel::Children::iterator iter = _tree.get_model()->get_iter(path);
1037 Gtk::TreeModel::Row row = *iter;
1038
1039 SPItem* item = row[_model->_colObject];
1040
1041 if (col == _tree.get_column(COL_VISIBLE - 1)) {
1042 if (overVisible) {
1043 //Toggle visibility
1044 bool newValue = !row[_model->_colVisible];
1045 if (_tree.get_selection()->is_selected(path))
1046 {
1047 //If the current row is selected, toggle the visibility
1048 //for all selected items
1049 _tree.get_selection()->selected_foreach_iter(sigc::bind<bool>(sigc::mem_fun(*this, &ObjectsPanel::_setVisibleIter), newValue));
1050 }
1051 else
1052 {
1053 //If the current row is not selected, toggle just its visibility
1054 row[_model->_colVisible] = newValue;
1055 item->setHidden(!newValue);
1056 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1057 }
1058 DocumentUndo::done( _desktop->doc() , SP_VERB_DIALOG_OBJECTS,
1059 newValue? _("Unhide objects") : _("Hide objects"));
1060 overVisible = false;
1061 }
1062 } else if (col == _tree.get_column(COL_LOCKED - 1)) {
1063 //Toggle locking
1064 bool newValue = !row[_model->_colLocked];
1065 if (_tree.get_selection()->is_selected(path))
1066 {
1067 //If the current row is selected, toggle the sensitivity for
1068 //all selected items
1069 _tree.get_selection()->selected_foreach_iter(sigc::bind<bool>(sigc::mem_fun(*this, &ObjectsPanel::_setLockedIter), newValue));
1070 }
1071 else
1072 {
1073 //If the current row is not selected, toggle just its sensitivity
1074 row[_model->_colLocked] = newValue;
1075 item->setLocked( newValue );
1076 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1077 }
1078 DocumentUndo::done( _desktop->doc() , SP_VERB_DIALOG_OBJECTS,
1079 newValue? _("Lock objects") : _("Unlock objects"));
1080
1081 } else if (col == _tree.get_column(COL_TYPE - 1)) {
1082 if (SP_IS_GROUP(item))
1083 {
1084 //Toggle the current item between a group and a layer
1085 SPGroup * g = SP_GROUP(item);
1086 bool newValue = g->layerMode() == SPGroup::LAYER;
1087 row[_model->_colType] = newValue ? 1: 2;
1088 g->setLayerMode(newValue ? SPGroup::GROUP : SPGroup::LAYER);
1089 g->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1090 DocumentUndo::done( _desktop->doc() , SP_VERB_DIALOG_OBJECTS,
1091 newValue? _("Layer to group") : _("Group to layer"));
1092 _pushTreeSelectionToCurrent();
1093 }
1094 } /*else if (col == _tree.get_column(COL_INSERTORDER - 1)) {
1095 if (SP_IS_GROUP(item))
1096 {
1097 //Toggle the current item's insert order
1098 SPGroup * g = SP_GROUP(item);
1099 bool newValue = !g->insertBottom();
1100 row[_model->_colInsertOrder] = newValue ? 2: 1;
1101 g->setInsertBottom(newValue);
1102 g->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1103 DocumentUndo::done( _desktop->doc() , SP_VERB_DIALOG_OBJECTS,
1104 newValue? _("Set insert mode bottom") : _("Set insert mode top"));
1105 }
1106 }*/ else if (col == _tree.get_column(COL_HIGHLIGHT - 1)) {
1107 //Clear the highlight targets
1108 _highlight_target.clear();
1109 if (_tree.get_selection()->is_selected(path))
1110 {
1111 //If the current item is selected, store all selected items
1112 //in the highlight source
1113 _tree.get_selection()->selected_foreach_iter(sigc::mem_fun(*this, &ObjectsPanel::_storeHighlightTarget));
1114 } else {
1115 //If the current item is not selected, store only it in the highlight source
1116 _storeHighlightTarget(iter);
1117 }
1118 if (_selectedColor)
1119 {
1120 //Set up the color selector
1121 SPColor color;
1122 color.set( row[_model->_colHighlight] );
1123 _selectedColor->setColorAlpha(color, SP_RGBA32_A_F(row[_model->_colHighlight]));
1124 }
1125 //Show the color selector dialog
1126 _colorSelectorDialog.show();
1127 }
1128 }
1129 }
1130 }
1131 }
1132
1133 //Second mouse button press, set double click status for when the mouse is released
1134 if ( (event->type == GDK_2BUTTON_PRESS) && (event->button == 1) ) {
1135 doubleclick = 1;
1136 }
1137
1138 //Double click on mouse button release, if we're over the label column, edit
1139 //the item name
1140 if ( event->type == GDK_BUTTON_RELEASE && doubleclick) {
1141 doubleclick = 0;
1142 Gtk::TreeModel::Path path;
1143 Gtk::TreeViewColumn* col = nullptr;
1144 int x = static_cast<int>(event->x);
1145 int y = static_cast<int>(event->y);
1146 int x2 = 0;
1147 int y2 = 0;
1148 if ( _tree.get_path_at_pos( x, y, path, col, x2, y2 ) && col == _name_column) {
1149 // Double click on the Layer name, enable editing
1150 _text_renderer->property_editable() = true;
1151 _tree.set_cursor (path, *_name_column, true);
1152 grab_focus();
1153 }
1154 }
1155
1156 return false;
1157 }
1158
1159 /**
1160 * Stores items in the highlight target vector to manipulate with the color selector
1161 * @param iter Current tree item to store
1162 */
_storeHighlightTarget(const Gtk::TreeModel::iterator & iter)1163 void ObjectsPanel::_storeHighlightTarget(const Gtk::TreeModel::iterator& iter)
1164 {
1165 Gtk::TreeModel::Row row = *iter;
1166 SPItem* item = row[_model->_colObject];
1167 if (item)
1168 {
1169 _highlight_target.push_back(item);
1170 }
1171 }
1172
1173 /*
1174 * Drag and drop within the tree
1175 */
_handleDragDrop(const Glib::RefPtr<Gdk::DragContext> &,int x,int y,guint)1176 bool ObjectsPanel::_handleDragDrop(const Glib::RefPtr<Gdk::DragContext>& /*context*/, int x, int y, guint /*time*/)
1177 {
1178 //Set up our defaults and clear the source vector
1179 _dnd_into = false;
1180 _dnd_target = nullptr;
1181 _dnd_source.clear();
1182 _dnd_source_includes_layer = false;
1183
1184 //Add all selected items to the source vector
1185 _tree.get_selection()->selected_foreach_iter(sigc::mem_fun(*this, &ObjectsPanel::_storeDragSource));
1186
1187 bool cancel_dnd = false;
1188 bool dnd_to_top_at_end = false;
1189
1190 Gtk::TreeModel::Path target_path;
1191 Gtk::TreeViewDropPosition pos;
1192 if (_tree.get_dest_row_at_pos(x, y, target_path, pos)) {
1193 // SPItem::moveTo() will be used to move the selected items to their new position, but
1194 // moveTo() can only "drop before"; we therefore need to find the next path and drop
1195 // the selection just before it, instead of "dropping after" the target path
1196 if (pos == Gtk::TREE_VIEW_DROP_AFTER) {
1197 Gtk::TreeModel::Path next_path = target_path;
1198 if (_tree.row_expanded(next_path)) {
1199 next_path.down(); // The next path is at a lower level in the hierarchy, i.e. in a layer or group
1200 } else {
1201 next_path.next(); // The next path is at the same level
1202 }
1203 // A next path might however not be present, if we're dropping at the end of the tree view
1204 if (_store->iter_is_valid(_store->get_iter(next_path))) {
1205 target_path = next_path;
1206 } else {
1207 // Dragging to the "end" of the treeview ; we'll get the parent group or layer of the last
1208 // item, and drop into that parent
1209 Gtk::TreeModel::Path up_path = target_path;
1210 up_path.up();
1211 if (_store->iter_is_valid(_store->get_iter(up_path))) {
1212 // Drop into the parent of the last item
1213 target_path = up_path;
1214 _dnd_into = true;
1215 } else {
1216 // Drop into the top level, completely at the end of the treeview;
1217 dnd_to_top_at_end = true;
1218 }
1219 }
1220 }
1221
1222 if (dnd_to_top_at_end) {
1223 g_assert(_dnd_target == nullptr);
1224 } else {
1225 // Find the SPItem corresponding to the target_path/row at which we're dropping our selection
1226 Gtk::TreeModel::iterator iter = _store->get_iter(target_path);
1227 if (_store->iter_is_valid(iter)) {
1228 Gtk::TreeModel::Row row = *iter;
1229 _dnd_target = row[_model->_colObject]; //Set the drop target
1230 if ((pos == Gtk::TREE_VIEW_DROP_INTO_OR_BEFORE) || (pos == Gtk::TREE_VIEW_DROP_INTO_OR_AFTER)) {
1231 // Trying to drop into a layer or group
1232 if (SP_IS_GROUP(_dnd_target)) {
1233 _dnd_into = true;
1234 } else {
1235 // If the target is not a group (or layer), then we cannot drop into it (unless we
1236 // would create a group on the fly), so we will cancel the drag and drop action.
1237 cancel_dnd = true;
1238 }
1239 }
1240 // If the source selection contains a layer however, then it can not be dropped ...
1241 bool c1 = target_path.size() > 1; // .. below the top-level
1242 bool c2 = SP_IS_GROUP(_dnd_target) && _dnd_into; // .. or in any group (at the top level)
1243 if (SP_IS_GROUP(_dnd_target) && SP_GROUP(_dnd_target)->layerMode() != SPGroup::LAYER &&
1244 _dnd_source_includes_layer &&
1245 (c1 || c2))
1246 {
1247 cancel_dnd = true;
1248 }
1249 } else {
1250 cancel_dnd = true;
1251 }
1252 }
1253 }
1254
1255 if (not cancel_dnd) {
1256 _takeAction(DRAGNDROP);
1257 }
1258
1259 return true; // If True: then we're signaling here that nothing needs to be done by the TreeView; we're updating ourselves..
1260 }
1261
1262 /**
1263 * Stores all selected items as the drag source
1264 * @param iter Current tree item
1265 */
_storeDragSource(const Gtk::TreeModel::iterator & iter)1266 void ObjectsPanel::_storeDragSource(const Gtk::TreeModel::iterator& iter)
1267 {
1268 Gtk::TreeModel::Row row = *iter;
1269 SPItem* item = row[_model->_colObject];
1270 if (item) {
1271 _dnd_source.push_back(item);
1272 if (SP_IS_GROUP(item) && (SP_GROUP(item)->layerMode() == SPGroup::LAYER)) {
1273 _dnd_source_includes_layer = true;
1274 }
1275 }
1276 }
1277
1278 /*
1279 * Move a selection of items in response to a drag & drop action
1280 */
_doTreeMove()1281 void ObjectsPanel::_doTreeMove( )
1282 {
1283 g_assert(_desktop != nullptr);
1284 g_assert(_document != nullptr);
1285
1286 std::vector<gchar *> idvector;
1287
1288 //Clear the desktop selection
1289 _desktop->selection->clear();
1290 while (!_dnd_source.empty())
1291 {
1292 SPItem *obj = _dnd_source.back();
1293 _dnd_source.pop_back();
1294
1295 if (obj != _dnd_target) {
1296 //Store the object id (for selection later) and move the object
1297 idvector.push_back(g_strdup(obj->getId()));
1298 obj->moveTo(_dnd_target, _dnd_into);
1299 }
1300 }
1301 //Select items
1302 while (!idvector.empty()) {
1303 //Grab the id from the vector, get the item in the document and select it
1304 gchar * id = idvector.back();
1305 idvector.pop_back();
1306 SPObject *obj = _document->getObjectById(id);
1307 g_free(id);
1308 if (obj && SP_IS_ITEM(obj)) {
1309 SPItem *item = SP_ITEM(obj);
1310 if (!SP_IS_GROUP(item) || SP_GROUP(item)->layerMode() != SPGroup::LAYER)
1311 {
1312 if (_desktop->selection->isEmpty()) _desktop->setCurrentLayer(item->parent);
1313 _desktop->selection->add(item);
1314 }
1315 else
1316 {
1317 if (_desktop->selection->isEmpty()) _desktop->setCurrentLayer(item);
1318 }
1319 }
1320 }
1321
1322 DocumentUndo::done( _desktop->doc() , SP_VERB_NONE,
1323 _("Moved objects"));
1324 }
1325
1326 /**
1327 * Prevents the treeview from emiting and responding to most signals; needed when it's not up to date
1328 */
_blockAllSignals(bool should_block=true)1329 void ObjectsPanel::_blockAllSignals(bool should_block = true) {
1330
1331 // incoming signals
1332 _documentChangedCurrentLayer.block(should_block);
1333 _isolationConnection.block(should_block);
1334 _opacityConnection.block(should_block);
1335 _blendConnection.block(should_block);
1336 _blurConnection.block(should_block);
1337 if (_pending && should_block) {
1338 // Kill any pending UI event, e.g. a delete or drag 'n drop action, which could
1339 // become unpredictable after the tree has been updated
1340 _pending->_signal.disconnect();
1341 }
1342
1343 _selectionChangedConnection.block(should_block);
1344 // outgoing signal
1345 _selectedConnection.block(should_block);
1346
1347 // These are not blocked: desktopChangeConn, _documentChangedConnection
1348 }
1349
1350 /**
1351 * Fires the action verb
1352 */
_fireAction(unsigned int code)1353 void ObjectsPanel::_fireAction( unsigned int code )
1354 {
1355 if ( _desktop ) {
1356 Verb *verb = Verb::get( code );
1357 if ( verb ) {
1358 SPAction *action = verb->get_action(_desktop);
1359 if ( action ) {
1360 sp_action_perform( action, nullptr );
1361 }
1362 }
1363 }
1364 }
1365
_executeUpdate()1366 bool ObjectsPanel::_executeUpdate() {
1367 _objectsChanged(nullptr);
1368 return false;
1369 }
1370
1371 /**
1372 * Executes the given button action during the idle time
1373 */
_takeAction(int val)1374 void ObjectsPanel::_takeAction( int val )
1375 {
1376 if (val == UPDATE_TREE) {
1377 _pending_update = true;
1378 // We might already have been updating the tree, but new data is available now
1379 // so we will then first cancel the old update before scheduling a new one
1380 _processQueue_sig.disconnect();
1381 _executeUpdate_sig.disconnect();
1382 _blockAllSignals(true);
1383 //_store->clear();
1384 _tree_cache.clear();
1385 _executeUpdate_sig = Glib::signal_timeout().connect( sigc::mem_fun(*this, &ObjectsPanel::_executeUpdate), 500, Glib::PRIORITY_DEFAULT_IDLE+50);
1386 // In the spray tool, updating the tree competes in priority with the redrawing of the canvas,
1387 // see SPCanvas::addIdle(), which is set to UPDATE_PRIORITY (=G_PRIORITY_DEFAULT_IDLE). We
1388 // should take a lower priority (= higher value) to keep the spray tool updating longer, and to prevent
1389 // the objects-panel from clogging the processor; however, once the spraying slows down, the tree might
1390 // get updated anyway.
1391 } else if ( !_pending ) {
1392 _pending = new InternalUIBounce();
1393 _pending->_actionCode = val;
1394 _pending->_signal = Glib::signal_timeout().connect( sigc::mem_fun(*this, &ObjectsPanel::_executeAction), 0 );
1395 }
1396 }
1397
1398 /**
1399 * Executes the pending button action
1400 */
_executeAction()1401 bool ObjectsPanel::_executeAction()
1402 {
1403 // Make sure selected layer hasn't changed since the action was triggered
1404 if ( _document && _pending)
1405 {
1406 int val = _pending->_actionCode;
1407 // SPObject* target = _pending->_target;
1408
1409 switch ( val ) {
1410 case BUTTON_NEW:
1411 {
1412 _fireAction( SP_VERB_LAYER_NEW );
1413 }
1414 break;
1415 case BUTTON_RENAME:
1416 {
1417 _fireAction( SP_VERB_LAYER_RENAME );
1418 }
1419 break;
1420 case BUTTON_TOP:
1421 {
1422 if (_desktop->selection->isEmpty())
1423 {
1424 _fireAction( SP_VERB_LAYER_TO_TOP );
1425 }
1426 else
1427 {
1428 _fireAction( SP_VERB_SELECTION_TO_FRONT);
1429 }
1430 }
1431 break;
1432 case BUTTON_BOTTOM:
1433 {
1434 if (_desktop->selection->isEmpty())
1435 {
1436 _fireAction( SP_VERB_LAYER_TO_BOTTOM );
1437 }
1438 else
1439 {
1440 _fireAction( SP_VERB_SELECTION_TO_BACK);
1441 }
1442 }
1443 break;
1444 case BUTTON_UP:
1445 {
1446 if (_desktop->selection->isEmpty())
1447 {
1448 _fireAction( SP_VERB_LAYER_RAISE );
1449 }
1450 else
1451 {
1452 _fireAction( SP_VERB_SELECTION_STACK_UP );
1453 }
1454 }
1455 break;
1456 case BUTTON_DOWN:
1457 {
1458 if (_desktop->selection->isEmpty())
1459 {
1460 _fireAction( SP_VERB_LAYER_LOWER );
1461 }
1462 else
1463 {
1464 _fireAction( SP_VERB_SELECTION_STACK_DOWN );
1465 }
1466 }
1467 break;
1468 case BUTTON_DUPLICATE:
1469 {
1470 if (_desktop->selection->isEmpty())
1471 {
1472 _fireAction( SP_VERB_LAYER_DUPLICATE );
1473 }
1474 else
1475 {
1476 _fireAction( SP_VERB_EDIT_DUPLICATE );
1477 }
1478 }
1479 break;
1480 case BUTTON_DELETE:
1481 {
1482 if (_desktop->selection->isEmpty())
1483 {
1484 _fireAction( SP_VERB_LAYER_DELETE );
1485 }
1486 else
1487 {
1488 _fireAction( SP_VERB_EDIT_DELETE );
1489 }
1490 }
1491 break;
1492 case BUTTON_SOLO:
1493 {
1494 _fireAction( SP_VERB_LAYER_SOLO );
1495 }
1496 break;
1497 case BUTTON_SHOW_ALL:
1498 {
1499 _fireAction( SP_VERB_LAYER_SHOW_ALL );
1500 }
1501 break;
1502 case BUTTON_HIDE_ALL:
1503 {
1504 _fireAction( SP_VERB_LAYER_HIDE_ALL );
1505 }
1506 break;
1507 case BUTTON_LOCK_OTHERS:
1508 {
1509 _fireAction( SP_VERB_LAYER_LOCK_OTHERS );
1510 }
1511 break;
1512 case BUTTON_LOCK_ALL:
1513 {
1514 _fireAction( SP_VERB_LAYER_LOCK_ALL );
1515 }
1516 break;
1517 case BUTTON_UNLOCK_ALL:
1518 {
1519 _fireAction( SP_VERB_LAYER_UNLOCK_ALL );
1520 }
1521 break;
1522 case BUTTON_CLIPGROUP:
1523 {
1524 _fireAction ( SP_VERB_OBJECT_CREATE_CLIP_GROUP );
1525 }
1526 case BUTTON_SETCLIP:
1527 {
1528 _fireAction( SP_VERB_OBJECT_SET_CLIPPATH );
1529 }
1530 break;
1531 case BUTTON_UNSETCLIP:
1532 {
1533 _fireAction( SP_VERB_OBJECT_UNSET_CLIPPATH );
1534 }
1535 break;
1536 case BUTTON_SETMASK:
1537 {
1538 _fireAction( SP_VERB_OBJECT_SET_MASK );
1539 }
1540 break;
1541 case BUTTON_UNSETMASK:
1542 {
1543 _fireAction( SP_VERB_OBJECT_UNSET_MASK );
1544 }
1545 break;
1546 case BUTTON_GROUP:
1547 {
1548 _fireAction( SP_VERB_SELECTION_GROUP );
1549 }
1550 break;
1551 case BUTTON_UNGROUP:
1552 {
1553 _fireAction( SP_VERB_SELECTION_UNGROUP );
1554 }
1555 break;
1556 case BUTTON_COLLAPSE_ALL:
1557 {
1558 for (auto& obj: _document->getRoot()->children) {
1559 if (SP_IS_GROUP(&obj)) {
1560 _setCollapsed(SP_GROUP(&obj));
1561 }
1562 }
1563 _objectsChanged(_document->getRoot());
1564 }
1565 break;
1566 case DRAGNDROP:
1567 {
1568 _doTreeMove( );
1569 // The notifyChildOrderChanged signal will ensure that the TreeView gets updated
1570 }
1571 break;
1572 }
1573
1574 delete _pending;
1575 _pending = nullptr;
1576 }
1577
1578 return false;
1579 }
1580
1581 /**
1582 * Handles an unsuccessful item label edit (escape pressed, etc.)
1583 */
_handleEditingCancelled()1584 void ObjectsPanel::_handleEditingCancelled()
1585 {
1586 _text_renderer->property_editable() = false;
1587 }
1588
1589 /**
1590 * Handle a successful item label edit
1591 * @param path Tree path of the item currently being edited
1592 * @param new_text New label text
1593 */
_handleEdited(const Glib::ustring & path,const Glib::ustring & new_text)1594 void ObjectsPanel::_handleEdited(const Glib::ustring& path, const Glib::ustring& new_text)
1595 {
1596 Gtk::TreeModel::iterator iter = _tree.get_model()->get_iter(path);
1597 Gtk::TreeModel::Row row = *iter;
1598
1599 _renameObject(row, new_text);
1600 _text_renderer->property_editable() = false;
1601 }
1602
1603 /**
1604 * Renames an item in the tree
1605 * @param row Tree row
1606 * @param name New label to give to the item
1607 */
_renameObject(Gtk::TreeModel::Row row,const Glib::ustring & name)1608 void ObjectsPanel::_renameObject(Gtk::TreeModel::Row row, const Glib::ustring& name)
1609 {
1610 if ( row && _desktop) {
1611 SPItem* item = row[_model->_colObject];
1612 if ( item ) {
1613 gchar const* oldLabel = item->label();
1614 if ( !name.empty() && (!oldLabel || name != oldLabel) ) {
1615 item->setLabel(name.c_str());
1616 DocumentUndo::done( _desktop->doc() , SP_VERB_NONE,
1617 _("Rename object"));
1618 }
1619 }
1620 }
1621 }
1622
1623 /**
1624 * A row selection function used by the tree that doesn't allow any new items to be selected.
1625 * Currently, this is used to allow multi-item drag & drop.
1626 */
_noSelection(Glib::RefPtr<Gtk::TreeModel> const &,Gtk::TreeModel::Path const &,bool)1627 bool ObjectsPanel::_noSelection( Glib::RefPtr<Gtk::TreeModel> const & /*model*/, Gtk::TreeModel::Path const & /*path*/, bool /*currentlySelected*/ )
1628 {
1629 return false;
1630 }
1631
1632 /**
1633 * Default row selection function taken from the layers dialog
1634 */
_rowSelectFunction(Glib::RefPtr<Gtk::TreeModel> const &,Gtk::TreeModel::Path const &,bool currentlySelected)1635 bool ObjectsPanel::_rowSelectFunction( Glib::RefPtr<Gtk::TreeModel> const & /*model*/, Gtk::TreeModel::Path const & /*path*/, bool currentlySelected )
1636 {
1637 bool val = true;
1638 if ( !currentlySelected && _toggleEvent )
1639 {
1640 GdkEvent* event = gtk_get_current_event();
1641 if ( event ) {
1642 // (keep these checks separate, so we know when to call gdk_event_free()
1643 if ( event->type == GDK_BUTTON_PRESS ) {
1644 GdkEventButton const* target = reinterpret_cast<GdkEventButton const*>(_toggleEvent);
1645 GdkEventButton const* evtb = reinterpret_cast<GdkEventButton const*>(event);
1646
1647 if ( (evtb->window == target->window)
1648 && (evtb->send_event == target->send_event)
1649 && (evtb->time == target->time)
1650 && (evtb->state == target->state)
1651 )
1652 {
1653 // Ooooh! It's a magic one
1654 val = false;
1655 }
1656 }
1657 gdk_event_free(event);
1658 }
1659 }
1660 return val;
1661 }
1662
1663 /**
1664 * Sets a group to be collapsed and recursively collapses its children
1665 * @param group The group to collapse
1666 */
_setCollapsed(SPGroup * group)1667 void ObjectsPanel::_setCollapsed(SPGroup * group)
1668 {
1669 group->setExpanded(false);
1670 group->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1671 for (auto& iter: group->children) {
1672 if (SP_IS_GROUP(&iter)) {
1673 _setCollapsed(SP_GROUP(&iter));
1674 }
1675 }
1676 }
1677
1678 /**
1679 * Sets a group to be expanded or collapsed
1680 * @param iter Current tree item
1681 * @param isexpanded Whether to expand or collapse
1682 */
_setExpanded(const Gtk::TreeModel::iterator & iter,const Gtk::TreeModel::Path &,bool isexpanded)1683 void ObjectsPanel::_setExpanded(const Gtk::TreeModel::iterator& iter, const Gtk::TreeModel::Path& /*path*/, bool isexpanded)
1684 {
1685 Gtk::TreeModel::Row row = *iter;
1686
1687 SPItem* item = row[_model->_colObject];
1688 if (item && SP_IS_GROUP(item))
1689 {
1690 if (isexpanded)
1691 {
1692 //If we're expanding, simply perform the expansion
1693 SP_GROUP(item)->setExpanded(isexpanded);
1694 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1695 }
1696 else
1697 {
1698 //If we're collapsing, we need to recursively collapse, so call our helper function
1699 _setCollapsed(SP_GROUP(item));
1700 }
1701 }
1702 }
1703
1704 /**
1705 * Callback for when the highlight color is changed
1706 * @param csel Color selector
1707 * @param cp Objects panel
1708 */
_highlightPickerColorMod()1709 void ObjectsPanel::_highlightPickerColorMod()
1710 {
1711 SPColor color;
1712 float alpha = 0;
1713 _selectedColor->colorAlpha(color, alpha);
1714
1715 guint32 rgba = color.toRGBA32( alpha );
1716
1717 //Set the highlight color for all items in the _highlight_target (all selected items)
1718 for (auto target : _highlight_target)
1719 {
1720 target->setHighlightColor(rgba);
1721 target->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1722 }
1723 DocumentUndo::maybeDone(SP_ACTIVE_DOCUMENT, "highlight", SP_VERB_DIALOG_OBJECTS, _("Set object highlight color"));
1724 }
1725
1726 /**
1727 * Callback for when the opacity value is changed
1728 */
_opacityValueChanged()1729 void ObjectsPanel::_opacityValueChanged()
1730 {
1731 _blockCompositeUpdate = true;
1732 _tree.get_selection()->selected_foreach_iter(sigc::mem_fun(*this, &ObjectsPanel::_opacityChangedIter));
1733 DocumentUndo::maybeDone(_document, "opacity", SP_VERB_DIALOG_OBJECTS, _("Set object opacity"));
1734 _blockCompositeUpdate = false;
1735 }
1736
1737 /**
1738 * Change the opacity of the selected items in the tree
1739 * @param iter Current tree item
1740 */
_opacityChangedIter(const Gtk::TreeIter & iter)1741 void ObjectsPanel::_opacityChangedIter(const Gtk::TreeIter& iter)
1742 {
1743 Gtk::TreeModel::Row row = *iter;
1744 SPItem* item = row[_model->_colObject];
1745 if (item)
1746 {
1747 item->style->opacity.set = TRUE;
1748 item->style->opacity.value = SP_SCALE24_FROM_FLOAT(_filter_modifier.get_opacity_value() / 100);
1749 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1750 }
1751 }
1752
1753 /**
1754 * Callback for when the isolation value is changed
1755 */
_isolationValueChanged()1756 void ObjectsPanel::_isolationValueChanged()
1757 {
1758 _blockCompositeUpdate = true;
1759 _tree.get_selection()->selected_foreach_iter(sigc::mem_fun(*this, &ObjectsPanel::_isolationChangedIter));
1760 DocumentUndo::maybeDone(_document, "isolation", SP_VERB_DIALOG_OBJECTS, _("Set object isolation"));
1761 _blockCompositeUpdate = false;
1762 }
1763
1764 /**
1765 * Change the isolation of the selected items in the tree
1766 * @param iter Current tree item
1767 */
_isolationChangedIter(const Gtk::TreeIter & iter)1768 void ObjectsPanel::_isolationChangedIter(const Gtk::TreeIter &iter)
1769 {
1770 Gtk::TreeModel::Row row = *iter;
1771 SPItem *item = row[_model->_colObject];
1772 if (item) {
1773 item->style->isolation.set = TRUE;
1774 item->style->isolation.value = _filter_modifier.get_isolation_mode();
1775 if (item->style->isolation.value == SP_CSS_ISOLATION_ISOLATE) {
1776 item->style->mix_blend_mode.set = TRUE;
1777 item->style->mix_blend_mode.value = SP_CSS_BLEND_NORMAL;
1778 _filter_modifier.set_blend_mode(SP_CSS_BLEND_NORMAL, false);
1779 }
1780 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1781 }
1782 }
1783
1784 /**
1785 * Callback for when the blend mode is changed
1786 */
_blendValueChanged()1787 void ObjectsPanel::_blendValueChanged()
1788 {
1789 _blockCompositeUpdate = true;
1790 _tree.get_selection()->selected_foreach_iter(sigc::mem_fun(*this, &ObjectsPanel::_blendChangedIter));
1791 DocumentUndo::done(_document, SP_VERB_DIALOG_OBJECTS, _("Set object blend mode"));
1792 _blockCompositeUpdate = false;
1793 }
1794
1795 /**
1796 * Sets the blend mode of the selected tree items
1797 * @param iter Current tree item
1798 * @param blendmode Blend mode to set
1799 */
_blendChangedIter(const Gtk::TreeIter & iter)1800 void ObjectsPanel::_blendChangedIter(const Gtk::TreeIter &iter)
1801 {
1802 Gtk::TreeModel::Row row = *iter;
1803 SPItem* item = row[_model->_colObject];
1804 if (item)
1805 {
1806 // < 1.0 filter based blend removal
1807 if (!item->style->mix_blend_mode.set && item->style->filter.set && item->style->getFilter()) {
1808 remove_filter_legacy_blend(item);
1809 }
1810 item->style->mix_blend_mode.set = TRUE;
1811 if (_filter_modifier.get_blend_mode() &&
1812 item->style->isolation.value == SP_CSS_ISOLATION_ISOLATE)
1813 {
1814 item->style->mix_blend_mode.value = SP_CSS_BLEND_NORMAL;
1815 _filter_modifier.set_blend_mode(SP_CSS_BLEND_NORMAL, false);
1816 } else {
1817 item->style->mix_blend_mode.value = _filter_modifier.get_blend_mode();
1818 }
1819 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
1820 }
1821 }
1822
1823 /**
1824 * Callback for when the blur value has changed
1825 */
_blurValueChanged()1826 void ObjectsPanel::_blurValueChanged()
1827 {
1828 _blockCompositeUpdate = true;
1829 _tree.get_selection()->selected_foreach_iter(sigc::bind<double>(sigc::mem_fun(*this, &ObjectsPanel::_blurChangedIter), _filter_modifier.get_blur_value()));
1830 DocumentUndo::maybeDone(_document, "blur", SP_VERB_DIALOG_OBJECTS, _("Set object blur"));
1831 _blockCompositeUpdate = false;
1832 }
1833
1834 /**
1835 * Sets the blur value for the selected items in the tree
1836 * @param iter Current tree item
1837 * @param blur Blur value to set
1838 */
_blurChangedIter(const Gtk::TreeIter & iter,double blur)1839 void ObjectsPanel::_blurChangedIter(const Gtk::TreeIter& iter, double blur)
1840 {
1841 Gtk::TreeModel::Row row = *iter;
1842 SPItem* item = row[_model->_colObject];
1843 if (item)
1844 {
1845 //Since blur and blend are both filters, we need to set both at the same time
1846 SPStyle *style = item->style;
1847 if (style) {
1848 Geom::OptRect bbox = item->bounds(SPItem::GEOMETRIC_BBOX);
1849 double radius;
1850 if (bbox) {
1851 double perimeter = bbox->dimensions()[Geom::X] + bbox->dimensions()[Geom::Y]; // fixme: this is only half the perimeter, is that correct?
1852 radius = blur * perimeter / 400;
1853 } else {
1854 radius = 0;
1855 }
1856
1857 if (radius != 0) {
1858 // The modify function expects radius to be in display pixels.
1859 Geom::Affine i2d (item->i2dt_affine());
1860 double expansion = i2d.descrim();
1861 radius *= expansion;
1862 SPFilter *filter = modify_filter_gaussian_blur_from_item(_document, item, radius);
1863 sp_style_set_property_url(item, "filter", filter, false);
1864 } else if (item->style->filter.set && item->style->getFilter()) {
1865 for (auto& primitive: item->style->getFilter()->children) {
1866 if (!SP_IS_FILTER_PRIMITIVE(&primitive)) {
1867 break;
1868 }
1869 if (SP_IS_GAUSSIANBLUR(&primitive)) {
1870 primitive.deleteObject();
1871 break;
1872 }
1873 }
1874 if (!item->style->getFilter()->firstChild()) {
1875 remove_filter(item, false);
1876 }
1877 }
1878 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1879 }
1880 }
1881 }
1882
1883 /**
1884 * Constructor
1885 */
ObjectsPanel()1886 ObjectsPanel::ObjectsPanel() :
1887 DialogBase("/dialogs/objects", SP_VERB_DIALOG_OBJECTS),
1888 _rootWatcher(nullptr),
1889 _desktop(nullptr),
1890 _document(nullptr),
1891 _model(nullptr),
1892 _pending(nullptr),
1893 _pending_update(false),
1894 _toggleEvent(nullptr),
1895 _defer_target(),
1896 _visibleHeader(C_("Visibility", "V")),
1897 _lockHeader(C_("Lock", "L")),
1898 _typeHeader(C_("Type", "T")),
1899 _clipmaskHeader(C_("Clip and mask", "CM")),
1900 _highlightHeader(C_("Highlight", "HL")),
1901 _nameHeader(_("Label")),
1902 _filter_modifier( UI::Widget::SimpleFilterModifier::ISOLATION |
1903 UI::Widget::SimpleFilterModifier::BLEND |
1904 UI::Widget::SimpleFilterModifier::BLUR |
1905 UI::Widget::SimpleFilterModifier::OPACITY ),
1906 _colorSelectorDialog("dialogs.colorpickerwindow"),
1907 _page(Gtk::ORIENTATION_VERTICAL)
1908 {
1909 //Create the tree model and store
1910 ModelColumns *zoop = new ModelColumns();
1911 _model = zoop;
1912
1913 _store = Gtk::TreeStore::create( *zoop );
1914
1915 //Set up the tree
1916 _tree.set_model( _store );
1917 _tree.set_headers_visible(true);
1918 _tree.set_reorderable(false); // Reorderable means that we allow drag-and-drop, but we only allow that when at least one row is selected
1919 _tree.enable_model_drag_dest (Gdk::ACTION_MOVE);
1920
1921 //Create the column CellRenderers
1922 //Visible
1923 Inkscape::UI::Widget::ImageToggler *eyeRenderer = Gtk::manage( new Inkscape::UI::Widget::ImageToggler(
1924 INKSCAPE_ICON("object-visible"), INKSCAPE_ICON("object-hidden")) );
1925 int visibleColNum = _tree.append_column("vis", *eyeRenderer) - 1;
1926 eyeRenderer->property_activatable() = true;
1927 Gtk::TreeViewColumn* col = _tree.get_column(visibleColNum);
1928 if ( col ) {
1929 col->add_attribute( eyeRenderer->property_active(), _model->_colVisible );
1930 // In order to get tooltips on header, we must create our own label.
1931 _visibleHeader.set_tooltip_text(_("Toggle visibility of Layer, Group, or Object."));
1932 _visibleHeader.show();
1933 col->set_widget( _visibleHeader );
1934 }
1935
1936 //Locked
1937 Inkscape::UI::Widget::ImageToggler * renderer = Gtk::manage( new Inkscape::UI::Widget::ImageToggler(
1938 INKSCAPE_ICON("object-locked"), INKSCAPE_ICON("object-unlocked")) );
1939 int lockedColNum = _tree.append_column("lock", *renderer) - 1;
1940 renderer->property_activatable() = true;
1941 col = _tree.get_column(lockedColNum);
1942 if ( col ) {
1943 col->add_attribute( renderer->property_active(), _model->_colLocked );
1944 _lockHeader.set_tooltip_text(_("Toggle lock of Layer, Group, or Object."));
1945 _lockHeader.show();
1946 col->set_widget( _lockHeader );
1947 }
1948
1949 //Type
1950 Inkscape::UI::Widget::LayerTypeIcon * typeRenderer = Gtk::manage( new Inkscape::UI::Widget::LayerTypeIcon());
1951 int typeColNum = _tree.append_column("type", *typeRenderer) - 1;
1952 typeRenderer->property_activatable() = true;
1953 col = _tree.get_column(typeColNum);
1954 if ( col ) {
1955 col->add_attribute( typeRenderer->property_active(), _model->_colType );
1956 _typeHeader.set_tooltip_text(_("Type: Layer, Group, or Object. Clicking on Layer or Group icon, toggles between the two types."));
1957 _typeHeader.show();
1958 col->set_widget( _typeHeader );
1959 }
1960
1961 //Insert order (LiamW: unused)
1962 /*Inkscape::UI::Widget::InsertOrderIcon * insertRenderer = Gtk::manage( new Inkscape::UI::Widget::InsertOrderIcon());
1963 int insertColNum = _tree.append_column("type", *insertRenderer) - 1;
1964 col = _tree.get_column(insertColNum);
1965 if ( col ) {
1966 col->add_attribute( insertRenderer->property_active(), _model->_colInsertOrder );
1967 }*/
1968
1969 //Clip/mask
1970 Inkscape::UI::Widget::ClipMaskIcon * clipRenderer = Gtk::manage( new Inkscape::UI::Widget::ClipMaskIcon());
1971 int clipColNum = _tree.append_column("clipmask", *clipRenderer) - 1;
1972 col = _tree.get_column(clipColNum);
1973 if ( col ) {
1974 col->add_attribute( clipRenderer->property_active(), _model->_colClipMask );
1975 _clipmaskHeader.set_tooltip_text(_("Is object clipped and/or masked?"));
1976 _clipmaskHeader.show();
1977 col->set_widget( _clipmaskHeader );
1978 }
1979
1980 //Highlight
1981 Inkscape::UI::Widget::HighlightPicker * highlightRenderer = Gtk::manage( new Inkscape::UI::Widget::HighlightPicker());
1982 int highlightColNum = _tree.append_column("highlight", *highlightRenderer) - 1;
1983 col = _tree.get_column(highlightColNum);
1984 if ( col ) {
1985 col->add_attribute( highlightRenderer->property_active(), _model->_colHighlight );
1986 _highlightHeader.set_tooltip_text(_("Highlight color of outline in Node tool. Click to set. If alpha is zero, use inherited color."));
1987 _highlightHeader.show();
1988 col->set_widget( _highlightHeader );
1989 }
1990
1991 //Label
1992 _text_renderer = Gtk::manage(new Gtk::CellRendererText());
1993 int nameColNum = _tree.append_column("Name", *_text_renderer) - 1;
1994 _name_column = _tree.get_column(nameColNum);
1995 if( _name_column ) {
1996 _name_column->add_attribute(_text_renderer->property_text(), _model->_colLabel);
1997 _nameHeader.set_tooltip_text(_("Layer/Group/Object label (inkscape:label). Double-click to set. Default value is object 'id'."));
1998 _nameHeader.show();
1999 _name_column->set_widget( _nameHeader );
2000 }
2001
2002 //Set the expander and search columns
2003 _tree.set_expander_column( *_tree.get_column(nameColNum) );
2004 _tree.set_search_column(_model->_colLabel);
2005 // use ctrl+f to start search
2006 _tree.set_enable_search(false);
2007
2008 //Set up the tree selection
2009 _tree.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
2010 _selectedConnection = _tree.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &ObjectsPanel::_pushTreeSelectionToCurrent) );
2011 _tree.get_selection()->set_select_function( sigc::mem_fun(*this, &ObjectsPanel::_rowSelectFunction) );
2012
2013 //Set up tree signals
2014 _tree.signal_button_press_event().connect( sigc::mem_fun(*this, &ObjectsPanel::_handleButtonEvent), false );
2015 _tree.signal_button_release_event().connect( sigc::mem_fun(*this, &ObjectsPanel::_handleButtonEvent), false );
2016 _tree.signal_key_press_event().connect( sigc::mem_fun(*this, &ObjectsPanel::_handleKeyEvent), false );
2017 _tree.signal_drag_drop().connect( sigc::mem_fun(*this, &ObjectsPanel::_handleDragDrop), false);
2018 _tree.signal_row_collapsed().connect( sigc::bind<bool>(sigc::mem_fun(*this, &ObjectsPanel::_setExpanded), false));
2019 _tree.signal_row_expanded().connect( sigc::bind<bool>(sigc::mem_fun(*this, &ObjectsPanel::_setExpanded), true));
2020
2021 //Set up the label editing signals
2022 _text_renderer->signal_edited().connect( sigc::mem_fun(*this, &ObjectsPanel::_handleEdited) );
2023 _text_renderer->signal_editing_canceled().connect( sigc::mem_fun(*this, &ObjectsPanel::_handleEditingCancelled) );
2024
2025 //Set up the scroller window and pack the page
2026 _scroller.add( _tree );
2027 _scroller.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC );
2028 _scroller.set_shadow_type(Gtk::SHADOW_IN);
2029 Gtk::Requisition sreq;
2030 Gtk::Requisition sreq_natural;
2031 _scroller.get_preferred_size(sreq_natural, sreq);
2032 int minHeight = 70;
2033 if (sreq.height < minHeight) {
2034 // Set a min height to see the layers when used with Ubuntu liboverlay-scrollbar
2035 _scroller.set_size_request(sreq.width, minHeight);
2036 }
2037
2038 _page.pack_start( _scroller, Gtk::PACK_EXPAND_WIDGET );
2039
2040 //Set up the compositing items
2041 _blendConnection = _filter_modifier.signal_blend_changed().connect(sigc::mem_fun(*this, &ObjectsPanel::_blendValueChanged));
2042 _blurConnection = _filter_modifier.signal_blur_changed().connect(sigc::mem_fun(*this, &ObjectsPanel::_blurValueChanged));
2043 _opacityConnection = _filter_modifier.signal_opacity_changed().connect( sigc::mem_fun(*this, &ObjectsPanel::_opacityValueChanged));
2044 _isolationConnection = _filter_modifier.signal_isolation_changed().connect(
2045 sigc::mem_fun(*this, &ObjectsPanel::_isolationValueChanged));
2046 //Pack the compositing functions and the button row
2047 _page.pack_end(_filter_modifier, Gtk::PACK_SHRINK);
2048 _page.pack_end(_buttonsRow, Gtk::PACK_SHRINK);
2049
2050 //Pack into the panel contents
2051 pack_start(_page, Gtk::PACK_EXPAND_WIDGET);
2052
2053 SPDesktop* targetDesktop = getDesktop();
2054
2055 //Set up the button row
2056
2057
2058 //Add object/layer
2059 Gtk::Button* btn = Gtk::manage( new Gtk::Button() );
2060 _styleButton(*btn, INKSCAPE_ICON("list-add"), _("Add layer..."));
2061 btn->set_relief(Gtk::RELIEF_NONE);
2062 btn->signal_clicked().connect( sigc::bind( sigc::mem_fun(*this, &ObjectsPanel::_takeAction), (int)BUTTON_NEW) );
2063 _buttonsSecondary.pack_start(*btn, Gtk::PACK_SHRINK);
2064
2065 //Remove object
2066 btn = Gtk::manage( new Gtk::Button() );
2067 _styleButton(*btn, INKSCAPE_ICON("list-remove"), _("Remove object"));
2068 btn->set_relief(Gtk::RELIEF_NONE);
2069 btn->signal_clicked().connect( sigc::bind( sigc::mem_fun(*this, &ObjectsPanel::_takeAction), (int)BUTTON_DELETE) );
2070 _watching.push_back( btn );
2071 _buttonsSecondary.pack_start(*btn, Gtk::PACK_SHRINK);
2072
2073 //Move to bottom
2074 btn = Gtk::manage( new Gtk::Button() );
2075 _styleButton(*btn, INKSCAPE_ICON("go-bottom"), _("Move To Bottom"));
2076 btn->set_relief(Gtk::RELIEF_NONE);
2077 btn->signal_clicked().connect( sigc::bind( sigc::mem_fun(*this, &ObjectsPanel::_takeAction), (int)BUTTON_BOTTOM) );
2078 _watchingNonBottom.push_back( btn );
2079 _buttonsPrimary.pack_end(*btn, Gtk::PACK_SHRINK);
2080
2081 //Move down
2082 btn = Gtk::manage( new Gtk::Button() );
2083 _styleButton(*btn, INKSCAPE_ICON("go-down"), _("Move Down"));
2084 btn->set_relief(Gtk::RELIEF_NONE);
2085 btn->signal_clicked().connect( sigc::bind( sigc::mem_fun(*this, &ObjectsPanel::_takeAction), (int)BUTTON_DOWN) );
2086 _watchingNonBottom.push_back( btn );
2087 _buttonsPrimary.pack_end(*btn, Gtk::PACK_SHRINK);
2088
2089 //Move up
2090 btn = Gtk::manage( new Gtk::Button() );
2091 _styleButton(*btn, INKSCAPE_ICON("go-up"), _("Move Up"));
2092 btn->set_relief(Gtk::RELIEF_NONE);
2093 btn->signal_clicked().connect( sigc::bind( sigc::mem_fun(*this, &ObjectsPanel::_takeAction), (int)BUTTON_UP) );
2094 _watchingNonTop.push_back( btn );
2095 _buttonsPrimary.pack_end(*btn, Gtk::PACK_SHRINK);
2096
2097 //Move to top
2098 btn = Gtk::manage( new Gtk::Button() );
2099 _styleButton(*btn, INKSCAPE_ICON("go-top"), _("Move To Top"));
2100 btn->set_relief(Gtk::RELIEF_NONE);
2101 btn->signal_clicked().connect( sigc::bind( sigc::mem_fun(*this, &ObjectsPanel::_takeAction), (int)BUTTON_TOP) );
2102 _watchingNonTop.push_back( btn );
2103 _buttonsPrimary.pack_end(*btn, Gtk::PACK_SHRINK);
2104
2105 //Collapse all
2106 btn = Gtk::manage( new Gtk::Button() );
2107 _styleButton(*btn, INKSCAPE_ICON("format-indent-less"), _("Collapse All"));
2108 btn->set_relief(Gtk::RELIEF_NONE);
2109 btn->signal_clicked().connect( sigc::bind( sigc::mem_fun(*this, &ObjectsPanel::_takeAction), (int)BUTTON_COLLAPSE_ALL) );
2110 _watchingNonBottom.push_back( btn );
2111 _buttonsPrimary.pack_end(*btn, Gtk::PACK_SHRINK);
2112
2113 _buttonsRow.pack_start(_buttonsSecondary, Gtk::PACK_EXPAND_WIDGET);
2114 _buttonsRow.pack_end(_buttonsPrimary, Gtk::PACK_EXPAND_WIDGET);
2115
2116 _watching.push_back(&_filter_modifier);
2117
2118 //Set up the pop-up menu
2119 // -------------------------------------------------------
2120 {
2121 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2122 _show_contextmenu_icons = prefs->getBool("/theme/menuIcons_objects", true);
2123
2124 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_LAYER_RENAME, (int)BUTTON_RENAME ) );
2125 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_LAYER_NEW, (int)BUTTON_NEW ) );
2126
2127 _popupMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem()));
2128
2129 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_LAYER_SOLO, (int)BUTTON_SOLO ) );
2130 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_LAYER_SHOW_ALL, (int)BUTTON_SHOW_ALL ) );
2131 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_LAYER_HIDE_ALL, (int)BUTTON_HIDE_ALL ) );
2132
2133 _popupMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem()));
2134
2135 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_LAYER_LOCK_OTHERS, (int)BUTTON_LOCK_OTHERS ) );
2136 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_LAYER_LOCK_ALL, (int)BUTTON_LOCK_ALL ) );
2137 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_LAYER_UNLOCK_ALL, (int)BUTTON_UNLOCK_ALL ) );
2138
2139 _popupMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem()));
2140
2141 _watchingNonTop.push_back( &_addPopupItem(targetDesktop, SP_VERB_SELECTION_STACK_UP, (int)BUTTON_UP) );
2142 _watchingNonBottom.push_back( &_addPopupItem(targetDesktop, SP_VERB_SELECTION_STACK_DOWN, (int)BUTTON_DOWN) );
2143
2144 _popupMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem()));
2145
2146 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_SELECTION_GROUP, (int)BUTTON_GROUP ) );
2147 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_SELECTION_UNGROUP, (int)BUTTON_UNGROUP ) );
2148
2149 _popupMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem()));
2150
2151 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_OBJECT_SET_CLIPPATH, (int)BUTTON_SETCLIP ) );
2152
2153 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_OBJECT_CREATE_CLIP_GROUP, (int)BUTTON_CLIPGROUP ) );
2154
2155 //will never be implemented
2156 //_watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_OBJECT_SET_INVERSE_CLIPPATH, (int)BUTTON_SETINVCLIP ) );
2157 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_OBJECT_UNSET_CLIPPATH, (int)BUTTON_UNSETCLIP ) );
2158
2159 _popupMenu.append(*Gtk::manage(new Gtk::SeparatorMenuItem()));
2160
2161 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_OBJECT_SET_MASK, (int)BUTTON_SETMASK ) );
2162 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_OBJECT_UNSET_MASK, (int)BUTTON_UNSETMASK ) );
2163
2164 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_EDIT_DUPLICATE, (int)BUTTON_DUPLICATE ) );
2165 _watching.push_back( &_addPopupItem( targetDesktop, SP_VERB_EDIT_DELETE, (int)BUTTON_DELETE ) );
2166
2167 _popupMenu.show_all_children();
2168
2169 // Install CSS to shift icons into the space reserved for toggles (i.e. check and radio items).
2170 _popupMenu.signal_map().connect(sigc::bind<Gtk::MenuShell *>(sigc::ptr_fun(shift_icons), &_popupMenu));
2171 }
2172
2173 // -------------------------------------------------------
2174
2175 //Set initial sensitivity of buttons
2176 for (auto & it : _watching) {
2177 it->set_sensitive( false );
2178 }
2179 for (auto & it : _watchingNonTop) {
2180 it->set_sensitive( false );
2181 }
2182 for (auto & it : _watchingNonBottom) {
2183 it->set_sensitive( false );
2184 }
2185
2186 //Set up the color selection dialog
2187 _colorSelectorDialog.hide();
2188 _colorSelectorDialog.set_title (_("Select Highlight Color"));
2189 _colorSelectorDialog.set_border_width (4);
2190 _colorSelectorDialog.property_modal() = true;
2191 _selectedColor.reset(new Inkscape::UI::SelectedColor);
2192 Gtk::Widget *color_selector = Gtk::manage(new Inkscape::UI::Widget::ColorNotebook(*_selectedColor));
2193 _colorSelectorDialog.get_content_area()->pack_start (
2194 *color_selector, true, true, 0);
2195
2196 _selectedColor->signal_dragged.connect(sigc::mem_fun(*this, &ObjectsPanel::_highlightPickerColorMod));
2197 _selectedColor->signal_released.connect(sigc::mem_fun(*this, &ObjectsPanel::_highlightPickerColorMod));
2198 _selectedColor->signal_changed.connect(sigc::mem_fun(*this, &ObjectsPanel::_highlightPickerColorMod));
2199
2200 color_selector->show();
2201
2202 show_all_children();
2203 }
2204
2205 /**
2206 * Callback method that will be called when the desktop is destroyed
2207 */
_desktopDestroyed(SPDesktop *)2208 void ObjectsPanel::_desktopDestroyed(SPDesktop* /*desktop*/) {
2209 // We need to make sure that we're not trying to update the tree after the desktop has vanished, e.g.
2210 // when closing Inkscape. Preferably, we would have done so in the destructor of the ObjectsPanel. But
2211 // as this destructor is never ever called, we will do this by attaching to the desktop_destroyed signal
2212 // instead
2213 _processQueue_sig.disconnect();
2214 _executeUpdate_sig.disconnect();
2215 _desktop = nullptr;
2216 }
2217
2218 /**
2219 * Destructor
2220 */
~ObjectsPanel()2221 ObjectsPanel::~ObjectsPanel()
2222 {
2223 //Close the highlight selection dialog
2224 _colorSelectorDialog.hide();
2225
2226 // Disconnect signals
2227 _desktopDestroyedConnection.disconnect();
2228 _documentChangedConnection.disconnect();
2229 _documentChangedCurrentLayer.disconnect();
2230 _selectionChangedConnection.disconnect();
2231 setDocument(nullptr, nullptr);
2232 _desktopDestroyed(_desktop);
2233
2234 if ( _model )
2235 {
2236 delete _model;
2237 _model = nullptr;
2238 }
2239
2240 if (_pending) {
2241 delete _pending;
2242 _pending = nullptr;
2243 }
2244
2245 if ( _toggleEvent )
2246 {
2247 gdk_event_free( _toggleEvent );
2248 _toggleEvent = nullptr;
2249 }
2250 }
2251
2252 /**
2253 * Sets the current document
2254 */
setDocument(SPDesktop *,SPDocument * document)2255 void ObjectsPanel::setDocument(SPDesktop* /*desktop*/, SPDocument* document)
2256 {
2257 //Clear all object watchers
2258 _removeWatchers();
2259
2260 //Delete the root watcher
2261 if (_rootWatcher)
2262 {
2263 _rootWatcher->_repr->removeObserver(*_rootWatcher);
2264 delete _rootWatcher;
2265 _rootWatcher = nullptr;
2266 }
2267
2268 _document = document;
2269
2270 if (document && document->getRoot() && document->getRoot()->getRepr())
2271 {
2272 //Create a new root watcher for the document and then call _objectsChanged to fill the tree
2273 _rootWatcher = new ObjectsPanel::ObjectWatcher(this, document->getRoot());
2274 document->getRoot()->getRepr()->addObserver(*_rootWatcher);
2275 _objectsChanged(document->getRoot());
2276 }
2277 }
2278
2279 /**
2280 * Set the current panel desktop
2281 */
update()2282 void ObjectsPanel::update()
2283 {
2284 if (!_app) {
2285 std::cerr << "ObjectsPanel::update(): _app is null" << std::endl;
2286 return;
2287 }
2288
2289 SPDesktop *desktop = getDesktop();
2290
2291 if ( desktop != _desktop ) {
2292 _documentChangedConnection.disconnect();
2293 _documentChangedCurrentLayer.disconnect();
2294 _selectionChangedConnection.disconnect();
2295 if ( _desktop ) {
2296 _desktop = nullptr;
2297 }
2298
2299 _desktop = getDesktop();
2300 if ( _desktop ) {
2301 //Connect desktop signals
2302 _documentChangedConnection = _desktop->connectDocumentReplaced( sigc::mem_fun(*this, &ObjectsPanel::setDocument));
2303
2304 _documentChangedCurrentLayer = _desktop->connectCurrentLayerChanged( sigc::mem_fun(*this, &ObjectsPanel::_objectsChangedWrapper));
2305
2306 _selectionChangedConnection = _desktop->selection->connectChanged( sigc::mem_fun(*this, &ObjectsPanel::_objectsSelected));
2307
2308 _desktopDestroyedConnection = _desktop->connectDestroy( sigc::mem_fun(*this, &ObjectsPanel::_desktopDestroyed));
2309
2310 setDocument(_desktop, _desktop->doc());
2311 } else {
2312 setDocument(nullptr, nullptr);
2313 }
2314 }
2315 }
2316 } //namespace Dialogs
2317 } //namespace UI
2318 } //namespace Inkscape
2319
2320 //should be okay to put these here because they are never referenced anywhere else
2321 using namespace Inkscape::UI::Tools;
2322
setHighlightColor(guint32 const color)2323 void SPItem::setHighlightColor(guint32 const color)
2324 {
2325 g_free(_highlightColor);
2326 if (color & 0x000000ff)
2327 {
2328 _highlightColor = g_strdup_printf("%u", color);
2329 }
2330 else
2331 {
2332 _highlightColor = nullptr;
2333 }
2334
2335 NodeTool *tool = nullptr;
2336 if (SP_ACTIVE_DESKTOP ) {
2337 Inkscape::UI::Tools::ToolBase *ec = SP_ACTIVE_DESKTOP->event_context;
2338 if (INK_IS_NODE_TOOL(ec)) {
2339 tool = static_cast<NodeTool*>(ec);
2340 tools_switch(tool->getDesktop(), TOOLS_NODES);
2341 }
2342 }
2343 }
2344
unsetHighlightColor()2345 void SPItem::unsetHighlightColor()
2346 {
2347 g_free(_highlightColor);
2348 _highlightColor = nullptr;
2349 NodeTool *tool = nullptr;
2350 if (SP_ACTIVE_DESKTOP ) {
2351 Inkscape::UI::Tools::ToolBase *ec = SP_ACTIVE_DESKTOP->event_context;
2352 if (INK_IS_NODE_TOOL(ec)) {
2353 tool = static_cast<NodeTool*>(ec);
2354 tools_switch(tool->getDesktop(), TOOLS_NODES);
2355 }
2356 }
2357 }
2358
2359 /*
2360 Local Variables:
2361 mode:c++
2362 c-file-style:"stroustrup"
2363 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2364 indent-tabs-mode:nil
2365 fill-column:99
2366 End:
2367 */
2368 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
2369