Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
preferences-widget.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/*
7 * Authors:
8 * Marco Scholten
9 * Bruno Dilly <bruno.dilly@gmail.com>
10 *
11 * Copyright (C) 2004, 2006, 2007 Authors
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16#include <glibmm/ustring.h>
17#include <gtkmm/stringobject.h>
18#include <iostream>
19#include <sstream>
20#include <string>
21#include <utility>
22#include <glibmm/i18n.h>
23#include <glibmm/convert.h>
24#include <glibmm/regex.h>
25#include <giomm/file.h> // query_info_async
26#include <gtkmm/box.h>
27#include <gtkmm/popover.h>
28#include <gtkmm/filedialog.h> // select_folder
29#include <gtkmm/scale.h>
30#include <sigc++/functors/mem_fun.h>
31
32#include "desktop.h"
33#include "inkscape.h"
34#include "inkscape-window.h"
35#include "message-stack.h"
36#include "preferences-widget.h"
37#include "preferences.h"
38#include "selcue.h"
39#include "selection-chemistry.h"
41#include "io/sys.h"
44#include "ui/icon-loader.h"
45#include "ui/pack.h"
46#include "ui/popup-menu.h"
47#include "ui/util.h"
49
50#ifdef _WIN32
51#include <windows.h>
52#endif
53
54namespace Inkscape::UI::Widget {
55
57{
58 set_margin(12);
59
60 set_orientation(Gtk::Orientation::VERTICAL);
61 set_column_spacing(12);
62 set_row_spacing(6);
63}
64
76void DialogPage::add_line(bool indent,
77 Glib::ustring const &label,
78 Gtk::Widget &widget,
79 Glib::ustring const &suffix,
80 const Glib::ustring &tip,
81 bool expand_widget,
82 Gtk::Widget *other_widget)
83{
84 if (!tip.empty())
85 widget.set_tooltip_text(tip);
86
87 auto const hb = Gtk::make_managed<Gtk::Box>();
88 hb->set_spacing(12);
89 hb->set_hexpand(true);
91 hb->set_valign(Gtk::Align::CENTER);
92
93 // Add a label in the first column if provided
94 if (!label.empty()) {
95 auto const label_widget = Gtk::make_managed<Gtk::Label>(label, Gtk::Align::START,
96 Gtk::Align::CENTER, true);
97 label_widget->set_mnemonic_widget(widget);
98 label_widget->set_markup(label_widget->get_text());
99
100 if (indent) {
101 label_widget->set_margin_start(12);
102 }
103
104 label_widget->set_valign(Gtk::Align::CENTER);
105 attach_next_to(*label_widget, Gtk::PositionType::BOTTOM);
106
107 attach_next_to(*hb, *label_widget, Gtk::PositionType::RIGHT, 1, 1);
108 } else {
109 if (indent) {
110 hb->set_margin_start(12);
111 }
112
113 attach_next_to(*hb, Gtk::PositionType::BOTTOM, 2, 1);
114 }
115
116 // Add a label on the right of the widget if desired
117 if (!suffix.empty()) {
118 auto const suffix_widget = Gtk::make_managed<Gtk::Label>(suffix, Gtk::Align::START, Gtk::Align::CENTER, true);
119 suffix_widget->set_markup(suffix_widget->get_text());
120 UI::pack_start(*hb, *suffix_widget,false,false);
121 }
122
123 // Pack an additional widget into a box with the widget if desired
124 if (other_widget)
125 UI::pack_start(*hb, *other_widget, expand_widget, expand_widget);
126}
127
128void DialogPage::add_group_header(Glib::ustring name, int columns)
129{
130 if (name.empty()) return;
131
132 auto const label_widget = Gtk::make_managed<Gtk::Label>(Glib::ustring("<b>").append(name).append("</b>"),
133 Gtk::Align::START, Gtk::Align::CENTER, true);
134
135 label_widget->set_use_markup(true);
136 label_widget->set_valign(Gtk::Align::CENTER);
137 attach_next_to(*label_widget, Gtk::PositionType::BOTTOM, columns, 1);
138}
139
141{
142 if (name.empty()) return;
143
144 auto const label_widget = Gtk::make_managed<Gtk::Label>(Glib::ustring("<i>").append(name).append("</i>"),
145 Gtk::Align::START , Gtk::Align::CENTER, true);
146 label_widget->set_use_markup(true);
147 label_widget->set_valign(Gtk::Align::CENTER);
148 label_widget->set_wrap(true);
149 label_widget->set_wrap_mode(Pango::WrapMode::WORD);
150 attach_next_to(*label_widget, Gtk::PositionType::BOTTOM, 2, 1);
151}
152
153void DialogPage::set_tip(Gtk::Widget& widget, Glib::ustring const &tip)
154{
155 widget.set_tooltip_text (tip);
156}
157
158void PrefCheckButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
159 bool default_value)
160{
163 if (!label.empty())
164 this->set_label(label);
165 this->set_active( prefs->getBool(_prefs_path, default_value) );
166}
167
169{
170 if (this->get_visible()) //only take action if the user toggled it
171 {
173 prefs->setBool(_prefs_path, this->get_active());
174 }
175 this->changed_signal.emit(this->get_active());
176}
177
178void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
179 Glib::ustring const &string_value, bool default_value, PrefRadioButton* group_member)
180{
183 _string_value = string_value;
184 (void)default_value;
185 this->set_label(label);
186
187 if (group_member)
188 {
189 this->set_group(*group_member);
190 }
191
193 Glib::ustring val = prefs->getString(_prefs_path);
194 if ( !val.empty() )
195 this->set_active(val == _string_value);
196 else
197 this->set_active( false );
198}
199
200void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
201 int int_value, bool default_value, PrefRadioButton* group_member)
202{
205 _int_value = int_value;
206 this->set_label(label);
207
208 if (group_member)
209 {
210 this->set_group(*group_member);
211 }
212
214 if (default_value)
215 this->set_active( prefs->getInt(_prefs_path, int_value) == _int_value );
216 else
217 this->set_active( prefs->getInt(_prefs_path, int_value + 1) == _int_value );
218}
219
221{
223
224 if (this->get_visible() && this->get_active() ) //only take action if toggled by user (to active)
225 {
226 if ( _value_type == VAL_STRING )
228 else if ( _value_type == VAL_INT )
230 }
231 this->changed_signal.emit(this->get_active());
232}
233
234PrefRadioButtons::PrefRadioButtons(const std::vector<PrefItem>& buttons, const Glib::ustring& prefs_path) {
235 set_spacing(2);
236
237 PrefRadioButton* group = nullptr;
238 for (auto&& item : buttons) {
239 auto* btn = Gtk::make_managed<PrefRadioButton>();
240 btn->init(item.label, prefs_path, item.int_value, item.is_default, group);
241 btn->set_tooltip_text(item.tooltip);
242 append(*btn);
243 if (!group) group = btn;
244 }
245}
246
247void PrefSpinButton::init(Glib::ustring const &prefs_path,
248 double lower, double upper, double step_increment, double /*page_increment*/,
249 double default_value, bool is_int, bool is_percent)
250{
252 _is_int = is_int;
253 _is_percent = is_percent;
255 double value;
256 if (is_int) {
257 if (is_percent) {
258 value = 100 * prefs->getDoubleLimited(prefs_path, default_value, lower/100.0, upper/100.0);
259 } else {
260 value = (double) prefs->getIntLimited(prefs_path, (int) default_value, (int) lower, (int) upper);
261 }
262 } else {
263 value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper);
264 }
265
266 this->set_range (lower, upper);
267 this->set_increments (step_increment, 0);
268 this->set_value (value);
269 this->set_width_chars(6);
270 if (is_int)
271 this->set_digits(0);
272 else if (step_increment < 0.1)
273 this->set_digits(4);
274 else
275 this->set_digits(2);
276
277 signal_value_changed().connect(sigc::mem_fun(*this, &PrefSpinButton::on_value_changed));
278}
279
281{
283 if (this->get_visible()) //only take action if user changed value
284 {
285 if (_is_int) {
286 if (_is_percent) {
287 prefs->setDouble(_prefs_path, this->get_value()/100.0);
288 } else {
289 prefs->setInt(_prefs_path, (int) this->get_value());
290 }
291 } else {
292 prefs->setDouble(_prefs_path, this->get_value());
293 }
294 }
295 this->changed_signal.emit(this->get_value());
296}
297
298void PrefSpinUnit::init(Glib::ustring const &prefs_path,
299 double lower, double upper, double step_increment,
300 double default_value, UnitType unit_type, Glib::ustring const &default_unit)
301{
303 _is_percent = (unit_type == UNIT_TYPE_DIMENSIONLESS);
304
305 resetUnitType(unit_type);
306 setUnit(default_unit);
307 setRange (lower, upper);
308 setIncrements (step_increment, 0);
309 if (step_increment < 0.1) {
310 setDigits(4);
311 } else {
312 setDigits(2);
313 }
314
316 double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper);
317 Glib::ustring unitstr = prefs->getUnit(prefs_path);
318 if (unitstr.length() == 0) {
319 unitstr = default_unit;
320 // write the assumed unit to preferences:
321 prefs->setDoubleUnit(_prefs_path, value, unitstr);
322 }
323 setValue(value, unitstr);
324
325 signal_value_changed().connect(sigc::mem_fun(*this, &PrefSpinUnit::on_my_value_changed));
326}
327
329{
331 if (getWidget()->get_visible()) //only take action if user changed value
332 {
333 prefs->setDoubleUnit(_prefs_path, getValue(getUnit()->abbr), getUnit()->abbr);
334 }
335}
336
337const double ZoomCorrRuler::textsize = 7;
338const double ZoomCorrRuler::textpadding = 5;
339
341 _unitconv(1.0),
342 _border(5)
343{
345
346 set_draw_func(sigc::mem_fun(*this, &ZoomCorrRuler::on_draw));
347}
348
349void ZoomCorrRuler::set_size(int x, int y)
350{
351 _min_width = x;
352 _height = y;
353 set_size_request(x + _border*2, y + _border*2);
354}
355
356// The following two functions are borrowed from 2geom's toy-framework-2; if they are useful in
357// other locations, we should perhaps make them (or adapted versions of them) publicly available
358static void
359draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom = false,
360 double fontsize = ZoomCorrRuler::textsize, std::string fontdesc = "Sans") {
361 PangoLayout* layout = pango_cairo_create_layout (cr);
362 pango_layout_set_text(layout, txt, -1);
363
364 // set font and size
365 std::ostringstream sizestr;
366 sizestr << fontsize;
367 fontdesc = fontdesc + " " + sizestr.str();
368 PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc.c_str());
369 pango_layout_set_font_description(layout, font_desc);
370 pango_font_description_free (font_desc);
371
372 PangoRectangle logical_extent;
373 pango_layout_get_pixel_extents(layout, nullptr, &logical_extent);
374 cairo_move_to(cr, loc[Geom::X], loc[Geom::Y] - (bottom ? logical_extent.height : 0));
375 pango_cairo_show_layout(cr, layout);
376}
377
378static void
380 std::ostringstream number;
381 number << num;
382 draw_text(cr, pos, number.str().c_str(), true);
383}
384
385/*
386 * \arg dist The distance between consecutive minor marks
387 * \arg major_interval Number of marks after which to draw a major mark
388 */
389void
390ZoomCorrRuler::draw_marks(Cairo::RefPtr<Cairo::Context> const &cr,
391 double const dist, int const major_interval)
392{
394 const double zoomcorr = prefs->getDouble("/options/zoomcorrection/value", 1.0);
395 double mark = 0;
396 int i = 0;
397 double step = dist * zoomcorr / _unitconv;
398 bool draw_minor = true;
399 if (step <= 0) {
400 return;
401 }
402 else if (step < 2) {
403 // marks too dense
404 draw_minor = false;
405 }
406 int last_pos = -1;
407 while (mark <= _drawing_width) {
408 cr->move_to(mark, _height);
409 if ((i % major_interval) == 0) {
410 // don't overcrowd the marks
411 if (static_cast<int>(mark) > last_pos) {
412 // major mark
413 cr->line_to(mark, 0);
415 draw_number(cr->cobj(), textpos, dist * i);
416
417 last_pos = static_cast<int>(mark) + 1;
418 }
419 } else if (draw_minor) {
420 // minor mark
422 }
423 mark += step;
424 ++i;
425 }
426}
427
428void
429ZoomCorrRuler::on_draw(Cairo::RefPtr<Cairo::Context> const &cr, int const width, int const height)
430{
431 auto const &w = width;
432 _drawing_width = w - _border * 2;
433
434 auto const fg = get_color();
435 cr->set_line_width(1);
436 cr->set_source_rgb(fg.get_red(), fg.get_green(), fg.get_blue());
437
438 cr->translate(_border, _border); // so that we have a small white border around the ruler
439 cr->move_to (0, _height);
440 cr->line_to (_drawing_width, _height);
441
443 Glib::ustring abbr = prefs->getString("/options/zoomcorrection/unit");
444 if (abbr == "cm") {
445 draw_marks(cr, 0.1, 10);
446 } else if (abbr == "in") {
447 draw_marks(cr, 0.25, 4);
448 } else if (abbr == "mm") {
449 draw_marks(cr, 10, 10);
450 } else if (abbr == "pc") {
451 draw_marks(cr, 1, 10);
452 } else if (abbr == "pt") {
453 draw_marks(cr, 10, 10);
454 } else if (abbr == "px") {
455 draw_marks(cr, 10, 10);
456 } else {
457 draw_marks(cr, 1, 1);
458 }
459 cr->stroke();
460}
461
462void
464{
465 if (this->get_visible() || freeze) //only take action if user changed value
466 {
467 freeze = true;
469 prefs->setDouble("/options/zoomcorrection/value", _slider->get_value() / 100.0);
470 _sb->set_value(_slider->get_value());
471 _ruler.queue_draw();
472 freeze = false;
473 }
474}
475
476void
478{
479 if (this->get_visible() || freeze) //only take action if user changed value
480 {
481 freeze = true;
483 prefs->setDouble("/options/zoomcorrection/value", _sb->get_value() / 100.0);
484 _slider->set_value(_sb->get_value());
485 _ruler.queue_draw();
486 freeze = false;
487 }
488}
489
490void
492 if (!_unit.get_sensitive()) {
493 // when the unit menu is initialized, the unit is set to the default but
494 // it needs to be reset later so we don't perform the change in this case
495 return;
496 }
498 prefs->setString("/options/zoomcorrection/unit", _unit.getUnitAbbr());
499 double conv = _unit.getConversion(_unit.getUnitAbbr(), "px");
501 if (_ruler.get_visible()) {
502 _ruler.queue_draw();
503 }
504}
505
507{
508 return _sb->mnemonic_activate ( group_cycling );
509}
510
511void
512ZoomCorrRulerSlider::init(int ruler_width, int ruler_height, double lower, double upper,
513 double step_increment, double page_increment, double default_value)
514{
516 double value = prefs->getDoubleLimited("/options/zoomcorrection/value", default_value, lower, upper) * 100.0;
517
518 freeze = false;
519
520 _ruler.set_size(ruler_width, ruler_height);
521
522 _slider = Gtk::make_managed<Gtk::Scale>(Gtk::Orientation::HORIZONTAL);
523
524 _slider->set_size_request(_ruler.width(), -1);
525 _slider->set_range (lower, upper);
526 _slider->set_increments (step_increment, page_increment);
527 _slider->set_value (value);
528 _slider->set_digits(2);
529
530 _slider->signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_slider_value_changed));
531 _sb = Gtk::make_managed<Inkscape::UI::Widget::SpinButton>();
532 _sb->signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_spinbutton_value_changed));
533 _unit.signal_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_unit_changed));
534
535 _sb->set_range (lower, upper);
536 _sb->set_increments (step_increment, 0);
537 _sb->set_value (value);
538 _sb->set_digits(2);
539 _sb->set_max_width_chars(5); // to fit "100.00"
540 _sb->set_halign(Gtk::Align::CENTER);
541 _sb->set_valign(Gtk::Align::END);
542
543 _unit.set_sensitive(false);
544 _unit.setUnitType(UNIT_TYPE_LINEAR);
545 _unit.set_sensitive(true);
546 _unit.setUnit(prefs->getString("/options/zoomcorrection/unit"));
547 _unit.set_halign(Gtk::Align::CENTER);
548 _unit.set_valign(Gtk::Align::END);
549
550 _slider->set_hexpand(true);
551 _ruler.set_hexpand(true);
552 auto const table = Gtk::make_managed<Gtk::Grid>();
553 table->attach(*_slider, 0, 0, 1, 1);
554 table->attach(*_sb, 1, 0, 1, 1);
555 table->attach(_ruler, 0, 1, 1, 1);
556 table->attach(_unit, 1, 1, 1, 1);
557
559}
560
561void
563{
564 if (this->get_visible() || freeze) //only take action if user changed value
565 {
566 freeze = true;
568 prefs->setDouble(_prefs_path, _slider->get_value());
569 if (_sb) _sb->set_value(_slider->get_value());
570 freeze = false;
571 }
572}
573
574void
576{
577 if (this->get_visible() || freeze) //only take action if user changed value
578 {
579 freeze = true;
581 if (_sb) {
582 prefs->setDouble(_prefs_path, _sb->get_value());
583 _slider->set_value(_sb->get_value());
584 }
585 freeze = false;
586 }
587}
588
589bool PrefSlider::on_mnemonic_activate ( bool group_cycling )
590{
591 return _sb ? _sb->mnemonic_activate ( group_cycling ) : false;
592}
593
594void PrefSlider::init(Glib::ustring const &prefs_path,
595 double lower, double upper, double step_increment, double page_increment, double default_value, int digits)
596{
598
600 double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper);
601
602 freeze = false;
603
604 _slider = Gtk::make_managed<Gtk::Scale>(Gtk::Orientation::HORIZONTAL);
605
606 _slider->set_range (lower, upper);
607 _slider->set_increments (step_increment, page_increment);
608 _slider->set_value (value);
609 _slider->set_digits(digits);
610 _slider->signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_slider_value_changed));
611 if (_spin) {
612 _sb = Gtk::make_managed<Inkscape::UI::Widget::SpinButton>();
613 _sb->signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_spinbutton_value_changed));
614 _sb->set_range (lower, upper);
615 _sb->set_increments (step_increment, 0);
616 _sb->set_value (value);
617 _sb->set_digits(digits);
618 _sb->set_halign(Gtk::Align::CENTER);
619 _sb->set_valign(Gtk::Align::CENTER);
620 }
621
622 auto const table = Gtk::make_managed<Gtk::Grid>();
623 _slider->set_hexpand();
624 table->attach(*_slider, 0, 0, 1, 1);
625 if (_sb) table->attach(*_sb, 1, 0, 1, 1);
626
628}
629
630void PrefCombo::init(Glib::ustring const &prefs_path,
631 std::span<Glib::ustring const> labels,
632 std::span<int const> values,
633 int const default_value)
634{
635 int const labels_size = labels.size();
636 int const values_size = values.size();
637 if (values_size != labels_size) {
638 std::cerr << "PrefCombo::"
639 << "Different number of values/labels in " << prefs_path.raw() << std::endl;
640 return;
641 }
642
644 auto prefs = Inkscape::Preferences::get();
645 int row = 0;
646 int value = prefs->getInt(_prefs_path, default_value);
647
648 for (int i = 0; i < labels_size; ++i) {
649 append(labels[i]);
650 _values.push_back(values[i]);
651 if (value == values[i]) {
652 row = i;
653 }
654 }
655 set_selected(row);
656 property_selected().signal_changed().connect([this](){ on_changed(); });
657}
658
659void PrefCombo::init(Glib::ustring const &prefs_path,
660 std::span<Glib::ustring const> labels,
661 std::span<Glib::ustring const> values,
662 Glib::ustring const &default_value)
663{
664 int const labels_size = labels.size();
665 int const values_size = values.size();
666 if (values_size != labels_size) {
667 std::cerr << "PrefCombo::"
668 << "Different number of values/labels in " << prefs_path.raw() << std::endl;
669 return;
670 }
671
673 auto prefs = Inkscape::Preferences::get();
674 int row = 0;
675 Glib::ustring value = prefs->getString(_prefs_path);
676 if (value.empty()) {
677 value = default_value;
678 }
679
680 for (int i = 0; i < labels_size; ++i) {
681 append(labels[i]);
682 _ustr_values.push_back(values[i]);
683 if (value == values[i]) {
684 row = i;
685 }
686 }
687 set_selected(row);
688 property_selected().signal_changed().connect([this](){ on_changed(); });
689}
690
692{
693 if (!get_visible()) return; //only take action if user changed value
694
695 auto prefs = Inkscape::Preferences::get();
696 auto row = get_selected();
697 if (!_values.empty()) {
698 prefs->setInt(_prefs_path, _values.at(row));
699 }
700 else {
701 prefs->setString(_prefs_path, _ustr_values.at(row));
702 }
703}
704
705void PrefEntryButtonHBox::init(Glib::ustring const &prefs_path,
706 bool visibility, Glib::ustring const &default_string)
707{
709 _default_string = default_string;
711 relatedEntry = Gtk::make_managed<Gtk::Entry>();
712 relatedButton = Gtk::make_managed<Gtk::Button>(_("Reset"));
713 relatedEntry->set_invisible_char('*');
714 relatedEntry->set_visibility(visibility);
715 relatedEntry->set_text(prefs->getString(_prefs_path));
718 relatedButton->signal_clicked().connect(
720 relatedEntry->signal_changed().connect(
722}
723
725{
726 if (this->get_visible()) //only take action if user changed value
727 {
729 prefs->setString(_prefs_path, relatedEntry->get_text());
730 }
731}
732
734{
735 if (this->get_visible()) //only take action if user changed value
736 {
739 relatedEntry->set_text(_default_string);
740 }
741}
742
744{
745 return relatedEntry->mnemonic_activate ( group_cycling );
746}
747
749 bool visibility)
750{
753
754 relatedEntry = Gtk::make_managed<Gtk::Entry>();
755 relatedEntry->set_invisible_char('*');
756 relatedEntry->set_visibility(visibility);
757 relatedEntry->set_text(prefs->getString(_prefs_path));
758
759 relatedButton = Gtk::make_managed<Gtk::Button>();
760 auto const pixlabel = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 3);
761 auto const im = sp_get_icon_image("applications-graphics", Gtk::IconSize::NORMAL);
762 UI::pack_start(*pixlabel, *im);
763 auto const l = Gtk::make_managed<Gtk::Label>();
764 l->set_markup_with_mnemonic(_("_Browse..."));
765 UI::pack_start(*pixlabel, *l);
766 relatedButton->set_child(*pixlabel);
767
768 UI::pack_end(*this, *relatedButton, false, false, 4);
769 UI::pack_start(*this, *relatedEntry, true, true);
770
771 relatedButton->signal_clicked().connect(
773 relatedEntry->signal_changed().connect(
775}
776
778{
779 if (this->get_visible()) { // Only take action if user changed value.
781 prefs->setString(_prefs_path, Glib::filename_from_utf8(relatedEntry->get_text()));
782 }
783}
784
786{
787 if (this->get_visible()) { // Only take action if user changed value.
788
789 // Get the current directory for finding files.
790 static std::string current_folder;
792
794
795 // Create a filter to limit options to executables.
796 // (Only used to select Bitmap and SVG editors.)
797 auto filter_app = Gtk::FileFilter::create();
798 filter_app->set_name(_("Applications"));
799 filter_app->add_mime_type("application/x-executable"); // Linux (xdg-mime query filetype)
800 filter_app->add_mime_type("application/x-pie-executable"); // Linux (filetype --mime-type)
801 filter_app->add_mime_type("application/x-mach-binary"); // MacOS
802 filter_app->add_mime_type("application/vnd.microsoft.portable-executable"); // Windows
803 filter_app->add_suffix("exe"); // Windows
804 filters->append(filter_app);
805
806 // Just in case...
807 auto filter_all = Gtk::FileFilter::create();
808 filter_all->set_name(_("All Files"));
809 filter_all->add_pattern("*");
810 filters->append(filter_all);
811
812 // Create a dialog.
813 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
814 auto window = desktop->getInkscapeWindow();
815 auto file = choose_file_open(_("Select an editor"), window, filters, current_folder, _("Select"));
816
817 if (!file) {
818 return; // Cancel
819 }
820
822 prefs->setString(_prefs_path, file->get_path());
823 relatedEntry->set_text(file->get_parse_name());
824 }
825}
826
828{
829 return relatedEntry->mnemonic_activate ( group_cycling );
830}
831
832void PrefOpenFolder::init(Glib::ustring const &entry_string, Glib::ustring const &tooltip)
833{
834 relatedEntry = Gtk::make_managed<Gtk::Entry>();
835 relatedButton = Gtk::make_managed<Gtk::Button>();
836 auto const pixlabel = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 3);
837 auto const im = sp_get_icon_image("document-open", Gtk::IconSize::NORMAL);
838 UI::pack_start(*pixlabel, *im);
839 auto const l = Gtk::make_managed<Gtk::Label>();
840 l->set_markup_with_mnemonic(_("Open"));
841 UI::pack_start(*pixlabel, *l);
842 relatedButton->set_child(*pixlabel);
843 relatedButton->set_tooltip_text(tooltip);
844 relatedEntry->set_text(entry_string);
845 relatedEntry->set_sensitive(false);
846 UI::pack_end(*this, *relatedButton, false, false, 4);
847 UI::pack_start(*this, *relatedEntry, true, true);
848 relatedButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefOpenFolder::onRelatedButtonClickedCallback));
849}
850
852{
853 g_mkdir_with_parents(relatedEntry->get_text().c_str(), 0700);
854 // helper function in ui/util.h to open folder path
855 system_open(relatedEntry->get_text());
856}
857
858void PrefEditFolder::init(Glib::ustring const &entry_string, Glib::ustring const &prefs_path, Glib::ustring const &reset_string)
859{
861 _reset_string = reset_string;
862
863 // warning popup
864 warningPopup = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 3);
865 warningPopupLabel = Gtk::make_managed<Gtk::Label>();
866 warningPopupButton = Gtk::make_managed<Gtk::Button>();
868 warningPopupButton->set_label(_("Create"));
869 warningPopupButton->set_visible(true);
870 UI::pack_end(*warningPopup, *warningPopupButton, false, false, 4);
871 popover = Gtk::make_managed<Gtk::Popover>();
872 popover->set_child(*warningPopup);
873 popover->set_parent(*this);
874 warningPopupButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefEditFolder::onCreateButtonClickedCallback));
875
876 // reset button
877 resetButton = Gtk::make_managed<Gtk::Button>();
878 auto const resetim = sp_get_icon_image("reset-settings", Gtk::IconSize::NORMAL);
879 resetButton->set_child(*resetim);
880 resetButton->set_tooltip_text(_("Reset to default directory"));
881 resetButton->set_margin_start(4);
882 UI::pack_end(*this, *resetButton, false, false, 0);
883 resetButton->signal_clicked().connect(
884 sigc::mem_fun(*this, &PrefEditFolder::onResetButtonClickedCallback));
885
886 // open button
887 openButton = Gtk::make_managed<Gtk::Button>();
888 auto const openim = sp_get_icon_image("document-open", Gtk::IconSize::NORMAL);
889 openButton->set_child(*openim);
890 openButton->set_tooltip_text(_("Open directory"));
891 openButton->set_margin_start(4);
892 UI::pack_end(*this, *openButton, false, false, 0);
893 openButton->signal_clicked().connect(
894 sigc::mem_fun(*this, &PrefEditFolder::onOpenButtonClickedCallback));
895
896 // linked entry/select box
897 relatedPathBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 0);
898 relatedPathBox->set_css_classes({"linked"});
899
900 // select button
901 selectButton = Gtk::make_managed<Gtk::Button>();
902 auto const selectl = Gtk::make_managed<Gtk::Label>();
903 selectl->set_markup_with_mnemonic(_("..."));
904 selectButton->set_child(*selectl);
905 selectButton->set_tooltip_text(_("Select a new directory"));
906 UI::pack_end(*relatedPathBox, *selectButton, false, false, 0);
907 selectButton->signal_clicked().connect(
908 sigc::mem_fun(*this, &PrefEditFolder::onChangeButtonClickedCallback));
909
910 // entry string
911 relatedEntry = Gtk::make_managed<Gtk::Entry>();
912 relatedEntry->set_text(entry_string);
913 relatedEntry->set_width_chars(12);
914 relatedEntry->set_sensitive(true);
916
917 // when warning icon clicked
918 relatedEntry->signal_icon_press().connect([&](Gtk::Entry::IconPosition) {
919 UI::popup_at(*popover, *relatedEntry, relatedEntry->get_icon_area(Gtk::Entry::IconPosition::SECONDARY));
920 });
921 relatedEntry->signal_changed().connect(
922 sigc::mem_fun(*this, &PrefEditFolder::onRelatedEntryChangedCallback));
923
924 UI::pack_start(*this, *relatedPathBox, true, true);
925
926 // check path at init
928}
929
931{
932 // Get the current directory for finding files.
933 static std::string current_folder;
935
936 // Create a dialog.
937 auto dialog = Gtk::FileDialog::create();
938 dialog->set_initial_folder(Gio::File::create_for_path(current_folder));
939 dialog->select_folder(dynamic_cast<Gtk::Window &>(*get_root()), sigc::track_object([&dialog = *dialog, this] (auto &result) {
940 try {
941 if (auto folder = dialog.select_folder_finish(result)) {
942 // write folder path into prefs & update entry
943 setFolderPath(folder);
944 return;
945 }
946 } catch (Gtk::DialogError const& e) {
947 if (e.code() == Gtk::DialogError::Code::FAILED) {
948 std::cerr << "PrefEditFolder::onChangeButtonClickedCallback: "
949 << "Gtk::FileDialog returned " << e.what() << std::endl;
950 }
951 }
952 }, *this), {});
953}
954
955void PrefEditFolder::setFolderPath(Glib::RefPtr<Gio::File const> folder)
956{
957 Glib::ustring folder_path = folder->get_parse_name();
959 prefs->setString(_prefs_path, folder_path);
960 relatedEntry->set_text(folder_path);
961}
962
964{
965 // helper function in ui/util.h to open folder path
966 system_open(relatedEntry->get_text());
967}
968
973
975{
978 prefs->setString(_prefs_path, Glib::filename_to_utf8(relatedEntry->get_text()));
979}
980
982{
983 _fileInfo = std::make_unique<QueryFileInfo>(relatedEntry->get_text().raw(), [this](auto info) {
984 checkPathValidityResults(info);
985 });
986}
987
988void PrefEditFolder::checkPathValidityResults(Glib::RefPtr<Gio::FileInfo> fileInfo)
989{
991 if (fileInfo) { // failsafe, is possible it returns nullptr
992 if (fileInfo->get_file_type() == Gio::FileType::DIRECTORY) {
994 } else {
996 }
997 }
998
999 // test path validity
1000 switch (fileis)
1001 {
1003 relatedEntry->unset_icon(Gtk::Entry::IconPosition::SECONDARY);
1004 // helper class in the stylesheet to remove icons (hack)
1005 relatedEntry->add_css_class("no-icon");
1006 relatedEntry->set_icon_sensitive(Gtk::Entry::IconPosition::SECONDARY, false);
1007 // invalidate the icon tooltip, making it inherit the Gtk::Entry one
1008 relatedEntry->set_has_tooltip(false);
1009 openButton->set_sensitive(true);
1010 break;
1012 relatedEntry->set_icon_from_icon_name("dialog-warning", Gtk::Entry::IconPosition::SECONDARY);
1013 relatedEntry->remove_css_class("no-icon");
1014 relatedEntry->set_icon_tooltip_markup(_("This is a file. Please select a directory."), Gtk::Entry::IconPosition::SECONDARY);
1015 relatedEntry->set_icon_sensitive(Gtk::Entry::IconPosition::SECONDARY, true);
1016 warningPopupLabel->set_markup(relatedEntry->get_icon_tooltip_markup(Gtk::Entry::IconPosition::SECONDARY));
1017 warningPopupButton->set_visible(false);
1018 openButton->set_sensitive(false);
1019 break;
1021 relatedEntry->set_icon_from_icon_name("dialog-warning", Gtk::Entry::IconPosition::SECONDARY);
1022 relatedEntry->remove_css_class("no-icon");
1023 relatedEntry->set_icon_sensitive(Gtk::Entry::IconPosition::SECONDARY, true);
1024 relatedEntry->set_icon_tooltip_markup(_("This directory does not exist."), Gtk::Entry::IconPosition::SECONDARY);
1025 warningPopupLabel->set_markup(relatedEntry->get_icon_tooltip_markup(Gtk::Entry::IconPosition::SECONDARY));
1026 warningPopupButton->set_visible(true);
1027 openButton->set_sensitive(false);
1028 break;
1029 default:
1030 std::cerr << "PrefEditFolder::checkPathValidityResults: "
1031 << "Invalid fileis value!" << std::endl;
1032 break;
1033 }
1034
1035 // disable reset button if path is same as default path
1036 (relatedEntry->get_text() == _reset_string)
1037 ? resetButton->set_sensitive(false)
1038 : resetButton->set_sensitive(true);
1039}
1040
1042{
1043 g_mkdir_with_parents(relatedEntry->get_text().c_str(), 0700);
1044 popover->popdown();
1046}
1047
1048
1049void PrefEntry::init(Glib::ustring const &prefs_path, bool visibility)
1050{
1053 this->set_invisible_char('*');
1054 this->set_visibility(visibility);
1055 this->set_text(prefs->getString(_prefs_path));
1056}
1057
1059{
1060 if (this->get_visible()) //only take action if user changed value
1061 {
1063 prefs->setString(_prefs_path, this->get_text());
1064 }
1065}
1066
1068{
1069 if (this->get_visible()) //only take action if user changed value
1070 {
1072 prefs->setString(_prefs_path, Glib::filename_to_utf8(this->get_text()));
1073 }
1074}
1075
1076void PrefMultiEntry::init(Glib::ustring const &prefs_path, int height)
1077{
1078 // TODO: Figure out if there's a way to specify height in lines instead of px
1079 // and how to obtain a reasonable default width if 'expand_widget' is not used
1080 set_size_request(100, height);
1081 set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
1082 set_has_frame(true);
1083
1084 set_child(_text);
1085
1088 Glib::ustring value = prefs->getString(_prefs_path);
1089 value = Glib::Regex::create("\\|")->replace_literal(value, 0, "\n", (Glib::Regex::MatchFlags)0);
1090 _text.get_buffer()->set_text(value);
1091 _text.get_buffer()->signal_changed().connect(sigc::mem_fun(*this, &PrefMultiEntry::on_changed));
1092}
1093
1095{
1096 if (get_visible()) //only take action if user changed value
1097 {
1099 Glib::ustring value = _text.get_buffer()->get_text();
1100 value = Glib::Regex::create("\\n")->replace_literal(value, 0, "|", (Glib::Regex::MatchFlags)0);
1101 prefs->setString(_prefs_path, value);
1102 }
1103}
1104
1105void PrefColorPicker::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
1106 std::string const &default_color)
1107{
1109 setTitle(label);
1112}
1113
1115{
1116 if (this->get_visible()) { //only take action if the user toggled it
1118 prefs->setColor(_prefs_path, color);
1119 }
1120}
1121
1122void PrefUnit::init(Glib::ustring const &prefs_path)
1123{
1126 setUnitType(UNIT_TYPE_LINEAR);
1127 setUnit(prefs->getString(_prefs_path));
1128}
1129
1131{
1132 if (this->get_visible()) //only take action if user changed value
1133 {
1136 }
1137}
1138
1139} // namespace Inkscape::UI::Widget
1140
1141/*
1142 Local Variables:
1143 mode:c++
1144 c-file-style:"stroustrup"
1145 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1146 indent-tabs-mode:nil
1147 fill-column:99
1148 End:
1149*/
1150// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
_PangoFontDescription PangoFontDescription
Definition Layout-TNG.h:44
struct _PangoLayout PangoLayout
Two-dimensional point that doubles as a vector.
Definition point.h:66
Preference storage class.
Definition preferences.h:61
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
void setDoubleUnit(Glib::ustring const &pref_path, double value, Glib::ustring const &unit_abbr)
Set a floating point value with unit.
Glib::ustring getUnit(Glib::ustring const &pref_path)
Retrieve the unit string.
double getDouble(Glib::ustring const &pref_path, double def=0.0, Glib::ustring const &unit="")
Retrieve a floating point value.
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
Colors::Color getColor(Glib::ustring const &pref_path, std::string const &def="black")
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setColor(Glib::ustring const &pref_path, Colors::Color const &color)
Set an RGBA color value.
void setString(Glib::ustring const &pref_path, Glib::ustring const &value)
Set an UTF-8 string value.
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.
int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX)
Retrieve a limited integer.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
double getDoubleLimited(Glib::ustring const &pref_path, double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="")
Retrieve a limited floating point value.
void setTitle(Glib::ustring title)
void setColor(Colors::Color const &)
void add_group_note(Glib::ustring name)
void add_group_header(Glib::ustring name, int columns=1)
void add_line(bool indent, Glib::ustring const &label, Gtk::Widget &widget, Glib::ustring const &suffix, Glib::ustring const &tip, bool expand=true, Gtk::Widget *other_widget=nullptr)
Add a widget to the bottom row of the dialog page.
void set_tip(Gtk::Widget &widget, Glib::ustring const &tip)
unsigned int append(const Glib::ustring &item)
Gtk::Widget const * getWidget() const
Definition labelled.h:48
void init(Glib::ustring const &label, Glib::ustring const &prefs_path, bool default_value)
sigc::signal< void(bool)> changed_signal
void on_changed(Inkscape::Colors::Color const &color) override
void init(Glib::ustring const &abel, Glib::ustring const &prefs_path, std::string const &default_color)
void init(Glib::ustring const &prefs_path, std::span< Glib::ustring const > labels, std::span< int const > values, int default_value)
std::vector< Glib::ustring > _ustr_values
string key values used optionally instead of numeric _values
void setFolderPath(Glib::RefPtr< Gio::File const > folder)
void checkPathValidityResults(Glib::RefPtr< Gio::FileInfo > info)
void init(Glib::ustring const &entry_string, Glib::ustring const &prefs_path, Glib::ustring const &reset_string)
std::unique_ptr< QueryFileInfo > _fileInfo
bool on_mnemonic_activate(bool group_cycling) override
void init(Glib::ustring const &prefs_path, bool mask, Glib::ustring const &default_string)
bool on_mnemonic_activate(bool group_cycling) override
void init(Glib::ustring const &prefs_path, bool mask)
void init(Glib::ustring const &prefs_path, bool mask)
void init(Glib::ustring const &prefs_path, int height)
void init(Glib::ustring const &entry_string, Glib::ustring const &tooltip)
sigc::signal< void(bool)> changed_signal
void init(Glib::ustring const &label, Glib::ustring const &prefs_path, int int_value, bool default_value, PrefRadioButton *group_member)
PrefRadioButtons(const std::vector< PrefItem > &buttons, const Glib::ustring &prefs_path)
void init(Glib::ustring const &prefs_path, double lower, double upper, double step_increment, double page_increment, double default_value, int digits)
bool on_mnemonic_activate(bool group_cycling) override
Inkscape::UI::Widget::SpinButton * _sb
sigc::signal< void(double)> changed_signal
void init(Glib::ustring const &prefs_path, double lower, double upper, double step_increment, double page_increment, double default_value, bool is_int, bool is_percent)
void init(Glib::ustring const &prefs_path, double lower, double upper, double step_increment, double default_value, UnitType unit_type, Glib::ustring const &default_unit)
void init(Glib::ustring const &prefs_path)
void setValue(double number, Glib::ustring const &units)
Sets the number and unit system.
bool setUnit(Glib::ustring const &units)
Sets the unit for the ScalarUnit widget.
void resetUnitType(UnitType unit_type)
Resets the unit type for the ScalarUnit widget.
Unit const * getUnit() const
Gets the object for the currently selected unit.
void setIncrements(double step, double page)
Sets the step and page increments for the spin button.
Definition scalar.cpp:123
Glib::SignalProxy< void()> signal_value_changed()
Signal raised when the spin button's value changes.
Definition scalar.cpp:159
void setDigits(unsigned digits)
Sets the precision to be displayed by the spin button.
Definition scalar.cpp:94
void setRange(double min, double max)
Sets the minimum and maximum range allowed for the spin button.
Definition scalar.cpp:128
double getValue() const
Get the value in the spin_button.
Definition scalar.cpp:83
Glib::SignalProxyProperty signal_changed()
double getConversion(Glib::ustring const &new_unit_abbr, Glib::ustring const &old_unit_abbr="no_unit") const
Returns the conversion factor required to convert values of the currently selected unit into units of...
bool setUnit(Glib::ustring const &unit)
Sets the dropdown widget to the given unit abbreviation.
Definition unit-menu.cpp:75
bool setUnitType(UnitType unit_type, bool svg_length=false)
Adds the unit type to the widget.
Definition unit-menu.cpp:33
Glib::ustring getUnitAbbr() const
Returns the abbreviated unit name of the selected unit.
Definition unit-menu.cpp:92
bool on_mnemonic_activate(bool group_cycling) override
Inkscape::UI::Widget::SpinButton * _sb
void init(int ruler_width, int ruler_height, double lower, double upper, double step_increment, double page_increment, double default_value)
ZoomCorrRuler(int width=100, int height=20)
void draw_marks(Cairo::RefPtr< Cairo::Context > const &cr, double dist, int major_interval)
void on_draw(Cairo::RefPtr< Cairo::Context > const &cr, int width, int height)
To do: update description of desktop.
Definition desktop.h:149
InkscapeWindow const * getInkscapeWindow() const
Definition desktop.cpp:975
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
const double w
Definition conic-4.cpp:19
Css & result
Editable view implementation.
static Glib::ustring const prefs_path
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
macro for checking gtkmm version
Gtk::Image * sp_get_icon_image(Glib::ustring const &icon_name, int size)
Icon Loader.
SPItem * item
Inkscape - An SVG editor.
Glib::ustring label
Raw stack of active status messages.
void get_start_directory(std::string &start_path, Glib::ustring const &prefs_path, bool try_document_dir)
Find the start directory for a file dialog.
Custom widgets.
Definition desktop.h:126
static constexpr int height
static void draw_number(cairo_t *cr, Geom::Point pos, double num)
static void draw_text(cairo_t *cr, Geom::Point loc, const char *txt, bool bottom=false, double fontsize=ZoomCorrRuler::textsize, std::string fontdesc="Sans")
static char const * get_text(Gtk::Editable const &editable)
void pack_end(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the end of box.
Definition pack.cpp:153
static void popup_at(Gtk::Popover &popover, Gtk::Widget &widget, double const x_offset, double const y_offset, int width, int height)
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition pack.cpp:141
void system_open(const Glib::ustring &path)
Opens the given path with platform-specific tools.
Definition util.cpp:128
static void append(std::vector< T > &target, std::vector< T > &&source)
Glib::RefPtr< Gio::File > choose_file_open(Glib::ustring const &title, Gtk::Window *parent, Glib::RefPtr< Gio::ListStore< Gtk::FileFilter > > const &filters_model, std::string &current_folder, Glib::ustring const &accept)
Synchronously run a Gtk::FileDialog to open a single file for reading data.
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
struct _cairo cairo_t
Definition path-cairo.h:16
void cairo_move_to(cairo_t *cr, Geom::Point p1)
Helpers to connect signals to events that popup a menu in both GTK3 and GTK4.
Widgets for Inkscape Preferences dialog.
Singleton class to access the preferences file in a convenient way.
int num
Definition scribble.cpp:47
static Inkscape::Colors::Color default_color(SPItem *item)
Find default color based on colors in existing fill.
SPDesktop * desktop
double width
Glib::ustring name
Definition toolbars.cpp:55