diff options
Diffstat (limited to '')
-rw-r--r-- | src/ui/widget/layer-selector.cpp | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/src/ui/widget/layer-selector.cpp b/src/ui/widget/layer-selector.cpp new file mode 100644 index 0000000..779542c --- /dev/null +++ b/src/ui/widget/layer-selector.cpp @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Inkscape::Widgets::LayerSelector - layer selector widget + * + * Authors: + * MenTaLguY <mental@rydia.net> + * Abhishek Sharma + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cstring> +#include <string> + +#include "ui/dialog/layer-properties.h" +#include "ui/icon-loader.h" +#include <boost/range/adaptor/filtered.hpp> +#include <boost/range/adaptor/reversed.hpp> +#include <glibmm/i18n.h> + +#include "desktop.h" + +#include "document.h" +#include "document-undo.h" +#include "layer-manager.h" +#include "ui/icon-names.h" +#include "ui/util.h" +#include "util/reverse-list.h" +#include "verbs.h" +#include "xml/node-event-vector.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +namespace { + +class AlternateIcons : public Gtk::HBox { +public: + AlternateIcons(Gtk::BuiltinIconSize size, Glib::ustring const &a, Glib::ustring const &b) + : _a(nullptr), _b(nullptr) + { + set_name("AlternateIcons"); + if (!a.empty()) { + _a = Gtk::manage(sp_get_icon_image(a, size)); + _a->set_no_show_all(true); + add(*_a); + } + if (!b.empty()) { + _b = Gtk::manage(sp_get_icon_image(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::Image *_a; + Gtk::Image *_b; + bool _state; +}; + +} + +/** LayerSelector constructor. Creates lock and hide buttons, + * initializes 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(nullptr), _layer(nullptr) +{ + set_name("LayerSelector"); + AlternateIcons *label; + + label = Gtk::manage(new AlternateIcons(Gtk::ICON_SIZE_MENU, + INKSCAPE_ICON("object-visible"), INKSCAPE_ICON("object-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); + _visibility_toggle.set_tooltip_text(_("Toggle current layer visibility")); + pack_start(_visibility_toggle, Gtk::PACK_EXPAND_PADDING); + + label = Gtk::manage(new AlternateIcons(Gtk::ICON_SIZE_MENU, + INKSCAPE_ICON("object-unlocked"), INKSCAPE_ICON("object-locked"))); + _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); + _lock_toggle.set_tooltip_text(_("Lock or unlock current layer")); + pack_start(_lock_toggle, Gtk::PACK_EXPAND_PADDING); + + _selector.set_tooltip_text(_("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(nullptr); + _selection_changed_connection.disconnect(); +} + +/** 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(); + if (_current_layer_changed_connection) + _current_layer_changed_connection.disconnect(); + if (_layers_changed_connection) + _layers_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); + + LayerManager *mgr = _desktop->layer_manager; + if ( mgr ) { + _current_layer_changed_connection = mgr->connectCurrentLayerChanged( sigc::mem_fun(*this, &LayerSelector::_selectLayer) ); + //_layerUpdatedConnection = mgr->connectLayerDetailsChanged( sigc::mem_fun(*this, &LayerSelector::_updateLayer) ); + _layers_changed_connection = mgr->connectChanged( sigc::mem_fun(*this, &LayerSelector::_layersChanged) ); + } + + _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; +}; + +} + +void LayerSelector::_layersChanged() +{ + if (_desktop) { + /* + * This code fixes #166691 but causes issues #1066543 and #1080378. + * Comment out until solution found. + */ + //_selectLayer(_desktop->currentLayer()); + } +} + +/** 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(); + _visibility_toggled_connection.block(); + _lock_toggled_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, nullptr); + _layer = nullptr; + } + + 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, nullptr); + } + + 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 )); + } + + _lock_toggled_connection.unblock(); + _visibility_toggled_connection.unblock(); + _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 ) { + _current_layer_changed_connection.block(); + _layers_changed_connection.block(); + + _desktop->layer_manager->setCurrentLayer(layer); + + _current_layer_changed_connection.unblock(); + _layers_changed_connection.unblock(); + + _selectLayer(_desktop->currentLayer()); + } + if (_desktop && _desktop->canvas) { + gtk_widget_grab_focus (GTK_WIDGET(_desktop->canvas)); + } +} + +/** 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::rest; + + auto siblings = parent.children | boost::adaptors::filtered(is_layer(_desktop)) | boost::adaptors::reversed; + + SPObject *layer( hierarchy ? &*hierarchy : nullptr ); + + for (auto& sib: siblings) { + _buildEntry(depth, sib); + if ( &sib == layer ) { + _buildSiblingEntries(depth+1, *layer, rest(hierarchy)); + } + } +} + +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() : nullptr; + 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 == layer->parent) ) { + 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, + nullptr, + &node_reordered + }; + + vector = new Inkscape::XML::NodeEventVector(events); + } else { + Inkscape::XML::NodeEventVector events = { + nullptr, + nullptr, + &attribute_changed, + nullptr, + nullptr + }; + + vector = new Inkscape::XML::NodeEventVector(events); + } + + Gtk::ListStore::iterator row(_layer_model->append()); + + row->set_value(_model_columns.depth, depth); + + sp_object_ref(&object, nullptr); + row->set_value(_model_columns.object, &object); + + Inkscape::GC::anchor(object.getRepr()); + row->set_value(_model_columns.repr, object.getRepr()); + + row->set_value(_model_columns.callbacks, reinterpret_cast<void *>(callbacks)); + + sp_repr_add_listener(object.getRepr(), 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, nullptr); + } + 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 && object->getRepr() ) { + SPObject *layer=( _desktop ? _desktop->currentLayer() : nullptr ); + SPObject *root=( _desktop ? _desktop->currentRoot() : nullptr ); + + bool isancestor = !( (layer && (object->parent == layer->parent)) || ((layer == root) && (object->parent == 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 (!object->label()) { + label = object->defaultLabel(); + label_defaulted = true; + } + } else { + label = _("(root)"); + } + + gchar *text = g_markup_printf_escaped(format, ink_ellipsize_text (label, 50).c_str()); + _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); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_NONE, + lock? _("Lock layer") : _("Unlock layer")); + } +} + +void LayerSelector::_hideLayer(bool hide) { + if ( _layer && SP_IS_ITEM(_layer) ) { + SP_ITEM(_layer)->setHidden(hide); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_NONE, + hide? _("Hide layer") : _("Unhide layer")); + } +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +/* + 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:fileencoding=utf-8:textwidth=99 : |