1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * Dialog for renaming layers.
5  */
6 /* Author:
7  *   Bryce W. Harrington <bryce@bryceharrington.org>
8  *   Andrius R. <knutux@gmail.com>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 2004 Bryce Harrington
12  * Copyright (C) 2006 Andrius R.
13  *
14  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15  */
16 
17 #include "layer-properties.h"
18 #include <glibmm/i18n.h>
19 #include <glibmm/main.h>
20 
21 #include "inkscape.h"
22 #include "desktop.h"
23 #include "document.h"
24 #include "document-undo.h"
25 #include "layer-manager.h"
26 #include "message-stack.h"
27 
28 #include "verbs.h"
29 #include "selection-chemistry.h"
30 #include "ui/icon-names.h"
31 #include "ui/widget/imagetoggler.h"
32 #include "ui/tools/tool-base.h"
33 
34 namespace Inkscape {
35 namespace UI {
36 namespace Dialogs {
37 
LayerPropertiesDialog()38 LayerPropertiesDialog::LayerPropertiesDialog()
39     : _strategy(nullptr),
40       _desktop(nullptr),
41       _layer(nullptr),
42       _position_visible(false),
43       _close_button(_("_Cancel"), true)
44 {
45     auto mainVBox = get_content_area();
46     _layout_table.set_row_spacing(4);
47     _layout_table.set_column_spacing(4);
48 
49     // Layer name widgets
50     _layer_name_entry.set_activates_default(true);
51     _layer_name_label.set_label(_("Layer name:"));
52     _layer_name_label.set_halign(Gtk::ALIGN_START);
53     _layer_name_label.set_valign(Gtk::ALIGN_CENTER);
54 
55     _layout_table.attach(_layer_name_label, 0, 0, 1, 1);
56 
57     _layer_name_entry.set_halign(Gtk::ALIGN_FILL);
58     _layer_name_entry.set_valign(Gtk::ALIGN_FILL);
59     _layer_name_entry.set_hexpand();
60     _layout_table.attach(_layer_name_entry, 1, 0, 1, 1);
61 
62     mainVBox->pack_start(_layout_table, true, true, 4);
63 
64     // Buttons
65     _close_button.set_can_default();
66 
67     _apply_button.set_use_underline(true);
68     _apply_button.set_can_default();
69 
70     _close_button.signal_clicked()
71         .connect(sigc::mem_fun(*this, &LayerPropertiesDialog::_close));
72     _apply_button.signal_clicked()
73         .connect(sigc::mem_fun(*this, &LayerPropertiesDialog::_apply));
74 
75     signal_delete_event().connect(
76         sigc::bind_return(
77             sigc::hide(sigc::mem_fun(*this, &LayerPropertiesDialog::_close)),
78             true
79         )
80     );
81 
82     add_action_widget(_close_button, Gtk::RESPONSE_CLOSE);
83     add_action_widget(_apply_button, Gtk::RESPONSE_APPLY);
84 
85     _apply_button.grab_default();
86 
87     show_all_children();
88 }
89 
~LayerPropertiesDialog()90 LayerPropertiesDialog::~LayerPropertiesDialog() {
91 
92     _setDesktop(nullptr);
93     _setLayer(nullptr);
94 }
95 
_showDialog(LayerPropertiesDialog::Strategy & strategy,SPDesktop * desktop,SPObject * layer)96 void LayerPropertiesDialog::_showDialog(LayerPropertiesDialog::Strategy &strategy,
97                                         SPDesktop *desktop, SPObject *layer)
98 {
99     LayerPropertiesDialog *dialog = new LayerPropertiesDialog();
100 
101     dialog->_strategy = &strategy;
102     dialog->_setDesktop(desktop);
103     dialog->_setLayer(layer);
104 
105     dialog->_strategy->setup(*dialog);
106 
107     dialog->set_modal(true);
108     desktop->setWindowTransient (dialog->gobj());
109     dialog->property_destroy_with_parent() = true;
110 
111     dialog->show();
112     dialog->present();
113 }
114 
115 void
_apply()116 LayerPropertiesDialog::_apply()
117 {
118     g_assert(_strategy != nullptr);
119 
120     _strategy->perform(*this);
121     _close();
122 }
123 
124 void
_close()125 LayerPropertiesDialog::_close()
126 {
127     _setLayer(nullptr);
128     _setDesktop(nullptr);
129     destroy_();
130     Glib::signal_idle().connect(
131         sigc::bind_return(
132             sigc::bind(sigc::ptr_fun<void*, void>(&::operator delete), this),
133             false
134         )
135     );
136 }
137 
138 void
_setup_position_controls()139 LayerPropertiesDialog::_setup_position_controls() {
140     if ( nullptr == _layer || _desktop->currentRoot() == _layer ) {
141         // no layers yet, so option above/below/sublayer is useless
142         return;
143     }
144 
145     _position_visible = true;
146     _dropdown_list = Gtk::ListStore::create(_dropdown_columns);
147     _layer_position_combo.set_model(_dropdown_list);
148     _layer_position_combo.pack_start(_label_renderer);
149     _layer_position_combo.set_cell_data_func(_label_renderer,
150                                              sigc::mem_fun(*this, &LayerPropertiesDialog::_prepareLabelRenderer));
151 
152     Gtk::ListStore::iterator row;
153     row = _dropdown_list->append();
154     row->set_value(_dropdown_columns.position, LPOS_ABOVE);
155     row->set_value(_dropdown_columns.name, Glib::ustring(_("Above current")));
156     _layer_position_combo.set_active(row);
157     row = _dropdown_list->append();
158     row->set_value(_dropdown_columns.position, LPOS_BELOW);
159     row->set_value(_dropdown_columns.name, Glib::ustring(_("Below current")));
160     row = _dropdown_list->append();
161     row->set_value(_dropdown_columns.position, LPOS_CHILD);
162     row->set_value(_dropdown_columns.name, Glib::ustring(_("As sublayer of current")));
163 
164     _layer_position_label.set_label(_("Position:"));
165     _layer_position_label.set_halign(Gtk::ALIGN_START);
166     _layer_position_label.set_valign(Gtk::ALIGN_CENTER);
167 
168     _layer_position_combo.set_halign(Gtk::ALIGN_FILL);
169     _layer_position_combo.set_valign(Gtk::ALIGN_FILL);
170     _layer_position_combo.set_hexpand();
171     _layout_table.attach(_layer_position_combo, 1, 1, 1, 1);
172 
173     _layout_table.attach(_layer_position_label, 0, 1, 1, 1);
174 
175     show_all_children();
176 }
177 
178 void
_setup_layers_controls()179 LayerPropertiesDialog::_setup_layers_controls() {
180 
181     ModelColumns *zoop = new ModelColumns();
182     _model = zoop;
183     _store = Gtk::TreeStore::create( *zoop );
184     _tree.set_model( _store );
185     _tree.set_headers_visible(false);
186 
187     Inkscape::UI::Widget::ImageToggler *eyeRenderer = Gtk::manage( new Inkscape::UI::Widget::ImageToggler(
188         INKSCAPE_ICON("object-visible"), INKSCAPE_ICON("object-hidden")) );
189     int visibleColNum = _tree.append_column("vis", *eyeRenderer) - 1;
190     Gtk::TreeViewColumn* col = _tree.get_column(visibleColNum);
191     if ( col ) {
192         col->add_attribute( eyeRenderer->property_active(), _model->_colVisible );
193     }
194 
195     Inkscape::UI::Widget::ImageToggler * renderer = Gtk::manage( new Inkscape::UI::Widget::ImageToggler(
196         INKSCAPE_ICON("object-locked"), INKSCAPE_ICON("object-unlocked")) );
197     int lockedColNum = _tree.append_column("lock", *renderer) - 1;
198     col = _tree.get_column(lockedColNum);
199     if ( col ) {
200         col->add_attribute( renderer->property_active(), _model->_colLocked );
201     }
202 
203     Gtk::CellRendererText *_text_renderer = Gtk::manage(new Gtk::CellRendererText());
204     int nameColNum = _tree.append_column("Name", *_text_renderer) - 1;
205     Gtk::TreeView::Column *_name_column = _tree.get_column(nameColNum);
206     _name_column->add_attribute(_text_renderer->property_text(), _model->_colLabel);
207 
208     _tree.set_expander_column( *_tree.get_column(nameColNum) );
209     _tree.signal_key_press_event().connect( sigc::mem_fun(*this, &LayerPropertiesDialog::_handleKeyEvent), false );
210     _tree.signal_button_press_event().connect_notify( sigc::mem_fun(*this, &LayerPropertiesDialog::_handleButtonEvent) );
211 
212     _scroller.add( _tree );
213     _scroller.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC );
214     _scroller.set_shadow_type(Gtk::SHADOW_IN);
215     _scroller.set_size_request(220, 180);
216 
217     SPDocument* document = _desktop->doc();
218     SPRoot* root = document->getRoot();
219     if ( root ) {
220         SPObject* target = _desktop->currentLayer();
221         _store->clear();
222         _addLayer( document, SP_OBJECT(root), nullptr, target, 0 );
223     }
224 
225     _layout_table.remove(_layer_name_entry);
226     _layout_table.remove(_layer_name_label);
227 
228     _scroller.set_halign(Gtk::ALIGN_FILL);
229     _scroller.set_valign(Gtk::ALIGN_FILL);
230     _scroller.set_hexpand();
231     _scroller.set_vexpand();
232     _scroller.set_propagate_natural_width(true);
233     _scroller.set_propagate_natural_height(true);
234     _layout_table.attach(_scroller, 0, 1, 2, 1);
235 
236     show_all_children();
237 }
238 
_addLayer(SPDocument * doc,SPObject * layer,Gtk::TreeModel::Row * parentRow,SPObject * target,int level)239 void LayerPropertiesDialog::_addLayer( SPDocument* doc, SPObject* layer, Gtk::TreeModel::Row* parentRow, SPObject* target, int level )
240 {
241     int _maxNestDepth = 20;
242     if ( _desktop && _desktop->layer_manager && layer && (level < _maxNestDepth) ) {
243         unsigned int counter = _desktop->layer_manager->childCount(layer);
244         for ( unsigned int i = 0; i < counter; i++ ) {
245             SPObject *child = _desktop->layer_manager->nthChildOf(layer, i);
246             if ( child ) {
247 #if DUMP_LAYERS
248                 g_message(" %3d    layer:%p  {%s}   [%s]", level, child, child->id, child->label() );
249 #endif // DUMP_LAYERS
250 
251                 Gtk::TreeModel::iterator iter = parentRow ? _store->prepend(parentRow->children()) : _store->prepend();
252                 Gtk::TreeModel::Row row = *iter;
253                 row[_model->_colObject] = child;
254                 row[_model->_colLabel] = child->label() ? child->label() : child->getId();
255                 row[_model->_colVisible] = SP_IS_ITEM(child) ? !SP_ITEM(child)->isHidden() : false;
256                 row[_model->_colLocked] = SP_IS_ITEM(child) ? SP_ITEM(child)->isLocked() : false;
257 
258                 if ( target && child == target ) {
259                     _tree.expand_to_path( _store->get_path(iter) );
260 
261                     Glib::RefPtr<Gtk::TreeSelection> select = _tree.get_selection();
262                     select->select(iter);
263 
264                     //_checkTreeSelection();
265                 }
266 
267                 _addLayer( doc, child, &row, target, level + 1 );
268             }
269         }
270     }
271 }
272 
_selectedLayer()273 SPObject* LayerPropertiesDialog::_selectedLayer()
274 {
275     SPObject* obj = nullptr;
276 
277     Gtk::TreeModel::iterator iter = _tree.get_selection()->get_selected();
278     if ( iter ) {
279         Gtk::TreeModel::Row row = *iter;
280         obj = row[_model->_colObject];
281     }
282 
283     return obj;
284 }
285 
_handleKeyEvent(GdkEventKey * event)286 bool LayerPropertiesDialog::_handleKeyEvent(GdkEventKey *event)
287 {
288 
289     switch (Inkscape::UI::Tools::get_latin_keyval(event)) {
290         case GDK_KEY_Return:
291         case GDK_KEY_KP_Enter: {
292             _strategy->perform(*this);
293             _close();
294             return true;
295         }
296         break;
297     }
298     return false;
299 }
300 
_handleButtonEvent(GdkEventButton * event)301 void LayerPropertiesDialog::_handleButtonEvent(GdkEventButton* event)
302 {
303     if ( (event->type == GDK_2BUTTON_PRESS) && (event->button == 1) ) {
304         _strategy->perform(*this);
305         _close();
306     }
307 }
308 
309 /** Formats the label for a given layer row
310  */
_prepareLabelRenderer(Gtk::TreeModel::const_iterator const & row)311 void LayerPropertiesDialog::_prepareLabelRenderer(
312     Gtk::TreeModel::const_iterator const &row
313 ) {
314     Glib::ustring name=(*row)[_dropdown_columns.name];
315     _label_renderer.property_markup() = name.c_str();
316 }
317 
setup(LayerPropertiesDialog & dialog)318 void LayerPropertiesDialog::Rename::setup(LayerPropertiesDialog &dialog) {
319     SPDesktop *desktop=dialog._desktop;
320     dialog.set_title(_("Rename Layer"));
321     gchar const *name = desktop->currentLayer()->label();
322     dialog._layer_name_entry.set_text(( name ? name : _("Layer") ));
323     dialog._apply_button.set_label(_("_Rename"));
324 }
325 
perform(LayerPropertiesDialog & dialog)326 void LayerPropertiesDialog::Rename::perform(LayerPropertiesDialog &dialog) {
327     SPDesktop *desktop=dialog._desktop;
328     Glib::ustring name(dialog._layer_name_entry.get_text());
329     if (name.empty())
330         return;
331     desktop->layer_manager->renameLayer( desktop->currentLayer(),
332                                          (gchar *)name.c_str(),
333                                          FALSE
334     );
335     DocumentUndo::done(desktop->getDocument(), SP_VERB_LAYER_RENAME, _("Rename layer"));
336     // TRANSLATORS: This means "The layer has been renamed"
337     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Renamed layer"));
338 }
339 
setup(LayerPropertiesDialog & dialog)340 void LayerPropertiesDialog::Create::setup(LayerPropertiesDialog &dialog) {
341     dialog.set_title(_("Add Layer"));
342 
343     // Set the initial name to the "next available" layer name
344     LayerManager *mgr = dialog._desktop->layer_manager;
345     Glib::ustring newName = mgr->getNextLayerName(nullptr, dialog._desktop->currentLayer()->label());
346     dialog._layer_name_entry.set_text(newName.c_str());
347     dialog._apply_button.set_label(_("_Add"));
348     dialog._setup_position_controls();
349 }
350 
perform(LayerPropertiesDialog & dialog)351 void LayerPropertiesDialog::Create::perform(LayerPropertiesDialog &dialog) {
352     SPDesktop *desktop=dialog._desktop;
353 
354     LayerRelativePosition position = LPOS_ABOVE;
355 
356     if (dialog._position_visible) {
357         Gtk::ListStore::iterator activeRow(dialog._layer_position_combo.get_active());
358         position = activeRow->get_value(dialog._dropdown_columns.position);
359     }
360     Glib::ustring name(dialog._layer_name_entry.get_text());
361     if (name.empty())
362         return;
363 
364     SPObject *new_layer=Inkscape::create_layer(desktop->currentRoot(), dialog._layer, position);
365 
366     if (!name.empty()) {
367         desktop->layer_manager->renameLayer( new_layer, (gchar *)name.c_str(), TRUE );
368     }
369     desktop->getSelection()->clear();
370     desktop->setCurrentLayer(new_layer);
371     DocumentUndo::done(desktop->getDocument(), SP_VERB_LAYER_NEW, _("Add layer"));
372     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("New layer created."));
373 }
374 
setup(LayerPropertiesDialog & dialog)375 void LayerPropertiesDialog::Move::setup(LayerPropertiesDialog &dialog) {
376     dialog.set_title(_("Move to Layer"));
377     //TODO: find an unused layer number, forming name from _("Layer ") + "%d"
378     dialog._layer_name_entry.set_text(_("Layer"));
379     dialog._apply_button.set_label(_("_Move"));
380     dialog._setup_layers_controls();
381 }
382 
perform(LayerPropertiesDialog & dialog)383 void LayerPropertiesDialog::Move::perform(LayerPropertiesDialog &dialog) {
384 
385     SPObject *moveto = dialog._selectedLayer();
386     dialog._desktop->selection->toLayer(moveto);
387 }
388 
_setDesktop(SPDesktop * desktop)389 void LayerPropertiesDialog::_setDesktop(SPDesktop *desktop) {
390     if (desktop) {
391         Inkscape::GC::anchor (desktop);
392     }
393     if (_desktop) {
394         Inkscape::GC::release (_desktop);
395     }
396     _desktop = desktop;
397 }
398 
_setLayer(SPObject * layer)399 void LayerPropertiesDialog::_setLayer(SPObject *layer) {
400     if (layer) {
401         sp_object_ref(layer, nullptr);
402     }
403     if (_layer) {
404         sp_object_unref(_layer, nullptr);
405     }
406     _layer = layer;
407 }
408 
409 } // namespace
410 } // namespace
411 } // namespace
412 
413 
414 /*
415   Local Variables:
416   mode:c++
417   c-file-style:"stroustrup"
418   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
419   indent-tabs-mode:nil
420   fill-column:99
421   End:
422 */
423 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
424