diff options
Diffstat (limited to 'src/ui/widget/preferences-widget.cpp')
-rw-r--r-- | src/ui/widget/preferences-widget.cpp | 1117 |
1 files changed, 1117 insertions, 0 deletions
diff --git a/src/ui/widget/preferences-widget.cpp b/src/ui/widget/preferences-widget.cpp new file mode 100644 index 0000000..b76f077 --- /dev/null +++ b/src/ui/widget/preferences-widget.cpp @@ -0,0 +1,1117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Inkscape Preferences dialog. + * + * Authors: + * Marco Scholten + * Bruno Dilly <bruno.dilly@gmail.com> + * + * Copyright (C) 2004, 2006, 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/i18n.h> +#include <glibmm/convert.h> +#include <glibmm/regex.h> + +#include <gtkmm/box.h> +#include <gtkmm/frame.h> +#include <gtkmm/scale.h> +#include <gtkmm/table.h> + + +#include "desktop.h" +#include "inkscape.h" +#include "message-stack.h" +#include "preferences.h" +#include "selcue.h" +#include "selection-chemistry.h" + +#include "include/gtkmm_version.h" + +#include "io/sys.h" + +#include "ui/dialog/filedialog.h" +#include "ui/icon-loader.h" +#include "ui/util.h" +#include "ui/widget/preferences-widget.h" + + +#ifdef _WIN32 +#include <windows.h> +#endif + +using namespace Inkscape::UI::Widget; + +namespace Inkscape { +namespace UI { +namespace Widget { + +DialogPage::DialogPage() +{ + set_border_width(12); + + set_orientation(Gtk::ORIENTATION_VERTICAL); + set_column_spacing(12); + set_row_spacing(6); +} + +/** + * Add a widget to the bottom row of the dialog page + * + * \param[in] indent Whether the widget should be indented by one column + * \param[in] label The label text for the widget + * \param[in] widget The widget to add to the page + * \param[in] suffix Text for an optional label at the right of the widget + * \param[in] tip Tooltip text for the widget + * \param[in] expand_widget Whether to expand the widget horizontally + * \param[in] other_widget An optional additional widget to display at the right of the first one + */ +void DialogPage::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) +{ + if (tip != "") + widget.set_tooltip_text (tip); + + auto hb = Gtk::manage(new Gtk::Box()); + hb->set_spacing(12); + hb->set_hexpand(true); + hb->pack_start(widget, expand_widget, expand_widget); + hb->set_valign(Gtk::ALIGN_CENTER); + + // Add a label in the first column if provided + if (label != "") + { + Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_START, + Gtk::ALIGN_CENTER, true)); + label_widget->set_mnemonic_widget(widget); + label_widget->set_markup(label_widget->get_text()); + + if (indent) { + label_widget->set_margin_start(12); + } + + label_widget->set_valign(Gtk::ALIGN_CENTER); + add(*label_widget); + attach_next_to(*hb, *label_widget, Gtk::POS_RIGHT, 1, 1); + } + + // Now add the widget to the bottom of the dialog + if (label == "") + { + if (indent) { + hb->set_margin_start(12); + } + + add(*hb); + + GValue width = G_VALUE_INIT; + g_value_init(&width, G_TYPE_INT); + g_value_set_int(&width, 2); + gtk_container_child_set_property(GTK_CONTAINER(gobj()), GTK_WIDGET(hb->gobj()), "width", &width); + } + + // Add a label on the right of the widget if desired + if (suffix != "") + { + Gtk::Label* suffix_widget = Gtk::manage(new Gtk::Label(suffix , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true)); + suffix_widget->set_markup(suffix_widget->get_text()); + hb->pack_start(*suffix_widget,false,false); + } + + // Pack an additional widget into a box with the widget if desired + if (other_widget) + hb->pack_start(*other_widget, expand_widget, expand_widget); +} + +void DialogPage::add_group_header(Glib::ustring name, int columns) +{ + if (name != "") + { + Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(Glib::ustring(/*"<span size='large'>*/"<b>") + name + + Glib::ustring("</b>"/*</span>"*/) , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true)); + + label_widget->set_use_markup(true); + label_widget->set_valign(Gtk::ALIGN_CENTER); + add(*label_widget); + if (columns > 1) { + GValue width = G_VALUE_INIT; + g_value_init(&width, G_TYPE_INT); + g_value_set_int(&width, columns); + gtk_container_child_set_property(GTK_CONTAINER(gobj()), GTK_WIDGET(label_widget->gobj()), "width", &width); + } + } +} + +void DialogPage::add_group_note(Glib::ustring name) +{ + if (name != "") + { + Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(Glib::ustring("<i>") + name + + Glib::ustring("</i>") , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true)); + label_widget->set_use_markup(true); + label_widget->set_valign(Gtk::ALIGN_CENTER); + label_widget->set_line_wrap(true); + label_widget->set_line_wrap_mode(Pango::WRAP_WORD); + + add(*label_widget); + GValue width = G_VALUE_INIT; + g_value_init(&width, G_TYPE_INT); + g_value_set_int(&width, 2); + gtk_container_child_set_property(GTK_CONTAINER(gobj()), GTK_WIDGET(label_widget->gobj()), "width", &width); + } +} + +void DialogPage::set_tip(Gtk::Widget& widget, Glib::ustring const &tip) +{ + widget.set_tooltip_text (tip); +} + +void PrefCheckButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path, + bool default_value) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (!label.empty()) + this->set_label(label); + this->set_active( prefs->getBool(_prefs_path, default_value) ); +} + +void PrefCheckButton::on_toggled() +{ + if (this->get_visible()) //only take action if the user toggled it + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(_prefs_path, this->get_active()); + } + this->changed_signal.emit(this->get_active()); +} + +void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path, + Glib::ustring const &string_value, bool default_value, PrefRadioButton* group_member) +{ + _prefs_path = prefs_path; + _value_type = VAL_STRING; + _string_value = string_value; + (void)default_value; + this->set_label(label); + if (group_member) + { + Gtk::RadioButtonGroup rbg = group_member->get_group(); + this->set_group(rbg); + } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring val = prefs->getString(_prefs_path); + if ( !val.empty() ) + this->set_active(val == _string_value); + else + this->set_active( false ); +} + +void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path, + int int_value, bool default_value, PrefRadioButton* group_member) +{ + _prefs_path = prefs_path; + _value_type = VAL_INT; + _int_value = int_value; + this->set_label(label); + if (group_member) + { + Gtk::RadioButtonGroup rbg = group_member->get_group(); + this->set_group(rbg); + } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (default_value) + this->set_active( prefs->getInt(_prefs_path, int_value) == _int_value ); + else + this->set_active( prefs->getInt(_prefs_path, int_value + 1) == _int_value ); +} + +void PrefRadioButton::on_toggled() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (this->get_visible() && this->get_active() ) //only take action if toggled by user (to active) + { + if ( _value_type == VAL_STRING ) + prefs->setString(_prefs_path, _string_value); + else if ( _value_type == VAL_INT ) + prefs->setInt(_prefs_path, _int_value); + } + this->changed_signal.emit(this->get_active()); +} + + +PrefRadioButtons::PrefRadioButtons(const std::vector<PrefItem>& buttons, const Glib::ustring& prefs_path) { + set_spacing(2); + + PrefRadioButton* group = nullptr; + for (auto&& item : buttons) { + auto* btn = Gtk::make_managed<PrefRadioButton>(); + btn->init(item.label, prefs_path, item.int_value, item.is_default, group); + btn->set_tooltip_text(item.tooltip); + add(*btn); + if (!group) group = btn; + } +} + + +void PrefSpinButton::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) +{ + _prefs_path = prefs_path; + _is_int = is_int; + _is_percent = is_percent; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value; + if (is_int) { + if (is_percent) { + value = 100 * prefs->getDoubleLimited(prefs_path, default_value, lower/100.0, upper/100.0); + } else { + value = (double) prefs->getIntLimited(prefs_path, (int) default_value, (int) lower, (int) upper); + } + } else { + value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper); + } + + this->set_range (lower, upper); + this->set_increments (step_increment, 0); + this->set_value (value); + this->set_width_chars(6); + if (is_int) + this->set_digits(0); + else if (step_increment < 0.1) + this->set_digits(4); + else + this->set_digits(2); + +} + +void PrefSpinButton::on_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (this->get_visible()) //only take action if user changed value + { + if (_is_int) { + if (_is_percent) { + prefs->setDouble(_prefs_path, this->get_value()/100.0); + } else { + prefs->setInt(_prefs_path, (int) this->get_value()); + } + } else { + prefs->setDouble(_prefs_path, this->get_value()); + } + } + this->changed_signal.emit(this->get_value()); +} + +void PrefSpinUnit::init(Glib::ustring const &prefs_path, + double lower, double upper, double step_increment, + double default_value, UnitType unit_type, Glib::ustring const &default_unit) +{ + _prefs_path = prefs_path; + _is_percent = (unit_type == UNIT_TYPE_DIMENSIONLESS); + + resetUnitType(unit_type); + setUnit(default_unit); + setRange (lower, upper); /// @fixme this disregards changes of units + setIncrements (step_increment, 0); + if (step_increment < 0.1) { + setDigits(4); + } else { + setDigits(2); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper); + Glib::ustring unitstr = prefs->getUnit(prefs_path); + if (unitstr.length() == 0) { + unitstr = default_unit; + // write the assumed unit to preferences: + prefs->setDoubleUnit(_prefs_path, value, unitstr); + } + setValue(value, unitstr); + + signal_value_changed().connect_notify(sigc::mem_fun(*this, &PrefSpinUnit::on_my_value_changed)); +} + +void PrefSpinUnit::on_my_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (getWidget()->get_visible()) //only take action if user changed value + { + prefs->setDoubleUnit(_prefs_path, getValue(getUnit()->abbr), getUnit()->abbr); + } +} + +const double ZoomCorrRuler::textsize = 7; +const double ZoomCorrRuler::textpadding = 5; + +ZoomCorrRuler::ZoomCorrRuler(int width, int height) : + _unitconv(1.0), + _border(5) +{ + set_size(width, height); +} + +void ZoomCorrRuler::set_size(int x, int y) +{ + _min_width = x; + _height = y; + set_size_request(x + _border*2, y + _border*2); +} + +// The following two functions are borrowed from 2geom's toy-framework-2; if they are useful in +// other locations, we should perhaps make them (or adapted versions of them) publicly available +static void +draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom = false, + double fontsize = ZoomCorrRuler::textsize, std::string fontdesc = "Sans") { + PangoLayout* layout = pango_cairo_create_layout (cr); + pango_layout_set_text(layout, txt, -1); + + // set font and size + std::ostringstream sizestr; + sizestr << fontsize; + fontdesc = fontdesc + " " + sizestr.str(); + PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc.c_str()); + pango_layout_set_font_description(layout, font_desc); + pango_font_description_free (font_desc); + + PangoRectangle logical_extent; + pango_layout_get_pixel_extents(layout, nullptr, &logical_extent); + cairo_move_to(cr, loc[Geom::X], loc[Geom::Y] - (bottom ? logical_extent.height : 0)); + pango_cairo_show_layout(cr, layout); +} + +static void +draw_number(cairo_t *cr, Geom::Point pos, double num) { + std::ostringstream number; + number << num; + draw_text(cr, pos, number.str().c_str(), true); +} + +/* + * \arg dist The distance between consecutive minor marks + * \arg major_interval Number of marks after which to draw a major mark + */ +void +ZoomCorrRuler::draw_marks(Cairo::RefPtr<Cairo::Context> cr, double dist, int major_interval) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + const double zoomcorr = prefs->getDouble("/options/zoomcorrection/value", 1.0); + double mark = 0; + int i = 0; + double step = dist * zoomcorr / _unitconv; + bool draw_minor = true; + if (step <= 0) { + return; + } + else if (step < 2) { + // marks too dense + draw_minor = false; + } + int last_pos = -1; + while (mark <= _drawing_width) { + cr->move_to(mark, _height); + if ((i % major_interval) == 0) { + // don't overcrowd the marks + if (static_cast<int>(mark) > last_pos) { + // major mark + cr->line_to(mark, 0); + Geom::Point textpos(mark + 3, ZoomCorrRuler::textsize + ZoomCorrRuler::textpadding); + draw_number(cr->cobj(), textpos, dist * i); + + last_pos = static_cast<int>(mark) + 1; + } + } else if (draw_minor) { + // minor mark + cr->line_to(mark, ZoomCorrRuler::textsize + 2 * ZoomCorrRuler::textpadding); + } + mark += step; + ++i; + } +} + +bool +ZoomCorrRuler::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) { + Glib::RefPtr<Gdk::Window> window = get_window(); + + int w = window->get_width(); + _drawing_width = w - _border * 2; + + auto context = get_style_context(); + Gdk::RGBA fg = context->get_color(get_state_flags()); + + context->render_background(cr, 0, 0, w, _height + _border * 2); + + cr->set_line_width(1); + cr->set_source_rgb(fg.get_red(), fg.get_green(), fg.get_blue()); + + cr->translate(_border, _border); // so that we have a small white border around the ruler + cr->move_to (0, _height); + cr->line_to (_drawing_width, _height); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring abbr = prefs->getString("/options/zoomcorrection/unit"); + if (abbr == "cm") { + draw_marks(cr, 0.1, 10); + } else if (abbr == "in") { + draw_marks(cr, 0.25, 4); + } else if (abbr == "mm") { + draw_marks(cr, 10, 10); + } else if (abbr == "pc") { + draw_marks(cr, 1, 10); + } else if (abbr == "pt") { + draw_marks(cr, 10, 10); + } else if (abbr == "px") { + draw_marks(cr, 10, 10); + } else { + draw_marks(cr, 1, 1); + } + cr->stroke(); + + return true; +} + + +void +ZoomCorrRulerSlider::on_slider_value_changed() +{ + if (this->get_visible() || freeze) //only take action if user changed value + { + freeze = true; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble("/options/zoomcorrection/value", _slider->get_value() / 100.0); + _sb->set_value(_slider->get_value()); + _ruler.queue_draw(); + freeze = false; + } +} + +void +ZoomCorrRulerSlider::on_spinbutton_value_changed() +{ + if (this->get_visible() || freeze) //only take action if user changed value + { + freeze = true; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble("/options/zoomcorrection/value", _sb->get_value() / 100.0); + _slider->set_value(_sb->get_value()); + _ruler.queue_draw(); + freeze = false; + } +} + +void +ZoomCorrRulerSlider::on_unit_changed() { + if (!_unit.get_sensitive()) { + // when the unit menu is initialized, the unit is set to the default but + // it needs to be reset later so we don't perform the change in this case + return; + } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString("/options/zoomcorrection/unit", _unit.getUnitAbbr()); + double conv = _unit.getConversion(_unit.getUnitAbbr(), "px"); + _ruler.set_unit_conversion(conv); + if (_ruler.get_visible()) { + _ruler.queue_draw(); + } +} + +bool ZoomCorrRulerSlider::on_mnemonic_activate ( bool group_cycling ) +{ + return _sb->mnemonic_activate ( group_cycling ); +} + + +void +ZoomCorrRulerSlider::init(int ruler_width, int ruler_height, double lower, double upper, + double step_increment, double page_increment, double default_value) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value = prefs->getDoubleLimited("/options/zoomcorrection/value", default_value, lower, upper) * 100.0; + + freeze = false; + + _ruler.set_size(ruler_width, ruler_height); + + _slider = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL)); + + _slider->set_size_request(_ruler.width(), -1); + _slider->set_range (lower, upper); + _slider->set_increments (step_increment, page_increment); + _slider->set_value (value); + _slider->set_digits(2); + + _slider->signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_slider_value_changed)); + _sb = Gtk::manage(new Inkscape::UI::Widget::SpinButton()); + _sb->signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_spinbutton_value_changed)); + _unit.signal_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_unit_changed)); + + _sb->set_range (lower, upper); + _sb->set_increments (step_increment, 0); + _sb->set_value (value); + _sb->set_digits(2); + _sb->set_halign(Gtk::ALIGN_CENTER); + _sb->set_valign(Gtk::ALIGN_END); + + _unit.set_sensitive(false); + _unit.setUnitType(UNIT_TYPE_LINEAR); + _unit.set_sensitive(true); + _unit.setUnit(prefs->getString("/options/zoomcorrection/unit")); + _unit.set_halign(Gtk::ALIGN_CENTER); + _unit.set_valign(Gtk::ALIGN_END); + + _slider->set_hexpand(true); + _ruler.set_hexpand(true); + auto table = Gtk::manage(new Gtk::Grid()); + table->attach(*_slider, 0, 0, 1, 1); + table->attach(*_sb, 1, 0, 1, 1); + table->attach(_ruler, 0, 1, 1, 1); + table->attach(_unit, 1, 1, 1, 1); + + pack_start(*table, Gtk::PACK_SHRINK); +} + +void +PrefSlider::on_slider_value_changed() +{ + if (this->get_visible() || freeze) //only take action if user changed value + { + freeze = true; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble(_prefs_path, _slider->get_value()); + if (_sb) _sb->set_value(_slider->get_value()); + freeze = false; + } +} + +void +PrefSlider::on_spinbutton_value_changed() +{ + if (this->get_visible() || freeze) //only take action if user changed value + { + freeze = true; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (_sb) { + prefs->setDouble(_prefs_path, _sb->get_value()); + _slider->set_value(_sb->get_value()); + } + freeze = false; + } +} + +bool PrefSlider::on_mnemonic_activate ( bool group_cycling ) +{ + return _sb ? _sb->mnemonic_activate ( group_cycling ) : false; +} + +void +PrefSlider::init(Glib::ustring const &prefs_path, + double lower, double upper, double step_increment, double page_increment, double default_value, int digits) +{ + _prefs_path = prefs_path; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper); + + freeze = false; + + _slider = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL)); + + _slider->set_range (lower, upper); + _slider->set_increments (step_increment, page_increment); + _slider->set_value (value); + _slider->set_digits(digits); + _slider->signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_slider_value_changed)); + if (_spin) { + _sb = Gtk::manage(new Inkscape::UI::Widget::SpinButton()); + _sb->signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_spinbutton_value_changed)); + _sb->set_range (lower, upper); + _sb->set_increments (step_increment, 0); + _sb->set_value (value); + _sb->set_digits(digits); + _sb->set_halign(Gtk::ALIGN_CENTER); + _sb->set_valign(Gtk::ALIGN_END); + } + + auto table = Gtk::manage(new Gtk::Grid()); + _slider->set_hexpand(); + table->attach(*_slider, 0, 0, 1, 1); + if (_sb) table->attach(*_sb, 1, 0, 1, 1); + + this->pack_start(*table, Gtk::PACK_EXPAND_WIDGET); +} + +void PrefCombo::init(Glib::ustring const &prefs_path, + Glib::ustring const labels[], int const values[], int num_items, int default_value) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int row = 0; + int value = prefs->getInt(_prefs_path, default_value); + + for (int i = 0 ; i < num_items; ++i) + { + this->append(labels[i]); + _values.push_back(values[i]); + if (value == values[i]) + row = i; + } + this->set_active(row); +} + +void PrefCombo::init(Glib::ustring const &prefs_path, + Glib::ustring labels[], Glib::ustring values[], int num_items, Glib::ustring default_value) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int row = 0; + Glib::ustring value = prefs->getString(_prefs_path); + if(value.empty()) + { + value = default_value; + } + + for (int i = 0 ; i < num_items; ++i) + { + this->append(labels[i]); + _ustr_values.push_back(values[i]); + if (value == values[i]) + row = i; + } + this->set_active(row); +} + +void PrefCombo::init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, std::vector<int> values, + int default_value) +{ + size_t labels_size = labels.size(); + size_t values_size = values.size(); + if (values_size != labels_size) { + std::cerr << "PrefCombo::" + << "Different number of values/labels in " << prefs_path.raw() << std::endl; + return; + } + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int row = 0; + int value = prefs->getInt(_prefs_path, default_value); + + for (int i = 0; i < labels_size; ++i) { + this->append(labels[i]); + _values.push_back(values[i]); + if (value == values[i]) + row = i; + } + this->set_active(row); +} + +void PrefCombo::init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, + std::vector<Glib::ustring> values, Glib::ustring default_value) +{ + size_t labels_size = labels.size(); + size_t values_size = values.size(); + if (values_size != labels_size) { + std::cerr << "PrefCombo::" + << "Different number of values/labels in " << prefs_path.raw() << std::endl; + return; + } + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int row = 0; + Glib::ustring value = prefs->getString(_prefs_path); + if (value.empty()) { + value = default_value; + } + + for (int i = 0; i < labels_size; ++i) { + this->append(labels[i]); + _ustr_values.push_back(values[i]); + if (value == values[i]) + row = i; + } + this->set_active(row); +} + +void PrefCombo::init(Glib::ustring const &prefs_path, + std::vector<std::pair<Glib::ustring, Glib::ustring>> labels_and_values, + Glib::ustring default_value) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring value = prefs->getString(_prefs_path); + if (value.empty()) { + value = default_value; + } + + int row = 0; + int i = 0; + for (auto entry : labels_and_values) { + this->append(entry.first); + _ustr_values.push_back(entry.second); + if (value == entry.second) { + row = i; + } + ++i; + } + this->set_active(row); +} + +void PrefCombo::on_changed() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if(!_values.empty()) + { + prefs->setInt(_prefs_path, _values[this->get_active_row_number()]); + } + else + { + prefs->setString(_prefs_path, _ustr_values[this->get_active_row_number()]); + } + } +} + +void PrefEntryButtonHBox::init(Glib::ustring const &prefs_path, + bool visibility, Glib::ustring const &default_string) +{ + _prefs_path = prefs_path; + _default_string = default_string; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + relatedEntry = new Gtk::Entry(); + relatedButton = new Gtk::Button(_("Reset")); + relatedEntry->set_invisible_char('*'); + relatedEntry->set_visibility(visibility); + relatedEntry->set_text(prefs->getString(_prefs_path)); + this->pack_start(*relatedEntry); + this->pack_start(*relatedButton); + relatedButton->signal_clicked().connect( + sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedButtonClickedCallback)); + relatedEntry->signal_changed().connect( + sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedEntryChangedCallback)); +} + +void PrefEntryButtonHBox::onRelatedEntryChangedCallback() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, relatedEntry->get_text()); + } +} + +void PrefEntryButtonHBox::onRelatedButtonClickedCallback() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, _default_string); + relatedEntry->set_text(_default_string); + } +} + +bool PrefEntryButtonHBox::on_mnemonic_activate ( bool group_cycling ) +{ + return relatedEntry->mnemonic_activate ( group_cycling ); +} + +void PrefEntryFileButtonHBox::init(Glib::ustring const &prefs_path, + bool visibility) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + relatedEntry = new Gtk::Entry(); + relatedEntry->set_invisible_char('*'); + relatedEntry->set_visibility(visibility); + relatedEntry->set_text(prefs->getString(_prefs_path)); + + relatedButton = new Gtk::Button(); + Gtk::Box* pixlabel = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 3); + Gtk::Image *im = sp_get_icon_image("applications-graphics", Gtk::ICON_SIZE_BUTTON); + pixlabel->pack_start(*im); + Gtk::Label *l = new Gtk::Label(); + l->set_markup_with_mnemonic(_("_Browse...")); + pixlabel->pack_start(*l); + relatedButton->add(*pixlabel); + + this->pack_end(*relatedButton, false, false, 4); + this->pack_start(*relatedEntry, true, true, 0); + + relatedButton->signal_clicked().connect( + sigc::mem_fun(*this, &PrefEntryFileButtonHBox::onRelatedButtonClickedCallback)); + relatedEntry->signal_changed().connect( + sigc::mem_fun(*this, &PrefEntryFileButtonHBox::onRelatedEntryChangedCallback)); +} + +void PrefEntryFileButtonHBox::onRelatedEntryChangedCallback() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, relatedEntry->get_text()); + } +} + +static Inkscape::UI::Dialog::FileOpenDialog * selectPrefsFileInstance = nullptr; + +void PrefEntryFileButtonHBox::onRelatedButtonClickedCallback() +{ + if (this->get_visible()) //only take action if user changed value + { + //# Get the current directory for finding files + static Glib::ustring open_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + + Glib::ustring attr = prefs->getString(_prefs_path); + if (!attr.empty()) open_path = attr; + + //# Test if the open_path directory exists + if (!Inkscape::IO::file_test(open_path.c_str(), + (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) + open_path = ""; + +#ifdef _WIN32 + //# If no open path, default to our win32 documents folder + if (open_path.empty()) + { + // The path to the My Documents folder is read from the + // value "HKEY_CURRENT_USER\Software\Windows\CurrentVersion\Explorer\Shell Folders\Personal" + HKEY key = NULL; + if(RegOpenKeyExA(HKEY_CURRENT_USER, + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", + 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) + { + WCHAR utf16path[_MAX_PATH]; + DWORD value_type; + DWORD data_size = sizeof(utf16path); + if(RegQueryValueExW(key, L"Personal", NULL, &value_type, + (BYTE*)utf16path, &data_size) == ERROR_SUCCESS) + { + g_assert(value_type == REG_SZ); + gchar *utf8path = g_utf16_to_utf8( + (const gunichar2*)utf16path, -1, NULL, NULL, NULL); + if(utf8path) + { + open_path = Glib::ustring(utf8path); + g_free(utf8path); + } + } + } + } +#endif + + //# If no open path, default to our home directory + if (open_path.empty()) + { + open_path = g_get_home_dir(); + open_path.append(G_DIR_SEPARATOR_S); + } + + //# Create a dialog + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!selectPrefsFileInstance) { + selectPrefsFileInstance = + Inkscape::UI::Dialog::FileOpenDialog::create( + *desktop->getToplevel(), + open_path, + Inkscape::UI::Dialog::EXE_TYPES, + _("Select a bitmap editor")); + } + + //# Show the dialog + bool const success = selectPrefsFileInstance->show(); + + if (!success) { + return; + } + + //# User selected something. Get name and type + Glib::ustring fileName = selectPrefsFileInstance->getFilename(); + + if (!fileName.empty()) + { + Glib::ustring newFileName = Glib::filename_to_utf8(fileName); + + if ( newFileName.size() > 0) + open_path = newFileName; + else + g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" ); + + prefs->setString(_prefs_path, open_path); + } + + relatedEntry->set_text(fileName); + } +} + +bool PrefEntryFileButtonHBox::on_mnemonic_activate ( bool group_cycling ) +{ + return relatedEntry->mnemonic_activate ( group_cycling ); +} + +void PrefOpenFolder::init(Glib::ustring const &entry_string, Glib::ustring const &tooltip) +{ + relatedEntry = new Gtk::Entry(); + relatedButton = new Gtk::Button(); + Gtk::Box *pixlabel = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 3); + Gtk::Image *im = sp_get_icon_image("document-open", Gtk::ICON_SIZE_BUTTON); + pixlabel->pack_start(*im); + Gtk::Label *l = new Gtk::Label(); + l->set_markup_with_mnemonic(_("Open")); + pixlabel->pack_start(*l); + relatedButton->add(*pixlabel); + relatedButton->set_tooltip_text(tooltip); + relatedEntry->set_text(entry_string); + relatedEntry->set_sensitive(false); + this->pack_end(*relatedButton, false, false, 4); + this->pack_start(*relatedEntry, true, true, 0); + relatedButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefOpenFolder::onRelatedButtonClickedCallback)); +} + +void PrefOpenFolder::onRelatedButtonClickedCallback() +{ + g_mkdir_with_parents(relatedEntry->get_text().c_str(), 0700); + // https://stackoverflow.com/questions/42442189/how-to-open-spawn-a-file-with-glib-gtkmm-in-windows +#ifdef _WIN32 + ShellExecute(NULL, "open", relatedEntry->get_text().c_str(), NULL, NULL, SW_SHOWDEFAULT); +#elif defined(__APPLE__) + std::vector<std::string> argv = { "open", relatedEntry->get_text().raw() }; + Glib::spawn_async("", argv, Glib::SpawnFlags::SPAWN_SEARCH_PATH); +#else + gchar *path = g_filename_to_uri(relatedEntry->get_text().c_str(), NULL, NULL); + std::vector<std::string> argv = { "xdg-open", path }; + Glib::spawn_async("", argv, Glib::SpawnFlags::SPAWN_SEARCH_PATH); + g_free(path); +#endif +} + +void PrefFileButton::init(Glib::ustring const &prefs_path) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + select_filename(Glib::filename_from_utf8(prefs->getString(_prefs_path))); + + signal_selection_changed().connect(sigc::mem_fun(*this, &PrefFileButton::onFileChanged)); +} + +void PrefFileButton::onFileChanged() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, Glib::filename_to_utf8(get_filename())); +} + +void PrefEntry::init(Glib::ustring const &prefs_path, bool visibility) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->set_invisible_char('*'); + this->set_visibility(visibility); + this->set_text(prefs->getString(_prefs_path)); +} + +void PrefEntry::on_changed() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, this->get_text()); + } +} + +void PrefEntryFile::on_changed() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, Glib::filename_to_utf8(this->get_text())); + } +} + +void PrefMultiEntry::init(Glib::ustring const &prefs_path, int height) +{ + // TODO: Figure out if there's a way to specify height in lines instead of px + // and how to obtain a reasonable default width if 'expand_widget' is not used + set_size_request(100, height); + set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + set_shadow_type(Gtk::SHADOW_IN); + + add(_text); + + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring value = prefs->getString(_prefs_path); + value = Glib::Regex::create("\\|")->replace_literal(value, 0, "\n", (Glib::RegexMatchFlags)0); + _text.get_buffer()->set_text(value); + _text.get_buffer()->signal_changed().connect(sigc::mem_fun(*this, &PrefMultiEntry::on_changed)); +} + +void PrefMultiEntry::on_changed() +{ + if (get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring value = _text.get_buffer()->get_text(); + value = Glib::Regex::create("\\n")->replace_literal(value, 0, "|", (Glib::RegexMatchFlags)0); + prefs->setString(_prefs_path, value); + } +} + +void PrefColorPicker::init(Glib::ustring const &label, Glib::ustring const &prefs_path, + guint32 default_rgba) +{ + _prefs_path = prefs_path; + _title = label; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->setRgba32( prefs->getInt(_prefs_path, (int)default_rgba) ); +} + +void PrefColorPicker::on_changed (guint32 rgba) +{ + if (this->get_visible()) //only take action if the user toggled it + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setInt(_prefs_path, (int) rgba); + } +} + +void PrefUnit::init(Glib::ustring const &prefs_path) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + setUnitType(UNIT_TYPE_LINEAR); + setUnit(prefs->getString(_prefs_path)); +} + +void PrefUnit::on_changed() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, getUnitAbbr()); + } +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |