summaryrefslogtreecommitdiffstats
path: root/src/ui/widget/dash-selector.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/widget/dash-selector.cpp')
-rw-r--r--src/ui/widget/dash-selector.cpp259
1 files changed, 259 insertions, 0 deletions
diff --git a/src/ui/widget/dash-selector.cpp b/src/ui/widget/dash-selector.cpp
new file mode 100644
index 0000000..c3f40ab
--- /dev/null
+++ b/src/ui/widget/dash-selector.cpp
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Combobox for selecting dash patterns - implementation.
+ */
+/* Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ *
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "dash-selector.h"
+
+#include <cstring>
+#include <glibmm/i18n.h>
+#include <2geom/coord.h>
+#include <numeric>
+
+#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<std::vector<double>> 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<Cairo::Surface> surface;
+ if (index == 1) {
+ // add the custom one as a second option; it'll show up at the top of second column
+ surface = sp_text_to_pixbuf((char *)"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<Cairo::Surface>(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<double> map_values(const std::vector<SPILength>& values) {
+ std::vector<double> 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<Glib::ustring> 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<double>());
+ }
+ }
+ } 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<double>());
+ }
+
+ std::vector<double> 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<double>& 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<double>& 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<Cairo::Surface> DashSelector::sp_dash_to_pixbuf(const std::vector<double>& 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<Cairo::Surface>(new Cairo::Surface(s));
+}
+
+/**
+ * Fill a pixbuf with a text label using standard cairo drawing
+ */
+Cairo::RefPtr<Cairo::Surface> 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<Cairo::Surface>(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 :