Inkscape
Vector Graphics Editor
gradient-selector.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
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 <string>
18#include <glibmm/i18n.h>
19#include <gtkmm/liststore.h>
20#include <gtkmm/scrolledwindow.h>
21#include <gtkmm/treeview.h>
22#include <sigc++/adaptors/bind.h>
23#include <sigc++/functors/mem_fun.h>
24
25#include "actions/actions-tools.h" // Invoke gradient tool
26#include "document.h"
27#include "document-undo.h"
28#include "gradient-chemistry.h"
29#include "id-clash.h"
30#include "inkscape.h"
31#include "object/sp-defs.h"
32#include "preferences.h"
33#include "ui/controller.h"
34#include "ui/icon-loader.h"
35#include "ui/icon-names.h"
36#include "ui/pack.h"
38
39namespace Inkscape::UI::Widget {
40
41void GradientSelector::style_button(Gtk::Button *btn, char const *iconName)
42{
43 btn->set_image_from_icon_name(iconName, Gtk::IconSize::NORMAL); // Previously GTK_ICON_SIZE_SMALL_TOOLBAR
44 btn->set_has_frame(false);
45}
46
48 : _blocked(false)
49 , _mode(MODE_LINEAR)
50 , _gradientUnits(SP_GRADIENT_UNITS_USERSPACEONUSE)
51 , _gradientSpread(SP_GRADIENT_SPREAD_PAD)
52{
53 set_orientation(Gtk::Orientation::VERTICAL);
54
55 /* Vectors */
56 _vectors = Gtk::make_managed<GradientVectorSelector>(nullptr, nullptr);
59
60 _treeview = Gtk::make_managed<Gtk::TreeView>();
61 _treeview->set_model(_store);
62 _treeview->set_headers_clickable(true);
63 _treeview->set_search_column(1);
64 _treeview->set_vexpand();
65 _icon_renderer = Gtk::make_managed<Gtk::CellRendererPixbuf>();
66 _text_renderer = Gtk::make_managed<Gtk::CellRendererText>();
67
68 _treeview->append_column(_("Gradient"), *_icon_renderer);
69 auto icon_column = _treeview->get_column(0);
70 icon_column->add_attribute(_icon_renderer->property_pixbuf(), _columns->pixbuf);
71 icon_column->set_sort_column(_columns->color);
72 icon_column->set_clickable(true);
73
74 _treeview->append_column(_("Name"), *_text_renderer);
75 auto name_column = _treeview->get_column(1);
76 _text_renderer->property_editable() = true;
77 name_column->add_attribute(_text_renderer->property_text(), _columns->name);
78 name_column->set_min_width(180);
79 name_column->set_clickable(true);
80 name_column->set_resizable(true);
81
82 _treeview->append_column("#", _columns->refcount);
83 auto count_column = _treeview->get_column(2);
84 count_column->set_clickable(true);
85 count_column->set_resizable(true);
86
87 Controller::add_key<&GradientSelector::onKeyPressed>(*_treeview, *this);
88
89 _treeview->set_visible(true);
90
91 icon_column->signal_clicked().connect(sigc::mem_fun(*this, &GradientSelector::onTreeColorColClick));
92 name_column->signal_clicked().connect(sigc::mem_fun(*this, &GradientSelector::onTreeNameColClick));
93 count_column->signal_clicked().connect(sigc::mem_fun(*this, &GradientSelector::onTreeCountColClick));
94
95 auto tree_select_connection = _treeview->get_selection()->signal_changed().connect(sigc::mem_fun(*this, &GradientSelector::onTreeSelection));
96 _vectors->set_tree_select_connection(tree_select_connection);
97 _text_renderer->signal_edited().connect(sigc::mem_fun(*this, &GradientSelector::onGradientRename));
98
99 _scrolled_window = Gtk::make_managed<Gtk::ScrolledWindow>();
100 _scrolled_window->set_child(*_treeview);
101 _scrolled_window->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
102 _scrolled_window->set_has_frame(true);
103 _scrolled_window->set_size_request(0, 180);
104 _scrolled_window->set_hexpand();
105 _scrolled_window->set_visible(true);
106
107 UI::pack_start(*this, *_scrolled_window, true, true, 4);
108
109
110 /* Create box for buttons */
111 auto const hb = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 0);
112 UI::pack_start(*this, *hb, false, false);
113
114 _add = Gtk::make_managed<Gtk::Button>();
115 style_button(_add, INKSCAPE_ICON("list-add"));
116
117 _nonsolid.push_back(_add);
118 UI::pack_start(*hb, *_add, false, false);
119
120 _add->signal_clicked().connect(sigc::mem_fun(*this, &GradientSelector::add_vector_clicked));
121 _add->set_sensitive(false);
122 _add->set_has_frame(false);
123 _add->set_tooltip_text(_("Create a duplicate gradient"));
124
125 _del2 = Gtk::make_managed<Gtk::Button>();
126 style_button(_del2, INKSCAPE_ICON("list-remove"));
127
128 _nonsolid.push_back(_del2);
129 UI::pack_start(*hb, *_del2, false, false);
130 _del2->signal_clicked().connect(sigc::mem_fun(*this, &GradientSelector::delete_vector_clicked_2));
131 _del2->set_sensitive(false);
132 _del2->set_has_frame(false);
133 _del2->set_tooltip_text(_("Delete unused gradient"));
134
135 // The only use of this button is hidden!
136 _edit = Gtk::make_managed<Gtk::Button>();
137 style_button(_edit, INKSCAPE_ICON("edit"));
138
139 _nonsolid.push_back(_edit);
140 UI::pack_start(*hb, *_edit, false, false);
141 _edit->signal_clicked().connect(sigc::mem_fun(*this, &GradientSelector::edit_vector_clicked));
142 _edit->set_sensitive(false);
143 _edit->set_has_frame(false);
144 _edit->set_tooltip_text(_("Edit gradient"));
145 _edit->set_visible(false);
146
147 _del = Gtk::make_managed<Gtk::Button>();
148 style_button(_del, INKSCAPE_ICON("list-remove"));
149
150 _swatch_widgets.push_back(_del);
151 UI::pack_start(*hb, *_del, false, false);
152 _del->signal_clicked().connect(sigc::mem_fun(*this, &GradientSelector::delete_vector_clicked));
153 _del->set_sensitive(false);
154 _del->set_has_frame(false);
155 _del->set_tooltip_text(_("Delete swatch"));
156}
157
159
161{
162 _gradientSpread = spread;
163 // gtk_combo_box_set_active (GTK_COMBO_BOX(this->spread), gradientSpread);
164}
165
167{
168 if (mode != _mode) {
169 _mode = mode;
170 if (mode == MODE_SWATCH) {
171 for (auto &it : _nonsolid) {
172 it->set_visible(false);
173 }
174 for (auto &swatch_widget : _swatch_widgets) {
175 swatch_widget->set_visible(true);
176 }
177
178 auto icon_column = _treeview->get_column(0);
179 icon_column->set_title(_("Swatch"));
180
182 } else {
183 for (auto &it : _nonsolid) {
184 it->set_visible(true);
185 }
186 for (auto &swatch_widget : _swatch_widgets) {
187 swatch_widget->set_visible(false);
188 }
189 auto icon_column = _treeview->get_column(0);
190 icon_column->set_title(_("Gradient"));
191 }
192 }
193}
194
196
198
200
201void GradientSelector::onGradientRename(const Glib::ustring &path_string, const Glib::ustring &new_text)
202{
203 Gtk::TreePath path(path_string);
204 auto iter = _store->get_iter(path);
205
206 if (iter) {
207 Gtk::TreeModel::Row row = *iter;
208 if (row) {
209 SPObject *obj = row[_columns->data];
210 if (obj) {
211 if (!new_text.empty() && new_text != gr_prepare_label(obj)) {
212 obj->setLabel(new_text.c_str());
213 Inkscape::DocumentUndo::done(obj->document, _("Rename gradient"), INKSCAPE_ICON("color-gradient"));
214 }
215 row[_columns->name] = gr_prepare_label(obj);
216 }
217 }
218 }
219}
220
222{
223 auto column = _treeview->get_column(0);
224 column->set_sort_column(_columns->color);
225}
226
228{
229 auto column = _treeview->get_column(1);
230 column->set_sort_column(_columns->name);
231}
232
233
235{
236 auto column = _treeview->get_column(2);
237 column->set_sort_column(_columns->refcount);
238}
239
240void GradientSelector::moveSelection(int amount, bool down, bool toEnd)
241{
242 auto select = _treeview->get_selection();
243 auto iter = select->get_selected();
244
245 if (amount < 0) {
246 down = !down;
247 amount = -amount;
248 }
249
250 auto canary = iter;
251 if (down) {
252 ++canary;
253 } else {
254 --canary;
255 }
256 while (canary && (toEnd || amount > 0)) {
257 --amount;
258 if (down) {
259 ++canary;
260 ++iter;
261 } else {
262 --canary;
263 --iter;
264 }
265 }
266
267 select->select(iter);
268 _treeview->scroll_to_row(_store->get_path(iter), 0.5);
269}
270
271bool GradientSelector::onKeyPressed(GtkEventControllerKey const * controller,
272 unsigned /*keyval*/, unsigned const keycode,
273 GdkModifierType const state)
274{
275 auto display = Gdk::Display::get_default();
276 auto key = 0u;
277 gdk_display_translate_key(gdk_display_get_default(),
278 keycode,
279 state,
280 0,
281 &key,
282 nullptr,
283 nullptr,
284 nullptr);
285 switch (key) {
286 case GDK_KEY_Up:
287 case GDK_KEY_KP_Up:
288 moveSelection(-1);
289 return true;
290
291 case GDK_KEY_Down:
292 case GDK_KEY_KP_Down:
293 moveSelection(1);
294 return true;
295
296 case GDK_KEY_Page_Up:
297 case GDK_KEY_KP_Page_Up:
298 moveSelection(-5);
299 return true;
300
301 case GDK_KEY_Page_Down:
302 case GDK_KEY_KP_Page_Down:
303 moveSelection(5);
304 return true;
305
306 case GDK_KEY_End:
307 case GDK_KEY_KP_End:
308 moveSelection(0, true, true);
309 return true;
310
311 case GDK_KEY_Home:
312 case GDK_KEY_KP_Home:
313 moveSelection(0, false, true);
314 return true;
315 }
316
317 return false;
318}
319
321{
322 if (!_treeview) {
323 return;
324 }
325
326 if (_blocked) {
327 return;
328 }
329
330 if (!_treeview->has_focus()) {
331 /* Workaround for GTK bug on Windows/OS X
332 * When the treeview initially doesn't have focus and is clicked
333 * sometimes get_selection()->signal_changed() has the wrong selection
334 */
335 _treeview->grab_focus();
336 }
337
338 const auto sel = _treeview->get_selection();
339 if (!sel) {
340 return;
341 }
342
343 SPGradient *obj = nullptr;
344 /* Single selection */
345 auto iter = sel->get_selected();
346 if (iter) {
347 Gtk::TreeModel::Row row = *iter;
348 obj = row[_columns->data];
349 }
350
351 if (obj) {
352 vector_set(obj);
353 }
354
356}
357
359 const auto sel = _treeview->get_selection();
360 if (!sel) {
361 return;
362 }
363
364 SPGradient *obj = nullptr;
365 /* Single selection */
366 auto iter = sel->get_selected();
367 if (iter) {
368 Gtk::TreeModel::Row row = *iter;
369 obj = row[_columns->data];
370 }
371 if (_del2) {
372 _del2->set_sensitive(obj && sp_get_gradient_refcount(obj->document, obj) < 2 && _store->children().size() > 1);
373 }
374}
375
376bool GradientSelector::_checkForSelected(const Gtk::TreePath &path, const Gtk::TreeModel::iterator &iter, SPGradient *vector)
377{
378 bool found = false;
379
380 Gtk::TreeModel::Row row = *iter;
381 if (vector == row[_columns->data]) {
382 _treeview->scroll_to_row(path, 0.5);
383 auto select = _treeview->get_selection();
384 bool wasBlocked = _blocked;
385 _blocked = true;
386 select->select(iter);
387 _blocked = wasBlocked;
388 found = true;
389 }
390
391 return found;
392}
393
395{
396 _store->foreach (sigc::bind(sigc::mem_fun(*this, &GradientSelector::_checkForSelected), vector));
397}
398
400{
401 g_return_if_fail(!vector || (vector->document == doc));
402
403 if (vector && !vector->hasStops()) {
404 return;
405 }
406
407 _vectors->set_gradient(doc, vector);
408
409 selectGradientInTree(vector);
410
411 if (vector) {
412 if ((_mode == MODE_SWATCH) && vector->isSwatch()) {
413 if (vector->isSolid()) {
414 for (auto &it : _nonsolid) {
415 it->set_visible(false);
416 }
417 } else {
418 for (auto &it : _nonsolid) {
419 it->set_visible(true);
420 }
421 }
422 } else if (_mode != MODE_SWATCH) {
423
424 for (auto &swatch_widget : _swatch_widgets) {
425 swatch_widget->set_visible(false);
426 }
427 for (auto &it : _nonsolid) {
428 it->set_visible(true);
429 }
430 }
431
432 if (_edit) {
433 _edit->set_sensitive(true);
434 }
435 if (_add) {
436 _add->set_sensitive(true);
437 }
438 if (_del) {
439 _del->set_sensitive(true);
440 }
442 } else {
443 if (_edit) {
444 _edit->set_sensitive(false);
445 }
446 if (_add) {
447 _add->set_sensitive(doc != nullptr);
448 }
449 if (_del) {
450 _del->set_sensitive(false);
451 }
452 if (_del2) {
453 _del2->set_sensitive(false);
454 }
455 }
456}
457
459{
460 return _vectors->get_gradient();
461}
462
463
465{
466 if (!_blocked) {
467 _blocked = true;
469 setVector((gr) ? gr->document : nullptr, gr);
470 _signal_changed.emit(gr);
471 _blocked = false;
472 }
473}
474
476 const auto selection = _treeview->get_selection();
477 if (!selection) {
478 return;
479 }
480
481 SPGradient *obj = nullptr;
482 /* Single selection */
483 Gtk::TreeModel::iterator iter = selection->get_selected();
484 if (iter) {
485 Gtk::TreeModel::Row row = *iter;
486 obj = row[_columns->data];
487 }
488
489 if (obj) {
490 if (auto repr = obj->getRepr()) {
491 repr->setAttribute("inkscape:collect", "always");
492
493 auto move = iter;
494 --move;
495 if (!move) {
496 move = iter;
497 ++move;
498 }
499 if (move) {
500 selection->select(move);
501 _treeview->scroll_to_row(_store->get_path(move), 0.5);
502 }
503 }
504 }
505}
506
508{
509 const auto selection = _treeview->get_selection();
510 if (!selection) {
511 return;
512 }
513
514 SPGradient *obj = nullptr;
515 /* Single selection */
516 Gtk::TreeModel::iterator iter = selection->get_selected();
517 if (iter) {
518 Gtk::TreeModel::Row row = *iter;
519 obj = row[_columns->data];
520 }
521
522 if (obj) {
523 std::string id = obj->getId();
524 sp_gradient_unset_swatch(SP_ACTIVE_DESKTOP, id);
525 }
526}
527
529{
530 // Invoke the gradient tool.... never actually called as button is hidden in only use!
531 set_active_tool(SP_ACTIVE_DESKTOP, "Gradient");
532}
533
535{
536 auto doc = _vectors->get_document();
537
538 if (!doc)
539 return;
540
541 auto gr = _vectors->get_gradient();
542 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
543
544 Inkscape::XML::Node *repr = nullptr;
545
546 if (gr) {
547 gr->getRepr()->removeAttribute("inkscape:collect");
548 repr = gr->getRepr()->duplicate(xml_doc);
549 // Rename the new gradients id to be similar to the cloned gradients
550 auto new_id = generate_similar_unique_id(doc, gr->getId());
551 gr->setAttribute("id", new_id.c_str());
552 doc->getDefs()->getRepr()->addChild(repr, nullptr);
553 } else {
554 repr = xml_doc->createElement("svg:linearGradient");
555 Inkscape::XML::Node *stop = xml_doc->createElement("svg:stop");
556 stop->setAttribute("offset", "0");
557 stop->setAttribute("style", "stop-color:#000;stop-opacity:1;");
558 repr->appendChild(stop);
560 stop = xml_doc->createElement("svg:stop");
561 stop->setAttribute("offset", "1");
562 stop->setAttribute("style", "stop-color:#fff;stop-opacity:1;");
563 repr->appendChild(stop);
565 doc->getDefs()->getRepr()->addChild(repr, nullptr);
566 gr = cast<SPGradient>(doc->getObjectByRepr(repr));
567 }
568
569 _vectors->set_gradient(doc, gr);
570
572
573 // assign gradient to selection
574 vector_set(gr);
575
577}
578
580 _edit->set_visible(show);
581}
582
584 auto name_column = _treeview->get_column(1);
585 name_column->set_min_width(min_width);
586}
587
590}
591
592} // namespace Inkscape::UI::Widget
593
594/*
595 Local Variables:
596 mode:c++
597 c-file-style:"stroustrup"
598 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
599 indent-tabs-mode:nil
600 fill-column:99
601 End:
602*/
603// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
void set_active_tool(InkscapeWindow *win, Glib::ustring const &tool)
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon)
Gtk::TreeModelColumn< Glib::ustring > name
Gtk::TreeModelColumn< unsigned long > color
Gtk::TreeModelColumn< Glib::RefPtr< Gdk::Pixbuf > > pixbuf
bool onKeyPressed(GtkEventControllerKey const *controller, unsigned keyval, unsigned keycode, GdkModifierType state)
void onGradientRename(const Glib::ustring &path_string, const Glib::ustring &new_text)
Gtk::CellRendererPixbuf * _icon_renderer
void setSpread(SPGradientSpread spread) override
void setVector(SPDocument *doc, SPGradient *vector) override
bool _checkForSelected(const Gtk::TreePath &path, const Gtk::TreeModel::iterator &iter, SPGradient *vector)
void setUnits(SPGradientUnits units) override
void set_gradient_size(int width, int height)
void moveSelection(int amount, bool down=true, bool toEnd=false)
std::vector< Gtk::Widget * > _nonsolid
std::vector< Gtk::Widget * > _swatch_widgets
void setMode(SelectorMode mode) override
Glib::RefPtr< Gtk::ListStore > _store
sigc::signal< void(SPGradient *)> _signal_changed
void selectGradientInTree(SPGradient *vector)
void style_button(Gtk::Button *btn, char const *iconName)
void set_tree_select_connection(sigc::connection &connection)
void set_gradient(SPDocument *doc, SPGradient *gr)
Interface for refcounted XML nodes.
Definition: node.h:80
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition: node.cpp:25
virtual Node * duplicate(Document *doc) const =0
Create a duplicate of this node.
void removeAttribute(Inkscape::Util::const_char_ptr key)
Remove an attribute of this node.
Definition: node.h:280
Typed SVG document implementation.
Definition: document.h:106
Gradient.
Definition: sp-gradient.h:86
bool hasStops() const
Definition: sp-gradient.cpp:59
bool isSolid() const
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition: sp-object.h:146
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Definition: sp-object.cpp:1588
SPDocument * document
Definition: sp-object.h:174
char const * getId() const
Returns the objects current ID string.
Definition: sp-object.cpp:206
void setLabel(char const *label)
Sets the author-visible label for this object.
Definition: sp-object.cpp:449
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
Definition: sp-object.cpp:232
bool isSwatch() const
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
TODO: insert short description here.
void sp_gradient_unset_swatch(SPDesktop *desktop, std::string const &id)
int sp_get_gradient_refcount(SPDocument *document, SPGradient *gradient)
SPGradient * sp_gradient_ensure_vector_normalized(SPGradient *gr)
Either normalizes given gradient to vector, or returns fresh normalized vector - in latter case,...
Glib::ustring gr_prepare_label(SPObject *obj)
Icon Loader.
Macro for icon names used in Inkscape.
Glib::ustring generate_similar_unique_id(SPDocument *document, const Glib::ustring &base_name)
Modify 'base_name' to create a new ID that is not used in the 'document'.
Definition: id-clash.cpp:501
TODO: insert short description here.
static R & release(R &r)
Decrements the reference count of a anchored object.
Definition: gc-anchored.h:133
Button
helper to stop accidents on int vs gtkmm3's weak=typed enums, & looks nicer!
Definition: controller.h:70
Custom widgets.
Definition: desktop.h:127
static constexpr int height
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition: pack.cpp:141
@ HORIZONTAL
The x-dimension (0).
Definition: rectangle.h:43
@ VERTICAL
The y-dimension (1).
Definition: rectangle.h:47
static cairo_user_data_key_t key
Definition: nr-svgfonts.cpp:46
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
int mode
Definition: parametrics.cpp:20
Singleton class to access the preferences file in a convenient way.
SPGradientSpread
@ SP_GRADIENT_SPREAD_PAD
SPGradientUnits
@ SP_GRADIENT_UNITS_USERSPACEONUSE
Interface for XML documents.
Definition: document.h:43
virtual Node * createElement(char const *name)=0
double width