1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Gradient vector selection widget
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * MenTaLguY <mental@rydia.net>
9 * Jon A. Cruz <jon@joncruz.org>
10 * Abhishek Sharma
11 *
12 * Copyright (C) 2001-2002 Lauris Kaplinski
13 * Copyright (C) 2001 Ximian, Inc.
14 * Copyright (C) 2004 Monash University
15 * Copyright (C) 2004 David Turner
16 * Copyright (C) 2006 MenTaLguY
17 * Copyright (C) 2010 Jon A. Cruz
18 *
19 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
20 *
21 */
22
23 #include "ui/widget/gradient-vector-selector.h"
24
25 #include <set>
26
27 #include <glibmm.h>
28 #include <glibmm/i18n.h>
29
30 #include "gradient-chemistry.h"
31 #include "inkscape.h"
32 #include "preferences.h"
33 #include "desktop.h"
34 #include "document-undo.h"
35 #include "layer-manager.h"
36 #include "include/macros.h"
37 #include "selection-chemistry.h"
38 #include "verbs.h"
39
40 #include "io/resource.h"
41
42 #include "object/sp-defs.h"
43 #include "object/sp-linear-gradient.h"
44 #include "object/sp-radial-gradient.h"
45 #include "object/sp-root.h"
46 #include "object/sp-stop.h"
47 #include "style.h"
48
49 #include "svg/css-ostringstream.h"
50
51 #include "ui/dialog-events.h"
52 #include "ui/selected-color.h"
53 #include "ui/widget/color-notebook.h"
54 #include "ui/widget/color-preview.h"
55 #include "ui/widget/gradient-image.h"
56
57 #include "xml/repr.h"
58
59 using Inkscape::DocumentUndo;
60 using Inkscape::UI::SelectedColor;
61
62 void gr_get_usage_counts(SPDocument *doc, std::map<SPGradient *, gint> *mapUsageCount );
63 unsigned long sp_gradient_to_hhssll(SPGradient *gr);
64
65 // TODO FIXME kill these globals!!!
66 static Glib::ustring const prefs_path = "/dialogs/gradienteditor/";
67
68 namespace Inkscape {
69 namespace UI {
70 namespace Widget {
71
GradientVectorSelector(SPDocument * doc,SPGradient * gr)72 GradientVectorSelector::GradientVectorSelector(SPDocument *doc, SPGradient *gr)
73 {
74 _columns = new GradientSelector::ModelColumns();
75 _store = Gtk::ListStore::create(*_columns);
76 set_orientation(Gtk::ORIENTATION_VERTICAL);
77
78 if (doc) {
79 set_gradient(doc, gr);
80 } else {
81 rebuild_gui_full();
82 }
83 }
84
~GradientVectorSelector()85 GradientVectorSelector::~GradientVectorSelector()
86 {
87 if (_gr) {
88 _gradient_release_connection.disconnect();
89 _tree_select_connection.disconnect();
90 _gr = nullptr;
91 }
92
93 if (_doc) {
94 _defs_release_connection.disconnect();
95 _defs_modified_connection.disconnect();
96 _doc = nullptr;
97 }
98 }
99
set_gradient(SPDocument * doc,SPGradient * gr)100 void GradientVectorSelector::set_gradient(SPDocument *doc, SPGradient *gr)
101 {
102 // g_message("sp_gradient_vector_selector_set_gradient(%p, %p, %p) [%s] %d %d", gvs, doc, gr,
103 // (gr ? gr->getId():"N/A"),
104 // (gr ? gr->isSwatch() : -1),
105 // (gr ? gr->isSolid() : -1));
106 static gboolean suppress = FALSE;
107
108 g_return_if_fail(!gr || (doc != nullptr));
109 g_return_if_fail(!gr || SP_IS_GRADIENT(gr));
110 g_return_if_fail(!gr || (gr->document == doc));
111 g_return_if_fail(!gr || gr->hasStops());
112
113 if (doc != _doc) {
114 /* Disconnect signals */
115 if (_gr) {
116 _gradient_release_connection.disconnect();
117 _gr = nullptr;
118 }
119 if (_doc) {
120 _defs_release_connection.disconnect();
121 _defs_modified_connection.disconnect();
122 _doc = nullptr;
123 }
124
125 // Connect signals
126 if (doc) {
127 _defs_release_connection = doc->getDefs()->connectRelease(sigc::mem_fun(this, &GradientVectorSelector::defs_release));
128 _defs_modified_connection = doc->getDefs()->connectModified(sigc::mem_fun(this, &GradientVectorSelector::defs_modified));
129 }
130 if (gr) {
131 _gradient_release_connection = gr->connectRelease(sigc::mem_fun(this, &GradientVectorSelector::gradient_release));
132 }
133 _doc = doc;
134 _gr = gr;
135 rebuild_gui_full();
136 if (!suppress) _signal_vector_set.emit(gr);
137 } else if (gr != _gr) {
138 // Harder case - keep document, rebuild list and stuff
139 // fixme: (Lauris)
140 suppress = TRUE;
141 set_gradient(nullptr, nullptr);
142 set_gradient(doc, gr);
143 suppress = FALSE;
144 _signal_vector_set.emit(gr);
145 }
146 /* The case of setting NULL -> NULL is not very interesting */
147 }
148
149 void
gradient_release(SPObject *)150 GradientVectorSelector::gradient_release(SPObject * /*obj*/)
151 {
152 /* Disconnect gradient */
153 if (_gr) {
154 _gradient_release_connection.disconnect();
155 _gr = nullptr;
156 }
157
158 /* Rebuild GUI */
159 rebuild_gui_full();
160 }
161
162 void
defs_release(SPObject *)163 GradientVectorSelector::defs_release(SPObject * /*defs*/)
164 {
165 _doc = nullptr;
166
167 _defs_release_connection.disconnect();
168 _defs_modified_connection.disconnect();
169
170 /* Disconnect gradient as well */
171 if (_gr) {
172 _gradient_release_connection.disconnect();
173 _gr = nullptr;
174 }
175
176 /* Rebuild GUI */
177 rebuild_gui_full();
178 }
179
180 void
defs_modified(SPObject * defs,guint flags)181 GradientVectorSelector::defs_modified(SPObject *defs, guint flags)
182 {
183 /* fixme: We probably have to check some flags here (Lauris) */
184 rebuild_gui_full();
185 }
186
187 void
rebuild_gui_full()188 GradientVectorSelector::rebuild_gui_full()
189 {
190 _tree_select_connection.block();
191
192 /* Clear old list, if there is any */
193 _store->clear();
194
195 /* Pick up all gradients with vectors */
196 std::vector<SPGradient *> gl;
197 if (_gr) {
198 auto gradients = _gr->document->getResourceList("gradient");
199 for (auto gradient : gradients) {
200 SPGradient* grad = SP_GRADIENT(gradient);
201 if ( grad->hasStops() && (grad->isSwatch() == _swatched) ) {
202 gl.push_back(SP_GRADIENT(gradient));
203 }
204 }
205 }
206
207 /* Get usage count of all the gradients */
208 std::map<SPGradient *, gint> usageCount;
209 gr_get_usage_counts(_doc, &usageCount);
210
211 if (!_doc) {
212 Gtk::TreeModel::Row row = *(_store->append());
213 row[_columns->name] = _("No document selected");
214
215 } else if (gl.empty()) {
216 Gtk::TreeModel::Row row = *(_store->append());
217 row[_columns->name] = _("No gradients in document");
218
219 } else if (!_gr) {
220 Gtk::TreeModel::Row row = *(_store->append());
221 row[_columns->name] = _("No gradient selected");
222
223 } else {
224 for (auto gr:gl) {
225 unsigned long hhssll = sp_gradient_to_hhssll(gr);
226 GdkPixbuf *pixb = sp_gradient_to_pixbuf (gr, 64, 18);
227 Glib::ustring label = gr_prepare_label(gr);
228
229 Gtk::TreeModel::Row row = *(_store->append());
230 row[_columns->name] = label.c_str();
231 row[_columns->color] = hhssll;
232 row[_columns->refcount] = usageCount[gr];
233 row[_columns->data] = gr;
234 row[_columns->pixbuf] = Glib::wrap(pixb);
235 }
236 }
237
238 _tree_select_connection.unblock();
239 }
240
241 void
setSwatched()242 GradientVectorSelector::setSwatched()
243 {
244 _swatched = true;
245 rebuild_gui_full();
246 }
247
248 } // namespace Widget
249 } // namespace UI
250 } // namespace Inkscape
251
gr_prepare_label(SPObject * obj)252 Glib::ustring gr_prepare_label(SPObject *obj)
253 {
254 const gchar *id = obj->label() ? obj->label() : obj->getId();
255 if (!id) {
256 id = obj->getRepr()->name();
257 }
258
259 if (strlen(id) > 14 && (!strncmp (id, "linearGradient", 14) || !strncmp (id, "radialGradient", 14)))
260 return gr_ellipsize_text(id+14, 35);
261 return gr_ellipsize_text (id, 35);
262 }
263
264 /*
265 * Ellipse text if longer than maxlen, "50% start text + ... + ~50% end text"
266 * Text should be > length 8 or just return the original text
267 */
gr_ellipsize_text(Glib::ustring const & src,size_t maxlen)268 Glib::ustring gr_ellipsize_text(Glib::ustring const &src, size_t maxlen)
269 {
270 if (src.length() > maxlen && maxlen > 8) {
271 size_t p1 = (size_t) maxlen / 2;
272 size_t p2 = (size_t) src.length() - (maxlen - p1 - 1);
273 return src.substr(0, p1) + "…" + src.substr(p2);
274 }
275 return src;
276 }
277
278
279 /*
280 * Return a "HHSSLL" version of the first stop color so we can sort by it
281 */
sp_gradient_to_hhssll(SPGradient * gr)282 unsigned long sp_gradient_to_hhssll(SPGradient *gr)
283 {
284 SPStop *stop = gr->getFirstStop();
285 unsigned long rgba = stop->get_rgba32();
286 float hsl[3];
287 SPColor::rgb_to_hsl_floatv (hsl, SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba));
288
289 return ((int)(hsl[0]*100 * 10000)) + ((int)(hsl[1]*100 * 100)) + ((int)(hsl[2]*100 * 1));
290 }
291
get_all_doc_items(std::vector<SPItem * > & list,SPObject * from)292 static void get_all_doc_items(std::vector<SPItem*> &list, SPObject *from)
293 {
294 for (auto& child: from->children) {
295 if (SP_IS_ITEM(&child)) {
296 list.push_back(SP_ITEM(&child));
297 }
298 get_all_doc_items(list, &child);
299 }
300 }
301
302 /*
303 * Return a SPItem's gradient
304 */
gr_item_get_gradient(SPItem * item,gboolean fillorstroke)305 static SPGradient * gr_item_get_gradient(SPItem *item, gboolean fillorstroke)
306 {
307 SPIPaint *item_paint = item->style->getFillOrStroke(fillorstroke);
308 if (item_paint->isPaintserver()) {
309
310 SPPaintServer *item_server = (fillorstroke) ?
311 item->style->getFillPaintServer() : item->style->getStrokePaintServer();
312
313 if (SP_IS_LINEARGRADIENT(item_server) || SP_IS_RADIALGRADIENT(item_server) ||
314 (SP_IS_GRADIENT(item_server) && SP_GRADIENT(item_server)->getVector()->isSwatch())) {
315
316 return SP_GRADIENT(item_server)->getVector();
317 }
318 }
319
320 return nullptr;
321 }
322
323 /*
324 * Map each gradient to its usage count for both fill and stroke styles
325 */
gr_get_usage_counts(SPDocument * doc,std::map<SPGradient *,gint> * mapUsageCount)326 void gr_get_usage_counts(SPDocument *doc, std::map<SPGradient *, gint> *mapUsageCount )
327 {
328 if (!doc)
329 return;
330
331 std::vector<SPItem *> all_list;
332 get_all_doc_items(all_list, doc->getRoot());
333
334 for (auto item:all_list) {
335 if (!item->getId())
336 continue;
337 SPGradient *gr = nullptr;
338 gr = gr_item_get_gradient(item, true); // fill
339 if (gr) {
340 mapUsageCount->count(gr) > 0 ? (*mapUsageCount)[gr] += 1 : (*mapUsageCount)[gr] = 1;
341 }
342 gr = gr_item_get_gradient(item, false); // stroke
343 if (gr) {
344 mapUsageCount->count(gr) > 0 ? (*mapUsageCount)[gr] += 1 : (*mapUsageCount)[gr] = 1;
345 }
346 }
347 }
348
349 /*
350 Local Variables:
351 mode:c++
352 c-file-style:"stroustrup"
353 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
354 indent-tabs-mode:nil
355 fill-column:99
356 End:
357 */
358 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
359