diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/ui/dialog/object-attributes.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-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.cpp | 760 |
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 : |