Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
pencil-toolbar.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
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 * Vaibhav Malik <vaibhavmalik2018@gmail.com>
20 *
21 * Copyright (C) 2004 David Turner
22 * Copyright (C) 2003 MenTaLguY
23 * Copyright (C) 1999-2011 authors
24 * Copyright (C) 2001-2002 Ximian, Inc.
25 *
26 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
27 */
28
29#include "pencil-toolbar.h"
30
31#include <glibmm/i18n.h>
32#include <gtkmm/adjustment.h>
33#include <gtkmm/togglebutton.h>
34
35#include "desktop.h"
44#include "object/sp-shape.h"
45#include "selection.h"
46#include "ui/builder-utils.h"
48#include "ui/tools/pen-tool.h"
49#include "ui/util.h"
52
53namespace Inkscape::UI::Toolbar {
54
56 : PencilToolbar{create_builder("toolbar-pencil.ui"), pencil_mode}
57{}
58
59PencilToolbar::PencilToolbar(Glib::RefPtr<Gtk::Builder> const &builder, bool pencil_mode)
60 : Toolbar{get_widget<Gtk::Box>(builder, "pencil-toolbar")}
61 , _tool_is_pencil(pencil_mode)
62 , _flatten_spiro_bspline_btn(get_widget<Gtk::Button>(builder, "_flatten_spiro_bspline_btn"))
63 , _usepressure_btn(get_widget<Gtk::ToggleButton>(builder, "_usepressure_btn"))
64 , _minpressure_box(get_widget<Gtk::Box>(builder, "_minpressure_box"))
65 , _minpressure_item(get_derived_widget<UI::Widget::SpinButton>(builder, "_minpressure_item"))
66 , _maxpressure_box(get_widget<Gtk::Box>(builder, "_maxpressure_box"))
67 , _maxpressure_item(get_derived_widget<UI::Widget::SpinButton>(builder, "_maxpressure_item"))
68 , _tolerance_item(get_derived_widget<UI::Widget::SpinButton>(builder, "_tolerance_item"))
69 , _simplify_btn(get_widget<Gtk::ToggleButton>(builder, "_simplify_btn"))
70 , _flatten_simplify_btn(get_widget<Gtk::Button>(builder, "_flatten_simplify_btn"))
71 , _shapescale_box(get_widget<Gtk::Box>(builder, "_shapescale_box"))
72 , _shapescale_item(get_derived_widget<UI::Widget::SpinButton>(builder, "_shapescale_item"))
73{
74 auto prefs = Preferences::get();
75
76 // Configure mode buttons
77 int btn_index = 0;
78 for_each_child(get_widget<Gtk::Box>(builder, "mode_buttons_box"), [&] (Gtk::Widget &item) {
79 auto &btn = dynamic_cast<Gtk::ToggleButton &>(item);
80 _mode_buttons.push_back(&btn);
81 btn.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &PencilToolbar::mode_changed), btn_index++));
83 });
84
85 // Configure LPE bspline spiro flatten button.
86 _flatten_spiro_bspline_btn.signal_clicked().connect([this] {
87 _flattenLPE<LivePathEffect::LPEBSpline, LivePathEffect::LPESpiro>();
88 });
89
90 int freehandMode = prefs->getInt(freehand_tool_name() + "/freehand-mode", 0);
91
92 // freehandMode range is (0,5] for the pen tool, (0,3] for the pencil tool
93 // freehandMode = 3 is an old way of signifying pressure, set it to 0.
94 _mode_buttons[freehandMode < _mode_buttons.size() ? freehandMode : 0]->set_active();
95
96 if (_tool_is_pencil) {
97 // Setup the spin buttons.
101
104
105 // Smoothing
107 {1, _("(many nodes, rough)")},
108 {10, _("(default)")},
109 {20, ""},
110 {30, ""},
111 {50, ""},
112 {75, ""},
113 {100, _("(few nodes, smooth)")}
114 });
115
116 // Configure usepressure button.
117 bool pressure = prefs->getBool("/tools/freehand/pencil/pressure", false);
118 _usepressure_btn.set_active(pressure);
119 _usepressure_btn.signal_toggled().connect(sigc::mem_fun(*this, &PencilToolbar::use_pencil_pressure));
120
121 // Powerstoke combo item.
123
124 // Configure LPE simplify based tolerance button.
125 _simplify_btn.set_active(prefs->getInt("/tools/freehand/pencil/simplify", 0));
126 _simplify_btn.signal_toggled().connect(sigc::mem_fun(*this, &PencilToolbar::simplify_lpe));
127
128 // Configure LPE simplify flatten button.
129 _flatten_simplify_btn.signal_clicked().connect([this] { _flattenLPE<LivePathEffect::LPESimplify>(); });
130 }
131
132 // Advanced shape options.
134
135 // Setup the spin buttons.
137
138 // Values auto-calculated.
140
142
144}
145
146void PencilToolbar::add_powerstroke_cap(Glib::RefPtr<Gtk::Builder> const &builder)
147{
148 // Powerstroke cap combo tool item.
150
151 auto store = Gtk::ListStore::create(columns);
152
153 for (auto item : std::vector<char const *>{C_("Cap", "Butt"), _("Square"), _("Round"), _("Peak"), _("Zero width")}) {
154 Gtk::TreeModel::Row row = *store->append();
155 row[columns.col_label] = item;
156 row[columns.col_sensitive] = true;
157 }
158
160 _("Caps"), _("Line endings when drawing with pressure-sensitive PowerPencil"), "Not Used", store));
161
162 auto prefs = Preferences::get();
163
164 int cap = prefs->getInt("/live_effects/powerstroke/powerpencilcap", 2);
165 _cap_item->set_active(cap);
167
168 _cap_item->signal_changed().connect(sigc::mem_fun(*this, &PencilToolbar::change_cap));
169
170 get_widget<Gtk::Box>(builder, "powerstroke_cap_box").append(*_cap_item);
171}
172
173void PencilToolbar::add_shape_option(Glib::RefPtr<Gtk::Builder> const &builder)
174{
176
177 auto store = Gtk::ListStore::create(columns);
178
179 std::vector<char const *> freehand_shape_dropdown_items_list = {(C_("Freehand shape", "None")),
180 _("Triangle in"),
181 _("Triangle out"),
182 _("Ellipse"),
183 _("From clipboard"),
184 _("Bend from clipboard"),
185 _("Last applied")};
186
187 for (auto item : freehand_shape_dropdown_items_list) {
188 Gtk::TreeModel::Row row = *store->append();
189 row[columns.col_label] = item;
190 row[columns.col_sensitive] = true;
191 }
192
193 _shape_item = Gtk::manage(
194 UI::Widget::ComboToolItem::create(_("Shape"), _("Shape of new paths drawn by this tool"), "Not Used", store));
196
197 int shape =
198 Preferences::get()->getInt(_tool_is_pencil ? "/tools/freehand/pencil/shape" : "/tools/freehand/pen/shape", 0);
199 _shape_item->set_active(shape);
200
201 _shape_item->signal_changed().connect(sigc::mem_fun(*this, &PencilToolbar::change_shape));
202 get_widget<Gtk::Box>(builder, "shape_box").append(*_shape_item);
203}
204
206 double default_value, ValueChangedMemFun value_changed_mem_fun)
207{
208 auto const prefs = Preferences::get();
209 auto const path = "/tools/freehand/pencil/" + name;
210 auto const val = prefs->getDouble(path, default_value);
211
212 auto adj = btn.get_adjustment();
213 adj->set_value(val);
214 adj->signal_value_changed().connect(sigc::mem_fun(*this, value_changed_mem_fun));
215
216 btn.setDefocusTarget(this);
217}
218
219void PencilToolbar::hide_extra_widgets(Glib::RefPtr<Gtk::Builder> const &builder)
220{
221 auto const pen_only_items = std::vector<Gtk::Widget *>{
222 &get_widget<Gtk::Widget>(builder, "zigzag_btn"),
223 &get_widget<Gtk::Widget>(builder, "paraxial_btn")
224 };
225
226 auto const pencil_only_items = std::vector<Gtk::Widget *>{
227 &get_widget<Gtk::Widget>(builder, "pencil_only_box")
228 };
229
230 for (auto child : pen_only_items) {
231 child->set_visible(!_tool_is_pencil);
232 }
233
234 for (auto child : pencil_only_items) {
235 child->set_visible(_tool_is_pencil);
236 }
237
238 // Elements must be hidden after being initially visible.
239 int freehandMode = Preferences::get()->getInt(freehand_tool_name() + "/freehand-mode", 0);
240
241 if (freehandMode != 1 && freehandMode != 2) {
242 _flatten_spiro_bspline_btn.set_visible(false);
243 }
244 if (_tool_is_pencil) {
246 }
247}
248
250
252{
254
255 if (_desktop) {
256 if (!_set_shape) {
257 int shape = Preferences::get()->getInt(freehand_tool_name() + "/shape", 0);
258 update_width_value(shape);
259 _set_shape = true;
260 }
261 }
262}
263
265{
266 Preferences::get()->setInt(freehand_tool_name() + "/freehand-mode", mode);
267
268 _flatten_spiro_bspline_btn.set_visible(mode == 1 || mode == 2);
269
270 bool visible = mode != 2;
271
272 _simplify_btn.set_visible(visible);
273 _flatten_simplify_btn.set_visible(visible && _simplify_btn.get_active());
274
275 // Recall, the PencilToolbar is also used as the PenToolbar with minor changes.
276 if (auto pt = dynamic_cast<Tools::PenTool *>(_desktop->getTool())) {
277 pt->setPolylineMode();
278 }
279}
280
281// This is used in generic functions below to share large portions of code between pen and pencil tool.
283{
284 return _tool_is_pencil ? "/tools/freehand/pencil" : "/tools/freehand/pen";
285}
286
288{
289 assert(_tool_is_pencil);
290
291 // quit if run by the attr_changed listener
292 if (_blocker.pending()) {
293 return;
294 }
295
296 Preferences::get()->setDouble("/tools/freehand/pencil/minpressure", _minpressure_item.get_adjustment()->get_value());
297}
298
300{
301 assert(_tool_is_pencil);
302
303 // quit if run by the attr_changed listener
304 if (_blocker.pending()) {
305 return;
306 }
307
308 Preferences::get()->setDouble("/tools/freehand/pencil/maxpressure", _maxpressure_item.get_adjustment()->get_value());
309}
310
312{
313 // quit if run by the attr_changed listener
314 if (_blocker.pending()) {
315 return;
316 }
317
318 auto prefs = Preferences::get();
319 auto selection = _desktop->getSelection();
320 auto lpeitem = cast<SPLPEItem>(selection->singleItem());
321 double width = _shapescale_item.get_adjustment()->get_value();
322
323 using namespace LivePathEffect;
324 switch (_shape_item->get_active()) {
327 prefs->setDouble("/live_effects/powerstroke/width", width);
328 if (lpeitem) {
329 if (auto effect = dynamic_cast<LPEPowerStroke *>(lpeitem->getFirstPathEffectOfType(POWERSTROKE))) {
330 auto points = effect->offset_points.data();
331 if (points.size() == 1) {
332 points[0].y() = width;
333 effect->offset_points.param_set_and_write_new_value(points);
334 }
335 }
336 }
337 break;
338 case Tools::ELLIPSE:
339 case Tools::CLIPBOARD:
340 // The scale of the clipboard isn't known, so getting it to the right size isn't possible.
341 prefs->setDouble("/live_effects/skeletal/width", width);
342 if (lpeitem) {
343 if (auto effect = dynamic_cast<LPEPatternAlongPath *>(lpeitem->getFirstPathEffectOfType(PATTERN_ALONG_PATH))) {
344 effect->prop_scale.param_set_value(width);
345 sp_lpe_item_update_patheffect(lpeitem, false, true);
346 }
347 }
348 break;
350 prefs->setDouble("/live_effects/bend_path/width", width);
351 if (lpeitem) {
352 if (auto effect = dynamic_cast<LPEBendPath *>(lpeitem->getFirstPathEffectOfType(BEND_PATH))) {
353 effect->prop_scale.param_set_value(width);
354 sp_lpe_item_update_patheffect(lpeitem, false, true);
355 }
356 }
357 break;
358 case Tools::NONE:
360 default:
361 break;
362 }
363}
364
366{
367 assert(_tool_is_pencil);
368
369 bool pressure = _usepressure_btn.get_active();
370 auto prefs = Preferences::get();
371 prefs->setBool("/tools/freehand/pencil/pressure", pressure);
372
373 _minpressure_box.set_visible(pressure);
374 _maxpressure_box.set_visible(pressure);
375 _cap_item->set_visible(pressure);
376 _shape_item->set_visible(!pressure);
377 _shapescale_box.set_visible(!pressure);
378
379 if (pressure) {
380 _simplify_btn.set_visible(false);
381 _flatten_spiro_bspline_btn.set_visible(false);
382 _flatten_simplify_btn.set_visible(false);
383 } else {
384 int freehandMode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0);
385 bool simplify_visible = freehandMode != 2;
386 _simplify_btn.set_visible(simplify_visible);
387 _flatten_simplify_btn.set_visible(simplify_visible && _simplify_btn.get_active());
388 _flatten_spiro_bspline_btn.set_visible(freehandMode == 1 || freehandMode == 2);
389 }
390
391 for (auto button : _mode_buttons) {
392 button->set_sensitive(!pressure);
393 }
394}
395
397{
398 Preferences::get()->setInt(freehand_tool_name() + "/shape", shape);
399 update_width_value(shape);
400}
401
403{
404 // Update shape width with correct width.
405 auto prefs = Preferences::get();
406 double width = 1.0;
407 _shapescale_item.set_sensitive(true);
408 double powerstrokedefsize = 10 / (0.265 * _desktop->getDocument()->getDocumentScale()[0] * 2.0);
409 switch (shape) {
412 width = prefs->getDouble("/live_effects/powerstroke/width", powerstrokedefsize);
413 break;
414 case Tools::ELLIPSE:
415 case Tools::CLIPBOARD:
416 width = prefs->getDouble("/live_effects/skeletal/width", 1.0);
417 break;
419 width = prefs->getDouble("/live_effects/bend_path/width", 1.0);
420 break;
421 case Tools::NONE: // Apply width from style?
423 default:
424 _shapescale_item.set_sensitive(false);
425 break;
426 }
427 _shapescale_item.get_adjustment()->set_value(width);
428}
429
431{
432 Preferences::get()->setInt("/live_effects/powerstroke/powerpencilcap", cap);
433}
434
436{
437 bool simplify = _simplify_btn.get_active();
438 Preferences::get()->setBool(freehand_tool_name() + "/simplify", simplify);
439 _flatten_simplify_btn.set_visible(simplify);
440}
441
442template <typename... T>
444{
445 for (auto const item : _desktop->getSelection()->items()) {
446 auto const shape = cast<SPShape>(item);
447 if (shape && shape->hasPathEffect()){
448 auto const lpelist = shape->getEffectList();
449 for (auto const i : lpelist) {
450 if (auto const lpeobj = i->lpeobject) {
451 auto const lpe = lpeobj->get_lpe();
452 if ((dynamic_cast<T const *>(lpe) || ...)) { // if lpe is any T
453 auto c = *shape->curveForEdit();
454 lpe->doEffect(c);
455 shape->setCurrentPathEffect(i);
456 if (lpelist.size() > 1) {
457 shape->removeCurrentPathEffect(true);
458 shape->setCurveBeforeLPE(std::move(c));
459 } else {
460 shape->removeCurrentPathEffect(false);
461 shape->setCurve(std::move(c));
462 }
463 sp_lpe_item_update_patheffect(shape, false, false);
464 break;
465 }
466 }
467 }
468 }
469 }
470}
471
473{
474 assert(_tool_is_pencil);
475
476 // quit if run by the attr_changed listener
477 if (_blocker.pending()) {
478 return;
479 }
480
481 auto const tol_pref = _tolerance_item.get_adjustment()->get_value();
482 auto const tol = tol_pref / (100.0 * (102.0 - tol_pref));
483 auto const tol_str = (std::ostringstream{} << tol).str();
484
485 {
486 // in turn, prevent listener from responding
487 auto guard = _blocker.block();
488 Preferences::get()->setDouble("/tools/freehand/pencil/tolerance", tol_pref);
489 }
490
491 for (auto const item : _desktop->getSelection()->items()) {
492 auto const lpeitem = cast<SPLPEItem>(item);
493 if (lpeitem && lpeitem->hasPathEffect()) {
494 if (auto const simplify = lpeitem->getFirstPathEffectOfType(LivePathEffect::SIMPLIFY)) {
495 if (auto const lpe_simplify = dynamic_cast<LivePathEffect::LPESimplify *>(simplify->getLPEObj()->get_lpe())) {
496
497 bool simplified = false;
498 if (auto const powerstroke = lpeitem->getFirstPathEffectOfType(LivePathEffect::POWERSTROKE)) {
499 if (auto const lpe_powerstroke = dynamic_cast<LivePathEffect::LPEPowerStroke *>(powerstroke->getLPEObj()->get_lpe())) {
500 lpe_powerstroke->getRepr()->setAttribute("is_visible", "false");
501 sp_lpe_item_update_patheffect(lpeitem, false, false);
502 if (auto const sp_shape = cast<SPShape>(lpeitem)) {
503 auto const previous_curve_length = sp_shape->curve()->curveCount();
504 lpe_simplify->getRepr()->setAttribute("threshold", tol_str);
505 sp_lpe_item_update_patheffect(lpeitem, false, false);
506 simplified = true;
507 auto const curve_length = sp_shape->curve()->curveCount();
508 auto const factor = (double)curve_length / previous_curve_length;
509 auto ts = lpe_powerstroke->offset_points.data();
510 for (auto &t : ts) {
511 t.x() *= factor;
512 }
513 lpe_powerstroke->offset_points.param_setValue(ts);
514 }
515 lpe_powerstroke->getRepr()->setAttribute("is_visible", "true");
516 sp_lpe_item_update_patheffect(lpeitem, false, false);
517 }
518 }
519
520 if (!simplified) {
521 lpe_simplify->getRepr()->setAttribute("threshold", tol_str);
522 }
523 }
524 }
525 }
526 }
527}
528
529} // namespace Inkscape::UI::Toolbar
530
531/*
532 Local Variables:
533 mode:c++
534 c-file-style:"stroustrup"
535 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
536 indent-tabs-mode:nil
537 fill-column:99
538 End:
539*/
540// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Gtk builder utilities.
Geom::IntRect visible
Definition canvas.cpp:154
Fragment store
Definition canvas.cpp:155
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:230
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setDouble(Glib::ustring const &pref_path, double value)
Set a floating point value.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
UI::Widget::SpinButton & _shapescale_item
UI::Widget::ComboToolItem * _shape_item
void setup_derived_spin_button(UI::Widget::SpinButton &btn, Glib::ustring const &name, double default_value, ValueChangedMemFun value_changed_mem_fun)
void hide_extra_widgets(Glib::RefPtr< Gtk::Builder > const &builder)
std::vector< Gtk::ToggleButton * > _mode_buttons
void setDesktop(SPDesktop *desktop) override
UI::Widget::SpinButton & _minpressure_item
void(PencilToolbar::*)() ValueChangedMemFun
UI::Widget::SpinButton & _tolerance_item
void add_shape_option(Glib::RefPtr< Gtk::Builder > const &builder)
UI::Widget::ComboToolItem * _cap_item
void add_powerstroke_cap(Glib::RefPtr< Gtk::Builder > const &builder)
UI::Widget::SpinButton & _maxpressure_item
Base class for all tool toolbars.
Definition toolbar.h:72
virtual void setDesktop(SPDesktop *desktop)
Definition toolbar.h:76
PenTool: a context for pen tool events.
Definition pen-tool.h:38
Gtk::TreeModelColumn< bool > col_sensitive
Gtk::TreeModelColumn< Glib::ustring > col_label
void use_group_label(bool use_group_label)
static ComboToolItem * create(const Glib::ustring &label, const Glib::ustring &tooltip, const Glib::ustring &stock_id, Glib::RefPtr< Gtk::ListStore > store, bool has_entry=false)
sigc::signal< void(int)> signal_changed()
SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMen...
Definition spinbutton.h:52
void setDefocusTarget(decltype(_defocus_target) target)
Definition spinbutton.h:127
void set_custom_numeric_menu_data(NumericMenuData &&custom_menu_data)
scoped_block block()
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
Geom::Scale getDocumentScale(bool computed=true) const
Returns document scale as defined by width/height (in pixels) and viewBox (real world to user-units).
Definition document.cpp:764
A combobox that can be displayed in a toolbar.
double c[8][4]
Editable view implementation.
SPItem * item
PowerStroke LPE effect, see lpe-powerstroke.cpp.
Definition desktop.h:50
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
Gtk::Widget * for_each_child(Gtk::Widget &widget, Func &&func, bool const plus_self=false, bool const recurse=false, int const level=0)
Call Func with a reference to each child of parent, until it returns _break.
Definition util.h:90
W & get_derived_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id, Args &&... args)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
int mode
PenTool: a context for pen tool events.
Pencil and pen toolbars.
Ocnode * child[8]
Definition quantize.cpp:33
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
SPDesktop * desktop
double width
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder