Code

A simple layout document as to what, why and how is cppification.
[inkscape.git] / src / ui / widget / 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 <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/icon-names.h"
27 #include "ui/widget/layer-selector.h"
28 #include "util/filter-list.h"
29 #include "util/reverse-list.h"
30 #include "verbs.h"
31 #include "widgets/icon.h"
32 #include "widgets/shrink-wrap-button.h"
33 #include "xml/node-event-vector.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,
97         INKSCAPE_ICON_OBJECT_VISIBLE, INKSCAPE_ICON_OBJECT_HIDDEN));
98     _visibility_toggle.add(*label);
99     _visibility_toggle.signal_toggled().connect(
100         sigc::compose(
101             sigc::mem_fun(*label, &AlternateIcons::setState),
102             sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
103         )
104     );
105     _visibility_toggled_connection = _visibility_toggle.signal_toggled().connect(
106         sigc::compose(
107             sigc::mem_fun(*this, &LayerSelector::_hideLayer),
108             sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
109         )
110     );
112     _visibility_toggle.set_relief(Gtk::RELIEF_NONE);
113     shrink_wrap_button(_visibility_toggle);
114     _tooltips.set_tip(_visibility_toggle, _("Toggle current layer visibility"));
115     pack_start(_visibility_toggle, Gtk::PACK_EXPAND_PADDING);
117     label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION,
118         INKSCAPE_ICON_OBJECT_UNLOCKED, INKSCAPE_ICON_OBJECT_LOCKED));
119     _lock_toggle.add(*label);
120     _lock_toggle.signal_toggled().connect(
121         sigc::compose(
122             sigc::mem_fun(*label, &AlternateIcons::setState),
123             sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
124         )
125     );
126     _lock_toggled_connection = _lock_toggle.signal_toggled().connect(
127         sigc::compose(
128             sigc::mem_fun(*this, &LayerSelector::_lockLayer),
129             sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
130         )
131     );
133     _lock_toggle.set_relief(Gtk::RELIEF_NONE);
134     shrink_wrap_button(_lock_toggle);
135     _tooltips.set_tip(_lock_toggle, _("Lock or unlock current layer"));
136     pack_start(_lock_toggle, Gtk::PACK_EXPAND_PADDING);
138     _tooltips.set_tip(_selector, _("Current layer"));
139     pack_start(_selector, Gtk::PACK_EXPAND_WIDGET);
141     _layer_model = Gtk::ListStore::create(_model_columns);
142     _selector.set_model(_layer_model);
143     _selector.pack_start(_label_renderer);
144     _selector.set_cell_data_func(
145         _label_renderer,
146         sigc::mem_fun(*this, &LayerSelector::_prepareLabelRenderer)
147     );
149     _selection_changed_connection = _selector.signal_changed().connect(
150         sigc::mem_fun(*this, &LayerSelector::_setDesktopLayer)
151     );
152     setDesktop(desktop);
155 /**  Destructor - disconnects signal handler
156  */
157 LayerSelector::~LayerSelector() {
158     setDesktop(NULL);
159     _selection_changed_connection.disconnect();
162 namespace {
164 /** Helper function - detaches desktop from selector
165  */
166 bool detach(LayerSelector *selector) {
167     selector->setDesktop(NULL);
168     return FALSE;
173 /** Sets the desktop for the widget.  First disconnects signals
174  *  for the current desktop, then stores the pointer to the
175  *  given \a desktop, and attaches its signals to this one.
176  *  Then it selects the current layer for the desktop.
177  */
178 void LayerSelector::setDesktop(SPDesktop *desktop) {
179     if ( desktop == _desktop ) {
180         return;
181     }
183     if (_desktop) {
184 //        _desktop_shutdown_connection.disconnect();
185         _layer_changed_connection.disconnect();
186 //        g_signal_handlers_disconnect_by_func(_desktop, (gpointer)&detach, this);
187     }
188     _desktop = desktop;
189     if (_desktop) {
190         // TODO we need a different signal for this, really..s
191 //        _desktop_shutdown_connection = _desktop->connectShutdown(
192 //          sigc::bind (sigc::ptr_fun (detach), this));
193 //        g_signal_connect_after(_desktop, "shutdown", GCallback(detach), this);
195         _layer_changed_connection = _desktop->connectCurrentLayerChanged(
196             sigc::mem_fun(*this, &LayerSelector::_selectLayer)
197         );
198         _selectLayer(_desktop->currentLayer());
199     }
202 namespace {
204 class is_layer {
205 public:
206     is_layer(SPDesktop *desktop) : _desktop(desktop) {}
207     bool operator()(SPObject &object) const {
208         return _desktop->isLayer(&object);
209     }
210 private:
211     SPDesktop *_desktop;
212 };
214 class column_matches_object {
215 public:
216     column_matches_object(Gtk::TreeModelColumn<SPObject *> const &column,
217                           SPObject &object)
218     : _column(column), _object(object) {}
219     bool operator()(Gtk::TreeModel::const_iterator const &iter) const {
220         SPObject *current=(*iter)[_column];
221         return current == &_object;
222     }
223 private:
224     Gtk::TreeModelColumn<SPObject *> const &_column;
225     SPObject &_object;
226 };
230 /** Selects the given layer in the dropdown selector.
231  */
232 void LayerSelector::_selectLayer(SPObject *layer) {
233     using Inkscape::Util::List;
234     using Inkscape::Util::cons;
235     using Inkscape::Util::reverse_list;
237     _selection_changed_connection.block();
239     while (!_layer_model->children().empty()) {
240         Gtk::ListStore::iterator first_row(_layer_model->children().begin());
241         _destroyEntry(first_row);
242         _layer_model->erase(first_row);
243     }
245     SPObject *root=_desktop->currentRoot();
247     if (_layer) {
248         sp_object_unref(_layer, NULL);
249         _layer = NULL;
250     }
252     if (layer) {
253         List<SPObject &> hierarchy=reverse_list<SPObject::ParentIterator>(layer, root);
254         if ( layer == root ) {
255             _buildEntries(0, cons(*root, hierarchy));
256         } else if (hierarchy) {
257             _buildSiblingEntries(0, *root, hierarchy);
258         }
260         Gtk::TreeIter row(
261             std::find_if(
262                 _layer_model->children().begin(),
263                 _layer_model->children().end(),
264                 column_matches_object(_model_columns.object, *layer)
265             )
266         );
267         if ( row != _layer_model->children().end() ) {
268             _selector.set_active(row);
269         }
271         _layer = layer;
272         sp_object_ref(_layer, NULL);
273     }
275     if ( !layer || layer == root ) {
276         _visibility_toggle.set_sensitive(false);
277         _visibility_toggle.set_active(false);
278         _lock_toggle.set_sensitive(false);
279         _lock_toggle.set_active(false);
280     } else {
281         _visibility_toggle.set_sensitive(true);
282         _visibility_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false ));
283         _lock_toggle.set_sensitive(true);
284         _lock_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false ));
285     }
287     _selection_changed_connection.unblock();
290 /** Sets the current desktop layer to the actively selected layer.
291  */
292 void LayerSelector::_setDesktopLayer() {
293     Gtk::ListStore::iterator selected(_selector.get_active());
294     SPObject *layer=_selector.get_active()->get_value(_model_columns.object);
295     if ( _desktop && layer ) {
296         _layer_changed_connection.block();
298         _desktop->layer_manager->setCurrentLayer(layer);
300         _layer_changed_connection.unblock();
302         _selectLayer(_desktop->currentLayer());
303     }
304     if (_desktop && _desktop->canvas) {
305         gtk_widget_grab_focus (GTK_WIDGET(_desktop->canvas));
306     }
309 /** Creates rows in the _layer_model data structure for each item
310  *  in \a hierarchy, to a given \a depth.
311  */
312 void LayerSelector::_buildEntries(unsigned depth,
313                                   Inkscape::Util::List<SPObject &> hierarchy)
315     using Inkscape::Util::List;
316     using Inkscape::Util::rest;
318     _buildEntry(depth, *hierarchy);
320     List<SPObject &> remainder=rest(hierarchy);
321     if (remainder) {
322         _buildEntries(depth+1, remainder);
323     } else {
324         _buildSiblingEntries(depth+1, *hierarchy, remainder);
325     }
328 /** Creates entries in the _layer_model data structure for
329  *  all siblings of the first child in \a parent.
330  */
331 void LayerSelector::_buildSiblingEntries(
332     unsigned depth, SPObject &parent,
333     Inkscape::Util::List<SPObject &> hierarchy
334 ) {
335     using Inkscape::Util::List;
336     using Inkscape::Util::rest;
337     using Inkscape::Util::reverse_list_in_place;
338     using Inkscape::Util::filter_list;
340     Inkscape::Util::List<SPObject &> siblings(
341         reverse_list_in_place(
342             filter_list<SPObject::SiblingIterator>(
343                 is_layer(_desktop), parent.firstChild(), NULL
344             )
345         )
346     );
348     SPObject *layer( hierarchy ? &*hierarchy : NULL );
350     while (siblings) {
351         _buildEntry(depth, *siblings);
352         if ( &*siblings == layer ) {
353             _buildSiblingEntries(depth+1, *layer, rest(hierarchy));
354         }
355         ++siblings;
356     }
359 namespace {
361 struct Callbacks {
362     sigc::slot<void> update_row;
363     sigc::slot<void> update_list;
364 };
366 void attribute_changed(Inkscape::XML::Node */*repr*/, gchar const *name,
367                        gchar const */*old_value*/, gchar const */*new_value*/,
368                        bool /*is_interactive*/, void *data)
370     if ( !std::strcmp(name, "inkscape:groupmode") ) {
371         reinterpret_cast<Callbacks *>(data)->update_list();
372     } else {
373         reinterpret_cast<Callbacks *>(data)->update_row();
374     }
377 void node_added(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, void *data) {
378     gchar const *mode=child->attribute("inkscape:groupmode");
379     if ( mode && !std::strcmp(mode, "layer") ) {
380         reinterpret_cast<Callbacks *>(data)->update_list();
381     }
384 void node_removed(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, void *data) {
385     gchar const *mode=child->attribute("inkscape:groupmode");
386     if ( mode && !std::strcmp(mode, "layer") ) {
387         reinterpret_cast<Callbacks *>(data)->update_list();
388     }
391 void node_reordered(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child,
392                     Inkscape::XML::Node */*old_ref*/, Inkscape::XML::Node */*new_ref*/,
393                     void *data)
395     gchar const *mode=child->attribute("inkscape:groupmode");
396     if ( mode && !std::strcmp(mode, "layer") ) {
397         reinterpret_cast<Callbacks *>(data)->update_list();
398     }
401 void update_row_for_object(SPObject *object,
402                            Gtk::TreeModelColumn<SPObject *> const &column,
403                            Glib::RefPtr<Gtk::ListStore> const &model)
405     Gtk::TreeIter row(
406         std::find_if(
407             model->children().begin(),
408             model->children().end(),
409             column_matches_object(column, *object)
410         )
411     );
412     if ( row != model->children().end() ) {
413         model->row_changed(model->get_path(row), row);
414     }
417 void rebuild_all_rows(sigc::slot<void, SPObject *> rebuild, SPDesktop *desktop)
419     rebuild(desktop->currentLayer());
424 void LayerSelector::_protectUpdate(sigc::slot<void> slot) {
425     bool visibility_blocked=_visibility_toggled_connection.blocked();
426     bool lock_blocked=_lock_toggled_connection.blocked();
427     _visibility_toggled_connection.block(true);
428     _lock_toggled_connection.block(true);
429     slot();
431     SPObject *layer = _desktop ? _desktop->currentLayer() : 0;
432     if ( layer ) {
433         bool wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false );
434         if ( _lock_toggle.get_active() != wantedValue ) {
435             _lock_toggle.set_active( wantedValue );
436         }
437         wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false );
438         if ( _visibility_toggle.get_active() != wantedValue ) {
439             _visibility_toggle.set_active( wantedValue );
440         }
441     }
442     _visibility_toggled_connection.block(visibility_blocked);
443     _lock_toggled_connection.block(lock_blocked);
446 /** Builds and appends a row in the layer model object.
447  */
448 void LayerSelector::_buildEntry(unsigned depth, SPObject &object) {
449     Inkscape::XML::NodeEventVector *vector;
451     Callbacks *callbacks=new Callbacks();
453     callbacks->update_row = sigc::bind(
454         sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
455         sigc::bind(
456             sigc::ptr_fun(&update_row_for_object),
457             &object, _model_columns.object, _layer_model
458         )
459     );
461     SPObject *layer=_desktop->currentLayer();
462     if ( &object == layer || &object == SP_OBJECT_PARENT(layer) ) {
463         callbacks->update_list = sigc::bind(
464             sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
465             sigc::bind(
466                 sigc::ptr_fun(&rebuild_all_rows),
467                 sigc::mem_fun(*this, &LayerSelector::_selectLayer),
468                 _desktop
469             )
470         );
472         Inkscape::XML::NodeEventVector events = {
473             &node_added,
474             &node_removed,
475             &attribute_changed,
476             NULL,
477             &node_reordered
478         };
480         vector = new Inkscape::XML::NodeEventVector(events);
481     } else {
482         Inkscape::XML::NodeEventVector events = {
483             NULL,
484             NULL,
485             &attribute_changed,
486             NULL,
487             NULL
488         };
490         vector = new Inkscape::XML::NodeEventVector(events);
491     }
493     Gtk::ListStore::iterator row(_layer_model->append());
495     row->set_value(_model_columns.depth, depth);
497     sp_object_ref(&object, NULL);
498     row->set_value(_model_columns.object, &object);
500     Inkscape::GC::anchor(SP_OBJECT_REPR(&object));
501     row->set_value(_model_columns.repr, SP_OBJECT_REPR(&object));
503     row->set_value(_model_columns.callbacks, reinterpret_cast<void *>(callbacks));
505     sp_repr_add_listener(SP_OBJECT_REPR(&object), vector, callbacks);
508 /** Removes a row from the _model_columns object, disconnecting listeners
509  *  on the slot.
510  */
511 void LayerSelector::_destroyEntry(Gtk::ListStore::iterator const &row) {
512     Callbacks *callbacks=reinterpret_cast<Callbacks *>(row->get_value(_model_columns.callbacks));
513     SPObject *object=row->get_value(_model_columns.object);
514     if (object) {
515         sp_object_unref(object, NULL);
516     }
517     Inkscape::XML::Node *repr=row->get_value(_model_columns.repr);
518     if (repr) {
519         sp_repr_remove_listener_by_data(repr, callbacks);
520         Inkscape::GC::release(repr);
521     }
522     delete callbacks;
525 /** Formats the label for a given layer row
526  */
527 void LayerSelector::_prepareLabelRenderer(
528     Gtk::TreeModel::const_iterator const &row
529 ) {
530     unsigned depth=(*row)[_model_columns.depth];
531     SPObject *object=(*row)[_model_columns.object];
532     bool label_defaulted(false);
534     // TODO: when the currently selected row is removed,
535     //       (or before one has been selected) something appears to
536     //       "invent" an iterator with null data and try to render it;
537     //       where does it come from, and how can we avoid it?
538     if ( object && SP_OBJECT_REPR(object) ) {
539         SPObject *layer=( _desktop ? _desktop->currentLayer() : NULL );
540         SPObject *root=( _desktop ? _desktop->currentRoot() : NULL );
542         bool isancestor = !( (layer && (SP_OBJECT_PARENT(object) == SP_OBJECT_PARENT(layer))) || ((layer == root) && (SP_OBJECT_PARENT(object) == root)));
544         bool iscurrent = ( object == layer && object != root );
546         gchar *format = g_strdup_printf (
547             "<span size=\"smaller\" %s><tt>%*s%s</tt>%s%s%s%%s%s%s%s</span>",
548             ( _desktop && _desktop->itemIsHidden (SP_ITEM(object)) ? "foreground=\"gray50\"" : "" ),
549             depth, "", ( iscurrent ? "&#8226;" : " " ),
550             ( iscurrent ? "<b>" : "" ),
551             ( SP_ITEM(object)->isLocked() ? "[" : "" ),
552             ( isancestor ? "<small>" : "" ),
553             ( isancestor ? "</small>" : "" ),
554             ( SP_ITEM(object)->isLocked() ? "]" : "" ),
555             ( iscurrent ? "</b>" : "" )
556             );
558         gchar const *label;
559         if ( object != root ) {
560             label = object->label();
561             if (!label) {
562                 label = object->defaultLabel();
563                 label_defaulted = true;
564             }
565         } else {
566             label = _("(root)");
567         }
569         gchar *text = g_markup_printf_escaped(format, label);
570         _label_renderer.property_markup() = text;
571         g_free(text);
572         g_free(format);
573     } else {
574         _label_renderer.property_markup() = "<small> </small>";
575     }
577     _label_renderer.property_ypad() = 1;
578     _label_renderer.property_style() = ( label_defaulted ?
579                                          Pango::STYLE_ITALIC :
580                                          Pango::STYLE_NORMAL );
583 void LayerSelector::_lockLayer(bool lock) {
584     if ( _layer && SP_IS_ITEM(_layer) ) {
585         SP_ITEM(_layer)->setLocked(lock);
586         SPDocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_NONE,
587                          lock? _("Lock layer") : _("Unlock layer"));
588     }
591 void LayerSelector::_hideLayer(bool hide) {
592     if ( _layer && SP_IS_ITEM(_layer) ) {
593         SP_ITEM(_layer)->setHidden(hide);
594         SPDocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_NONE,
595                          hide? _("Hide layer") : _("Unhide layer"));
596     }
602 /*
603   Local Variables:
604   mode:c++
605   c-file-style:"stroustrup"
606   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
607   indent-tabs-mode:nil
608   fill-column:99
609   End:
610 */
611 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :