Inkscape
Vector Graphics Editor
dash-selector.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * Tavmjong Bah (Rewrite to use Gio::ListStore and Gtk::GridView).
8 *
9 * Original authors:
10 * Lauris Kaplinski <lauris@kaplinski.com>
11 * bulia byak <buliabyak@users.sf.net>
12 * Maximilian Albert <maximilian.albert@gmail.com> (gtkmm-ification)
13 *
14 * Copyright (C) 2002 Lauris Kaplinski
15 * Copyright (C) 2023 Tavmjong Bah
16 *
17 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
18 */
19
20#include "dash-selector.h"
21
22#include <iostream>
23#include <numeric> // std::accumulate
24#include <vector>
25
26#include <giomm.h>
27#include <glibmm/i18n.h>
28#include <glibmm/regex.h>
29#include <gdkmm/general.h>
30#include <gtkmm/drawingarea.h>
31#include <gtkmm/gridview.h>
32#include <gtkmm/image.h>
33#include <gtkmm/menubutton.h>
34#include <gtkmm/popover.h>
35#include <gtkmm/signallistitemfactory.h>
36#include <gtkmm/singleselection.h>
37
38#include <2geom/coord.h> // Geom::are_near
39
40#include "preferences.h"
41#include "style.h" // Read dash patterns from preferences.
42#include "ui/dialog-events.h"
44
45namespace Inkscape::UI::Widget {
46namespace {
47
48constexpr auto DRAWING_AREA_WIDTH = 100;
49constexpr auto DRAWING_AREA_HEIGHT = 16;
50
51std::vector<std::vector<double>> get_dash_patterns()
52{
53 std::vector<std::vector<double>> dash_patterns;
54
55 auto prefs = Preferences::get();
56 auto const dash_prefs = prefs->getAllDirs("/palette/dashes");
57
58 SPStyle style;
59 std::vector<double> dash_pattern;
60 for (auto const &dash_pref : dash_prefs) {
61 style.readFromPrefs(dash_pref);
62 dash_pattern.clear();
63 for (auto const &v : style.stroke_dasharray.values) {
64 dash_pattern.push_back(v.value);
65 }
66 dash_patterns.emplace_back(std::move(dash_pattern));
67 }
68
69 return dash_patterns;
70}
71
72class DashPattern : public Glib::Object
73{
74public:
75 std::vector<double> dash_pattern;
76 bool custom = false;
77
78 static Glib::RefPtr<DashPattern> create(std::vector<double> dash_pattern) {
79 return Glib::make_refptr_for_instance<DashPattern>(new DashPattern(std::move(dash_pattern)));
80 }
81
82private:
83 DashPattern(std::vector<double> dash_pattern)
85 {}
86};
87
88} // namespace
89
91 : Gtk::Box(Gtk::Orientation::HORIZONTAL, 4)
92{
93 set_name("DashSelector");
94 set_hexpand();
95 set_halign(Gtk::Align::FILL);
96 set_valign(Gtk::Align::CENTER);
97
98 // Create liststore
99 auto dash_patterns = get_dash_patterns();
100 auto liststore = Gio::ListStore<DashPattern>::create();
101 for (auto const &dash_pattern : dash_patterns) {
102 liststore->append(DashPattern::create(dash_pattern));
103 }
104
105 // Add custom pattern slot (upper right corner).
106 auto custom_pattern = DashPattern::create({1, 2, 1, 4});
107 custom_pattern->custom = true;
108 liststore->insert(1, custom_pattern);
109
111 auto factory = Gtk::SignalListItemFactory::create();
112 factory->signal_setup().connect(sigc::mem_fun(*this, &DashSelector::setup_listitem_cb));
113 factory->signal_bind() .connect(sigc::mem_fun(*this, &DashSelector::bind_listitem_cb));
114
115 auto gridview = Gtk::make_managed<Gtk::GridView>(selection, factory);
116 gridview->set_min_columns(2);
117 gridview->set_max_columns(2);
118 gridview->set_single_click_activate(true);
119 gridview->signal_activate().connect(sigc::bind<0>(sigc::mem_fun(*this, &DashSelector::activate), gridview));
120
121 popover = Gtk::make_managed<Gtk::Popover>();
122 popover->set_child(*gridview);
123
124 // Menubutton
125 drawing_area = Gtk::make_managed<Gtk::DrawingArea>();
126 drawing_area->set_content_width(DRAWING_AREA_WIDTH);
127 drawing_area->set_content_height(DRAWING_AREA_HEIGHT);
128 drawing_area->set_draw_func(sigc::bind(sigc::mem_fun(*this, &DashSelector::draw_pattern), std::vector<double>{}));
129
130 auto menubutton = Gtk::make_managed<Gtk::MenuButton>();
131 menubutton->set_child(*drawing_area);
132 gtk_menu_button_set_always_show_arrow(menubutton->gobj(), true); // No C++ API!
133 menubutton->set_popover(*popover);
134
135 append(*menubutton);
136
137 // Offset spinbutton
138 adjustment = Gtk::Adjustment::create(0.0, 0.0, 1000.0, 0.1, 1.0, 0.0);
139 adjustment->signal_value_changed().connect([this] {
140 offset = adjustment->get_value();
141 changed_signal.emit();
142 });
143 auto spinbutton = Gtk::make_managed<Inkscape::UI::Widget::SpinButton>(adjustment, 0.1, 2); // Climb rate, digits.
144 spinbutton->set_tooltip_text(_("Dash pattern offset"));
145 spinbutton->set_width_chars(5);
146 sp_dialog_defocus_on_enter(*spinbutton);
147
148 append(*spinbutton);
149}
150
152
153// Set dash pattern from outside class.
154void DashSelector::set_dash_pattern(std::vector<double> const &new_dash_pattern, double new_offset)
155{
156 // See if there is already a dash pattern that matches (within delta).
157
158 // Set the criteria for matching (sum of dash lengths / number of dashes):
159 double const delta = std::accumulate(new_dash_pattern.begin(), new_dash_pattern.end(), 0.0)
160 / (10000.0 * (new_dash_pattern.empty() ? 1.0 : new_dash_pattern.size()));
161
162 int position = 1; // Position for custom dash patterns.
163 auto const item_count = selection->get_n_items();
164 for (int index = 0; index < item_count; ++index) {
165 auto const &item = dynamic_cast<DashPattern &>(*selection->get_object(index));
166 if (std::equal(new_dash_pattern.begin(), new_dash_pattern.end(), item.dash_pattern.begin(), item.dash_pattern.end(),
167 [=] (double a, double b) { return Geom::are_near(a, b, delta); }))
168 {
169 position = index;
170 break;
171 }
172 }
173
174 // Set selected pattern in GridView.
175 selection->set_selected(position);
176
177 if (position == 1) {
178 // Custom pattern!
179
180 // Update custom dash patterns.
181 auto &item = dynamic_cast<DashPattern &>(*selection->get_object(position));
182 item.dash_pattern.assign(new_dash_pattern.begin(), new_dash_pattern.end());
183 }
184
185 // Update MenuButton DrawingArea and offset.
186 dash_pattern = new_dash_pattern;
187 offset = new_offset;
188 update(position);
189}
190
191// Update display, offset. Common code for when dash changes (must not trigger signal!).
192void DashSelector::update(int position)
193{
194 // Update MenuButton DrawingArea.
195 if (position == 1) {
196 drawing_area->set_draw_func(sigc::mem_fun(*this, &DashSelector::draw_text));
197 } else {
198 drawing_area->set_draw_func(sigc::bind(sigc::mem_fun(*this, &DashSelector::draw_pattern), dash_pattern));
199 }
200
201 // If no dash pattern, reset offset to zero.
202 offset = dash_pattern.empty() ? 0.0 : offset;
203 adjustment->set_value(offset);
204}
205
206// User selected new dash pattern in GridView.
207void DashSelector::activate(Gtk::GridView *grid, unsigned position)
208{
209 auto &model = dynamic_cast<Gtk::SingleSelection &>(*grid->get_model());
210 auto const &item = dynamic_cast<DashPattern &>(*model.get_selected_item());
211
212 // Update MenuButton DrawingArea, offset, emit changed signal.
213 dash_pattern = item.dash_pattern;
214 update(position);
215 popover->popdown();
216
217 changed_signal.emit(); // Ensure Pattern widget updated.
218}
219
220void DashSelector::setup_listitem_cb(Glib::RefPtr<Gtk::ListItem> const &list_item)
221{
222 auto drawing_area = Gtk::make_managed<Gtk::DrawingArea>();
223 drawing_area->set_content_width(DRAWING_AREA_WIDTH);
224 drawing_area->set_content_height(DRAWING_AREA_HEIGHT);
225 list_item->set_child(*drawing_area);
226}
227
228void DashSelector::bind_listitem_cb(Glib::RefPtr<Gtk::ListItem> const &list_item)
229{
230 auto const &dash_pattern = dynamic_cast<DashPattern &>(*list_item->get_item());
231 auto &drawing_area = dynamic_cast<Gtk::DrawingArea &>(*list_item->get_child());
232
233 if (dash_pattern.custom) {
234 drawing_area.set_draw_func(sigc::mem_fun(*this, &DashSelector::draw_text));
235 } else {
236 drawing_area.set_draw_func(sigc::bind(sigc::mem_fun(*this, &DashSelector::draw_pattern), dash_pattern.dash_pattern));
237 }
238}
239
240// Draw dash pattern in a Gtk::DrawingArea.
241void DashSelector::draw_pattern(Cairo::RefPtr<Cairo::Context> const &cr, int width, int height,
242 std::vector<double> const &pattern)
243{
244 cr->set_line_width(2);
245 cr->scale(2, 1);
246 cr->set_dash(pattern, 0);
247 Gdk::Cairo::set_source_rgba(cr, get_color());
248 cr->move_to(0, height/2);
249 cr->line_to(width, height / 2);
250 cr->stroke();
251}
252
253// Draw text in a Gtk::DrawingArea.
254void DashSelector::draw_text(Cairo::RefPtr<Cairo::Context> const &cr, int width, int height)
255{
256 cr->select_font_face("Sans", Cairo::ToyFontFace::Slant::NORMAL, Cairo::ToyFontFace::Weight::NORMAL);
257 cr->set_font_size(12);
258 Gdk::Cairo::set_source_rgba(cr, get_color());
259 cr->move_to(16.0, (height + 12) / 2.0);
260 cr->show_text(_("Custom"));
261}
262
263} // namespace Inkscape::UI::Widget
264
265/*
266 Local Variables:
267 mode:c++
268 c-file-style:"stroustrup"
269 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
270 indent-tabs-mode:nil
271 fill-column:99
272 End:
273*/
274// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
static Preferences * get()
Access the singleton Preferences object.
Definition: preferences.h:599
void setup_listitem_cb(const Glib::RefPtr< Gtk::ListItem > &list_item)
std::vector< double > dash_pattern
Definition: dash-selector.h:62
void set_dash_pattern(const std::vector< double > &dash, double offset)
void activate(Gtk::GridView *grid, unsigned int position)
sigc::signal< void()> changed_signal
Definition: dash-selector.h:46
void draw_text(const Cairo::RefPtr< Cairo::Context > &cr, int width, int height)
Glib::RefPtr< Gtk::SingleSelection > selection
Definition: dash-selector.h:66
void draw_pattern(const Cairo::RefPtr< Cairo::Context > &cr, int width, int height, const std::vector< double > &pattern)
Glib::RefPtr< Gtk::Adjustment > adjustment
Definition: dash-selector.h:69
void bind_listitem_cb(const Glib::RefPtr< Gtk::ListItem > &list_item)
An SVG style object.
Definition: style.h:45
T< SPAttr::STROKE_DASHARRAY, SPIDashArray > stroke_dasharray
stroke-dasharray
Definition: style.h:257
void readFromPrefs(Glib::ustring const &path)
Read style properties from preferences.
Definition: style.cpp:623
Integral and real coordinate types and some basic utilities.
std::vector< double > dash_pattern
bool custom
A widget for selecting dash patterns and setting the dash offset.
void sp_dialog_defocus_on_enter(Gtk::Entry *e)
Event handler for dialog windows.
@ FILL
SPItem * item
Definition: imagemagick.cpp:43
size_t v
Definition: multi-index.h:105
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Definition: desktop.h:51
Custom widgets.
Definition: desktop.h:127
static constexpr int height
void set_source_rgba(const Cairo::RefPtr< Cairo::Context > &ctx, unsigned int rgba)
bool equal(std::optional< std::string > const &a, char const *b)
Definition: optstr.h:16
static void append(std::vector< T > &target, std::vector< T > &&source)
Definition: shortcuts.cpp:515
STL namespace.
Singleton class to access the preferences file in a convenient way.
SPStyle - a style object for SPItem objects.
double width
std::unique_ptr< Toolbar >(* create)(SPDesktop *desktop)
Definition: toolbars.cpp:63