Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
object-attributes.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
5 * Authors:
6 * see git history
7 * Kris De Gussem <Kris.DeGussem@gmail.com>
8 * Michael Kowalski
9 *
10 * Copyright (C) 2018 Authors
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include <cmath>
15#include <cstddef>
16#include <cstdio>
17#include <gtkmm/entry.h>
18#include <gtkmm/eventcontrollerkey.h>
19#include <gtkmm/menubutton.h>
20#include <gtkmm/scrolledwindow.h>
21#include <gtkmm/textview.h>
22#include <memory>
23#include <optional>
24#include <string>
25#include <tuple>
26#include <glibmm/i18n.h>
27#include <glibmm/markup.h>
28#include <glibmm/ustring.h>
29#include <gtkmm/button.h>
30#include <gtkmm/entry.h>
31#include <gtkmm/enums.h>
32#include <gtkmm/grid.h>
33#include <gtkmm/label.h>
34#include <gtkmm/spinbutton.h>
35#include <gtkmm/togglebutton.h>
36#include <gtkmm/treemodel.h>
37#include <2geom/rect.h>
38
39#include "desktop.h"
40#include "document-undo.h"
41#include "mod360.h"
42#include "preferences.h"
43#include "selection.h"
46#include "live_effects/effect.h"
48#include "object/sp-anchor.h"
49#include "object/sp-ellipse.h"
50#include "object/sp-image.h"
51#include "object/sp-item.h"
52#include "object/sp-lpe-item.h"
53#include "object/sp-namedview.h"
54#include "object/sp-object.h"
55#include "object/sp-path.h"
56#include "object/sp-rect.h"
57#include "object/sp-star.h"
58#include "ui/builder-utils.h"
59#include "ui/controller.h"
61#include "ui/icon-names.h"
62#include "ui/pack.h"
64#include "ui/syntax.h"
65#include "ui/util.h"
71
72namespace Inkscape::UI::Dialog {
73
74struct SPAttrDesc {
75 char const *label;
76 char const *attribute;
77};
78
79static const SPAttrDesc anchor_desc[] = {
80 { N_("Href:"), "xlink:href"},
81 { N_("Target:"), "target"},
82 { N_("Type:"), "xlink:type"},
83 // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkRoleAttribute
84 // Identifies the type of the related resource with an absolute URI
85 { N_("Role:"), "xlink:role"},
86 // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkArcRoleAttribute
87 // For situations where the nature/role alone isn't enough, this offers an additional URI defining the purpose of the link.
88 { N_("Arcrole:"), "xlink:arcrole"},
89 // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkTitleAttribute
90 { N_("Title:"), "xlink:title"},
91 { N_("Show:"), "xlink:show"},
92 // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkActuateAttribute
93 { N_("Actuate:"), "xlink:actuate"},
94 { nullptr, nullptr}
95};
96
97const auto dlg_pref_path = Glib::ustring("/dialogs/object-properties/");
98
100 : DialogBase(dlg_pref_path.c_str(), "ObjectProperties"),
101 _builder(create_builder("object-attributes.glade")),
102 _main_panel(get_widget<Gtk::Box>(_builder, "main-panel")),
103 _obj_title(get_widget<Gtk::Label>(_builder, "main-obj-name")),
104 _style_swatch(nullptr, _("Item's fill, stroke and opacity"), Gtk::Orientation::HORIZONTAL),
105 _obj_properties(*Gtk::make_managed<ObjectProperties>())
106{
107 auto& main = get_widget<Gtk::Box>(_builder, "main-widget");
108 main.append(_obj_properties);
109
110 _obj_title.set_text("");
111 _style_swatch.set_hexpand(false);
112 _style_swatch.set_valign(Gtk::Align::CENTER);
113 get_widget<Gtk::Box>(_builder, "main-header").append(_style_swatch);
114 append(main);
116 _style_swatch.set_visible(false);
117}
118
120 if (_update.pending() || !getDesktop()) return;
121
123 auto item = selection->singleItem();
124
125 auto scoped(_update.block());
126
127 auto panel = get_panel(item);
128 if (panel != _current_panel && _current_panel) {
129 _current_panel->update_panel(nullptr, nullptr);
131 _obj_title.set_text("");
132 }
133
134 _current_panel = panel;
135 _current_item = nullptr;
136 bool enable_props = panel != nullptr;
137
138 Glib::ustring title = panel ? panel->get_title() : "";
139 if (!panel) {
140 if (item) {
141 if (auto name = item->displayName()) {
142 title = name;
143 }
144 // show properties for element without dedicated attributes panel
145 enable_props = true;
146 }
147 else if (selection->size() > 1) {
148 title = _("Multiple objects selected");
149 // current "object properties" subdialog doesn't handle multiselection
150 enable_props = false;
151 }
152 else {
153 title = _("No selection");
154 }
155 }
156 _obj_title.set_markup("<b>" + Glib::Markup::escape_text(title) + "</b>");
157
158 if (!panel) {
159 _style_swatch.set_visible(false);
160 }
161 else {
162 if (_main_panel.get_children().empty()) {
163 UI::pack_start(_main_panel, panel->widget(), true, true);
164 }
165 bool show_style = false;
166 if (panel->supports_fill_stroke()) {
167 if (auto style = item ? item->style : nullptr) {
168 _style_swatch.setStyle(style);
169 show_style = true;
170 }
171 }
172 _style_swatch.set_visible(show_style);
173 panel->update_panel(item, getDesktop());
174 panel->widget().set_visible(true);
175 }
176
178
179 // TODO
180 // show no of LPEs?
181 // show locked status?
182}
183
185 if (!_current_panel) return;
186
188 if (auto style = item ? item->style : nullptr) {
189 _style_swatch.setStyle(style);
190 }
191 }
193}
194
198
203
204void ObjectAttributes::selectionModified(Selection* _selection, guint flags) {
205 if (_update.pending() || !getDesktop() || !_current_panel) return;
206
208 if (flags & (SP_OBJECT_MODIFIED_FLAG |
209 SP_OBJECT_PARENT_MODIFIED_FLAG |
210 SP_OBJECT_STYLE_MODIFIED_FLAG)) {
211
212 auto item = selection->singleItem();
213 if (item == _current_item) {
215 }
216 else {
217 g_warning("ObjectAttributes: missed selection change?");
218 }
219 }
220}
221
223
224std::tuple<bool, double, double> round_values(double x, double y) {
225 auto a = std::round(x);
226 auto b = std::round(y);
227 return std::make_tuple(a != x || b != y, a, b);
228}
229
230std::tuple<bool, double, double> round_values(Gtk::SpinButton& x, Gtk::SpinButton& y) {
231 return round_values(x.get_adjustment()->get_value(), y.get_adjustment()->get_value());
232}
233
235 if (!item) return nullptr;
236
237 auto lpe = item->getFirstPathEffectOfType(Inkscape::LivePathEffect::FILLET_CHAMFER);
238 if (!lpe) return nullptr;
239 return lpe->getLPEObj();
240}
241
243 if (auto effect = find_lpeffect(item, type)) {
244 item->setCurrentPathEffect(effect);
245 auto document = item->document;
246 item->removeCurrentPathEffect(false);
247 DocumentUndo::done(document, _("Removed live path effect"), INKSCAPE_ICON("dialog-path-effects"));
248 }
249}
250
251std::optional<double> get_number(SPItem* item, const char* attribute) {
252 if (!item) return {};
253
254 auto val = item->getAttribute(attribute);
255 if (!val) return {};
256
257 return item->getRepr()->getAttributeDouble(attribute);
258}
259
261 if (!path || !path->sides) return;
262
263 auto arg1 = path->arg[0];
264 auto arg2 = path->arg[1];
265 auto delta = arg2 - arg1;
266 auto top = -M_PI / 2;
267 auto odd = path->sides & 1;
268 if (odd) {
269 arg1 = top;
270 }
271 else {
272 arg1 = top - M_PI / path->sides;
273 }
274 arg2 = arg1 + delta;
275
276 path->setAttributeDouble("sodipodi:arg1", arg1);
277 path->setAttributeDouble("sodipodi:arg2", arg2);
278 path->updateRepr();
279}
280
282
284 _tracker = std::make_unique<UI::Widget::UnitTracker>(Inkscape::Util::UNIT_TYPE_LINEAR);
285 //todo:
286 // auto init_units = desktop->getNamedView()->display_units;
287 // _tracker->setActiveUnit(init_units);
288}
289
291 if (object && object->document) {
292 auto scoped(_update.block());
293 auto units = object->document->getNamedView() ? object->document->getNamedView()->display_units : nullptr;
294 // auto init_units = desktop->getNamedView()->display_units;
295 if (units) _tracker->setActiveUnit(units);
296 }
297
298 _desktop = desktop;
299
300 if (!_update.pending()) {
301 update(object);
302 }
303}
304
305void details::AttributesPanel::change_value_px(SPObject* object, const Glib::RefPtr<Gtk::Adjustment>& adj, const char* attr, std::function<void (double)>&& setter) {
306 if (_update.pending() || !object) return;
307
308 auto scoped(_update.block());
309
310 const auto unit = _tracker->getActiveUnit();
311 auto value = Util::Quantity::convert(adj->get_value(), unit, "px");
312 if (value != 0 || attr == nullptr) {
313 setter(value);
314 }
315 else if (attr) {
316 object->removeAttribute(attr);
317 }
318
319 DocumentUndo::done(object->document, _("Change object attribute"), ""); //TODO INKSCAPE_ICON("draw-rectangle"));
320}
321
322void details::AttributesPanel::change_angle(SPObject* object, const Glib::RefPtr<Gtk::Adjustment>& adj, std::function<void (double)>&& setter) {
323 if (_update.pending() || !object) return;
324
325 auto scoped(_update.block());
326
327 auto value = degree_to_radians_mod2pi(adj->get_value());
328 setter(value);
329
330 DocumentUndo::done(object->document, _("Change object attribute"), ""); //TODO INKSCAPE_ICON("draw-rectangle"));
331}
332
333void details::AttributesPanel::change_value(SPObject* object, const Glib::RefPtr<Gtk::Adjustment>& adj, std::function<void (double)>&& setter) {
334 if (_update.pending() || !object) return;
335
336 auto scoped(_update.block());
337
338 auto value = adj ? adj->get_value() : 0;
339 setter(value);
340
341 DocumentUndo::done(object->document, _("Change object attribute"), ""); //TODO INKSCAPE_ICON("draw-rectangle"));
342}
343
345
346class ImagePanel : public details::AttributesPanel {
347public:
348 ImagePanel() {
349 _title = _("Image");
350 _show_fill_stroke = false;
351 _panel = std::make_unique<Inkscape::UI::Widget::ImageProperties>();
352 _widget = _panel.get();
353 }
354 ~ImagePanel() override = default;
355
356 void update(SPObject* object) override { _panel->update(cast<SPImage>(object)); }
357
358private:
359 std::unique_ptr<Inkscape::UI::Widget::ImageProperties> _panel;
360};
361
363
364class AnchorPanel : public details::AttributesPanel {
365public:
366 AnchorPanel() {
367 _title = _("Anchor");
368 _show_fill_stroke = false;
369 _table = std::make_unique<SPAttributeTable>();
370 _table->set_visible(true);
371 _table->set_hexpand();
372 _table->set_vexpand(false);
373 _widget = _table.get();
374
375 std::vector<Glib::ustring> labels;
376 std::vector<Glib::ustring> attrs;
377 int len = 0;
378 while (anchor_desc[len].label) {
379 labels.emplace_back(anchor_desc[len].label);
380 attrs.emplace_back(anchor_desc[len].attribute);
381 len += 1;
382 }
383 _table->create(labels, attrs);
384 }
385
386 ~AnchorPanel() override = default;
387
388 void update(SPObject* object) override {
389 auto anchor = cast<SPAnchor>(object);
390 auto changed = _anchor != anchor;
391 _anchor = anchor;
392 if (!anchor) {
393 _picker.disconnect();
394 return;
395 }
396
397 if (changed) {
398 _table->change_object(anchor);
399
400 if (auto grid = dynamic_cast<Gtk::Grid*>(_table->get_first_child())) {
401 auto op_button = Gtk::make_managed<Gtk::ToggleButton>();
402 op_button->set_active(false);
403 op_button->set_tooltip_markup(_("<b>Picker Tool</b>\nSelect objects on canvas"));
404 op_button->set_margin_start(4);
405 op_button->set_image_from_icon_name("object-pick");
406
407 op_button->signal_toggled().connect([=, this] {
408 // Use operation blocker to block the toggle signal
409 // emitted when the object has been picked and the
410 // button is toggled.
411 if (!_desktop || _update.pending()) {
412 return;
413 }
414
415 // Disconnect the picker signal if the button state is
416 // toggled to inactive.
417 if (!op_button->get_active()) {
418 _picker.disconnect();
419 set_active_tool(_desktop, _desktop->getTool()->get_last_active_tool());
420 return;
421 }
422
423 // activate object picker tool
424 set_active_tool(_desktop, "Picker");
425
426 if (auto tool = dynamic_cast<Inkscape::UI::Tools::ObjectPickerTool*>(_desktop->getTool())) {
427 _picker = tool->signal_object_picked.connect([grid, this](SPObject* item){
428 // set anchor href
429 auto edit = dynamic_cast<Gtk::Entry*>(grid->get_child_at(1, 0));
430 if (edit && item) {
431 Glib::ustring id = "#";
432 edit->set_text(id + item->getId());
433 }
434 _picker.disconnect();
435 return false; // no more object picking
436 });
437
438 _tool_switched = tool->signal_tool_switched.connect([=, this] {
439 if (op_button->get_active()) {
440 auto scoped(_update.block());
441 op_button->set_active(false);
442 }
443 _tool_switched.disconnect();
444 });
445 }
446 });
447 grid->attach(*op_button, 2, 0);
448 }
449 }
450 else {
451 _table->reread_properties();
452 }
453 }
454
455private:
456 std::unique_ptr<SPAttributeTable> _table;
457 SPAnchor* _anchor = nullptr;
458 sigc::scoped_connection _picker;
459 sigc::scoped_connection _tool_switched;
460 bool _first_time_update = true;
461};
462
464
465class RectPanel : public details::AttributesPanel {
466public:
467 RectPanel(Glib::RefPtr<Gtk::Builder> builder) :
468 _main(get_widget<Gtk::Grid>(builder, "rect-main")),
469 _width(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "rect-width")),
470 _height(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "rect-height")),
471 _rx(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "rect-rx")),
472 _ry(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "rect-ry")),
473 _sharp(get_widget<Gtk::Button>(builder, "rect-sharp")),
474 _round(get_widget<Gtk::Button>(builder, "rect-corners"))
475 {
476 _title = _("Rectangle");
477 _widget = &_main;
478
479 _width.get_adjustment()->signal_value_changed().connect([this](){
480 change_value_px(_rect, _width.get_adjustment(), "width", [this](double w){ _rect->setVisibleWidth(w); });
481 });
482 _height.get_adjustment()->signal_value_changed().connect([this](){
483 change_value_px(_rect, _height.get_adjustment(), "height", [this](double h){ _rect->setVisibleHeight(h); });
484 });
485 _rx.get_adjustment()->signal_value_changed().connect([this](){
486 change_value_px(_rect, _rx.get_adjustment(), "rx", [this](double rx){ _rect->setVisibleRx(rx); });
487 });
488 _ry.get_adjustment()->signal_value_changed().connect([this](){
489 change_value_px(_rect, _ry.get_adjustment(), "ry", [this](double ry){ _rect->setVisibleRy(ry); });
490 });
491 get_widget<Gtk::Button>(builder, "rect-round").signal_clicked().connect([this](){
492 auto [changed, x, y] = round_values(_width, _height);
493 if (changed) {
494 _width.get_adjustment()->set_value(x);
495 _height.get_adjustment()->set_value(y);
496 }
497 });
498 _sharp.signal_clicked().connect([this](){
499 if (!_rect) return;
500
501 // remove rounded corners if LPE is there (first one found)
503 _rx.get_adjustment()->set_value(0);
504 _ry.get_adjustment()->set_value(0);
505 });
506 _round.signal_clicked().connect([this](){
507 if (!_rect || !_desktop) return;
508
509 // switch to node tool to show handles
510 set_active_tool(_desktop, "Node");
511 // rx/ry need to be reset first, LPE doesn't handle them too well
512 _rx.get_adjustment()->set_value(0);
513 _ry.get_adjustment()->set_value(0);
514 // add flexible corners effect if not yet present
516 LivePathEffect::Effect::createAndApply("fillet_chamfer", _rect->document, _rect);
517 DocumentUndo::done(_rect->document, _("Add fillet/chamfer effect"), INKSCAPE_ICON("dialog-path-effects"));
518 }
519 });
520 }
521
522 ~RectPanel() override = default;
523
524 void update(SPObject* object) override {
525 _rect = cast<SPRect>(object);
526 if (!_rect) return;
527
528 auto scoped(_update.block());
529 _width.set_value(_rect->width.value);
530 _height.set_value(_rect->height.value);
531 _rx.set_value(_rect->rx.value);
532 _ry.set_value(_rect->ry.value);
534 _sharp.set_sensitive(_rect->rx.value > 0 || _rect->ry.value > 0 || lpe);
535 _round.set_sensitive(!lpe);
536 }
537
538private:
539 SPRect* _rect = nullptr;
540 Gtk::Widget& _main;
545 Gtk::Button& _sharp;
546 Gtk::Button& _round;
547};
548
550
551class EllipsePanel : public details::AttributesPanel {
552public:
553 EllipsePanel(Glib::RefPtr<Gtk::Builder> builder) :
554 _main(get_widget<Gtk::Grid>(builder, "ellipse-main")),
555 _rx(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "el-rx")),
556 _ry(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "el-ry")),
557 _start(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "el-start")),
558 _end(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "el-end")),
559 _slice(get_widget<Gtk::ToggleButton>(builder, "el-slice")),
560 _arc(get_widget<Gtk::ToggleButton>(builder, "el-arc")),
561 _chord(get_widget<Gtk::ToggleButton>(builder, "el-chord")),
562 _whole(get_widget<Gtk::Button>(builder, "el-whole"))
563 {
564 _title = _("Ellipse");
565 _widget = &_main;
566
567 _type[0] = &_slice;
568 _type[1] = &_arc;
569 _type[2] = &_chord;
570
571 int type = 0;
572 for (auto btn : _type) {
573 btn->signal_toggled().connect([type, this](){ set_type(type); });
574 type++;
575 }
576
577 _whole.signal_clicked().connect([this](){
578 _start.get_adjustment()->set_value(0);
579 _end.get_adjustment()->set_value(0);
580 });
581
582 auto normalize = [this](){
583 _ellipse->normalize();
584 _ellipse->updateRepr();
585 _ellipse->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
586 };
587
588 _rx.get_adjustment()->signal_value_changed().connect([=, this](){
589 change_value_px(_ellipse, _rx.get_adjustment(), nullptr, [=, this](double rx){ _ellipse->setVisibleRx(rx); normalize(); });
590 });
591 _ry.get_adjustment()->signal_value_changed().connect([=, this](){
592 change_value_px(_ellipse, _ry.get_adjustment(), nullptr, [=, this](double ry){ _ellipse->setVisibleRy(ry); normalize(); });
593 });
594 _start.get_adjustment()->signal_value_changed().connect([=, this](){
595 change_angle(_ellipse, _start.get_adjustment(), [=, this](double s){ _ellipse->start = s; normalize(); });
596 });
597 _end.get_adjustment()->signal_value_changed().connect([=, this](){
598 change_angle(_ellipse, _end.get_adjustment(), [=, this](double e){ _ellipse->end = e; normalize(); });
599 });
600
601 get_widget<Gtk::Button>(builder, "el-round").signal_clicked().connect([this](){
602 auto [changed, x, y] = round_values(_rx, _ry);
603 if (changed && x > 0 && y > 0) {
604 _rx.get_adjustment()->set_value(x);
605 _ry.get_adjustment()->set_value(y);
606 }
607 });
608 }
609
610 ~EllipsePanel() override = default;
611
612 void update(SPObject* object) override {
613 _ellipse = cast<SPGenericEllipse>(object);
614 if (!_ellipse) return;
615
616 auto scoped(_update.block());
617 _rx.set_value(_ellipse->rx.value);
618 _ry.set_value(_ellipse->ry.value);
619 _start.set_value(radians_to_degree_mod360(_ellipse->start));
620 _end.set_value(radians_to_degree_mod360(_ellipse->end));
621
622 _slice.set_active(_ellipse->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE);
623 _arc.set_active(_ellipse->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_ARC);
624 _chord.set_active(_ellipse->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD);
625
626 auto slice = !_ellipse->is_whole();
627 _whole.set_sensitive(slice);
628 for (auto btn : _type) {
629 btn->set_sensitive(slice);
630 }
631 }
632
633 void set_type(int type) {
634 if (!_ellipse) return;
635
636 auto scoped(_update.block());
637
638 Glib::ustring arc_type = "slice";
639 bool open = false;
640 switch (type) {
641 case 0:
642 arc_type = "slice";
643 open = false;
644 break;
645 case 1:
646 arc_type = "arc";
647 open = true;
648 break;
649 case 2:
650 arc_type = "chord";
651 open = true; // For backward compat, not truly open but chord most like arc.
652 break;
653 default:
654 std::cerr << "Ellipse type change - bad arc type: " << type << std::endl;
655 break;
656 }
657 _ellipse->setAttribute("sodipodi:open", open ? "true" : nullptr);
658 _ellipse->setAttribute("sodipodi:arc-type", arc_type.c_str());
659 _ellipse->updateRepr();
660 DocumentUndo::done(_ellipse->document, _("Change arc type"), INKSCAPE_ICON("draw-ellipse"));
661 }
662
663private:
664 SPGenericEllipse* _ellipse = nullptr;
665 Gtk::Widget& _main;
670 Gtk::ToggleButton &_slice;
671 Gtk::ToggleButton &_arc;
672 Gtk::ToggleButton &_chord;
673 Gtk::Button& _whole;
674 Gtk::ToggleButton *_type[3];
675};
676
678
679class StarPanel : public details::AttributesPanel {
680public:
681 StarPanel(Glib::RefPtr<Gtk::Builder> builder) :
682 _main(get_widget<Gtk::Grid>(builder, "star-main")),
683 _corners(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "star-corners")),
684 _ratio(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "star-ratio")),
685 _rounded(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "star-rounded")),
686 _rand(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "star-rand")),
687 _poly(get_widget<Gtk::ToggleButton>(builder, "star-poly")),
688 _star(get_widget<Gtk::ToggleButton>(builder, "star-star")),
689 _align(get_widget<Gtk::Button>(builder, "star-align")),
690 _clear_rnd(get_widget<Gtk::Button>(builder, "star-rnd-clear")),
691 _clear_round(get_widget<Gtk::Button>(builder, "star-round-clear")),
692 _clear_ratio(get_widget<Gtk::Button>(builder, "star-ratio-clear"))
693 {
694 _title = _("Star");
695 _widget = &_main;
696
697 _corners.get_adjustment()->signal_value_changed().connect([this](){
698 change_value(_path, _corners.get_adjustment(), [this](double sides) {
699 _path->setAttributeDouble("sodipodi:sides", (int)sides);
700 auto arg1 = get_number(_path, "sodipodi:arg1").value_or(0.5);
701 _path->setAttributeDouble("sodipodi:arg2", arg1 + M_PI / sides);
702 _path->updateRepr();
703 });
704 });
705 _rounded.get_adjustment()->signal_value_changed().connect([this](){
706 change_value(_path, _rounded.get_adjustment(), [this](double rounded) {
707 _path->setAttributeDouble("inkscape:rounded", rounded);
708 _path->updateRepr();
709 });
710 });
711 _ratio.get_adjustment()->signal_value_changed().connect([this](){
712 change_value(_path, _ratio.get_adjustment(), [this](double ratio){
713 auto r1 = get_number(_path, "sodipodi:r1").value_or(1.0);
714 auto r2 = get_number(_path, "sodipodi:r2").value_or(1.0);
715 if (r2 < r1) {
716 _path->setAttributeDouble("sodipodi:r2", r1 * ratio);
717 } else {
718 _path->setAttributeDouble("sodipodi:r1", r2 * ratio);
719 }
720 _path->updateRepr();
721 });
722 });
723 _rand.get_adjustment()->signal_value_changed().connect([this](){
724 change_value(_path, _rand.get_adjustment(), [this](double rnd){
725 _path->setAttributeDouble("inkscape:randomized", rnd);
726 _path->updateRepr();
727 });
728 });
729
730 _clear_rnd.signal_clicked().connect([this](){ _rand.get_adjustment()->set_value(0); });
731 _clear_round.signal_clicked().connect([this](){ _rounded.get_adjustment()->set_value(0); });
732 _clear_ratio.signal_clicked().connect([this](){ _ratio.get_adjustment()->set_value(0.5); });
733
734 _poly.signal_toggled().connect([this](){ set_flat(true); });
735 _star.signal_toggled().connect([this](){ set_flat(false); });
736 _align.signal_clicked().connect([this](){
737 change_value(_path, {}, [this](double) { align_star_shape(_path); });
738 });
739 }
740
741 ~StarPanel() override = default;
742
743 void update(SPObject* object) override {
744 _path = cast<SPStar>(object);
745 if (!_path) return;
746
747 auto scoped(_update.block());
748 _corners.set_value(_path->sides);
749 double r1 = get_number(_path, "sodipodi:r1").value_or(0.5);
750 double r2 = get_number(_path, "sodipodi:r2").value_or(0.5);
751 if (r2 < r1) {
752 _ratio.set_value(r1 > 0 ? r2 / r1 : 0.5);
753 } else {
754 _ratio.set_value(r2 > 0 ? r1 / r2 : 0.5);
755 }
756 _rounded.set_value(_path->rounded);
757 _rand.set_value(_path->randomized);
758
759 _clear_rnd .set_visible(_path->randomized != 0);
760 _clear_round.set_visible(_path->rounded != 0);
761 _clear_ratio.set_visible(std::abs(_ratio.get_value() - 0.5) > 0.0005);
762
763 _poly.set_active(_path->flatsided);
764 _star.set_active(!_path->flatsided);
765 }
766
767 void set_flat(bool flat) {
768 change_value(_path, {}, [flat, this](double){
769 _path->setAttribute("inkscape:flatsided", flat ? "true" : "false");
770 _path->updateRepr();
771 });
772 // adjust corners/sides
773 _corners.get_adjustment()->set_lower(flat ? 3 : 2);
774 if (flat && _corners.get_value() < 3) {
775 _corners.get_adjustment()->set_value(3);
776 }
777 }
778
779private:
780 SPStar* _path = nullptr;
781 Gtk::Widget& _main;
786 Gtk::Button& _clear_rnd;
787 Gtk::Button& _clear_round;
788 Gtk::Button& _clear_ratio;
789 Gtk::Button& _align;
790 Gtk::ToggleButton &_poly;
791 Gtk::ToggleButton &_star;
792};
793
795
796class TextPanel : public details::AttributesPanel {
797public:
798 TextPanel(Glib::RefPtr<Gtk::Builder> builder) :
799 _main(get_widget<Gtk::Grid>(builder, "text-main"))
800 {
801 // TODO - text panel
802 }
803
804private:
805 Gtk::Widget& _main;
806
807};
808
810
811class PathPanel : public details::AttributesPanel {
812public:
813 PathPanel(Glib::RefPtr<Gtk::Builder> builder) :
814 _main(get_widget<Gtk::Grid>(builder, "path-main")),
815 _width(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "path-width")),
816 _height(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "path-height")),
817 _x(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "path-x")),
818 _y(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "path-y")),
819 _info(get_widget<Gtk::Label>(builder, "path-info")),
820 _data(_svgd_edit->getTextView())
821 {
822 _title = _("Path");
823 _widget = &_main;
824
825 //TODO: do we need to duplicate x/y/w/h toolbar widgets here?
826 /*
827 _width.get_adjustment()->signal_value_changed().connect([=](){
828 });
829 _height.get_adjustment()->signal_value_changed().connect([=](){
830 });
831 _x.get_adjustment()->signal_value_changed().connect([=](){
832 });
833 _y.get_adjustment()->signal_value_changed().connect([=](){
834 });
835 */
836 auto pref_path = dlg_pref_path + "path-panel/";
837
838 auto theme = Inkscape::Preferences::get()->getString("/theme/syntax-color-theme", "-none-");
839 _svgd_edit->setStyle(theme);
840 _data.set_wrap_mode(Gtk::WrapMode::WORD);
841
842 auto const key = Gtk::EventControllerKey::create();
843 key->signal_key_pressed().connect(sigc::mem_fun(*this, &PathPanel::on_key_pressed), true);
844 _data.add_controller(key);
845
846 auto& wnd = get_widget<Gtk::ScrolledWindow>(builder, "path-data-wnd");
847 wnd.set_child(_data);
848
849 auto set_precision = [=, this](int const n) {
850 _precision = n;
851 auto& menu_button = get_widget<Gtk::MenuButton>(builder, "path-menu");
852 auto const menu = menu_button.get_menu_model();
853 auto const section = menu->get_item_link(0, Gio::MenuModel::Link::SECTION);
854 auto const type = Glib::VariantType{g_variant_type_new("s")};
855 auto const variant = section->get_item_attribute(n, Gio::MenuModel::Attribute::LABEL, type);
856 auto const label = ' ' + static_cast<Glib::Variant<Glib::ustring> const &>(variant).get();
857 get_widget<Gtk::Label>(builder, "path-precision").set_label(label);
858 Inkscape::Preferences::get()->setInt(pref_path + "precision", n);
859 menu_button.set_active(false);
860 };
861
862 const int N = 5;
863 _precision = Inkscape::Preferences::get()->getIntLimited(pref_path + "precision", 2, 0, N);
864 set_precision(_precision);
865 auto group = Gio::SimpleActionGroup::create();
866 auto action = group->add_action_radio_integer("precision", _precision);
867 action->property_state().signal_changed().connect([=, this]{ int n; action->get_state(n);
868 set_precision(n); });
869 _main.insert_action_group("attrdialog", std::move(group));
870
871 get_widget<Gtk::Button>(builder, "path-data-round").signal_clicked().connect([this]{
872 truncate_digits(_data.get_buffer(), _precision);
873 commit_d();
874 });
875 get_widget<Gtk::Button>(builder, "path-enter").signal_clicked().connect([this](){ commit_d(); });
876 }
877
878 ~PathPanel() override = default;
879
880 void update(SPObject* object) override {
881 _path = cast<SPPath>(object);
882 if (!_path) return;
883
884 auto scoped(_update.block());
885
886 auto d = _path->getAttribute("inkscape:original-d");
887 if (d && _path->hasPathEffect()) {
888 _original = true;
889 }
890 else {
891 _original = false;
892 d = _path->getAttribute("d");
893 }
894 _svgd_edit->setText(d ? d : "");
895
896 auto curve = _path->curveBeforeLPE();
897 if (!curve) curve = _path->curve();
898 size_t node_count = 0;
899 if (curve) {
900 node_count = curve->get_segment_count();
901 }
902 _info.set_text(_("Nodes: ") + std::to_string(node_count));
903
904 //TODO: we can consider adding more stats, like perimeter, area, etc.
905 }
906
907private:
908 bool on_key_pressed(unsigned keyval, unsigned keycode, Gdk::ModifierType state) {
909 switch (keyval) {
910 case GDK_KEY_Return:
911 case GDK_KEY_KP_Enter:
912 return Controller::has_flag(state, Gdk::ModifierType::SHIFT_MASK) ? commit_d() : false;
913 }
914 return false;
915 }
916
917 bool commit_d() {
918 if (!_path || !_data.is_visible()) return false;
919
920 auto scoped(_update.block());
921 auto d = _svgd_edit->getText();
922 _path->setAttribute(_original ? "inkscape:original-d" : "d", d);
923 DocumentUndo::maybeDone(_path->document, "path-data", _("Change path"), INKSCAPE_ICON(""));
924 return true;
925 }
926
927 SPPath* _path = nullptr;
928 bool _original = false;
929 Gtk::Widget& _main;
934 Gtk::Label& _info;
935 std::unique_ptr<Syntax::TextEditView> _svgd_edit = Syntax::TextEditView::create(Syntax::SyntaxMode::SvgPathData);
936 Gtk::TextView& _data;
937 int _precision = 2;
938};
939
941
942std::string get_key(SPObject* object) {
943 if (!object) return {};
944
945 return typeid(*object).name();
946}
947
949 if (!object) return nullptr;
950
951 std::string name = get_key(object);
952 auto it = _panels.find(name);
953 return it == _panels.end() ? nullptr : it->second.get();
954}
955
957 _panels[typeid(SPImage).name()] = std::make_unique<ImagePanel>();
958 _panels[typeid(SPRect).name()] = std::make_unique<RectPanel>(_builder);
959 _panels[typeid(SPGenericEllipse).name()] = std::make_unique<EllipsePanel>(_builder);
960 _panels[typeid(SPStar).name()] = std::make_unique<StarPanel>(_builder);
961 _panels[typeid(SPAnchor).name()] = std::make_unique<AnchorPanel>();
962 _panels[typeid(SPPath).name()] = std::make_unique<PathPanel>(_builder);
963}
964
965} // namespace Inkscape::UI::Dialog
966
967/*
968 Local Variables:
969 mode:c++
970 c-file-style:"stroustrup"
971 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
972 indent-tabs-mode:nil
973 fill-column:99
974 End:
975*/
976// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
vector_type normalize(const vector_type)
void set_active_tool(InkscapeWindow *win, Glib::ustring const &tool)
int main()
Gtk builder utilities.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void createAndApply(const char *name, SPDocument *doc, SPItem *item)
Definition effect.cpp:1118
int size()
Returns size of the selection.
SPItem * singleItem()
Returns a single selected item.
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX)
Retrieve a limited integer.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
DialogBase is the base class for the dialog system.
Definition dialog-base.h:42
SPDesktop * getDesktop() const
Definition dialog-base.h:79
void selectionModified(Selection *selection, guint flags) override
Inkscape::UI::Widget::StyleSwatch _style_swatch
std::map< std::string, std::unique_ptr< details::AttributesPanel > > _panels
void widget_setup()
Updates entries and other child widgets on selection change, object modification, etc.
Glib::RefPtr< Gtk::Builder > _builder
void selectionChanged(Selection *selection) override
details::AttributesPanel * get_panel(SPObject *object)
void desktopReplaced() override
Called when the desktop has certainly changed.
details::AttributesPanel * _current_panel
A subdialog widget to show object properties.
void update_entries()
Updates entries and other child widgets on selection change, object modification, etc.
void update_panel(SPObject *object, SPDesktop *desktop)
void change_angle(SPObject *object, const Glib::RefPtr< Gtk::Adjustment > &adj, std::function< void(double)> &&setter)
std::unique_ptr< UI::Widget::UnitTracker > _tracker
void change_value(SPObject *object, const Glib::RefPtr< Gtk::Adjustment > &adj, std::function< void(double)> &&setter)
void change_value_px(SPObject *object, const Glib::RefPtr< Gtk::Adjustment > &adj, const char *attr, std::function< void(double)> &&setter)
static std::unique_ptr< TextEditView > create(SyntaxMode mode)
Create a styled text view using the desired syntax highlighting mode.
Definition syntax.cpp:382
SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMen...
Definition spinbutton.h:52
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:588
double getAttributeDouble(Util::const_char_ptr key, double default_value=0.0) const
Definition node.cpp:76
scoped_block block()
To do: update description of desktop.
Definition desktop.h:149
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Base class for visual SVG elements.
Definition sp-item.h:109
virtual const char * displayName() const
The item's type name as a translated human string.
Definition sp-item.cpp:1201
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
void setAttributeDouble(Inkscape::Util::const_char_ptr key, double value)
SPDocument * document
Definition sp-object.h:188
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getAttribute(char const *name) const
SVG <path> implementation.
Definition sp-path.h:29
double arg[2]
Definition sp-star.h:35
int sides
Definition sp-star.h:31
const double w
Definition conic-4.cpp:19
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
Point const * _data
std::size_t _x
Editable view implementation.
TODO: insert short description here.
double const _precision
Macro for icon names used in Inkscape.
SPItem * item
Glib::ustring label
double degree_to_radians_mod2pi(double degrees)
Definition mod360.cpp:47
double radians_to_degree_mod360(double rad)
Definition mod360.cpp:43
TODO: insert short description here.
Definition desktop.h:50
std::unique_ptr< SPDocument > open(Extension *key, char const *filename, bool is_importing)
This is a generic function to use the open function of a module (including Autodetect)
Definition system.cpp:66
bool has_flag(Gdk::ModifierType const state, Gdk::ModifierType const flags)
Helper to query if ModifierType state contains one or more of given flag(s).
Definition controller.h:25
Dialog code.
Definition desktop.h:117
static auto get_key(std::size_t const notebook_idx)
void align_star_shape(SPStar *path)
void remove_lpeffect(SPLPEItem *item, LivePathEffect::EffectType type)
std::optional< double > get_number(SPItem *item, const char *attribute)
const LivePathEffectObject * find_lpeffect(SPLPEItem *item, LivePathEffect::EffectType etype)
std::tuple< bool, double, double > round_values(double x, double y)
static const SPAttrDesc anchor_desc[]
@ SvgPathData
Contents of the 'd' attribute of the SVG <path> element.
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
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
W & get_derived_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id, Args &&... args)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
GType type()
Returns the type used for storing an object of type T inside a value.
Definition value-utils.h:29
T * get(GValue *value)
Returns a borrowed pointer to the T held by a value if it holds one, else nullptr.
Definition value-utils.h:64
@ UNIT_TYPE_LINEAR
Definition units.h:32
Helper class to stream background task notifications as a series of messages.
static void append(std::vector< T > &target, std::vector< T > &&source)
int n
Definition spiro.cpp:66
static cairo_user_data_key_t key
Generic object attribute editor.
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Singleton class to access the preferences file in a convenient way.
Axis-aligned rectangle.
auto len
Definition safe-printf.h:21
size_t N
Widget that listens and modifies repr attributes.
@ SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD
Definition sp-ellipse.h:33
@ SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE
Definition sp-ellipse.h:31
@ SP_GENERIC_ELLIPSE_ARC_TYPE_ARC
Definition sp-ellipse.h:32
SVG <image> implementation.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
Base class for live path effect items.
static double rnd(guint32 const seed, unsigned steps)
Returns a random number in the range [-0.5, 0.5) from the given seed, stepping the given number of st...
Definition sp-star.cpp:289
Definition curve.h:24
Static style swatch (fill, stroke, opacity)
int delta
SPDesktop * desktop
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder
void truncate_digits(const Glib::RefPtr< Gtk::TextBuffer > &buffer, int precision)
Definition util.cpp:481