/*
7 * Kris De Gussem <Kris.DeGussem@gmail.com>
10 * Copyright (C) 2018 Authors
11 * Released under GNU GPL v2+, read the file
'COPYING' for more information.
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>
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>
76 char const *attribute;
80 { N_(
"Href:"),
"xlink:href"},
81 { N_(
"Target:"),
"target"},
82 { N_(
"Type:"),
"xlink:type"},
85 { N_(
"Role:"),
"xlink:role"},
88 { N_(
"Arcrole:"),
"xlink:arcrole"},
90 { N_(
"Title:"),
"xlink:title"},
91 { N_(
"Show:"),
"xlink:show"},
93 { N_(
"Actuate:"),
"xlink:actuate"},
103 _obj_title(
get_widget<
Gtk::Label>(_builder,
"main-obj-name")),
104 _style_swatch(nullptr, _(
"Item's fill, stroke and opacity"),
Gtk::Orientation::
HORIZONTAL),
107 auto&
main = get_widget<Gtk::Box>(
_builder,
"main-widget");
136 bool enable_props = panel !=
nullptr;
138 Glib::ustring title = panel ? panel->get_title() :
"";
148 title = _(
"Multiple objects selected");
150 enable_props =
false;
153 title = _(
"No selection");
156 _obj_title.set_markup(
"<b>" + Glib::Markup::escape_text(title) +
"</b>");
165 bool show_style =
false;
166 if (panel->supports_fill_stroke()) {
174 panel->widget().set_visible(
true);
208 if (flags & (SP_OBJECT_MODIFIED_FLAG |
209 SP_OBJECT_PARENT_MODIFIED_FLAG |
210 SP_OBJECT_STYLE_MODIFIED_FLAG)) {
217 g_warning(
"ObjectAttributes: missed selection change?");
225 auto a = std::round(x);
226 auto b = std::round(y);
227 return std::make_tuple(a != x || b != y, a, b);
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());
235 if (!
item)
return nullptr;
238 if (!lpe)
return nullptr;
239 return lpe->getLPEObj();
244 item->setCurrentPathEffect(effect);
246 item->removeCurrentPathEffect(
false);
247 DocumentUndo::done(document, _(
"Removed live path effect"), INKSCAPE_ICON(
"dialog-path-effects"));
252 if (!
item)
return {};
261 if (!path || !path->
sides)
return;
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;
272 arg1 = top - M_PI / path->
sides;
292 auto scoped(_update.block());
293 auto units =
object->document->getNamedView() ?
object->document->getNamedView()->display_units :
nullptr;
295 if (units) _tracker->setActiveUnit(units);
300 if (!_update.pending()) {
306 if (_update.pending() || !
object)
return;
308 auto scoped(_update.block());
310 const auto unit = _tracker->getActiveUnit();
312 if (value != 0 || attr ==
nullptr) {
316 object->removeAttribute(attr);
323 if (_update.pending() || !
object)
return;
325 auto scoped(_update.block());
334 if (_update.pending() || !
object)
return;
336 auto scoped(_update.block());
338 auto value = adj ? adj->get_value() : 0;
350 _show_fill_stroke =
false;
351 _panel = std::make_unique<Inkscape::UI::Widget::ImageProperties>();
352 _widget = _panel.get();
354 ~ImagePanel()
override =
default;
356 void update(
SPObject*
object)
override { _panel->update(cast<SPImage>(
object)); }
359 std::unique_ptr<Inkscape::UI::Widget::ImageProperties> _panel;
364class AnchorPanel :
public details::AttributesPanel {
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();
375 std::vector<Glib::ustring> labels;
376 std::vector<Glib::ustring> attrs;
379 labels.emplace_back(anchor_desc[
len].
label);
380 attrs.emplace_back(anchor_desc[
len].attribute);
383 _table->create(labels, attrs);
386 ~AnchorPanel()
override =
default;
388 void update(
SPObject*
object)
override {
389 auto anchor = cast<SPAnchor>(
object);
390 auto changed = _anchor != anchor;
393 _picker.disconnect();
398 _table->change_object(anchor);
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");
407 op_button->signal_toggled().connect([=,
this] {
411 if (!_desktop || _update.pending()) {
417 if (!op_button->get_active()) {
418 _picker.disconnect();
419 set_active_tool(_desktop, _desktop->getTool()->get_last_active_tool());
427 _picker = tool->signal_object_picked.connect([grid, this](SPObject* item){
429 auto edit = dynamic_cast<Gtk::Entry*>(grid->get_child_at(1, 0));
431 Glib::ustring id =
"#";
432 edit->set_text(id + item->getId());
434 _picker.disconnect();
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);
443 _tool_switched.disconnect();
447 grid->attach(*op_button, 2, 0);
451 _table->reread_properties();
456 std::unique_ptr<SPAttributeTable> _table;
458 sigc::scoped_connection _picker;
459 sigc::scoped_connection _tool_switched;
460 bool _first_time_update =
true;
465class RectPanel :
public details::AttributesPanel {
467 RectPanel(Glib::RefPtr<Gtk::Builder>
builder) :
479 _width.get_adjustment()->signal_value_changed().connect([
this](){
480 change_value_px(_rect, _width.get_adjustment(),
"width", [
this](
double w){ _rect->setVisibleWidth(w); });
482 _height.get_adjustment()->signal_value_changed().connect([
this](){
483 change_value_px(_rect, _height.get_adjustment(),
"height", [
this](
double h){ _rect->setVisibleHeight(h); });
485 _rx.get_adjustment()->signal_value_changed().connect([
this](){
486 change_value_px(_rect, _rx.get_adjustment(),
"rx", [
this](
double rx){ _rect->setVisibleRx(rx); });
488 _ry.get_adjustment()->signal_value_changed().connect([
this](){
489 change_value_px(_rect, _ry.get_adjustment(),
"ry", [
this](
double ry){ _rect->setVisibleRy(ry); });
491 get_widget<Gtk::Button>(
builder,
"rect-round").signal_clicked().connect([
this](){
494 _width.get_adjustment()->set_value(x);
495 _height.get_adjustment()->set_value(y);
498 _sharp.signal_clicked().connect([
this](){
503 _rx.get_adjustment()->set_value(0);
504 _ry.get_adjustment()->set_value(0);
506 _round.signal_clicked().connect([
this](){
512 _rx.get_adjustment()->set_value(0);
513 _ry.get_adjustment()->set_value(0);
517 DocumentUndo::done(_rect->document, _(
"Add fillet/chamfer effect"), INKSCAPE_ICON(
"dialog-path-effects"));
522 ~RectPanel()
override =
default;
524 void update(
SPObject*
object)
override {
525 _rect = cast<SPRect>(
object);
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);
551class EllipsePanel :
public details::AttributesPanel {
553 EllipsePanel(Glib::RefPtr<Gtk::Builder>
builder) :
572 for (
auto btn : _type) {
573 btn->signal_toggled().connect([type,
this](){ set_type(type); });
577 _whole.signal_clicked().connect([
this](){
578 _start.get_adjustment()->set_value(0);
579 _end.get_adjustment()->set_value(0);
583 _ellipse->normalize();
584 _ellipse->updateRepr();
585 _ellipse->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
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(); });
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(); });
594 _start.get_adjustment()->signal_value_changed().connect([=,
this](){
595 change_angle(_ellipse, _start.get_adjustment(), [=,
this](
double s){ _ellipse->start = s; normalize(); });
597 _end.get_adjustment()->signal_value_changed().connect([=,
this](){
598 change_angle(_ellipse, _end.get_adjustment(), [=,
this](
double e){ _ellipse->end = e; normalize(); });
601 get_widget<Gtk::Button>(
builder,
"el-round").signal_clicked().connect([
this](){
603 if (changed && x > 0 && y > 0) {
604 _rx.get_adjustment()->set_value(x);
605 _ry.get_adjustment()->set_value(y);
610 ~EllipsePanel()
override =
default;
612 void update(
SPObject*
object)
override {
613 _ellipse = cast<SPGenericEllipse>(
object);
614 if (!_ellipse)
return;
617 _rx.set_value(_ellipse->rx.value);
618 _ry.set_value(_ellipse->ry.value);
626 auto slice = !_ellipse->is_whole();
627 _whole.set_sensitive(slice);
628 for (
auto btn : _type) {
629 btn->set_sensitive(slice);
633 void set_type(
int type) {
634 if (!_ellipse)
return;
638 Glib::ustring arc_type =
"slice";
654 std::cerr <<
"Ellipse type change - bad arc type: " <<
type << std::endl;
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"));
670 Gtk::ToggleButton &_slice;
671 Gtk::ToggleButton &_arc;
672 Gtk::ToggleButton &_chord;
674 Gtk::ToggleButton *_type[3];
679class StarPanel :
public details::AttributesPanel {
681 StarPanel(Glib::RefPtr<Gtk::Builder>
builder) :
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);
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);
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);
716 _path->setAttributeDouble(
"sodipodi:r2", r1 * ratio);
718 _path->setAttributeDouble(
"sodipodi:r1", r2 * ratio);
723 _rand.get_adjustment()->signal_value_changed().connect([
this](){
725 _path->setAttributeDouble(
"inkscape:randomized", rnd);
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); });
734 _poly.signal_toggled().connect([
this](){ set_flat(
true); });
735 _star.signal_toggled().connect([
this](){ set_flat(
false); });
736 _align.signal_clicked().connect([
this](){
741 ~StarPanel()
override =
default;
743 void update(
SPObject*
object)
override {
744 _path = cast<SPStar>(
object);
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);
752 _ratio.set_value(r1 > 0 ? r2 / r1 : 0.5);
754 _ratio.set_value(r2 > 0 ? r1 / r2 : 0.5);
756 _rounded.set_value(_path->rounded);
757 _rand.set_value(_path->randomized);
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);
763 _poly.set_active(_path->flatsided);
764 _star.set_active(!_path->flatsided);
767 void set_flat(
bool flat) {
768 change_value(_path, {}, [flat,
this](double){
769 _path->setAttribute(
"inkscape:flatsided", flat ?
"true" :
"false");
773 _corners.get_adjustment()->set_lower(flat ? 3 : 2);
774 if (flat && _corners.get_value() < 3) {
775 _corners.get_adjustment()->set_value(3);
786 Gtk::Button& _clear_rnd;
787 Gtk::Button& _clear_round;
788 Gtk::Button& _clear_ratio;
790 Gtk::ToggleButton &_poly;
791 Gtk::ToggleButton &_star;
796class TextPanel :
public details::AttributesPanel {
798 TextPanel(Glib::RefPtr<Gtk::Builder>
builder) :
811class PathPanel :
public details::AttributesPanel {
813 PathPanel(Glib::RefPtr<Gtk::Builder>
builder) :
820 _data(_svgd_edit->getTextView())
839 _svgd_edit->setStyle(theme);
840 _data.set_wrap_mode(Gtk::WrapMode::WORD);
842 auto const key = Gtk::EventControllerKey::create();
843 key->signal_key_pressed().connect(sigc::mem_fun(*
this, &PathPanel::on_key_pressed),
true);
846 auto& wnd = get_widget<Gtk::ScrolledWindow>(
builder,
"path-data-wnd");
847 wnd.set_child(_data);
849 auto set_precision = [=,
this](
int const 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);
859 menu_button.set_active(
false);
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));
871 get_widget<Gtk::Button>(
builder,
"path-data-round").signal_clicked().connect([
this]{
875 get_widget<Gtk::Button>(
builder,
"path-enter").signal_clicked().connect([
this](){ commit_d(); });
878 ~PathPanel()
override =
default;
880 void update(
SPObject*
object)
override {
881 _path = cast<SPPath>(
object);
886 auto d = _path->getAttribute(
"inkscape:original-d");
887 if (d && _path->hasPathEffect()) {
892 d = _path->getAttribute(
"d");
894 _svgd_edit->setText(d ? d :
"");
896 auto curve = _path->curveBeforeLPE();
898 size_t node_count = 0;
900 node_count =
curve->get_segment_count();
902 _info.set_text(_(
"Nodes: ") + std::to_string(node_count));
908 bool on_key_pressed(
unsigned keyval,
unsigned keycode, Gdk::ModifierType state) {
911 case GDK_KEY_KP_Enter:
918 if (!_path || !
_data.is_visible())
return false;
921 auto d = _svgd_edit->getText();
922 _path->setAttribute(_original ?
"inkscape:original-d" :
"d", d);
928 bool _original =
false;
936 Gtk::TextView&
_data;
943 if (!
object)
return {};
945 return typeid(*object).name();
949 if (!
object)
return nullptr;
953 return it ==
_panels.end() ? nullptr : it->second.get();
vector_type normalize(const vector_type)
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)
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.
DialogBase is the base class for the dialog system.
SPDesktop * getDesktop() const
void selectionModified(Selection *selection, guint flags) override
Inkscape::UI::Widget::StyleSwatch _style_swatch
ObjectProperties & _obj_properties
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.
void update_panel(SPObject *object)
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)
bool supports_fill_stroke() const
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.
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
double getAttributeDouble(Util::const_char_ptr key, double default_value=0.0) const
To do: update description of desktop.
Inkscape::Selection * getSelection() const
Base class for visual SVG elements.
virtual const char * displayName() const
The item's type name as a translated human string.
SPObject is an abstract base class of all of the document nodes at the SVG document level.
void setAttributeDouble(Inkscape::Util::const_char_ptr key, double value)
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
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.
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
Editable view implementation.
TODO: insert short description here.
Macro for icon names used in Inkscape.
double degree_to_radians_mod2pi(double degrees)
double radians_to_degree_mod360(double rad)
TODO: insert short description here.
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)
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).
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.
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.
T * get(GValue *value)
Returns a borrowed pointer to the T held by a value if it holds one, else nullptr.
Helper class to stream background task notifications as a series of messages.
static void append(std::vector< T > &target, std::vector< T > &&source)
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.
@ SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD
@ SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE
@ SP_GENERIC_ELLIPSE_ARC_TYPE_ARC
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...
Static style swatch (fill, stroke, opacity)
Glib::RefPtr< Gtk::Builder > builder
void truncate_digits(const Glib::RefPtr< Gtk::TextBuffer > &buffer, int precision)