Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
marker-combo-box.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Author:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 * Maximilian Albert <maximilian.albert@gmail.com>
10 *
11 * Copyright (C) 2002 Lauris Kaplinski
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16#include "marker-combo-box.h"
17
18#include <chrono>
19#include <optional>
20#include <sstream>
21#include <utility>
22
23#include <gtkmm/layoutmanager.h>
24#include <gtkmm/binlayout.h>
25#include <glibmm/fileutils.h>
26#include <glibmm/i18n.h>
27#include <glibmm/main.h>
28#include <gtkmm/button.h>
29#include <gtkmm/checkbutton.h>
30#include <gtkmm/flowbox.h>
31#include <gtkmm/grid.h>
32#include <gtkmm/image.h>
33#include <gtkmm/label.h>
34#include <gtkmm/menubutton.h>
35#include <gtkmm/picture.h>
36#include <gtkmm/spinbutton.h>
37#include <gtkmm/togglebutton.h>
38#include <gtkmm/window.h>
39
40#include "helper/stock-items.h"
41#include "io/resource.h"
42#include "object/sp-defs.h"
43#include "object/sp-marker.h"
44#include "object/sp-root.h"
45#include "ui/builder-utils.h"
47#include "ui/svg-renderer.h"
48#include "ui/util.h"
50#include "util/static-doc.h"
51
52#define noTIMING_INFO 1;
53
56
57// size of marker image in a list
58static constexpr int ITEM_WIDTH = 40;
59static constexpr int ITEM_HEIGHT = 32;
60
61namespace Inkscape::UI::Widget {
62
63// separator for FlowBox widget
64static cairo_surface_t* create_separator(double alpha, int width, int height, int device_scale) {
65 width *= device_scale;
66 height *= device_scale;
67 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
68 cairo_t* ctx = cairo_create(surface);
69 cairo_set_source_rgba(ctx, 0.5, 0.5, 0.5, alpha);
70 cairo_move_to(ctx, 0.5, height / 2 + 0.5);
71 cairo_line_to(ctx, width + 0.5, height / 2 + 0.5);
72 cairo_set_line_width(ctx, 1.0 * device_scale);
73 cairo_stroke(ctx);
74 cairo_surface_flush(surface);
75 cairo_surface_set_device_scale(surface, device_scale, device_scale);
76 return surface;
77}
78
79// empty image; "no marker"
80static Cairo::RefPtr<Cairo::Surface> g_image_none;
81// error extracting/rendering marker; "bad marker"
82static Cairo::RefPtr<Cairo::Surface> g_bad_marker;
83
84Glib::ustring get_attrib(SPMarker* marker, const char* attrib) {
85 auto value = marker->getAttribute(attrib);
86 return value ? value : "";
87}
88
89double get_attrib_num(SPMarker* marker, const char* attrib) {
90 auto val = get_attrib(marker, attrib);
91 return strtod(val.c_str(), nullptr);
92}
93
94MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) :
95 Glib::ObjectBase{"MarkerComboBox"},
97 Gtk::Box{},
98 _combo_id(std::move(id)),
99 _loc(l),
100 _builder(create_builder("marker-popup.glade")),
101 _marker_list(get_widget<Gtk::FlowBox>(_builder, "flowbox")),
102 // _preview_bin(get_derived_widget<UI::Widget::Bin>(_builder, "preview-bin")),
103 _preview(get_widget<Gtk::Picture>(_builder, "preview")),
104 _marker_name(get_widget<Gtk::Label>(_builder, "marker-id")),
105 _link_scale(get_widget<Gtk::Button>(_builder, "link-scale")),
106 _scale_x(get_widget<Gtk::SpinButton>(_builder, "scale-x")),
107 _scale_y(get_widget<Gtk::SpinButton>(_builder, "scale-y")),
108 _scale_with_stroke(get_widget<Gtk::CheckButton>(_builder, "scale-with-stroke")),
109 _menu_btn(get_widget<Gtk::MenuButton>(_builder, "menu-btn")),
110 _angle_btn(get_widget<Gtk::SpinButton>(_builder, "angle")),
111 _offset_x(get_widget<Gtk::SpinButton>(_builder, "offset-x")),
112 _offset_y(get_widget<Gtk::SpinButton>(_builder, "offset-y")),
113 _input_grid(get_widget<Gtk::Grid>(_builder, "input-grid")),
114 _orient_auto_rev(get_widget<Gtk::ToggleButton>(_builder, "orient-auto-rev")),
115 _orient_auto(get_widget<Gtk::ToggleButton>(_builder, "orient-auto")),
116 _orient_angle(get_widget<Gtk::ToggleButton>(_builder, "orient-angle")),
117 _orient_flip_horz(get_widget<Gtk::Button>(_builder, "btn-horz-flip")),
118 _current_img(get_widget<Gtk::Picture>(_builder, "current-img")),
119 _edit_marker(get_widget<Gtk::Button>(_builder, "edit-marker"))
120{
121 // override picture's size reporting and rely on widget size instead of using texture dimensions;
122 // texture has no density metadata, so it cannot be used for the purpose of calculating widget's natural size
123 _current_img.set_layout_manager(Gtk::BinLayout::create());
124
125 _background_color = 0x808080ff;
126 _foreground_color = 0x808080ff;
127
128 if (!g_image_none) {
129 auto device_scale = get_scale_factor();
130 g_image_none = Cairo::RefPtr<Cairo::Surface>(new Cairo::Surface(create_separator(1, ITEM_WIDTH, ITEM_HEIGHT, device_scale)));
131 }
132
133 if (!g_bad_marker) {
135 Inkscape::svg_renderer renderer(path.c_str());
136 g_bad_marker = renderer.render_surface(1.0);
137 }
138
139 prepend(_menu_btn);
140
141 auto lm = SizeReporter::create();
142 _preview.set_layout_manager(lm);
143 lm->resized.connect([this] {
144 // refresh after preview widget has been finally resized/expanded
146 });
147
149 _marker_list.bind_list_store(_marker_store, [this](const Glib::RefPtr<MarkerItem>& item){
150 auto const image = Gtk::make_managed<Gtk::Picture>(to_texture(item->pix));
151 image->set_content_fit(Gtk::ContentFit::SCALE_DOWN);
152 image->set_layout_manager(Gtk::BinLayout::create());
153 image->set_visible(true);
154 auto const box = Gtk::make_managed<Gtk::FlowBoxChild>();
155 box->set_child(*image);
156 if (item->separator) {
157 image->set_sensitive(false);
158 image->set_focusable(false);
159 image->set_size_request(-1, 10);
160 box->set_sensitive(false);
161 box->set_focusable(false);
162 box->add_css_class("marker-separator");
163 }
164 else {
165 image->set_size_request(item->width, item->height);
166 box->add_css_class("marker-item-box");
167 }
169 box->set_size_request(item->width, item->height);
170 // removing ability to focus from all items to prevent crash when user edits "Offset Y" and presses tab key
171 // to move to the next widget; not ideal, as it limits navigation, but lesser evil
172 box->set_focusable(false);
173 return box;
174 });
175
177
178 set_sensitive(true);
179
180 _marker_list.signal_selected_children_changed().connect([this](){
181 auto item = get_active();
182 if (!item && !_marker_list.get_selected_children().empty()) {
183 _marker_list.unselect_all();
184 }
185 });
186
187 _marker_list.signal_child_activated().connect([this](Gtk::FlowBoxChild* box){
188 if (box->get_sensitive()) _signal_changed.emit();
189 });
190
191 auto set_orient = [this](bool enable_angle, const char* value) {
192 if (_update.pending()) return;
193 _angle_btn.set_sensitive(enable_angle);
195 };
196 _orient_auto_rev.signal_toggled().connect([=](){ set_orient(false, "auto-start-reverse"); });
197 _orient_auto.signal_toggled().connect([=]() { set_orient(false, "auto"); });
198 _orient_angle.signal_toggled().connect([=, this]() { set_orient(true, _angle_btn.get_text().c_str()); });
199 _orient_flip_horz.signal_clicked().connect([this]() { sp_marker_flip_horizontally(get_current()); });
200
201 _angle_btn.signal_value_changed().connect([this]() {
202 if (_update.pending() || !_angle_btn.is_sensitive()) return;
203 sp_marker_set_orient(get_current(), _angle_btn.get_text().c_str());
204 });
205
206 auto set_scale = [this](bool changeWidth) {
207 if (_update.pending()) return;
208
209 if (auto marker = get_current()) {
210 auto sx = _scale_x.get_value();
211 auto sy = _scale_y.get_value();
212 auto width = get_attrib_num(marker, "markerWidth");
213 auto height = get_attrib_num(marker, "markerHeight");
214 if (_scale_linked && width > 0.0 && height > 0.0) {
215 auto scoped(_update.block());
216 if (changeWidth) {
217 // scale height proportionally
218 sy = height * (sx / width);
219 _scale_y.set_value(sy);
220 }
221 else {
222 // scale width proportionally
223 sx = width * (sy / height);
224 _scale_x.set_value(sx);
225 }
226 }
227 sp_marker_set_size(marker, sx, sy);
228 }
229 };
230
231 // delay setting scale to idle time; if invoked by focus change due to new marker selection
232 // it leads to marker list rebuild and apparent flowbox content corruption
233 auto idle_set_scale = [=, this](bool changeWidth) {
234 if (_update.pending()) return;
235
236 if (auto orig_marker = get_current()) {
237 _idle = Glib::signal_idle().connect([=, this](){
238 if (auto marker = get_current()) {
239 if (marker == orig_marker) {
240 set_scale(changeWidth);
241 }
242 }
243 return false; // don't call again
244 });
245 }
246 };
247
248 _link_scale.signal_clicked().connect([this](){
249 if (_update.pending()) return;
253 });
254
255 _scale_x.signal_value_changed().connect([=]() { idle_set_scale(true); });
256 _scale_y.signal_value_changed().connect([=]() { idle_set_scale(false); });
257
258 _scale_with_stroke.signal_toggled().connect([this](){
259 if (_update.pending()) return;
261 });
262
263 auto set_offset = [this](){
264 if (_update.pending()) return;
265 sp_marker_set_offset(get_current(), _offset_x.get_value(), _offset_y.get_value());
266 };
267 _offset_x.signal_value_changed().connect([=]() { set_offset(); });
268 _offset_y.signal_value_changed().connect([=]() { set_offset(); });
269
270 // request to edit marker on canvas; close popup to get it out of the way and call marker edit tool
271 _edit_marker.signal_clicked().connect([this]{ _menu_btn.get_popover()->popdown(); _signal_edit(); });
272
273 // before showing popover refresh marker attributes
274 _menu_btn.get_popover()->signal_show().connect([this](){ update_ui(get_current(), false); }, false);
275
277 _current_img.set_paintable(to_texture(g_image_none));
278 set_visible(true);
279}
280
282 _input_grid.set_sensitive(marker != nullptr);
283
284 if (marker) {
285 _scale_x.set_value(get_attrib_num(marker, "markerWidth"));
286 _scale_y.set_value(get_attrib_num(marker, "markerHeight"));
287 auto units = get_attrib(marker, "markerUnits");
288 _scale_with_stroke.set_active(units == "strokeWidth" || units == "");
289 auto aspect = get_attrib(marker, "preserveAspectRatio");
290 _scale_linked = aspect != "none";
292 // marker->setAttribute("markerUnits", scale_with_stroke ? "strokeWidth" : "userSpaceOnUse");
293 _offset_x.set_value(get_attrib_num(marker, "refX"));
294 _offset_y.set_value(get_attrib_num(marker, "refY"));
295 auto orient = get_attrib(marker, "orient");
296
297 // try parsing as number
298 _angle_btn.set_value(strtod(orient.c_str(), nullptr));
299 if (orient == "auto-start-reverse") {
300 _orient_auto_rev.set_active();
301 _angle_btn.set_sensitive(false);
302 }
303 else if (orient == "auto") {
304 _orient_auto.set_active();
305 _angle_btn.set_sensitive(false);
306 }
307 else {
308 _orient_angle.set_active();
309 _angle_btn.set_sensitive(true);
310 }
311 }
312}
313
315 _link_scale.set_child(get_widget<Gtk::Image>(_builder, _scale_linked ? "image-linked" : "image-unlinked"));
316}
317
318// update marker image inside the menu button
319void MarkerComboBox::update_menu_btn(Glib::RefPtr<MarkerItem> marker) {
320 _current_img.set_paintable(to_texture(marker ? marker->pix : g_image_none));
321}
322
323// update marker preview image in the popover panel
324void MarkerComboBox::update_preview(Glib::RefPtr<MarkerItem> item) {
325 Cairo::RefPtr<Cairo::Surface> surface;
326 Glib::ustring label;
327
328 if (!item) {
329 // TRANSLATORS: None - no marker selected for a path
330 label = _("None");
331 }
332
333 if (item && item->source && !item->id.empty()) {
334 Inkscape::Drawing drawing;
335 unsigned const visionkey = SPItem::display_key_new(1);
336 drawing.setRoot(_sandbox->getRoot()->invoke_show(drawing, visionkey, SP_ITEM_SHOW_DISPLAY));
337 // generate preview
338 auto alloc = _preview.get_allocation();
339 auto size = Geom::IntPoint(alloc.get_width(), alloc.get_height());
340 if (size.x() > 0 && size.y() > 0) {
341 surface = create_marker_image(size, item->id.c_str(), item->source, drawing, visionkey, true, true, 2.60);
342 }
343 else {
344 // too early, preview hasn't been expanded/resized yet
345 _preview_no_alloc = true;
346 }
347 _sandbox->getRoot()->invoke_hide(visionkey);
348 label = _(item->label.c_str());
349 }
350
351 _preview.set_paintable(to_texture(surface));
352 std::ostringstream ost;
353 ost << "<small>" << label.raw() << "</small>";
354 _marker_name.set_markup(ost.str().c_str());
355}
356
358 return
359 id == item.id &&
360 label == item.label &&
361 separator == item.separator &&
362 stock == item.stock &&
363 history == item.history &&
364 source == item.source &&
365 width == item.width &&
366 height == item.height;
367}
368
369// find marker object by ID in a document
370SPMarker* find_marker(SPDocument* document, const Glib::ustring& marker_id) {
371 if (!document) return nullptr;
372
373 SPDefs* defs = document->getDefs();
374 if (!defs) return nullptr;
375
376 for (auto& child : defs->children) {
377 if (is<SPMarker>(&child)) {
378 auto marker = cast<SPMarker>(&child);
379 auto id = marker->getId();
380 if (id && marker_id == id) {
381 // found it
382 return marker;
383 }
384 }
385 }
386
387 // not found
388 return nullptr;
389}
390
392 // find current marker
394}
395
396void MarkerComboBox::set_active(Glib::RefPtr<MarkerItem> item) {
397 bool selected = false;
398 if (item) {
399 UI::for_each_child(_marker_list, [item, &selected, this] (Gtk::Widget &widget) {
400 if (auto box = dynamic_cast<Gtk::FlowBoxChild*>(&widget)) {
401 if (auto marker = _widgets_to_markers[box->get_child()]) {
402 if (*marker == *item) {
403 _marker_list.select_child(*box);
404 selected = true;
405 }
406 }
407 }
409 });
410 }
411
412 if (!selected) {
413 _marker_list.unselect_all();
414 }
415}
416
417Glib::RefPtr<MarkerComboBox::MarkerItem> MarkerComboBox::find_marker_item(SPMarker* marker) {
418 std::string id;
419 if (marker != nullptr) {
420 if (auto markname = marker->getRepr()->attribute("id")) {
421 id = markname;
422 }
423 }
424
425 Glib::RefPtr<MarkerItem> marker_item;
426 if (!id.empty()) {
427 for (auto&& item : _history_items) {
428 if (item->id == id) {
429 marker_item = item;
430 break;
431 }
432 }
433 }
434
435 return marker_item;
436}
437
438Glib::RefPtr<MarkerComboBox::MarkerItem> MarkerComboBox::get_active() {
439 auto empty = Glib::RefPtr<MarkerItem>();
440 auto sel = _marker_list.get_selected_children();
441 if (sel.size() == 1) {
442 auto item = _widgets_to_markers[sel.front()->get_child()];
443 if (item && item->separator) {
444 return empty;
445 }
446 return item;
447 }
448 else {
449 return empty;
450 }
451}
452
454{
455 if (_document != document) {
456
457 if (_document) {
458 modified_connection.disconnect();
459 }
460
461 _document = document;
462
463 if (_document) {
464 modified_connection = _document->getDefs()->connectModified([this](SPObject*, unsigned int){
466 });
467 }
468
470
472 }
473}
474
483 if (_update.pending()) return;
484
485 auto scoped(_update.block());
486
487 /*
488 * Seems to be no way to get notified of changes just to markers,
489 * so listen to changes in all defs and check if the number of markers has changed here
490 * to avoid unnecessary refreshes when things like gradients change
491 */
492 // TODO: detect changes to markers; ignore changes to everything else;
493 // simple count check doesn't cut it, so just do it unconditionally for now
495
496 auto marker = find_marker_item(get_current());
497 update_menu_btn(marker);
498 update_preview(marker);
499}
500
501Glib::RefPtr<MarkerComboBox::MarkerItem> MarkerComboBox::add_separator(bool filler) {
502 auto item = MarkerItem::create();
503 item->history = false;
504 item->separator = true;
505 item->id = "None";
506 item->label = filler ? "filler" : "Separator";
507 item->stock = false;
508 if (!filler) {
509 auto device_scale = get_scale_factor();
510 static Cairo::RefPtr<Cairo::Surface> separator(new Cairo::Surface(create_separator(0.7, ITEM_WIDTH, 10, device_scale)));
511 item->pix = separator;
512 }
513 item->height = 10;
514 item->width = -1;
515 return item;
516}
517
521void
523{
524 if (_update.pending()) return;
525
526 auto const markers_doc = Util::cache_static_doc([] {
527 // find and load markers.svg
528 using namespace Inkscape::IO::Resource;
529 auto markers_source = get_path_string(SYSTEM, MARKERS, "markers.svg");
530 return SPDocument::createNewDoc(markers_source.c_str(), false);
531 });
532
533 // load markers from markers.svg
534 if (markers_doc) {
535 marker_list_from_doc(markers_doc, false);
536 }
537
539}
540
545{
546 auto sp_marker = cast<SPMarker>(marker);
547
548 bool reselect = sp_marker != get_current();
549
550 update_ui(sp_marker, reselect);
551}
552
553void MarkerComboBox::update_ui(SPMarker* marker, bool select) {
554 auto scoped(_update.block());
555
556 auto id = marker ? marker->getId() : nullptr;
557 _current_marker_id = id ? id : "";
558
559 auto marker_item = find_marker_item(marker);
560
561 if (select) {
562 set_active(marker_item);
563 }
564
566 update_menu_btn(marker_item);
567 update_preview(marker_item);
568}
569
574{
575 /* Get Marker */
576 auto item = get_active();
577 if (!item) {
578 return std::string();
579 }
580
581 std::string marker;
582
583 if (item->id != "none") {
584 bool stockid = item->stock;
585
586 std::string markurn = stockid ? "urn:inkscape:marker:" + item->id : item->id;
587 auto mark = cast<SPMarker>(get_stock_item(markurn.c_str(), stockid));
588
589 if (mark) {
590 Inkscape::XML::Node* repr = mark->getRepr();
591 auto id = repr->attribute("id");
592 if (id) {
593 std::ostringstream ost;
594 ost << "url(#" << id << ")";
595 marker = ost.str();
596 }
597 if (stockid) {
598 mark->getRepr()->setAttribute("inkscape:collect", "always");
599 }
600 // adjust marker's attributes (or add missing ones) to stay in sync with marker tool
602 }
603 } else {
604 marker = item->id;
605 }
606
607 return marker;
608}
609
616 std::vector<SPMarker*> markers = get_marker_list(source);
617 remove_markers(history);
618 add_markers(markers, source, history);
619 update_store();
620}
621
623 _marker_store->freeze_notify();
624
625 auto selected = get_active();
626
627 _marker_store->remove_all();
628 _widgets_to_markers.clear();
629
630 // recent and user-defined markers come first
631 for (auto&& item : _history_items) {
632 _marker_store->append(item);
633 }
634
635 // separator
636 if (!_history_items.empty()) {
637 // add empty boxes to fill up the row to 'max' elements and then
638 // extra ones to create entire new empty row (a separator of sorts)
639 auto max = _marker_list.get_max_children_per_line();
640 auto fillup = max - _history_items.size() % max;
641
642 for (int i = 0; i < fillup; ++i) {
643 _marker_store->append(add_separator(true));
644 }
645 for (int i = 0; i < max; ++i) {
646 _marker_store->append(add_separator(false));
647 }
648 }
649
650 // stock markers
651 for (auto&& item : _stock_items) {
652 _marker_store->append(item);
653 }
654
655 _marker_store->thaw_notify();
656
657 // reselect current
658 set_active(selected);
659}
665std::vector<SPMarker*> MarkerComboBox::get_marker_list(SPDocument* source)
666{
667 std::vector<SPMarker *> ml;
668 if (source == nullptr) return ml;
669
670 SPDefs *defs = source->getDefs();
671 if (!defs) {
672 return ml;
673 }
674
675 for (auto& child: defs->children) {
676 if (is<SPMarker>(&child)) {
677 auto marker = cast<SPMarker>(&child);
678 ml.push_back(marker);
679 }
680 }
681 return ml;
682}
683
687void MarkerComboBox::remove_markers (gboolean history)
688{
689 if (history) {
690 _history_items.clear();
691 }
692 else {
693 _stock_items.clear();
694 }
695}
696
700void MarkerComboBox::add_markers (std::vector<SPMarker *> const& marker_list, SPDocument *source, gboolean history)
701{
702 // Do this here, outside of loop, to speed up preview generation:
703 Inkscape::Drawing drawing;
704 unsigned const visionkey = SPItem::display_key_new(1);
705 drawing.setRoot(_sandbox->getRoot()->invoke_show(drawing, visionkey, SP_ITEM_SHOW_DISPLAY));
706
707 if (history) {
708 // add "None"
709 auto item = MarkerItem::create();
710 item->pix = g_image_none;
711 item->history = true;
712 item->separator = false;
713 item->id = "None";
714 item->label = "None";
715 item->stock = false;
716 item->width = ITEM_WIDTH;
717 item->height = ITEM_HEIGHT;
718 _history_items.push_back(item);
719 }
720
721#if TIMING_INFO
722auto old_time = std::chrono::high_resolution_clock::now();
723#endif
724
725 for (auto i:marker_list) {
726
727 Inkscape::XML::Node *repr = i->getRepr();
728 gchar const *markid = repr->attribute("inkscape:stockid") ? repr->attribute("inkscape:stockid") : repr->attribute("id");
729
730 // generate preview
731 auto pixbuf = create_marker_image(Geom::IntPoint(ITEM_WIDTH, ITEM_HEIGHT), repr->attribute("id"), source, drawing, visionkey, false, true, 1.50);
732
733 auto item = MarkerItem::create();
734 item->source = source;
735 item->pix = pixbuf;
736 if (auto id = repr->attribute("id")) {
737 item->id = id;
738 }
739 item->label = markid ? markid : "";
740 item->stock = !history;
741 item->history = history;
742 item->width = ITEM_WIDTH;
743 item->height = ITEM_HEIGHT;
744
745 if (history) {
746 _history_items.emplace_back(std::move(item));
747 }
748 else {
749 _stock_items.emplace_back(std::move(item));
750 }
751 }
752
753 _sandbox->getRoot()->invoke_hide(visionkey);
754
755#if TIMING_INFO
756auto current_time = std::chrono::high_resolution_clock::now();
757auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - old_time);
758g_warning("%s render time for %d markers: %d ms", combo_id, (int)marker_list.size(), static_cast<int>(elapsed.count()));
759#endif
760}
761
767Cairo::RefPtr<Cairo::Surface>
769 SPDocument *source, Inkscape::Drawing &drawing, unsigned /*visionkey*/, bool checkerboard, bool no_clip, double scale)
770{
771 std::optional<guint32> checkerboard_color;
772 if (checkerboard) {
773 checkerboard_color = _background_color;
774 }
775
776 int device_scale = get_scale_factor();
777 auto const fg = get_color();
778 return Inkscape::create_marker_image(_combo_id, _sandbox.get(), fg, pixel_size, mname, source,
779 drawing, checkerboard_color, no_clip, scale, device_scale);
780}
781
782// capture background color when styles change
783void MarkerComboBox::css_changed(GtkCssStyleChange *) {
784 auto background = _background_color;
785 if (auto wnd = dynamic_cast<Gtk::Window*>(this->get_root())) {
786 auto const color = get_color_with_class(*wnd, "theme_bg_color");
787 background =
788 gint32(0xff * color.get_red()) << 24 |
789 gint32(0xff * color.get_green()) << 16 |
790 gint32(0xff * color.get_blue()) << 8 |
791 0xff;
792 }
793
794 auto const color = get_color();
795 auto foreground =
796 gint32(0xff * color.get_red()) << 24 |
797 gint32(0xff * color.get_green()) << 16 |
798 gint32(0xff * color.get_blue()) << 8 |
799 0xff;
800 if (foreground != _foreground_color || background != _background_color) {
801 _foreground_color = foreground;
802 _background_color = background;
803 // theme changed?
804 init_combo();
805 }
806}
807
808sigc::connection MarkerComboBox::connect_changed(sigc::slot<void ()> slot)
809{
810 return _signal_changed.connect(std::move(slot));
811}
812
813sigc::connection MarkerComboBox::connect_edit(sigc::slot<void ()> slot)
814{
815 return _signal_edit.connect(std::move(slot));
816}
817
818} // namespace Inkscape::UI::Widget
819
820/*
821 Local Variables:
822 mode:c++
823 c-file-style:"stroustrup"
824 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
825 indent-tabs-mode:nil
826 fill-column:99
827 End:
828*/
829// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Gtk builder utilities.
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
Two-dimensional point with integer coordinates.
Definition int-point.h:57
void setRoot(DrawingItem *root)
Definition drawing.cpp:67
bool operator==(const MarkerItem &item) const
static Glib::RefPtr< MarkerItem > create()
Glib::RefPtr< MarkerItem > add_separator(bool filler)
void init_combo()
Init the combobox widget to display markers from markers.svg.
void update_widgets_from_marker(SPMarker *marker)
void css_changed(GtkCssStyleChange *change) override
Called after gtk_widget_css_changed(): when a CSS widget node is validated & style changed.
void marker_list_from_doc(SPDocument *source, bool history)
Pick up all markers from source and add items to the list/store.
std::map< Gtk::Widget *, Glib::RefPtr< MarkerItem > > _widgets_to_markers
std::string get_active_marker_uri()
Return a uri string representing the current selected marker used for setting the marker style in the...
sigc::connection connect_changed(sigc::slot< void()> slot)
void remove_markers(gboolean history)
Remove history or non-history markers from the combo.
Glib::RefPtr< Gtk::Builder > _builder
void add_markers(std::vector< SPMarker * > const &marker_list, SPDocument *source, gboolean history)
Adds markers in marker_list to the combo.
void set_active(Glib::RefPtr< MarkerItem > item)
sigc::connection connect_edit(sigc::slot< void()> slot)
Glib::RefPtr< MarkerItem > get_active()
Glib::RefPtr< Gio::ListStore< MarkerItem > > _marker_store
std::unique_ptr< SPDocument > _sandbox
MarkerComboBox(Glib::ustring id, int loc)
void set_current(SPObject *marker)
Sets the current marker in the marker combobox.
void update_menu_btn(Glib::RefPtr< MarkerItem > marker_item)
void update_ui(SPMarker *marker, bool select)
sigc::scoped_connection modified_connection
std::vector< Glib::RefPtr< MarkerItem > > _stock_items
void update_preview(Glib::RefPtr< MarkerItem > marker_item)
std::vector< Glib::RefPtr< MarkerItem > > _history_items
void refresh_after_markers_modified()
This function is invoked after document "defs" section changes.
std::vector< SPMarker * > get_marker_list(SPDocument *source)
Returns a vector of markers in the defs of the given source document as a vector.
Cairo::RefPtr< Cairo::Surface > create_marker_image(Geom::IntPoint pixel_size, gchar const *mname, SPDocument *source, Inkscape::Drawing &drawing, unsigned, bool checkerboard, bool no_clip, double scale)
Creates a copy of the marker named mname, determines its visible and renderable area in the bounding ...
Glib::RefPtr< MarkerItem > find_marker_item(SPMarker *marker)
SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMen...
Definition spinbutton.h:52
A class you can inherit to access GTK4ʼs Widget.css_changed & .focus vfuncs, missing in gtkmm4.
Interface for refcounted XML nodes.
Definition node.h:80
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
Cairo::RefPtr< Cairo::ImageSurface > render_surface(double scale)
scoped_block block()
Typed SVG document implementation.
Definition document.h:101
SPDefs * getDefs()
Return the main defs object for the document.
Definition document.cpp:247
static std::unique_ptr< SPDocument > createNewDoc(char const *filename, bool keepalive, bool make_new=false, SPDocument *parent=nullptr)
Fetches document from filename, or creates new, if NULL; public document appears in document list.
Definition document.cpp:659
void invoke_hide(unsigned int key)
Definition sp-item.cpp:1328
static unsigned int display_key_new(unsigned numkeys)
Allocates unique integer keys.
Definition sp-item.cpp:1254
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
char * id
Definition sp-object.h:192
char const * getId() const
Returns the objects current ID string.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getAttribute(char const *name) const
sigc::connection connectModified(sigc::slot< void(SPObject *, unsigned int)> slot)
Connects to the modification notification signal.
Definition sp-object.h:705
ChildrenList children
Definition sp-object.h:907
static Glib::RefPtr< SizeReporter > create()
struct _cairo_surface cairo_surface_t
std::unique_ptr< Magick::Image > image
SPItem * item
static constexpr int ITEM_HEIGHT
static constexpr int ITEM_WIDTH
Combobox for selecting dash patterns - implementation.
Glib::ustring label
Definition desktop.h:50
std::string get_filename(Type type, char const *filename, bool localized, bool silent)
Definition resource.cpp:170
Custom widgets.
Definition desktop.h:126
static cairo_surface_t * create_separator(double alpha, int width, int height, int device_scale)
static Cairo::RefPtr< Cairo::Surface > g_bad_marker
static constexpr int height
Glib::ustring get_attrib(SPMarker *marker, const char *attrib)
SPMarker * find_marker(SPDocument *document, const Glib::ustring &marker_id)
static Cairo::RefPtr< Cairo::Surface > g_image_none
static constexpr int ITEM_WIDTH
double get_attrib_num(SPMarker *marker, const char *attrib)
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
Gtk::Widget * for_each_child(Gtk::Widget &widget, Func &&func, bool const plus_self=false, bool const recurse=false, int const level=0)
Call Func with a reference to each child of parent, until it returns _break.
Definition util.h:90
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
SPDocument * cache_static_doc(F &&f)
Wrapper for a static SPDocument to ensure it is destroyed early enough.
Definition static-doc.h:27
std::unique_ptr< SPDocument > ink_markers_preview_doc(const Glib::ustring &group_id)
Returns a new document containing default start, mid, and end markers.
Cairo::RefPtr< Cairo::Surface > create_marker_image(const Glib::ustring &group_id, SPDocument *_sandbox, Gdk::RGBA marker_color, Geom::IntPoint pixel_size, const char *mname, SPDocument *source, Inkscape::Drawing &drawing, std::optional< guint32 > checkerboard, bool no_clip, double scale, int device_scale)
Creates a copy of the marker named mname, determines its visible and renderable area in the bounding ...
STL namespace.
signed int gint32
void cairo_line_to(cairo_t *cr, Geom::Point p1)
struct _cairo cairo_t
Definition path-cairo.h:16
void cairo_move_to(cairo_t *cr, Geom::Point p1)
Ocnode * child[8]
Definition quantize.cpp:33
Inkscape::IO::Resource - simple resource API.
void sp_marker_set_orient(SPMarker *marker, const char *value)
void sp_marker_scale_with_stroke(SPMarker *marker, bool scale_with_stroke)
void sp_validate_marker(SPMarker *sp_marker, SPDocument *doc)
void sp_marker_set_offset(SPMarker *marker, double dx, double dy)
void sp_marker_set_uniform_scale(SPMarker *marker, bool uniform)
void sp_marker_set_size(SPMarker *marker, double sx, double sy)
void sp_marker_flip_horizontally(SPMarker *marker)
SPRoot: SVG <svg> implementation.
SPObject * get_stock_item(gchar const *urn, bool stock, SPDocument *stock_doc)
TODO: insert short description here.
double width
void cairo_set_source_rgba(cairo_t *cr, colour c)
Glib::RefPtr< Gdk::Texture > to_texture(Cairo::RefPtr< Cairo::Surface > const &surface)
Convert an image surface in ARGB32 format to a texture.
Definition util.cpp:495
Gdk::RGBA get_color_with_class(Gtk::Widget &widget, Glib::ustring const &css_class)
Definition util.cpp:288