1 /*
2 * Inkscape::Widgets::LayerSelector - layer selector widget
3 *
4 * Authors:
5 * MenTaLguY <mental@rydia.net>
6 *
7 * Copyright (C) 2004 MenTaLguY
8 *
9 * Released under GNU GPL, read the file 'COPYING' for more information
10 */
12 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
16 #include <cstring>
17 #include <string>
18 #include <glibmm/i18n.h>
20 #include "desktop-handles.h"
22 #include "widgets/layer-selector.h"
23 #include "widgets/shrink-wrap-button.h"
24 #include "widgets/icon.h"
26 #include "util/reverse-list.h"
27 #include "util/filter-list.h"
29 #include "sp-item.h"
30 #include "desktop.h"
31 #include "document.h"
32 #include "dialogs/layer-properties.h"
33 #include "layer-manager.h"
34 #include "xml/node-event-vector.h"
35 #include "verbs.h"
37 namespace Inkscape {
38 namespace Widgets {
40 namespace {
42 class AlternateIcons : public Gtk::HBox {
43 public:
44 AlternateIcons(Inkscape::IconSize size, gchar const *a, gchar const *b)
45 : _a(NULL), _b(NULL)
46 {
47 if (a) {
48 _a = Gtk::manage(sp_icon_get_icon(a, size));
49 _a->set_no_show_all(true);
50 add(*_a);
51 }
52 if (b) {
53 _b = Gtk::manage(sp_icon_get_icon(b, size));
54 _b->set_no_show_all(true);
55 add(*_b);
56 }
57 setState(false);
58 }
60 bool state() const { return _state; }
61 void setState(bool state) {
62 _state = state;
63 if (_state) {
64 if (_a) {
65 _a->hide();
66 }
67 if (_b) {
68 _b->show();
69 }
70 } else {
71 if (_a) {
72 _a->show();
73 }
74 if (_b) {
75 _b->hide();
76 }
77 }
78 }
80 private:
81 Gtk::Widget *_a;
82 Gtk::Widget *_b;
83 bool _state;
84 };
86 }
88 /** LayerSelector constructor. Creates lock and hide buttons,
89 * initalizes the layer dropdown selector with a label renderer,
90 * and hooks up signal for setting the desktop layer when the
91 * selector is changed.
92 */
93 LayerSelector::LayerSelector(SPDesktop *desktop)
94 : _desktop(NULL), _layer(NULL)
95 {
96 AlternateIcons *label;
98 label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION, "visible", "hidden"));
99 _visibility_toggle.add(*label);
100 _visibility_toggle.signal_toggled().connect(
101 sigc::compose(
102 sigc::mem_fun(*label, &AlternateIcons::setState),
103 sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
104 )
105 );
106 _visibility_toggled_connection = _visibility_toggle.signal_toggled().connect(
107 sigc::compose(
108 sigc::mem_fun(*this, &LayerSelector::_hideLayer),
109 sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
110 )
111 );
113 _visibility_toggle.set_relief(Gtk::RELIEF_NONE);
114 shrink_wrap_button(_visibility_toggle);
115 _tooltips.set_tip(_visibility_toggle, _("Toggle current layer visibility"));
116 pack_start(_visibility_toggle, Gtk::PACK_EXPAND_PADDING);
118 label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION, "lock_unlocked", "width_height_lock"));
119 _lock_toggle.add(*label);
120 _lock_toggle.signal_toggled().connect(
121 sigc::compose(
122 sigc::mem_fun(*label, &AlternateIcons::setState),
123 sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
124 )
125 );
126 _lock_toggled_connection = _lock_toggle.signal_toggled().connect(
127 sigc::compose(
128 sigc::mem_fun(*this, &LayerSelector::_lockLayer),
129 sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
130 )
131 );
133 _lock_toggle.set_relief(Gtk::RELIEF_NONE);
134 shrink_wrap_button(_lock_toggle);
135 _tooltips.set_tip(_lock_toggle, _("Lock or unlock current layer"));
136 pack_start(_lock_toggle, Gtk::PACK_EXPAND_PADDING);
138 _tooltips.set_tip(_selector, _("Current layer"));
139 pack_start(_selector, Gtk::PACK_EXPAND_WIDGET);
141 _layer_model = Gtk::ListStore::create(_model_columns);
142 _selector.set_model(_layer_model);
143 _selector.pack_start(_label_renderer);
144 _selector.set_cell_data_func(
145 _label_renderer,
146 sigc::mem_fun(*this, &LayerSelector::_prepareLabelRenderer)
147 );
149 _selection_changed_connection = _selector.signal_changed().connect(
150 sigc::mem_fun(*this, &LayerSelector::_setDesktopLayer)
151 );
152 setDesktop(desktop);
153 }
155 /** Destructor - disconnects signal handler
156 */
157 LayerSelector::~LayerSelector() {
158 setDesktop(NULL);
159 _selection_changed_connection.disconnect();
160 }
162 namespace {
164 /** Helper function - detaches desktop from selector
165 */
166 bool detach(LayerSelector *selector) {
167 selector->setDesktop(NULL);
168 return FALSE;
169 }
171 }
173 /** Sets the desktop for the widget. First disconnects signals
174 * for the current desktop, then stores the pointer to the
175 * given \a desktop, and attaches its signals to this one.
176 * Then it selects the current layer for the desktop.
177 */
178 void LayerSelector::setDesktop(SPDesktop *desktop) {
179 if ( desktop == _desktop ) {
180 return;
181 }
183 if (_desktop) {
184 // _desktop_shutdown_connection.disconnect();
185 _layer_changed_connection.disconnect();
186 // g_signal_handlers_disconnect_by_func(_desktop, (gpointer)&detach, this);
187 }
188 _desktop = desktop;
189 if (_desktop) {
190 // TODO we need a different signal for this, really..s
191 // _desktop_shutdown_connection = _desktop->connectShutdown(
192 // sigc::bind (sigc::ptr_fun (detach), this));
193 // g_signal_connect_after(_desktop, "shutdown", GCallback(detach), this);
195 _layer_changed_connection = _desktop->connectCurrentLayerChanged(
196 sigc::mem_fun(*this, &LayerSelector::_selectLayer)
197 );
198 _selectLayer(_desktop->currentLayer());
199 }
200 }
202 namespace {
204 class is_layer {
205 public:
206 is_layer(SPDesktop *desktop) : _desktop(desktop) {}
207 bool operator()(SPObject &object) const {
208 return _desktop->isLayer(&object);
209 }
210 private:
211 SPDesktop *_desktop;
212 };
214 class column_matches_object {
215 public:
216 column_matches_object(Gtk::TreeModelColumn<SPObject *> const &column,
217 SPObject &object)
218 : _column(column), _object(object) {}
219 bool operator()(Gtk::TreeModel::const_iterator const &iter) const {
220 SPObject *current=(*iter)[_column];
221 return current == &_object;
222 }
223 private:
224 Gtk::TreeModelColumn<SPObject *> const &_column;
225 SPObject &_object;
226 };
228 }
230 /** Selects the given layer in the dropdown selector.
231 */
232 void LayerSelector::_selectLayer(SPObject *layer) {
233 using Inkscape::Util::List;
234 using Inkscape::Util::cons;
235 using Inkscape::Util::reverse_list;
237 _selection_changed_connection.block();
239 while (!_layer_model->children().empty()) {
240 Gtk::ListStore::iterator first_row(_layer_model->children().begin());
241 _destroyEntry(first_row);
242 _layer_model->erase(first_row);
243 }
245 SPObject *root=_desktop->currentRoot();
247 if (_layer) {
248 sp_object_unref(_layer, NULL);
249 _layer = NULL;
250 }
252 if (layer) {
253 List<SPObject &> hierarchy=reverse_list<SPObject::ParentIterator>(layer, root);
254 if ( layer == root ) {
255 _buildEntries(0, cons(*root, hierarchy));
256 } else if (hierarchy) {
257 _buildSiblingEntries(0, *root, hierarchy);
258 }
260 Gtk::TreeIter row(
261 std::find_if(
262 _layer_model->children().begin(),
263 _layer_model->children().end(),
264 column_matches_object(_model_columns.object, *layer)
265 )
266 );
267 if ( row != _layer_model->children().end() ) {
268 _selector.set_active(row);
269 }
271 _layer = layer;
272 sp_object_ref(_layer, NULL);
273 }
275 if ( !layer || layer == root ) {
276 _visibility_toggle.set_sensitive(false);
277 _visibility_toggle.set_active(false);
278 _lock_toggle.set_sensitive(false);
279 _lock_toggle.set_active(false);
280 } else {
281 _visibility_toggle.set_sensitive(true);
282 _visibility_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false ));
283 _lock_toggle.set_sensitive(true);
284 _lock_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false ));
285 }
287 _selection_changed_connection.unblock();
288 }
290 /** Sets the current desktop layer to the actively selected layer.
291 */
292 void LayerSelector::_setDesktopLayer() {
293 Gtk::ListStore::iterator selected(_selector.get_active());
294 SPObject *layer=_selector.get_active()->get_value(_model_columns.object);
295 if ( _desktop && layer ) {
296 _layer_changed_connection.block();
298 _desktop->layer_manager->setCurrentLayer(layer);
300 _layer_changed_connection.unblock();
302 _selectLayer(_desktop->currentLayer());
303 }
304 if (_desktop && _desktop->canvas) {
305 gtk_widget_grab_focus (GTK_WIDGET(_desktop->canvas));
306 }
307 }
309 /** Creates rows in the _layer_model data structure for each item
310 * in \a hierarchy, to a given \a depth.
311 */
312 void LayerSelector::_buildEntries(unsigned depth,
313 Inkscape::Util::List<SPObject &> hierarchy)
314 {
315 using Inkscape::Util::List;
316 using Inkscape::Util::rest;
318 _buildEntry(depth, *hierarchy);
320 List<SPObject &> remainder=rest(hierarchy);
321 if (remainder) {
322 _buildEntries(depth+1, remainder);
323 } else {
324 _buildSiblingEntries(depth+1, *hierarchy, remainder);
325 }
326 }
328 /** Creates entries in the _layer_model data structure for
329 * all siblings of the first child in \a parent.
330 */
331 void LayerSelector::_buildSiblingEntries(
332 unsigned depth, SPObject &parent,
333 Inkscape::Util::List<SPObject &> hierarchy
334 ) {
335 using Inkscape::Util::List;
336 using Inkscape::Util::rest;
337 using Inkscape::Util::reverse_list_in_place;
338 using Inkscape::Util::filter_list;
340 Inkscape::Util::List<SPObject &> siblings(
341 reverse_list_in_place(
342 filter_list<SPObject::SiblingIterator>(
343 is_layer(_desktop), parent.firstChild(), NULL
344 )
345 )
346 );
348 SPObject *layer( hierarchy ? &*hierarchy : NULL );
350 while (siblings) {
351 _buildEntry(depth, *siblings);
352 if ( &*siblings == layer ) {
353 _buildSiblingEntries(depth+1, *layer, rest(hierarchy));
354 }
355 ++siblings;
356 }
357 }
359 namespace {
361 struct Callbacks {
362 sigc::slot<void> update_row;
363 sigc::slot<void> update_list;
364 };
366 void attribute_changed(Inkscape::XML::Node */*repr*/, gchar const *name,
367 gchar const */*old_value*/, gchar const */*new_value*/,
368 bool /*is_interactive*/, void *data)
369 {
370 if ( !std::strcmp(name, "inkscape:groupmode") ) {
371 reinterpret_cast<Callbacks *>(data)->update_list();
372 } else {
373 reinterpret_cast<Callbacks *>(data)->update_row();
374 }
375 }
377 void node_added(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, void *data) {
378 gchar const *mode=child->attribute("inkscape:groupmode");
379 if ( mode && !std::strcmp(mode, "layer") ) {
380 reinterpret_cast<Callbacks *>(data)->update_list();
381 }
382 }
384 void node_removed(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, void *data) {
385 gchar const *mode=child->attribute("inkscape:groupmode");
386 if ( mode && !std::strcmp(mode, "layer") ) {
387 reinterpret_cast<Callbacks *>(data)->update_list();
388 }
389 }
391 void node_reordered(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child,
392 Inkscape::XML::Node */*old_ref*/, Inkscape::XML::Node */*new_ref*/,
393 void *data)
394 {
395 gchar const *mode=child->attribute("inkscape:groupmode");
396 if ( mode && !std::strcmp(mode, "layer") ) {
397 reinterpret_cast<Callbacks *>(data)->update_list();
398 }
399 }
401 void update_row_for_object(SPObject *object,
402 Gtk::TreeModelColumn<SPObject *> const &column,
403 Glib::RefPtr<Gtk::ListStore> const &model)
404 {
405 Gtk::TreeIter row(
406 std::find_if(
407 model->children().begin(),
408 model->children().end(),
409 column_matches_object(column, *object)
410 )
411 );
412 if ( row != model->children().end() ) {
413 model->row_changed(model->get_path(row), row);
414 }
415 }
417 void rebuild_all_rows(sigc::slot<void, SPObject *> rebuild, SPDesktop *desktop)
418 {
419 rebuild(desktop->currentLayer());
420 }
422 }
424 void LayerSelector::_protectUpdate(sigc::slot<void> slot) {
425 bool visibility_blocked=_visibility_toggled_connection.blocked();
426 bool lock_blocked=_lock_toggled_connection.blocked();
427 _visibility_toggled_connection.block(true);
428 _lock_toggled_connection.block(true);
429 slot();
431 SPObject *layer = _desktop ? _desktop->currentLayer() : 0;
432 if ( layer ) {
433 bool wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false );
434 if ( _lock_toggle.get_active() != wantedValue ) {
435 _lock_toggle.set_active( wantedValue );
436 }
437 wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false );
438 if ( _visibility_toggle.get_active() != wantedValue ) {
439 _visibility_toggle.set_active( wantedValue );
440 }
441 }
442 _visibility_toggled_connection.block(visibility_blocked);
443 _lock_toggled_connection.block(lock_blocked);
444 }
446 /** Builds and appends a row in the layer model object.
447 */
448 void LayerSelector::_buildEntry(unsigned depth, SPObject &object) {
449 Inkscape::XML::NodeEventVector *vector;
451 Callbacks *callbacks=new Callbacks();
453 callbacks->update_row = sigc::bind(
454 sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
455 sigc::bind(
456 sigc::ptr_fun(&update_row_for_object),
457 &object, _model_columns.object, _layer_model
458 )
459 );
461 SPObject *layer=_desktop->currentLayer();
462 if ( &object == layer || &object == SP_OBJECT_PARENT(layer) ) {
463 callbacks->update_list = sigc::bind(
464 sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
465 sigc::bind(
466 sigc::ptr_fun(&rebuild_all_rows),
467 sigc::mem_fun(*this, &LayerSelector::_selectLayer),
468 _desktop
469 )
470 );
472 Inkscape::XML::NodeEventVector events = {
473 &node_added,
474 &node_removed,
475 &attribute_changed,
476 NULL,
477 &node_reordered
478 };
480 vector = new Inkscape::XML::NodeEventVector(events);
481 } else {
482 Inkscape::XML::NodeEventVector events = {
483 NULL,
484 NULL,
485 &attribute_changed,
486 NULL,
487 NULL
488 };
490 vector = new Inkscape::XML::NodeEventVector(events);
491 }
493 Gtk::ListStore::iterator row(_layer_model->append());
495 row->set_value(_model_columns.depth, depth);
497 sp_object_ref(&object, NULL);
498 row->set_value(_model_columns.object, &object);
500 Inkscape::GC::anchor(SP_OBJECT_REPR(&object));
501 row->set_value(_model_columns.repr, SP_OBJECT_REPR(&object));
503 row->set_value(_model_columns.callbacks, reinterpret_cast<void *>(callbacks));
505 sp_repr_add_listener(SP_OBJECT_REPR(&object), vector, callbacks);
506 }
508 /** Removes a row from the _model_columns object, disconnecting listeners
509 * on the slot.
510 */
511 void LayerSelector::_destroyEntry(Gtk::ListStore::iterator const &row) {
512 Callbacks *callbacks=reinterpret_cast<Callbacks *>(row->get_value(_model_columns.callbacks));
513 SPObject *object=row->get_value(_model_columns.object);
514 if (object) {
515 sp_object_unref(object, NULL);
516 }
517 Inkscape::XML::Node *repr=row->get_value(_model_columns.repr);
518 if (repr) {
519 sp_repr_remove_listener_by_data(repr, callbacks);
520 Inkscape::GC::release(repr);
521 }
522 delete callbacks;
523 }
525 /** Formats the label for a given layer row
526 */
527 void LayerSelector::_prepareLabelRenderer(
528 Gtk::TreeModel::const_iterator const &row
529 ) {
530 unsigned depth=(*row)[_model_columns.depth];
531 SPObject *object=(*row)[_model_columns.object];
532 bool label_defaulted(false);
534 // TODO: when the currently selected row is removed,
535 // (or before one has been selected) something appears to
536 // "invent" an iterator with null data and try to render it;
537 // where does it come from, and how can we avoid it?
538 if ( object && SP_OBJECT_REPR(object) ) {
539 SPObject *layer=( _desktop ? _desktop->currentLayer() : NULL );
540 SPObject *root=( _desktop ? _desktop->currentRoot() : NULL );
542 bool isancestor = !( layer && SP_OBJECT_PARENT(object) == SP_OBJECT_PARENT(layer) || layer == root && SP_OBJECT_PARENT(object) == root);
544 bool iscurrent = ( object == layer && object != root );
546 gchar *format = g_strdup_printf (
547 "<span size=\"smaller\" %s><tt>%*s%s</tt>%s%s%s%%s%s%s%s</span>",
548 ( _desktop && _desktop->itemIsHidden (SP_ITEM(object)) ? "foreground=\"gray50\"" : "" ),
549 depth, "", ( iscurrent ? "•" : " " ),
550 ( iscurrent ? "<b>" : "" ),
551 ( SP_ITEM(object)->isLocked() ? "[" : "" ),
552 ( isancestor ? "<small>" : "" ),
553 ( isancestor ? "</small>" : "" ),
554 ( SP_ITEM(object)->isLocked() ? "]" : "" ),
555 ( iscurrent ? "</b>" : "" )
556 );
558 gchar const *label;
559 if ( object != root ) {
560 label = object->label();
561 if (!label) {
562 label = object->defaultLabel();
563 label_defaulted = true;
564 }
565 } else {
566 label = _("(root)");
567 }
569 gchar *text = g_markup_printf_escaped(format, label);
570 _label_renderer.property_markup() = text;
571 g_free(text);
572 g_free(format);
573 } else {
574 _label_renderer.property_markup() = "<small> </small>";
575 }
577 _label_renderer.property_ypad() = 1;
578 _label_renderer.property_style() = ( label_defaulted ?
579 Pango::STYLE_ITALIC :
580 Pango::STYLE_NORMAL );
581 }
583 void LayerSelector::_lockLayer(bool lock) {
584 if ( _layer && SP_IS_ITEM(_layer) ) {
585 SP_ITEM(_layer)->setLocked(lock);
586 sp_document_done(sp_desktop_document(_desktop), SP_VERB_NONE,
587 lock? _("Lock layer") : _("Unlock layer"));
588 }
589 }
591 void LayerSelector::_hideLayer(bool hide) {
592 if ( _layer && SP_IS_ITEM(_layer) ) {
593 SP_ITEM(_layer)->setHidden(hide);
594 sp_document_done(sp_desktop_document(_desktop), SP_VERB_NONE,
595 hide? _("Hide layer") : _("Unhide layer"));
596 }
597 }
599 }
600 }
602 /*
603 Local Variables:
604 mode:c++
605 c-file-style:"stroustrup"
606 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
607 indent-tabs-mode:nil
608 fill-column:99
609 End:
610 */
611 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :