1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3 * @file
4 * Static style swatch (fill, stroke, opacity).
5 */
6 /* Authors:
7 * buliabyak@gmail.com
8 * Krzysztof Kosiński <tweenk.pl@gmail.com>
9 *
10 * Copyright (C) 2005-2008 Authors
11 *
12 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13 */
14
15 #include "style-swatch.h"
16
17 #include <glibmm/i18n.h>
18 #include <gtkmm/grid.h>
19
20 #include "inkscape.h"
21 #include "verbs.h"
22
23 #include "object/sp-linear-gradient.h"
24 #include "object/sp-pattern.h"
25 #include "object/sp-radial-gradient.h"
26 #include "style.h"
27
28 #include "helper/action.h"
29
30 #include "ui/widget/color-preview.h"
31 #include "util/units.h"
32
33 #include "widgets/spw-utilities.h"
34
35 #include "xml/sp-css-attr.h"
36 #include "xml/attribute-record.h"
37
38 enum {
39 SS_FILL,
40 SS_STROKE
41 };
42
43 namespace Inkscape {
44 namespace UI {
45 namespace Widget {
46
47 /**
48 * Watches whether the tool uses the current style.
49 */
50 class StyleSwatch::ToolObserver : public Inkscape::Preferences::Observer {
51 public:
ToolObserver(Glib::ustring const & path,StyleSwatch & ss)52 ToolObserver(Glib::ustring const &path, StyleSwatch &ss) :
53 Observer(path),
54 _style_swatch(ss)
55 {}
56 void notify(Inkscape::Preferences::Entry const &val) override;
57 private:
58 StyleSwatch &_style_swatch;
59 };
60
61 /**
62 * Watches for changes in the observed style pref.
63 */
64 class StyleSwatch::StyleObserver : public Inkscape::Preferences::Observer {
65 public:
StyleObserver(Glib::ustring const & path,StyleSwatch & ss)66 StyleObserver(Glib::ustring const &path, StyleSwatch &ss) :
67 Observer(path),
68 _style_swatch(ss)
69 {
70 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
71 this->notify(prefs->getEntry(path));
72 }
notify(Inkscape::Preferences::Entry const & val)73 void notify(Inkscape::Preferences::Entry const &val) override {
74 SPCSSAttr *css = val.getInheritedStyle();
75 _style_swatch.setStyle(css);
76 sp_repr_css_attr_unref(css);
77 }
78 private:
79 StyleSwatch &_style_swatch;
80 };
81
notify(Inkscape::Preferences::Entry const & val)82 void StyleSwatch::ToolObserver::notify(Inkscape::Preferences::Entry const &val)
83 {
84 bool usecurrent = val.getBool();
85
86 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
87 if (_style_swatch._style_obs) delete _style_swatch._style_obs;
88
89 if (usecurrent) {
90 _style_swatch._style_obs = new StyleObserver("/desktop/style", _style_swatch);
91
92 // If desktop's last-set style is empty, a tool uses its own fixed style even if set to use
93 // last-set (so long as it's empty). To correctly show this, we get the tool's style
94 // if the desktop's style is empty.
95 SPCSSAttr *css = prefs->getStyle("/desktop/style");
96 const auto & al = css->attributeList();
97 if (al.empty()) {
98 SPCSSAttr *css2 = prefs->getInheritedStyle(_style_swatch._tool_path + "/style");
99 _style_swatch.setStyle(css2);
100 sp_repr_css_attr_unref(css2);
101 }
102 sp_repr_css_attr_unref(css);
103 } else {
104 _style_swatch._style_obs = new StyleObserver(_style_swatch._tool_path + "/style", _style_swatch);
105 }
106 prefs->addObserver(*_style_swatch._style_obs);
107 }
108
StyleSwatch(SPCSSAttr * css,gchar const * main_tip)109 StyleSwatch::StyleSwatch(SPCSSAttr *css, gchar const *main_tip)
110 : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL),
111 _desktop(nullptr),
112 _verb_t(0),
113 _css(nullptr),
114 _tool_obs(nullptr),
115 _style_obs(nullptr),
116 _table(Gtk::manage(new Gtk::Grid())),
117 _sw_unit(nullptr),
118 _stroke(Gtk::ORIENTATION_HORIZONTAL)
119 {
120 set_name("StyleSwatch");
121
122 _label[SS_FILL].set_markup(_("Fill:"));
123 _label[SS_STROKE].set_markup(_("Stroke:"));
124
125 for (int i = SS_FILL; i <= SS_STROKE; i++) {
126 _label[i].set_halign(Gtk::ALIGN_START);
127 _label[i].set_valign(Gtk::ALIGN_CENTER);
128 _label[i].set_margin_top(0);
129 _label[i].set_margin_bottom(0);
130 _label[i].set_margin_start(0);
131 _label[i].set_margin_end(0);
132
133 _color_preview[i] = new Inkscape::UI::Widget::ColorPreview (0);
134 }
135
136 _opacity_value.set_halign(Gtk::ALIGN_START);
137 _opacity_value.set_valign(Gtk::ALIGN_CENTER);
138 _opacity_value.set_margin_top(0);
139 _opacity_value.set_margin_bottom(0);
140 _opacity_value.set_margin_start(0);
141 _opacity_value.set_margin_end(0);
142
143 _table->set_column_spacing(2);
144 _table->set_row_spacing(0);
145
146 _stroke.pack_start(_place[SS_STROKE]);
147 _stroke_width_place.add(_stroke_width);
148 _stroke.pack_start(_stroke_width_place, Gtk::PACK_SHRINK);
149
150 _opacity_place.add(_opacity_value);
151
152 _table->attach(_label[SS_FILL], 0, 0, 1, 1);
153 _table->attach(_label[SS_STROKE], 0, 1, 1, 1);
154 _table->attach(_place[SS_FILL], 1, 0, 1, 1);
155 _table->attach(_stroke, 1, 1, 1, 1);
156 _table->attach(_opacity_place, 2, 0, 1, 2);
157
158 _swatch.add(*_table);
159 pack_start(_swatch, true, true, 0);
160
161 set_size_request (STYLE_SWATCH_WIDTH, -1);
162
163 setStyle (css);
164
165 _swatch.signal_button_press_event().connect(sigc::mem_fun(*this, &StyleSwatch::on_click));
166
167 if (main_tip)
168 {
169 _swatch.set_tooltip_text(main_tip);
170 }
171 }
172
setClickVerb(sp_verb_t verb_t)173 void StyleSwatch::setClickVerb(sp_verb_t verb_t) {
174 _verb_t = verb_t;
175 }
176
setDesktop(SPDesktop * desktop)177 void StyleSwatch::setDesktop(SPDesktop *desktop) {
178 _desktop = desktop;
179 }
180
181 bool
on_click(GdkEventButton *)182 StyleSwatch::on_click(GdkEventButton */*event*/)
183 {
184 if (this->_desktop && this->_verb_t != SP_VERB_NONE) {
185 Inkscape::Verb *verb = Inkscape::Verb::get(this->_verb_t);
186 SPAction *action = verb->get_action(Inkscape::ActionContext((Inkscape::UI::View::View *) this->_desktop));
187 sp_action_perform (action, nullptr);
188 return true;
189 }
190 return false;
191 }
192
~StyleSwatch()193 StyleSwatch::~StyleSwatch()
194 {
195 if (_css)
196 sp_repr_css_attr_unref (_css);
197
198 for (int i = SS_FILL; i <= SS_STROKE; i++) {
199 delete _color_preview[i];
200 }
201
202 if (_style_obs) delete _style_obs;
203 if (_tool_obs) delete _tool_obs;
204 }
205
206 void
setWatchedTool(const char * path,bool synthesize)207 StyleSwatch::setWatchedTool(const char *path, bool synthesize)
208 {
209 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
210
211 if (_tool_obs) {
212 delete _tool_obs;
213 _tool_obs = nullptr;
214 }
215
216 if (path) {
217 _tool_path = path;
218 _tool_obs = new ToolObserver(_tool_path + "/usecurrent", *this);
219 prefs->addObserver(*_tool_obs);
220 } else {
221 _tool_path = "";
222 }
223
224 // hack until there is a real synthesize events function for prefs,
225 // which shouldn't be hard to write once there is sufficient need for it
226 if (synthesize && _tool_obs) {
227 _tool_obs->notify(prefs->getEntry(_tool_path + "/usecurrent"));
228 }
229 }
230
231
setStyle(SPCSSAttr * css)232 void StyleSwatch::setStyle(SPCSSAttr *css)
233 {
234 if (_css)
235 sp_repr_css_attr_unref (_css);
236
237 if (!css)
238 return;
239
240 _css = sp_repr_css_attr_new();
241 sp_repr_css_merge(_css, css);
242
243 Glib::ustring css_string;
244 sp_repr_css_write_string (_css, css_string);
245
246 SPStyle style(_desktop ? _desktop->getDocument() : nullptr);
247 if (!css_string.empty()) {
248 style.mergeString(css_string.c_str());
249 }
250 setStyle (&style);
251 }
252
setStyle(SPStyle * query)253 void StyleSwatch::setStyle(SPStyle *query)
254 {
255 _place[SS_FILL].remove();
256 _place[SS_STROKE].remove();
257
258 bool has_stroke = true;
259
260 for (int i = SS_FILL; i <= SS_STROKE; i++) {
261 Gtk::EventBox *place = &(_place[i]);
262
263 SPIPaint *paint;
264 if (i == SS_FILL) {
265 paint = &(query->fill);
266 } else {
267 paint = &(query->stroke);
268 }
269
270 if (paint->set && paint->isPaintserver()) {
271 SPPaintServer *server = (i == SS_FILL)? SP_STYLE_FILL_SERVER (query) : SP_STYLE_STROKE_SERVER (query);
272
273 if (SP_IS_LINEARGRADIENT (server)) {
274 _value[i].set_markup(_("L Gradient"));
275 place->add(_value[i]);
276 place->set_tooltip_text((i == SS_FILL)? (_("Linear gradient (fill)")) : (_("Linear gradient (stroke)")));
277 } else if (SP_IS_RADIALGRADIENT (server)) {
278 _value[i].set_markup(_("R Gradient"));
279 place->add(_value[i]);
280 place->set_tooltip_text((i == SS_FILL)? (_("Radial gradient (fill)")) : (_("Radial gradient (stroke)")));
281 } else if (SP_IS_PATTERN (server)) {
282 _value[i].set_markup(_("Pattern"));
283 place->add(_value[i]);
284 place->set_tooltip_text((i == SS_FILL)? (_("Pattern (fill)")) : (_("Pattern (stroke)")));
285 }
286
287 } else if (paint->set && paint->isColor()) {
288 guint32 color = paint->value.color.toRGBA32( SP_SCALE24_TO_FLOAT ((i == SS_FILL)? query->fill_opacity.value : query->stroke_opacity.value) );
289 ((Inkscape::UI::Widget::ColorPreview*)_color_preview[i])->setRgba32 (color);
290 _color_preview[i]->show_all();
291 place->add(*_color_preview[i]);
292 gchar *tip;
293 if (i == SS_FILL) {
294 tip = g_strdup_printf (_("Fill: %06x/%.3g"), color >> 8, SP_RGBA32_A_F(color));
295 } else {
296 tip = g_strdup_printf (_("Stroke: %06x/%.3g"), color >> 8, SP_RGBA32_A_F(color));
297 }
298 place->set_tooltip_text(tip);
299 g_free (tip);
300 } else if (paint->set && paint->isNone()) {
301 _value[i].set_markup(C_("Fill and stroke", "<i>None</i>"));
302 place->add(_value[i]);
303 place->set_tooltip_text((i == SS_FILL)? (C_("Fill and stroke", "No fill")) : (C_("Fill and stroke", "No stroke")));
304 if (i == SS_STROKE) has_stroke = false;
305 } else if (!paint->set) {
306 _value[i].set_markup(_("<b>Unset</b>"));
307 place->add(_value[i]);
308 place->set_tooltip_text((i == SS_FILL)? (_("Unset fill")) : (_("Unset stroke")));
309 if (i == SS_STROKE) has_stroke = false;
310 }
311 }
312
313 // Now query stroke_width
314 if (has_stroke) {
315 if (query->stroke_extensions.hairline) {
316 _stroke_width.set_markup(_("Hairline"));
317 auto str = Glib::ustring::compose(_("Stroke width: %1"), _("Hairline"));
318 _stroke_width_place.set_tooltip_text(str);
319 } else {
320 double w;
321 if (_sw_unit) {
322 w = Inkscape::Util::Quantity::convert(query->stroke_width.computed, "px", _sw_unit);
323 } else {
324 w = query->stroke_width.computed;
325 }
326
327 {
328 gchar *str = g_strdup_printf(" %.3g", w);
329 _stroke_width.set_markup(str);
330 g_free (str);
331 }
332 {
333 gchar *str = g_strdup_printf(_("Stroke width: %.5g%s"),
334 w,
335 _sw_unit? _sw_unit->abbr.c_str() : "px");
336 _stroke_width_place.set_tooltip_text(str);
337 g_free (str);
338 }
339 }
340 } else {
341 _stroke_width_place.set_tooltip_text("");
342 _stroke_width.set_markup("");
343 _stroke_width.set_has_tooltip(false);
344 }
345
346 gdouble op = SP_SCALE24_TO_FLOAT(query->opacity.value);
347 if (op != 1) {
348 {
349 gchar *str;
350 str = g_strdup_printf(_("O: %2.0f"), (op*100.0));
351 _opacity_value.set_markup (str);
352 g_free (str);
353 }
354 {
355 gchar *str = g_strdup_printf(_("Opacity: %2.1f %%"), (op*100.0));
356 _opacity_place.set_tooltip_text(str);
357 g_free (str);
358 }
359 } else {
360 _opacity_place.set_tooltip_text("");
361 _opacity_value.set_markup("");
362 _opacity_value.set_has_tooltip(false);
363 }
364
365 show_all();
366 }
367
368 } // namespace Widget
369 } // namespace UI
370 } // namespace Inkscape
371
372 /*
373 Local Variables:
374 mode:c++
375 c-file-style:"stroustrup"
376 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
377 indent-tabs-mode:nil
378 fill-column:99
379 End:
380 */
381 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
382