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