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