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