Code

8fe0c928e73da187c9a086efe4eab5be704bfa98
[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 "xml/node-event-vector.h"
33 namespace Inkscape {
34 namespace Widgets {
36 namespace {
38 class AlternateIcons : public Gtk::HBox {
39 public:
40     AlternateIcons(Inkscape::IconSize size, gchar const *a, gchar const *b)
41     : _a(NULL), _b(NULL)
42     {
43         if (a) {
44             _a = Gtk::manage(sp_icon_get_icon(a, size));
45             _a->set_no_show_all(true);
46             add(*_a);
47         }
48         if (b) {
49             _b = Gtk::manage(sp_icon_get_icon(b, size));
50             _b->set_no_show_all(true);
51             add(*_b);
52         }
53         setState(false);
54     }
56     bool state() const { return _state; }
57     void setState(bool state) {
58         _state = state;
59         if (_state) {
60             if (_a) {
61                 _a->hide();
62             }
63             if (_b) {
64                 _b->show();
65             }
66         } else {
67             if (_a) {
68                 _a->show();
69             }
70             if (_b) {
71                 _b->hide();
72             }
73         }
74     }
76 private:
77     Gtk::Widget *_a; 
78     Gtk::Widget *_b;
79     bool _state;
80 };
82 }
84 /** LayerSelector constructor.  Creates lock and hide buttons, 
85  *  initalizes the layer dropdown selector with a label renderer,
86  *  and hooks up signal for setting the desktop layer when the
87  *  selector is changed.
88  */ 
89 LayerSelector::LayerSelector(SPDesktop *desktop)
90 : _desktop(NULL), _layer(NULL)
91 {
92     AlternateIcons *label;
94     label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION, "visible", "hidden"));
95     _visibility_toggle.add(*label);
96     _visibility_toggle.signal_toggled().connect(
97         sigc::compose(
98             sigc::mem_fun(*label, &AlternateIcons::setState),
99             sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
100         )
101     );
102     _visibility_toggled_connection = _visibility_toggle.signal_toggled().connect(
103         sigc::compose(
104             sigc::mem_fun(*this, &LayerSelector::_hideLayer),
105             sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
106         )
107     );
109     _visibility_toggle.set_relief(Gtk::RELIEF_NONE);
110     shrink_wrap_button(_visibility_toggle);
111     _tooltips.set_tip(_visibility_toggle, _("Toggle current layer visibility"));
112     pack_start(_visibility_toggle, Gtk::PACK_EXPAND_PADDING);
114     label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION, "lock_unlocked", "width_height_lock"));
115     _lock_toggle.add(*label);
116     _lock_toggle.signal_toggled().connect(
117         sigc::compose(
118             sigc::mem_fun(*label, &AlternateIcons::setState),
119             sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
120         )
121     );
122     _lock_toggled_connection = _lock_toggle.signal_toggled().connect(
123         sigc::compose(
124             sigc::mem_fun(*this, &LayerSelector::_lockLayer),
125             sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
126         )
127     );
129     _lock_toggle.set_relief(Gtk::RELIEF_NONE);
130     shrink_wrap_button(_lock_toggle);
131     _tooltips.set_tip(_lock_toggle, _("Lock or unlock current layer"));
132     pack_start(_lock_toggle, Gtk::PACK_EXPAND_PADDING);
134     _tooltips.set_tip(_selector, _("Current layer"));
135     pack_start(_selector, Gtk::PACK_EXPAND_WIDGET);
137     _layer_model = Gtk::ListStore::create(_model_columns);
138     _selector.set_model(_layer_model);
139     _selector.pack_start(_label_renderer);
140     _selector.set_cell_data_func(
141         _label_renderer,
142         sigc::mem_fun(*this, &LayerSelector::_prepareLabelRenderer)
143     );
145     _selection_changed_connection = _selector.signal_changed().connect(
146         sigc::mem_fun(*this, &LayerSelector::_setDesktopLayer)
147     );
148     setDesktop(desktop);
151 /**  Destructor - disconnects signal handler 
152  */
153 LayerSelector::~LayerSelector() {
154     setDesktop(NULL);
155     _selection_changed_connection.disconnect();
158 namespace {
160 /** Helper function - detaches desktop from selector 
161  */
162 bool detach(LayerSelector *selector) {
163     selector->setDesktop(NULL);
164     return FALSE;
169 /** Sets the desktop for the widget.  First disconnects signals
170  *  for the current desktop, then stores the pointer to the
171  *  given \a desktop, and attaches its signals to this one.
172  *  Then it selects the current layer for the desktop.
173  */
174 void LayerSelector::setDesktop(SPDesktop *desktop) {
175     if ( desktop == _desktop ) {
176         return;
177     }
179     if (_desktop) {
180 //        _desktop_shutdown_connection.disconnect();
181         _layer_changed_connection.disconnect();
182 //        g_signal_handlers_disconnect_by_func(_desktop, (gpointer)&detach, this);
183     }
184     _desktop = desktop;
185     if (_desktop) {
186         // TODO we need a different signal for this, really..s
187 //        _desktop_shutdown_connection = _desktop->connectShutdown(
188 //          sigc::bind (sigc::ptr_fun (detach), this));
189 //        g_signal_connect_after(_desktop, "shutdown", GCallback(detach), this);
191         _layer_changed_connection = _desktop->connectCurrentLayerChanged(
192             sigc::mem_fun(*this, &LayerSelector::_selectLayer)
193         );
194         _selectLayer(_desktop->currentLayer());
195     }
198 namespace {
200 class is_layer {
201 public:
202     is_layer(SPDesktop *desktop) : _desktop(desktop) {}
203     bool operator()(SPObject &object) const {
204         return _desktop->isLayer(&object);
205     }
206 private:
207     SPDesktop *_desktop;
208 };
210 class column_matches_object {
211 public:
212     column_matches_object(Gtk::TreeModelColumn<SPObject *> const &column,
213                           SPObject &object)
214     : _column(column), _object(object) {}
215     bool operator()(Gtk::TreeModel::const_iterator const &iter) const {
216         SPObject *current=(*iter)[_column];
217         return current == &_object;
218     }
219 private:
220     Gtk::TreeModelColumn<SPObject *> const &_column;
221     SPObject &_object;
222 };
226 /** Selects the given layer in the dropdown selector.  
227  */
228 void LayerSelector::_selectLayer(SPObject *layer) {
229     using Inkscape::Util::List;
230     using Inkscape::Util::cons;
231     using Inkscape::Util::reverse_list;
233     _selection_changed_connection.block();
235     while (!_layer_model->children().empty()) {
236         Gtk::ListStore::iterator first_row(_layer_model->children().begin());
237         _destroyEntry(first_row);
238         _layer_model->erase(first_row);
239     }
241     SPObject *root=_desktop->currentRoot();
243     if (_layer) {
244         sp_object_unref(_layer, NULL);
245         _layer = NULL;
246     }
248     if (layer) {
249         List<SPObject &> hierarchy=reverse_list<SPObject::ParentIterator>(layer, root);
250         if ( layer == root ) {
251             _buildEntries(0, cons(*root, hierarchy));
252         } else if (hierarchy) {
253             _buildSiblingEntries(0, *root, hierarchy);
254         }
256         Gtk::TreeIter row(
257             std::find_if(
258                 _layer_model->children().begin(),
259                 _layer_model->children().end(),
260                 column_matches_object(_model_columns.object, *layer)
261             )
262         );
263         if ( row != _layer_model->children().end() ) {
264             _selector.set_active(row);
265         }
267         _layer = layer;
268         sp_object_ref(_layer, NULL);
269     }
271     if ( !layer || layer == root ) {
272         _visibility_toggle.set_sensitive(false);
273         _visibility_toggle.set_active(false);
274         _lock_toggle.set_sensitive(false);
275         _lock_toggle.set_active(false);
276     } else {
277         _visibility_toggle.set_sensitive(true);
278         _visibility_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false ));
279         _lock_toggle.set_sensitive(true);
280         _lock_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false ));
281     }
283     _selection_changed_connection.unblock();
286 /** Sets the current desktop layer to the actively selected layer.
287  */
288 void LayerSelector::_setDesktopLayer() {
289     Gtk::ListStore::iterator selected(_selector.get_active());
290     SPObject *layer=_selector.get_active()->get_value(_model_columns.object);
291     if ( _desktop && layer ) {
292         _layer_changed_connection.block();
293         _desktop->setCurrentLayer(layer);
294         _layer_changed_connection.unblock();
295         sp_desktop_selection(_desktop)->clear();
296         _selectLayer(_desktop->currentLayer());
297     }
300 /** Creates rows in the _layer_model data structure for each item
301  *  in \a hierarchy, to a given \a depth.
302  */
303 void LayerSelector::_buildEntries(unsigned depth,
304                                   Inkscape::Util::List<SPObject &> hierarchy)
306     using Inkscape::Util::List;
307     using Inkscape::Util::rest;
309     _buildEntry(depth, *hierarchy);
311     List<SPObject &> remainder=rest(hierarchy);
312     if (remainder) {
313         _buildEntries(depth+1, remainder);
314     } else {
315         _buildSiblingEntries(depth+1, *hierarchy, remainder);
316     }
319 /** Creates entries in the _layer_model data structure for
320  *  all siblings of the first child in \a parent.
321  */
322 void LayerSelector::_buildSiblingEntries(
323     unsigned depth, SPObject &parent,
324     Inkscape::Util::List<SPObject &> hierarchy
325 ) {
326     using Inkscape::Util::List;
327     using Inkscape::Util::rest;
328     using Inkscape::Util::reverse_list_in_place;
329     using Inkscape::Util::filter_list;
331     Inkscape::Util::List<SPObject &> siblings(
332         reverse_list_in_place(
333             filter_list<SPObject::SiblingIterator>(
334                 is_layer(_desktop), parent.firstChild(), NULL
335             )
336         )
337     );
339     SPObject *layer( hierarchy ? &*hierarchy : NULL );
341     while (siblings) {
342         _buildEntry(depth, *siblings);
343         if ( &*siblings == layer ) {
344             _buildSiblingEntries(depth+1, *layer, rest(hierarchy));
345         }
346         ++siblings;
347     }
350 namespace {
352 struct Callbacks {
353     sigc::slot<void> update_row;
354     sigc::slot<void> update_list;
355 };
357 void attribute_changed(Inkscape::XML::Node *repr, gchar const *name,
358                        gchar const *old_value, gchar const *new_value,
359                        bool is_interactive, void *data) 
361     if ( !std::strcmp(name, "inkscape:groupmode") ) {
362         reinterpret_cast<Callbacks *>(data)->update_list();
363     } else {
364         reinterpret_cast<Callbacks *>(data)->update_row();
365     }
368 void node_added(Inkscape::XML::Node *parent, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data) {
369     gchar const *mode=child->attribute("inkscape:groupmode");
370     if ( mode && !std::strcmp(mode, "layer") ) {
371         reinterpret_cast<Callbacks *>(data)->update_list();
372     }
375 void node_removed(Inkscape::XML::Node *parent, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data) {
376     gchar const *mode=child->attribute("inkscape:groupmode");
377     if ( mode && !std::strcmp(mode, "layer") ) {
378         reinterpret_cast<Callbacks *>(data)->update_list();
379     }
382 void node_reordered(Inkscape::XML::Node *parent, Inkscape::XML::Node *child,
383                     Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref,
384                     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     }
392 void update_row_for_object(SPObject *object,
393                            Gtk::TreeModelColumn<SPObject *> const &column,
394                            Glib::RefPtr<Gtk::ListStore> const &model)
396     Gtk::TreeIter row(
397         std::find_if(
398             model->children().begin(),
399             model->children().end(),
400             column_matches_object(column, *object)
401         )
402     );
403     if ( row != model->children().end() ) {
404         model->row_changed(model->get_path(row), row);
405     }
408 void rebuild_all_rows(sigc::slot<void, SPObject *> rebuild, SPDesktop *desktop)
410     rebuild(desktop->currentLayer());
415 void LayerSelector::_protectUpdate(sigc::slot<void> slot) {
416     bool visibility_blocked=_visibility_toggled_connection.blocked();
417     bool lock_blocked=_lock_toggled_connection.blocked();
418     _visibility_toggled_connection.block(true);
419     _lock_toggled_connection.block(true);
420     slot();
421     _visibility_toggled_connection.block(visibility_blocked);
422     _lock_toggled_connection.block(lock_blocked);
425 /** Builds and appends a row in the layer model object.
426  */
427 void LayerSelector::_buildEntry(unsigned depth, SPObject &object) {
428     Inkscape::XML::NodeEventVector *vector;
430     Callbacks *callbacks=new Callbacks();
432     callbacks->update_row = sigc::bind(
433         sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
434         sigc::bind(
435             sigc::ptr_fun(&update_row_for_object),
436             &object, _model_columns.object, _layer_model
437         )
438     );
440     SPObject *layer=_desktop->currentLayer();
441     if ( &object == layer || &object == SP_OBJECT_PARENT(layer) ) {
442         callbacks->update_list = sigc::bind(
443             sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
444             sigc::bind(
445                 sigc::ptr_fun(&rebuild_all_rows),
446                 sigc::mem_fun(*this, &LayerSelector::_selectLayer),
447                 _desktop
448             )
449         );
451         Inkscape::XML::NodeEventVector events = {
452             &node_added,
453             &node_removed,
454             &attribute_changed,
455             NULL,
456             &node_reordered
457         };
459         vector = new Inkscape::XML::NodeEventVector(events);
460     } else {
461         Inkscape::XML::NodeEventVector events = {
462             NULL,
463             NULL,
464             &attribute_changed,
465             NULL,
466             NULL
467         };
469         vector = new Inkscape::XML::NodeEventVector(events);
470     }
472     Gtk::ListStore::iterator row(_layer_model->append());
474     row->set_value(_model_columns.depth, depth);
476     sp_object_ref(&object, NULL);
477     row->set_value(_model_columns.object, &object);
479     Inkscape::GC::anchor(SP_OBJECT_REPR(&object));
480     row->set_value(_model_columns.repr, SP_OBJECT_REPR(&object));
482     row->set_value(_model_columns.callbacks, reinterpret_cast<void *>(callbacks));
484     sp_repr_add_listener(SP_OBJECT_REPR(&object), vector, callbacks);
487 /** Removes a row from the _model_columns object, disconnecting listeners
488  *  on the slot.
489  */
490 void LayerSelector::_destroyEntry(Gtk::ListStore::iterator const &row) {
491     Callbacks *callbacks=reinterpret_cast<Callbacks *>(row->get_value(_model_columns.callbacks));
492     SPObject *object=row->get_value(_model_columns.object);
493     if (object) {
494         sp_object_unref(object, NULL);
495     }
496     Inkscape::XML::Node *repr=row->get_value(_model_columns.repr);
497     if (repr) {
498         sp_repr_remove_listener_by_data(repr, callbacks);
499         Inkscape::GC::release(repr);
500     }
501     delete callbacks;
504 /** Formats the label for a given layer row 
505  */
506 void LayerSelector::_prepareLabelRenderer(
507     Gtk::TreeModel::const_iterator const &row
508 ) {
509     unsigned depth=(*row)[_model_columns.depth];
510     SPObject *object=(*row)[_model_columns.object];
511     bool label_defaulted(false);
513     // TODO: when the currently selected row is removed,
514     //       (or before one has been selected) something appears to
515     //       "invent" an iterator with null data and try to render it;
516     //       where does it come from, and how can we avoid it?
517     if ( object && SP_OBJECT_REPR(object) ) {
518         SPObject *layer=( _desktop ? _desktop->currentLayer() : NULL );
519         SPObject *root=( _desktop ? _desktop->currentRoot() : NULL );
521         bool isancestor = !( layer && SP_OBJECT_PARENT(object) == SP_OBJECT_PARENT(layer) || layer == root && SP_OBJECT_PARENT(object) == root);
523         bool iscurrent = ( object == layer && object != root );
525         gchar *format = g_strdup_printf (
526             "<span size=\"smaller\" %s><tt>%*s%s</tt>%s%s%s%%s%s%s%s</span>",
527             ( _desktop && _desktop->itemIsHidden (SP_ITEM(object)) ? "foreground=\"gray50\"" : "" ),
528             depth, "", ( iscurrent ? "&#8226;" : " " ),
529             ( iscurrent ? "<b>" : "" ),
530             ( SP_ITEM(object)->isLocked() ? "[" : "" ),
531             ( isancestor ? "<small>" : "" ),
532             ( isancestor ? "</small>" : "" ),
533             ( SP_ITEM(object)->isLocked() ? "]" : "" ),
534             ( iscurrent ? "</b>" : "" )
535             );
537         gchar const *label;
538         if ( object != root ) {
539             label = object->label();
540             if (!label) {
541                 label = object->defaultLabel();
542                 label_defaulted = true;
543             }
544         } else {
545             label = _("(root)");
546         }
548         gchar *text = g_markup_printf_escaped(format, label); 
549         _label_renderer.property_markup() = text;
550         g_free(text);
551         g_free(format);
552     } else {
553         _label_renderer.property_markup() = "<small> </small>";
554     }
556     _label_renderer.property_ypad() = 1;
557     _label_renderer.property_style() = ( label_defaulted ?
558                                          Pango::STYLE_ITALIC :
559                                          Pango::STYLE_NORMAL );
562 void LayerSelector::_lockLayer(bool lock) {
563     if ( _layer && SP_IS_ITEM(_layer) ) {
564         SP_ITEM(_layer)->setLocked(lock);
565         sp_document_maybe_done(sp_desktop_document(_desktop), "LayerSelector:lock");
566     }
569 void LayerSelector::_hideLayer(bool hide) {
570     if ( _layer && SP_IS_ITEM(_layer) ) {
571         SP_ITEM(_layer)->setHidden(hide);
572         sp_document_maybe_done(sp_desktop_document(_desktop), "LayerSelector:hide");
573     }
579 /*
580   Local Variables:
581   mode:c++
582   c-file-style:"stroustrup"
583   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
584   indent-tabs-mode:nil
585   fill-column:99
586   End:
587 */
588 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :