Code

swap tooltips
[inkscape.git] / src / widgets / layer-selector.cpp
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"
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);
152 /**  Destructor - disconnects signal handler 
153  */
154 LayerSelector::~LayerSelector() {
155     setDesktop(NULL);
156     _selection_changed_connection.disconnect();
159 namespace {
161 /** Helper function - detaches desktop from selector 
162  */
163 bool detach(LayerSelector *selector) {
164     selector->setDesktop(NULL);
165     return FALSE;
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     }
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 };
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();
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     }
303 /** Creates rows in the _layer_model data structure for each item
304  *  in \a hierarchy, to a given \a depth.
305  */
306 void LayerSelector::_buildEntries(unsigned depth,
307                                   Inkscape::Util::List<SPObject &> hierarchy)
309     using Inkscape::Util::List;
310     using Inkscape::Util::rest;
312     _buildEntry(depth, *hierarchy);
314     List<SPObject &> remainder=rest(hierarchy);
315     if (remainder) {
316         _buildEntries(depth+1, remainder);
317     } else {
318         _buildSiblingEntries(depth+1, *hierarchy, remainder);
319     }
322 /** Creates entries in the _layer_model data structure for
323  *  all siblings of the first child in \a parent.
324  */
325 void LayerSelector::_buildSiblingEntries(
326     unsigned depth, SPObject &parent,
327     Inkscape::Util::List<SPObject &> hierarchy
328 ) {
329     using Inkscape::Util::List;
330     using Inkscape::Util::rest;
331     using Inkscape::Util::reverse_list_in_place;
332     using Inkscape::Util::filter_list;
334     Inkscape::Util::List<SPObject &> siblings(
335         reverse_list_in_place(
336             filter_list<SPObject::SiblingIterator>(
337                 is_layer(_desktop), parent.firstChild(), NULL
338             )
339         )
340     );
342     SPObject *layer( hierarchy ? &*hierarchy : NULL );
344     while (siblings) {
345         _buildEntry(depth, *siblings);
346         if ( &*siblings == layer ) {
347             _buildSiblingEntries(depth+1, *layer, rest(hierarchy));
348         }
349         ++siblings;
350     }
353 namespace {
355 struct Callbacks {
356     sigc::slot<void> update_row;
357     sigc::slot<void> update_list;
358 };
360 void attribute_changed(Inkscape::XML::Node *repr, gchar const *name,
361                        gchar const *old_value, gchar const *new_value,
362                        bool is_interactive, void *data) 
364     if ( !std::strcmp(name, "inkscape:groupmode") ) {
365         reinterpret_cast<Callbacks *>(data)->update_list();
366     } else {
367         reinterpret_cast<Callbacks *>(data)->update_row();
368     }
371 void node_added(Inkscape::XML::Node *parent, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data) {
372     gchar const *mode=child->attribute("inkscape:groupmode");
373     if ( mode && !std::strcmp(mode, "layer") ) {
374         reinterpret_cast<Callbacks *>(data)->update_list();
375     }
378 void node_removed(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     }
385 void node_reordered(Inkscape::XML::Node *parent, Inkscape::XML::Node *child,
386                     Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref,
387                     void *data)
389     gchar const *mode=child->attribute("inkscape:groupmode");
390     if ( mode && !std::strcmp(mode, "layer") ) {
391         reinterpret_cast<Callbacks *>(data)->update_list();
392     }
395 void update_row_for_object(SPObject *object,
396                            Gtk::TreeModelColumn<SPObject *> const &column,
397                            Glib::RefPtr<Gtk::ListStore> const &model)
399     Gtk::TreeIter row(
400         std::find_if(
401             model->children().begin(),
402             model->children().end(),
403             column_matches_object(column, *object)
404         )
405     );
406     if ( row != model->children().end() ) {
407         model->row_changed(model->get_path(row), row);
408     }
411 void rebuild_all_rows(sigc::slot<void, SPObject *> rebuild, SPDesktop *desktop)
413     rebuild(desktop->currentLayer());
418 void LayerSelector::_protectUpdate(sigc::slot<void> slot) {
419     bool visibility_blocked=_visibility_toggled_connection.blocked();
420     bool lock_blocked=_lock_toggled_connection.blocked();
421     _visibility_toggled_connection.block(true);
422     _lock_toggled_connection.block(true);
423     slot();
425     SPObject *layer = _desktop ? _desktop->currentLayer() : 0;
426     if ( layer ) {
427         bool wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false );
428         if ( _lock_toggle.get_active() != wantedValue ) {
429             _lock_toggle.set_active( wantedValue );
430         }
431         wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false );
432         if ( _visibility_toggle.get_active() != wantedValue ) {
433             _visibility_toggle.set_active( wantedValue );
434         }
435     }
436     _visibility_toggled_connection.block(visibility_blocked);
437     _lock_toggled_connection.block(lock_blocked);
440 /** Builds and appends a row in the layer model object.
441  */
442 void LayerSelector::_buildEntry(unsigned depth, SPObject &object) {
443     Inkscape::XML::NodeEventVector *vector;
445     Callbacks *callbacks=new Callbacks();
447     callbacks->update_row = sigc::bind(
448         sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
449         sigc::bind(
450             sigc::ptr_fun(&update_row_for_object),
451             &object, _model_columns.object, _layer_model
452         )
453     );
455     SPObject *layer=_desktop->currentLayer();
456     if ( &object == layer || &object == SP_OBJECT_PARENT(layer) ) {
457         callbacks->update_list = sigc::bind(
458             sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
459             sigc::bind(
460                 sigc::ptr_fun(&rebuild_all_rows),
461                 sigc::mem_fun(*this, &LayerSelector::_selectLayer),
462                 _desktop
463             )
464         );
466         Inkscape::XML::NodeEventVector events = {
467             &node_added,
468             &node_removed,
469             &attribute_changed,
470             NULL,
471             &node_reordered
472         };
474         vector = new Inkscape::XML::NodeEventVector(events);
475     } else {
476         Inkscape::XML::NodeEventVector events = {
477             NULL,
478             NULL,
479             &attribute_changed,
480             NULL,
481             NULL
482         };
484         vector = new Inkscape::XML::NodeEventVector(events);
485     }
487     Gtk::ListStore::iterator row(_layer_model->append());
489     row->set_value(_model_columns.depth, depth);
491     sp_object_ref(&object, NULL);
492     row->set_value(_model_columns.object, &object);
494     Inkscape::GC::anchor(SP_OBJECT_REPR(&object));
495     row->set_value(_model_columns.repr, SP_OBJECT_REPR(&object));
497     row->set_value(_model_columns.callbacks, reinterpret_cast<void *>(callbacks));
499     sp_repr_add_listener(SP_OBJECT_REPR(&object), vector, callbacks);
502 /** Removes a row from the _model_columns object, disconnecting listeners
503  *  on the slot.
504  */
505 void LayerSelector::_destroyEntry(Gtk::ListStore::iterator const &row) {
506     Callbacks *callbacks=reinterpret_cast<Callbacks *>(row->get_value(_model_columns.callbacks));
507     SPObject *object=row->get_value(_model_columns.object);
508     if (object) {
509         sp_object_unref(object, NULL);
510     }
511     Inkscape::XML::Node *repr=row->get_value(_model_columns.repr);
512     if (repr) {
513         sp_repr_remove_listener_by_data(repr, callbacks);
514         Inkscape::GC::release(repr);
515     }
516     delete callbacks;
519 /** Formats the label for a given layer row 
520  */
521 void LayerSelector::_prepareLabelRenderer(
522     Gtk::TreeModel::const_iterator const &row
523 ) {
524     unsigned depth=(*row)[_model_columns.depth];
525     SPObject *object=(*row)[_model_columns.object];
526     bool label_defaulted(false);
528     // TODO: when the currently selected row is removed,
529     //       (or before one has been selected) something appears to
530     //       "invent" an iterator with null data and try to render it;
531     //       where does it come from, and how can we avoid it?
532     if ( object && SP_OBJECT_REPR(object) ) {
533         SPObject *layer=( _desktop ? _desktop->currentLayer() : NULL );
534         SPObject *root=( _desktop ? _desktop->currentRoot() : NULL );
536         bool isancestor = !( layer && SP_OBJECT_PARENT(object) == SP_OBJECT_PARENT(layer) || layer == root && SP_OBJECT_PARENT(object) == root);
538         bool iscurrent = ( object == layer && object != root );
540         gchar *format = g_strdup_printf (
541             "<span size=\"smaller\" %s><tt>%*s%s</tt>%s%s%s%%s%s%s%s</span>",
542             ( _desktop && _desktop->itemIsHidden (SP_ITEM(object)) ? "foreground=\"gray50\"" : "" ),
543             depth, "", ( iscurrent ? "&#8226;" : " " ),
544             ( iscurrent ? "<b>" : "" ),
545             ( SP_ITEM(object)->isLocked() ? "[" : "" ),
546             ( isancestor ? "<small>" : "" ),
547             ( isancestor ? "</small>" : "" ),
548             ( SP_ITEM(object)->isLocked() ? "]" : "" ),
549             ( iscurrent ? "</b>" : "" )
550             );
552         gchar const *label;
553         if ( object != root ) {
554             label = object->label();
555             if (!label) {
556                 label = object->defaultLabel();
557                 label_defaulted = true;
558             }
559         } else {
560             label = _("(root)");
561         }
563         gchar *text = g_markup_printf_escaped(format, label); 
564         _label_renderer.property_markup() = text;
565         g_free(text);
566         g_free(format);
567     } else {
568         _label_renderer.property_markup() = "<small> </small>";
569     }
571     _label_renderer.property_ypad() = 1;
572     _label_renderer.property_style() = ( label_defaulted ?
573                                          Pango::STYLE_ITALIC :
574                                          Pango::STYLE_NORMAL );
577 void LayerSelector::_lockLayer(bool lock) {
578     if ( _layer && SP_IS_ITEM(_layer) ) {
579         SP_ITEM(_layer)->setLocked(lock);
580         sp_document_maybe_done(sp_desktop_document(_desktop), "LayerSelector:lock");
581     }
584 void LayerSelector::_hideLayer(bool hide) {
585     if ( _layer && SP_IS_ITEM(_layer) ) {
586         SP_ITEM(_layer)->setHidden(hide);
587         sp_document_maybe_done(sp_desktop_document(_desktop), "LayerSelector:hide");
588     }
594 /*
595   Local Variables:
596   mode:c++
597   c-file-style:"stroustrup"
598   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
599   indent-tabs-mode:nil
600   fill-column:99
601   End:
602 */
603 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :