1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Gradient vector widget
4  *
5  * Authors:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   bulia byak <buliabyak@users.sf.net>
8  *   Jon A. Cruz <jon@joncruz.org>
9  *
10  * Copyright (C) 2001-2002 Lauris Kaplinski
11  * Copyright (C) 2001 Ximian, Inc.
12  * Copyright (C) 2010 Jon A. Cruz
13  *
14  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15  */
16 
17 #include <glibmm/i18n.h>
18 #include <gtkmm/treeview.h>
19 #include <vector>
20 
21 #include "document-undo.h"
22 #include "document.h"
23 #include "gradient-chemistry.h"
24 #include "id-clash.h"
25 #include "inkscape.h"
26 #include "preferences.h"
27 #include "verbs.h"
28 
29 #include "object/sp-defs.h"
30 #include "style.h"
31 
32 #include "helper/action.h"
33 #include "ui/icon-loader.h"
34 
35 #include "ui/icon-names.h"
36 
37 #include "ui/widget/gradient-vector-selector.h"
38 
39 namespace Inkscape {
40 namespace UI {
41 namespace Widget {
42 
style_button(Gtk::Button * btn,char const * iconName)43 void GradientSelector::style_button(Gtk::Button *btn, char const *iconName)
44 {
45     GtkWidget *child = sp_get_icon_image(iconName, GTK_ICON_SIZE_SMALL_TOOLBAR);
46     gtk_widget_show(child);
47     btn->add(*manage(Glib::wrap(child)));
48     btn->set_relief(Gtk::RELIEF_NONE);
49 }
50 
GradientSelector()51 GradientSelector::GradientSelector()
52     : _blocked(false)
53     , _mode(MODE_LINEAR)
54     , _gradientUnits(SP_GRADIENT_UNITS_USERSPACEONUSE)
55     , _gradientSpread(SP_GRADIENT_SPREAD_PAD)
56 {
57     set_orientation(Gtk::ORIENTATION_VERTICAL);
58 
59     /* Vectors */
60     _vectors = Gtk::manage(new GradientVectorSelector(nullptr, nullptr));
61     _store = _vectors->get_store();
62     _columns = _vectors->get_columns();
63 
64     _treeview = Gtk::manage(new Gtk::TreeView());
65     _treeview->set_model(_store);
66     _treeview->set_headers_clickable(true);
67     _treeview->set_search_column(1);
68     _treeview->set_vexpand();
69     _icon_renderer = Gtk::manage(new Gtk::CellRendererPixbuf());
70     _text_renderer = Gtk::manage(new Gtk::CellRendererText());
71 
72     _treeview->append_column(_("Gradient"), *_icon_renderer);
73     auto icon_column = _treeview->get_column(0);
74     icon_column->add_attribute(_icon_renderer->property_pixbuf(), _columns->pixbuf);
75     icon_column->set_sort_column(_columns->color);
76     icon_column->set_clickable(true);
77 
78     _treeview->append_column(_("Name"), *_text_renderer);
79     auto name_column = _treeview->get_column(1);
80     _text_renderer->property_editable() = true;
81     name_column->add_attribute(_text_renderer->property_text(), _columns->name);
82     name_column->set_min_width(180);
83     name_column->set_clickable(true);
84     name_column->set_resizable(true);
85 
86     _treeview->append_column("#", _columns->refcount);
87     auto count_column = _treeview->get_column(2);
88     count_column->set_clickable(true);
89     count_column->set_resizable(true);
90 
91     _treeview->signal_key_press_event().connect(sigc::mem_fun(this, &GradientSelector::onKeyPressEvent), false);
92 
93     _treeview->show();
94 
95     icon_column->signal_clicked().connect(sigc::mem_fun(this, &GradientSelector::onTreeColorColClick));
96     name_column->signal_clicked().connect(sigc::mem_fun(this, &GradientSelector::onTreeNameColClick));
97     count_column->signal_clicked().connect(sigc::mem_fun(this, &GradientSelector::onTreeCountColClick));
98 
99     auto tree_select_connection = _treeview->get_selection()->signal_changed().connect(sigc::mem_fun(this, &GradientSelector::onTreeSelection));
100     _vectors->set_tree_select_connection(tree_select_connection);
101     _text_renderer->signal_edited().connect(sigc::mem_fun(this, &GradientSelector::onGradientRename));
102 
103     _scrolled_window = Gtk::manage(new Gtk::ScrolledWindow());
104     _scrolled_window->add(*_treeview);
105     _scrolled_window->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
106     _scrolled_window->set_shadow_type(Gtk::SHADOW_IN);
107     _scrolled_window->set_size_request(0, 180);
108     _scrolled_window->set_hexpand();
109     _scrolled_window->show();
110 
111     pack_start(*_scrolled_window, true, true, 4);
112 
113 
114     /* Create box for buttons */
115     auto hb = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
116     hb->set_homogeneous(false);
117     pack_start(*hb, false, false, 0);
118 
119     _add = Gtk::manage(new Gtk::Button());
120     style_button(_add, INKSCAPE_ICON("list-add"));
121 
122     _nonsolid.push_back(_add);
123     hb->pack_start(*_add, false, false, 0);
124 
125     _add->signal_clicked().connect(sigc::mem_fun(this, &GradientSelector::add_vector_clicked));
126     _add->set_sensitive(false);
127     _add->set_relief(Gtk::RELIEF_NONE);
128     _add->set_tooltip_text(_("Create a duplicate gradient"));
129 
130     _edit = Gtk::manage(new Gtk::Button());
131     style_button(_edit, INKSCAPE_ICON("edit"));
132 
133     _nonsolid.push_back(_edit);
134     hb->pack_start(*_edit, false, false, 0);
135     _edit->signal_clicked().connect(sigc::mem_fun(this, &GradientSelector::edit_vector_clicked));
136     _edit->set_sensitive(false);
137     _edit->set_relief(Gtk::RELIEF_NONE);
138     _edit->set_tooltip_text(_("Edit gradient"));
139 
140     _del = Gtk::manage(new Gtk::Button());
141     style_button(_del, INKSCAPE_ICON("list-remove"));
142 
143     _swatch_widgets.push_back(_del);
144     hb->pack_start(*_del, false, false, 0);
145     _del->signal_clicked().connect(sigc::mem_fun(this, &GradientSelector::delete_vector_clicked));
146     _del->set_sensitive(false);
147     _del->set_relief(Gtk::RELIEF_NONE);
148     _del->set_tooltip_text(_("Delete swatch"));
149 
150     hb->show_all();
151 }
152 
setSpread(SPGradientSpread spread)153 void GradientSelector::setSpread(SPGradientSpread spread)
154 {
155     _gradientSpread = spread;
156     // gtk_combo_box_set_active (GTK_COMBO_BOX(this->spread), gradientSpread);
157 }
158 
setMode(SelectorMode mode)159 void GradientSelector::setMode(SelectorMode mode)
160 {
161     if (mode != _mode) {
162         _mode = mode;
163         if (mode == MODE_SWATCH) {
164             for (auto &it : _nonsolid) {
165                 it->hide();
166             }
167             for (auto &swatch_widget : _swatch_widgets) {
168                 swatch_widget->show_all();
169             }
170 
171             auto icon_column = _treeview->get_column(0);
172             icon_column->set_title(_("Swatch"));
173 
174             _vectors->setSwatched();
175         } else {
176             for (auto &it : _nonsolid) {
177                 it->show_all();
178             }
179             for (auto &swatch_widget : _swatch_widgets) {
180                 swatch_widget->hide();
181             }
182             auto icon_column = _treeview->get_column(0);
183             icon_column->set_title(_("Gradient"));
184         }
185     }
186 }
187 
setUnits(SPGradientUnits units)188 void GradientSelector::setUnits(SPGradientUnits units) { _gradientUnits = units; }
189 
getUnits()190 SPGradientUnits GradientSelector::getUnits() { return _gradientUnits; }
191 
getSpread()192 SPGradientSpread GradientSelector::getSpread() { return _gradientSpread; }
193 
onGradientRename(const Glib::ustring & path_string,const Glib::ustring & new_text)194 void GradientSelector::onGradientRename(const Glib::ustring &path_string, const Glib::ustring &new_text)
195 {
196     Gtk::TreePath path(path_string);
197     auto iter = _store->get_iter(path);
198 
199     if (iter) {
200         Gtk::TreeModel::Row row = *iter;
201         if (row) {
202             SPObject *obj = row[_columns->data];
203             if (obj) {
204                 row[_columns->name] = gr_prepare_label(obj);
205                 if (!new_text.empty() && new_text != row[_columns->name]) {
206                     rename_id(obj, new_text);
207                     Inkscape::DocumentUndo::done(obj->document, SP_VERB_CONTEXT_GRADIENT, _("Rename gradient"));
208                 }
209             }
210         }
211     }
212 }
213 
onTreeColorColClick()214 void GradientSelector::onTreeColorColClick()
215 {
216     auto column = _treeview->get_column(0);
217     column->set_sort_column(_columns->color);
218 }
219 
onTreeNameColClick()220 void GradientSelector::onTreeNameColClick()
221 {
222     auto column = _treeview->get_column(1);
223     column->set_sort_column(_columns->name);
224 }
225 
226 
onTreeCountColClick()227 void GradientSelector::onTreeCountColClick()
228 {
229     auto column = _treeview->get_column(2);
230     column->set_sort_column(_columns->refcount);
231 }
232 
moveSelection(int amount,bool down,bool toEnd)233 void GradientSelector::moveSelection(int amount, bool down, bool toEnd)
234 {
235     auto select = _treeview->get_selection();
236     auto iter = select->get_selected();
237 
238     if (amount < 0) {
239         down = !down;
240         amount = -amount;
241     }
242 
243     auto canary = iter;
244     if (down) {
245         ++canary;
246     } else {
247         --canary;
248     }
249     while (canary && (toEnd || amount > 0)) {
250         --amount;
251         if (down) {
252             ++canary;
253             ++iter;
254         } else {
255             --canary;
256             --iter;
257         }
258     }
259 
260     select->select(iter);
261     _treeview->scroll_to_row(_store->get_path(iter), 0.5);
262 }
263 
onKeyPressEvent(GdkEventKey * event)264 bool GradientSelector::onKeyPressEvent(GdkEventKey *event)
265 {
266     bool consume = false;
267     auto display = Gdk::Display::get_default();
268     auto keymap = display->get_keymap();
269     guint key = 0;
270     gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode, static_cast<GdkModifierType>(event->state), 0,
271                                         &key, 0, 0, 0);
272 
273     switch (key) {
274         case GDK_KEY_Up:
275         case GDK_KEY_KP_Up: {
276             moveSelection(-1);
277             consume = true;
278             break;
279         }
280         case GDK_KEY_Down:
281         case GDK_KEY_KP_Down: {
282             moveSelection(1);
283             consume = true;
284             break;
285         }
286         case GDK_KEY_Page_Up:
287         case GDK_KEY_KP_Page_Up: {
288             moveSelection(-5);
289             consume = true;
290             break;
291         }
292 
293         case GDK_KEY_Page_Down:
294         case GDK_KEY_KP_Page_Down: {
295             moveSelection(5);
296             consume = true;
297             break;
298         }
299 
300         case GDK_KEY_End:
301         case GDK_KEY_KP_End: {
302             moveSelection(0, true, true);
303             consume = true;
304             break;
305         }
306 
307         case GDK_KEY_Home:
308         case GDK_KEY_KP_Home: {
309             moveSelection(0, false, true);
310             consume = true;
311             break;
312         }
313     }
314     return consume;
315 }
316 
onTreeSelection()317 void GradientSelector::onTreeSelection()
318 {
319     if (!_treeview) {
320         return;
321     }
322 
323     if (_blocked) {
324         return;
325     }
326 
327     if (!_treeview->has_focus()) {
328         /* Workaround for GTK bug on Windows/OS X
329          * When the treeview initially doesn't have focus and is clicked
330          * sometimes get_selection()->signal_changed() has the wrong selection
331          */
332         _treeview->grab_focus();
333     }
334 
335     const auto sel = _treeview->get_selection();
336     if (!sel) {
337         return;
338     }
339 
340     SPGradient *obj = nullptr;
341     /* Single selection */
342     auto iter = sel->get_selected();
343     if (iter) {
344         Gtk::TreeModel::Row row = *iter;
345         obj = row[_columns->data];
346     }
347 
348     if (obj) {
349         vector_set(SP_GRADIENT(obj));
350     }
351 }
352 
_checkForSelected(const Gtk::TreePath & path,const Gtk::TreeIter & iter,SPGradient * vector)353 bool GradientSelector::_checkForSelected(const Gtk::TreePath &path, const Gtk::TreeIter &iter, SPGradient *vector)
354 {
355     bool found = false;
356 
357     Gtk::TreeModel::Row row = *iter;
358     if (vector == row[_columns->data]) {
359         _treeview->scroll_to_row(path, 0.5);
360         auto select = _treeview->get_selection();
361         bool wasBlocked = _blocked;
362         _blocked = true;
363         select->select(iter);
364         _blocked = wasBlocked;
365         found = true;
366     }
367 
368     return found;
369 }
370 
selectGradientInTree(SPGradient * vector)371 void GradientSelector::selectGradientInTree(SPGradient *vector)
372 {
373     _store->foreach (sigc::bind<SPGradient *>(sigc::mem_fun(*this, &GradientSelector::_checkForSelected), vector));
374 }
375 
setVector(SPDocument * doc,SPGradient * vector)376 void GradientSelector::setVector(SPDocument *doc, SPGradient *vector)
377 {
378     g_return_if_fail(!vector || SP_IS_GRADIENT(vector));
379     g_return_if_fail(!vector || (vector->document == doc));
380 
381     if (vector && !vector->hasStops()) {
382         return;
383     }
384 
385     _vectors->set_gradient(doc, vector);
386 
387     selectGradientInTree(vector);
388 
389     if (vector) {
390         if ((_mode == MODE_SWATCH) && vector->isSwatch()) {
391             if (vector->isSolid()) {
392                 for (auto &it : _nonsolid) {
393                     it->hide();
394                 }
395             } else {
396                 for (auto &it : _nonsolid) {
397                     it->show_all();
398                 }
399             }
400         } else if (_mode != MODE_SWATCH) {
401 
402             for (auto &swatch_widget : _swatch_widgets) {
403                 swatch_widget->hide();
404             }
405             for (auto &it : _nonsolid) {
406                 it->show_all();
407             }
408         }
409 
410         if (_edit) {
411             _edit->set_sensitive(true);
412         }
413         if (_add) {
414             _add->set_sensitive(true);
415         }
416         if (_del) {
417             _del->set_sensitive(true);
418         }
419     } else {
420         if (_edit) {
421             _edit->set_sensitive(false);
422         }
423         if (_add) {
424             _add->set_sensitive(doc != nullptr);
425         }
426         if (_del) {
427             _del->set_sensitive(false);
428         }
429     }
430 }
431 
getVector()432 SPGradient *GradientSelector::getVector()
433 {
434     return _vectors->get_gradient();
435 }
436 
437 
vector_set(SPGradient * gr)438 void GradientSelector::vector_set(SPGradient *gr)
439 {
440     if (!_blocked) {
441         _blocked = true;
442         gr = sp_gradient_ensure_vector_normalized(gr);
443         setVector((gr) ? gr->document : nullptr, gr);
444         _signal_changed.emit(gr);
445         _blocked = false;
446     }
447 }
448 
449 
delete_vector_clicked()450 void GradientSelector::delete_vector_clicked()
451 {
452     const auto selection = _treeview->get_selection();
453     if (!selection) {
454         return;
455     }
456 
457     SPGradient *obj = nullptr;
458     /* Single selection */
459     Gtk::TreeModel::iterator iter = selection->get_selected();
460     if (iter) {
461         Gtk::TreeModel::Row row = *iter;
462         obj = row[_columns->data];
463     }
464 
465     if (obj) {
466         std::string id = obj->getId();
467         sp_gradient_unset_swatch(SP_ACTIVE_DESKTOP, id);
468     }
469 }
470 
edit_vector_clicked()471 void GradientSelector::edit_vector_clicked()
472 {
473     // Invoke the gradient tool
474     auto verb = Inkscape::Verb::get(SP_VERB_CONTEXT_GRADIENT);
475     if (verb) {
476         auto action = verb->get_action(Inkscape::ActionContext((Inkscape::UI::View::View *)SP_ACTIVE_DESKTOP));
477         if (action) {
478             sp_action_perform(action, nullptr);
479         }
480     }
481 }
482 
add_vector_clicked()483 void GradientSelector::add_vector_clicked()
484 {
485     auto doc = _vectors->get_document();
486 
487     if (!doc)
488         return;
489 
490     auto gr = _vectors->get_gradient();
491     Inkscape::XML::Document *xml_doc = doc->getReprDoc();
492 
493     Inkscape::XML::Node *repr = nullptr;
494 
495     if (gr) {
496         repr = gr->getRepr()->duplicate(xml_doc);
497         // Rename the new gradients id to be similar to the cloned gradients
498         Glib::ustring old_id = gr->getId();
499         rename_id(gr, old_id);
500         doc->getDefs()->getRepr()->addChild(repr, nullptr);
501     } else {
502         repr = xml_doc->createElement("svg:linearGradient");
503         Inkscape::XML::Node *stop = xml_doc->createElement("svg:stop");
504         stop->setAttribute("offset", "0");
505         stop->setAttribute("style", "stop-color:#000;stop-opacity:1;");
506         repr->appendChild(stop);
507         Inkscape::GC::release(stop);
508         stop = xml_doc->createElement("svg:stop");
509         stop->setAttribute("offset", "1");
510         stop->setAttribute("style", "stop-color:#fff;stop-opacity:1;");
511         repr->appendChild(stop);
512         Inkscape::GC::release(stop);
513         doc->getDefs()->getRepr()->addChild(repr, nullptr);
514         gr = SP_GRADIENT(doc->getObjectByRepr(repr));
515     }
516 
517     _vectors->set_gradient(doc, gr);
518 
519     selectGradientInTree(gr);
520 
521     Inkscape::GC::release(repr);
522 }
523 
524 } // namespace Widget
525 } // namespace UI
526 } // namespace Inkscape
527 
528 /*
529   Local Variables:
530   mode:c++
531   c-file-style:"stroustrup"
532   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
533   indent-tabs-mode:nil
534   fill-column:99
535   End:
536 */
537 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
538