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