Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
attrdialog.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/* Authors:
6 * Martin Owens
7 *
8 * Copyright (C) Martin Owens 2018 <doctormo@gmail.com>
9 *
10 * Released under GNU GPLv2 or later, read the file 'COPYING' for more information
11 */
12
13#include "attrdialog.h"
14
15#include <algorithm>
16#include <cstddef>
17#include <memory>
18#include <gdk/gdkkeysyms.h>
19#include <glibmm/i18n.h>
20#include <glibmm/main.h>
21#include <glibmm/regex.h>
22#include <glibmm/timer.h>
23#include <glibmm/ustring.h>
24#include <glibmm/varianttype.h>
25#include <giomm/simpleactiongroup.h>
26#include <gtkmm/box.h>
27#include <gtkmm/builder.h>
28#include <gtkmm/button.h>
29#include <gtkmm/entry.h>
30#include <gtkmm/enums.h>
31#include <gtkmm/eventcontrollerkey.h>
32#include <gtkmm/image.h>
33#include <gtkmm/label.h>
34#include <gtkmm/liststore.h>
35#include <gtkmm/menubutton.h>
36#include <gtkmm/object.h>
37#include <gtkmm/popover.h>
38#include <gtkmm/scrolledwindow.h>
39#include <gtkmm/textview.h>
40#include <gtkmm/treemodel.h>
41#include <gtkmm/treeview.h>
42#include <sigc++/adaptors/bind.h>
43#include <sigc++/functors/mem_fun.h>
44
45#include "config.h"
46#include "ui/util.h"
47#if WITH_GSOURCEVIEW
48# include <gtksourceview/gtksource.h>
49#endif
50
51#include "document-undo.h"
52#include "message-context.h"
53#include "message-stack.h"
54#include "preferences.h"
55#include "ui/builder-utils.h"
56#include "ui/controller.h"
57#include "ui/icon-loader.h"
58#include "ui/icon-names.h"
59#include "ui/pack.h"
60#include "ui/popup-menu.h"
61#include "ui/syntax.h"
64
69{
70 switch (node.type()) {
73 return true;
74 default:
75 return false;
76 }
77}
78
79static Glib::ustring get_syntax_theme()
80{
81 return Inkscape::Preferences::get()->getString("/theme/syntax-color-theme", "-none-");
82}
83
84namespace Inkscape::UI::Dialog {
85
86// arbitrarily selected size limits
87constexpr int MAX_POPOVER_HEIGHT = 450;
88constexpr int MAX_POPOVER_WIDTH = 520;
89constexpr int TEXT_MARGIN = 3;
90
91std::unique_ptr<Syntax::TextEditView> AttrDialog::init_text_view(AttrDialog* owner, Syntax::SyntaxMode coloring, bool map)
92{
93 auto edit = Syntax::TextEditView::create(coloring);
94 auto& textview = edit->getTextView();
95 textview.set_wrap_mode(Gtk::WrapMode::WORD);
96
97 // this actually sets padding rather than margin and extends textview's background color to the sides
98 textview.set_top_margin(TEXT_MARGIN);
99 textview.set_left_margin(TEXT_MARGIN);
100 textview.set_right_margin(TEXT_MARGIN);
101 textview.set_bottom_margin(TEXT_MARGIN);
102
103 if (map) {
104 textview.signal_map().connect([owner](){
105 // this is not effective: text view recalculates its size on idle, so it's too early to call on 'map';
106 // (note: there's no signal on a TextView to tell us that formatting has been done)
107 // delay adjustment; this will work if UI is fast enough, but at the cost of popup jumping,
108 // but at least it will be sized properly
109 owner->_adjust_size = Glib::signal_timeout().connect([=](){ owner->adjust_popup_edit_size(); return false; }, 50);
110 });
111 }
112
113 return edit;
114}
115
122 : DialogBase("/dialogs/attr", "AttrDialog")
123 , _builder(create_builder("attribute-edit-component.glade"))
124 , _scrolled_text_view(get_widget<Gtk::ScrolledWindow>(_builder, "scroll-wnd"))
125 , _content_sw(get_widget<Gtk::ScrolledWindow>(_builder, "content-sw"))
126 , _scrolled_window(get_widget<Gtk::ScrolledWindow>(_builder, "scrolled-wnd"))
127 , _treeView(get_widget<Gtk::TreeView>(_builder, "tree-view"))
128 , _popover(&get_widget<Gtk::Popover>(_builder, "popup"))
129 , _status_box(get_widget<Gtk::Box>(_builder, "status-box"))
130 , _status(get_widget<Gtk::Label>(_builder, "status-label"))
131{
132 // Attribute value editing (with syntax highlighting).
133 using namespace Syntax;
134 _css_edit = init_text_view(this, SyntaxMode::InlineCss, true);
135 _svgd_edit = init_text_view(this, SyntaxMode::SvgPathData, true);
136 _points_edit = init_text_view(this, SyntaxMode::SvgPolyPoints, true);
137 _attr_edit = init_text_view(this, SyntaxMode::PlainText, true);
138
139 // string content editing
140 _text_edit = init_text_view(this, SyntaxMode::PlainText, false);
141 _style_edit = init_text_view(this, SyntaxMode::CssStyle, false);
142
143 set_size_request(20, 15);
144
145 // For text and comment nodes: update XML on the fly, as users type
146 for (auto tv : {&_text_edit->getTextView(), &_style_edit->getTextView()}) {
147 tv->get_buffer()->signal_end_user_action().connect([=, this]() {
148 if (_repr) {
149 _repr->setContent(tv->get_buffer()->get_text().c_str());
150 setUndo(_("Type text"));
151 }
152 });
153 }
154
155 _store = Gtk::ListStore::create(_attrColumns);
156 _treeView.set_model(_store);
157
158 auto const delete_renderer = Gtk::make_managed<UI::Widget::IconRenderer>();
159 delete_renderer->add_icon("edit-delete");
160 delete_renderer->signal_activated().connect(sigc::mem_fun(*this, &AttrDialog::onAttrDelete));
161 _treeView.append_column("", *delete_renderer);
162
163 if (auto const col = _treeView.get_column(0)) {
164 auto add_icon = Gtk::manage(sp_get_icon_image("list-add", Gtk::IconSize::NORMAL));
165 col->set_clickable(true);
166 col->set_widget(*add_icon);
167 add_icon->set_tooltip_text(_("Add a new attribute"));
168 add_icon->set_visible(true);
169 col->signal_clicked().connect(sigc::mem_fun(*this, &AttrDialog::onCreateClicked), false);
170 }
171
172 auto const key = Gtk::EventControllerKey::create();
173 key->signal_key_pressed().connect(sigc::mem_fun(*this, &AttrDialog::onTreeViewKeyPressed), true);
174 key->signal_key_released().connect(sigc::mem_fun(*this, &AttrDialog::onTreeViewKeyReleased));
175 _treeView.add_controller(key);
176
177 _nameRenderer = Gtk::make_managed<Gtk::CellRendererText>();
178 _nameRenderer->property_editable() = true;
179 _nameRenderer->property_placeholder_text().set_value(_("Attribute Name"));
180 _nameRenderer->signal_edited().connect(sigc::mem_fun(*this, &AttrDialog::nameEdited));
181 _nameRenderer->signal_editing_started().connect(sigc::mem_fun(*this, &AttrDialog::startNameEdit));
182 _treeView.append_column(_("Name"), *_nameRenderer);
183 _nameCol = _treeView.get_column(1);
184 if (_nameCol) {
185 _nameCol->set_resizable(true);
186 _nameCol->add_attribute(_nameRenderer->property_text(), _attrColumns._attributeName);
187 }
188
189 _message_stack = std::make_unique<Inkscape::MessageStack>();
190 _message_context = std::make_unique<Inkscape::MessageContext>(*_message_stack);
191 _message_changed_connection = _message_stack->connectChanged([this] (MessageType, char const *message) {
192 _status.set_markup(message ? message : "");
193 });
194
195 _valueRenderer = Gtk::make_managed<Gtk::CellRendererText>();
196 _valueRenderer->property_editable() = true;
197 _valueRenderer->property_placeholder_text().set_value(_("Attribute Value"));
198 _valueRenderer->property_ellipsize().set_value(Pango::EllipsizeMode::END);
199 _valueRenderer->signal_edited().connect(sigc::mem_fun(*this, &AttrDialog::valueEdited));
200 _valueRenderer->signal_editing_started().connect(sigc::mem_fun(*this, &AttrDialog::startValueEdit), true);
201 _treeView.append_column(_("Value"), *_valueRenderer);
202 _valueCol = _treeView.get_column(2);
203 if (_valueCol) {
204 _valueCol->add_attribute(_valueRenderer->property_text(), _attrColumns._attributeValueRender);
205 }
206
208 _scrolled_text_view.set_max_content_height(MAX_POPOVER_HEIGHT);
209
210 auto& apply = get_widget<Gtk::Button>(_builder, "btn-ok");
211 apply.signal_clicked().connect([this]{ valueEditedPop(); });
212
213 auto& cancel = get_widget<Gtk::Button>(_builder, "btn-cancel");
214 cancel.signal_clicked().connect([this]{
215 if (!_value_editing.empty()) {
216 _activeTextView().get_buffer()->set_text(_value_editing);
217 }
218 _popover->popdown();
219 });
220
221 _popover->set_parent(*this);
222 _popover->signal_closed().connect([this]{ popClosed(); });
223
224 auto const popover_key = Gtk::EventControllerKey::create();
225 popover_key->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
226 popover_key->signal_key_pressed().connect(sigc::mem_fun(*this, &AttrDialog::onPopoverKeyPressed), true);
227 _popover->add_controller(popover_key);
228
229 get_widget<Gtk::Button>(_builder, "btn-truncate").signal_clicked().connect([this]{ truncateDigits(); });
230
231 const int N = 5;
232 _rounding_precision = Inkscape::Preferences::get()->getIntLimited("/dialogs/attrib/precision", 2, 0, N);
234 auto group = Gio::SimpleActionGroup::create();
235 auto action = group->add_action_radio_integer("precision", _rounding_precision);
236 action->property_state().signal_changed().connect([=, this]{ int n; action->get_state(n);
237 setPrecision(n); });
238 insert_action_group("attrdialog", std::move(group));
239
241 UI::pack_start(*this, get_widget<Gtk::Box>(_builder, "main-box"), UI::PackOptions::expand_widget);
242 _updating = false;
243}
244
246{
247 _current_text_edit = nullptr;
248 _popover->set_visible(false);
249
250 // remove itself from the list of node observers
251 setRepr(nullptr);
252}
253
256{
257 if (!_current_text_edit) {
258 return;
259 }
260
261 auto buffer = _current_text_edit->getTextView().get_buffer();
263}
264
271
273{
274 auto vscroll = _scrolled_text_view.get_vadjustment();
275 int height = vscroll->get_upper() + 2 * TEXT_MARGIN;
277 _scrolled_text_view.set_min_content_height(height);
278 vscroll->set_value(vscroll->get_lower());
279 } else {
280 _scrolled_text_view.set_min_content_height(MAX_POPOVER_HEIGHT);
281 }
282}
283
284bool AttrDialog::onPopoverKeyPressed(unsigned keyval, unsigned /*keycode*/, Gdk::ModifierType state)
285{
286 if (!_popover->is_visible()) return false;
287
288 switch (keyval) {
289 case GDK_KEY_Return:
290 case GDK_KEY_KP_Enter:
291 if (Controller::has_flag(state, Gdk::ModifierType::SHIFT_MASK)) {
293 return true;
294 }
295
296 // as we type and content grows, resize the popup to accommodate it
297 _adjust_size = Glib::signal_timeout().connect([this]{ adjust_popup_edit_size(); return false; }, 50);
298 }
299
300 return false;
301}
302
312static Glib::ustring prepare_rendervalue(const char *value)
313{
314 constexpr int MAX_LENGTH = 500; // maximum length of string before it's truncated for performance reasons
315 // ~400 characters fit horizontally on a WQHD display, so 500 should be plenty
316 Glib::ustring renderval;
317
318 // truncate to MAX_LENGTH
319 if (g_utf8_strlen(value, -1) > MAX_LENGTH) {
320 renderval = Glib::ustring(value, MAX_LENGTH) + "…";
321 } else {
322 renderval = value;
323 }
324
325 // truncate at first newline (if present) and add a visual indicator
326 auto ind = renderval.find('\n');
327 if (ind != Glib::ustring::npos) {
328 renderval.replace(ind, Glib::ustring::npos, " ⏎ …");
329 }
330
331 return renderval;
332}
333
334void set_mono_class(Gtk::Widget* widget, bool mono)
335{
336 if (!widget) {
337 return;
338 }
339
340 static Glib::ustring const class_name = "mono-font";
341 auto const has_class = widget->has_css_class(class_name);
342
343 if (mono && !has_class) {
344 widget->add_css_class(class_name);
345 } else if (!mono && has_class) {
346 widget->remove_css_class(class_name);
347 }
348}
349
351{
353}
354
355void AttrDialog::startNameEdit(Gtk::CellEditable *cell, const Glib::ustring &path)
356{
357 Gtk::Entry *entry = dynamic_cast<Gtk::Entry *>(cell);
358 setEditingEntry(entry, false);
359}
360
361Gtk::TextView &AttrDialog::_activeTextView() const
362{
364}
365
366void AttrDialog::startValueEdit(Gtk::CellEditable *cell, const Glib::ustring &path)
367{
368 _value_path = path;
369
370 if (!_repr || !cell) {
371 return;
372 }
373
374 auto const iter = _store->get_iter(path);
375 if (!iter) {
376 return;
377 }
378
379 auto &row = *iter;
380
381 // popover in GTK3 is clipped to dialog window (in a floating dialog); limit size:
382 const int dlg_width = get_allocated_width() - 10;
383 _popover->set_size_request(std::min(MAX_POPOVER_WIDTH, dlg_width), -1);
384
385 auto const &attribute = row.get_value(_attrColumns._attributeName);
386 bool edit_in_popup =
387#if WITH_GSOURCEVIEW
388 true;
389#else
390 false;
391#endif
392 bool enable_rouding = false;
393
394 if (attribute == "style") {
396 } else if (attribute == "d" || attribute == "inkscape:original-d") {
397 enable_rouding = true;
399 } else if (attribute == "points") {
400 enable_rouding = true;
402 } else {
404 edit_in_popup = false;
405 }
406
407 // number rounding functionality
408 get_widget<Gtk::Box>(_builder, "rounding-box").set_visible(enable_rouding);
409
410 _activeTextView().set_size_request(std::min(MAX_POPOVER_WIDTH - 10, dlg_width), -1);
411
412 auto theme = get_syntax_theme();
413
414 auto entry = dynamic_cast<Gtk::Entry*>(cell);
415 /* TODO: GTK4: We probably need a better replacement here:
416 int width, height;
417 entry->get_layout()->get_pixel_size(width, height);
418 */
419 int const width = entry->get_width();
420 int colwidth = _valueCol->get_width();
421
422 if (row.get_value(_attrColumns._attributeValue) != row.get_value(_attrColumns._attributeValueRender) ||
423 edit_in_popup || colwidth - 10 < width)
424 {
425 _value_editing = entry->get_text();
426
427 Gdk::Rectangle rect;
428 _treeView.get_cell_area((Gtk::TreeModel::Path)iter, *_valueCol, rect);
429 if (_popover->get_position() == Gtk::PositionType::BOTTOM) {
430 rect.set_y(rect.get_y() + 20);
431 }
432 if (rect.get_x() >= dlg_width) {
433 rect.set_x(dlg_width - 1);
434 }
435
436 auto current_value = row[_attrColumns._attributeValue];
438 _current_text_edit->setText(current_value);
439
440 // close in-line entry
441 cell->property_editing_canceled() = true;
442 cell->remove_widget();
443 // cannot dismiss it right away without warning from GTK, so delay it
444 Glib::signal_timeout().connect_once([=](){
445 cell->editing_done(); // only this call will actually remove in-line edit widget
446 cell->remove_widget();
447 }, 0);
448 // and show popup edit instead
449 Glib::signal_timeout().connect_once([=, this]{ UI::popup_at(*_popover, _treeView, rect); },
450 10);
451 } else {
452 setEditingEntry(entry, true);
453 }
454}
455
457{
458 if (!_current_text_edit) {
459 return;
460 }
461
462 _activeTextView().get_buffer()->set_text("");
463
464 // delay this resizing, so it is not visible as popover fades out
465 _close_popup = Glib::signal_timeout().connect(
466 [this]{ _scrolled_text_view.set_min_content_height(20); return false; }, 250);
467}
468
474{
475 if (repr == _repr) {
476 return;
477 }
478 if (_repr) {
479 _store->clear();
480 _repr->removeObserver(*this);
482 _repr = nullptr;
483 }
484 _repr = repr;
485 if (repr) {
487 _repr->addObserver(*this);
488
489 // show either attributes or content
490 bool show_content = is_text_or_comment_node(*_repr);
491 if (show_content) {
492 _content_sw.unset_child();
493 auto type = repr->name();
494 auto elem = repr->parent();
495 if (type && strcmp(type, "string") == 0 && elem && elem->name() && strcmp(elem->name(), "svg:style") == 0) {
496 // editing embedded CSS style
497 _style_edit->setStyle(get_syntax_theme());
498 _content_sw.set_child(_style_edit->getTextView());
499 } else {
500 _content_sw.set_child(_text_edit->getTextView());
501 }
502 }
503
504 _repr->synthesizeEvents(*this);
505 _scrolled_window.set_visible(!show_content);
506 _content_sw.set_visible(show_content);
507 }
508}
509
510void AttrDialog::setUndo(Glib::ustring const &event_description)
511{
512 DocumentUndo::done(getDocument(), event_description, INKSCAPE_ICON("dialog-xml-editor"));
513}
514
516{
517 auto const iter = _store->prepend();
518 auto const path = static_cast<Gtk::TreeModel::Path>(iter);
519 _treeView.set_cursor(path, *_nameCol, true);
520 grab_focus();
521}
522
523void AttrDialog::deleteAttribute(Gtk::TreeRow &row)
524{
525 auto const name = row.get_value(_attrColumns._attributeName);
526 _store->erase(row.get_iter());
528 setUndo(_("Delete attribute"));
529}
530
531void AttrDialog::setEditingEntry(Gtk::Entry * const entry, bool const embedNewline)
532{
533 g_assert(!(entry == nullptr && embedNewline));
534
535 _editingEntry = entry;
536 _embedNewline = embedNewline;
537
538 if (_editingEntry == nullptr) return;
539
540 _editingEntry->signal_editing_done().connect([this]{ setEditingEntry(nullptr, false); });
541}
542
547{
548 if (attr == 0) {
549 _message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> attribute to edit."));
550 } else {
551 const gchar *name = g_quark_to_string(attr);
552 _message_context->setF(
554 _("Attribute <b>%s</b> selected. Press <b>Ctrl+Enter</b> when done editing to commit changes."), name);
555 }
556}
557
563 Util::ptr_shared /*old_value*/, Util::ptr_shared new_value)
564{
565 if (_updating) {
566 return;
567 }
568
569 auto const name = g_quark_to_string(name_);
570
571 Glib::ustring renderval;
572 if (new_value) {
573 renderval = prepare_rendervalue(new_value.pointer());
574 }
575
576 for (auto &row : _store->children()) {
577 Glib::ustring col_name = row[_attrColumns._attributeName];
578 if (name == col_name) {
579 if (new_value) {
580 row[_attrColumns._attributeValue] = new_value.pointer();
581 row[_attrColumns._attributeValueRender] = renderval;
582 new_value = Util::ptr_shared(); // Don't make a new one
583 } else {
584 _store->erase(row.get_iter());
585 }
586 break;
587 }
588 }
589
590 if (new_value) {
591 Gtk::TreeModel::Row row = *_store->prepend();
593 row[_attrColumns._attributeValue] = new_value.pointer();
594 row[_attrColumns._attributeValueRender] = renderval;
595 }
596}
597
603{
604 if (_repr) {
606 }
607}
608
615void AttrDialog::onAttrDelete(Glib::ustring const &path)
616{
617 Gtk::TreeModel::Row row = *_store->get_iter(path);
618 if (row) {
619 deleteAttribute(row);
620 }
621}
622
624 Util::ptr_shared new_content)
625{
626 auto textview = dynamic_cast<Gtk::TextView *>(_content_sw.get_child());
627 if (!textview) {
628 return;
629 }
630 auto buffer = textview->get_buffer();
631 if (!buffer->get_modified()) {
632 auto str = new_content.pointer();
633 buffer->set_text(str ? str : "");
634 }
635 buffer->set_modified(false);
636}
637
642bool AttrDialog::onTreeViewKeyPressed(unsigned keyval, unsigned /*keycode*/, Gdk::ModifierType state)
643{
644 if (!_repr) {
645 return false;
646 }
647
648 switch (keyval) {
649 case GDK_KEY_Delete:
650 case GDK_KEY_KP_Delete: {
651 if (auto const selection = _treeView.get_selection()) {
652 auto row = *selection->get_selected();
653 deleteAttribute(row);
654 }
655 return true;
656 }
657
658 case GDK_KEY_plus:
659 case GDK_KEY_Insert:
661 return true;
662
663 case GDK_KEY_Return:
664 case GDK_KEY_KP_Enter:
665 if (_popover->is_visible() && Controller::has_flag(state, Gdk::ModifierType::SHIFT_MASK)) {
667 return true;
668 }
669 }
670
671 return false;
672}
673
674void AttrDialog::onTreeViewKeyReleased(unsigned keyval, unsigned /*keycode*/, Gdk::ModifierType state)
675{
676 if (_editingEntry == nullptr) return;
677
678 switch (keyval) {
679 case GDK_KEY_Return:
680 case GDK_KEY_KP_Enter:
681 if (_embedNewline && Controller::has_flag(state, Gdk::ModifierType::SHIFT_MASK)) {
682 auto pos = _editingEntry->get_position();
683 _editingEntry->insert_text("\n", 1, pos);
684 _editingEntry->set_position(pos + 1);
685 }
686 }
687}
688
689void AttrDialog::storeMoveToNext(Gtk::TreeModel::Path modelpath)
690{
691 auto selection = _treeView.get_selection();
692 auto const iter = selection->get_selected();
693 Gtk::TreeModel::Path path;
694 Gtk::TreeViewColumn *focus_column;
695 _treeView.get_cursor(path, focus_column);
696 if (path == modelpath && focus_column == _treeView.get_column(1)) {
697 _treeView.set_cursor(modelpath, *_valueCol, true);
698 }
699}
700
704void AttrDialog::nameEdited (const Glib::ustring& path, const Glib::ustring& name)
705{
706 auto iter = _store->get_iter(path);
707 auto modelpath = static_cast<Gtk::TreeModel::Path>(iter);
708 auto &row = *iter;
709 if(row && this->_repr) {
710 Glib::ustring old_name = row[_attrColumns._attributeName];
711 if (old_name == name) {
712 Glib::signal_timeout().connect_once([=, this]{ storeMoveToNext(modelpath); }, 50);
713 grab_focus();
714 return;
715 }
716 // Do not allow empty name (this would delete the attribute)
717 if (name.empty()) {
718 return;
719 }
720 // Do not allow duplicate names
721 const auto children = _store->children();
722 for (const auto &child : children) {
723 if (name == child.get_value(_attrColumns._attributeName)) {
724 return;
725 }
726 }
727 if(std::any_of(name.begin(), name.end(), isspace)) {
728 return;
729 }
730 // Copy old value and remove old name
731 Glib::ustring value;
732 if (!old_name.empty()) {
733 value = row[_attrColumns._attributeValue];
734 _updating = true;
735 _repr->removeAttribute(old_name);
736 _updating = false;
737 }
738
739 // Do the actual renaming and set new value
741 grab_focus();
742 _updating = true;
743 _repr->setAttributeOrRemoveIfEmpty(name, value); // use char * overload (allows empty attribute values)
744 _updating = false;
745 Glib::signal_timeout().connect_once([=, this]{ storeMoveToNext(modelpath); }, 50);
746 setUndo(_("Rename attribute"));
747 }
748}
749
756
763void AttrDialog::valueEdited (const Glib::ustring& path, const Glib::ustring& value)
764{
765 if (!getDesktop()) {
766 return;
767 }
768
769 Gtk::TreeModel::Row row = *_store->get_iter(path);
770 if (row && _repr) {
771 Glib::ustring name = row[_attrColumns._attributeName];
772 Glib::ustring old_value = row[_attrColumns._attributeValue];
773 if (old_value == value || name.empty()) {
774 return;
775 }
776
778
779 if (!value.empty()) {
780 row[_attrColumns._attributeValue] = value;
781 Glib::ustring renderval = prepare_rendervalue(value.c_str());
782 row[_attrColumns._attributeValueRender] = renderval;
783 }
784 setUndo(_("Change attribute value"));
785 }
786}
787
789{
791 auto &menu_button = get_widget<Gtk::MenuButton>(_builder, "btn-menu");
792 auto const menu = menu_button.get_menu_model();
793 auto const section = menu->get_item_link(0, Gio::MenuModel::Link::SECTION);
794 auto const type = Glib::VariantType{g_variant_type_new("s")};
795 auto const variant = section->get_item_attribute(n, Gio::MenuModel::Attribute::LABEL, type);
796 auto const label = ' ' + static_cast<Glib::Variant<Glib::ustring> const &>(variant).get();
797 get_widget<Gtk::Label>(_builder, "precision").set_label(label);
798 Inkscape::Preferences::get()->setInt("/dialogs/attrib/precision", n);
799 menu_button.set_active(false);
800}
801
802} // namespace Inkscape::UI::Dialog
803
804/*
805 Local Variables:
806 mode:c++
807 c-file-style:"stroustrup"
808 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
809 indent-tabs-mode:nil
810 fill-column:99
811 End:
812*/
813// vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99:
static Glib::ustring get_syntax_theme()
static bool is_text_or_comment_node(Inkscape::XML::Node const &node)
Return true if node is a text or comment node.
A dialog for XML attributes based on Gtk TreeView.
Gtk builder utilities.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
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.
Gtk::TreeModelColumn< Glib::ustring > _attributeValue
Definition attrdialog.h:82
Gtk::TreeModelColumn< Glib::ustring > _attributeValueRender
Definition attrdialog.h:83
Gtk::TreeModelColumn< Glib::ustring > _attributeName
Definition attrdialog.h:81
The AttrDialog class This dialog allows to add, delete and modify XML attributes created in the xml e...
Definition attrdialog.h:56
static std::unique_ptr< Syntax::TextEditView > init_text_view(AttrDialog *owner, Syntax::SyntaxMode coloring, bool map)
void setUndo(Glib::ustring const &event_description)
void startNameEdit(Gtk::CellEditable *cell, const Glib::ustring &path)
bool onTreeViewKeyPressed(unsigned keyval, unsigned keycode, Gdk::ModifierType state)
AttrDialog::onTreeViewKeyPressed Delete or create elements based on key presses.
void attr_reset_context(gint attr)
Sets the XML status bar, depending on which attr is selected.
Gtk::ScrolledWindow & _scrolled_text_view
Definition attrdialog.h:102
sigc::scoped_connection _message_changed_connection
Signal handlers.
Definition attrdialog.h:122
Gtk::TreeViewColumn * _valueCol
Definition attrdialog.h:93
void startValueEdit(Gtk::CellEditable *cell, const Glib::ustring &path)
Gtk::CellRendererText * _valueRenderer
Definition attrdialog.h:91
void set_current_textedit(Syntax::TextEditView *edit)
Gtk::ScrolledWindow & _scrolled_window
Definition attrdialog.h:101
void deleteAttribute(Gtk::TreeRow &row)
std::unique_ptr< Syntax::TextEditView > _points_edit
Definition attrdialog.h:149
void nameEdited(const Glib::ustring &path, const Glib::ustring &name)
Called when the name is edited in the TreeView editable column.
void notifyContentChanged(XML::Node &node, Util::ptr_shared old_content, Util::ptr_shared new_content) final
Content change callback.
void storeMoveToNext(Gtk::TreeModel::Path modelpath)
std::unique_ptr< Syntax::TextEditView > _text_edit
Definition attrdialog.h:143
std::unique_ptr< Inkscape::MessageContext > _message_context
Definition attrdialog.h:99
Gtk::CellRendererText * _nameRenderer
Definition attrdialog.h:90
Syntax::TextEditView * _current_text_edit
Definition attrdialog.h:151
std::unique_ptr< Syntax::TextEditView > _style_edit
Definition attrdialog.h:144
Glib::RefPtr< Gtk::Builder > _builder
Definition attrdialog.h:69
std::unique_ptr< Syntax::TextEditView > _attr_edit
Definition attrdialog.h:150
void truncateDigits() const
Round the selected floating point numbers in the attribute edit popover.
void valueEdited(const Glib::ustring &path, const Glib::ustring &value)
AttrDialog::valueEdited.
void onAttrDelete(Glib::ustring const &path)
AttrDialog::onAttrDelete.
sigc::scoped_connection _adjust_size
Definition attrdialog.h:152
void setRepr(Inkscape::XML::Node *repr)
AttrDialog::setRepr Set the internal xml object that I'm working on right now.
void notifyAttributeChanged(XML::Node &repr, GQuark name, Util::ptr_shared old_value, Util::ptr_shared new_value) final
AttrDialog::notifyAttributeChanged This is called when the XML has an updated attribute.
Gtk::TreeViewColumn * _nameCol
Definition attrdialog.h:92
std::unique_ptr< Inkscape::MessageStack > _message_stack
Definition attrdialog.h:98
sigc::scoped_connection _close_popup
Definition attrdialog.h:153
std::unique_ptr< Syntax::TextEditView > _css_edit
Definition attrdialog.h:147
Inkscape::XML::Node * _repr
Definition attrdialog.h:104
void onTreeViewKeyReleased(unsigned keyval, unsigned keycode, Gdk::ModifierType state)
void onCreateClicked()
AttrDialog::onCreateClicked This function is a slot to signal_clicked for '+' button panel.
Gtk::ScrolledWindow & _content_sw
Definition attrdialog.h:142
Gtk::TextView & _activeTextView() const
std::unique_ptr< Syntax::TextEditView > _svgd_edit
Definition attrdialog.h:148
void setEditingEntry(Gtk::Entry *entry, bool embedNewline)
bool onPopoverKeyPressed(unsigned keyval, unsigned keycode, Gdk::ModifierType state)
AttrDialog()
Constructor A treeview whose each row corresponds to an XML attribute of a selected node New attribut...
Glib::RefPtr< Gtk::ListStore > _store
Definition attrdialog.h:89
DialogBase is the base class for the dialog system.
Definition dialog-base.h:42
SPDocument * getDocument() const
Definition dialog-base.h:83
SPDesktop * getDesktop() const
Definition dialog-base.h:79
Base class for styled text editing widget.
Definition syntax.h:109
static std::unique_ptr< TextEditView > create(SyntaxMode mode)
Create a styled text view using the desired syntax highlighting mode.
Definition syntax.cpp:382
virtual Glib::ustring getText() const =0
virtual void setStyle(const Glib::ustring &theme)=0
virtual void setText(const Glib::ustring &text)=0
virtual Gtk::TextView & getTextView() const =0
char const * pointer() const
Definition share.h:33
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual void synthesizeEvents(NodeObserver &observer)=0
Generate a sequence of events corresponding to the state of this node.
virtual char const * name() const =0
Get the name of the element node.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
virtual void setContent(char const *value)=0
Set the content of a text or comment node.
void removeAttribute(Inkscape::Util::const_char_ptr key)
Remove an attribute of this node.
Definition node.h:280
virtual void addObserver(NodeObserver &observer)=0
Add an object that will be notified of the changes to this node.
virtual NodeType type() const =0
Get the type of the node.
virtual void removeObserver(NodeObserver &observer)=0
Remove an object from the list of observers.
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
Utility functions to convert ascii representations to numbers.
std::unordered_map< std::string, std::unique_ptr< SPDocument > > map
TODO: insert short description here.
Gtk::Image * sp_get_icon_image(Glib::ustring const &icon_name, int size)
Icon Loader.
Macro for icon names used in Inkscape.
Inkscape::XML::Node * node
Glib::ustring label
Interface for locally managing a current status message.
Raw stack of active status messages.
Definition desktop.h:50
static R & anchor(R &r)
Increments the reference count of a anchored object.
Definition gc-anchored.h:92
static R & release(R &r)
Decrements the reference count of a anchored object.
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
constexpr int MAX_POPOVER_WIDTH
void set_mono_class(Gtk::Widget *widget, bool mono)
static Glib::ustring prepare_rendervalue(const char *value)
Prepare value string suitable for display in a Gtk::CellRendererText.
constexpr int TEXT_MARGIN
constexpr int MAX_POPOVER_HEIGHT
SyntaxMode
Syntax highlighting mode (language).
Definition syntax.h:98
static constexpr int height
static void popup_at(Gtk::Popover &popover, Gtk::Widget &widget, double const x_offset, double const y_offset, int width, int height)
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
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
@ COMMENT_NODE
Comment node, e.g. <!– some comment –>.
@ TEXT_NODE
Text node, e.g. "Some text" in <group>Some text</group> is represented by a text node.
MessageType
A hint about the meaning of a message; is it an ordinary message, a message advising the user of some...
Definition message.h:25
@ NORMAL_MESSAGE
Definition message.h:26
static cairo_user_data_key_t key
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Helpers to connect signals to events that popup a menu in both GTK3 and GTK4.
Singleton class to access the preferences file in a convenient way.
Ocnode * child[8]
Definition quantize.cpp:33
size_t N
guint32 GQuark
double width
Glib::ustring name
Definition toolbars.cpp:55
void truncate_digits(const Glib::RefPtr< Gtk::TextBuffer > &buffer, int precision)
Definition util.cpp:481