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