Inkscape
Vector Graphics Editor
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages Concepts
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
110 selection = Gtk::SingleSelection::create(liststore);
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_has_arrow(false);
123 popover->add_css_class("menu");
124 popover->set_child(*gridview);
125
126 // Menubutton
127 drawing_area = Gtk::make_managed<Gtk::DrawingArea>();
128 drawing_area->set_content_width(DRAWING_AREA_WIDTH);
129 drawing_area->set_content_height(DRAWING_AREA_HEIGHT);
130 drawing_area->set_draw_func(sigc::bind(sigc::mem_fun(*this, &DashSelector::draw_pattern), std::vector<double>{}));
131
132 auto menubutton = Gtk::make_managed<Gtk::MenuButton>();
133 menubutton->set_child(*drawing_area);
134 gtk_menu_button_set_always_show_arrow(menubutton->gobj(), true); // No C++ API!
135 menubutton->set_popover(*popover);
136
137 append(*menubutton);
138
139 // Offset spinbutton
140 adjustment = Gtk::Adjustment::create(0.0, 0.0, 1000.0, 0.1, 1.0, 0.0);
141 adjustment->signal_value_changed().connect([this] {
142 offset = adjustment->get_value();
143 changed_signal.emit();
144 });
145 auto spinbutton = Gtk::make_managed<Inkscape::UI::Widget::SpinButton>(adjustment, 0.1, 2); // Climb rate, digits.
146 spinbutton->set_tooltip_text(_("Dash pattern offset"));
147 spinbutton->set_width_chars(5);
148 sp_dialog_defocus_on_enter(*spinbutton);
149
150 append(*spinbutton);
151}
152
154
155// Set dash pattern from outside class.
156void DashSelector::set_dash_pattern(std::vector<double> const &new_dash_pattern, double new_offset)
157{
158 // See if there is already a dash pattern that matches (within delta).
159
160 // Set the criteria for matching (sum of dash lengths / number of dashes):
161 double const delta = std::accumulate(new_dash_pattern.begin(), new_dash_pattern.end(), 0.0)
162 / (10000.0 * (new_dash_pattern.empty() ? 1.0 : new_dash_pattern.size()));
163
164 int position = 1; // Position for custom dash patterns.
165 auto const item_count = selection->get_n_items();
166 for (int index = 0; index < item_count; ++index) {
167 auto const &item = dynamic_cast<DashPattern &>(*selection->get_object(index));
168 if (std::equal(new_dash_pattern.begin(), new_dash_pattern.end(), item.dash_pattern.begin(), item.dash_pattern.end(),
169 [=] (double a, double b) { return Geom::are_near(a, b, delta); }))
170 {
171 position = index;
172 break;
173 }
174 }
175
176 // Set selected pattern in GridView.
177 selection->set_selected(position);
178
179 if (position == 1) {
180 // Custom pattern!
181
182 // Update custom dash patterns.
183 auto &item = dynamic_cast<DashPattern &>(*selection->get_object(position));
184 item.dash_pattern.assign(new_dash_pattern.begin(), new_dash_pattern.end());
185 }
186
187 // Update MenuButton DrawingArea and offset.
188 dash_pattern = new_dash_pattern;
189 offset = new_offset;
190 update(position);
191}
192
193// Update display, offset. Common code for when dash changes (must not trigger signal!).
194void DashSelector::update(int position)
195{
196 // Update MenuButton DrawingArea.
197 if (position == 1) {
198 drawing_area->set_draw_func(sigc::mem_fun(*this, &DashSelector::draw_text));
199 } else {
200 drawing_area->set_draw_func(sigc::bind(sigc::mem_fun(*this, &DashSelector::draw_pattern), dash_pattern));
201 }
202
203 // If no dash pattern, reset offset to zero.
204 offset = dash_pattern.empty() ? 0.0 : offset;
205 adjustment->set_value(offset);
206}
207
208// User selected new dash pattern in GridView.
209void DashSelector::activate(Gtk::GridView *grid, unsigned position)
210{
211 auto &model = dynamic_cast<Gtk::SingleSelection &>(*grid->get_model());
212 auto const &item = dynamic_cast<DashPattern &>(*model.get_selected_item());
213
214 // Update MenuButton DrawingArea, offset, emit changed signal.
215 dash_pattern = item.dash_pattern;
216 update(position);
217 popover->popdown();
218
219 changed_signal.emit(); // Ensure Pattern widget updated.
220}
221
222void DashSelector::setup_listitem_cb(Glib::RefPtr<Gtk::ListItem> const &list_item)
223{
224 auto drawing_area = Gtk::make_managed<Gtk::DrawingArea>();
225 drawing_area->set_content_width(DRAWING_AREA_WIDTH);
226 drawing_area->set_content_height(DRAWING_AREA_HEIGHT);
227 list_item->set_child(*drawing_area);
228}
229
230void DashSelector::bind_listitem_cb(Glib::RefPtr<Gtk::ListItem> const &list_item)
231{
232 auto const &dash_pattern = dynamic_cast<DashPattern &>(*list_item->get_item());
233 auto &drawing_area = dynamic_cast<Gtk::DrawingArea &>(*list_item->get_child());
234
235 if (dash_pattern.custom) {
236 drawing_area.set_draw_func(sigc::mem_fun(*this, &DashSelector::draw_text));
237 } else {
238 drawing_area.set_draw_func(sigc::bind(sigc::mem_fun(*this, &DashSelector::draw_pattern), dash_pattern.dash_pattern));
239 }
240}
241
242// Draw dash pattern in a Gtk::DrawingArea.
243void DashSelector::draw_pattern(Cairo::RefPtr<Cairo::Context> const &cr, int width, int height,
244 std::vector<double> const &pattern)
245{
246 cr->set_line_width(2);
247 cr->scale(2, 1);
248 cr->set_dash(pattern, 0);
249 Gdk::Cairo::set_source_rgba(cr, get_color());
250 cr->move_to(0, height/2);
251 cr->line_to(width, height / 2);
252 cr->stroke();
253}
254
255// Draw text in a Gtk::DrawingArea.
256void DashSelector::draw_text(Cairo::RefPtr<Cairo::Context> const &cr, int width, int height)
257{
258 cr->select_font_face("Sans", Cairo::ToyFontFace::Slant::NORMAL, Cairo::ToyFontFace::Weight::NORMAL);
259 cr->set_font_size(12);
260 Gdk::Cairo::set_source_rgba(cr, get_color());
261 cr->move_to(16.0, (height + 12) / 2.0);
262 cr->show_text(_("Custom"));
263}
264
265} // namespace Inkscape::UI::Widget
266
267/*
268 Local Variables:
269 mode:c++
270 c-file-style:"stroustrup"
271 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
272 indent-tabs-mode:nil
273 fill-column:99
274 End:
275*/
276// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
static Preferences * get()
Access the singleton Preferences object.
void setup_listitem_cb(const Glib::RefPtr< Gtk::ListItem > &list_item)
std::vector< double > dash_pattern
void set_dash_pattern(const std::vector< double > &dash, double offset)
void activate(Gtk::GridView *grid, unsigned int position)
sigc::signal< void()> changed_signal
void draw_text(const Cairo::RefPtr< Cairo::Context > &cr, int width, int height)
Glib::RefPtr< Gtk::SingleSelection > selection
void draw_pattern(const Cairo::RefPtr< Cairo::Context > &cr, int width, int height, const std::vector< double > &pattern)
Glib::RefPtr< Gtk::Adjustment > adjustment
void bind_listitem_cb(const Glib::RefPtr< Gtk::ListItem > &list_item)
An SVG style object.
Definition style.h:45
void readFromPrefs(Glib::ustring const &path)
Read style properties from preferences.
Definition style.cpp:627
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.
SPItem * item
size_t v
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Definition desktop.h:50
Custom widgets.
Definition desktop.h:126
static constexpr int height
static void append(std::vector< T > &target, std::vector< T > &&source)
STL namespace.
Singleton class to access the preferences file in a convenient way.
SPStyle - a style object for SPItem objects.
int delta
int index
double width
std::unique_ptr< Toolbar >(* create)()
Definition toolbars.cpp:56