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