2
* Inkscape::Widgets::LayerSelector - layer selector widget
5
* MenTaLguY <mental@rydia.net>
7
* Copyright (C) 2004 MenTaLguY
9
* Released under GNU GPL, read the file 'COPYING' for more information
18
#include <glibmm/i18n.h>
20
#include "desktop-handles.h"
22
#include "widgets/layer-selector.h"
23
#include "widgets/shrink-wrap-button.h"
24
#include "widgets/icon.h"
26
#include "util/reverse-list.h"
27
#include "util/filter-list.h"
32
#include "dialogs/layer-properties.h"
33
#include "layer-manager.h"
34
#include "xml/node-event-vector.h"
42
class AlternateIcons : public Gtk::HBox {
44
AlternateIcons(Inkscape::IconSize size, gchar const *a, gchar const *b)
48
_a = Gtk::manage(sp_icon_get_icon(a, size));
49
_a->set_no_show_all(true);
53
_b = Gtk::manage(sp_icon_get_icon(b, size));
54
_b->set_no_show_all(true);
60
bool state() const { return _state; }
61
void setState(bool state) {
88
/** LayerSelector constructor. Creates lock and hide buttons,
89
* initalizes the layer dropdown selector with a label renderer,
90
* and hooks up signal for setting the desktop layer when the
91
* selector is changed.
93
LayerSelector::LayerSelector(SPDesktop *desktop)
94
: _desktop(NULL), _layer(NULL)
96
AlternateIcons *label;
98
label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION, "visible", "hidden"));
99
_visibility_toggle.add(*label);
100
_visibility_toggle.signal_toggled().connect(
102
sigc::mem_fun(*label, &AlternateIcons::setState),
103
sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
106
_visibility_toggled_connection = _visibility_toggle.signal_toggled().connect(
108
sigc::mem_fun(*this, &LayerSelector::_hideLayer),
109
sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
113
_visibility_toggle.set_relief(Gtk::RELIEF_NONE);
114
shrink_wrap_button(_visibility_toggle);
115
_tooltips.set_tip(_visibility_toggle, _("Toggle current layer visibility"));
116
pack_start(_visibility_toggle, Gtk::PACK_EXPAND_PADDING);
118
label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION, "lock_unlocked", "width_height_lock"));
119
_lock_toggle.add(*label);
120
_lock_toggle.signal_toggled().connect(
122
sigc::mem_fun(*label, &AlternateIcons::setState),
123
sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
126
_lock_toggled_connection = _lock_toggle.signal_toggled().connect(
128
sigc::mem_fun(*this, &LayerSelector::_lockLayer),
129
sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
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(
146
sigc::mem_fun(*this, &LayerSelector::_prepareLabelRenderer)
149
_selection_changed_connection = _selector.signal_changed().connect(
150
sigc::mem_fun(*this, &LayerSelector::_setDesktopLayer)
155
/** Destructor - disconnects signal handler
157
LayerSelector::~LayerSelector() {
159
_selection_changed_connection.disconnect();
164
/** Helper function - detaches desktop from selector
166
bool detach(LayerSelector *selector) {
167
selector->setDesktop(NULL);
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.
178
void LayerSelector::setDesktop(SPDesktop *desktop) {
179
if ( desktop == _desktop ) {
184
// _desktop_shutdown_connection.disconnect();
185
_layer_changed_connection.disconnect();
186
// g_signal_handlers_disconnect_by_func(_desktop, (gpointer)&detach, this);
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)
198
_selectLayer(_desktop->currentLayer());
206
is_layer(SPDesktop *desktop) : _desktop(desktop) {}
207
bool operator()(SPObject &object) const {
208
return _desktop->isLayer(&object);
214
class column_matches_object {
216
column_matches_object(Gtk::TreeModelColumn<SPObject *> const &column,
218
: _column(column), _object(object) {}
219
bool operator()(Gtk::TreeModel::const_iterator const &iter) const {
220
SPObject *current=(*iter)[_column];
221
return current == &_object;
224
Gtk::TreeModelColumn<SPObject *> const &_column;
230
/** Selects the given layer in the dropdown selector.
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);
245
SPObject *root=_desktop->currentRoot();
248
sp_object_unref(_layer, NULL);
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);
262
_layer_model->children().begin(),
263
_layer_model->children().end(),
264
column_matches_object(_model_columns.object, *layer)
267
if ( row != _layer_model->children().end() ) {
268
_selector.set_active(row);
272
sp_object_ref(_layer, NULL);
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);
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 ));
287
_selection_changed_connection.unblock();
290
/** Sets the current desktop layer to the actively selected layer.
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());
304
if (_desktop && _desktop->canvas) {
305
gtk_widget_grab_focus (GTK_WIDGET(_desktop->canvas));
309
/** Creates rows in the _layer_model data structure for each item
310
* in \a hierarchy, to a given \a depth.
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);
322
_buildEntries(depth+1, remainder);
324
_buildSiblingEntries(depth+1, *hierarchy, remainder);
328
/** Creates entries in the _layer_model data structure for
329
* all siblings of the first child in \a parent.
331
void LayerSelector::_buildSiblingEntries(
332
unsigned depth, SPObject &parent,
333
Inkscape::Util::List<SPObject &> hierarchy
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
348
SPObject *layer( hierarchy ? &*hierarchy : NULL );
351
_buildEntry(depth, *siblings);
352
if ( &*siblings == layer ) {
353
_buildSiblingEntries(depth+1, *layer, rest(hierarchy));
362
sigc::slot<void> update_row;
363
sigc::slot<void> update_list;
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();
373
reinterpret_cast<Callbacks *>(data)->update_row();
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();
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();
391
void node_reordered(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child,
392
Inkscape::XML::Node */*old_ref*/, Inkscape::XML::Node */*new_ref*/,
395
gchar const *mode=child->attribute("inkscape:groupmode");
396
if ( mode && !std::strcmp(mode, "layer") ) {
397
reinterpret_cast<Callbacks *>(data)->update_list();
401
void update_row_for_object(SPObject *object,
402
Gtk::TreeModelColumn<SPObject *> const &column,
403
Glib::RefPtr<Gtk::ListStore> const &model)
407
model->children().begin(),
408
model->children().end(),
409
column_matches_object(column, *object)
412
if ( row != model->children().end() ) {
413
model->row_changed(model->get_path(row), row);
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);
431
SPObject *layer = _desktop ? _desktop->currentLayer() : 0;
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 );
437
wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false );
438
if ( _visibility_toggle.get_active() != wantedValue ) {
439
_visibility_toggle.set_active( wantedValue );
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.
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),
456
sigc::ptr_fun(&update_row_for_object),
457
&object, _model_columns.object, _layer_model
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),
466
sigc::ptr_fun(&rebuild_all_rows),
467
sigc::mem_fun(*this, &LayerSelector::_selectLayer),
472
Inkscape::XML::NodeEventVector events = {
480
vector = new Inkscape::XML::NodeEventVector(events);
482
Inkscape::XML::NodeEventVector events = {
490
vector = new Inkscape::XML::NodeEventVector(events);
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
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);
515
sp_object_unref(object, NULL);
517
Inkscape::XML::Node *repr=row->get_value(_model_columns.repr);
519
sp_repr_remove_listener_by_data(repr, callbacks);
520
Inkscape::GC::release(repr);
525
/** Formats the label for a given layer row
527
void LayerSelector::_prepareLabelRenderer(
528
Gtk::TreeModel::const_iterator const &row
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 ? "•" : " " ),
550
( iscurrent ? "<b>" : "" ),
551
( SP_ITEM(object)->isLocked() ? "[" : "" ),
552
( isancestor ? "<small>" : "" ),
553
( isancestor ? "</small>" : "" ),
554
( SP_ITEM(object)->isLocked() ? "]" : "" ),
555
( iscurrent ? "</b>" : "" )
559
if ( object != root ) {
560
label = object->label();
562
label = object->defaultLabel();
563
label_defaulted = true;
569
gchar *text = g_markup_printf_escaped(format, label);
570
_label_renderer.property_markup() = text;
574
_label_renderer.property_markup() = "<small> </small>";
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
sp_document_done(sp_desktop_document(_desktop), SP_VERB_NONE,
587
lock? _("Lock layer") : _("Unlock layer"));
591
void LayerSelector::_hideLayer(bool hide) {
592
if ( _layer && SP_IS_ITEM(_layer) ) {
593
SP_ITEM(_layer)->setHidden(hide);
594
sp_document_done(sp_desktop_document(_desktop), SP_VERB_NONE,
595
hide? _("Hide layer") : _("Unhide layer"));
605
c-file-style:"stroustrup"
606
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
611
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :