1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * LPE aux toolbar
5  */
6 /* Authors:
7  *   MenTaLguY <mental@rydia.net>
8  *   Lauris Kaplinski <lauris@kaplinski.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Frank Felfe <innerspace@iname.com>
11  *   John Cliff <simarilius@yahoo.com>
12  *   David Turner <novalis@gnu.org>
13  *   Josh Andler <scislac@scislac.com>
14  *   Jon A. Cruz <jon@joncruz.org>
15  *   Maximilian Albert <maximilian.albert@gmail.com>
16  *   Tavmjong Bah <tavmjong@free.fr>
17  *   Abhishek Sharma
18  *   Kris De Gussem <Kris.DeGussem@gmail.com>
19  *
20  * Copyright (C) 2004 David Turner
21  * Copyright (C) 2003 MenTaLguY
22  * Copyright (C) 1999-2011 authors
23  * Copyright (C) 2001-2002 Ximian, Inc.
24  *
25  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
26  */
27 
28 #include "lpe-toolbar.h"
29 
30 #include <gtkmm/radiotoolbutton.h>
31 #include <gtkmm/separatortoolitem.h>
32 
33 #include "live_effects/lpe-line_segment.h"
34 
35 #include "helper/action-context.h"
36 #include "helper/action.h"
37 
38 #include "ui/icon-names.h"
39 #include "ui/tools/lpe-tool.h"
40 #include "ui/widget/combo-tool-item.h"
41 #include "ui/widget/unit-tracker.h"
42 
43 using Inkscape::UI::Widget::UnitTracker;
44 using Inkscape::Util::Unit;
45 using Inkscape::Util::Quantity;
46 using Inkscape::DocumentUndo;
47 using Inkscape::UI::Tools::ToolBase;
48 using Inkscape::UI::Tools::LpeTool;
49 
50 namespace Inkscape {
51 namespace UI {
52 namespace Toolbar {
LPEToolbar(SPDesktop * desktop)53 LPEToolbar::LPEToolbar(SPDesktop *desktop)
54     : Toolbar(desktop),
55       _tracker(new UnitTracker(Util::UNIT_TYPE_LINEAR)),
56       _freeze(false),
57       _currentlpe(nullptr),
58       _currentlpeitem(nullptr)
59 {
60     _tracker->setActiveUnit(_desktop->getNamedView()->display_units);
61 
62     auto unit = _tracker->getActiveUnit();
63     g_return_if_fail(unit != nullptr);
64 
65     auto prefs = Inkscape::Preferences::get();
66     prefs->setString("/tools/lpetool/unit", unit->abbr);
67 
68     /* Automatically create a list of LPEs that get added to the toolbar **/
69     {
70         Gtk::RadioToolButton::Group mode_group;
71 
72         // The first toggle button represents the state that no subtool is active.
73         auto inactive_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("All inactive")));
74         inactive_mode_btn->set_tooltip_text(_("No geometric tool is active"));
75         inactive_mode_btn->set_icon_name(INKSCAPE_ICON("draw-geometry-inactive"));
76         _mode_buttons.push_back(inactive_mode_btn);
77 
78         Inkscape::LivePathEffect::EffectType type;
79         for (int i = 1; i < num_subtools; ++i) { // i == 0 ia INVALIDE_LPE.
80 
81             type =  lpesubtools[i].type;
82 
83             auto btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, Inkscape::LivePathEffect::LPETypeConverter.get_label(type)));
84             btn->set_tooltip_text(_(Inkscape::LivePathEffect::LPETypeConverter.get_label(type).c_str()));
85             btn->set_icon_name(lpesubtools[i].icon_name);
86             _mode_buttons.push_back(btn);
87         }
88 
89         int btn_idx = 0;
90         for (auto btn : _mode_buttons) {
91             btn->set_sensitive(true);
92             add(*btn);
93             btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPEToolbar::mode_changed), btn_idx++));
94         }
95 
96         int mode = prefs->getInt("/tools/lpetool/mode", 0);
97         _mode_buttons[mode]->set_active();
98     }
99 
100     add(* Gtk::manage(new Gtk::SeparatorToolItem()));
101 
102     /* Show limiting bounding box */
103     {
104         _show_bbox_item = add_toggle_button(_("Show limiting bounding box"),
105                                             _("Show bounding box (used to cut infinite lines)"));
106         _show_bbox_item->set_icon_name(INKSCAPE_ICON("show-bounding-box"));
107         _show_bbox_item->signal_toggled().connect(sigc::mem_fun(*this, &LPEToolbar::toggle_show_bbox));
108         _show_bbox_item->set_active(prefs->getBool( "/tools/lpetool/show_bbox", true ));
109     }
110 
111     /* Set limiting bounding box to bbox of current selection */
112     {
113         // TODO: Shouldn't this just be a button (not toggle button)?
114         _bbox_from_selection_item = add_toggle_button(_("Get limiting bounding box from selection"),
115                                                       _("Set limiting bounding box (used to cut infinite lines) to the bounding box of current selection"));
116         _bbox_from_selection_item->set_icon_name(INKSCAPE_ICON("draw-geometry-set-bounding-box"));
117         _bbox_from_selection_item->signal_toggled().connect(sigc::mem_fun(*this, &LPEToolbar::toggle_set_bbox));
118         _bbox_from_selection_item->set_active(false);
119     }
120 
121     add(* Gtk::manage(new Gtk::SeparatorToolItem()));
122 
123     /* Combo box to choose line segment type */
124     {
125         UI::Widget::ComboToolItemColumns columns;
126         Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
127 
128         std::vector<gchar*> line_segment_dropdown_items_list = {
129             _("Closed"),
130             _("Open start"),
131             _("Open end"),
132             _("Open both")
133         };
134 
135         for (auto item: line_segment_dropdown_items_list) {
136             Gtk::TreeModel::Row row = *(store->append());
137             row[columns.col_label    ] = item;
138             row[columns.col_sensitive] = true;
139         }
140 
141         _line_segment_combo = Gtk::manage(UI::Widget::ComboToolItem::create(_("Line Type"), _("Choose a line segment type"), "Not Used", store));
142         _line_segment_combo->use_group_label(false);
143 
144         _line_segment_combo->set_active(0);
145 
146         _line_segment_combo->signal_changed().connect(sigc::mem_fun(*this, &LPEToolbar::change_line_segment_type));
147         add(*_line_segment_combo);
148     }
149 
150     add(* Gtk::manage(new Gtk::SeparatorToolItem()));
151 
152     /* Display measuring info for selected items */
153     {
154         _measuring_item = add_toggle_button(_("Display measuring info"),
155                                             _("Display measuring info for selected items"));
156         _measuring_item->set_icon_name(INKSCAPE_ICON("draw-geometry-show-measuring-info"));
157         _measuring_item->signal_toggled().connect(sigc::mem_fun(*this, &LPEToolbar::toggle_show_measuring_info));
158         _measuring_item->set_active( prefs->getBool( "/tools/lpetool/show_measuring_info", true ) );
159     }
160 
161     // Add the units menu
162     {
163         _units_item = _tracker->create_tool_item(_("Units"), ("") );
164         add(*_units_item);
165         _units_item->signal_changed_after().connect(sigc::mem_fun(*this, &LPEToolbar::unit_changed));
166         _units_item->set_sensitive( prefs->getBool("/tools/lpetool/show_measuring_info", true));
167     }
168 
169     add(* Gtk::manage(new Gtk::SeparatorToolItem()));
170 
171     /* Open LPE dialog (to adapt parameters numerically) */
172     {
173         // TODO: Shouldn't this be a regular Gtk::ToolButton (not toggle)?
174         _open_lpe_dialog_item = add_toggle_button(_("Open LPE dialog"),
175                                                   _("Open LPE dialog (to adapt parameters numerically)"));
176         _open_lpe_dialog_item->set_icon_name(INKSCAPE_ICON("dialog-geometry"));
177         _open_lpe_dialog_item->signal_toggled().connect(sigc::mem_fun(*this, &LPEToolbar::open_lpe_dialog));
178         _open_lpe_dialog_item->set_active(false);
179     }
180 
181     desktop->connectEventContextChanged(sigc::mem_fun(*this, &LPEToolbar::watch_ec));
182 
183     show_all();
184 }
185 
186 void
set_mode(int mode)187 LPEToolbar::set_mode(int mode)
188 {
189     _mode_buttons[mode]->set_active();
190 }
191 
192 GtkWidget *
create(SPDesktop * desktop)193 LPEToolbar::create(SPDesktop *desktop)
194 {
195     auto toolbar = new LPEToolbar(desktop);
196     return GTK_WIDGET(toolbar->gobj());
197 }
198 
199 // this is called when the mode is changed via the toolbar (i.e., one of the subtool buttons is pressed)
200 void
mode_changed(int mode)201 LPEToolbar::mode_changed(int mode)
202 {
203     using namespace Inkscape::LivePathEffect;
204 
205     ToolBase *ec = _desktop->event_context;
206     if (!SP_IS_LPETOOL_CONTEXT(ec)) {
207         return;
208     }
209 
210     // only take action if run by the attr_changed listener
211     if (!_freeze) {
212         // in turn, prevent listener from responding
213         _freeze = true;
214 
215         EffectType type = lpesubtools[mode].type;
216 
217         LpeTool *lc = SP_LPETOOL_CONTEXT(_desktop->event_context);
218         bool success = lpetool_try_construction(lc, type);
219         if (success) {
220             // since the construction was already performed, we set the state back to inactive
221             _mode_buttons[0]->set_active();
222             mode = 0;
223         } else {
224             // switch to the chosen subtool
225             SP_LPETOOL_CONTEXT(_desktop->event_context)->mode = type;
226         }
227 
228         if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
229             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
230             prefs->setInt( "/tools/lpetool/mode", mode );
231         }
232 
233         _freeze = false;
234     }
235 }
236 
237 void
toggle_show_bbox()238 LPEToolbar::toggle_show_bbox() {
239     auto prefs = Inkscape::Preferences::get();
240 
241     bool show = _show_bbox_item->get_active();
242     prefs->setBool("/tools/lpetool/show_bbox",  show);
243 
244     LpeTool *lc = dynamic_cast<LpeTool *>(_desktop->event_context);
245     if (lc) {
246         lpetool_context_reset_limiting_bbox(lc);
247     }
248 }
249 
250 void
toggle_set_bbox()251 LPEToolbar::toggle_set_bbox()
252 {
253     auto selection = _desktop->selection;
254 
255     auto bbox = selection->visualBounds();
256 
257     if (bbox) {
258         Geom::Point A(bbox->min());
259         Geom::Point B(bbox->max());
260 
261         A *= _desktop->doc2dt();
262         B *= _desktop->doc2dt();
263 
264         // TODO: should we provide a way to store points in prefs?
265         auto prefs = Inkscape::Preferences::get();
266         prefs->setDouble("/tools/lpetool/bbox_upperleftx", A[Geom::X]);
267         prefs->setDouble("/tools/lpetool/bbox_upperlefty", A[Geom::Y]);
268         prefs->setDouble("/tools/lpetool/bbox_lowerrightx", B[Geom::X]);
269         prefs->setDouble("/tools/lpetool/bbox_lowerrighty", B[Geom::Y]);
270 
271         lpetool_context_reset_limiting_bbox(SP_LPETOOL_CONTEXT(_desktop->event_context));
272     }
273 
274     _bbox_from_selection_item->set_active(false);
275 }
276 
277 void
change_line_segment_type(int mode)278 LPEToolbar::change_line_segment_type(int mode)
279 {
280     using namespace Inkscape::LivePathEffect;
281 
282     // quit if run by the attr_changed listener
283     if (_freeze) {
284         return;
285     }
286 
287     // in turn, prevent listener from responding
288     _freeze = true;
289     auto line_seg = dynamic_cast<LPELineSegment *>(_currentlpe);
290 
291     if (_currentlpeitem && line_seg) {
292         line_seg->end_type.param_set_value(static_cast<Inkscape::LivePathEffect::EndType>(mode));
293         sp_lpe_item_update_patheffect(_currentlpeitem, true, true);
294     }
295 
296     _freeze = false;
297 }
298 
299 void
toggle_show_measuring_info()300 LPEToolbar::toggle_show_measuring_info()
301 {
302     LpeTool *lc = dynamic_cast<LpeTool *>(_desktop->event_context);
303     if (!lc) {
304         return;
305     }
306 
307     bool show = _measuring_item->get_active();
308 
309     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
310     prefs->setBool("/tools/lpetool/show_measuring_info",  show);
311 
312     lpetool_show_measuring_info(lc, show);
313 
314     _units_item->set_sensitive( show );
315 }
316 
317 void
unit_changed(int)318 LPEToolbar::unit_changed(int /* NotUsed */)
319 {
320     Unit const *unit = _tracker->getActiveUnit();
321     g_return_if_fail(unit != nullptr);
322     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
323     prefs->setString("/tools/lpetool/unit", unit->abbr);
324 
325     if (SP_IS_LPETOOL_CONTEXT(_desktop->event_context)) {
326         LpeTool *lc = SP_LPETOOL_CONTEXT(_desktop->event_context);
327         lpetool_delete_measuring_items(lc);
328         lpetool_create_measuring_items(lc);
329     }
330 }
331 
332 void
open_lpe_dialog()333 LPEToolbar::open_lpe_dialog()
334 {
335     if (dynamic_cast<LpeTool *>(_desktop->event_context)) {
336         sp_action_perform(Inkscape::Verb::get(SP_VERB_DIALOG_LIVE_PATH_EFFECT)->get_action(Inkscape::ActionContext(_desktop)), nullptr);
337     } else {
338         std::cerr << "LPEToolbar::open_lpe_dialog: LPEToolbar active but current tool is not LPE tool!" << std::endl;
339     }
340     _open_lpe_dialog_item->set_active(false);
341 }
342 
343 void
watch_ec(SPDesktop * desktop,Inkscape::UI::Tools::ToolBase * ec)344 LPEToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
345 {
346     if (SP_IS_LPETOOL_CONTEXT(ec)) {
347         // Watch selection
348         c_selection_modified = desktop->getSelection()->connectModified(sigc::mem_fun(*this, &LPEToolbar::sel_modified));
349         c_selection_changed = desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &LPEToolbar::sel_changed));
350         sel_changed(desktop->getSelection());
351     } else {
352         if (c_selection_modified)
353             c_selection_modified.disconnect();
354         if (c_selection_changed)
355             c_selection_changed.disconnect();
356     }
357 }
358 
359 void
sel_modified(Inkscape::Selection * selection,guint)360 LPEToolbar::sel_modified(Inkscape::Selection *selection, guint /*flags*/)
361 {
362     ToolBase *ec = selection->desktop()->event_context;
363     if (SP_IS_LPETOOL_CONTEXT(ec)) {
364         lpetool_update_measuring_items(SP_LPETOOL_CONTEXT(ec));
365     }
366 }
367 
368 void
sel_changed(Inkscape::Selection * selection)369 LPEToolbar::sel_changed(Inkscape::Selection *selection)
370 {
371     using namespace Inkscape::LivePathEffect;
372     ToolBase *ec = selection->desktop()->event_context;
373     if (!SP_IS_LPETOOL_CONTEXT(ec)) {
374         return;
375     }
376     LpeTool *lc = SP_LPETOOL_CONTEXT(ec);
377 
378     lpetool_delete_measuring_items(lc);
379     lpetool_create_measuring_items(lc, selection);
380 
381     // activate line segment combo box if a single item with LPELineSegment is selected
382     SPItem *item = selection->singleItem();
383     if (item && SP_IS_LPE_ITEM(item) && lpetool_item_has_construction(lc, item)) {
384 
385         SPLPEItem *lpeitem = SP_LPE_ITEM(item);
386         Effect* lpe = lpeitem->getCurrentLPE();
387         if (lpe && lpe->effectType() == LINE_SEGMENT) {
388             LPELineSegment *lpels = static_cast<LPELineSegment*>(lpe);
389             _currentlpe = lpe;
390             _currentlpeitem = lpeitem;
391             _line_segment_combo->set_sensitive(true);
392             _line_segment_combo->set_active( lpels->end_type.get_value() );
393         } else {
394             _currentlpe = nullptr;
395             _currentlpeitem = nullptr;
396             _line_segment_combo->set_sensitive(false);
397         }
398 
399     } else {
400         _currentlpe = nullptr;
401         _currentlpeitem = nullptr;
402         _line_segment_combo->set_sensitive(false);
403     }
404 }
405 
406 }
407 }
408 }
409 
410 /*
411   Local Variables:
412   mode:c++
413   c-file-style:"stroustrup"
414   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
415   indent-tabs-mode:nil
416   fill-column:99
417   End:
418 */
419 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
420