Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
text-toolbar.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
4/* Authors:
5 * MenTaLguY <mental@rydia.net>
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * Frank Felfe <innerspace@iname.com>
9 * John Cliff <simarilius@yahoo.com>
10 * David Turner <novalis@gnu.org>
11 * Josh Andler <scislac@scislac.com>
12 * Jon A. Cruz <jon@joncruz.org>
13 * Maximilian Albert <maximilian.albert@gmail.com>
14 * Tavmjong Bah <tavmjong@free.fr>
15 * Abhishek Sharma
16 * Kris De Gussem <Kris.DeGussem@gmail.com>
17 * Tavmjong Bah <tavmjong@free.fr>
18 * Vaibhav Malik <vaibhavmalik2018@gmail.com>
19 *
20 * Copyright (C) 2004 David Turner
21 * Copyright (C) 2003 MenTaLguY
22 * Copyright (C) 2001-2002 Ximian, Inc.
23 * Copyright (C) 1999-2013 authors
24 * Copyright (C) 2017 Tavmjong Bah
25 *
26 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
27 */
28
29#include "text-toolbar.h"
30
31#include <boost/range/adaptor/reversed.hpp>
32#include <glibmm/i18n.h>
33#include <gtkmm/adjustment.h>
34#include <gtkmm/button.h>
35#include <gtkmm/checkbutton.h>
36#include <gtkmm/frame.h>
37#include <gtkmm/grid.h>
38#include <gtkmm/listbox.h>
39#include <gtkmm/menubutton.h>
40#include <gtkmm/separator.h>
41#include <gtkmm/togglebutton.h>
42
43#include "desktop-style.h"
44#include "desktop.h"
45#include "document-undo.h"
46#include "document.h"
47#include "inkscape.h"
49#include "object/sp-flowdiv.h"
50#include "object/sp-flowtext.h"
51#include "object/sp-root.h"
52#include "object/sp-string.h"
53#include "object/sp-text.h"
54#include "object/sp-tspan.h"
55#include "selection-chemistry.h"
56#include "selection.h"
58#include "ui/builder-utils.h"
60#include "ui/icon-names.h"
62#include "ui/tools/text-tool.h"
63#include "ui/util.h"
70#include "widgets/style-utils.h"
71
76
77constexpr bool DEBUG_TEXT = false;
78
79namespace Inkscape::UI::Toolbar {
80namespace {
81
82bool is_relative(Unit const *unit)
83{
84 return unit->abbr == "" || unit->abbr == "em" || unit->abbr == "ex" || unit->abbr == "%";
85}
86
87bool is_relative(SPCSSUnit const unit)
88{
89 return unit == SP_CSS_UNIT_NONE || unit == SP_CSS_UNIT_EM || unit == SP_CSS_UNIT_EX || unit == SP_CSS_UNIT_PERCENT;
90}
91
92// Set property for object, but unset all descendents
93// Should probably be moved to desktop_style.cpp
94void recursively_set_properties(SPObject *object, SPCSSAttr *css, bool unset_descendents = true)
95{
96 object->changeCSS(css, "style");
97
98 auto css_unset = sp_repr_css_attr_unset_all(css);
99 for (auto i : object->childList(false)) {
100 recursively_set_properties(i, unset_descendents ? css_unset : css);
101 }
102 sp_repr_css_attr_unref(css_unset);
103}
104
105Glib::RefPtr<Gtk::ListStore> create_sizes_store_uncached(int unit)
106{
107 // List of font sizes for dropdown menu
108 constexpr int sizes[] = {
109 4, 6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28,
110 32, 36, 40, 48, 56, 64, 72, 144
111 };
112
113 // Array must be same length as SPCSSUnit in style.h
114 constexpr float ratios[] = {1, 1, 1, 10, 4, 40, 100, 16, 8, 0.16};
115
116 struct Columns : Gtk::TreeModelColumnRecord
117 {
118 Gtk::TreeModelColumn<Glib::ustring> str;
119 Columns() { add(str); }
120 };
121 static Columns const columns;
122
123 auto store = Gtk::ListStore::create(columns);
124
125 for (int i : sizes) {
126 store->append()->set_value(columns.str, ustring::format_classic(i / ratios[unit]));
127 }
128
129 return store;
130}
131
135Glib::RefPtr<Gtk::ListStore> create_sizes_store(int unit)
136{
137 static std::unordered_map<int, Glib::RefPtr<Gtk::ListStore>> cache;
138
139 auto &result = cache[unit];
140
141 if (!result) {
142 result = create_sizes_store_uncached(unit);
143 }
144
145 return result;
146}
147
148// TODO: possibly share with font-selector by moving most code to font-lister (passing family name)
149void sp_text_toolbox_select_cb(Gtk::Entry const &entry)
150{
151 auto const family = entry.get_buffer()->get_text();
152 // std::cout << "text_toolbox_missing_font_cb: selecting: " << family << std::endl;
153
154 // Get all items with matching font-family set (not inherited!).
155 std::vector<SPItem *> selectList;
156
157 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
158 SPDocument *document = desktop->getDocument();
159 auto allList = get_all_items(document->getRoot(), desktop, false, false, true);
160 for (auto item : boost::adaptors::reverse(allList)) {
161 auto style = item->style;
162 if (!style) {
163 continue;
164 }
165
166 Glib::ustring family_style;
167 if (style->font_family.set) {
168 family_style = style->font_family.value();
169 // std::cout << " family style from font_family: " << family_style << std::endl;
170 } else if (style->font_specification.set) {
171 family_style = style->font_specification.value();
172 // std::cout << " family style from font_spec: " << family_style << std::endl;
173 }
174
175 if (family_style.compare(family) == 0) {
176 // std::cout << " found: " << item->getId() << std::endl;
177 selectList.push_back(item);
178 }
179 }
180
181 // Update selection
182 auto selection = desktop->getSelection();
183 selection->clear();
184 // std::cout << " list length: " << selectList.size() << std::endl;
185 selection->setList(selectList);
186}
187
188} // namespace
189
191 : TextToolbar{create_builder("toolbar-text.ui")}
192{}
193
194TextToolbar::TextToolbar(Glib::RefPtr<Gtk::Builder> const &builder)
195 : Toolbar{get_widget<Gtk::Box>(builder, "text-toolbar")}
196 , _tracker{std::make_unique<UnitTracker>(Util::UNIT_TYPE_LINEAR)}
197 , _tracker_fs{std::make_unique<UnitTracker>(Util::UNIT_TYPE_LINEAR)}
198 , _font_collections_list{get_widget<Gtk::ListBox>(builder, "_font_collections_list")}
199 , _reset_button{get_widget<Gtk::Button>(builder, "reset_btn")}
200 , _line_height_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_line_height_item")}
201 , _superscript_btn{get_widget<Gtk::ToggleButton>(builder, "_superscript_btn")}
202 , _subscript_btn{get_widget<Gtk::ToggleButton>(builder, "_subscript_btn")}
203 , _word_spacing_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_word_spacing_item")}
204 , _letter_spacing_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_letter_spacing_item")}
205 , _dx_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_dx_item")}
206 , _dy_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_dy_item")}
207 , _rotation_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_rotation_item")}
208{
209 auto prefs = Preferences::get();
210
211 // Line height unit tracker.
212 auto const &unit_table = Util::UnitTable::get();
213 _tracker->prependUnit(unit_table.getUnit("")); // Ratio
214 _tracker->addUnit(unit_table.getUnit("%"));
215 _tracker->addUnit(unit_table.getUnit("em"));
216 _tracker->addUnit(unit_table.getUnit("ex"));
217 _tracker->setActiveUnit(unit_table.getUnit(""));
218
219 // We change only the display value
220 _tracker->changeLabel("lines", 0, true);
221 _tracker_fs->setActiveUnit(unit_table.getUnit("mm"));
222
223 // Setup the spin buttons.
224 // TODO: Take care of the line-height pref settings.
231
233 {1, _("Single spaced")},
234 {1.25, _("Default")},
235 {1.5, ""},{
236 2, _("Double spaced")}
237 });
238 _letter_spacing_item.set_custom_numeric_menu_data({{0, C_("Text tool", "Normal")}});
239 _word_spacing_item.set_custom_numeric_menu_data({{0, C_("Text tool", "Normal")}});
243 {-90, ""},
244 {-45, ""},
245 {-30, ""},
246 {-15, ""},
247 { 0, ""},
248 { 15, ""},
249 { 30, ""},
250 { 45, ""},
251 { 90, ""}
252 });
253
254 // Configure alignment mode buttons
255 configure_mode_buttons(_alignment_buttons, get_widget<Gtk::Box>(builder, "alignment_buttons_box"), "align_mode",
257 configure_mode_buttons(_writing_buttons, get_widget<Gtk::Box>(builder, "writing_buttons_box"), "writing_mode",
259 configure_mode_buttons(_orientation_buttons, get_widget<Gtk::Box>(builder, "orientation_buttons_box"),
260 "orientation_mode", &TextToolbar::orientation_changed);
261 configure_mode_buttons(_direction_buttons, get_widget<Gtk::Box>(builder, "direction_buttons_box"),
262 "direction_mode", &TextToolbar::direction_changed);
263
264 auto const fontlister = FontLister::get_instance();
265
266 font_count_changed_connection = fontlister->connectUpdate([this] {
267 auto const [all_fonts, _] = FontLister::get_instance()->get_font_count_label();
268 _reset_button.set_sensitive(!all_fonts);
269 });
270
271 // Font family
272 _font_family_item = Gtk::make_managed<UI::Widget::ComboBoxEntryToolItem>(
273 "TextFontFamilyAction",
274 _("Font Family"),
275 _("Select Font Family (Alt-X to access)"),
276 fontlister->get_font_list(),
277 -1, // Entry width
278 50, // Extra list width
279 &font_lister_cell_data_func2, // Cell layout
281 );
282
283 _font_family_item->popup_enable(); // Enable entry completion
284 _font_family_item->set_info(_("Select all text with this font-family")); // Show selection icon
285 _font_family_item->set_info_cb(&sp_text_toolbox_select_cb);
286 _font_family_item->set_warning(_("Font not found on system")); // Show icon w/ tooltip if font missing
287 _font_family_item->set_warning_cb(&sp_text_toolbox_select_cb);
291
292 get_widget<Gtk::Box>(builder, "font_list_box").append(*_font_family_item);
293
294 // Font styles
296 "TextFontStyleAction",
297 _("Font Style"),
298 _("Font style"),
299 fontlister->get_style_list(),
300 12, // Width in characters
301 0, // Extra list width
302 {}, // Cell layout
303 {} // Separator
304 ));
305
309
310 get_widget<Gtk::Box>(builder, "styles_list_box").append(*_font_style_item);
311
312 // Font size
313 int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
314 auto unit_str = sp_style_get_css_unit_string(unit);
315 auto tooltip = Glib::ustring::format(_("Font size"), " (", unit_str, ")");
316
318 "TextFontSizeAction",
319 _("Font Size"),
320 tooltip,
321 create_sizes_store(unit),
322 8, // Width in characters
323 0, // Extra list width
324 {}, // Cell layout
325 {} // Separator
326 ));
327
331
332 get_widget<Gtk::Box>(builder, "font_size_box").append(*_font_size_item);
333
334 // Font size units
335 _font_size_units_item = _tracker_fs->create_tool_item(_("Units"), "");
338 get_widget<Gtk::Box>(builder, "unit_menu_box").append(*_font_size_units_item);
339
340 // Line height units
341 _line_height_units_item = _tracker->create_tool_item( _("Units"), "");
344 get_widget<Gtk::Box>(builder, "line_height_unit_box").append(*_line_height_units_item);
345
346 // Superscript button.
347 _superscript_btn.signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &TextToolbar::script_changed), 0));
348 _superscript_btn.set_active(prefs->getBool("/tools/text/super", false));
349
350 // Subscript button.
351 _subscript_btn.signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &TextToolbar::script_changed), 1));
352 _subscript_btn.set_active(prefs->getBool("/tools/text/sub", false));
353
354 // Font collections signals.
355 auto const font_collections = FontCollections::get();
356
357 get_widget<Gtk::Popover>(builder, "font_collections_popover")
358 .signal_show()
359 .connect([this] { display_font_collections(); }, false);
360
361 // This signal will keep both the Text and Font dialog and
362 // TextToolbar popovers in sync with each other.
363 fc_changed_selection = font_collections->connect_selection_update([this] { display_font_collections(); });
364
365 // This one will keep the text toolbar Font Collections
366 // updated in case of any change in the Font Collections.
367 fc_update = font_collections->connect_update([this] { display_font_collections(); });
368
369 get_widget<Gtk::Button>(builder, "fc_dialog_btn").signal_clicked().connect([this]() {
371 });
372
373 _reset_button.signal_clicked().connect([this]() { TextToolbar::on_reset_button_pressed(); });
374
376}
377
378TextToolbar::~TextToolbar() = default;
379
381{
382 if (_desktop) {
383 _selection_changed_conn.disconnect();
384 _selection_modified_conn.disconnect();
385 _cursor_moved_conn.disconnect();
386 _fonts_updated_conn.disconnect();
387 }
388
390
391 if (_desktop) {
392 auto fontlister = FontLister::get_instance();
393 fontlister->update_font_list(_desktop->getDocument());
394
395 // Keep font list up to date with document fonts when refreshed.
396 _fonts_updated_conn = fontlister->connectNewFonts([=] {
397 fontlister->update_font_list(desktop->getDocument());
398 });
399
400 auto sel = desktop->getSelection();
402 _selection_modified_conn = sel->connectModifiedFirst(sigc::mem_fun(*this, &TextToolbar::_selectionModified));
404 _cursorMoved(tool);
405 });
406 _sub_active_item = nullptr;
407 _cursor_numbers = 0;
409 }
410}
411
413 double default_value, ValueChangedMemFun value_changed_mem_fun)
414{
415 auto const path = "/tools/text/" + name;
416 auto const val = Preferences::get()->getDouble(path, default_value);
417 auto const adj = btn.get_adjustment();
418 adj->set_value(val);
419 adj->signal_value_changed().connect(sigc::mem_fun(*this, value_changed_mem_fun));
420 btn.setDefocusTarget(this);
421}
422
423void TextToolbar::configure_mode_buttons(std::vector<Gtk::ToggleButton *> &buttons, Gtk::Box &box,
424 Glib::ustring const &name, ModeChangedMemFun mode_changed_mem_fun)
425{
426 int btn_index = 0;
427
428 for_each_child(box, [this, mode_changed_mem_fun, &btn_index, &buttons](Gtk::Widget &item) {
429 auto &btn = dynamic_cast<Gtk::ToggleButton &>(item);
430 buttons.push_back(&btn);
431 btn.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, mode_changed_mem_fun), btn_index++));
432
434 });
435
436 // Set the active button after all the buttons have been pushed.
437 auto const path = "/tools/text/" + name;
438 int const active_button_index = Preferences::get()->getInt(path, 0);
439 buttons[active_button_index < buttons.size() ? active_button_index : 0]->set_active(true);
440}
441
442/*
443 * Set the style, depending on the inner or outer text being selected
444 */
446{
447 // Calling sp_desktop_set_style will result in a call to TextTool::_styleSet() which
448 // will set the style on selected text inside the <text> element. If we want to set
449 // the style on the outer <text> objects we need to bypass this call.
450 if (_outer) {
451 // Apply css to parent text objects directly.
452 for (auto item : _desktop->getSelection()->items()) {
453 if (is<SPText>(item) || is<SPFlowtext>(item)) {
454 // Scale by inverse of accumulated parent transform
455 SPCSSAttr *css_set = sp_repr_css_attr_new();
456 sp_repr_css_merge(css_set, css);
457 auto const local = item->i2doc_affine();
458 double const ex = local.descrim();
459 if (ex != 0.0 && ex != 1.0) {
460 sp_css_attr_scale(css_set, 1 / ex);
461 }
462 recursively_set_properties(item, css_set);
463 sp_repr_css_attr_unref(css_set);
464 }
465 }
466 } else {
467 // Apply css to selected inner objects.
468 sp_desktop_set_style(_desktop, css, true, false);
469 }
470}
471
473{
474 if constexpr (DEBUG_TEXT) {
475 std::cout << std::endl;
476 std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" << std::endl;
477 std::cout << "sp_text_fontfamily_value_changed: " << std::endl;
478 }
479
480 // quit if run by the _changed callbacks
481 if (_freeze) {
482 if constexpr (DEBUG_TEXT) {
483 std::cout << "sp_text_fontfamily_value_changed: frozen... return" << std::endl;
484 std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n" << std::endl;
485 }
486 return;
487 }
488 _freeze = true;
489
490 Glib::ustring new_family = _font_family_item->get_active_text();
491 css_font_family_unquote( new_family ); // Remove quotes around font family names.
492
493 // TODO: Think about how to handle handle multiple selections. While
494 // the font-family may be the same for all, the styles might be different.
495 // See: TextEdit::onApply() for example of looping over selected items.
496 auto fontlister = FontLister::get_instance();
497 if constexpr (DEBUG_TEXT) {
498 std::cout << " Old family: " << fontlister->get_font_family() << std::endl;
499 std::cout << " New family: " << new_family << std::endl;
500 std::cout << " Old active: " << fontlister->get_font_family_row() << std::endl;
501 // std::cout << " New active: " << act->active << std::endl;
502 }
503 if (new_family.compare(fontlister->get_font_family()) != 0) {
504 // Changed font-family
505
506 if (_font_family_item->get_active() == -1) {
507 // New font-family, not in document, not on system (could be fallback list)
508 fontlister->insert_font_family(new_family);
509
510 // This just sets a variable in the ComboBoxEntryAction object...
511 // shouldn't we also set the actual active row in the combobox?
512 _font_family_item->set_active(0); // New family is always at top of list.
513 }
514
515 fontlister->set_font_family( _font_family_item->get_active() );
516 // active text set in sp_text_toolbox_selection_changed()
517
518 auto css = sp_repr_css_attr_new();
519 fontlister->fill_css(css);
520
521 if (mergeDefaultStyle(css)) {
522 // If there is a selection, update
523 DocumentUndo::done(_desktop->getDocument(), _("Text: Change font family"), INKSCAPE_ICON("draw-text"));
524 }
526 }
527
528 // unfreeze
529 _freeze = false;
530
531 fontlister->add_document_fonts_at_top(_desktop->getDocument());
532
533 if constexpr (DEBUG_TEXT) {
534 std::cout << "sp_text_toolbox_fontfamily_changes: exit" << std::endl;
535 std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" << std::endl;
536 std::cout << std::endl;
537 }
538}
539
541{
542 // quit if run by the _changed callbacks
543 if (_freeze) {
544 return;
545 }
546 _freeze = true;
547
548 auto active_text = _font_size_item->get_active_text();
549 char const *text = active_text.c_str();
550 char *endptr;
551 double size = g_strtod(text, &endptr);
552 if (endptr == text) { // Conversion failed, non-numeric input.
553 g_warning("Conversion of size text to double failed, input: %s\n", text);
554 _freeze = false;
555 return;
556 }
557
558 auto prefs = Preferences::get();
559 int max_size = prefs->getInt("/dialogs/textandfont/maxFontSize", 10000); // somewhat arbitrary, but text&font preview freezes with too huge fontsizes
560
561 size = std::min<double>(size, max_size);
562
563 // Set css font size.
564 auto css = sp_repr_css_attr_new();
565 CSSOStringStream osfs;
566 int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
567 if (prefs->getBool("/options/font/textOutputPx", true)) {
569 } else {
570 osfs << size << sp_style_get_css_unit_string(unit);
571 }
572 sp_repr_css_set_property (css, "font-size", osfs.str().c_str());
573 double factor = size / selection_fontsize;
574
575 // Apply font size to selected objects.
577
578 auto const unit_lh = _tracker->getActiveUnit();
579 if (!is_relative(unit_lh) && _outer) {
580 double lineheight = _line_height_item.get_adjustment()->get_value();
581 _freeze = false;
582 _line_height_item.get_adjustment()->set_value(lineheight * factor);
583 _freeze = true;
584 }
585
586 if (mergeDefaultStyle(css)) {
587 DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:size", _("Text: Change font size"), INKSCAPE_ICON("draw-text"));
588 }
589
591
592 _freeze = false;
593}
594
596{
597 // quit if run by the _changed callbacks
598 if (_freeze) {
599 return;
600 }
601 _freeze = true;
602
603 Glib::ustring new_style = _font_style_item->get_active_text();
604
605 auto const fontlister = FontLister::get_instance();
606
607 if (new_style.compare(fontlister->get_font_style()) != 0) {
608
609 fontlister->set_font_style(new_style);
610 // active text set in sp_text_toolbox_seletion_changed()
611
612 auto css = sp_repr_css_attr_new();
613 fontlister->fill_css(css);
614
615 sp_desktop_set_style(_desktop, css, true, true);
616
617 if (mergeDefaultStyle(css)) {
618 DocumentUndo::done(_desktop->getDocument(), _("Text: Change font style"), INKSCAPE_ICON("draw-text"));
619 }
620
622 }
623
624 _freeze = false;
625}
626
627// Handles both Superscripts and Subscripts
629{
630 // quit if run by the _changed callbacks
631 if (_freeze) {
632 return;
633 }
634
635 _freeze = true;
636
637 // Called by Superscript or Subscript button?
638
639 if constexpr (DEBUG_TEXT) {
640 std::cout << "TextToolbar::script_changed: " << mode << std::endl;
641 }
642
643 // Query baseline
644 SPStyle query(_desktop->getDocument());
645 int result_baseline = sp_desktop_query_style(_desktop, &query, QUERY_STYLE_PROPERTY_BASELINES);
646
647 bool setSuper = false;
648 bool setSub = false;
649
650 if (is_query_style_updateable(result_baseline)) {
651 // If not set or mixed, turn on superscript or subscript
652 if (mode == 0) {
653 setSuper = true;
654 } else {
655 setSub = true;
656 }
657 } else {
658 // Superscript
659 bool superscriptSet = query.baseline_shift.set &&
662
663 // Subscript
664 bool subscriptSet = query.baseline_shift.set &&
667
668 setSuper = !superscriptSet && mode == 0;
669 setSub = !subscriptSet && mode == 1;
670 }
671
672 // Set css properties
674 if (setSuper || setSub) {
675 // Openoffice 2.3 and Adobe use 58%, Microsoft Word 2002 uses 65%, LaTex about 70%.
676 // 58% looks too small to me, especially if a superscript is placed on a superscript.
677 // If you make a change here, consider making a change to baseline-shift amount
678 // in style.cpp.
679 sp_repr_css_set_property (css, "font-size", "65%");
680 } else {
681 sp_repr_css_set_property (css, "font-size", "");
682 }
683 if( setSuper ) {
684 sp_repr_css_set_property (css, "baseline-shift", "super");
685 } else if( setSub ) {
686 sp_repr_css_set_property (css, "baseline-shift", "sub");
687 } else {
688 sp_repr_css_set_property (css, "baseline-shift", "baseline");
689 }
690
691 // Apply css to selected objects.
692 sp_desktop_set_style(_desktop, css, true, false);
693
694 // Save for undo
695 if (result_baseline != QUERY_STYLE_NOTHING) {
696 DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:script", _("Text: Change superscript or subscript"), INKSCAPE_ICON("draw-text"));
697 }
698
699 _freeze = false;
700}
701
703{
704 // quit if run by the _changed callbacks
705 if (_freeze) {
706 return;
707 }
708 _freeze = true;
709
710 Preferences::get()->setInt("/tools/text/align_mode", mode);
711
712 // move the x of all texts to preserve the same bbox
713 Selection *selection = _desktop->getSelection();
714 for (auto i : selection->items()) {
715 auto text = cast<SPText>(i);
716 // auto flowtext = cast<SPFlowtext>(i);
717 if (text) {
718 SPItem *item = i;
719
720 unsigned writing_mode = item->style->writing_mode.value;
721 // below, variable names suggest horizontal move, but we check the writing direction
722 // and move in the corresponding axis
723 Geom::Dim2 axis;
724 if (writing_mode == SP_CSS_WRITING_MODE_LR_TB || writing_mode == SP_CSS_WRITING_MODE_RL_TB) {
725 axis = Geom::X;
726 } else {
727 axis = Geom::Y;
728 }
729
731 if (!bbox)
732 continue;
733 double width = bbox->dimensions()[axis];
734 // If you want to align within some frame, other than the text's own bbox, calculate
735 // the left and right (or top and bottom for tb text) slacks of the text inside that
736 // frame (currently unused)
737 double left_slack = 0;
738 double right_slack = 0;
739 unsigned old_align = item->style->text_align.value;
740 double move = 0;
741 if (old_align == SP_CSS_TEXT_ALIGN_START || old_align == SP_CSS_TEXT_ALIGN_LEFT) {
742 switch (mode) {
743 case 0:
744 move = -left_slack;
745 break;
746 case 1:
747 move = width/2 + (right_slack - left_slack)/2;
748 break;
749 case 2:
750 move = width + right_slack;
751 break;
752 }
753 } else if (old_align == SP_CSS_TEXT_ALIGN_CENTER) {
754 switch (mode) {
755 case 0:
756 move = -width/2 - left_slack;
757 break;
758 case 1:
759 move = (right_slack - left_slack)/2;
760 break;
761 case 2:
762 move = width/2 + right_slack;
763 break;
764 }
765 } else if (old_align == SP_CSS_TEXT_ALIGN_END || old_align == SP_CSS_TEXT_ALIGN_RIGHT) {
766 switch (mode) {
767 case 0:
768 move = -width - left_slack;
769 break;
770 case 1:
771 move = -width/2 + (right_slack - left_slack)/2;
772 break;
773 case 2:
774 move = right_slack;
775 break;
776 }
777 }
778 Geom::Point XY = cast<SPText>(item)->attributes.firstXY();
779 if (axis == Geom::X) {
780 XY = XY + Geom::Point (move, 0);
781 } else {
782 XY = XY + Geom::Point (0, move);
783 }
784 cast<SPText>(item)->attributes.setFirstXY(XY);
785 item->updateRepr();
786 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
787 }
788 }
789
791 switch (mode)
792 {
793 case 0:
794 {
795 sp_repr_css_set_property (css, "text-anchor", "start");
796 sp_repr_css_set_property (css, "text-align", "start");
797 break;
798 }
799 case 1:
800 {
801 sp_repr_css_set_property (css, "text-anchor", "middle");
802 sp_repr_css_set_property (css, "text-align", "center");
803 break;
804 }
805
806 case 2:
807 {
808 sp_repr_css_set_property (css, "text-anchor", "end");
809 sp_repr_css_set_property (css, "text-align", "end");
810 break;
811 }
812
813 case 3:
814 {
815 sp_repr_css_set_property (css, "text-anchor", "start");
816 sp_repr_css_set_property (css, "text-align", "justify");
817 break;
818 }
819 }
820
821 if (mergeDefaultStyle(css)) {
822 DocumentUndo::done(_desktop->getDocument(), _("Text: Change alignment"), INKSCAPE_ICON("draw-text"));
823 }
825
826 onDefocus();
827
828 _freeze = false;
829}
830
832{
833 // quit if run by the _changed callbacks
834 if (_freeze) {
835 return;
836 }
837 _freeze = true;
838
839 Preferences::get()->setInt("/tools/text/writing_mode", mode);
840
841 auto css = sp_repr_css_attr_new();
842 switch (mode) {
843 case 0:
844 sp_repr_css_set_property (css, "writing-mode", "lr-tb");
845 break;
846 case 1:
847 sp_repr_css_set_property (css, "writing-mode", "tb-rl");
848 break;
849 case 2:
850 sp_repr_css_set_property (css, "writing-mode", "vertical-lr");
851 break;
852 default:
853 break;
854 }
855
856 if (mergeDefaultStyle(css)) {
857 DocumentUndo::done(_desktop->getDocument(), _("Text: Change writing mode"), INKSCAPE_ICON("draw-text"));
858 }
860
861 onDefocus();
862
863 _freeze = false;
864}
865
867{
868 // quit if run by the _changed callbacks
869 if (_freeze) {
870 return;
871 }
872 _freeze = true;
873
874 Preferences::get()->setInt("/tools/text/orientation_mode", mode);
875
876 auto css = sp_repr_css_attr_new();
877 switch (mode) {
878 case 0:
879 sp_repr_css_set_property(css, "text-orientation", "auto");
880 break;
881 case 1:
882 sp_repr_css_set_property(css, "text-orientation", "upright");
883 break;
884 case 2:
885 sp_repr_css_set_property(css, "text-orientation", "sideways");
886 break;
887 default:
888 break;
889 }
890
891 if (mergeDefaultStyle(css)) {
892 DocumentUndo::done(_desktop->getDocument(), _("Text: Change orientation"), INKSCAPE_ICON("draw-text"));
893 }
895 onDefocus();
896
897 _freeze = false;
898}
899
901{
902 // quit if run by the _changed callbacks
903 if (_freeze) {
904 return;
905 }
906 _freeze = true;
907
908 Preferences::get()->setInt("/tools/text/direction_mode", mode);
909
910 auto css = sp_repr_css_attr_new();
911 switch (mode) {
912 case 0:
913 sp_repr_css_set_property (css, "direction", "ltr");
914 break;
915 case 1:
916 sp_repr_css_set_property (css, "direction", "rtl");
917 break;
918 default:
919 break;
920 }
921
922 if (mergeDefaultStyle(css)) {
923 DocumentUndo::done(_desktop->getDocument(), _("Text: Change direction"), INKSCAPE_ICON("draw-text"));
924 }
926
927 onDefocus();
928
929 _freeze = false;
930}
931
933{
934 // quit if run by the _changed callbacks or is not text tool
936 return;
937 }
938
939 _freeze = true;
940
941 // Get user selected unit and save as preference
942 auto const unit = _tracker->getActiveUnit();
943
944 // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit so
945 // we can save it (allows us to adjust line height value when unit changes).
946
947 // Set css line height.
948 auto css = sp_repr_css_attr_new();
949 CSSOStringStream osfs;
950 if (is_relative(unit)) {
951 osfs << _line_height_item.get_adjustment()->get_value() << unit->abbr;
952 } else {
953 // Inside SVG file, always use "px" for absolute units.
954 osfs << Quantity::convert(_line_height_item.get_adjustment()->get_value(), unit, "px") << "px";
955 }
956
957 sp_repr_css_set_property (css, "line-height", osfs.str().c_str());
958
959 auto selection = _desktop->getSelection();
960 auto itemlist = selection->items();
961 if (_outer) {
962 // Special else makes this different from other uses of text_outer_set_style
964 } else {
965 auto parent = itemlist.front();
966 SPStyle *parent_style = parent->style;
967 SPCSSAttr *parent_cssatr = sp_css_attr_from_style(parent_style, SP_STYLE_FLAG_IFSET);
968 Glib::ustring parent_lineheight = sp_repr_css_property(parent_cssatr, "line-height", "1.25");
970 sp_repr_css_set_property(cssfit, "line-height", parent_lineheight.c_str());
971 double minheight = 0;
972 if (parent_style) {
973 minheight = parent_style->line_height.computed;
974 }
975 if (minheight) {
976 for (auto i : parent->childList(false)) {
977 auto child = cast<SPItem>(i);
978 if (!child) {
979 continue;
980 }
981 recursively_set_properties(child, cssfit);
982 }
983 }
984 sp_repr_css_set_property(cssfit, "line-height", "0");
985 parent->changeCSS(cssfit, "style");
987 sp_desktop_set_style(_desktop, css, true, true);
990 }
991 // Only need to save for undo if a text item has been changed.
992 itemlist = selection->items();
993 bool modified = false;
994 for (auto item : itemlist) {
995 if (is<SPText>(item) || is<SPFlowtext>(item)) {
996 modified = true;
997 break;
998 }
999 }
1000
1001 // Save for undo
1002 if (modified) {
1003 // Call ensureUpToDate() causes rebuild of text layout (with all proper style
1004 // cascading, etc.). For multi-line text with sodipodi::role="line", we must explicitly
1005 // save new <tspan> 'x' and 'y' attribute values by calling updateRepr().
1006 // Partial fix for bug #1590141.
1007
1009 for (auto item : itemlist) {
1010 if (is<SPText>(item) || is<SPFlowtext>(item)) {
1011 item->updateRepr();
1012 }
1013 }
1014 if (!_outer) {
1015 prepare_inner();
1016 }
1017 DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:line-height", _("Text: Change line-height"), INKSCAPE_ICON("draw-text"));
1018 }
1019
1021
1023
1024 _freeze = false;
1025}
1026
1034{
1035 // If no selected objects, set default.
1036 auto query = SPStyle{_desktop->getDocument()};
1038 if (result_numbers == QUERY_STYLE_NOTHING) {
1039 Preferences::get()->mergeStyle("/tools/text/style", css);
1040 }
1041 // This updates the global style
1042 sp_desktop_set_style(_desktop, css, true, true);
1043 return result_numbers != QUERY_STYLE_NOTHING;
1044}
1045
1047{
1048 // quit if run by the _changed callbacks or is not text tool
1050 return;
1051 }
1052 _freeze = true;
1053
1054 // Get old saved unit
1055 int old_unit = _lineheight_unit;
1056
1057 // Get user selected unit and save as preference
1058 auto const unit = _tracker->getActiveUnit();
1059
1060 // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit.
1061 SPILength temp_length;
1062 CSSOStringStream temp_stream;
1063 temp_stream << 1 << unit->abbr;
1064 temp_length.read(temp_stream.str().c_str());
1065 Preferences::get()->setInt("/tools/text/lineheight/display_unit", temp_length.unit);
1066 if (old_unit == temp_length.unit) {
1067 _freeze = false;
1068 return;
1069 } else {
1070 _lineheight_unit = temp_length.unit;
1071 }
1072
1073 // Read current line height value
1074 auto line_height_adj = _line_height_item.get_adjustment();
1075 double line_height = line_height_adj->get_value();
1077 Selection *selection = desktop->getSelection();
1078 auto itemlist = selection->items();
1079
1080 // Convert between units
1081 double font_size = 0;
1082 double doc_scale = 1;
1083 int count = 0;
1084
1085 for (auto item : itemlist) {
1086 if (is<SPText>(item) || is<SPFlowtext>(item)) {
1087 doc_scale = Geom::Affine(item->i2dt_affine()).descrim();
1088 font_size += item->style->font_size.computed * doc_scale;
1089 ++count;
1090 }
1091 }
1092 if (count > 0) {
1093 font_size /= count;
1094 } else {
1095 // ideally use default font-size.
1096 font_size = 20;
1097 }
1098 if ((unit->abbr == "" || unit->abbr == "em") && (old_unit == SP_CSS_UNIT_NONE || old_unit == SP_CSS_UNIT_EM)) {
1099 // Do nothing
1100 } else if ((unit->abbr == "" || unit->abbr == "em") && old_unit == SP_CSS_UNIT_EX) {
1101 line_height *= 0.5;
1102 } else if ((unit->abbr) == "ex" && (old_unit == SP_CSS_UNIT_EM || old_unit == SP_CSS_UNIT_NONE)) {
1103 line_height *= 2.0;
1104 } else if ((unit->abbr == "" || unit->abbr == "em") && old_unit == SP_CSS_UNIT_PERCENT) {
1105 line_height /= 100.0;
1106 } else if ((unit->abbr) == "%" && (old_unit == SP_CSS_UNIT_EM || old_unit == SP_CSS_UNIT_NONE)) {
1107 line_height *= 100;
1108 } else if ((unit->abbr) == "ex" && old_unit == SP_CSS_UNIT_PERCENT) {
1109 line_height /= 50.0;
1110 } else if ((unit->abbr) == "%" && old_unit == SP_CSS_UNIT_EX) {
1111 line_height *= 50;
1112 } else if (is_relative(unit)) {
1113 // Convert absolute to relative... for the moment use average font-size
1114 if (old_unit == SP_CSS_UNIT_NONE) old_unit = SP_CSS_UNIT_EM;
1115 line_height = Quantity::convert(line_height, sp_style_get_css_unit_string(old_unit), "px");
1116
1117 if (font_size > 0) {
1118 line_height /= font_size;
1119 }
1120 if (unit->abbr == "%") {
1121 line_height *= 100;
1122 } else if (unit->abbr == "ex") {
1123 line_height *= 2;
1124 }
1125 } else if (old_unit == SP_CSS_UNIT_NONE || old_unit == SP_CSS_UNIT_PERCENT || old_unit == SP_CSS_UNIT_EM ||
1126 old_unit == SP_CSS_UNIT_EX) {
1127 // Convert relative to absolute... for the moment use average font-size
1128 if (old_unit == SP_CSS_UNIT_PERCENT) {
1129 line_height /= 100.0;
1130 } else if (old_unit == SP_CSS_UNIT_EX) {
1131 line_height /= 2.0;
1132 }
1133 line_height *= font_size;
1134 line_height = Quantity::convert(line_height, "px", unit);
1135 } else {
1136 // Convert between different absolute units (only used in GUI)
1137 line_height = Quantity::convert(line_height, sp_style_get_css_unit_string(old_unit), unit);
1138 }
1139 // Set css line height.
1140 auto css = sp_repr_css_attr_new();
1141 CSSOStringStream osfs;
1142 // Set css line height.
1143 if ( is_relative(unit) ) {
1144 osfs << line_height << unit->abbr;
1145 } else {
1146 osfs << Quantity::convert(line_height, unit, "px") << "px";
1147 }
1148 sp_repr_css_set_property(css, "line-height", osfs.str().c_str());
1149
1150 // Update GUI with line_height value.
1151 line_height_adj->set_value(line_height);
1152 // Update "climb rate" The custom action has a step property but no way to set it.
1153 if (unit->abbr == "%") {
1154 line_height_adj->set_step_increment(1.0);
1155 line_height_adj->set_page_increment(10.0);
1156 } else {
1157 line_height_adj->set_step_increment(0.1);
1158 line_height_adj->set_page_increment(1.0);
1159 }
1160 // Internal function to set line-height which is spacing mode dependent.
1161 SPItem *parent = itemlist.empty() ? nullptr : itemlist.front();
1162 SPStyle *parent_style = nullptr;
1163 if (parent) {
1164 parent_style = parent->style;
1165 }
1166 bool inside = false;
1167 if (_outer) {
1168 if (!selection->singleItem() || !parent_style || parent_style->line_height.computed != 0) {
1169 for (auto item : itemlist) {
1170 if (is<SPText>(item) || is<SPFlowtext>(item)) {
1171 // Scale by inverse of accumulated parent transform
1172 auto css_set = sp_repr_css_attr_new();
1173 sp_repr_css_merge(css_set, css);
1174 auto const local = item->i2doc_affine();
1175 double const ex = local.descrim();
1176 if (ex != 0.0 && ex != 1.0) {
1177 sp_css_attr_scale(css_set, 1 / ex);
1178 }
1179 recursively_set_properties(item, css_set);
1180 sp_repr_css_attr_unref(css_set);
1181 }
1182 }
1183 } else {
1184 inside = true;
1185 }
1186 }
1187 if (!_outer || inside) {
1188 SPCSSAttr *parent_cssatr = sp_css_attr_from_style(parent_style, SP_STYLE_FLAG_IFSET);
1189 Glib::ustring parent_lineheight = sp_repr_css_property(parent_cssatr, "line-height", "1.25");
1190 SPCSSAttr *cssfit = sp_repr_css_attr_new();
1191 sp_repr_css_set_property(cssfit, "line-height", parent_lineheight.c_str());
1192 double minheight = 0;
1193 if (parent_style) {
1194 minheight = parent_style->line_height.computed;
1195 }
1196 if (minheight) {
1197 for (auto child : parent->childList(false)) {
1198 if (is<SPItem>(child)) {
1199 recursively_set_properties(child, cssfit);
1200 }
1201 }
1202 }
1203 sp_repr_css_set_property(cssfit, "line-height", "0");
1204 parent->changeCSS(cssfit, "style");
1206 sp_desktop_set_style(desktop, css, true, true);
1208 sp_repr_css_attr_unref(cssfit);
1209 }
1210 itemlist = selection->items();
1211 // Only need to save for undo if a text item has been changed.
1212 bool modified = false;
1213 for (auto item : itemlist) {
1214 if (is<SPText>(item) || is<SPFlowtext>(item)) {
1215 modified = true;
1216 break;
1217 }
1218 }
1219 // Save for undo
1220 if (modified) {
1221 // Call ensureUpToDate() causes rebuild of text layout (with all proper style
1222 // cascading, etc.). For multi-line text with sodipodi::role="line", we must explicitly
1223 // save new <tspan> 'x' and 'y' attribute values by calling updateRepr().
1224 // Partial fix for bug #1590141.
1225
1227 for (auto item : itemlist) {
1228 if (is<SPText>(item) || is<SPFlowtext>(item)) {
1229 item->updateRepr();
1230 }
1231 }
1232 if (_outer) {
1233 prepare_inner();
1234 }
1235 DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:line-height", _("Text: Change line-height unit"), INKSCAPE_ICON("draw-text"));
1236 }
1237
1239
1241
1242 _freeze = false;
1243}
1244
1246{
1247 // quit if run by the _changed callbacks
1248 auto const unit = _tracker_fs->getActiveUnit();
1249
1250 // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit.
1251 SPILength temp_size;
1252 CSSOStringStream temp_size_stream;
1253 temp_size_stream << 1 << unit->abbr;
1254 temp_size.read(temp_size_stream.str().c_str());
1255 Preferences::get()->setInt("/options/font/unitType", temp_size.unit);
1256}
1257
1259{
1260 // quit if run by the _changed callbacks
1261 if (_freeze) {
1262 return;
1263 }
1264 _freeze = true;
1265
1266 // At the moment this handles only numerical values (i.e. no em unit).
1267 // Set css word-spacing
1269 CSSOStringStream osfs;
1270 osfs << _word_spacing_item.get_adjustment()->get_value() << "px"; // For now always use px
1271 sp_repr_css_set_property (css, "word-spacing", osfs.str().c_str());
1273
1274 if (mergeDefaultStyle(css)) {
1275 DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:word-spacing", _("Text: Change word-spacing"), INKSCAPE_ICON("draw-text"));
1276 }
1277
1279
1280 _freeze = false;
1281}
1282
1284{
1285 // quit if run by the _changed callbacks
1286 if (_freeze) {
1287 return;
1288 }
1289 _freeze = true;
1290
1291 // At the moment this handles only numerical values (i.e. no em unit).
1292 // Set css letter-spacing
1293 auto css = sp_repr_css_attr_new();
1294 CSSOStringStream osfs;
1295 osfs << _letter_spacing_item.get_adjustment()->get_value() << "px"; // For now always use px
1296 sp_repr_css_set_property(css, "letter-spacing", osfs.str().c_str());
1298
1299 if (mergeDefaultStyle(css)) {
1300 DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:letter-spacing", _("Text: Change letter-spacing"), INKSCAPE_ICON("draw-text"));
1301 }
1302
1304
1305 _freeze = false;
1306}
1307
1309{
1310 // quit if run by the _changed callbacks
1311 if (_freeze) {
1312 return;
1313 }
1314 _freeze = true;
1315
1316 double new_dx = _dx_item.get_adjustment()->get_value();
1317 bool modified = false;
1318
1319 if (auto tc = SP_TEXT_CONTEXT(_desktop->getTool())) {
1320 unsigned char_index = -1;
1321 if (auto attributes = text_tag_attributes_at_position(tc->textItem(), std::min(tc->text_sel_start, tc->text_sel_end), &char_index)) {
1322 double old_dx = attributes->getDx(char_index);
1323 double delta_dx = new_dx - old_dx;
1324 sp_te_adjust_dx(tc->textItem(), tc->text_sel_start, tc->text_sel_end, _desktop, delta_dx);
1325 modified = true;
1326 }
1327 }
1328
1329 if (modified) {
1330 DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:dx", _("Text: Change dx (kern)"), INKSCAPE_ICON("draw-text"));
1331 }
1332
1333 _freeze = false;
1334}
1335
1337{
1338 // quit if run by the _changed callbacks
1339 if (_freeze) {
1340 return;
1341 }
1342 _freeze = true;
1343
1344 double new_dy = _dy_item.get_adjustment()->get_value();
1345 bool modified = false;
1346
1347 if (auto tc = SP_TEXT_CONTEXT(_desktop->getTool())) {
1348 unsigned char_index = -1;
1349 if (auto attributes = text_tag_attributes_at_position(tc->textItem(), std::min(tc->text_sel_start, tc->text_sel_end), &char_index)) {
1350 double old_dy = attributes->getDy(char_index);
1351 double delta_dy = new_dy - old_dy;
1352 sp_te_adjust_dy(tc->textItem(), tc->text_sel_start, tc->text_sel_end, _desktop, delta_dy);
1353 modified = true;
1354 }
1355 }
1356
1357 if (modified) {
1358 DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:dy", _("Text: Change dy"), INKSCAPE_ICON("draw-text"));
1359 }
1360
1361 _freeze = false;
1362}
1363
1365{
1366 // quit if run by the _changed callbacks
1367 if (_freeze) {
1368 return;
1369 }
1370 _freeze = true;
1371
1372 double new_degrees = _rotation_item.get_adjustment()->get_value();
1373
1374 bool modified = false;
1375 if (auto tc = SP_TEXT_CONTEXT(_desktop->getTool())) {
1376 unsigned char_index = -1;
1377 if (auto attributes = text_tag_attributes_at_position(tc->textItem(), std::min(tc->text_sel_start, tc->text_sel_end), &char_index)) {
1378 double old_degrees = attributes->getRotate(char_index);
1379 double delta_deg = new_degrees - old_degrees;
1380 sp_te_adjust_rotation(tc->textItem(), tc->text_sel_start, tc->text_sel_end, _desktop, delta_deg);
1381 modified = true;
1382 }
1383 }
1384
1385 if (modified) {
1386 DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:rotate", _("Text: Change rotate"), INKSCAPE_ICON("draw-text"));
1387 }
1388
1389 _freeze = false;
1390}
1391
1392void TextToolbar::_selectionChanged(Selection *selection) // don't bother to update font list if subsel changed
1393{
1394 static int count = 0; // for DEBUG_TEXT
1395
1396 if constexpr (DEBUG_TEXT) {
1397 ++count;
1398 std::cout << std::endl;
1399 std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
1400 std::cout << "sp_text_toolbox_selection_changed: start " << count << std::endl;
1401 }
1402
1403 // quit if run by the _changed callbacks
1404 if (_freeze) {
1405 if constexpr (DEBUG_TEXT) {
1406 std::cout << " Frozen, returning" << std::endl;
1407 std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
1408 std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
1409 std::cout << std::endl;
1410 }
1411 return;
1412 }
1413 _freeze = true;
1414
1415 // selection defined as argument but not used, argh!!!
1417 SPDocument *document = _desktop->getDocument();
1418 selection = desktop->getSelection();
1419 auto itemlist = selection->items();
1420
1421 if constexpr (DEBUG_TEXT) {
1422 for (auto i : itemlist) {
1423 std::cout << " " << i->getId() << std::endl;
1424 }
1425 if (auto text_tool = dynamic_cast<Tools::TextTool const *>(_desktop->getTool())) {
1426 auto const selected_text = get_selected_text(*text_tool);
1427 std::cout << " Selected text: |" << selected_text << "|" << std::endl;
1428 }
1429 }
1430
1431 // Only flowed text can be justified, only normal text can be kerned...
1432 // Find out if we have flowed text now so we can use it several places
1433 bool isFlow = false;
1434 std::vector<SPItem *> to_work;
1435 for (auto i : itemlist) {
1436 auto text = cast<SPText>(i);
1437 auto flowtext = cast<SPFlowtext>(i);
1438 if (text || flowtext) {
1439 to_work.push_back(i);
1440 }
1441 if (flowtext ||
1442 (text && text->style && text->style->shape_inside.set)) {
1443 isFlow = true;
1444 }
1445 }
1446 bool outside = false;
1447 if (selection && to_work.empty()) {
1448 outside = true;
1449 }
1450
1451 auto fontlister = FontLister::get_instance();
1452 fontlister->selection_update();
1453 // Update font list, but only if widget already created.
1454 _font_family_item->set_active_text(fontlister->get_font_family().c_str(), fontlister->get_font_family_row());
1455 _font_style_item->set_active_text(fontlister->get_font_style().c_str());
1456
1457 /*
1458 * Query from current selection:
1459 * Font family (font-family)
1460 * Style (font-weight, font-style, font-stretch, font-variant, font-align)
1461 * Numbers (font-size, letter-spacing, word-spacing, line-height, text-anchor, writing-mode)
1462 * Font specification (Inkscape private attribute)
1463 */
1464 SPStyle query(document);
1465 SPStyle query_fallback(document);
1468 int result_baseline = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_BASELINES);
1470
1471 // Calling sp_desktop_query_style will result in a call to TextTool::_styleQueried().
1472 // This returns the style of the selected text inside the <text> element... which
1473 // is often the style of one or more <tspan>s. If we want the style of the outer
1474 // <text> objects then we need to bypass the call to TextTool::_styleQueried().
1475 // The desktop selection never includes the elements inside the <text> element.
1476 int result_numbers = 0;
1477 int result_numbers_fallback = 0;
1478 if (!outside) {
1479 if (_outer && _sub_active_item) {
1480 auto parent = cast<SPItem>(_sub_active_item->parent);
1481 result_numbers = objects_query_fontnumbers({_sub_active_item}, &query);
1482 result_numbers_fallback = objects_query_fontnumbers({parent}, &query_fallback);
1483 } else if (_outer) {
1484 result_numbers = objects_query_fontnumbers(to_work, &query);
1485 } else {
1487 }
1488 } else {
1490 }
1491
1492 auto prefs = Preferences::get();
1493 /*
1494 * If no text in selection (querying returned nothing), read the style from
1495 * the /tools/text preferences (default style for new texts). Return if
1496 * tool bar already set to these preferences.
1497 */
1498 if (result_family == QUERY_STYLE_NOTHING ||
1499 result_style == QUERY_STYLE_NOTHING ||
1500 result_numbers == QUERY_STYLE_NOTHING ||
1501 result_wmode == QUERY_STYLE_NOTHING)
1502 {
1503 // There are no texts in selection, read from preferences.
1504 if (prefs->getBool("/tools/text/usecurrent")) {
1506 } else {
1507 query.readFromPrefs("/tools/text");
1508 }
1509
1510 if constexpr (DEBUG_TEXT) {
1511 std::cout << " read style from prefs:" << std::endl;
1512 std::cout << " Family set? " << query.font_family.set
1513 << " Style set? " << query.font_style.set
1514 << " FontSpec set? " << query.font_specification.set
1515 << std::endl;
1516 }
1518 // Do not reset the toolbar style from prefs if we already did it last time
1519 _freeze = false;
1520 if constexpr (DEBUG_TEXT) {
1521 std::cout << " text_style_from_prefs: toolbar already set" << std:: endl;
1522 std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
1523 std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
1524 std::cout << std::endl;
1525 }
1526 return;
1527 }
1528
1529 // To ensure the value of the combobox is properly set on start-up, only mark
1530 // the prefs set if the combobox has already been constructed.
1532 } else {
1533 _text_style_from_prefs = false;
1534 }
1535
1536 // If we have valid query data for text (font-family, font-specification) set toolbar accordingly.
1537 {
1538 // Size (average of text selected)
1539
1540 int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
1541 double size = 0;
1544 }
1545 if (!size && result_numbers != QUERY_STYLE_NOTHING) {
1546 size = sp_style_css_size_px_to_units(query.font_size.computed, unit);
1547 }
1548 if (!size && result_numbers_fallback != QUERY_STYLE_NOTHING) {
1549 size = sp_style_css_size_px_to_units(query_fallback.font_size.computed, unit);
1550 }
1551 if (!size && _text_style_from_prefs) {
1552 size = sp_style_css_size_px_to_units(query.font_size.computed, unit);
1553 }
1554
1555 auto unit_str = sp_style_get_css_unit_string(unit);
1556 Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", unit_str, ")");
1557
1558 _font_size_item->set_tooltip(tooltip.c_str());
1559
1561 // We don't want to parse values just show
1562
1563 _tracker_fs->setActiveUnitByAbbr(sp_style_get_css_unit_string(unit));
1564 int rounded_size = std::round(size);
1565 if (std::abs((size - rounded_size)/size) < 0.0001) {
1566 // We use rounded_size to avoid rounding errors when, say, converting stored 'px' values to displayed 'pt' values.
1567 os << rounded_size;
1568 selection_fontsize = rounded_size;
1569 } else {
1570 os << size;
1572 }
1573
1574 // Freeze to ignore callbacks.
1575 //g_object_freeze_notify( G_OBJECT( fontSizeAction->combobox ) );
1576 _font_size_item->set_model(create_sizes_store(unit));
1577 //g_object_thaw_notify( G_OBJECT( fontSizeAction->combobox ) );
1578
1579 _font_size_item->set_active_text( os.str().c_str() );
1580
1581 // Superscript
1582 bool superscriptSet =
1583 ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) &&
1584 query.baseline_shift.set &&
1587
1588 _superscript_btn.set_active(superscriptSet);
1589
1590 // Subscript
1591 bool subscriptSet =
1592 ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) &&
1593 query.baseline_shift.set &&
1595 query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUB );
1596
1597 _subscript_btn.set_active(subscriptSet);
1598
1599 // Alignment
1600
1601 // Note: SVG 1.1 doesn't include text-align, SVG 1.2 Tiny doesn't include text-align="justify"
1602 // text-align="justify" was a draft SVG 1.2 item (along with flowed text).
1603 // Only flowed text can be left and right justified at the same time.
1604 // Disable button if we don't have flowed text.
1605
1606 _alignment_buttons[3]->set_sensitive(isFlow);
1607
1608 int activeButton = 0;
1609 if (query.text_align.computed == SP_CSS_TEXT_ALIGN_START || query.text_align.computed == SP_CSS_TEXT_ALIGN_LEFT) {
1610 activeButton = 0;
1611 } else if (query.text_align.computed == SP_CSS_TEXT_ALIGN_CENTER) {
1612 activeButton = 1;
1613 } else if (query.text_align.computed == SP_CSS_TEXT_ALIGN_END || query.text_align.computed == SP_CSS_TEXT_ALIGN_RIGHT) {
1614 activeButton = 2;
1615 } else if (query.text_align.computed == SP_CSS_TEXT_ALIGN_JUSTIFY) {
1616 activeButton = 3;
1617 }
1618 _alignment_buttons[activeButton]->set_active(true);
1619
1620 double height = 0;
1621 gint line_height_unit = 0;
1622
1625 line_height_unit = _query_cursor.line_height.unit;
1626 }
1627
1628 if (!height && result_numbers != QUERY_STYLE_NOTHING) {
1629 height = query.line_height.value;
1630 line_height_unit = query.line_height.unit;
1631 }
1632
1633 if (!height && result_numbers_fallback != QUERY_STYLE_NOTHING) {
1634 height = query_fallback.line_height.value;
1635 line_height_unit = query_fallback.line_height.unit;
1636 }
1637
1639 height = query.line_height.value;
1640 line_height_unit = query.line_height.unit;
1641 }
1642
1643 if (line_height_unit == SP_CSS_UNIT_PERCENT) {
1644 height *= 100.0; // Inkscape store % as fraction in .value
1645 }
1646
1647 // We dot want to parse values just show
1648 if (!is_relative(SPCSSUnit(line_height_unit))) {
1649 int curunit = prefs->getInt("/tools/text/lineheight/display_unit", 1);
1650 // For backwards comaptibility
1651 if (is_relative(SPCSSUnit(curunit))) {
1652 prefs->setInt("/tools/text/lineheight/display_unit", 1);
1653 curunit = 1;
1654 }
1655 height = Quantity::convert(height, "px", sp_style_get_css_unit_string(curunit));
1656 line_height_unit = curunit;
1657 }
1658 auto line_height_adj = _line_height_item.get_adjustment();
1659 line_height_adj->set_value(height);
1660
1661 // Update "climb rate"
1662 if (line_height_unit == SP_CSS_UNIT_PERCENT) {
1663 line_height_adj->set_step_increment(1.0);
1664 line_height_adj->set_page_increment(10.0);
1665 } else {
1666 line_height_adj->set_step_increment(0.1);
1667 line_height_adj->set_page_increment(1.0);
1668 }
1669
1670 if (line_height_unit == SP_CSS_UNIT_NONE) {
1671 // Function 'sp_style_get_css_unit_string' returns 'px' for unit none.
1672 // We need to avoid this.
1673 _tracker->setActiveUnitByAbbr("");
1674 } else {
1675 _tracker->setActiveUnitByAbbr(sp_style_get_css_unit_string(line_height_unit));
1676 }
1677
1678 // Save unit so we can do conversions between new/old units.
1679 _lineheight_unit = line_height_unit;
1680 // Word spacing
1681 double wordSpacing;
1682 if (query.word_spacing.normal) {
1683 wordSpacing = 0.0;
1684 } else {
1685 wordSpacing = query.word_spacing.computed; // Assume no units (change in desktop-style.cpp)
1686 }
1687
1688 _word_spacing_item.get_adjustment()->set_value(wordSpacing);
1689
1690 // Letter spacing
1691 double letterSpacing;
1692 if (query.letter_spacing.normal) {
1693 letterSpacing = 0.0;
1694 } else {
1695 letterSpacing = query.letter_spacing.computed; // Assume no units (change in desktop-style.cpp)
1696 }
1697
1698 _letter_spacing_item.get_adjustment()->set_value(letterSpacing);
1699
1700 // Writing mode
1701 int activeButton2 = 0;
1702 if (query.writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) activeButton2 = 0;
1703 if (query.writing_mode.computed == SP_CSS_WRITING_MODE_TB_RL) activeButton2 = 1;
1704 if (query.writing_mode.computed == SP_CSS_WRITING_MODE_TB_LR) activeButton2 = 2;
1705
1706 _writing_buttons[activeButton2]->set_active(true);
1707
1708 // Orientation
1709 int activeButton3 = 0;
1710 if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_MIXED ) activeButton3 = 0;
1711 if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_UPRIGHT ) activeButton3 = 1;
1712 if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_SIDEWAYS) activeButton3 = 2;
1713
1714 _orientation_buttons[activeButton3]->set_active(true);
1715
1716 // Disable text orientation for horizontal text...
1717 for (auto btn : _orientation_buttons) {
1718 btn->set_sensitive(activeButton2 != 0);
1719 }
1720
1721 // Direction
1722 int activeButton4 = 0;
1723 if (query.direction.computed == SP_CSS_DIRECTION_LTR) activeButton4 = 0;
1724 if (query.direction.computed == SP_CSS_DIRECTION_RTL) activeButton4 = 1;
1725 _direction_buttons[activeButton4]->set_active(true);
1726 }
1727
1728 if constexpr (DEBUG_TEXT) {
1729 std::cout << " GUI: fontfamily.value: " << query.font_family.value() << std::endl;
1730 std::cout << " GUI: font_size.computed: " << query.font_size.computed << std::endl;
1731 std::cout << " GUI: font_weight.computed: " << query.font_weight.computed << std::endl;
1732 std::cout << " GUI: font_style.computed: " << query.font_style.computed << std::endl;
1733 std::cout << " GUI: text_anchor.computed: " << query.text_anchor.computed << std::endl;
1734 std::cout << " GUI: text_align.computed: " << query.text_align.computed << std::endl;
1735 std::cout << " GUI: line_height.computed: " << query.line_height.computed
1736 << " line_height.value: " << query.line_height.value
1737 << " line_height.unit: " << query.line_height.unit << std::endl;
1738 std::cout << " GUI: word_spacing.computed: " << query.word_spacing.computed
1739 << " word_spacing.value: " << query.word_spacing.value
1740 << " word_spacing.unit: " << query.word_spacing.unit << std::endl;
1741 std::cout << " GUI: letter_spacing.computed: " << query.letter_spacing.computed
1742 << " letter_spacing.value: " << query.letter_spacing.value
1743 << " letter_spacing.unit: " << query.letter_spacing.unit << std::endl;
1744 std::cout << " GUI: writing_mode.computed: " << query.writing_mode.computed << std::endl;
1745 }
1746
1747 // Kerning (xshift), yshift, rotation. NB: These are not CSS attributes.
1748 if (auto tc = SP_TEXT_CONTEXT(_desktop->getTool())) {
1749 unsigned char_index = -1;
1750 if (auto attributes = text_tag_attributes_at_position(tc->textItem(), std::min(tc->text_sel_start, tc->text_sel_end), &char_index)) {
1751 // Dx
1752 double dx = attributes->getDx(char_index);
1753 _dx_item.get_adjustment()->set_value(dx);
1754
1755 // Dy
1756 double dy = attributes->getDy(char_index);
1757 _dy_item.get_adjustment()->set_value(dy);
1758
1759 // Rotation
1760 double rotation = attributes->getRotate(char_index);
1761 /* SVG value is between 0 and 360 but we're using -180 to 180 in widget */
1762 if (rotation > 180.0) {
1763 rotation -= 360.0;
1764 }
1765 _rotation_item.get_adjustment()->set_value(rotation);
1766
1767 if constexpr (DEBUG_TEXT) {
1768 std::cout << " GUI: Dx: " << dx << std::endl;
1769 std::cout << " GUI: Dy: " << dy << std::endl;
1770 std::cout << " GUI: Rotation: " << rotation << std::endl;
1771 }
1772 }
1773 }
1774
1775 // Set these here as we don't always have kerning/rotating attributes
1776 _dx_item.set_sensitive(!isFlow);
1777 _dy_item.set_sensitive(!isFlow);
1778 _rotation_item.set_sensitive(!isFlow);
1779
1780 if constexpr (DEBUG_TEXT) {
1781 std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
1782 std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
1783 std::cout << std::endl;
1784 }
1785
1786 _freeze = false;
1787}
1788
1790{
1791 _sub_active_item = nullptr;
1792 _selectionChanged(selection);
1793}
1794
1796{
1797 if (auto tc = SP_TEXT_CONTEXT(_desktop->getTool())) {
1798 _updating = true;
1799 if (te_get_layout(tc->textItem())) {
1800 std::swap(tc->text_sel_start, wrap_start);
1801 std::swap(tc->text_sel_end, wrap_end);
1802 }
1803 _updating = start;
1804 }
1805}
1806
1807/*
1808* This function parses the just created line height in one or more lines of a text subselection.
1809* It can describe 2 kinds of input because when we store a text element we apply a fallback that change
1810* structure. This visually is not reflected but user maybe want to change a part of this subselection
1811* once the fallback is created, so we need more complex logic here to fill the gap.
1812* Basically, we have a line height changed in the new wrapper element/s between wrap_start and wrap_end.
1813* These variables store starting iterator of first char in line and last char in line in a subselection.
1814* These elements are styled well but we can have orphaned text nodes before and after the subselection.
1815* So, normally 3 elements are inside a container as direct child of a text element.
1816* We need to apply the container style to the optional first and last text nodes,
1817* wrapping into a new element that gets the container style (this is not part to the sub-selection).
1818* After wrapping, we unindent all children of the container and remove the container.
1819*
1820*/
1822{
1823 auto const tc = SP_TEXT_CONTEXT(_desktop->getTool());
1824 if (!tc) {
1825 return;
1826 }
1827 auto const layout = te_get_layout(tc->textItem());
1828 if (!layout) {
1829 return;
1830 }
1831 auto doc = _desktop->getDocument();
1832 auto spobject = tc->textItem();
1833 auto spitem = tc->textItem();
1834 auto text = cast<SPText>(tc->textItem());
1835 auto flowtext = cast<SPFlowtext>(tc->textItem());
1836 XML::Document *xml_doc = doc->getReprDoc();
1837 if (!spobject) {
1838 return;
1839 }
1840
1841 // We check for external files with text nodes direct children of text element
1842 // and wrap it into a tspan elements as inkscape do.
1843 if (text) {
1844 bool changed = false;
1845 std::vector<SPObject *> childs = spitem->childList(false);
1846 for (auto child : childs) {
1847 auto spstring = cast<SPString>(child);
1848 if (spstring) {
1849 Glib::ustring content = spstring->string;
1850 if (content != "\n") {
1851 XML::Node *rstring = xml_doc->createTextNode(content.c_str());
1852 XML::Node *rtspan = xml_doc->createElement("svg:tspan");
1853 //XML::Node *rnl = xml_doc->createTextNode("\n");
1854 rtspan->setAttribute("sodipodi:role", "line");
1855 rtspan->addChild(rstring, nullptr);
1856 text->getRepr()->addChild(rtspan, child->getRepr());
1857 GC::release(rstring);
1858 GC::release(rtspan);
1859 text->getRepr()->removeChild(spstring->getRepr());
1860 changed = true;
1861 }
1862 }
1863 }
1864 if (changed) {
1865 // proper rebuild happens later,
1866 // this just updates layout to use now, avoids use after free
1867 text->rebuildLayout();
1868 }
1869 }
1870
1871 std::vector<SPObject *> containers;
1872 {
1873 // populate `containers` with objects that will be modified.
1874
1875 // Temporarily remove the shape so Layout calculates
1876 // the position of wrap_end and wrap_start, even if
1877 // one of these are hidden because the previous line height was changed
1878 if (text) {
1879 text->hide_shape_inside();
1880 } else if (flowtext) {
1881 flowtext->fix_overflow_flowregion(false);
1882 }
1883 SPObject *rawptr_start = nullptr;
1884 SPObject *rawptr_end = nullptr;
1885 layout->validateIterator(&wrap_start);
1886 layout->validateIterator(&wrap_end);
1887 layout->getSourceOfCharacter(wrap_start, &rawptr_start);
1888 layout->getSourceOfCharacter(wrap_end, &rawptr_end);
1889 if (text) {
1890 text->show_shape_inside();
1891 } else if (flowtext) {
1892 flowtext->fix_overflow_flowregion(true);
1893 }
1894 if (!rawptr_start || !rawptr_end) {
1895 return;
1896 }
1897
1898 // Loop through parents of start and end till we reach
1899 // first children of the text element.
1900 // Get all objects between start and end (inclusive)
1901 SPObject *start = rawptr_start;
1902 SPObject *end = rawptr_end;
1903 while (start->parent != spobject) {
1904 start = start->parent;
1905 }
1906 while (end->parent != spobject) {
1907 end = end->parent;
1908 }
1909
1910 while (start && start != end) {
1911 containers.push_back(start);
1912 start = start->getNext();
1913 }
1914 if (start) {
1915 containers.push_back(start);
1916 }
1917 }
1918
1919 for (auto container : containers) {
1920 XML::Node *prevchild = container->getRepr();
1921 std::vector<SPObject*> childs = container->childList(false);
1922 for (auto child : childs) {
1923 auto spstring = cast<SPString>(child);
1924 auto flowtspan = cast<SPFlowtspan>(child);
1925 auto tspan = cast<SPTSpan>(child);
1926 // we need to upper all flowtspans to container level
1927 // to do this we need to change the element from flowspan to flowpara
1928 if (flowtspan) {
1929 XML::Node *flowpara = xml_doc->createElement("svg:flowPara");
1930 std::vector<SPObject*> fts_childs = flowtspan->childList(false);
1931 bool hascontent = false;
1932 // we need to move the contents to the new created element
1933 // maybe we can move directly but it is safer for me to duplicate,
1934 // inject into the new element and delete original
1935 for (auto fts_child : fts_childs) {
1936 // is this check necessary?
1937 if (fts_child) {
1938 XML::Node *fts_child_node = fts_child->getRepr()->duplicate(xml_doc);
1939 flowtspan->getRepr()->removeChild(fts_child->getRepr());
1940 flowpara->addChild(fts_child_node, nullptr);
1941 GC::release(fts_child_node);
1942 hascontent = true;
1943 }
1944 }
1945 // if no contents we dont want to add
1946 if (hascontent) {
1947 flowpara->setAttribute("style", flowtspan->getRepr()->attribute("style"));
1948 spobject->getRepr()->addChild(flowpara, prevchild);
1949 GC::release(flowpara);
1950 prevchild = flowpara;
1951 }
1952 container->getRepr()->removeChild(flowtspan->getRepr());
1953 } else if (tspan) {
1954 if (child->childList(false).size()) {
1955 child->getRepr()->setAttribute("sodipodi:role", "line");
1956 // maybe we need to move unindent function here
1957 // to be the same as other here
1958 prevchild = unindent_node(child->getRepr(), prevchild);
1959 } else {
1960 // if no contents we dont want to add
1961 container->getRepr()->removeChild(child->getRepr());
1962 }
1963 } else if (spstring) {
1964 // we are on a text node, we act different if in a text or flowtext.
1965 // wrap a duplicate of the element and unindent after the prevchild
1966 // and finally delete original
1967 XML::Node *string_node = xml_doc->createTextNode(spstring->string.c_str());
1968 if (text) {
1969 XML::Node *tspan_node = xml_doc->createElement("svg:tspan");
1970 tspan_node->setAttribute("style", container->getRepr()->attribute("style"));
1971 tspan_node->addChild(string_node, nullptr);
1972 tspan_node->setAttribute("sodipodi:role", "line");
1973 text->getRepr()->addChild(tspan_node, prevchild);
1974 GC::release(string_node);
1975 GC::release(tspan_node);
1976 prevchild = tspan_node;
1977 } else if (flowtext) {
1978 XML::Node *flowpara_node = xml_doc->createElement("svg:flowPara");
1979 flowpara_node->setAttribute("style", container->getRepr()->attribute("style"));
1980 flowpara_node->addChild(string_node, nullptr);
1981 flowtext->getRepr()->addChild(flowpara_node, prevchild);
1982 GC::release(string_node);
1983 GC::release(flowpara_node);
1984 prevchild = flowpara_node;
1985 }
1986 container->getRepr()->removeChild(spstring->getRepr());
1987 }
1988 }
1989 tc->textItem()->getRepr()->removeChild(container->getRepr());
1990 }
1991}
1992
1994{
1995 g_assert(repr != nullptr);
1996
1997 XML::Node *parent = repr->parent();
1998 if (parent) {
1999 XML::Node *grandparent = parent->parent();
2000 if (grandparent) {
2002 XML::Document *xml_doc = doc->getReprDoc();
2003 XML::Node *newrepr = repr->duplicate(xml_doc);
2004 parent->removeChild(repr);
2005 grandparent->addChild(newrepr, prevchild);
2006 GC::release(newrepr);
2007 newrepr->setAttribute("sodipodi:role", "line");
2008 return newrepr;
2009 }
2010 }
2011 std::cerr << "TextToolbar::unindent_node error: node has no (grand)parent, nothing done.\n";
2012 return repr;
2013}
2014
2016{
2018
2019 auto font_collections = FontCollections::get();
2020
2021 // Insert system collections.
2022 for(auto const &col: font_collections->get_collections(true)) {
2023 auto const btn = Gtk::make_managed<Gtk::CheckButton>(col);
2024 btn->set_margin_bottom(2);
2025 btn->set_active(font_collections->is_collection_selected(col));
2026 btn->signal_toggled().connect([font_collections, col](){
2027 // toggle font system collection
2028 font_collections->update_selected_collections(col);
2029 });
2030// g_message("tag: %s", tag.display_name.c_str());
2031 auto const row = Gtk::make_managed<Gtk::ListBoxRow>();
2032 row->set_focusable(false);
2033 row->set_child(*btn);
2034 _font_collections_list.append(*row);
2035 }
2036
2037 // Insert row separator.
2038 auto const sep = Gtk::make_managed<Gtk::Separator>();
2039 sep->set_margin_bottom(2);
2040 auto const sep_row = Gtk::make_managed<Gtk::ListBoxRow>();
2041 sep_row->set_focusable(false);
2042 sep_row->set_child(*sep);
2043 _font_collections_list.append(*sep_row);
2044
2045 // Insert user collections.
2046 for (auto const& col: font_collections->get_collections()) {
2047 auto const btn = Gtk::make_managed<Gtk::CheckButton>(col);
2048 btn->set_margin_bottom(2);
2049 btn->set_active(font_collections->is_collection_selected(col));
2050 btn->signal_toggled().connect([font_collections, col](){
2051 // toggle font collection
2052 font_collections->update_selected_collections(col);
2053 });
2054// g_message("tag: %s", tag.display_name.c_str());
2055 auto const row = Gtk::make_managed<Gtk::ListBoxRow>();
2056 row->set_focusable(false);
2057 row->set_child(*btn);
2058 _font_collections_list.append(*row);
2059 }
2060}
2061
2063{
2064 if (_desktop) {
2065 if (auto container = _desktop->getContainer()) {
2066 container->new_floating_dialog("FontCollections");
2067 }
2068 }
2069}
2070
2072{
2073 auto font_collections = FontCollections::get();
2074 font_collections->clear_selected_collections();
2075
2076 auto font_lister = FontLister::get_instance();
2077 font_lister->init_font_families();
2078 font_lister->init_default_styles();
2079
2080 auto document = _desktop->getDocument();
2081 font_lister->add_document_fonts_at_top(document);
2082}
2083
2085{
2086 if constexpr (DEBUG_TEXT) {
2087 std::cout << std::endl;
2088 std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
2089 std::cout << "subselection_changed: start " << std::endl;
2090 }
2091 // quit if run by the _changed callbacks
2092 this->_sub_active_item = nullptr;
2093 if (_updating) {
2094 return;
2095 }
2096 if (tc) {
2097 Text::Layout const *layout = te_get_layout(tc->textItem());
2098 if (layout) {
2100 Text::Layout::iterator end = layout->end();
2101 Text::Layout::iterator start_selection = tc->text_sel_start;
2102 Text::Layout::iterator end_selection = tc->text_sel_end;
2103 if constexpr (DEBUG_TEXT) {
2104 std::cout << " GUI: Start of text: " << layout->iteratorToCharIndex(start) << std::endl;
2105 std::cout << " GUI: End of text: " << layout->iteratorToCharIndex(end) << std::endl;
2106 std::cout << " GUI: Start of selection: " << layout->iteratorToCharIndex(start_selection) << std::endl;
2107 std::cout << " GUI: End of selection: " << layout->iteratorToCharIndex(end_selection) << std::endl;
2108 std::cout << " GUI: Loop Subelements: " << std::endl;
2109 std::cout << " ::::::::::::::::::::::::::::::::::::::::::::: " << std::endl;
2110 }
2111 gint startline = layout->paragraphIndex(start_selection);
2112 if (start_selection == end_selection) {
2113 _outer = true;
2114 int counter = 0;
2115 for (auto child : tc->textItem()->childList(false)) {
2116 auto item = cast<SPItem>(child);
2117 if (item && counter == startline) {
2119 int origin_selection = layout->iteratorToCharIndex(start_selection);
2120 Text::Layout::iterator next = layout->charIndexToIterator(origin_selection + 1);
2121 Text::Layout::iterator prev = layout->charIndexToIterator(origin_selection - 1);
2122 //TODO: find a better way to init
2123 _updating = true;
2124 auto query = SPStyle{_desktop->getDocument()};
2125 _query_cursor = query;
2126 Text::Layout::iterator start_line = tc->text_sel_start;
2127 start_line.thisStartOfLine();
2128 if (tc->text_sel_start == start_line) {
2129 tc->text_sel_start = next;
2130 } else {
2131 tc->text_sel_start = prev;
2132 }
2134 tc->text_sel_start = start_selection;
2136 wrap_end = tc->text_sel_end;
2139 _updating = false;
2140 break;
2141 }
2142 ++counter;
2143 }
2144 _selectionChanged(nullptr);
2145 } else if ((start_selection == start && end_selection == end) ||
2146 (start_selection == end && end_selection == start)) {
2147 // full subselection
2148 _cursor_numbers = 0;
2149 _outer = true;
2150 _selectionChanged(nullptr);
2151 } else {
2152 _cursor_numbers = 0;
2153 _outer = false;
2155 wrap_end = tc->text_sel_end;
2156 if (tc->text_sel_start > tc->text_sel_end) {
2159 } else {
2162 }
2163 _selectionChanged(nullptr);
2164 }
2165 }
2166 }
2167 if constexpr (DEBUG_TEXT) {
2168 std::cout << "subselection_changed: exit " << std::endl;
2169 std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
2170 std::cout << std::endl;
2171 }
2172}
2173
2174} // namespace Inkscape::UI::Toolbar
2175
2176/*
2177 Local Variables:
2178 mode:c++
2179 c-file-style:"stroustrup"
2180 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2181 indent-tabs-mode:nil
2182 fill-column:99
2183 End:
2184*/
2185// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
Gtk builder utilities.
Fragment store
Definition canvas.cpp:155
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
A thin wrapper around std::ostringstream, but writing floating point numbers in the format required b...
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 FontCollections * get()
std::pair< bool, std::string > get_font_count_label() const
static Inkscape::FontLister * get_instance()
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
void clear()
Unselects all selected objects.
SPItem * singleItem()
Returns a single selected item.
double getDouble(Glib::ustring const &pref_path, double def=0.0, Glib::ustring const &unit="")
Retrieve a floating point value.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
void mergeStyle(Glib::ustring const &pref_path, SPCSSAttr *style)
Merge a CSS style with the current preference value.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
sigc::connection connectChangedFirst(sigc::slot< void(Selection *)> slot)
Similar to connectChanged, but will be run first.
Definition selection.h:188
Holds a position within the glyph output of Layout.
Definition Layout-TNG.h:973
Generates the layout for either wrapped or non-wrapped text and stores the result.
Definition Layout-TNG.h:144
iterator charIndexToIterator(int char_index) const
Returns an iterator pointing at the given character index.
int iteratorToCharIndex(iterator const &it) const
Returns the character index from the start of the flow represented by the given iterator.
iterator end() const
Returns an iterator pointing just past the end of the last glyph, which is also just past the end of ...
unsigned paragraphIndex(iterator const &it) const
Returns the zero-based number of the paragraph containing the character pointed to by it.
iterator begin() const
Returns an iterator pointing at the first glyph of the flowed output.
std::unique_ptr< UI::Widget::UnitTracker > _tracker
std::vector< Gtk::ToggleButton * > _writing_buttons
void setup_derived_spin_button(UI::Widget::SpinButton &btn, Glib::ustring const &name, double default_value, ValueChangedMemFun value_changed_mem_fun)
XML::Node * unindent_node(XML::Node *repr, XML::Node *before)
void(TextToolbar::*)() ValueChangedMemFun
UI::Widget::ComboBoxEntryToolItem * _font_style_item
UI::Widget::ComboBoxEntryToolItem * _font_size_item
Text::Layout::iterator wrap_start
void configure_mode_buttons(std::vector< Gtk::ToggleButton * > &buttons, Gtk::Box &box, Glib::ustring const &name, ModeChangedMemFun mode_changed_mem_fun)
UI::Widget::SpinButton & _dx_item
void setDesktop(SPDesktop *desktop) override
void _selectionChanged(Selection *selection)
Gtk::ToggleButton & _superscript_btn
void text_outer_set_style(SPCSSAttr *css)
std::vector< Gtk::ToggleButton * > _direction_buttons
std::unique_ptr< UI::Widget::UnitTracker > _tracker_fs
std::vector< Gtk::ToggleButton * > _alignment_buttons
Gtk::ToggleButton & _subscript_btn
sigc::scoped_connection font_count_changed_connection
Text::Layout::iterator wrap_end
sigc::scoped_connection fc_update
UI::Widget::SpinButton & _rotation_item
bool mergeDefaultStyle(SPCSSAttr *css)
Merge the style into either the tool or the desktop style depending on which one the user has decided...
void _cursorMoved(Tools::TextTool *texttool)
UI::Widget::SpinButton & _letter_spacing_item
sigc::connection _selection_modified_conn
UI::Widget::ComboToolItem * _line_height_units_item
void _selectionModified(Selection *selection, guint flags)
UI::Widget::SpinButton & _line_height_item
UI::Widget::SpinButton & _dy_item
UI::Widget::SpinButton & _word_spacing_item
void(TextToolbar::*)(int) ModeChangedMemFun
sigc::scoped_connection fc_changed_selection
UI::Widget::ComboBoxEntryToolItem * _font_family_item
std::vector< Gtk::ToggleButton * > _orientation_buttons
UI::Widget::ComboToolItem * _font_size_units_item
Base class for all tool toolbars.
Definition toolbar.h:72
virtual void setDesktop(SPDesktop *desktop)
Definition toolbar.h:76
Text::Layout::iterator text_sel_end
Definition text-tool.h:59
SPItem * textItem() const
Definition text-tool.h:54
Text::Layout::iterator text_sel_start
Definition text-tool.h:58
Formerly a Gtk::ToolItem that wraps a Gtk::ComboBox object.
bool set_active_text(Glib::ustring text, int row=-1)
void set_model(Glib::RefPtr< Gtk::TreeModel > model)
sigc::connection connectChanged(sigc::slot< void()> &&slot)
sigc::signal< void(int)> signal_changed_after()
void focus_on_click(bool focus_on_click)
SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMen...
Definition spinbutton.h:52
void setDefocusTarget(decltype(_defocus_target) target)
Definition spinbutton.h:127
void set_custom_numeric_menu_data(NumericMenuData &&custom_menu_data)
static UnitTable & get()
Definition units.cpp:410
Glib::ustring abbr
Definition units.h:76
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * duplicate(Document *doc) const =0
Create a duplicate of this node.
virtual void removeChild(Node *child)=0
Remove a child of this node.
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::UI::Dialog::DialogContainer * getContainer()
Definition desktop.cpp:335
Inkscape::Selection * getSelection() const
Definition desktop.h:188
sigc::connection connect_text_cursor_moved(sigc::slot< void(Inkscape::UI::Tools::TextTool *)> const &slot)
Definition desktop.cpp:1357
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
Typed SVG document implementation.
Definition document.h:103
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:202
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
Length type internal to SPStyle.
void read(gchar const *str) override
unsigned unit
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1821
Geom::OptRect geometricBounds(Geom::Affine const &transform=Geom::identity()) const
Get item's geometric bounding box in this item's coordinate system.
Definition sp-item.cpp:920
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1816
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPObject * getNext()
std::vector< SPObject * > childList(bool add_ref, Action action=ActionGeneral)
Retrieves the children as a std vector object, optionally ref'ing the children in the process,...
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPObject * parent
Definition sp-object.h:189
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
An SVG style object.
Definition style.h:45
void mergeCSS(SPCSSAttr *css)
Definition style.cpp:859
T< SPAttr::FONT_WEIGHT, SPIEnum< SPCSSFontWeight > > font_weight
Weight of the font.
Definition style.h:112
T< SPAttr::LINE_HEIGHT, SPILengthOrNormal > line_height
Line height (css2 10.8.1)
Definition style.h:118
T< SPAttr::LETTER_SPACING, SPILengthOrNormal > letter_spacing
letter spacing (css2 16.4)
Definition style.h:153
T< SPAttr::FONT_FAMILY, SPIString > font_family
Font family.
Definition style.h:120
T< SPAttr::INKSCAPE_FONT_SPEC, SPIString > font_specification
Full font name, as FontFactory::ConstructFontSpecification would give, for internal use.
Definition style.h:124
T< SPAttr::DIRECTION, SPIEnum< SPCSSDirection > > direction
text direction (svg1.1)
Definition style.h:161
T< SPAttr::FONT_STYLE, SPIEnum< SPCSSFontStyle > > font_style
Font style.
Definition style.h:108
T< SPAttr::WORD_SPACING, SPILengthOrNormal > word_spacing
word spacing (also css2 16.4)
Definition style.h:155
T< SPAttr::TEXT_ALIGN, SPIEnum< SPCSSTextAlign > > text_align
text alignment (css2 16.2) (not to be confused with text-anchor)
Definition style.h:150
T< SPAttr::TEXT_ANCHOR, SPIEnum< SPTextAnchor > > text_anchor
Anchor of the text (svg1.1 10.9.1)
Definition style.h:173
T< SPAttr::WRITING_MODE, SPIEnum< SPCSSWritingMode > > writing_mode
Writing mode (svg1.1 10.7.2, CSS Writing Modes 3)
Definition style.h:163
void readFromPrefs(Glib::ustring const &path)
Read style properties from preferences.
Definition style.cpp:625
T< SPAttr::TEXT_ORIENTATION, SPIEnum< SPCSSTextOrientation > > text_orientation
Text orientation (CSS Writing Modes 3)
Definition style.h:165
T< SPAttr::FONT_SIZE, SPIFontSize > font_size
Size of the font.
Definition style.h:116
T< SPAttr::BASELINE_SHIFT, SPIBaselineShift > baseline_shift
Baseline shift (svg1.1 10.9.2)
Definition style.h:169
A combobox that can be displayed in a toolbar.
TODO: insert short description here.
std::shared_ptr< Css const > css
Css & result
void sp_desktop_set_style(SPDesktop *desktop, SPCSSAttr *css, bool change, bool write_current, bool switch_style)
Apply style on selection on desktop.
int objects_query_fontnumbers(const std::vector< SPItem * > &objects, SPStyle *style_res)
Write to style_res the average font size and spacing of objects.
SPCSSAttr * sp_desktop_get_style(SPDesktop *desktop, bool with_text)
Return the desktop's current style.
int sp_desktop_query_style(SPDesktop *desktop, SPStyle *style, int property)
Query the subselection (if any) or selection on the given desktop for the given property,...
@ QUERY_STYLE_PROPERTY_FONTNUMBERS
@ QUERY_STYLE_PROPERTY_BASELINES
@ QUERY_STYLE_PROPERTY_FONTSTYLE
@ QUERY_STYLE_PROPERTY_FONTFAMILY
@ QUERY_STYLE_PROPERTY_WRITINGMODES
@ QUERY_STYLE_SINGLE
@ QUERY_STYLE_NOTHING
@ QUERY_STYLE_MULTIPLE_SAME
Editable view implementation.
A widget that manages DialogNotebook's and other widgets inside a horizontal DialogMultipaned.
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
bool font_lister_separator_func(Glib::RefPtr< Gtk::TreeModel > const &, Gtk::TreeModel::const_iterator const &iter)
void font_lister_cell_data_func2(Gtk::CellRenderer &cell, Gtk::TreeModel::const_iterator const &iter, bool with_markup)
Font selection widgets.
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
std::string cache
SPItem * item
Definition desktop.h:50
static R & release(R &r)
Decrements the reference count of a anchored object.
void remove_all_children(Widget &widget)
For each child in get_children(widget), call widget.remove(*child). May not cause delete child!
Definition util.h:88
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
Gtk::Widget * for_each_child(Gtk::Widget &widget, Func &&func, bool const plus_self=false, bool const recurse=false, int const level=0)
Call Func with a reference to each child of parent, until it returns _break.
Definition util.h:103
W & get_derived_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id, Args &&... args)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
Miscellaneous supporting code.
Definition document.h:95
Glib::ustring format_classic(T const &... args)
bool is_query_style_updateable(const int style)
Definition style-utils.h:19
STL namespace.
static gint counter
Definition box3d.cpp:39
int mode
Ocnode * child[8]
Definition quantize.cpp:33
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_merge(SPCSSAttr *dst, SPCSSAttr *src)
Merges two SPCSSAttr's.
Definition repr-css.cpp:299
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
char const * sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
Returns a character string of the value of a given style property or a default value if the attribute...
Definition repr-css.cpp:147
SPCSSAttr * sp_repr_css_attr_unset_all(SPCSSAttr *css)
Return a new SPCSSAttr with all the properties found in the input SPCSSAttr unset.
Definition repr-css.cpp:387
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
std::vector< SPItem * > get_all_items(SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, bool ingroups, std::vector< SPItem * > const &exclude)
TODO: insert short description here.
TODO: insert short description here.
SPRoot: SVG <svg> implementation.
TODO: insert short description here.
TODO: insert short description here.
Interface for XML documents.
Definition document.h:43
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
@ SP_CSS_BASELINE_SHIFT_SUPER
@ SP_CSS_BASELINE_SHIFT_SUB
@ SP_CSS_TEXT_ALIGN_END
@ SP_CSS_TEXT_ALIGN_RIGHT
@ SP_CSS_TEXT_ALIGN_CENTER
@ SP_CSS_TEXT_ALIGN_START
@ SP_CSS_TEXT_ALIGN_LEFT
@ SP_CSS_TEXT_ALIGN_JUSTIFY
@ SP_CSS_WRITING_MODE_TB_RL
@ SP_CSS_WRITING_MODE_LR_TB
@ SP_CSS_WRITING_MODE_TB_LR
@ SP_CSS_WRITING_MODE_RL_TB
@ SP_CSS_DIRECTION_RTL
@ SP_CSS_DIRECTION_LTR
@ SP_CSS_TEXT_ORIENTATION_SIDEWAYS
@ SP_CSS_TEXT_ORIENTATION_UPRIGHT
@ SP_CSS_TEXT_ORIENTATION_MIXED
static const unsigned SP_STYLE_FLAG_IFSET(1<< 0)
@ SP_BASELINE_SHIFT_LITERAL
SPCSSUnit
@ SP_CSS_UNIT_PT
@ SP_CSS_UNIT_PX
@ SP_CSS_UNIT_PERCENT
@ SP_CSS_UNIT_NONE
@ SP_CSS_UNIT_EM
@ SP_CSS_UNIT_EX
Common utility functions for manipulating style.
void css_font_family_unquote(Glib::ustring &val)
Remove paired single and double quotes from font names in font-family lists, changing string in place...
Definition style.cpp:1722
SPCSSAttr * sp_css_attr_scale(SPCSSAttr *css, double ex)
Scale any properties that may hold <length> by ex.
Definition style.cpp:1625
double sp_style_css_size_px_to_units(double size, int unit, double font_size)
Definition style.cpp:1332
double sp_style_css_size_units_to_px(double size, int unit, double font_size)
Definition style.cpp:1367
SPCSSAttr * sp_css_attr_from_style(SPStyle const *const style, guint const flags)
Definition style.cpp:1409
gchar const * sp_style_get_css_unit_string(int unit)
Definition style.cpp:1305
SPDesktop * desktop
double height
double width
void sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *, gdouble degrees)
void sp_te_adjust_dx(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *, double delta)
TextTagAttributes * text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
Returns the attributes block and the character index within that block which represents the iterator ...
void sp_te_adjust_dy(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *, double delta)
Inkscape::Text::Layout const * te_get_layout(SPItem const *item)
TextTool.
auto SP_TEXT_CONTEXT(Inkscape::UI::Tools::ToolBase *tool)
Definition text-tool.h:131
constexpr bool DEBUG_TEXT
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder