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