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