summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/object-attributes.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
commitc853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch)
tree7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/ui/dialog/object-attributes.cpp
parentInitial commit. (diff)
downloadinkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz
inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/ui/dialog/object-attributes.cpp')
-rw-r--r--src/ui/dialog/object-attributes.cpp760
1 files changed, 760 insertions, 0 deletions
diff --git a/src/ui/dialog/object-attributes.cpp b/src/ui/dialog/object-attributes.cpp
new file mode 100644
index 0000000..dd5d8e4
--- /dev/null
+++ b/src/ui/dialog/object-attributes.cpp
@@ -0,0 +1,760 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Generic object attribute editor
+ *//*
+ * Authors:
+ * see git history
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/rect.h>
+#include <cmath>
+#include <cstddef>
+#include <glibmm/i18n.h>
+#include <glibmm/markup.h>
+#include <glibmm/ustring.h>
+#include <gtkmm/button.h>
+#include <gtkmm/enums.h>
+#include <gtkmm/grid.h>
+#include <gtkmm/label.h>
+#include <gtkmm/radiobutton.h>
+#include <gtkmm/spinbutton.h>
+#include <gtkmm/widget.h>
+#include <memory>
+#include <optional>
+#include <string>
+#include <tuple>
+#include "actions/actions-tools.h"
+#include "desktop.h"
+#include "live_effects/effect-enum.h"
+#include "mod360.h"
+#include "object/sp-anchor.h"
+#include "object/sp-ellipse.h"
+#include "object/sp-image.h"
+#include "object/sp-lpe-item.h"
+#include "object/sp-namedview.h"
+#include "object/sp-object.h"
+#include "object/sp-path.h"
+#include "object/sp-rect.h"
+#include "object/sp-star.h"
+#include "object/tags.h"
+#include "streq.h"
+#include "ui/builder-utils.h"
+#include "ui/dialog/object-attributes.h"
+#include "ui/icon-names.h"
+#include "ui/tools/node-tool.h"
+#include "ui/util.h"
+#include "ui/widget/image-properties.h"
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/style-swatch.h"
+#include "widgets/sp-attribute-widget.h"
+#include "xml/href-attribute-helper.h"
+#include "live_effects/lpeobject-reference.h"
+#include "live_effects/lpeobject.h"
+#include "live_effects/effect.h"
+
+namespace Inkscape {
+namespace UI {
+
+void sp_apply_lpeffect(SPDesktop* desktop, SPLPEItem* item, LivePathEffect::EffectType etype);
+
+namespace Dialog {
+
+
+struct SPAttrDesc {
+ gchar const *label;
+ gchar const *attribute;
+};
+
+static const SPAttrDesc anchor_desc[] = {
+ { N_("Href:"), "xlink:href"},
+ { N_("Target:"), "target"},
+ { N_("Type:"), "xlink:type"},
+ // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkRoleAttribute
+ // Identifies the type of the related resource with an absolute URI
+ { N_("Role:"), "xlink:role"},
+ // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkArcRoleAttribute
+ // For situations where the nature/role alone isn't enough, this offers an additional URI defining the purpose of the link.
+ { N_("Arcrole:"), "xlink:arcrole"},
+ // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkTitleAttribute
+ { N_("Title:"), "xlink:title"},
+ { N_("Show:"), "xlink:show"},
+ // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/linking.html#AElementXLinkActuateAttribute
+ { N_("Actuate:"), "xlink:actuate"},
+ { nullptr, nullptr}
+};
+
+ObjectAttributes::ObjectAttributes()
+ : DialogBase("/dialogs/objectattr/", "ObjectAttributes"),
+ _builder(create_builder("object-attributes.glade")),
+ _main_panel(get_widget<Gtk::Box>(_builder, "main-panel")),
+ _obj_title(get_widget<Gtk::Label>(_builder, "main-obj-name")),
+ _style_swatch(nullptr, _("Item's fill, stroke and opacity"), Gtk::ORIENTATION_HORIZONTAL)
+{
+ auto& main = get_widget<Gtk::Box>(_builder, "main-widget");
+ _obj_title.set_text("");
+ _style_swatch.set_hexpand(false);
+ _style_swatch.set_valign(Gtk::ALIGN_CENTER);
+ get_widget<Gtk::Box>(_builder, "main-header").pack_end(_style_swatch, false, true);
+ add(main);
+ create_panels();
+ _style_swatch.hide();
+}
+
+void ObjectAttributes::widget_setup() {
+ if (_update.pending() || !getDesktop()) return;
+
+ auto selection = getDesktop()->getSelection();
+ auto item = selection->singleItem();
+
+ auto scoped(_update.block());
+
+ auto panel = get_panel(item);
+ if (panel != _current_panel && _current_panel) {
+ _current_panel->update_panel(nullptr, nullptr);
+ _main_panel.remove(_current_panel->widget());
+ _obj_title.set_text("");
+ }
+
+ _current_panel = panel;
+ _current_item = nullptr;
+
+ Glib::ustring title = panel ? panel->get_title() : "";
+ if (!panel) {
+ if (item) {
+ if (auto name = item->displayName()) {
+ title = name;
+ }
+ }
+ else if (selection->size() > 1) {
+ title = _("Multiple objects selected");
+ }
+ }
+ _obj_title.set_markup("<b>" + Glib::Markup::escape_text(title) + "</b>");
+
+ if (!panel) {
+ _style_swatch.hide();
+ return;
+ }
+
+ _main_panel.pack_start(panel->widget(), true, true);
+ bool show_style = false;
+ if (panel->supports_fill_stroke()) {
+ if (auto style = item ? item->style : nullptr) {
+ _style_swatch.setStyle(style);
+ show_style = true;
+ }
+ }
+ widget_show(_style_swatch, show_style);
+ panel->update_panel(item, getDesktop());
+ panel->widget().show();
+ _current_item = item;
+
+ // TODO
+ // show no of LPEs?
+ // show locked status?
+}
+
+void ObjectAttributes::update_panel(SPObject* item) {
+ if (!_current_panel) return;
+
+ if (_current_panel->supports_fill_stroke()) {
+ if (auto style = item ? item->style : nullptr) {
+ _style_swatch.setStyle(style);
+ }
+ }
+ _current_panel->update_panel(item, getDesktop());
+}
+
+void ObjectAttributes::desktopReplaced() {
+ //todo; if anything
+}
+
+void ObjectAttributes::selectionChanged(Selection* selection) {
+ widget_setup();
+}
+
+void ObjectAttributes::selectionModified(Selection* _selection, guint flags) {
+ if (_update.pending() || !getDesktop() || !_current_panel) return;
+
+ auto selection = getDesktop()->getSelection();
+ if (flags & (SP_OBJECT_MODIFIED_FLAG |
+ SP_OBJECT_PARENT_MODIFIED_FLAG |
+ SP_OBJECT_STYLE_MODIFIED_FLAG)) {
+
+ auto item = selection->singleItem();
+ if (item == _current_item) {
+ update_panel(item);
+ }
+ else {
+ g_warning("ObjectAttributes: missed selection change?");
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+std::tuple<bool, double, double> round_values(double x, double y) {
+ auto a = std::round(x);
+ auto b = std::round(y);
+ return std::make_tuple(a != x || b != y, a, b);
+}
+
+std::tuple<bool, double, double> round_values(Gtk::SpinButton& x, Gtk::SpinButton& y) {
+ return round_values(x.get_adjustment()->get_value(), y.get_adjustment()->get_value());
+}
+
+const LivePathEffectObject* find_lpeffect(SPLPEItem* item, LivePathEffect::EffectType etype) {
+ if (!item) return nullptr;
+
+ auto lpe = item->getFirstPathEffectOfType(Inkscape::LivePathEffect::FILLET_CHAMFER);
+ if (!lpe) return nullptr;
+ return lpe->getLPEObj();
+}
+
+void remove_lpeffect(SPLPEItem* item, LivePathEffect::EffectType type) {
+ if (auto effect = find_lpeffect(item, type)) {
+ item->setCurrentPathEffect(effect);
+ item->removeCurrentPathEffect(false);
+ DocumentUndo::done(item->document, _("Removed live path effect"), INKSCAPE_ICON("dialog-path-effects"));
+ }
+}
+
+std::optional<double> get_number(SPItem* item, const char* attribute) {
+ if (!item) return {};
+
+ auto val = item->getAttribute(attribute);
+ if (!val) return {};
+
+ return item->getRepr()->getAttributeDouble(attribute);
+}
+
+void align_star_shape(SPStar* path) {
+ if (!path || !path->sides) return;
+
+ auto arg1 = path->arg[0];
+ auto arg2 = path->arg[1];
+ auto delta = arg2 - arg1;
+ auto top = -M_PI / 2;
+ auto odd = path->sides & 1;
+ if (odd) {
+ arg1 = top;
+ }
+ else {
+ arg1 = top - M_PI / path->sides;
+ }
+ arg2 = arg1 + delta;
+
+ path->setAttributeDouble("sodipodi:arg1", arg1);
+ path->setAttributeDouble("sodipodi:arg2", arg2);
+ path->updateRepr();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+details::AttributesPanel::AttributesPanel() {
+ _tracker = std::make_unique<UI::Widget::UnitTracker>(Inkscape::Util::UNIT_TYPE_LINEAR);
+ //todo:
+ // auto init_units = desktop->getNamedView()->display_units;
+ // _tracker->setActiveUnit(init_units);
+}
+
+void details::AttributesPanel::update_panel(SPObject* object, SPDesktop* desktop) {
+ if (object) {
+ auto scoped(_update.block());
+ auto units = object->document->getNamedView() ? object->document->getNamedView()->display_units : nullptr;
+ // auto init_units = desktop->getNamedView()->display_units;
+ if (units) _tracker->setActiveUnit(units);
+ }
+
+ _desktop = desktop;
+
+ if (!_update.pending()) {
+ update(object);
+ }
+}
+
+void details::AttributesPanel::change_value_px(SPObject* object, const Glib::RefPtr<Gtk::Adjustment>& adj, const char* attr, std::function<void (double)>&& setter) {
+ if (_update.pending() || !object) return;
+
+ auto scoped(_update.block());
+
+ const auto unit = _tracker->getActiveUnit();
+ auto value = Util::Quantity::convert(adj->get_value(), unit, "px");
+ if (value != 0 || attr == nullptr) {
+ setter(value);
+ }
+ else if (attr) {
+ object->removeAttribute(attr);
+ }
+
+ DocumentUndo::done(object->document, _("Change object attribute"), ""); //TODO INKSCAPE_ICON("draw-rectangle"));
+}
+
+void details::AttributesPanel::change_angle(SPObject* object, const Glib::RefPtr<Gtk::Adjustment>& adj, std::function<void (double)>&& setter) {
+ if (_update.pending() || !object) return;
+
+ auto scoped(_update.block());
+
+ auto value = degree_to_radians_mod2pi(adj->get_value());
+ setter(value);
+
+ DocumentUndo::done(object->document, _("Change object attribute"), ""); //TODO INKSCAPE_ICON("draw-rectangle"));
+}
+
+void details::AttributesPanel::change_value(SPObject* object, const Glib::RefPtr<Gtk::Adjustment>& adj, std::function<void (double)>&& setter) {
+ if (_update.pending() || !object) return;
+
+ auto scoped(_update.block());
+
+ auto value = adj ? adj->get_value() : 0;
+ setter(value);
+
+ DocumentUndo::done(object->document, _("Change object attribute"), ""); //TODO INKSCAPE_ICON("draw-rectangle"));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class ImagePanel : public details::AttributesPanel {
+public:
+ ImagePanel() {
+ _title = _("Image");
+ _show_fill_stroke = false;
+ _panel = std::make_unique<Inkscape::UI::Widget::ImageProperties>();
+ _widget = _panel.get();
+ }
+ ~ImagePanel() override = default;
+
+ void update(SPObject* object) override { _panel->update(cast<SPImage>(object)); }
+
+private:
+ std::unique_ptr<Inkscape::UI::Widget::ImageProperties> _panel;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class AnchorPanel : public details::AttributesPanel {
+public:
+ AnchorPanel() {
+ _title = _("Anchor");
+ _show_fill_stroke = false;
+ _table = std::make_unique<SPAttributeTable>();
+ _table->show();
+ _table->set_hexpand();
+ _table->set_vexpand(false);
+ _widget = _table.get();
+ }
+ ~AnchorPanel() override = default;
+
+ void update(SPObject* object) override {
+ auto anchor = cast<SPAnchor>(object);
+ auto changed = _anchor != anchor;
+ _anchor = anchor;
+ if (!anchor) return;
+
+ std::vector<Glib::ustring> labels;
+ std::vector<Glib::ustring> attrs;
+ if (changed) {
+ int len = 0;
+ while (anchor_desc[len].label) {
+ labels.emplace_back(anchor_desc[len].label);
+ attrs.emplace_back(anchor_desc[len].attribute);
+ len += 1;
+ }
+ _table->set_object(anchor, labels, attrs, (GtkWidget*)_table->gobj());
+ }
+ else {
+ _table->reread_properties();
+ }
+ }
+
+private:
+ std::unique_ptr<SPAttributeTable> _table;
+ SPAnchor* _anchor = nullptr;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class RectPanel : public details::AttributesPanel {
+public:
+ RectPanel(Glib::RefPtr<Gtk::Builder> builder) :
+ _main(get_widget<Gtk::Grid>(builder, "rect-main")),
+ _width(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "rect-width")),
+ _height(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "rect-height")),
+ _rx(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "rect-rx")),
+ _ry(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "rect-ry")),
+ _sharp(get_widget<Gtk::Button>(builder, "rect-sharp")),
+ _round(get_widget<Gtk::Button>(builder, "rect-corners"))
+ {
+ _title = _("Rectangle");
+ _widget = &_main;
+
+ _width.get_adjustment()->signal_value_changed().connect([=](){
+ change_value_px(_rect, _width.get_adjustment(), "width", [=](double w){ _rect->setVisibleWidth(w); });
+ });
+ _height.get_adjustment()->signal_value_changed().connect([=](){
+ change_value_px(_rect, _height.get_adjustment(), "height", [=](double h){ _rect->setVisibleHeight(h); });
+ });
+ _rx.get_adjustment()->signal_value_changed().connect([=](){
+ change_value_px(_rect, _rx.get_adjustment(), "rx", [=](double rx){ _rect->setVisibleRx(rx); });
+ });
+ _ry.get_adjustment()->signal_value_changed().connect([=](){
+ change_value_px(_rect, _ry.get_adjustment(), "ry", [=](double ry){ _rect->setVisibleRy(ry); });
+ });
+ get_widget<Gtk::Button>(builder, "rect-round").signal_clicked().connect([=](){
+ auto [changed, x, y] = round_values(_width, _height);
+ if (changed) {
+ _width.get_adjustment()->set_value(x);
+ _height.get_adjustment()->set_value(y);
+ }
+ });
+ _sharp.signal_clicked().connect([=](){
+ if (!_rect) return;
+
+ // remove rounded corners if LPE is there (first one found)
+ remove_lpeffect(_rect, LivePathEffect::FILLET_CHAMFER);
+ _rx.get_adjustment()->set_value(0);
+ _ry.get_adjustment()->set_value(0);
+ });
+ _round.signal_clicked().connect([=](){
+ if (!_rect || !_desktop) return;
+
+ // switch to node tool to show handles
+ set_active_tool(_desktop, "Node");
+ // rx/ry need to be reset first, LPE doesn't handle them too well
+ _rx.get_adjustment()->set_value(0);
+ _ry.get_adjustment()->set_value(0);
+ // add flexible corners effect if not yet present
+ if (!find_lpeffect(_rect, LivePathEffect::FILLET_CHAMFER)) {
+ sp_apply_lpeffect(_desktop, _rect, LivePathEffect::FILLET_CHAMFER);
+ }
+ });
+ }
+
+ ~RectPanel() override = default;
+
+ void update(SPObject* object) override {
+ _rect = cast<SPRect>(object);
+ if (!_rect) return;
+
+ auto scoped(_update.block());
+ _width.set_value(_rect->width.value);
+ _height.set_value(_rect->height.value);
+ _rx.set_value(_rect->rx.value);
+ _ry.set_value(_rect->ry.value);
+ auto lpe = find_lpeffect(_rect, LivePathEffect::FILLET_CHAMFER);
+ _sharp.set_sensitive(_rect->rx.value > 0 || _rect->ry.value > 0 || lpe);
+ _round.set_sensitive(!lpe);
+ }
+
+private:
+ SPRect* _rect = nullptr;
+ Gtk::Widget& _main;
+ Inkscape::UI::Widget::SpinButton& _width;
+ Inkscape::UI::Widget::SpinButton& _height;
+ Inkscape::UI::Widget::SpinButton& _rx;
+ Inkscape::UI::Widget::SpinButton& _ry;
+ Gtk::Button& _sharp;
+ Gtk::Button& _round;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class EllipsePanel : public details::AttributesPanel {
+public:
+ EllipsePanel(Glib::RefPtr<Gtk::Builder> builder) :
+ _main(get_widget<Gtk::Grid>(builder, "ellipse-main")),
+ _rx(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "el-rx")),
+ _ry(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "el-ry")),
+ _start(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "el-start")),
+ _end(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "el-end")),
+ _slice(get_widget<Gtk::RadioButton>(builder, "el-slice")),
+ _arc(get_widget<Gtk::RadioButton>(builder, "el-arc")),
+ _chord(get_widget<Gtk::RadioButton>(builder, "el-chord")),
+ _whole(get_widget<Gtk::Button>(builder, "el-whole"))
+ {
+ _title = _("Ellipse");
+ _widget = &_main;
+
+ _type[0] = &_slice;
+ _type[1] = &_arc;
+ _type[2] = &_chord;
+
+ int type = 0;
+ for (auto btn : _type) {
+ btn->signal_toggled().connect([=](){ set_type(type); });
+ type++;
+ }
+
+ _whole.signal_clicked().connect([=](){
+ _start.get_adjustment()->set_value(0);
+ _end.get_adjustment()->set_value(0);
+ });
+
+ auto normalize = [=](){
+ _ellipse->normalize();
+ _ellipse->updateRepr();
+ _ellipse->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ };
+
+ _rx.get_adjustment()->signal_value_changed().connect([=](){
+ change_value_px(_ellipse, _rx.get_adjustment(), nullptr, [=](double rx){ _ellipse->setVisibleRx(rx); normalize(); });
+ });
+ _ry.get_adjustment()->signal_value_changed().connect([=](){
+ change_value_px(_ellipse, _ry.get_adjustment(), nullptr, [=](double ry){ _ellipse->setVisibleRy(ry); normalize(); });
+ });
+ _start.get_adjustment()->signal_value_changed().connect([=](){
+ change_angle(_ellipse, _start.get_adjustment(), [=](double s){ _ellipse->start = s; normalize(); });
+ });
+ _end.get_adjustment()->signal_value_changed().connect([=](){
+ change_angle(_ellipse, _end.get_adjustment(), [=](double e){ _ellipse->end = e; normalize(); });
+ });
+
+ get_widget<Gtk::Button>(builder, "el-round").signal_clicked().connect([=](){
+ auto [changed, x, y] = round_values(_rx, _ry);
+ if (changed && x > 0 && y > 0) {
+ _rx.get_adjustment()->set_value(x);
+ _ry.get_adjustment()->set_value(y);
+ }
+ });
+ }
+
+ ~EllipsePanel() override = default;
+
+ void update(SPObject* object) override {
+ _ellipse = cast<SPGenericEllipse>(object);
+ if (!_ellipse) return;
+
+ auto scoped(_update.block());
+ _rx.set_value(_ellipse->rx.value);
+ _ry.set_value(_ellipse->ry.value);
+ _start.set_value(radians_to_degree_mod360(_ellipse->start));
+ _end.set_value(radians_to_degree_mod360(_ellipse->end));
+
+ _slice.set_active(_ellipse->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE);
+ _arc.set_active(_ellipse->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_ARC);
+ _chord.set_active(_ellipse->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD);
+
+ auto slice = !_ellipse->is_whole();
+ _whole.set_sensitive(slice);
+ for (auto btn : _type) {
+ btn->set_sensitive(slice);
+ }
+ }
+
+ void set_type(int type) {
+ if (!_ellipse) return;
+
+ auto scoped(_update.block());
+
+ Glib::ustring arc_type = "slice";
+ bool open = false;
+ switch (type) {
+ case 0:
+ arc_type = "slice";
+ open = false;
+ break;
+ case 1:
+ arc_type = "arc";
+ open = true;
+ break;
+ case 2:
+ arc_type = "chord";
+ open = true; // For backward compat, not truly open but chord most like arc.
+ break;
+ default:
+ std::cerr << "Ellipse type change - bad arc type: " << type << std::endl;
+ break;
+ }
+ _ellipse->setAttribute("sodipodi:open", open ? "true" : nullptr);
+ _ellipse->setAttribute("sodipodi:arc-type", arc_type.c_str());
+ _ellipse->updateRepr();
+ DocumentUndo::done(_ellipse->document, _("Change arc type"), INKSCAPE_ICON("draw-ellipse"));
+ }
+
+private:
+ SPGenericEllipse* _ellipse = nullptr;
+ Gtk::Widget& _main;
+ Inkscape::UI::Widget::SpinButton& _rx;
+ Inkscape::UI::Widget::SpinButton& _ry;
+ Inkscape::UI::Widget::SpinButton& _start;
+ Inkscape::UI::Widget::SpinButton& _end;
+ Gtk::RadioButton& _slice;
+ Gtk::RadioButton& _arc;
+ Gtk::RadioButton& _chord;
+ Gtk::Button& _whole;
+ Gtk::RadioButton* _type[3];
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class StarPanel : public details::AttributesPanel {
+public:
+ StarPanel(Glib::RefPtr<Gtk::Builder> builder) :
+ _main(get_widget<Gtk::Grid>(builder, "star-main")),
+ _corners(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "star-corners")),
+ _ratio(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "star-ratio")),
+ _rounded(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "star-rounded")),
+ _rand(get_derived_widget<Inkscape::UI::Widget::SpinButton>(builder, "star-rand")),
+ _poly(get_widget<Gtk::RadioButton>(builder, "star-poly")),
+ _star(get_widget<Gtk::RadioButton>(builder, "star-star")),
+ _align(get_widget<Gtk::Button>(builder, "star-align")),
+ _clear_rnd(get_widget<Gtk::Button>(builder, "star-rnd-clear")),
+ _clear_round(get_widget<Gtk::Button>(builder, "star-round-clear")),
+ _clear_ratio(get_widget<Gtk::Button>(builder, "star-ratio-clear"))
+ {
+ _title = _("Star");
+ _widget = &_main;
+
+ _corners.get_adjustment()->signal_value_changed().connect([=](){
+ change_value(_path, _corners.get_adjustment(), [=](double sides) {
+ _path->setAttributeDouble("sodipodi:sides", (int)sides);
+ auto arg1 = get_number(_path, "sodipodi:arg1").value_or(0.5);
+ _path->setAttributeDouble("sodipodi:arg2", arg1 + M_PI / sides);
+ _path->updateRepr();
+ });
+ });
+ _rounded.get_adjustment()->signal_value_changed().connect([=](){
+ change_value(_path, _rounded.get_adjustment(), [=](double rounded) {
+ _path->setAttributeDouble("inkscape:rounded", rounded);
+ _path->updateRepr();
+ });
+ });
+ _ratio.get_adjustment()->signal_value_changed().connect([=](){
+ change_value(_path, _ratio.get_adjustment(), [=](double ratio){
+ auto r1 = get_number(_path, "sodipodi:r1").value_or(1.0);
+ auto r2 = get_number(_path, "sodipodi:r2").value_or(1.0);
+ if (r2 < r1) {
+ _path->setAttributeDouble("sodipodi:r2", r1 * ratio);
+ } else {
+ _path->setAttributeDouble("sodipodi:r1", r2 * ratio);
+ }
+ _path->updateRepr();
+ });
+ });
+ _rand.get_adjustment()->signal_value_changed().connect([=](){
+ change_value(_path, _rand.get_adjustment(), [=](double rnd){
+ _path->setAttributeDouble("inkscape:randomized", rnd);
+ _path->updateRepr();
+ });
+ });
+ _clear_rnd.signal_clicked().connect([=](){ _rand.get_adjustment()->set_value(0); });
+ _clear_round.signal_clicked().connect([=](){ _rounded.get_adjustment()->set_value(0); });
+ _clear_ratio.signal_clicked().connect([=](){ _ratio.get_adjustment()->set_value(0.5); });
+
+ _poly.signal_toggled().connect([=](){ set_flat(true); });
+ _star.signal_toggled().connect([=](){ set_flat(false); });
+
+ _align.signal_clicked().connect([=](){
+ change_value(_path, {}, [=](double) { align_star_shape(_path); });
+ });
+ }
+
+ ~StarPanel() override = default;
+
+ void update(SPObject* object) override {
+ _path = cast<SPStar>(object);
+ if (!_path) return;
+
+ auto scoped(_update.block());
+ _corners.set_value(_path->sides);
+ double r1 = get_number(_path, "sodipodi:r1").value_or(0.5);
+ double r2 = get_number(_path, "sodipodi:r2").value_or(0.5);
+ if (r2 < r1) {
+ _ratio.set_value(r1 > 0 ? r2 / r1 : 0.5);
+ } else {
+ _ratio.set_value(r2 > 0 ? r1 / r2 : 0.5);
+ }
+ _rounded.set_value(_path->rounded);
+ _rand.set_value(_path->randomized);
+ widget_show(_clear_rnd, _path->randomized != 0);
+ widget_show(_clear_round, _path->rounded != 0);
+ widget_show(_clear_ratio, std::abs(_ratio.get_value() - 0.5) > 0.0005);
+
+ _poly.set_active(_path->flatsided);
+ _star.set_active(!_path->flatsided);
+ }
+
+ void set_flat(bool flat) {
+ change_value(_path, {}, [=](double){
+ _path->setAttribute("inkscape:flatsided", flat ? "true" : "false");
+ _path->updateRepr();
+ });
+ // adjust corners/sides
+ _corners.get_adjustment()->set_lower(flat ? 3 : 2);
+ if (flat && _corners.get_value() < 3) {
+ _corners.get_adjustment()->set_value(3);
+ }
+ }
+
+private:
+ SPStar* _path = nullptr;
+ Gtk::Widget& _main;
+ Inkscape::UI::Widget::SpinButton& _corners;
+ Inkscape::UI::Widget::SpinButton& _ratio;
+ Inkscape::UI::Widget::SpinButton& _rounded;
+ Inkscape::UI::Widget::SpinButton& _rand;
+ Gtk::Button& _clear_rnd;
+ Gtk::Button& _clear_round;
+ Gtk::Button& _clear_ratio;
+ Gtk::Button& _align;
+ Gtk::RadioButton& _poly;
+ Gtk::RadioButton& _star;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class TextPanel : public details::AttributesPanel {
+public:
+ TextPanel(Glib::RefPtr<Gtk::Builder> builder) :
+ _main(get_widget<Gtk::Grid>(builder, "text-main"))
+ {
+ // TODO - text panel
+ }
+
+private:
+ Gtk::Widget& _main;
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+std::string get_key(SPObject* object) {
+ if (!object) return {};
+
+ return typeid(*object).name();
+}
+
+details::AttributesPanel* ObjectAttributes::get_panel(SPObject* object) {
+ if (!object) return nullptr;
+
+ std::string name = get_key(object);
+ auto it = _panels.find(name);
+ return it == _panels.end() ? nullptr : it->second.get();
+}
+
+void ObjectAttributes::create_panels() {
+ _panels[typeid(SPImage).name()] = std::make_unique<ImagePanel>();
+ _panels[typeid(SPRect).name()] = std::make_unique<RectPanel>(_builder);
+ _panels[typeid(SPGenericEllipse).name()] = std::make_unique<EllipsePanel>(_builder);
+ _panels[typeid(SPStar).name()] = std::make_unique<StarPanel>(_builder);
+ _panels[typeid(SPAnchor).name()] = std::make_unique<AnchorPanel>();
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :