Code

Don't force focus on the canvas when the desktop is given
[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"
33 #include "verbs.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, "visible", "hidden"));
97     _visibility_toggle.add(*label);
98     _visibility_toggle.signal_toggled().connect(
99         sigc::compose(
100             sigc::mem_fun(*label, &AlternateIcons::setState),
101             sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
102         )
103     );
104     _visibility_toggled_connection = _visibility_toggle.signal_toggled().connect(
105         sigc::compose(
106             sigc::mem_fun(*this, &LayerSelector::_hideLayer),
107             sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
108         )
109     );
111     _visibility_toggle.set_relief(Gtk::RELIEF_NONE);
112     shrink_wrap_button(_visibility_toggle);
113     _tooltips.set_tip(_visibility_toggle, _("Toggle current layer visibility"));
114     pack_start(_visibility_toggle, Gtk::PACK_EXPAND_PADDING);
116     label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION, "lock_unlocked", "width_height_lock"));
117     _lock_toggle.add(*label);
118     _lock_toggle.signal_toggled().connect(
119         sigc::compose(
120             sigc::mem_fun(*label, &AlternateIcons::setState),
121             sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
122         )
123     );
124     _lock_toggled_connection = _lock_toggle.signal_toggled().connect(
125         sigc::compose(
126             sigc::mem_fun(*this, &LayerSelector::_lockLayer),
127             sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
128         )
129     );
131     _lock_toggle.set_relief(Gtk::RELIEF_NONE);
132     shrink_wrap_button(_lock_toggle);
133     _tooltips.set_tip(_lock_toggle, _("Lock or unlock current layer"));
134     pack_start(_lock_toggle, Gtk::PACK_EXPAND_PADDING);
136     _tooltips.set_tip(_selector, _("Current layer"));
137     pack_start(_selector, Gtk::PACK_EXPAND_WIDGET);
139     _layer_model = Gtk::ListStore::create(_model_columns);
140     _selector.set_model(_layer_model);
141     _selector.pack_start(_label_renderer);
142     _selector.set_cell_data_func(
143         _label_renderer,
144         sigc::mem_fun(*this, &LayerSelector::_prepareLabelRenderer)
145     );
147     _selection_changed_connection = _selector.signal_changed().connect(
148         sigc::mem_fun(*this, &LayerSelector::_setDesktopLayer)
149     );
150     setDesktop(desktop);
153 /**  Destructor - disconnects signal handler 
154  */
155 LayerSelector::~LayerSelector() {
156     setDesktop(NULL);
157     _selection_changed_connection.disconnect();
160 namespace {
162 /** Helper function - detaches desktop from selector 
163  */
164 bool detach(LayerSelector *selector) {
165     selector->setDesktop(NULL);
166     return FALSE;
171 /** Sets the desktop for the widget.  First disconnects signals
172  *  for the current desktop, then stores the pointer to the
173  *  given \a desktop, and attaches its signals to this one.
174  *  Then it selects the current layer for the desktop.
175  */
176 void LayerSelector::setDesktop(SPDesktop *desktop) {
177     if ( desktop == _desktop ) {
178         return;
179     }
181     if (_desktop) {
182 //        _desktop_shutdown_connection.disconnect();
183         _layer_changed_connection.disconnect();
184 //        g_signal_handlers_disconnect_by_func(_desktop, (gpointer)&detach, this);
185     }
186     _desktop = desktop;
187     if (_desktop) {
188         // TODO we need a different signal for this, really..s
189 //        _desktop_shutdown_connection = _desktop->connectShutdown(
190 //          sigc::bind (sigc::ptr_fun (detach), this));
191 //        g_signal_connect_after(_desktop, "shutdown", GCallback(detach), this);
193         _layer_changed_connection = _desktop->connectCurrentLayerChanged(
194             sigc::mem_fun(*this, &LayerSelector::_selectLayer)
195         );
196         _selectLayer(_desktop->currentLayer());
197     }
200 namespace {
202 class is_layer {
203 public:
204     is_layer(SPDesktop *desktop) : _desktop(desktop) {}
205     bool operator()(SPObject &object) const {
206         return _desktop->isLayer(&object);
207     }
208 private:
209     SPDesktop *_desktop;
210 };
212 class column_matches_object {
213 public:
214     column_matches_object(Gtk::TreeModelColumn<SPObject *> const &column,
215                           SPObject &object)
216     : _column(column), _object(object) {}
217     bool operator()(Gtk::TreeModel::const_iterator const &iter) const {
218         SPObject *current=(*iter)[_column];
219         return current == &_object;
220     }
221 private:
222     Gtk::TreeModelColumn<SPObject *> const &_column;
223     SPObject &_object;
224 };
228 /** Selects the given layer in the dropdown selector.  
229  */
230 void LayerSelector::_selectLayer(SPObject *layer) {
231     using Inkscape::Util::List;
232     using Inkscape::Util::cons;
233     using Inkscape::Util::reverse_list;
235     _selection_changed_connection.block();
237     while (!_layer_model->children().empty()) {
238         Gtk::ListStore::iterator first_row(_layer_model->children().begin());
239         _destroyEntry(first_row);
240         _layer_model->erase(first_row);
241     }
243     SPObject *root=_desktop->currentRoot();
245     if (_layer) {
246         sp_object_unref(_layer, NULL);
247         _layer = NULL;
248     }
250     if (layer) {
251         List<SPObject &> hierarchy=reverse_list<SPObject::ParentIterator>(layer, root);
252         if ( layer == root ) {
253             _buildEntries(0, cons(*root, hierarchy));
254         } else if (hierarchy) {
255             _buildSiblingEntries(0, *root, hierarchy);
256         }
258         Gtk::TreeIter row(
259             std::find_if(
260                 _layer_model->children().begin(),
261                 _layer_model->children().end(),
262                 column_matches_object(_model_columns.object, *layer)
263             )
264         );
265         if ( row != _layer_model->children().end() ) {
266             _selector.set_active(row);
267         }
269         _layer = layer;
270         sp_object_ref(_layer, NULL);
271     }
273     if ( !layer || layer == root ) {
274         _visibility_toggle.set_sensitive(false);
275         _visibility_toggle.set_active(false);
276         _lock_toggle.set_sensitive(false);
277         _lock_toggle.set_active(false);
278     } else {
279         _visibility_toggle.set_sensitive(true);
280         _visibility_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false ));
281         _lock_toggle.set_sensitive(true);
282         _lock_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false ));
283     }
285     _selection_changed_connection.unblock();
288 /** Sets the current desktop layer to the actively selected layer.
289  */
290 void LayerSelector::_setDesktopLayer() {
291     Gtk::ListStore::iterator selected(_selector.get_active());
292     SPObject *layer=_selector.get_active()->get_value(_model_columns.object);
293     if ( _desktop && layer ) {
294         _layer_changed_connection.block();
296         _desktop->layer_manager->setCurrentLayer(layer);
298         _layer_changed_connection.unblock();
300         _selectLayer(_desktop->currentLayer());
301     }
304 /** Creates rows in the _layer_model data structure for each item
305  *  in \a hierarchy, to a given \a depth.
306  */
307 void LayerSelector::_buildEntries(unsigned depth,
308                                   Inkscape::Util::List<SPObject &> hierarchy)
310     using Inkscape::Util::List;
311     using Inkscape::Util::rest;
313     _buildEntry(depth, *hierarchy);
315     List<SPObject &> remainder=rest(hierarchy);
316     if (remainder) {
317         _buildEntries(depth+1, remainder);
318     } else {
319         _buildSiblingEntries(depth+1, *hierarchy, remainder);
320     }
323 /** Creates entries in the _layer_model data structure for
324  *  all siblings of the first child in \a parent.
325  */
326 void LayerSelector::_buildSiblingEntries(
327     unsigned depth, SPObject &parent,
328     Inkscape::Util::List<SPObject &> hierarchy
329 ) {
330     using Inkscape::Util::List;
331     using Inkscape::Util::rest;
332     using Inkscape::Util::reverse_list_in_place;
333     using Inkscape::Util::filter_list;
335     Inkscape::Util::List<SPObject &> siblings(
336         reverse_list_in_place(
337             filter_list<SPObject::SiblingIterator>(
338                 is_layer(_desktop), parent.firstChild(), NULL
339             )
340         )
341     );
343     SPObject *layer( hierarchy ? &*hierarchy : NULL );
345     while (siblings) {
346         _buildEntry(depth, *siblings);
347         if ( &*siblings == layer ) {
348             _buildSiblingEntries(depth+1, *layer, rest(hierarchy));
349         }
350         ++siblings;
351     }
354 namespace {
356 struct Callbacks {
357     sigc::slot<void> update_row;
358     sigc::slot<void> update_list;
359 };
361 void attribute_changed(Inkscape::XML::Node *repr, gchar const *name,
362                        gchar const *old_value, gchar const *new_value,
363                        bool is_interactive, void *data) 
365     if ( !std::strcmp(name, "inkscape:groupmode") ) {
366         reinterpret_cast<Callbacks *>(data)->update_list();
367     } else {
368         reinterpret_cast<Callbacks *>(data)->update_row();
369     }
372 void node_added(Inkscape::XML::Node *parent, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data) {
373     gchar const *mode=child->attribute("inkscape:groupmode");
374     if ( mode && !std::strcmp(mode, "layer") ) {
375         reinterpret_cast<Callbacks *>(data)->update_list();
376     }
379 void node_removed(Inkscape::XML::Node *parent, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data) {
380     gchar const *mode=child->attribute("inkscape:groupmode");
381     if ( mode && !std::strcmp(mode, "layer") ) {
382         reinterpret_cast<Callbacks *>(data)->update_list();
383     }
386 void node_reordered(Inkscape::XML::Node *parent, Inkscape::XML::Node *child,
387                     Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref,
388                     void *data)
390     gchar const *mode=child->attribute("inkscape:groupmode");
391     if ( mode && !std::strcmp(mode, "layer") ) {
392         reinterpret_cast<Callbacks *>(data)->update_list();
393     }
396 void update_row_for_object(SPObject *object,
397                            Gtk::TreeModelColumn<SPObject *> const &column,
398                            Glib::RefPtr<Gtk::ListStore> const &model)
400     Gtk::TreeIter row(
401         std::find_if(
402             model->children().begin(),
403             model->children().end(),
404             column_matches_object(column, *object)
405         )
406     );
407     if ( row != model->children().end() ) {
408         model->row_changed(model->get_path(row), row);
409     }
412 void rebuild_all_rows(sigc::slot<void, SPObject *> rebuild, SPDesktop *desktop)
414     rebuild(desktop->currentLayer());
419 void LayerSelector::_protectUpdate(sigc::slot<void> slot) {
420     bool visibility_blocked=_visibility_toggled_connection.blocked();
421     bool lock_blocked=_lock_toggled_connection.blocked();
422     _visibility_toggled_connection.block(true);
423     _lock_toggled_connection.block(true);
424     slot();
426     SPObject *layer = _desktop ? _desktop->currentLayer() : 0;
427     if ( layer ) {
428         bool wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false );
429         if ( _lock_toggle.get_active() != wantedValue ) {
430             _lock_toggle.set_active( wantedValue );
431         }
432         wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false );
433         if ( _visibility_toggle.get_active() != wantedValue ) {
434             _visibility_toggle.set_active( wantedValue );
435         }
436     }
437     _visibility_toggled_connection.block(visibility_blocked);
438     _lock_toggled_connection.block(lock_blocked);
441 /** Builds and appends a row in the layer model object.
442  */
443 void LayerSelector::_buildEntry(unsigned depth, SPObject &object) {
444     Inkscape::XML::NodeEventVector *vector;
446     Callbacks *callbacks=new Callbacks();
448     callbacks->update_row = sigc::bind(
449         sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
450         sigc::bind(
451             sigc::ptr_fun(&update_row_for_object),
452             &object, _model_columns.object, _layer_model
453         )
454     );
456     SPObject *layer=_desktop->currentLayer();
457     if ( &object == layer || &object == SP_OBJECT_PARENT(layer) ) {
458         callbacks->update_list = sigc::bind(
459             sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
460             sigc::bind(
461                 sigc::ptr_fun(&rebuild_all_rows),
462                 sigc::mem_fun(*this, &LayerSelector::_selectLayer),
463                 _desktop
464             )
465         );
467         Inkscape::XML::NodeEventVector events = {
468             &node_added,
469             &node_removed,
470             &attribute_changed,
471             NULL,
472             &node_reordered
473         };
475         vector = new Inkscape::XML::NodeEventVector(events);
476     } else {
477         Inkscape::XML::NodeEventVector events = {
478             NULL,
479             NULL,
480             &attribute_changed,
481             NULL,
482             NULL
483         };
485         vector = new Inkscape::XML::NodeEventVector(events);
486     }
488     Gtk::ListStore::iterator row(_layer_model->append());
490     row->set_value(_model_columns.depth, depth);
492     sp_object_ref(&object, NULL);
493     row->set_value(_model_columns.object, &object);
495     Inkscape::GC::anchor(SP_OBJECT_REPR(&object));
496     row->set_value(_model_columns.repr, SP_OBJECT_REPR(&object));
498     row->set_value(_model_columns.callbacks, reinterpret_cast<void *>(callbacks));
500     sp_repr_add_listener(SP_OBJECT_REPR(&object), vector, callbacks);
503 /** Removes a row from the _model_columns object, disconnecting listeners
504  *  on the slot.
505  */
506 void LayerSelector::_destroyEntry(Gtk::ListStore::iterator const &row) {
507     Callbacks *callbacks=reinterpret_cast<Callbacks *>(row->get_value(_model_columns.callbacks));
508     SPObject *object=row->get_value(_model_columns.object);
509     if (object) {
510         sp_object_unref(object, NULL);
511     }
512     Inkscape::XML::Node *repr=row->get_value(_model_columns.repr);
513     if (repr) {
514         sp_repr_remove_listener_by_data(repr, callbacks);
515         Inkscape::GC::release(repr);
516     }
517     delete callbacks;
520 /** Formats the label for a given layer row 
521  */
522 void LayerSelector::_prepareLabelRenderer(
523     Gtk::TreeModel::const_iterator const &row
524 ) {
525     unsigned depth=(*row)[_model_columns.depth];
526     SPObject *object=(*row)[_model_columns.object];
527     bool label_defaulted(false);
529     // TODO: when the currently selected row is removed,
530     //       (or before one has been selected) something appears to
531     //       "invent" an iterator with null data and try to render it;
532     //       where does it come from, and how can we avoid it?
533     if ( object && SP_OBJECT_REPR(object) ) {
534         SPObject *layer=( _desktop ? _desktop->currentLayer() : NULL );
535         SPObject *root=( _desktop ? _desktop->currentRoot() : NULL );
537         bool isancestor = !( layer && SP_OBJECT_PARENT(object) == SP_OBJECT_PARENT(layer) || layer == root && SP_OBJECT_PARENT(object) == root);
539         bool iscurrent = ( object == layer && object != root );
541         gchar *format = g_strdup_printf (
542             "<span size=\"smaller\" %s><tt>%*s%s</tt>%s%s%s%%s%s%s%s</span>",
543             ( _desktop && _desktop->itemIsHidden (SP_ITEM(object)) ? "foreground=\"gray50\"" : "" ),
544             depth, "", ( iscurrent ? "&#8226;" : " " ),
545             ( iscurrent ? "<b>" : "" ),
546             ( SP_ITEM(object)->isLocked() ? "[" : "" ),
547             ( isancestor ? "<small>" : "" ),
548             ( isancestor ? "</small>" : "" ),
549             ( SP_ITEM(object)->isLocked() ? "]" : "" ),
550             ( iscurrent ? "</b>" : "" )
551             );
553         gchar const *label;
554         if ( object != root ) {
555             label = object->label();
556             if (!label) {
557                 label = object->defaultLabel();
558                 label_defaulted = true;
559             }
560         } else {
561             label = _("(root)");
562         }
564         gchar *text = g_markup_printf_escaped(format, label); 
565         _label_renderer.property_markup() = text;
566         g_free(text);
567         g_free(format);
568     } else {
569         _label_renderer.property_markup() = "<small> </small>";
570     }
572     _label_renderer.property_ypad() = 1;
573     _label_renderer.property_style() = ( label_defaulted ?
574                                          Pango::STYLE_ITALIC :
575                                          Pango::STYLE_NORMAL );
578 void LayerSelector::_lockLayer(bool lock) {
579     if ( _layer && SP_IS_ITEM(_layer) ) {
580         SP_ITEM(_layer)->setLocked(lock);
581         sp_document_done(sp_desktop_document(_desktop), SP_VERB_NONE, 
582                          lock? _("Lock layer") : _("Unlock layer"));
583     }
586 void LayerSelector::_hideLayer(bool hide) {
587     if ( _layer && SP_IS_ITEM(_layer) ) {
588         SP_ITEM(_layer)->setHidden(hide);
589         sp_document_done(sp_desktop_document(_desktop), SP_VERB_NONE, 
590                          hide? _("Hide layer") : _("Unhide layer"));
591     }
597 /*
598   Local Variables:
599   mode:c++
600   c-file-style:"stroustrup"
601   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
602   indent-tabs-mode:nil
603   fill-column:99
604   End:
605 */
606 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :