1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Inkscape Preferences dialog.
4 *
5 * Authors:
6 * Marco Scholten
7 * Bruno Dilly <bruno.dilly@gmail.com>
8 *
9 * Copyright (C) 2004, 2006, 2007 Authors
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14 #include <glibmm/i18n.h>
15 #include <glibmm/convert.h>
16 #include <glibmm/regex.h>
17
18 #include <gtkmm/box.h>
19 #include <gtkmm/frame.h>
20 #include <gtkmm/scale.h>
21 #include <gtkmm/table.h>
22
23
24 #include "desktop.h"
25 #include "inkscape.h"
26 #include "message-stack.h"
27 #include "preferences.h"
28 #include "selcue.h"
29 #include "selection-chemistry.h"
30 #include "verbs.h"
31
32 #include "include/gtkmm_version.h"
33
34 #include "io/sys.h"
35
36 #include "ui/dialog/filedialog.h"
37 #include "ui/icon-loader.h"
38 #include "ui/widget/preferences-widget.h"
39
40
41 #ifdef _WIN32
42 #include <windows.h>
43 #endif
44
45 using namespace Inkscape::UI::Widget;
46
47 namespace Inkscape {
48 namespace UI {
49 namespace Widget {
50
DialogPage()51 DialogPage::DialogPage()
52 {
53 set_border_width(12);
54
55 set_orientation(Gtk::ORIENTATION_VERTICAL);
56 set_column_spacing(12);
57 set_row_spacing(6);
58 }
59
60 /**
61 * Add a widget to the bottom row of the dialog page
62 *
63 * \param[in] indent Whether the widget should be indented by one column
64 * \param[in] label The label text for the widget
65 * \param[in] widget The widget to add to the page
66 * \param[in] suffix Text for an optional label at the right of the widget
67 * \param[in] tip Tooltip text for the widget
68 * \param[in] expand_widget Whether to expand the widget horizontally
69 * \param[in] other_widget An optional additional widget to display at the right of the first one
70 */
add_line(bool indent,Glib::ustring const & label,Gtk::Widget & widget,Glib::ustring const & suffix,const Glib::ustring & tip,bool expand_widget,Gtk::Widget * other_widget)71 void DialogPage::add_line(bool indent,
72 Glib::ustring const &label,
73 Gtk::Widget &widget,
74 Glib::ustring const &suffix,
75 const Glib::ustring &tip,
76 bool expand_widget,
77 Gtk::Widget *other_widget)
78 {
79 if (tip != "")
80 widget.set_tooltip_text (tip);
81
82 auto hb = Gtk::manage(new Gtk::Box());
83 hb->set_spacing(12);
84 hb->set_hexpand(true);
85 hb->pack_start(widget, expand_widget, expand_widget);
86
87 // Pack an additional widget into a box with the widget if desired
88 if (other_widget)
89 hb->pack_start(*other_widget, expand_widget, expand_widget);
90
91 hb->set_valign(Gtk::ALIGN_CENTER);
92
93 // Add a label in the first column if provided
94 if (label != "")
95 {
96 Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_START,
97 Gtk::ALIGN_CENTER, true));
98 label_widget->set_mnemonic_widget(widget);
99 label_widget->set_markup(label_widget->get_text());
100
101 if (indent) {
102 label_widget->set_margin_start(12);
103 }
104
105 label_widget->set_valign(Gtk::ALIGN_CENTER);
106 add(*label_widget);
107 attach_next_to(*hb, *label_widget, Gtk::POS_RIGHT, 1, 1);
108 }
109
110 // Now add the widget to the bottom of the dialog
111 if (label == "")
112 {
113 if (indent) {
114 hb->set_margin_start(12);
115 }
116
117 add(*hb);
118
119 GValue width = G_VALUE_INIT;
120 g_value_init(&width, G_TYPE_INT);
121 g_value_set_int(&width, 2);
122 gtk_container_child_set_property(GTK_CONTAINER(gobj()), GTK_WIDGET(hb->gobj()), "width", &width);
123 }
124
125 // Add a label on the right of the widget if desired
126 if (suffix != "")
127 {
128 Gtk::Label* suffix_widget = Gtk::manage(new Gtk::Label(suffix , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true));
129 suffix_widget->set_markup(suffix_widget->get_text());
130 hb->pack_start(*suffix_widget,false,false);
131 }
132
133 }
134
add_group_header(Glib::ustring name)135 void DialogPage::add_group_header(Glib::ustring name)
136 {
137 if (name != "")
138 {
139 Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(Glib::ustring(/*"<span size='large'>*/"<b>") + name +
140 Glib::ustring("</b>"/*</span>"*/) , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true));
141
142 label_widget->set_use_markup(true);
143 label_widget->set_valign(Gtk::ALIGN_CENTER);
144 add(*label_widget);
145 }
146 }
147
add_group_note(Glib::ustring name)148 void DialogPage::add_group_note(Glib::ustring name)
149 {
150 if (name != "")
151 {
152 Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(Glib::ustring("<i>") + name +
153 Glib::ustring("</i>") , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true));
154 label_widget->set_use_markup(true);
155 label_widget->set_valign(Gtk::ALIGN_CENTER);
156 label_widget->set_line_wrap(true);
157 label_widget->set_line_wrap_mode(Pango::WRAP_WORD);
158
159 add(*label_widget);
160 GValue width = G_VALUE_INIT;
161 g_value_init(&width, G_TYPE_INT);
162 g_value_set_int(&width, 2);
163 gtk_container_child_set_property(GTK_CONTAINER(gobj()), GTK_WIDGET(label_widget->gobj()), "width", &width);
164 }
165 }
166
set_tip(Gtk::Widget & widget,Glib::ustring const & tip)167 void DialogPage::set_tip(Gtk::Widget& widget, Glib::ustring const &tip)
168 {
169 widget.set_tooltip_text (tip);
170 }
171
init(Glib::ustring const & label,Glib::ustring const & prefs_path,bool default_value)172 void PrefCheckButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
173 bool default_value)
174 {
175 _prefs_path = prefs_path;
176 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
177 this->set_label(label);
178 this->set_active( prefs->getBool(_prefs_path, default_value) );
179 }
180
on_toggled()181 void PrefCheckButton::on_toggled()
182 {
183 if (this->get_visible()) //only take action if the user toggled it
184 {
185 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
186 prefs->setBool(_prefs_path, this->get_active());
187 }
188 this->changed_signal.emit(this->get_active());
189 }
190
init(Glib::ustring const & label,Glib::ustring const & prefs_path,Glib::ustring const & string_value,bool default_value,PrefRadioButton * group_member)191 void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
192 Glib::ustring const &string_value, bool default_value, PrefRadioButton* group_member)
193 {
194 _prefs_path = prefs_path;
195 _value_type = VAL_STRING;
196 _string_value = string_value;
197 (void)default_value;
198 this->set_label(label);
199 if (group_member)
200 {
201 Gtk::RadioButtonGroup rbg = group_member->get_group();
202 this->set_group(rbg);
203 }
204 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
205 Glib::ustring val = prefs->getString(_prefs_path);
206 if ( !val.empty() )
207 this->set_active(val == _string_value);
208 else
209 this->set_active( false );
210 }
211
init(Glib::ustring const & label,Glib::ustring const & prefs_path,int int_value,bool default_value,PrefRadioButton * group_member)212 void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
213 int int_value, bool default_value, PrefRadioButton* group_member)
214 {
215 _prefs_path = prefs_path;
216 _value_type = VAL_INT;
217 _int_value = int_value;
218 this->set_label(label);
219 if (group_member)
220 {
221 Gtk::RadioButtonGroup rbg = group_member->get_group();
222 this->set_group(rbg);
223 }
224 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
225 if (default_value)
226 this->set_active( prefs->getInt(_prefs_path, int_value) == _int_value );
227 else
228 this->set_active( prefs->getInt(_prefs_path, int_value + 1) == _int_value );
229 }
230
on_toggled()231 void PrefRadioButton::on_toggled()
232 {
233 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
234
235 if (this->get_visible() && this->get_active() ) //only take action if toggled by user (to active)
236 {
237 if ( _value_type == VAL_STRING )
238 prefs->setString(_prefs_path, _string_value);
239 else if ( _value_type == VAL_INT )
240 prefs->setInt(_prefs_path, _int_value);
241 }
242 this->changed_signal.emit(this->get_active());
243 }
244
init(Glib::ustring const & prefs_path,double lower,double upper,double step_increment,double,double default_value,bool is_int,bool is_percent)245 void PrefSpinButton::init(Glib::ustring const &prefs_path,
246 double lower, double upper, double step_increment, double /*page_increment*/,
247 double default_value, bool is_int, bool is_percent)
248 {
249 _prefs_path = prefs_path;
250 _is_int = is_int;
251 _is_percent = is_percent;
252 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
253 double value;
254 if (is_int) {
255 if (is_percent) {
256 value = 100 * prefs->getDoubleLimited(prefs_path, default_value, lower/100.0, upper/100.0);
257 } else {
258 value = (double) prefs->getIntLimited(prefs_path, (int) default_value, (int) lower, (int) upper);
259 }
260 } else {
261 value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper);
262 }
263
264 this->set_range (lower, upper);
265 this->set_increments (step_increment, 0);
266 this->set_value (value);
267 this->set_width_chars(6);
268 if (is_int)
269 this->set_digits(0);
270 else if (step_increment < 0.1)
271 this->set_digits(4);
272 else
273 this->set_digits(2);
274
275 }
276
on_value_changed()277 void PrefSpinButton::on_value_changed()
278 {
279 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
280 if (this->get_visible()) //only take action if user changed value
281 {
282 if (_is_int) {
283 if (_is_percent) {
284 prefs->setDouble(_prefs_path, this->get_value()/100.0);
285 } else {
286 prefs->setInt(_prefs_path, (int) this->get_value());
287 }
288 } else {
289 prefs->setDouble(_prefs_path, this->get_value());
290 }
291 }
292 this->changed_signal.emit(this->get_value());
293 }
294
init(Glib::ustring const & prefs_path,double lower,double upper,double step_increment,double default_value,UnitType unit_type,Glib::ustring const & default_unit)295 void PrefSpinUnit::init(Glib::ustring const &prefs_path,
296 double lower, double upper, double step_increment,
297 double default_value, UnitType unit_type, Glib::ustring const &default_unit)
298 {
299 _prefs_path = prefs_path;
300 _is_percent = (unit_type == UNIT_TYPE_DIMENSIONLESS);
301
302 resetUnitType(unit_type);
303 setUnit(default_unit);
304 setRange (lower, upper); /// @fixme this disregards changes of units
305 setIncrements (step_increment, 0);
306 if (step_increment < 0.1) {
307 setDigits(4);
308 } else {
309 setDigits(2);
310 }
311
312 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
313 double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper);
314 Glib::ustring unitstr = prefs->getUnit(prefs_path);
315 if (unitstr.length() == 0) {
316 unitstr = default_unit;
317 // write the assumed unit to preferences:
318 prefs->setDoubleUnit(_prefs_path, value, unitstr);
319 }
320 setValue(value, unitstr);
321
322 signal_value_changed().connect_notify(sigc::mem_fun(*this, &PrefSpinUnit::on_my_value_changed));
323 }
324
on_my_value_changed()325 void PrefSpinUnit::on_my_value_changed()
326 {
327 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
328 if (getWidget()->get_visible()) //only take action if user changed value
329 {
330 prefs->setDoubleUnit(_prefs_path, getValue(getUnit()->abbr), getUnit()->abbr);
331 }
332 }
333
334 const double ZoomCorrRuler::textsize = 7;
335 const double ZoomCorrRuler::textpadding = 5;
336
ZoomCorrRuler(int width,int height)337 ZoomCorrRuler::ZoomCorrRuler(int width, int height) :
338 _unitconv(1.0),
339 _border(5)
340 {
341 set_size(width, height);
342 }
343
set_size(int x,int y)344 void ZoomCorrRuler::set_size(int x, int y)
345 {
346 _min_width = x;
347 _height = y;
348 set_size_request(x + _border*2, y + _border*2);
349 }
350
351 // The following two functions are borrowed from 2geom's toy-framework-2; if they are useful in
352 // other locations, we should perhaps make them (or adapted versions of them) publicly available
353 static void
draw_text(cairo_t * cr,Geom::Point loc,const char * txt,bool bottom=false,double fontsize=ZoomCorrRuler::textsize,std::string fontdesc="Sans")354 draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom = false,
355 double fontsize = ZoomCorrRuler::textsize, std::string fontdesc = "Sans") {
356 PangoLayout* layout = pango_cairo_create_layout (cr);
357 pango_layout_set_text(layout, txt, -1);
358
359 // set font and size
360 std::ostringstream sizestr;
361 sizestr << fontsize;
362 fontdesc = fontdesc + " " + sizestr.str();
363 PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc.c_str());
364 pango_layout_set_font_description(layout, font_desc);
365 pango_font_description_free (font_desc);
366
367 PangoRectangle logical_extent;
368 pango_layout_get_pixel_extents(layout, nullptr, &logical_extent);
369 cairo_move_to(cr, loc[Geom::X], loc[Geom::Y] - (bottom ? logical_extent.height : 0));
370 pango_cairo_show_layout(cr, layout);
371 }
372
373 static void
draw_number(cairo_t * cr,Geom::Point pos,double num)374 draw_number(cairo_t *cr, Geom::Point pos, double num) {
375 std::ostringstream number;
376 number << num;
377 draw_text(cr, pos, number.str().c_str(), true);
378 }
379
380 /*
381 * \arg dist The distance between consecutive minor marks
382 * \arg major_interval Number of marks after which to draw a major mark
383 */
384 void
draw_marks(Cairo::RefPtr<Cairo::Context> cr,double dist,int major_interval)385 ZoomCorrRuler::draw_marks(Cairo::RefPtr<Cairo::Context> cr, double dist, int major_interval) {
386 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
387 const double zoomcorr = prefs->getDouble("/options/zoomcorrection/value", 1.0);
388 double mark = 0;
389 int i = 0;
390 while (mark <= _drawing_width) {
391 cr->move_to(mark, _height);
392 if ((i % major_interval) == 0) {
393 // major mark
394 cr->line_to(mark, 0);
395 Geom::Point textpos(mark + 3, ZoomCorrRuler::textsize + ZoomCorrRuler::textpadding);
396 draw_number(cr->cobj(), textpos, dist * i);
397 } else {
398 // minor mark
399 cr->line_to(mark, ZoomCorrRuler::textsize + 2 * ZoomCorrRuler::textpadding);
400 }
401 mark += dist * zoomcorr / _unitconv;
402 ++i;
403 }
404 }
405
406 bool
on_draw(const Cairo::RefPtr<Cairo::Context> & cr)407 ZoomCorrRuler::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
408 Glib::RefPtr<Gdk::Window> window = get_window();
409
410 int w = window->get_width();
411 _drawing_width = w - _border * 2;
412
413 cr->set_source_rgb(1.0, 1.0, 1.0);
414 cr->set_fill_rule(Cairo::FILL_RULE_WINDING);
415 cr->rectangle(0, 0, w, _height + _border*2);
416 cr->fill();
417
418 cr->set_source_rgb(0.0, 0.0, 0.0);
419 cr->set_line_width(0.5);
420
421 cr->translate(_border, _border); // so that we have a small white border around the ruler
422 cr->move_to (0, _height);
423 cr->line_to (_drawing_width, _height);
424
425 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
426 Glib::ustring abbr = prefs->getString("/options/zoomcorrection/unit");
427 if (abbr == "cm") {
428 draw_marks(cr, 0.1, 10);
429 } else if (abbr == "in") {
430 draw_marks(cr, 0.25, 4);
431 } else if (abbr == "mm") {
432 draw_marks(cr, 10, 10);
433 } else if (abbr == "pc") {
434 draw_marks(cr, 1, 10);
435 } else if (abbr == "pt") {
436 draw_marks(cr, 10, 10);
437 } else if (abbr == "px") {
438 draw_marks(cr, 10, 10);
439 } else {
440 draw_marks(cr, 1, 1);
441 }
442 cr->stroke();
443
444 return true;
445 }
446
447
448 void
on_slider_value_changed()449 ZoomCorrRulerSlider::on_slider_value_changed()
450 {
451 if (this->get_visible() || freeze) //only take action if user changed value
452 {
453 freeze = true;
454 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
455 prefs->setDouble("/options/zoomcorrection/value", _slider->get_value() / 100.0);
456 _sb->set_value(_slider->get_value());
457 _ruler.queue_draw();
458 freeze = false;
459 }
460 }
461
462 void
on_spinbutton_value_changed()463 ZoomCorrRulerSlider::on_spinbutton_value_changed()
464 {
465 if (this->get_visible() || freeze) //only take action if user changed value
466 {
467 freeze = true;
468 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
469 prefs->setDouble("/options/zoomcorrection/value", _sb->get_value() / 100.0);
470 _slider->set_value(_sb->get_value());
471 _ruler.queue_draw();
472 freeze = false;
473 }
474 }
475
476 void
on_unit_changed()477 ZoomCorrRulerSlider::on_unit_changed() {
478 if (!_unit.get_sensitive()) {
479 // when the unit menu is initialized, the unit is set to the default but
480 // it needs to be reset later so we don't perform the change in this case
481 return;
482 }
483 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
484 prefs->setString("/options/zoomcorrection/unit", _unit.getUnitAbbr());
485 double conv = _unit.getConversion(_unit.getUnitAbbr(), "px");
486 _ruler.set_unit_conversion(conv);
487 if (_ruler.get_visible()) {
488 _ruler.queue_draw();
489 }
490 }
491
on_mnemonic_activate(bool group_cycling)492 bool ZoomCorrRulerSlider::on_mnemonic_activate ( bool group_cycling )
493 {
494 return _sb->mnemonic_activate ( group_cycling );
495 }
496
497
498 void
init(int ruler_width,int ruler_height,double lower,double upper,double step_increment,double page_increment,double default_value)499 ZoomCorrRulerSlider::init(int ruler_width, int ruler_height, double lower, double upper,
500 double step_increment, double page_increment, double default_value)
501 {
502 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
503 double value = prefs->getDoubleLimited("/options/zoomcorrection/value", default_value, lower, upper) * 100.0;
504
505 freeze = false;
506
507 _ruler.set_size(ruler_width, ruler_height);
508
509 _slider = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL));
510
511 _slider->set_size_request(_ruler.width(), -1);
512 _slider->set_range (lower, upper);
513 _slider->set_increments (step_increment, page_increment);
514 _slider->set_value (value);
515 _slider->set_digits(2);
516
517 _slider->signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_slider_value_changed));
518 _sb = Gtk::manage(new Inkscape::UI::Widget::SpinButton());
519 _sb->signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_spinbutton_value_changed));
520 _unit.signal_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_unit_changed));
521
522 _sb->set_range (lower, upper);
523 _sb->set_increments (step_increment, 0);
524 _sb->set_value (value);
525 _sb->set_digits(2);
526 _sb->set_halign(Gtk::ALIGN_CENTER);
527 _sb->set_valign(Gtk::ALIGN_END);
528
529 _unit.set_sensitive(false);
530 _unit.setUnitType(UNIT_TYPE_LINEAR);
531 _unit.set_sensitive(true);
532 _unit.setUnit(prefs->getString("/options/zoomcorrection/unit"));
533 _unit.set_halign(Gtk::ALIGN_CENTER);
534 _unit.set_valign(Gtk::ALIGN_END);
535
536 _slider->set_hexpand(true);
537 _ruler.set_hexpand(true);
538 auto table = Gtk::manage(new Gtk::Grid());
539 table->attach(*_slider, 0, 0, 1, 1);
540 table->attach(*_sb, 1, 0, 1, 1);
541 table->attach(_ruler, 0, 1, 1, 1);
542 table->attach(_unit, 1, 1, 1, 1);
543
544 pack_start(*table, Gtk::PACK_SHRINK);
545 }
546
547 void
on_slider_value_changed()548 PrefSlider::on_slider_value_changed()
549 {
550 if (this->get_visible() || freeze) //only take action if user changed value
551 {
552 freeze = true;
553 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
554 prefs->setDouble(_prefs_path, _slider->get_value());
555 _sb->set_value(_slider->get_value());
556 freeze = false;
557 }
558 }
559
560 void
on_spinbutton_value_changed()561 PrefSlider::on_spinbutton_value_changed()
562 {
563 if (this->get_visible() || freeze) //only take action if user changed value
564 {
565 freeze = true;
566 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
567 prefs->setDouble(_prefs_path, _sb->get_value());
568 _slider->set_value(_sb->get_value());
569 freeze = false;
570 }
571 }
572
on_mnemonic_activate(bool group_cycling)573 bool PrefSlider::on_mnemonic_activate ( bool group_cycling )
574 {
575 return _sb->mnemonic_activate ( group_cycling );
576 }
577
578 void
init(Glib::ustring const & prefs_path,double lower,double upper,double step_increment,double page_increment,double default_value,int digits)579 PrefSlider::init(Glib::ustring const &prefs_path,
580 double lower, double upper, double step_increment, double page_increment, double default_value, int digits)
581 {
582 _prefs_path = prefs_path;
583
584 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
585 double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper);
586
587 freeze = false;
588
589 _slider = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL));
590
591 _slider->set_range (lower, upper);
592 _slider->set_increments (step_increment, page_increment);
593 _slider->set_value (value);
594 _slider->set_digits(digits);
595 _slider->signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_slider_value_changed));
596 _sb = Gtk::manage(new Inkscape::UI::Widget::SpinButton());
597 _sb->signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_spinbutton_value_changed));
598 _sb->set_range (lower, upper);
599 _sb->set_increments (step_increment, 0);
600 _sb->set_value (value);
601 _sb->set_digits(digits);
602 _sb->set_halign(Gtk::ALIGN_CENTER);
603 _sb->set_valign(Gtk::ALIGN_END);
604
605 auto table = Gtk::manage(new Gtk::Grid());
606 _slider->set_hexpand();
607 table->attach(*_slider, 0, 0, 1, 1);
608 table->attach(*_sb, 1, 0, 1, 1);
609
610 this->pack_start(*table, Gtk::PACK_EXPAND_WIDGET);
611 }
612
init(Glib::ustring const & prefs_path,Glib::ustring labels[],int values[],int num_items,int default_value)613 void PrefCombo::init(Glib::ustring const &prefs_path,
614 Glib::ustring labels[], int values[], int num_items, int default_value)
615 {
616 _prefs_path = prefs_path;
617 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
618 int row = 0;
619 int value = prefs->getInt(_prefs_path, default_value);
620
621 for (int i = 0 ; i < num_items; ++i)
622 {
623 this->append(labels[i]);
624 _values.push_back(values[i]);
625 if (value == values[i])
626 row = i;
627 }
628 this->set_active(row);
629 }
630
init(Glib::ustring const & prefs_path,Glib::ustring labels[],Glib::ustring values[],int num_items,Glib::ustring default_value)631 void PrefCombo::init(Glib::ustring const &prefs_path,
632 Glib::ustring labels[], Glib::ustring values[], int num_items, Glib::ustring default_value)
633 {
634 _prefs_path = prefs_path;
635 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
636 int row = 0;
637 Glib::ustring value = prefs->getString(_prefs_path);
638 if(value.empty())
639 {
640 value = default_value;
641 }
642
643 for (int i = 0 ; i < num_items; ++i)
644 {
645 this->append(labels[i]);
646 _ustr_values.push_back(values[i]);
647 if (value == values[i])
648 row = i;
649 }
650 this->set_active(row);
651 }
652
init(Glib::ustring const & prefs_path,std::vector<Glib::ustring> labels,std::vector<int> values,int default_value)653 void PrefCombo::init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, std::vector<int> values,
654 int default_value)
655 {
656 size_t labels_size = labels.size();
657 size_t values_size = values.size();
658 if (values_size != labels_size) {
659 std::cout << "PrefCombo::"
660 << "Different number of values/labels in " << prefs_path << std::endl;
661 return;
662 }
663 _prefs_path = prefs_path;
664 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
665 int row = 0;
666 int value = prefs->getInt(_prefs_path, default_value);
667
668 for (int i = 0; i < labels_size; ++i) {
669 this->append(labels[i]);
670 _values.push_back(values[i]);
671 if (value == values[i])
672 row = i;
673 }
674 this->set_active(row);
675 }
676
init(Glib::ustring const & prefs_path,std::vector<Glib::ustring> labels,std::vector<Glib::ustring> values,Glib::ustring default_value)677 void PrefCombo::init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels,
678 std::vector<Glib::ustring> values, Glib::ustring default_value)
679 {
680 size_t labels_size = labels.size();
681 size_t values_size = values.size();
682 if (values_size != labels_size) {
683 std::cout << "PrefCombo::"
684 << "Different number of values/labels in " << prefs_path << std::endl;
685 return;
686 }
687 _prefs_path = prefs_path;
688 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
689 int row = 0;
690 Glib::ustring value = prefs->getString(_prefs_path);
691 if (value.empty()) {
692 value = default_value;
693 }
694
695 for (int i = 0; i < labels_size; ++i) {
696 this->append(labels[i]);
697 _ustr_values.push_back(values[i]);
698 if (value == values[i])
699 row = i;
700 }
701 this->set_active(row);
702 }
703
init(Glib::ustring const & prefs_path,std::vector<std::pair<Glib::ustring,Glib::ustring>> labels_and_values,Glib::ustring default_value)704 void PrefCombo::init(Glib::ustring const &prefs_path,
705 std::vector<std::pair<Glib::ustring, Glib::ustring>> labels_and_values,
706 Glib::ustring default_value)
707 {
708 _prefs_path = prefs_path;
709 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
710 Glib::ustring value = prefs->getString(_prefs_path);
711 if (value.empty()) {
712 value = default_value;
713 }
714
715 int row = 0;
716 int i = 0;
717 for (auto entry : labels_and_values) {
718 this->append(entry.first);
719 _ustr_values.push_back(entry.second);
720 if (value == entry.second) {
721 row = i;
722 }
723 ++i;
724 }
725 this->set_active(row);
726 }
727
on_changed()728 void PrefCombo::on_changed()
729 {
730 if (this->get_visible()) //only take action if user changed value
731 {
732 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
733 if(!_values.empty())
734 {
735 prefs->setInt(_prefs_path, _values[this->get_active_row_number()]);
736 }
737 else
738 {
739 prefs->setString(_prefs_path, _ustr_values[this->get_active_row_number()]);
740 }
741 }
742 }
743
init(Glib::ustring const & prefs_path,bool visibility,Glib::ustring const & default_string)744 void PrefEntryButtonHBox::init(Glib::ustring const &prefs_path,
745 bool visibility, Glib::ustring const &default_string)
746 {
747 _prefs_path = prefs_path;
748 _default_string = default_string;
749 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
750 relatedEntry = new Gtk::Entry();
751 relatedButton = new Gtk::Button(_("Reset"));
752 relatedEntry->set_invisible_char('*');
753 relatedEntry->set_visibility(visibility);
754 relatedEntry->set_text(prefs->getString(_prefs_path));
755 this->pack_start(*relatedEntry);
756 this->pack_start(*relatedButton);
757 relatedButton->signal_clicked().connect(
758 sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedButtonClickedCallback));
759 relatedEntry->signal_changed().connect(
760 sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedEntryChangedCallback));
761 }
762
onRelatedEntryChangedCallback()763 void PrefEntryButtonHBox::onRelatedEntryChangedCallback()
764 {
765 if (this->get_visible()) //only take action if user changed value
766 {
767 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
768 prefs->setString(_prefs_path, relatedEntry->get_text());
769 }
770 }
771
onRelatedButtonClickedCallback()772 void PrefEntryButtonHBox::onRelatedButtonClickedCallback()
773 {
774 if (this->get_visible()) //only take action if user changed value
775 {
776 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
777 prefs->setString(_prefs_path, _default_string);
778 relatedEntry->set_text(_default_string);
779 }
780 }
781
on_mnemonic_activate(bool group_cycling)782 bool PrefEntryButtonHBox::on_mnemonic_activate ( bool group_cycling )
783 {
784 return relatedEntry->mnemonic_activate ( group_cycling );
785 }
786
init(Glib::ustring const & prefs_path,bool visibility)787 void PrefEntryFileButtonHBox::init(Glib::ustring const &prefs_path,
788 bool visibility)
789 {
790 _prefs_path = prefs_path;
791 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
792
793 relatedEntry = new Gtk::Entry();
794 relatedEntry->set_invisible_char('*');
795 relatedEntry->set_visibility(visibility);
796 relatedEntry->set_text(prefs->getString(_prefs_path));
797
798 relatedButton = new Gtk::Button();
799 Gtk::Box* pixlabel = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 3);
800 Gtk::Image *im = sp_get_icon_image("applications-graphics", Gtk::ICON_SIZE_BUTTON);
801 pixlabel->pack_start(*im);
802 Gtk::Label *l = new Gtk::Label();
803 l->set_markup_with_mnemonic(_("_Browse..."));
804 pixlabel->pack_start(*l);
805 relatedButton->add(*pixlabel);
806
807 this->pack_end(*relatedButton, false, false, 4);
808 this->pack_start(*relatedEntry, true, true, 0);
809
810 relatedButton->signal_clicked().connect(
811 sigc::mem_fun(*this, &PrefEntryFileButtonHBox::onRelatedButtonClickedCallback));
812 relatedEntry->signal_changed().connect(
813 sigc::mem_fun(*this, &PrefEntryFileButtonHBox::onRelatedEntryChangedCallback));
814 }
815
onRelatedEntryChangedCallback()816 void PrefEntryFileButtonHBox::onRelatedEntryChangedCallback()
817 {
818 if (this->get_visible()) //only take action if user changed value
819 {
820 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
821 prefs->setString(_prefs_path, relatedEntry->get_text());
822 }
823 }
824
825 static Inkscape::UI::Dialog::FileOpenDialog * selectPrefsFileInstance = nullptr;
826
onRelatedButtonClickedCallback()827 void PrefEntryFileButtonHBox::onRelatedButtonClickedCallback()
828 {
829 if (this->get_visible()) //only take action if user changed value
830 {
831 //# Get the current directory for finding files
832 static Glib::ustring open_path;
833 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
834
835
836 Glib::ustring attr = prefs->getString(_prefs_path);
837 if (!attr.empty()) open_path = attr;
838
839 //# Test if the open_path directory exists
840 if (!Inkscape::IO::file_test(open_path.c_str(),
841 (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
842 open_path = "";
843
844 #ifdef _WIN32
845 //# If no open path, default to our win32 documents folder
846 if (open_path.empty())
847 {
848 // The path to the My Documents folder is read from the
849 // value "HKEY_CURRENT_USER\Software\Windows\CurrentVersion\Explorer\Shell Folders\Personal"
850 HKEY key = NULL;
851 if(RegOpenKeyExA(HKEY_CURRENT_USER,
852 "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
853 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
854 {
855 WCHAR utf16path[_MAX_PATH];
856 DWORD value_type;
857 DWORD data_size = sizeof(utf16path);
858 if(RegQueryValueExW(key, L"Personal", NULL, &value_type,
859 (BYTE*)utf16path, &data_size) == ERROR_SUCCESS)
860 {
861 g_assert(value_type == REG_SZ);
862 gchar *utf8path = g_utf16_to_utf8(
863 (const gunichar2*)utf16path, -1, NULL, NULL, NULL);
864 if(utf8path)
865 {
866 open_path = Glib::ustring(utf8path);
867 g_free(utf8path);
868 }
869 }
870 }
871 }
872 #endif
873
874 //# If no open path, default to our home directory
875 if (open_path.empty())
876 {
877 open_path = g_get_home_dir();
878 open_path.append(G_DIR_SEPARATOR_S);
879 }
880
881 //# Create a dialog
882 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
883 if (!selectPrefsFileInstance) {
884 selectPrefsFileInstance =
885 Inkscape::UI::Dialog::FileOpenDialog::create(
886 *desktop->getToplevel(),
887 open_path,
888 Inkscape::UI::Dialog::EXE_TYPES,
889 _("Select a bitmap editor"));
890 }
891
892 //# Show the dialog
893 bool const success = selectPrefsFileInstance->show();
894
895 if (!success) {
896 return;
897 }
898
899 //# User selected something. Get name and type
900 Glib::ustring fileName = selectPrefsFileInstance->getFilename();
901
902 if (!fileName.empty())
903 {
904 Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
905
906 if ( newFileName.size() > 0)
907 open_path = newFileName;
908 else
909 g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
910
911 prefs->setString(_prefs_path, open_path);
912 }
913
914 relatedEntry->set_text(fileName);
915 }
916 }
917
on_mnemonic_activate(bool group_cycling)918 bool PrefEntryFileButtonHBox::on_mnemonic_activate ( bool group_cycling )
919 {
920 return relatedEntry->mnemonic_activate ( group_cycling );
921 }
922
init(Glib::ustring const & entry_string,Glib::ustring const & tooltip)923 void PrefOpenFolder::init(Glib::ustring const &entry_string, Glib::ustring const &tooltip)
924 {
925 relatedEntry = new Gtk::Entry();
926 relatedButton = new Gtk::Button();
927 Gtk::Box *pixlabel = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 3);
928 Gtk::Image *im = sp_get_icon_image("document-open", Gtk::ICON_SIZE_BUTTON);
929 pixlabel->pack_start(*im);
930 Gtk::Label *l = new Gtk::Label();
931 l->set_markup_with_mnemonic(_("Open"));
932 pixlabel->pack_start(*l);
933 relatedButton->add(*pixlabel);
934 relatedButton->set_tooltip_text(tooltip);
935 relatedEntry->set_text(entry_string);
936 relatedEntry->set_sensitive(false);
937 this->pack_end(*relatedButton, false, false, 4);
938 this->pack_start(*relatedEntry, true, true, 0);
939 relatedButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefOpenFolder::onRelatedButtonClickedCallback));
940 }
941
onRelatedButtonClickedCallback()942 void PrefOpenFolder::onRelatedButtonClickedCallback()
943 {
944 g_mkdir_with_parents(relatedEntry->get_text().c_str(), 0700);
945 // https://stackoverflow.com/questions/42442189/how-to-open-spawn-a-file-with-glib-gtkmm-in-windows
946 #ifdef _WIN32
947 ShellExecute(NULL, "open", relatedEntry->get_text().c_str(), NULL, NULL, SW_SHOWDEFAULT);
948 #elif defined(__APPLE__)
949 std::vector<std::string> argv = { "open", relatedEntry->get_text().raw() };
950 Glib::spawn_async("", argv, Glib::SpawnFlags::SPAWN_SEARCH_PATH);
951 #else
952 gchar *path = g_filename_to_uri(relatedEntry->get_text().c_str(), NULL, NULL);
953 std::vector<std::string> argv = { "xdg-open", path };
954 Glib::spawn_async("", argv, Glib::SpawnFlags::SPAWN_SEARCH_PATH);
955 g_free(path);
956 #endif
957 }
958
init(Glib::ustring const & prefs_path)959 void PrefFileButton::init(Glib::ustring const &prefs_path)
960 {
961 _prefs_path = prefs_path;
962 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
963 select_filename(Glib::filename_from_utf8(prefs->getString(_prefs_path)));
964
965 signal_selection_changed().connect(sigc::mem_fun(*this, &PrefFileButton::onFileChanged));
966 }
967
onFileChanged()968 void PrefFileButton::onFileChanged()
969 {
970 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
971 prefs->setString(_prefs_path, Glib::filename_to_utf8(get_filename()));
972 }
973
init(Glib::ustring const & prefs_path,bool visibility)974 void PrefEntry::init(Glib::ustring const &prefs_path, bool visibility)
975 {
976 _prefs_path = prefs_path;
977 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
978 this->set_invisible_char('*');
979 this->set_visibility(visibility);
980 this->set_text(prefs->getString(_prefs_path));
981 }
982
on_changed()983 void PrefEntry::on_changed()
984 {
985 if (this->get_visible()) //only take action if user changed value
986 {
987 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
988 prefs->setString(_prefs_path, this->get_text());
989 }
990 }
991
init(Glib::ustring const & prefs_path,int height)992 void PrefMultiEntry::init(Glib::ustring const &prefs_path, int height)
993 {
994 // TODO: Figure out if there's a way to specify height in lines instead of px
995 // and how to obtain a reasonable default width if 'expand_widget' is not used
996 set_size_request(100, height);
997 set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
998 set_shadow_type(Gtk::SHADOW_IN);
999
1000 add(_text);
1001
1002 _prefs_path = prefs_path;
1003 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1004 Glib::ustring value = prefs->getString(_prefs_path);
1005 value = Glib::Regex::create("\\|")->replace_literal(value, 0, "\n", (Glib::RegexMatchFlags)0);
1006 _text.get_buffer()->set_text(value);
1007 _text.get_buffer()->signal_changed().connect(sigc::mem_fun(*this, &PrefMultiEntry::on_changed));
1008 }
1009
on_changed()1010 void PrefMultiEntry::on_changed()
1011 {
1012 if (get_visible()) //only take action if user changed value
1013 {
1014 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1015 Glib::ustring value = _text.get_buffer()->get_text();
1016 value = Glib::Regex::create("\\n")->replace_literal(value, 0, "|", (Glib::RegexMatchFlags)0);
1017 prefs->setString(_prefs_path, value);
1018 }
1019 }
1020
init(Glib::ustring const & label,Glib::ustring const & prefs_path,guint32 default_rgba)1021 void PrefColorPicker::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
1022 guint32 default_rgba)
1023 {
1024 _prefs_path = prefs_path;
1025 _title = label;
1026 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1027 this->setRgba32( prefs->getInt(_prefs_path, (int)default_rgba) );
1028 }
1029
on_changed(guint32 rgba)1030 void PrefColorPicker::on_changed (guint32 rgba)
1031 {
1032 if (this->get_visible()) //only take action if the user toggled it
1033 {
1034 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1035 prefs->setInt(_prefs_path, (int) rgba);
1036 }
1037 }
1038
init(Glib::ustring const & prefs_path)1039 void PrefUnit::init(Glib::ustring const &prefs_path)
1040 {
1041 _prefs_path = prefs_path;
1042 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1043 setUnitType(UNIT_TYPE_LINEAR);
1044 setUnit(prefs->getString(_prefs_path));
1045 }
1046
on_changed()1047 void PrefUnit::on_changed()
1048 {
1049 if (this->get_visible()) //only take action if user changed value
1050 {
1051 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1052 prefs->setString(_prefs_path, getUnitAbbr());
1053 }
1054 }
1055
1056 } // namespace Widget
1057 } // namespace UI
1058 } // namespace Inkscape
1059
1060 /*
1061 Local Variables:
1062 mode:c++
1063 c-file-style:"stroustrup"
1064 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1065 indent-tabs-mode:nil
1066 fill-column:99
1067 End:
1068 */
1069 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1070