// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * Combobox for selecting dash patterns - implementation. */ /* Author: * Lauris Kaplinski * bulia byak * Maximilian Albert * * Copyright (C) 2002 Lauris Kaplinski * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "dash-selector.h" #include #include #include <2geom/coord.h> #include #include "preferences.h" #include "display/cairo-utils.h" #include "style.h" #include "ui/dialog-events.h" namespace Inkscape { namespace UI { namespace Widget { gchar const *const DashSelector::_prefs_path = "/palette/dashes"; static std::vector> s_dashes; DashSelector::DashSelector() : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4), _preview_width(100), _preview_height(16), _preview_lineheight(2) { // TODO: find something more sensible here!! init_dashes(); _dash_store = Gtk::ListStore::create(dash_columns); _dash_combo.set_model(_dash_store); _dash_combo.pack_start(_image_renderer); _dash_combo.set_cell_data_func(_image_renderer, sigc::mem_fun(*this, &DashSelector::prepareImageRenderer)); _dash_combo.set_tooltip_text(_("Dash pattern")); _dash_combo.show(); _dash_combo.signal_changed().connect( sigc::mem_fun(*this, &DashSelector::on_selection) ); // show dashes in two columns to eliminate or minimize scrolling _dash_combo.set_wrap_width(2); this->pack_start(_dash_combo, true, true, 0); _offset = Gtk::Adjustment::create(0.0, 0.0, 1000.0, 0.1, 1.0, 0.0); _offset->signal_value_changed().connect(sigc::mem_fun(*this, &DashSelector::offset_value_changed)); _sb = new Inkscape::UI::Widget::SpinButton(_offset, 0.1, 2); _sb->set_tooltip_text(_("Pattern offset")); sp_dialog_defocus_on_enter_cpp(_sb); _sb->set_width_chars(4); _sb->show(); this->pack_start(*_sb, false, false, 0); for (std::size_t i = 0; i < s_dashes.size(); ++i) { Gtk::TreeModel::Row row = *(_dash_store->append()); row[dash_columns.dash] = i; } _pattern = &s_dashes.front(); } DashSelector::~DashSelector() { // FIXME: for some reason this doesn't get called; does the call to manage() in // sp_stroke_style_line_widget_new() not processed correctly? } void DashSelector::prepareImageRenderer( Gtk::TreeModel::const_iterator const &row ) { // dashes are rendered on the fly to adapt to current theme colors std::size_t index = (*row)[dash_columns.dash]; Cairo::RefPtr surface; if (index == 1) { // add the custom one as a second option; it'll show up at the top of second column // TRANSLATORS: 'Custom' here means, that user-defined dash pattern is specified in an entry box surface = sp_text_to_pixbuf(_("Custom")); } else if (index < s_dashes.size()) { // add the dash to the combobox surface = sp_dash_to_pixbuf(s_dashes[index]); } else { surface = Cairo::RefPtr(new Cairo::Surface(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1))); g_warning("No surface in prepareImageRenderer."); } _image_renderer.property_surface() = surface; } static std::vector map_values(const std::vector& values) { std::vector out; out.reserve(values.size()); for (auto&& v : values) { out.push_back(v.value); } return out; } void DashSelector::init_dashes() { if (s_dashes.empty()) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); std::vector dash_prefs = prefs->getAllDirs(_prefs_path); if (!dash_prefs.empty()) { SPStyle style; s_dashes.reserve(dash_prefs.size() + 1); for (auto & dash_pref : dash_prefs) { style.readFromPrefs( dash_pref ); if (!style.stroke_dasharray.values.empty()) { s_dashes.emplace_back(map_values(style.stroke_dasharray.values)); } else { s_dashes.emplace_back(std::vector()); } } } else { g_warning("Missing stock dash definitions. DashSelector::init_dashes."); // This code may never execute - a new preferences.xml is created for a new user. Maybe if the user deletes dashes from preferences.xml? s_dashes.emplace_back(std::vector()); } std::vector custom {1, 2, 1, 4}; // 'custom' dashes second on the list, so they are at the top of the second column in a combo box s_dashes.insert(s_dashes.begin() + 1, custom); } } void DashSelector::set_dash(const std::vector& dash, double offset) { int pos = -1; // Allows custom patterns to remain unscathed by this. double delta = std::accumulate(dash.begin(), dash.end(), 0.0) / (10000.0 * (dash.empty() ? 1 : dash.size())); int index = 0; for (auto&& pattern : s_dashes) { if (dash.size() == pattern.size() && std::equal(dash.begin(), dash.end(), pattern.begin(), [=](double a, double b) { return Geom::are_near(a, b, delta); })) { pos = index; break; } ++index; } if (pos >= 0) { _pattern = &s_dashes.at(pos); _dash_combo.set_active(pos); _offset->set_value(offset); } else { // Hit a custom pattern in the SVG, write it into the combobox. pos = 1; // the one slot for custom patterns _pattern = &s_dashes[pos]; _pattern->assign(dash.begin(), dash.end()); _dash_combo.set_active(pos); _offset->set_value(offset); } } const std::vector& DashSelector::get_dash(double* offset) const { if (offset) *offset = _offset->get_value(); return *_pattern; } double DashSelector::get_offset() { return _offset ? _offset->get_value() : 0.0; } /** * Fill a pixbuf with the dash pattern using standard cairo drawing */ Cairo::RefPtr DashSelector::sp_dash_to_pixbuf(const std::vector& pattern) { auto device_scale = get_scale_factor(); auto height = _preview_height * device_scale; auto width = _preview_width * device_scale; cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); cairo_t *ct = cairo_create(s); auto context = get_style_context(); Gdk::RGBA fg = context->get_color(get_state_flags()); cairo_set_line_width (ct, _preview_lineheight * device_scale); cairo_scale (ct, _preview_lineheight * device_scale, 1); cairo_move_to (ct, 0, height/2); cairo_line_to (ct, width, height/2); cairo_set_dash(ct, pattern.data(), pattern.size(), 0); cairo_set_source_rgb(ct, fg.get_red(), fg.get_green(), fg.get_blue()); cairo_stroke (ct); cairo_destroy(ct); cairo_surface_flush(s); cairo_surface_set_device_scale(s, device_scale, device_scale); return Cairo::RefPtr(new Cairo::Surface(s)); } /** * Fill a pixbuf with a text label using standard cairo drawing */ Cairo::RefPtr DashSelector::sp_text_to_pixbuf(const char* text) { auto device_scale = get_scale_factor(); cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, _preview_width * device_scale, _preview_height * device_scale); cairo_t *ct = cairo_create(s); cairo_select_font_face (ct, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); // todo: how to find default font face and size? cairo_set_font_size (ct, 12 * device_scale); auto context = get_style_context(); Gdk::RGBA fg = context->get_color(get_state_flags()); cairo_set_source_rgb(ct, fg.get_red(), fg.get_green(), fg.get_blue()); cairo_move_to (ct, 16.0 * device_scale, 13.0 * device_scale); cairo_show_text (ct, text); cairo_destroy(ct); cairo_surface_flush(s); cairo_surface_set_device_scale(s, device_scale, device_scale); return Cairo::RefPtr(new Cairo::Surface(s)); } void DashSelector::on_selection() { _pattern = &s_dashes.at(_dash_combo.get_active()->get_value(dash_columns.dash)); changed_signal.emit(); } void DashSelector::offset_value_changed() { Glib::ustring offset = _("Pattern offset"); offset += " ("; offset += Glib::ustring::format(_sb->get_value()); offset += ")"; _sb->set_tooltip_text(offset.c_str()); changed_signal.emit(); } } // 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 :