/* * Inkscape::Widgets::LayerSelector - layer selector widget * * Authors: * MenTaLguY <mental@rydia.net> * * Copyright (C) 2004 MenTaLguY * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include <glibmm/i18n.h> #include "desktop-handles.h" #include "widgets/layer-selector.h" #include "widgets/shrink-wrap-button.h" #include "widgets/icon.h" #include "util/reverse-list.h" #include "util/filter-list.h" #include "sp-item.h" #include "desktop.h" #include "document.h" #include "dialogs/layer-properties.h" #include "layer-manager.h" #include "xml/node-event-vector.h" #include "verbs.h" namespace Inkscape { namespace Widgets { namespace { class AlternateIcons : public Gtk::HBox { public: AlternateIcons(Inkscape::IconSize size, gchar const *a, gchar const *b) : _a(NULL), _b(NULL) { if (a) { _a = Gtk::manage(sp_icon_get_icon(a, size)); _a->set_no_show_all(true); add(*_a); } if (b) { _b = Gtk::manage(sp_icon_get_icon(b, size)); _b->set_no_show_all(true); add(*_b); } setState(false); } bool state() const { return _state; } void setState(bool state) { _state = state; if (_state) { if (_a) { _a->hide(); } if (_b) { _b->show(); } } else { if (_a) { _a->show(); } if (_b) { _b->hide(); } } } private: Gtk::Widget *_a; Gtk::Widget *_b; bool _state; }; } /** LayerSelector constructor. Creates lock and hide buttons, * initalizes the layer dropdown selector with a label renderer, * and hooks up signal for setting the desktop layer when the * selector is changed. */ LayerSelector::LayerSelector(SPDesktop *desktop) : _desktop(NULL), _layer(NULL) { AlternateIcons *label; label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION, "visible", "hidden")); _visibility_toggle.add(*label); _visibility_toggle.signal_toggled().connect( sigc::compose( sigc::mem_fun(*label, &AlternateIcons::setState), sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active) ) ); _visibility_toggled_connection = _visibility_toggle.signal_toggled().connect( sigc::compose( sigc::mem_fun(*this, &LayerSelector::_hideLayer), sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active) ) ); _visibility_toggle.set_relief(Gtk::RELIEF_NONE); shrink_wrap_button(_visibility_toggle); _tooltips.set_tip(_visibility_toggle, _("Toggle current layer visibility")); pack_start(_visibility_toggle, Gtk::PACK_EXPAND_PADDING); label = Gtk::manage(new AlternateIcons(Inkscape::ICON_SIZE_DECORATION, "lock_unlocked", "width_height_lock")); _lock_toggle.add(*label); _lock_toggle.signal_toggled().connect( sigc::compose( sigc::mem_fun(*label, &AlternateIcons::setState), sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active) ) ); _lock_toggled_connection = _lock_toggle.signal_toggled().connect( sigc::compose( sigc::mem_fun(*this, &LayerSelector::_lockLayer), sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active) ) ); _lock_toggle.set_relief(Gtk::RELIEF_NONE); shrink_wrap_button(_lock_toggle); _tooltips.set_tip(_lock_toggle, _("Lock or unlock current layer")); pack_start(_lock_toggle, Gtk::PACK_EXPAND_PADDING); _tooltips.set_tip(_selector, _("Current layer")); pack_start(_selector, Gtk::PACK_EXPAND_WIDGET); _layer_model = Gtk::ListStore::create(_model_columns); _selector.set_model(_layer_model); _selector.pack_start(_label_renderer); _selector.set_cell_data_func( _label_renderer, sigc::mem_fun(*this, &LayerSelector::_prepareLabelRenderer) ); _selection_changed_connection = _selector.signal_changed().connect( sigc::mem_fun(*this, &LayerSelector::_setDesktopLayer) ); setDesktop(desktop); } /** Destructor - disconnects signal handler */ LayerSelector::~LayerSelector() { setDesktop(NULL); _selection_changed_connection.disconnect(); } namespace { /** Helper function - detaches desktop from selector */ bool detach(LayerSelector *selector) { selector->setDesktop(NULL); return FALSE; } } /** Sets the desktop for the widget. First disconnects signals * for the current desktop, then stores the pointer to the * given \a desktop, and attaches its signals to this one. * Then it selects the current layer for the desktop. */ void LayerSelector::setDesktop(SPDesktop *desktop) { if ( desktop == _desktop ) { return; } if (_desktop) { // _desktop_shutdown_connection.disconnect(); _layer_changed_connection.disconnect(); // g_signal_handlers_disconnect_by_func(_desktop, (gpointer)&detach, this); } _desktop = desktop; if (_desktop) { // TODO we need a different signal for this, really..s // _desktop_shutdown_connection = _desktop->connectShutdown( // sigc::bind (sigc::ptr_fun (detach), this)); // g_signal_connect_after(_desktop, "shutdown", GCallback(detach), this); _layer_changed_connection = _desktop->connectCurrentLayerChanged( sigc::mem_fun(*this, &LayerSelector::_selectLayer) ); _selectLayer(_desktop->currentLayer()); } } namespace { class is_layer { public: is_layer(SPDesktop *desktop) : _desktop(desktop) {} bool operator()(SPObject &object) const { return _desktop->isLayer(&object); } private: SPDesktop *_desktop; }; class column_matches_object { public: column_matches_object(Gtk::TreeModelColumn<SPObject *> const &column, SPObject &object) : _column(column), _object(object) {} bool operator()(Gtk::TreeModel::const_iterator const &iter) const { SPObject *current=(*iter)[_column]; return current == &_object; } private: Gtk::TreeModelColumn<SPObject *> const &_column; SPObject &_object; }; } /** Selects the given layer in the dropdown selector. */ void LayerSelector::_selectLayer(SPObject *layer) { using Inkscape::Util::List; using Inkscape::Util::cons; using Inkscape::Util::reverse_list; _selection_changed_connection.block(); while (!_layer_model->children().empty()) { Gtk::ListStore::iterator first_row(_layer_model->children().begin()); _destroyEntry(first_row); _layer_model->erase(first_row); } SPObject *root=_desktop->currentRoot(); if (_layer) { sp_object_unref(_layer, NULL); _layer = NULL; } if (layer) { List<SPObject &> hierarchy=reverse_list<SPObject::ParentIterator>(layer, root); if ( layer == root ) { _buildEntries(0, cons(*root, hierarchy)); } else if (hierarchy) { _buildSiblingEntries(0, *root, hierarchy); } Gtk::TreeIter row( std::find_if( _layer_model->children().begin(), _layer_model->children().end(), column_matches_object(_model_columns.object, *layer) ) ); if ( row != _layer_model->children().end() ) { _selector.set_active(row); } _layer = layer; sp_object_ref(_layer, NULL); } if ( !layer || layer == root ) { _visibility_toggle.set_sensitive(false); _visibility_toggle.set_active(false); _lock_toggle.set_sensitive(false); _lock_toggle.set_active(false); } else { _visibility_toggle.set_sensitive(true); _visibility_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false )); _lock_toggle.set_sensitive(true); _lock_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false )); } _selection_changed_connection.unblock(); } /** Sets the current desktop layer to the actively selected layer. */ void LayerSelector::_setDesktopLayer() { Gtk::ListStore::iterator selected(_selector.get_active()); SPObject *layer=_selector.get_active()->get_value(_model_columns.object); if ( _desktop && layer ) { _layer_changed_connection.block(); _desktop->layer_manager->setCurrentLayer(layer); _layer_changed_connection.unblock(); _selectLayer(_desktop->currentLayer()); } } /** Creates rows in the _layer_model data structure for each item * in \a hierarchy, to a given \a depth. */ void LayerSelector::_buildEntries(unsigned depth, Inkscape::Util::List<SPObject &> hierarchy) { using Inkscape::Util::List; using Inkscape::Util::rest; _buildEntry(depth, *hierarchy); List<SPObject &> remainder=rest(hierarchy); if (remainder) { _buildEntries(depth+1, remainder); } else { _buildSiblingEntries(depth+1, *hierarchy, remainder); } } /** Creates entries in the _layer_model data structure for * all siblings of the first child in \a parent. */ void LayerSelector::_buildSiblingEntries( unsigned depth, SPObject &parent, Inkscape::Util::List<SPObject &> hierarchy ) { using Inkscape::Util::List; using Inkscape::Util::rest; using Inkscape::Util::reverse_list_in_place; using Inkscape::Util::filter_list; Inkscape::Util::List<SPObject &> siblings( reverse_list_in_place( filter_list<SPObject::SiblingIterator>( is_layer(_desktop), parent.firstChild(), NULL ) ) ); SPObject *layer( hierarchy ? &*hierarchy : NULL ); while (siblings) { _buildEntry(depth, *siblings); if ( &*siblings == layer ) { _buildSiblingEntries(depth+1, *layer, rest(hierarchy)); } ++siblings; } } namespace { struct Callbacks { sigc::slot<void> update_row; sigc::slot<void> update_list; }; void attribute_changed(Inkscape::XML::Node *repr, gchar const *name, gchar const *old_value, gchar const *new_value, bool is_interactive, void *data) { if ( !std::strcmp(name, "inkscape:groupmode") ) { reinterpret_cast<Callbacks *>(data)->update_list(); } else { reinterpret_cast<Callbacks *>(data)->update_row(); } } void node_added(Inkscape::XML::Node *parent, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data) { gchar const *mode=child->attribute("inkscape:groupmode"); if ( mode && !std::strcmp(mode, "layer") ) { reinterpret_cast<Callbacks *>(data)->update_list(); } } void node_removed(Inkscape::XML::Node *parent, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data) { gchar const *mode=child->attribute("inkscape:groupmode"); if ( mode && !std::strcmp(mode, "layer") ) { reinterpret_cast<Callbacks *>(data)->update_list(); } } void node_reordered(Inkscape::XML::Node *parent, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref, void *data) { gchar const *mode=child->attribute("inkscape:groupmode"); if ( mode && !std::strcmp(mode, "layer") ) { reinterpret_cast<Callbacks *>(data)->update_list(); } } void update_row_for_object(SPObject *object, Gtk::TreeModelColumn<SPObject *> const &column, Glib::RefPtr<Gtk::ListStore> const &model) { Gtk::TreeIter row( std::find_if( model->children().begin(), model->children().end(), column_matches_object(column, *object) ) ); if ( row != model->children().end() ) { model->row_changed(model->get_path(row), row); } } void rebuild_all_rows(sigc::slot<void, SPObject *> rebuild, SPDesktop *desktop) { rebuild(desktop->currentLayer()); } } void LayerSelector::_protectUpdate(sigc::slot<void> slot) { bool visibility_blocked=_visibility_toggled_connection.blocked(); bool lock_blocked=_lock_toggled_connection.blocked(); _visibility_toggled_connection.block(true); _lock_toggled_connection.block(true); slot(); SPObject *layer = _desktop ? _desktop->currentLayer() : 0; if ( layer ) { bool wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false ); if ( _lock_toggle.get_active() != wantedValue ) { _lock_toggle.set_active( wantedValue ); } wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false ); if ( _visibility_toggle.get_active() != wantedValue ) { _visibility_toggle.set_active( wantedValue ); } } _visibility_toggled_connection.block(visibility_blocked); _lock_toggled_connection.block(lock_blocked); } /** Builds and appends a row in the layer model object. */ void LayerSelector::_buildEntry(unsigned depth, SPObject &object) { Inkscape::XML::NodeEventVector *vector; Callbacks *callbacks=new Callbacks(); callbacks->update_row = sigc::bind( sigc::mem_fun(*this, &LayerSelector::_protectUpdate), sigc::bind( sigc::ptr_fun(&update_row_for_object), &object, _model_columns.object, _layer_model ) ); SPObject *layer=_desktop->currentLayer(); if ( &object == layer || &object == SP_OBJECT_PARENT(layer) ) { callbacks->update_list = sigc::bind( sigc::mem_fun(*this, &LayerSelector::_protectUpdate), sigc::bind( sigc::ptr_fun(&rebuild_all_rows), sigc::mem_fun(*this, &LayerSelector::_selectLayer), _desktop ) ); Inkscape::XML::NodeEventVector events = { &node_added, &node_removed, &attribute_changed, NULL, &node_reordered }; vector = new Inkscape::XML::NodeEventVector(events); } else { Inkscape::XML::NodeEventVector events = { NULL, NULL, &attribute_changed, NULL, NULL }; vector = new Inkscape::XML::NodeEventVector(events); } Gtk::ListStore::iterator row(_layer_model->append()); row->set_value(_model_columns.depth, depth); sp_object_ref(&object, NULL); row->set_value(_model_columns.object, &object); Inkscape::GC::anchor(SP_OBJECT_REPR(&object)); row->set_value(_model_columns.repr, SP_OBJECT_REPR(&object)); row->set_value(_model_columns.callbacks, reinterpret_cast<void *>(callbacks)); sp_repr_add_listener(SP_OBJECT_REPR(&object), vector, callbacks); } /** Removes a row from the _model_columns object, disconnecting listeners * on the slot. */ void LayerSelector::_destroyEntry(Gtk::ListStore::iterator const &row) { Callbacks *callbacks=reinterpret_cast<Callbacks *>(row->get_value(_model_columns.callbacks)); SPObject *object=row->get_value(_model_columns.object); if (object) { sp_object_unref(object, NULL); } Inkscape::XML::Node *repr=row->get_value(_model_columns.repr); if (repr) { sp_repr_remove_listener_by_data(repr, callbacks); Inkscape::GC::release(repr); } delete callbacks; } /** Formats the label for a given layer row */ void LayerSelector::_prepareLabelRenderer( Gtk::TreeModel::const_iterator const &row ) { unsigned depth=(*row)[_model_columns.depth]; SPObject *object=(*row)[_model_columns.object]; bool label_defaulted(false); // TODO: when the currently selected row is removed, // (or before one has been selected) something appears to // "invent" an iterator with null data and try to render it; // where does it come from, and how can we avoid it? if ( object && SP_OBJECT_REPR(object) ) { SPObject *layer=( _desktop ? _desktop->currentLayer() : NULL ); SPObject *root=( _desktop ? _desktop->currentRoot() : NULL ); bool isancestor = !( layer && SP_OBJECT_PARENT(object) == SP_OBJECT_PARENT(layer) || layer == root && SP_OBJECT_PARENT(object) == root); bool iscurrent = ( object == layer && object != root ); gchar *format = g_strdup_printf ( "<span size=\"smaller\" %s><tt>%*s%s</tt>%s%s%s%%s%s%s%s</span>", ( _desktop && _desktop->itemIsHidden (SP_ITEM(object)) ? "foreground=\"gray50\"" : "" ), depth, "", ( iscurrent ? "•" : " " ), ( iscurrent ? "<b>" : "" ), ( SP_ITEM(object)->isLocked() ? "[" : "" ), ( isancestor ? "<small>" : "" ), ( isancestor ? "</small>" : "" ), ( SP_ITEM(object)->isLocked() ? "]" : "" ), ( iscurrent ? "</b>" : "" ) ); gchar const *label; if ( object != root ) { label = object->label(); if (!label) { label = object->defaultLabel(); label_defaulted = true; } } else { label = _("(root)"); } gchar *text = g_markup_printf_escaped(format, label); _label_renderer.property_markup() = text; g_free(text); g_free(format); } else { _label_renderer.property_markup() = "<small> </small>"; } _label_renderer.property_ypad() = 1; _label_renderer.property_style() = ( label_defaulted ? Pango::STYLE_ITALIC : Pango::STYLE_NORMAL ); } void LayerSelector::_lockLayer(bool lock) { if ( _layer && SP_IS_ITEM(_layer) ) { SP_ITEM(_layer)->setLocked(lock); sp_document_done(sp_desktop_document(_desktop), SP_VERB_NONE, lock? _("Lock layer") : _("Unlock layer")); } } void LayerSelector::_hideLayer(bool hide) { if ( _layer && SP_IS_ITEM(_layer) ) { SP_ITEM(_layer)->setHidden(hide); sp_document_done(sp_desktop_document(_desktop), SP_VERB_NONE, hide? _("Hide layer") : _("Unhide layer")); } } } } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :