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(GtkIconSize 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(GTK_ICON_SIZE_MENU, "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(GTK_ICON_SIZE_MENU, "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);
149 }
151 /** Destructor - disconnects signal handler
152 */
153 LayerSelector::~LayerSelector() {
154 setDesktop(NULL);
155 _selection_changed_connection.disconnect();
156 }
158 namespace {
160 /** Helper function - detaches desktop from selector
161 */
162 bool detach(LayerSelector *selector) {
163 selector->setDesktop(NULL);
164 return FALSE;
165 }
167 }
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 }
196 }
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 };
224 }
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();
284 }
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_DT_SELECTION(_desktop)->clear();
296 _selectLayer(_desktop->currentLayer());
297 }
298 }
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)
305 {
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 }
317 }
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 }
348 }
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)
360 {
361 if ( !std::strcmp(name, "inkscape:groupmode") ) {
362 reinterpret_cast<Callbacks *>(data)->update_list();
363 } else {
364 reinterpret_cast<Callbacks *>(data)->update_row();
365 }
366 }
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 }
373 }
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 }
380 }
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)
385 {
386 gchar const *mode=child->attribute("inkscape:groupmode");
387 if ( mode && !std::strcmp(mode, "layer") ) {
388 reinterpret_cast<Callbacks *>(data)->update_list();
389 }
390 }
392 void update_row_for_object(SPObject *object,
393 Gtk::TreeModelColumn<SPObject *> const &column,
394 Glib::RefPtr<Gtk::ListStore> const &model)
395 {
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 }
406 }
408 void rebuild_all_rows(sigc::slot<void, SPObject *> rebuild, SPDesktop *desktop)
409 {
410 rebuild(desktop->currentLayer());
411 }
413 }
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);
423 }
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);
485 }
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;
502 }
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 ? "•" : " " ),
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 );
560 }
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_DT_DOCUMENT(_desktop), "LayerSelector:lock");
566 }
567 }
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_DT_DOCUMENT(_desktop), "LayerSelector:hide");
573 }
574 }
576 }
577 }
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 :