1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * Text aux toolbar
5  */
6 /* Authors:
7  *   MenTaLguY <mental@rydia.net>
8  *   Lauris Kaplinski <lauris@kaplinski.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Frank Felfe <innerspace@iname.com>
11  *   John Cliff <simarilius@yahoo.com>
12  *   David Turner <novalis@gnu.org>
13  *   Josh Andler <scislac@scislac.com>
14  *   Jon A. Cruz <jon@joncruz.org>
15  *   Maximilian Albert <maximilian.albert@gmail.com>
16  *   Tavmjong Bah <tavmjong@free.fr>
17  *   Abhishek Sharma
18  *   Kris De Gussem <Kris.DeGussem@gmail.com>
19  *   Tavmjong Bah <tavmjong@free.fr>
20  *
21  * Copyright (C) 2004 David Turner
22  * Copyright (C) 2003 MenTaLguY
23  * Copyright (C) 2001-2002 Ximian, Inc.
24  * Copyright (C) 1999-2013 authors
25  * Copyright (C) 2017 Tavmjong Bah
26  *
27  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
28  */
29 
30 #include <glibmm/i18n.h>
31 
32 #include "text-toolbar.h"
33 
34 #include "desktop-style.h"
35 #include "desktop.h"
36 #include "document-undo.h"
37 #include "document.h"
38 #include "inkscape.h"
39 #include "selection-chemistry.h"
40 #include "verbs.h"
41 
42 #include "libnrtype/font-lister.h"
43 
44 #include "object/sp-flowdiv.h"
45 #include "object/sp-flowtext.h"
46 #include "object/sp-root.h"
47 #include "object/sp-text.h"
48 #include "object/sp-tspan.h"
49 #include "object/sp-string.h"
50 
51 #include "svg/css-ostringstream.h"
52 #include "ui/icon-names.h"
53 #include "ui/tools/select-tool.h"
54 #include "ui/tools/text-tool.h"
55 #include "ui/widget/canvas.h"  // Focus
56 #include "ui/widget/combo-box-entry-tool-item.h"
57 #include "ui/widget/combo-tool-item.h"
58 #include "ui/widget/spin-button-tool-item.h"
59 #include "ui/widget/unit-tracker.h"
60 #include "util/units.h"
61 
62 #include "widgets/style-utils.h"
63 
64 using Inkscape::DocumentUndo;
65 using Inkscape::Util::Unit;
66 using Inkscape::Util::Quantity;
67 using Inkscape::Util::unit_table;
68 using Inkscape::UI::Widget::UnitTracker;
69 
70 //#define DEBUG_TEXT
71 
72 //########################
73 //##    Text Toolbox    ##
74 //########################
75 
76 // Functions for debugging:
77 #ifdef DEBUG_TEXT
sp_print_font(SPStyle * query)78 static void sp_print_font(SPStyle *query)
79 {
80 
81 
82     bool family_set   = query->font_family.set;
83     bool style_set    = query->font_style.set;
84     bool fontspec_set = query->font_specification.set;
85 
86     std::cout << "    Family set? " << family_set
87               << "    Style set? "  << style_set
88               << "    FontSpec set? " << fontspec_set
89               << std::endl;
90 }
91 
sp_print_fontweight(SPStyle * query)92 static void       sp_print_fontweight( SPStyle *query ) {
93     const gchar* names[] = {"100", "200", "300", "400", "500", "600", "700", "800", "900",
94                             "NORMAL", "BOLD", "LIGHTER", "BOLDER", "Out of range"};
95     // Missing book = 380
96     int index = query->font_weight.computed;
97     if (index < 0 || index > 13)
98         index = 13;
99     std::cout << "    Weight: " << names[ index ]
100               << " (" << query->font_weight.computed << ")" << std::endl;
101 }
102 
sp_print_fontstyle(SPStyle * query)103 static void       sp_print_fontstyle( SPStyle *query ) {
104 
105     const gchar* names[] = {"NORMAL", "ITALIC", "OBLIQUE", "Out of range"};
106     int index = query->font_style.computed;
107     if( index < 0 || index > 3 ) index = 3;
108     std::cout << "    Style:  " << names[ index ] << std::endl;
109 
110 }
111 #endif
112 
is_relative(Unit const * unit)113 static bool is_relative( Unit const *unit ) {
114     return (unit->abbr == "" || unit->abbr == "em" || unit->abbr == "ex" || unit->abbr == "%");
115 }
116 
is_relative(SPCSSUnit const unit)117 static bool is_relative(SPCSSUnit const unit)
118 {
119     return (unit == SP_CSS_UNIT_NONE || unit == SP_CSS_UNIT_EM || unit == SP_CSS_UNIT_EX ||
120             unit == SP_CSS_UNIT_PERCENT);
121 }
122 
123 // Set property for object, but unset all descendents
124 // Should probably be moved to desktop_style.cpp
recursively_set_properties(SPObject * object,SPCSSAttr * css,bool unset_descendents=true)125 static void recursively_set_properties(SPObject *object, SPCSSAttr *css, bool unset_descendents = true)
126 {
127     object->changeCSS (css, "style");
128 
129     SPCSSAttr *css_unset = sp_repr_css_attr_unset_all( css );
130     std::vector<SPObject *> children = object->childList(false);
131     for (auto i: children) {
132         recursively_set_properties(i, unset_descendents ? css_unset : css);
133     }
134     sp_repr_css_attr_unref (css_unset);
135 }
136 
137 /*
138  * Set the default list of font sizes, scaled to the users preferred unit
139  */
sp_text_set_sizes(GtkListStore * model_size,int unit)140 static void sp_text_set_sizes(GtkListStore* model_size, int unit)
141 {
142     gtk_list_store_clear(model_size);
143 
144     // List of font sizes for dropchange-down menu
145     int sizes[] = {
146         4, 6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28,
147         32, 36, 40, 48, 56, 64, 72, 144
148     };
149 
150     // Array must be same length as SPCSSUnit in style.h
151     float ratios[] = {1, 1, 1, 10, 4, 40, 100, 16, 8, 0.16};
152 
153     for(int i : sizes) {
154         GtkTreeIter iter;
155         Glib::ustring size = Glib::ustring::format(i / (float)ratios[unit]);
156         gtk_list_store_append( model_size, &iter );
157         gtk_list_store_set( model_size, &iter, 0, size.c_str(), -1 );
158     }
159 }
160 
161 
162 // TODO: possibly share with font-selector by moving most code to font-lister (passing family name)
sp_text_toolbox_select_cb(GtkEntry * entry,GtkEntryIconPosition,GdkEvent,gpointer)163 static void sp_text_toolbox_select_cb( GtkEntry* entry, GtkEntryIconPosition /*position*/, GdkEvent /*event*/, gpointer /*data*/ ) {
164 
165   Glib::ustring family = gtk_entry_get_text ( entry );
166   //std::cout << "text_toolbox_missing_font_cb: selecting: " << family << std::endl;
167 
168   // Get all items with matching font-family set (not inherited!).
169   std::vector<SPItem*> selectList;
170 
171   SPDesktop *desktop = SP_ACTIVE_DESKTOP;
172   SPDocument *document = desktop->getDocument();
173   std::vector<SPItem*> x,y;
174   std::vector<SPItem*> allList = get_all_items(x, document->getRoot(), desktop, false, false, true, y);
175   for(std::vector<SPItem*>::const_reverse_iterator i=allList.rbegin();i!=allList.rend(); ++i){
176       SPItem *item = *i;
177     SPStyle *style = item->style;
178 
179     if (style) {
180 
181       Glib::ustring family_style;
182       if (style->font_family.set) {
183 	family_style = style->font_family.value();
184 	//std::cout << " family style from font_family: " << family_style << std::endl;
185       }
186       else if (style->font_specification.set) {
187 	family_style = style->font_specification.value();
188 	//std::cout << " family style from font_spec: " << family_style << std::endl;
189       }
190 
191       if (family_style.compare( family ) == 0 ) {
192         //std::cout << "   found: " << item->getId() << std::endl;
193 	selectList.push_back(item);
194       }
195     }
196   }
197 
198   // Update selection
199   Inkscape::Selection *selection = desktop->getSelection();
200   selection->clear();
201   //std::cout << "   list length: " << g_slist_length ( selectList ) << std::endl;
202   selection->setList(selectList);
203 }
204 
205 namespace Inkscape {
206 namespace UI {
207 namespace Toolbar {
208 
TextToolbar(SPDesktop * desktop)209 TextToolbar::TextToolbar(SPDesktop *desktop)
210     : Toolbar(desktop)
211     , _freeze(false)
212     , _text_style_from_prefs(false)
213     , _outer(true)
214     , _updating(false)
215     , _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR))
216     , _tracker_fs(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR))
217     , _cusor_numbers(0)
218 {
219     /* Line height unit tracker */
220     _tracker->prependUnit(unit_table.getUnit("")); // Ratio
221     _tracker->addUnit(unit_table.getUnit("%"));
222     _tracker->addUnit(unit_table.getUnit("em"));
223     _tracker->addUnit(unit_table.getUnit("ex"));
224     _tracker->setActiveUnit(unit_table.getUnit(""));
225     // We change only the display value
226     _tracker->changeLabel("lines", 0, true);
227     _tracker_fs->setActiveUnit(unit_table.getUnit("mm"));
228 
229     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
230 
231     /* Font family */
232     {
233         // Font list
234         Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
235         fontlister->update_font_list(desktop->getDocument());
236         Glib::RefPtr<Gtk::ListStore> store = fontlister->get_font_list();
237         GtkListStore* model = store->gobj();
238 
239         _font_family_item =
240             Gtk::manage(new UI::Widget::ComboBoxEntryToolItem( "TextFontFamilyAction",
241                                                                _("Font Family"),
242                                                                _("Select Font Family (Alt-X to access)"),
243                                                                GTK_TREE_MODEL(model),
244                                                                -1,                // Entry width
245                                                                50,                // Extra list width
246                                                                (gpointer)font_lister_cell_data_func2, // Cell layout
247                                                                (gpointer)font_lister_separator_func2,
248                                                                GTK_WIDGET(desktop->getCanvas()->gobj()))); // Focus widget
249         _font_family_item->popup_enable(); // Enable entry completion
250         gchar *const info = _("Select all text with this font-family");
251         _font_family_item->set_info( info ); // Show selection icon
252         _font_family_item->set_info_cb( (gpointer)sp_text_toolbox_select_cb );
253 
254         gchar *const warning = _("Font not found on system");
255         _font_family_item->set_warning( warning ); // Show icon w/ tooltip if font missing
256         _font_family_item->set_warning_cb( (gpointer)sp_text_toolbox_select_cb );
257 
258         //ink_comboboxentry_action_set_warning_callback( act, sp_text_fontfamily_select_all );
259         _font_family_item->signal_changed().connect( sigc::mem_fun(*this, &TextToolbar::fontfamily_value_changed) );
260         add(*_font_family_item);
261 
262         // Change style of drop-down from menu to list
263         auto css_provider = gtk_css_provider_new();
264         gtk_css_provider_load_from_data(css_provider,
265                                         "#TextFontFamilyAction_combobox {\n"
266                                         "  -GtkComboBox-appears-as-list: true;\n"
267                                         "}\n",
268                                         -1, nullptr);
269 
270         auto screen = gdk_screen_get_default();
271         _font_family_item->focus_on_click(false);
272         gtk_style_context_add_provider_for_screen(screen,
273                                                   GTK_STYLE_PROVIDER(css_provider),
274                                                   GTK_STYLE_PROVIDER_PRIORITY_USER);
275     }
276 
277     /* Font styles */
278     {
279         Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
280         Glib::RefPtr<Gtk::ListStore> store = fontlister->get_style_list();
281         GtkListStore* model_style = store->gobj();
282 
283         _font_style_item =
284             Gtk::manage(new UI::Widget::ComboBoxEntryToolItem( "TextFontStyleAction",
285                                                                _("Font Style"),
286                                                                _("Font style"),
287                                                                GTK_TREE_MODEL(model_style),
288                                                                12,     // Width in characters
289                                                                0,      // Extra list width
290                                                                nullptr,   // Cell layout
291                                                                nullptr,   // Separator
292                                                                GTK_WIDGET(desktop->getCanvas()->gobj()))); // Focus widget
293 
294         _font_style_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::fontstyle_value_changed));
295         _font_style_item->focus_on_click(false);
296         add(*_font_style_item);
297     }
298 
299     add_separator();
300 
301     /* Font size */
302     {
303         // List of font sizes for drop-down menu
304         GtkListStore* model_size = gtk_list_store_new( 1, G_TYPE_STRING );
305         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
306         int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
307 
308         sp_text_set_sizes(model_size, unit);
309 
310         auto unit_str = sp_style_get_css_unit_string(unit);
311         Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", unit_str, ")");
312 
313         _font_size_item =
314             Gtk::manage(new UI::Widget::ComboBoxEntryToolItem( "TextFontSizeAction",
315                                                                _("Font Size"),
316                                                                tooltip,
317                                                                GTK_TREE_MODEL(model_size),
318                                                                8,      // Width in characters
319                                                                0,      // Extra list width
320                                                                nullptr,   // Cell layout
321                                                                nullptr,   // Separator
322                                                                GTK_WIDGET(desktop->getCanvas()->gobj()))); // Focus widget
323 
324         _font_size_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::fontsize_value_changed));
325         _font_size_item->focus_on_click(false);
326         add(*_font_size_item);
327     }
328     /* Font_ size units */
329     {
330         _font_size_units_item = _tracker_fs->create_tool_item(_("Units"), (""));
331         _font_size_units_item->signal_changed_after().connect(
332             sigc::mem_fun(*this, &TextToolbar::fontsize_unit_changed));
333         _font_size_units_item->focus_on_click(false);
334         add(*_font_size_units_item);
335     }
336     {
337         // Drop down menu
338         std::vector<Glib::ustring> labels = {_("Smaller spacing"),  "",  "",  "",  "", C_("Text tool", "Normal"),  "", "",   "",  "",  "", _("Larger spacing")};
339         std::vector<double>        values = {                 0.5, 0.6, 0.7, 0.8, 0.9,                       1.0, 1.1, 1.2, 1.3, 1.4, 1.5,                 2.0};
340 
341         auto line_height_val = 1.25;
342         _line_height_adj = Gtk::Adjustment::create(line_height_val, 0.0, 1000.0, 0.1, 1.0);
343         _line_height_item =
344             Gtk::manage(new UI::Widget::SpinButtonToolItem("text-line-height", "", _line_height_adj, 0.1, 2));
345         _line_height_item->set_tooltip_text(_("Spacing between baselines"));
346         _line_height_item->set_custom_numeric_menu_data(values, labels);
347         _line_height_item->set_focus_widget(desktop->getCanvas());
348         _line_height_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::lineheight_value_changed));
349         //_tracker->addAdjustment(_line_height_adj->gobj()); // (Alex V) Why is this commented out?
350         _line_height_item->set_sensitive(true);
351         _line_height_item->set_icon(INKSCAPE_ICON("text_line_spacing"));
352         add(*_line_height_item);
353     }
354     /* Line height units */
355     {
356         _line_height_units_item = _tracker->create_tool_item( _("Units"), (""));
357         _line_height_units_item->signal_changed_after().connect(sigc::mem_fun(*this, &TextToolbar::lineheight_unit_changed));
358         _line_height_units_item->focus_on_click(false);
359         add(*_line_height_units_item);
360     }
361 
362     /* Alignment */
363     {
364         UI::Widget::ComboToolItemColumns columns;
365 
366         Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
367 
368         Gtk::TreeModel::Row row;
369 
370         row = *(store->append());
371         row[columns.col_label    ] = _("Align left");
372         row[columns.col_tooltip  ] = _("Align left");
373         row[columns.col_icon     ] = INKSCAPE_ICON("format-justify-left");
374         row[columns.col_sensitive] = true;
375 
376         row = *(store->append());
377         row[columns.col_label    ] = _("Align center");
378         row[columns.col_tooltip  ] = _("Align center");
379         row[columns.col_icon     ] = INKSCAPE_ICON("format-justify-center");
380         row[columns.col_sensitive] = true;
381 
382         row = *(store->append());
383         row[columns.col_label    ] = _("Align right");
384         row[columns.col_tooltip  ] = _("Align right");
385         row[columns.col_icon     ] = INKSCAPE_ICON("format-justify-right");
386         row[columns.col_sensitive] = true;
387 
388         row = *(store->append());
389         row[columns.col_label    ] = _("Justify");
390         row[columns.col_tooltip  ] = _("Justify (only flowed text)");
391         row[columns.col_icon     ] = INKSCAPE_ICON("format-justify-fill");
392         row[columns.col_sensitive] = false;
393 
394         _align_item =
395             UI::Widget::ComboToolItem::create(_("Alignment"),      // Label
396                                               _("Text alignment"), // Tooltip
397                                               "Not Used",          // Icon
398                                               store );             // Tree store
399         _align_item->use_icon( true );
400         _align_item->use_label( false );
401         gint mode = prefs->getInt("/tools/text/align_mode", 0);
402         _align_item->set_active( mode );
403 
404         add(*_align_item);
405         _align_item->focus_on_click(false);
406         _align_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::align_mode_changed));
407     }
408 
409     /* Style - Superscript */
410     {
411         _superscript_item = Gtk::manage(new Gtk::ToggleToolButton());
412         _superscript_item->set_label(_("Toggle superscript"));
413         _superscript_item->set_tooltip_text(_("Toggle superscript"));
414         _superscript_item->set_icon_name(INKSCAPE_ICON("text_superscript"));
415         _superscript_item->set_name("text-superscript");
416         add(*_superscript_item);
417         _superscript_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &TextToolbar::script_changed), _superscript_item));
418         _superscript_item->set_active(prefs->getBool("/tools/text/super", false));
419     }
420 
421     /* Style - Subscript */
422     {
423         _subscript_item = Gtk::manage(new Gtk::ToggleToolButton());
424         _subscript_item->set_label(_("Toggle subscript"));
425         _subscript_item->set_tooltip_text(_("Toggle subscript"));
426         _subscript_item->set_icon_name(INKSCAPE_ICON("text_subscript"));
427         _subscript_item->set_name("text-subscript");
428         add(*_subscript_item);
429         _subscript_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &TextToolbar::script_changed), _subscript_item));
430         _subscript_item->set_active(prefs->getBool("/tools/text/sub", false));
431     }
432 
433     /* Letter spacing */
434     {
435         // Drop down menu
436         std::vector<Glib::ustring> labels = {_("Negative spacing"),   "",   "",   "", C_("Text tool", "Normal"),  "",  "",  "",  "",  "",  "",  "", _("Positive spacing")};
437         std::vector<double>        values = {                 -2.0, -1.5, -1.0, -0.5,                         0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0,                   5.0};
438         auto letter_spacing_val = prefs->getDouble("/tools/text/letterspacing", 0.0);
439         _letter_spacing_adj = Gtk::Adjustment::create(letter_spacing_val, -100.0, 1000.0, 0.01, 0.10);
440         _letter_spacing_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-letter-spacing", _("Letter:"), _letter_spacing_adj, 0.1, 2));
441         _letter_spacing_item->set_tooltip_text(_("Spacing between letters (px)"));
442         _letter_spacing_item->set_custom_numeric_menu_data(values, labels);
443         _letter_spacing_item->set_focus_widget(desktop->getCanvas());
444         _letter_spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::letterspacing_value_changed));
445         add(*_letter_spacing_item);
446 
447         _letter_spacing_item->set_sensitive(true);
448         _letter_spacing_item->set_icon(INKSCAPE_ICON("text_letter_spacing"));
449     }
450 
451     /* Word spacing */
452     {
453         // Drop down menu
454         std::vector<Glib::ustring> labels = {_("Negative spacing"),   "",   "",   "", C_("Text tool", "Normal"),  "",  "",  "",  "",  "",  "",  "", _("Positive spacing")};
455         std::vector<double>        values = {                 -2.0, -1.5, -1.0, -0.5,                         0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0,                   5.0};
456         auto word_spacing_val = prefs->getDouble("/tools/text/wordspacing", 0.0);
457         _word_spacing_adj = Gtk::Adjustment::create(word_spacing_val, -100.0, 1000.0, 0.01, 0.10);
458         _word_spacing_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-word-spacing", _("Word:"), _word_spacing_adj, 0.1, 2));
459         _word_spacing_item->set_tooltip_text(_("Spacing between words (px)"));
460         _word_spacing_item->set_custom_numeric_menu_data(values, labels);
461         _word_spacing_item->set_focus_widget(desktop->getCanvas());
462         _word_spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::wordspacing_value_changed));
463 
464         add(*_word_spacing_item);
465         _word_spacing_item->set_sensitive(true);
466         _word_spacing_item->set_icon(INKSCAPE_ICON("text_word_spacing"));
467     }
468 
469     /* Character kerning (horizontal shift) */
470     {
471         // Drop down menu
472         std::vector<double> values = { -2.0, -1.5, -1.0, -0.5,   0,  0.5,  1.0,  1.5,  2.0, 2.5 };
473         auto dx_val = prefs->getDouble("/tools/text/dx", 0.0);
474         _dx_adj = Gtk::Adjustment::create(dx_val, -100.0, 1000.0, 0.01, 0.1);
475         _dx_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-dx", _("Kern:"), _dx_adj, 0.1, 2));
476         _dx_item->set_custom_numeric_menu_data(values);
477         _dx_item->set_tooltip_text(_("Horizontal kerning (px)"));
478         _dx_item->set_focus_widget(desktop->getCanvas());
479         _dx_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::dx_value_changed));
480         add(*_dx_item);
481         _dx_item->set_sensitive(true);
482         _dx_item->set_icon(INKSCAPE_ICON("text_horz_kern"));
483     }
484 
485     /* Character vertical shift */
486     {
487         // Drop down menu
488         std::vector<double> values = { -2.0, -1.5, -1.0, -0.5,   0,  0.5,  1.0,  1.5,  2.0, 2.5 };
489         auto dy_val = prefs->getDouble("/tools/text/dy", 0.0);
490         _dy_adj = Gtk::Adjustment::create(dy_val, -100.0, 1000.0, 0.01, 0.1);
491         _dy_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-dy", _("Vert:"), _dy_adj, 0.1, 2));
492         _dy_item->set_tooltip_text(_("Vertical kerning (px)"));
493         _dy_item->set_custom_numeric_menu_data(values);
494         _dy_item->set_focus_widget(desktop->getCanvas());
495         _dy_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::dy_value_changed));
496         _dy_item->set_sensitive(true);
497         _dy_item->set_icon(INKSCAPE_ICON("text_vert_kern"));
498         add(*_dy_item);
499     }
500 
501     /* Character rotation */
502     {
503         std::vector<double> values = { -90, -45, -30, -15,   0,  15,  30,  45,  90, 180 };
504         auto rotation_val = prefs->getDouble("/tools/text/rotation", 0.0);
505         _rotation_adj = Gtk::Adjustment::create(rotation_val, -180.0, 180.0, 0.1, 1.0);
506         _rotation_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-rotation", _("Rot:"), _rotation_adj, 0.1, 2));
507         _rotation_item->set_tooltip_text(_("Character rotation (degrees)"));
508         _rotation_item->set_custom_numeric_menu_data(values);
509         _rotation_item->set_focus_widget(desktop->getCanvas());
510         _rotation_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::rotation_value_changed));
511         _rotation_item->set_sensitive();
512         _rotation_item->set_icon(INKSCAPE_ICON("text_rotation"));
513         add(*_rotation_item);
514     }
515 
516 
517     /* Writing mode (Horizontal, Vertical-LR, Vertical-RL) */
518     {
519         UI::Widget::ComboToolItemColumns columns;
520 
521         Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
522 
523         Gtk::TreeModel::Row row;
524 
525         row = *(store->append());
526         row[columns.col_label    ] = _("Horizontal");
527         row[columns.col_tooltip  ] = _("Horizontal text");
528         row[columns.col_icon     ] = INKSCAPE_ICON("frmt-text-direction-horizontal");
529         row[columns.col_sensitive] = true;
530 
531         row = *(store->append());
532         row[columns.col_label    ] = _("Vertical — RL");
533         row[columns.col_tooltip  ] = _("Vertical text — lines: right to left");
534         row[columns.col_icon     ] = INKSCAPE_ICON("frmt-text-direction-vertical");
535         row[columns.col_sensitive] = true;
536 
537         row = *(store->append());
538         row[columns.col_label    ] = _("Vertical — LR");
539         row[columns.col_tooltip  ] = _("Vertical text — lines: left to right");
540         row[columns.col_icon     ] = INKSCAPE_ICON("frmt-text-direction-vertical-lr");
541         row[columns.col_sensitive] = true;
542 
543         _writing_mode_item =
544             UI::Widget::ComboToolItem::create( _("Writing mode"),       // Label
545                                                _("Block progression"),  // Tooltip
546                                                "Not Used",              // Icon
547                                                store );                 // Tree store
548         _writing_mode_item->use_icon(true);
549         _writing_mode_item->use_label( false );
550         gint mode = prefs->getInt("/tools/text/writing_mode", 0);
551         _writing_mode_item->set_active( mode );
552         add(*_writing_mode_item);
553         _writing_mode_item->focus_on_click(false);
554         _writing_mode_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::writing_mode_changed));
555     }
556 
557 
558     /* Text (glyph) orientation (Auto (mixed), Upright, Sideways) */
559     {
560         UI::Widget::ComboToolItemColumns columns;
561 
562         Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
563 
564         Gtk::TreeModel::Row row;
565 
566         row = *(store->append());
567         row[columns.col_label    ] = _("Auto");
568         row[columns.col_tooltip  ] = _("Auto glyph orientation");
569         row[columns.col_icon     ] = INKSCAPE_ICON("text-orientation-auto");
570         row[columns.col_sensitive] = true;
571 
572         row = *(store->append());
573         row[columns.col_label    ] = _("Upright");
574         row[columns.col_tooltip  ] = _("Upright glyph orientation");
575         row[columns.col_icon     ] = INKSCAPE_ICON("text-orientation-upright");
576         row[columns.col_sensitive] = true;
577 
578         row = *(store->append());
579         row[columns.col_label    ] = _("Sideways");
580         row[columns.col_tooltip  ] = _("Sideways glyph orientation");
581         row[columns.col_icon     ] = INKSCAPE_ICON("text-orientation-sideways");
582         row[columns.col_sensitive] = true;
583 
584         _orientation_item =
585             UI::Widget::ComboToolItem::create(_("Text orientation"),    // Label
586                                               _("Text (glyph) orientation in vertical text."),  // Tooltip
587                                               "Not Used",               // Icon
588                                               store );                  // List store
589         _orientation_item->use_icon(true);
590         _orientation_item->use_label(false);
591         gint mode = prefs->getInt("/tools/text/text_orientation", 0);
592         _orientation_item->set_active( mode );
593         _orientation_item->focus_on_click(false);
594         add(*_orientation_item);
595 
596         _orientation_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::orientation_changed));
597     }
598 
599     // Text direction (predominant direction of horizontal text).
600     {
601         UI::Widget::ComboToolItemColumns columns;
602 
603         Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
604 
605         Gtk::TreeModel::Row row;
606 
607         row = *(store->append());
608         row[columns.col_label    ] = _("LTR");
609         row[columns.col_tooltip  ] = _("Left to right text");
610         row[columns.col_icon     ] = INKSCAPE_ICON("frmt-text-direction-horizontal");
611         row[columns.col_sensitive] = true;
612 
613         row = *(store->append());
614         row[columns.col_label    ] = _("RTL");
615         row[columns.col_tooltip  ] = _("Right to left text");
616         row[columns.col_icon     ] = INKSCAPE_ICON("frmt-text-direction-r2l");
617         row[columns.col_sensitive] = true;
618 
619         _direction_item =
620             UI::Widget::ComboToolItem::create( _("Text direction"),    // Label
621                                                _("Text direction for normally horizontal text."),  // Tooltip
622                                                "Not Used",               // Icon
623                                                store );                  // List store
624         _direction_item->use_icon(true);
625         _direction_item->use_label(false);
626         gint mode = prefs->getInt("/tools/text/text_direction", 0);
627         _direction_item->set_active( mode );
628         _direction_item->focus_on_click(false);
629         add(*_direction_item);
630 
631         _direction_item->signal_changed_after().connect(sigc::mem_fun(*this, &TextToolbar::direction_changed));
632     }
633 
634     show_all();
635 
636     // we emit a selection change on tool switch to text
637     desktop->connectEventContextChanged(sigc::mem_fun(*this, &TextToolbar::watch_ec));
638 }
639 
640 /*
641  * Set the style, depending on the inner or outer text being selected
642  */
text_outer_set_style(SPCSSAttr * css)643 void TextToolbar::text_outer_set_style(SPCSSAttr *css)
644 {
645     // Calling sp_desktop_set_style will result in a call to TextTool::_styleSet() which
646     // will set the style on selected text inside the <text> element. If we want to set
647     // the style on the outer <text> objects we need to bypass this call.
648     SPDesktop *desktop = _desktop;
649     if(_outer) {
650         // Apply css to parent text objects directly.
651         for (auto i : desktop->getSelection()->items()) {
652             SPItem *item = dynamic_cast<SPItem *>(i);
653             if (dynamic_cast<SPText *>(item) || dynamic_cast<SPFlowtext *>(item)) {
654                 // Scale by inverse of accumulated parent transform
655                 SPCSSAttr *css_set = sp_repr_css_attr_new();
656                 sp_repr_css_merge(css_set, css);
657                 Geom::Affine const local(item->i2doc_affine());
658                 double const ex(local.descrim());
659                 if ((ex != 0.0) && (ex != 1.0)) {
660                     sp_css_attr_scale(css_set, 1 / ex);
661                 }
662                 recursively_set_properties(item, css_set);
663                 sp_repr_css_attr_unref(css_set);
664             }
665         }
666     } else {
667         // Apply css to selected inner objects.
668         sp_desktop_set_style (desktop, css, true, false);
669     }
670 }
671 
672 void
fontfamily_value_changed()673 TextToolbar::fontfamily_value_changed()
674 {
675 #ifdef DEBUG_TEXT
676     std::cout << std::endl;
677     std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" << std::endl;
678     std::cout << "sp_text_fontfamily_value_changed: " << std::endl;
679 #endif
680 
681      // quit if run by the _changed callbacks
682     if (_freeze) {
683 #ifdef DEBUG_TEXT
684         std::cout << "sp_text_fontfamily_value_changed: frozen... return" << std::endl;
685         std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n" << std::endl;
686 #endif
687         return;
688     }
689     _freeze = true;
690 
691     Glib::ustring new_family = _font_family_item->get_active_text();
692     css_font_family_unquote( new_family ); // Remove quotes around font family names.
693 
694     // TODO: Think about how to handle handle multiple selections. While
695     // the font-family may be the same for all, the styles might be different.
696     // See: TextEdit::onApply() for example of looping over selected items.
697     Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
698 #ifdef DEBUG_TEXT
699     std::cout << "  Old family: " << fontlister->get_font_family() << std::endl;
700     std::cout << "  New family: " << new_family << std::endl;
701     std::cout << "  Old active: " << fontlister->get_font_family_row() << std::endl;
702     // std::cout << "  New active: " << act->active << std::endl;
703 #endif
704     if( new_family.compare( fontlister->get_font_family() ) != 0 ) {
705         // Changed font-family
706 
707         if( _font_family_item->get_active() == -1 ) {
708             // New font-family, not in document, not on system (could be fallback list)
709             fontlister->insert_font_family( new_family );
710 
711             // This just sets a variable in the ComboBoxEntryAction object...
712             // shouldn't we also set the actual active row in the combobox?
713             _font_family_item->set_active(0); // New family is always at top of list.
714         }
715 
716         fontlister->set_font_family( _font_family_item->get_active() );
717         // active text set in sp_text_toolbox_selection_changed()
718 
719         SPCSSAttr *css = sp_repr_css_attr_new ();
720         fontlister->fill_css( css );
721 
722         SPDesktop   *desktop    = _desktop;
723         if( desktop->getSelection()->isEmpty() ) {
724             // Update default
725             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
726             prefs->mergeStyle("/tools/text/style", css);
727         } else {
728             // If there is a selection, update
729             sp_desktop_set_style (desktop, css, true, true); // Results in selection change called twice.
730             DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT,
731                                _("Text: Change font family"));
732         }
733         sp_repr_css_attr_unref (css);
734     }
735 
736     // unfreeze
737     _freeze = false;
738 
739 #ifdef DEBUG_TEXT
740     std::cout << "sp_text_toolbox_fontfamily_changes: exit"  << std::endl;
741     std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" << std::endl;
742     std::cout << std::endl;
743 #endif
744 }
745 
746 GtkWidget *
create(SPDesktop * desktop)747 TextToolbar::create(SPDesktop *desktop)
748 {
749     auto tb = Gtk::manage(new TextToolbar(desktop));
750     return GTK_WIDGET(tb->gobj());
751 }
752 
753 void
fontsize_value_changed()754 TextToolbar::fontsize_value_changed()
755 {
756     // quit if run by the _changed callbacks
757     if (_freeze) {
758         return;
759     }
760     _freeze = true;
761 
762     auto active_text = _font_size_item->get_active_text();
763     char const *text = active_text.c_str();
764     gchar *endptr;
765     gdouble size = g_strtod( text, &endptr );
766     if (endptr == text) {  // Conversion failed, non-numeric input.
767         g_warning( "Conversion of size text to double failed, input: %s\n", text );
768         _freeze = false;
769         return;
770     }
771 
772     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
773     int max_size = prefs->getInt("/dialogs/textandfont/maxFontSize", 10000); // somewhat arbitrary, but text&font preview freezes with too huge fontsizes
774 
775     if (size > max_size)
776         size = max_size;
777 
778     // Set css font size.
779     SPCSSAttr *css = sp_repr_css_attr_new ();
780     Inkscape::CSSOStringStream osfs;
781     int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
782     if (prefs->getBool("/options/font/textOutputPx", true)) {
783         osfs << sp_style_css_size_units_to_px(size, unit) << sp_style_get_css_unit_string(SP_CSS_UNIT_PX);
784     } else {
785         osfs << size << sp_style_get_css_unit_string(unit);
786     }
787     sp_repr_css_set_property (css, "font-size", osfs.str().c_str());
788     double factor = size / selection_fontsize;
789 
790     // Apply font size to selected objects.
791     text_outer_set_style(css);
792 
793     Unit const *unit_lh = _tracker->getActiveUnit();
794     g_return_if_fail(unit_lh != nullptr);
795     if (!is_relative(unit_lh) && _outer) {
796         double lineheight = _line_height_adj->get_value();
797         _freeze = false;
798         _line_height_adj->set_value(lineheight * factor);
799         _freeze = true;
800     }
801     // If no selected objects, set default.
802     SPStyle query(_desktop->getDocument());
803     int result_numbers =
804         sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
805     if (result_numbers == QUERY_STYLE_NOTHING)
806     {
807         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
808         prefs->mergeStyle("/tools/text/style", css);
809     } else {
810         // Save for undo
811         sp_desktop_set_style (_desktop, css, true, true);
812         DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:size", SP_VERB_NONE,
813                              _("Text: Change font size"));
814     }
815 
816     sp_repr_css_attr_unref(css);
817 
818     _freeze = false;
819 }
820 
821 void
fontstyle_value_changed()822 TextToolbar::fontstyle_value_changed()
823 {
824     // quit if run by the _changed callbacks
825     if (_freeze) {
826         return;
827     }
828     _freeze = true;
829 
830     Glib::ustring new_style = _font_style_item->get_active_text();
831 
832     Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
833 
834     if( new_style.compare( fontlister->get_font_style() ) != 0 ) {
835 
836         fontlister->set_font_style( new_style );
837         // active text set in sp_text_toolbox_seletion_changed()
838 
839         SPCSSAttr *css = sp_repr_css_attr_new ();
840         fontlister->fill_css( css );
841 
842         SPDesktop   *desktop    = _desktop;
843         sp_desktop_set_style (desktop, css, true, true);
844 
845 
846         // If no selected objects, set default.
847         SPStyle query(_desktop->getDocument());
848         int result_style =
849             sp_desktop_query_style (desktop, &query, QUERY_STYLE_PROPERTY_FONTSTYLE);
850         if (result_style == QUERY_STYLE_NOTHING) {
851             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
852             prefs->mergeStyle("/tools/text/style", css);
853         } else {
854             // Save for undo
855             DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_TEXT,
856                                _("Text: Change font style"));
857         }
858 
859         sp_repr_css_attr_unref (css);
860 
861     }
862 
863     _freeze = false;
864 }
865 
866 // Handles both Superscripts and Subscripts
867 void
script_changed(Gtk::ToggleToolButton * btn)868 TextToolbar::script_changed(Gtk::ToggleToolButton *btn)
869 {
870     // quit if run by the _changed callbacks
871     if (_freeze) {
872         return;
873     }
874 
875     _freeze = true;
876 
877     // Called by Superscript or Subscript button?
878     auto name = btn->get_name();
879     gint prop = (btn == _superscript_item) ? 0 : 1;
880 
881 #ifdef DEBUG_TEXT
882     std::cout << "TextToolbar::script_changed: " << prop << std::endl;
883 #endif
884 
885     // Query baseline
886     SPStyle query(_desktop->getDocument());
887     int result_baseline = sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_BASELINES);
888 
889     bool setSuper = false;
890     bool setSub   = false;
891 
892     if (Inkscape::is_query_style_updateable(result_baseline)) {
893         // If not set or mixed, turn on superscript or subscript
894         if( prop == 0 ) {
895             setSuper = true;
896         } else {
897             setSub = true;
898         }
899     } else {
900         // Superscript
901         gboolean superscriptSet = (query.baseline_shift.set &&
902                                    query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
903                                    query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUPER );
904 
905         // Subscript
906         gboolean subscriptSet = (query.baseline_shift.set &&
907                                  query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
908                                  query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUB );
909 
910         setSuper = !superscriptSet && prop == 0;
911         setSub   = !subscriptSet   && prop == 1;
912     }
913 
914     // Set css properties
915     SPCSSAttr *css = sp_repr_css_attr_new ();
916     if( setSuper || setSub ) {
917         // Openoffice 2.3 and Adobe use 58%, Microsoft Word 2002 uses 65%, LaTex about 70%.
918         // 58% looks too small to me, especially if a superscript is placed on a superscript.
919         // If you make a change here, consider making a change to baseline-shift amount
920         // in style.cpp.
921         sp_repr_css_set_property (css, "font-size", "65%");
922     } else {
923         sp_repr_css_set_property (css, "font-size", "");
924     }
925     if( setSuper ) {
926         sp_repr_css_set_property (css, "baseline-shift", "super");
927     } else if( setSub ) {
928         sp_repr_css_set_property (css, "baseline-shift", "sub");
929     } else {
930         sp_repr_css_set_property (css, "baseline-shift", "baseline");
931     }
932 
933     // Apply css to selected objects.
934     SPDesktop *desktop = _desktop;
935     sp_desktop_set_style (desktop, css, true, false);
936 
937     // Save for undo
938     if(result_baseline != QUERY_STYLE_NOTHING) {
939         DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:script", SP_VERB_NONE,
940                              _("Text: Change superscript or subscript"));
941     }
942     _freeze = false;
943 }
944 
945 void
align_mode_changed(int mode)946 TextToolbar::align_mode_changed(int mode)
947 {
948     // quit if run by the _changed callbacks
949     if (_freeze) {
950         return;
951     }
952     _freeze = true;
953 
954     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
955     prefs->setInt("/tools/text/align_mode", mode);
956 
957     SPDesktop *desktop = _desktop;
958 
959     // move the x of all texts to preserve the same bbox
960     Inkscape::Selection *selection = desktop->getSelection();
961     auto itemlist= selection->items();
962     for (auto i : itemlist) {
963         SPText *text = dynamic_cast<SPText *>(i);
964         // SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
965         if (text) {
966             SPItem *item = i;
967 
968             unsigned writing_mode = item->style->writing_mode.value;
969             // below, variable names suggest horizontal move, but we check the writing direction
970             // and move in the corresponding axis
971             Geom::Dim2 axis;
972             if (writing_mode == SP_CSS_WRITING_MODE_LR_TB || writing_mode == SP_CSS_WRITING_MODE_RL_TB) {
973                 axis = Geom::X;
974             } else {
975                 axis = Geom::Y;
976             }
977 
978             Geom::OptRect bbox = item->geometricBounds();
979             if (!bbox)
980                 continue;
981             double width = bbox->dimensions()[axis];
982             // If you want to align within some frame, other than the text's own bbox, calculate
983             // the left and right (or top and bottom for tb text) slacks of the text inside that
984             // frame (currently unused)
985             double left_slack = 0;
986             double right_slack = 0;
987             unsigned old_align = item->style->text_align.value;
988             double move = 0;
989             if (old_align == SP_CSS_TEXT_ALIGN_START || old_align == SP_CSS_TEXT_ALIGN_LEFT) {
990                 switch (mode) {
991                     case 0:
992                         move = -left_slack;
993                         break;
994                     case 1:
995                         move = width/2 + (right_slack - left_slack)/2;
996                         break;
997                     case 2:
998                         move = width + right_slack;
999                         break;
1000                 }
1001             } else if (old_align == SP_CSS_TEXT_ALIGN_CENTER) {
1002                 switch (mode) {
1003                     case 0:
1004                         move = -width/2 - left_slack;
1005                         break;
1006                     case 1:
1007                         move = (right_slack - left_slack)/2;
1008                         break;
1009                     case 2:
1010                         move = width/2 + right_slack;
1011                         break;
1012                 }
1013             } else if (old_align == SP_CSS_TEXT_ALIGN_END || old_align == SP_CSS_TEXT_ALIGN_RIGHT) {
1014                 switch (mode) {
1015                     case 0:
1016                         move = -width - left_slack;
1017                         break;
1018                     case 1:
1019                         move = -width/2 + (right_slack - left_slack)/2;
1020                         break;
1021                     case 2:
1022                         move = right_slack;
1023                         break;
1024                 }
1025             }
1026             Geom::Point XY = SP_TEXT(item)->attributes.firstXY();
1027             if (axis == Geom::X) {
1028                 XY = XY + Geom::Point (move, 0);
1029             } else {
1030                 XY = XY + Geom::Point (0, move);
1031             }
1032             SP_TEXT(item)->attributes.setFirstXY(XY);
1033             item->updateRepr();
1034             item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1035         }
1036     }
1037 
1038     SPCSSAttr *css = sp_repr_css_attr_new ();
1039     switch (mode)
1040     {
1041         case 0:
1042         {
1043             sp_repr_css_set_property (css, "text-anchor", "start");
1044             sp_repr_css_set_property (css, "text-align", "start");
1045             break;
1046         }
1047         case 1:
1048         {
1049             sp_repr_css_set_property (css, "text-anchor", "middle");
1050             sp_repr_css_set_property (css, "text-align", "center");
1051             break;
1052         }
1053 
1054         case 2:
1055         {
1056             sp_repr_css_set_property (css, "text-anchor", "end");
1057             sp_repr_css_set_property (css, "text-align", "end");
1058             break;
1059         }
1060 
1061         case 3:
1062         {
1063             sp_repr_css_set_property (css, "text-anchor", "start");
1064             sp_repr_css_set_property (css, "text-align", "justify");
1065             break;
1066         }
1067     }
1068 
1069     SPStyle query(_desktop->getDocument());
1070     int result_numbers =
1071         sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1072 
1073     // If querying returned nothing, update default style.
1074     if (result_numbers == QUERY_STYLE_NOTHING)
1075     {
1076         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1077         prefs->mergeStyle("/tools/text/style", css);
1078     }
1079 
1080     sp_desktop_set_style (desktop, css, true, true);
1081     if (result_numbers != QUERY_STYLE_NOTHING)
1082     {
1083         DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_TEXT,
1084                        _("Text: Change alignment"));
1085     }
1086     sp_repr_css_attr_unref (css);
1087 
1088     desktop->getCanvas()->grab_focus();
1089 
1090     _freeze = false;
1091 }
1092 
1093 void
writing_mode_changed(int mode)1094 TextToolbar::writing_mode_changed(int mode)
1095 {
1096     // quit if run by the _changed callbacks
1097     if (_freeze) {
1098         return;
1099     }
1100     _freeze = true;
1101 
1102     SPCSSAttr   *css        = sp_repr_css_attr_new ();
1103     switch (mode)
1104     {
1105         case 0:
1106             {
1107                 sp_repr_css_set_property (css, "writing-mode", "lr-tb");
1108                 break;
1109             }
1110 
1111         case 1:
1112             {
1113                 sp_repr_css_set_property (css, "writing-mode", "tb-rl");
1114                 break;
1115             }
1116 
1117         case 2:
1118             {
1119                 sp_repr_css_set_property (css, "writing-mode", "vertical-lr");
1120                 break;
1121             }
1122     }
1123 
1124     SPStyle query(_desktop->getDocument());
1125     int result_numbers =
1126         sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
1127 
1128     // If querying returned nothing, update default style.
1129     if (result_numbers == QUERY_STYLE_NOTHING)
1130     {
1131         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1132         prefs->mergeStyle("/tools/text/style", css);
1133     }
1134 
1135     sp_desktop_set_style (_desktop, css, true, true);
1136     if(result_numbers != QUERY_STYLE_NOTHING)
1137     {
1138         DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_TEXT,
1139                        _("Text: Change writing mode"));
1140     }
1141     sp_repr_css_attr_unref (css);
1142 
1143     _desktop->getCanvas()->grab_focus();
1144 
1145     _freeze = false;
1146 }
1147 
1148 void
orientation_changed(int mode)1149 TextToolbar::orientation_changed(int mode)
1150 {
1151     // quit if run by the _changed callbacks
1152     if (_freeze) {
1153         return;
1154     }
1155     _freeze = true;
1156 
1157     SPCSSAttr   *css        = sp_repr_css_attr_new ();
1158     switch (mode)
1159     {
1160         case 0:
1161         {
1162             sp_repr_css_set_property (css, "text-orientation", "auto");
1163             break;
1164         }
1165 
1166         case 1:
1167         {
1168             sp_repr_css_set_property (css, "text-orientation", "upright");
1169             break;
1170         }
1171 
1172         case 2:
1173         {
1174             sp_repr_css_set_property (css, "text-orientation", "sideways");
1175             break;
1176         }
1177     }
1178 
1179     SPStyle query(_desktop->getDocument());
1180     int result_numbers =
1181         sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
1182 
1183     // If querying returned nothing, update default style.
1184     if (result_numbers == QUERY_STYLE_NOTHING)
1185     {
1186         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1187         prefs->mergeStyle("/tools/text/style", css);
1188     }
1189 
1190     sp_desktop_set_style (_desktop, css, true, true);
1191     if(result_numbers != QUERY_STYLE_NOTHING)
1192     {
1193         DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_TEXT,
1194                        _("Text: Change orientation"));
1195     }
1196     sp_repr_css_attr_unref (css);
1197     _desktop->canvas->grab_focus();
1198 
1199     _freeze = false;
1200 }
1201 
1202 void
direction_changed(int mode)1203 TextToolbar::direction_changed(int mode)
1204 {
1205     // quit if run by the _changed callbacks
1206     if (_freeze) {
1207         return;
1208     }
1209     _freeze = true;
1210 
1211     SPCSSAttr   *css        = sp_repr_css_attr_new ();
1212     switch (mode)
1213     {
1214         case 0:
1215         {
1216             sp_repr_css_set_property (css, "direction", "ltr");
1217             break;
1218         }
1219 
1220         case 1:
1221         {
1222             sp_repr_css_set_property (css, "direction", "rtl");
1223             break;
1224         }
1225     }
1226 
1227     SPStyle query(_desktop->getDocument());
1228     int result_numbers =
1229         sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
1230 
1231     // If querying returned nothing, update default style.
1232     if (result_numbers == QUERY_STYLE_NOTHING)
1233     {
1234         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1235         prefs->mergeStyle("/tools/text/style", css);
1236     }
1237 
1238     sp_desktop_set_style (_desktop, css, true, true);
1239     if(result_numbers != QUERY_STYLE_NOTHING)
1240     {
1241         DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_TEXT,
1242                        _("Text: Change direction"));
1243     }
1244     sp_repr_css_attr_unref (css);
1245 
1246     _desktop->getCanvas()->grab_focus();
1247 
1248     _freeze = false;
1249 }
1250 
1251 void
lineheight_value_changed()1252 TextToolbar::lineheight_value_changed()
1253 {
1254     // quit if run by the _changed callbacks or is not text tool
1255     if (_freeze || !SP_IS_TEXT_CONTEXT(_desktop->event_context)) {
1256         return;
1257     }
1258 
1259     _freeze = true;
1260     SPDesktop *desktop = _desktop;
1261     // Get user selected unit and save as preference
1262     Unit const *unit = _tracker->getActiveUnit();
1263     // @Tav same disabled unit
1264     g_return_if_fail(unit != nullptr);
1265 
1266     // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit so
1267     // we can save it (allows us to adjust line height value when unit changes).
1268 
1269     // Set css line height.
1270     SPCSSAttr *css = sp_repr_css_attr_new ();
1271     Inkscape::CSSOStringStream osfs;
1272     if ( is_relative(unit) ) {
1273         osfs << _line_height_adj->get_value() << unit->abbr;
1274     } else {
1275         // Inside SVG file, always use "px" for absolute units.
1276         osfs << Quantity::convert(_line_height_adj->get_value(), unit, "px") << "px";
1277     }
1278 
1279     sp_repr_css_set_property (css, "line-height", osfs.str().c_str());
1280 
1281     Inkscape::Selection *selection = desktop->getSelection();
1282     auto itemlist = selection->items();
1283     if (_outer) {
1284         // Special else makes this different from other uses of text_outer_set_style
1285         text_outer_set_style(css);
1286     } else {
1287         SPItem *parent = dynamic_cast<SPItem *>(*itemlist.begin());
1288         SPStyle *parent_style = parent->style;
1289         SPCSSAttr *parent_cssatr = sp_css_attr_from_style(parent_style, SP_STYLE_FLAG_IFSET);
1290         Glib::ustring parent_lineheight = sp_repr_css_property(parent_cssatr, "line-height", "1.25");
1291         SPCSSAttr *cssfit = sp_repr_css_attr_new();
1292         sp_repr_css_set_property(cssfit, "line-height", parent_lineheight.c_str());
1293         double minheight = 0;
1294         if (parent_style) {
1295             minheight = parent_style->line_height.computed;
1296         }
1297         if (minheight) {
1298             for (auto i : parent->childList(false)) {
1299                 SPItem *child = dynamic_cast<SPItem *>(i);
1300                 if (!child) {
1301                     continue;
1302                 }
1303                 recursively_set_properties(child, cssfit);
1304             }
1305         }
1306         sp_repr_css_set_property(cssfit, "line-height", "0");
1307         parent->changeCSS(cssfit, "style");
1308         subselection_wrap_toggle(true);
1309         sp_desktop_set_style(desktop, css, true, true);
1310         subselection_wrap_toggle(false);
1311         sp_repr_css_attr_unref(cssfit);
1312     }
1313     // Only need to save for undo if a text item has been changed.
1314     itemlist = selection->items();
1315     bool modmade = false;
1316     for (auto i : itemlist) {
1317         SPText *text = dynamic_cast<SPText *>(i);
1318         SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
1319         if (text || flowtext) {
1320             modmade = true;
1321             break;
1322         }
1323     }
1324 
1325     // Save for undo
1326     if (modmade) {
1327         // Call ensureUpToDate() causes rebuild of text layout (with all proper style
1328         // cascading, etc.). For multi-line text with sodipodi::role="line", we must explicitly
1329         // save new <tspan> 'x' and 'y' attribute values by calling updateRepr().
1330         // Partial fix for bug #1590141.
1331 
1332         desktop->getDocument()->ensureUpToDate();
1333         for (auto i : itemlist) {
1334             SPText *text = dynamic_cast<SPText *>(i);
1335             SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
1336             if (text || flowtext) {
1337                 (i)->updateRepr();
1338             }
1339         }
1340         if (!_outer) {
1341             prepare_inner();
1342         }
1343         DocumentUndo::maybeDone(desktop->getDocument(), "ttb:line-height", SP_VERB_NONE, _("Text: Change line-height"));
1344     }
1345 
1346     // If no selected objects, set default.
1347     SPStyle query(_desktop->getDocument());
1348     int result_numbers = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1349     if (result_numbers == QUERY_STYLE_NOTHING)
1350     {
1351         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1352         prefs->mergeStyle("/tools/text/style", css);
1353     }
1354 
1355     sp_repr_css_attr_unref (css);
1356 
1357     _freeze = false;
1358 }
1359 
1360 void
lineheight_unit_changed(int)1361 TextToolbar::lineheight_unit_changed(int /* Not Used */)
1362 {
1363     // quit if run by the _changed callbacks or is not text tool
1364     if (_freeze || !SP_IS_TEXT_CONTEXT(_desktop->event_context)) {
1365         return;
1366     }
1367     _freeze = true;
1368 
1369     // Get old saved unit
1370     int old_unit = _lineheight_unit;
1371 
1372     // Get user selected unit and save as preference
1373     Unit const *unit = _tracker->getActiveUnit();
1374     g_return_if_fail(unit != nullptr);
1375     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1376 
1377     // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit.
1378     SPILength temp_length;
1379     Inkscape::CSSOStringStream temp_stream;
1380     temp_stream << 1 << unit->abbr;
1381     temp_length.read(temp_stream.str().c_str());
1382     prefs->setInt("/tools/text/lineheight/display_unit", temp_length.unit);
1383     if (old_unit == temp_length.unit) {
1384         _freeze = false;
1385         return;
1386     }
1387 
1388     // Read current line height value
1389     double line_height = _line_height_adj->get_value();
1390     SPDesktop *desktop = _desktop;
1391     Inkscape::Selection *selection = desktop->getSelection();
1392     auto itemlist = selection->items();
1393 
1394     // Convert between units
1395     double font_size = 0;
1396     double doc_scale = 1;
1397     int count = 0;
1398     bool has_flow = false;
1399 
1400     for (auto i : itemlist) {
1401         SPText *text = dynamic_cast<SPText *>(i);
1402         SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
1403         if (text || flowtext) {
1404             doc_scale = Geom::Affine(i->i2dt_affine()).descrim();
1405             font_size += i->style->font_size.computed * doc_scale;
1406             ++count;
1407         }
1408         if (flowtext) {
1409             has_flow = true;
1410         }
1411     }
1412     if (count > 0) {
1413         font_size /= count;
1414     } else {
1415         font_size = 20;
1416     }
1417     if ((unit->abbr == "" || unit->abbr == "em") && (old_unit == SP_CSS_UNIT_NONE || old_unit == SP_CSS_UNIT_EM)) {
1418         // Do nothing
1419     } else if ((unit->abbr == "" || unit->abbr == "em") && old_unit == SP_CSS_UNIT_EX) {
1420         line_height *= 0.5;
1421     } else if ((unit->abbr) == "ex" && (old_unit == SP_CSS_UNIT_EM || old_unit == SP_CSS_UNIT_NONE)) {
1422         line_height *= 2.0;
1423     } else if ((unit->abbr == "" || unit->abbr == "em") && old_unit == SP_CSS_UNIT_PERCENT) {
1424         line_height /= 100.0;
1425     } else if ((unit->abbr) == "%" && (old_unit == SP_CSS_UNIT_EM || old_unit == SP_CSS_UNIT_NONE)) {
1426         line_height *= 100;
1427     } else if ((unit->abbr) == "ex" && old_unit == SP_CSS_UNIT_PERCENT) {
1428         line_height /= 50.0;
1429     } else if ((unit->abbr) == "%" && old_unit == SP_CSS_UNIT_EX) {
1430         line_height *= 50;
1431     } else if (is_relative(unit)) {
1432         // Convert absolute to relative... for the moment use average font-size
1433         if (old_unit == SP_CSS_UNIT_NONE) old_unit = SP_CSS_UNIT_EM;
1434         line_height = Quantity::convert(line_height, sp_style_get_css_unit_string(old_unit), "px");
1435 
1436         if (font_size > 0) {
1437             line_height /= font_size;
1438         }
1439         if ((unit->abbr) == "%") {
1440             line_height *= 100;
1441         } else if ((unit->abbr) == "ex") {
1442             line_height *= 2;
1443         }
1444     } else if (old_unit == SP_CSS_UNIT_NONE || old_unit == SP_CSS_UNIT_PERCENT || old_unit == SP_CSS_UNIT_EM ||
1445             old_unit == SP_CSS_UNIT_EX) {
1446         // Convert relative to absolute... for the moment use average font-size
1447         if (old_unit == SP_CSS_UNIT_PERCENT) {
1448             line_height /= 100.0;
1449         } else if (old_unit == SP_CSS_UNIT_EX) {
1450             line_height /= 2.0;
1451         }
1452         line_height *= font_size;
1453         line_height = Quantity::convert(line_height, "px", unit);
1454     } else {
1455         // Convert between different absolute units (only used in GUI)
1456         line_height = Quantity::convert(line_height, sp_style_get_css_unit_string(old_unit), unit);
1457     }
1458     // Set css line height.
1459     SPCSSAttr *css = sp_repr_css_attr_new ();
1460     Inkscape::CSSOStringStream osfs;
1461     // Set css line height.
1462     if ( is_relative(unit) ) {
1463         osfs << line_height << unit->abbr;
1464     } else {
1465         osfs << Quantity::convert(line_height, unit, "px") << "px";
1466     }
1467     sp_repr_css_set_property (css, "line-height", osfs.str().c_str());
1468 
1469     // Update GUI with line_height value.
1470     _line_height_adj->set_value(line_height);
1471     // Update "climb rate"  The custom action has a step property but no way to set it.
1472     if (unit->abbr == "%") {
1473         _line_height_adj->set_step_increment(1.0);
1474         _line_height_adj->set_page_increment(10.0);
1475     } else {
1476         _line_height_adj->set_step_increment(0.1);
1477         _line_height_adj->set_page_increment(1.0);
1478     }
1479     // Internal function to set line-height which is spacing mode dependent.
1480     SPItem *parent = itemlist.empty() ? nullptr : dynamic_cast<SPItem *>(*itemlist.begin());
1481     SPStyle *parent_style = nullptr;
1482     if (parent) {
1483         parent_style = parent->style;
1484     }
1485     bool inside = false;
1486     if (_outer) {
1487         if (!selection->singleItem() || !parent_style || parent_style->line_height.computed != 0) {
1488             for (auto i = itemlist.begin(); i != itemlist.end(); ++i) {
1489                 if (dynamic_cast<SPText *>(*i) || dynamic_cast<SPFlowtext *>(*i)) {
1490                     SPItem *item = *i;
1491                     // Scale by inverse of accumulated parent transform
1492                     SPCSSAttr *css_set = sp_repr_css_attr_new();
1493                     sp_repr_css_merge(css_set, css);
1494                     Geom::Affine const local(item->i2doc_affine());
1495                     double const ex(local.descrim());
1496                     if ((ex != 0.0) && (ex != 1.0)) {
1497                         sp_css_attr_scale(css_set, 1 / ex);
1498                     }
1499                     recursively_set_properties(item, css_set);
1500                     sp_repr_css_attr_unref(css_set);
1501                 }
1502             }
1503         } else {
1504             inside = true;
1505         }
1506     }
1507     if (!_outer || inside) {
1508         SPCSSAttr *parent_cssatr = sp_css_attr_from_style(parent_style, SP_STYLE_FLAG_IFSET);
1509         Glib::ustring parent_lineheight = sp_repr_css_property(parent_cssatr, "line-height", "1.25");
1510         SPCSSAttr *cssfit = sp_repr_css_attr_new();
1511         sp_repr_css_set_property(cssfit, "line-height", parent_lineheight.c_str());
1512         double minheight = 0;
1513         if (parent_style) {
1514             minheight = parent_style->line_height.computed;
1515         }
1516         if (minheight) {
1517             for (auto i : parent->childList(false)) {
1518                 SPItem *child = dynamic_cast<SPItem *>(i);
1519                 if (!child) {
1520                     continue;
1521                 }
1522                 recursively_set_properties(child, cssfit);
1523             }
1524         }
1525         sp_repr_css_set_property(cssfit, "line-height", "0");
1526         parent->changeCSS(cssfit, "style");
1527         subselection_wrap_toggle(true);
1528         sp_desktop_set_style(desktop, css, true, true);
1529         subselection_wrap_toggle(false);
1530         sp_repr_css_attr_unref(cssfit);
1531     }
1532     itemlist= selection->items();
1533     // Only need to save for undo if a text item has been changed.
1534     bool modmade = false;
1535     for (auto i : itemlist) {
1536         SPText *text = dynamic_cast<SPText *>(i);
1537         SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
1538         if (text || flowtext) {
1539             modmade = true;
1540             break;
1541         }
1542     }
1543     // Save for undo
1544     if(modmade) {
1545         // Call ensureUpToDate() causes rebuild of text layout (with all proper style
1546         // cascading, etc.). For multi-line text with sodipodi::role="line", we must explicitly
1547         // save new <tspan> 'x' and 'y' attribute values by calling updateRepr().
1548         // Partial fix for bug #1590141.
1549 
1550         desktop->getDocument()->ensureUpToDate();
1551         for (auto i : itemlist) {
1552             SPText *text = dynamic_cast<SPText *>(i);
1553             SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
1554             if (text || flowtext) {
1555                 (i)->updateRepr();
1556             }
1557         }
1558         if (_outer) {
1559             prepare_inner();
1560         }
1561         DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:line-height", SP_VERB_NONE,
1562                              _("Text: Change line-height unit"));
1563     }
1564 
1565     // If no selected objects, set default.
1566     SPStyle query(_desktop->getDocument());
1567     int result_numbers =
1568         sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1569     if (result_numbers == QUERY_STYLE_NOTHING)
1570     {
1571         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1572         prefs->mergeStyle("/tools/text/style", css);
1573     }
1574 
1575     sp_repr_css_attr_unref (css);
1576 
1577     _freeze = false;
1578 }
1579 
fontsize_unit_changed(int)1580 void TextToolbar::fontsize_unit_changed(int /* Not Used */)
1581 {
1582     // quit if run by the _changed callbacks
1583     Unit const *unit = _tracker_fs->getActiveUnit();
1584     g_return_if_fail(unit != nullptr);
1585     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1586 
1587     // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit.
1588     SPILength temp_size;
1589     Inkscape::CSSOStringStream temp_size_stream;
1590     temp_size_stream << 1 << unit->abbr;
1591     temp_size.read(temp_size_stream.str().c_str());
1592     prefs->setInt("/options/font/unitType", temp_size.unit);
1593     selection_changed(_desktop->selection);
1594 }
1595 
1596 void
wordspacing_value_changed()1597 TextToolbar::wordspacing_value_changed()
1598 {
1599     // quit if run by the _changed callbacks
1600     if (_freeze) {
1601         return;
1602     }
1603     _freeze = true;
1604 
1605     // At the moment this handles only numerical values (i.e. no em unit).
1606     // Set css word-spacing
1607     SPCSSAttr *css = sp_repr_css_attr_new ();
1608     Inkscape::CSSOStringStream osfs;
1609     osfs << _word_spacing_adj->get_value() << "px"; // For now always use px
1610     sp_repr_css_set_property (css, "word-spacing", osfs.str().c_str());
1611     text_outer_set_style(css);
1612 
1613     // If no selected objects, set default.
1614     SPStyle query(_desktop->getDocument());
1615     int result_numbers =
1616         sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1617     if (result_numbers == QUERY_STYLE_NOTHING)
1618     {
1619         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1620         prefs->mergeStyle("/tools/text/style", css);
1621     } else {
1622         // Save for undo
1623         DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:word-spacing", SP_VERB_NONE,
1624                              _("Text: Change word-spacing"));
1625     }
1626 
1627     sp_repr_css_attr_unref (css);
1628 
1629     _freeze = false;
1630 }
1631 
1632 void
letterspacing_value_changed()1633 TextToolbar::letterspacing_value_changed()
1634 {
1635     // quit if run by the _changed callbacks
1636     if (_freeze) {
1637         return;
1638     }
1639     _freeze = true;
1640 
1641     // At the moment this handles only numerical values (i.e. no em unit).
1642     // Set css letter-spacing
1643     SPCSSAttr *css = sp_repr_css_attr_new ();
1644     Inkscape::CSSOStringStream osfs;
1645     osfs << _letter_spacing_adj->get_value() << "px";  // For now always use px
1646     sp_repr_css_set_property (css, "letter-spacing", osfs.str().c_str());
1647     text_outer_set_style(css);
1648 
1649     // If no selected objects, set default.
1650     SPStyle query(_desktop->getDocument());
1651     int result_numbers =
1652         sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1653     if (result_numbers == QUERY_STYLE_NOTHING)
1654     {
1655         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1656         prefs->mergeStyle("/tools/text/style", css);
1657     }
1658     else
1659     {
1660         // Save for undo
1661         DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:letter-spacing", SP_VERB_NONE,
1662                              _("Text: Change letter-spacing"));
1663     }
1664 
1665     sp_repr_css_attr_unref (css);
1666 
1667     _freeze = false;
1668 }
1669 
1670 void
dx_value_changed()1671 TextToolbar::dx_value_changed()
1672 {
1673     // quit if run by the _changed callbacks
1674     if (_freeze) {
1675         return;
1676     }
1677     _freeze = true;
1678 
1679     gdouble new_dx = _dx_adj->get_value();
1680     bool modmade = false;
1681 
1682     if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
1683         Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
1684         if( tc ) {
1685             unsigned char_index = -1;
1686             TextTagAttributes *attributes =
1687                 text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
1688             if( attributes ) {
1689                 double old_dx = attributes->getDx( char_index );
1690                 double delta_dx = new_dx - old_dx;
1691                 sp_te_adjust_dx( tc->text, tc->text_sel_start, tc->text_sel_end, _desktop, delta_dx );
1692                 modmade = true;
1693             }
1694         }
1695     }
1696 
1697     if(modmade) {
1698         // Save for undo
1699         DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:dx", SP_VERB_NONE,
1700                              _("Text: Change dx (kern)"));
1701     }
1702     _freeze = false;
1703 }
1704 
1705 void
dy_value_changed()1706 TextToolbar::dy_value_changed()
1707 {
1708     // quit if run by the _changed callbacks
1709     if (_freeze) {
1710         return;
1711     }
1712     _freeze = true;
1713 
1714     gdouble new_dy = _dy_adj->get_value();
1715     bool modmade = false;
1716 
1717     if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
1718         Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
1719         if( tc ) {
1720             unsigned char_index = -1;
1721             TextTagAttributes *attributes =
1722                 text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
1723             if( attributes ) {
1724                 double old_dy = attributes->getDy( char_index );
1725                 double delta_dy = new_dy - old_dy;
1726                 sp_te_adjust_dy( tc->text, tc->text_sel_start, tc->text_sel_end, _desktop, delta_dy );
1727                 modmade = true;
1728             }
1729         }
1730     }
1731 
1732     if(modmade) {
1733         // Save for undo
1734         DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:dy", SP_VERB_NONE,
1735                             _("Text: Change dy"));
1736     }
1737 
1738     _freeze = false;
1739 }
1740 
1741 void
rotation_value_changed()1742 TextToolbar::rotation_value_changed()
1743 {
1744     // quit if run by the _changed callbacks
1745     if (_freeze) {
1746         return;
1747     }
1748     _freeze = true;
1749 
1750     gdouble new_degrees = _rotation_adj->get_value();
1751 
1752     bool modmade = false;
1753     if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
1754         Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
1755         if( tc ) {
1756             unsigned char_index = -1;
1757             TextTagAttributes *attributes =
1758                 text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
1759             if( attributes ) {
1760                 double old_degrees = attributes->getRotate( char_index );
1761                 double delta_deg = new_degrees - old_degrees;
1762                 sp_te_adjust_rotation( tc->text, tc->text_sel_start, tc->text_sel_end, _desktop, delta_deg );
1763                 modmade = true;
1764             }
1765         }
1766     }
1767 
1768     // Save for undo
1769     if(modmade) {
1770         DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:rotate", SP_VERB_NONE,
1771                             _("Text: Change rotate"));
1772     }
1773 
1774     _freeze = false;
1775 }
1776 
selection_modified_select_tool(Inkscape::Selection * selection,guint flags)1777 void TextToolbar::selection_modified_select_tool(Inkscape::Selection *selection, guint flags)
1778 {
1779     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1780     double factor = prefs->getDouble("/options/font/scaleLineHeightFromFontSIze", 1.0);
1781     if (factor != 1.0) {
1782         Unit const *unit_lh = _tracker->getActiveUnit();
1783         g_return_if_fail(unit_lh != nullptr);
1784         if (!is_relative(unit_lh) && _outer) {
1785             double lineheight = _line_height_adj->get_value();
1786             bool is_freeze = _freeze;
1787             _freeze = false;
1788             _line_height_adj->set_value(lineheight * factor);
1789             _freeze = is_freeze;
1790         }
1791         prefs->setDouble("/options/font/scaleLineHeightFromFontSIze", 1.0);
1792     }
1793 }
1794 
selection_changed(Inkscape::Selection * selection)1795 void TextToolbar::selection_changed(Inkscape::Selection *selection) // don't bother to update font list if subsel
1796                                                                          // changed
1797 {
1798 #ifdef DEBUG_TEXT
1799     static int count = 0;
1800     ++count;
1801     std::cout << std::endl;
1802     std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
1803     std::cout << "sp_text_toolbox_selection_changed: start " << count << std::endl;
1804 #endif
1805 
1806     // quit if run by the _changed callbacks
1807     if (_freeze) {
1808 
1809 #ifdef DEBUG_TEXT
1810         std::cout << "    Frozen, returning" << std::endl;
1811         std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
1812         std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
1813         std::cout << std::endl;
1814 #endif
1815         return;
1816     }
1817     _freeze = true;
1818 
1819     // selection defined as argument but not used, argh!!!
1820     SPDesktop *desktop = _desktop;
1821     SPDocument *document = _desktop->getDocument();
1822     selection = desktop->getSelection();
1823     auto itemlist = selection->items();
1824 
1825 #ifdef DEBUG_TEXT
1826     for(auto i : itemlist) {
1827         const gchar* id = i->getId();
1828         std::cout << "    " << id << std::endl;
1829     }
1830     Glib::ustring selected_text = sp_text_get_selected_text(_desktop->event_context);
1831     std::cout << "  Selected text: |" << selected_text << "|" << std::endl;
1832 #endif
1833 
1834     // Only flowed text can be justified, only normal text can be kerned...
1835     // Find out if we have flowed text now so we can use it several places
1836     gboolean isFlow = false;
1837     std::vector<SPItem *> to_work;
1838     for (auto i : itemlist) {
1839         SPText *text = dynamic_cast<SPText *>(i);
1840         SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
1841         if (text || flowtext) {
1842             to_work.push_back(i);
1843         }
1844         if (flowtext ||
1845             (text && text->style && text->style->shape_inside.set)) {
1846             isFlow = true;
1847         }
1848     }
1849     bool outside = false;
1850     if (selection && to_work.size() == 0) {
1851         outside = true;
1852     }
1853 
1854     Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance();
1855     fontlister->selection_update();
1856     // Update font list, but only if widget already created.
1857     if (_font_family_item->get_combobox() != nullptr) {
1858         _font_family_item->set_active_text(fontlister->get_font_family().c_str(), fontlister->get_font_family_row());
1859         _font_style_item->set_active_text(fontlister->get_font_style().c_str());
1860     }
1861 
1862     /*
1863      * Query from current selection:
1864      *   Font family (font-family)
1865      *   Style (font-weight, font-style, font-stretch, font-variant, font-align)
1866      *   Numbers (font-size, letter-spacing, word-spacing, line-height, text-anchor, writing-mode)
1867      *   Font specification (Inkscape private attribute)
1868      */
1869     SPStyle query(document);
1870     SPStyle query_fallback(document);
1871     int result_family = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTFAMILY);
1872     int result_style = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTSTYLE);
1873     int result_baseline = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_BASELINES);
1874     int result_wmode = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
1875 
1876     // Calling sp_desktop_query_style will result in a call to TextTool::_styleQueried().
1877     // This returns the style of the selected text inside the <text> element... which
1878     // is often the style of one or more <tspan>s. If we want the style of the outer
1879     // <text> objects then we need to bypass the call to TextTool::_styleQueried().
1880     // The desktop selection never includes the elements inside the <text> element.
1881     int result_numbers = 0;
1882     int result_numbers_fallback = 0;
1883     if (!outside) {
1884         if (_outer && this->_sub_active_item) {
1885             std::vector<SPItem *> qactive{ this->_sub_active_item };
1886             SPItem *parent = dynamic_cast<SPItem *>(this->_sub_active_item->parent);
1887             std::vector<SPItem *> qparent{ parent };
1888             result_numbers =
1889                 sp_desktop_query_style_from_list(qactive, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1890             result_numbers_fallback =
1891                 sp_desktop_query_style_from_list(qparent, &query_fallback, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1892         } else if (_outer) {
1893             result_numbers = sp_desktop_query_style_from_list(to_work, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1894         } else {
1895             result_numbers = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1896         }
1897     } else {
1898         result_numbers =
1899                 sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
1900     }
1901 
1902     /*
1903      * If no text in selection (querying returned nothing), read the style from
1904      * the /tools/text preferences (default style for new texts). Return if
1905      * tool bar already set to these preferences.
1906      */
1907     if (result_family  == QUERY_STYLE_NOTHING ||
1908         result_style   == QUERY_STYLE_NOTHING ||
1909         result_numbers == QUERY_STYLE_NOTHING ||
1910         result_wmode   == QUERY_STYLE_NOTHING ) {
1911         // There are no texts in selection, read from preferences.
1912         query.readFromPrefs("/tools/text");
1913 #ifdef DEBUG_TEXT
1914         std::cout << "    read style from prefs:" << std::endl;
1915         sp_print_font( &query );
1916 #endif
1917         if (_text_style_from_prefs) {
1918             // Do not reset the toolbar style from prefs if we already did it last time
1919             _freeze = false;
1920 #ifdef DEBUG_TEXT
1921             std::cout << "    text_style_from_prefs: toolbar already set" << std:: endl;
1922             std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
1923             std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
1924             std::cout << std::endl;
1925 #endif
1926             return;
1927         }
1928 
1929         // To ensure the value of the combobox is properly set on start-up, only mark
1930         // the prefs set if the combobox has already been constructed.
1931         if( _font_family_item->get_combobox() != nullptr ) {
1932             _text_style_from_prefs = true;
1933         }
1934     } else {
1935         _text_style_from_prefs = false;
1936     }
1937 
1938     // If we have valid query data for text (font-family, font-specification) set toolbar accordingly.
1939     {
1940         // Size (average of text selected)
1941 
1942         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1943         int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
1944         double size = 0;
1945         if (!size && _cusor_numbers != QUERY_STYLE_NOTHING) {
1946             size = sp_style_css_size_px_to_units(_query_cursor.font_size.computed, unit);
1947         }
1948         if (!size && result_numbers != QUERY_STYLE_NOTHING) {
1949             size = sp_style_css_size_px_to_units(query.font_size.computed, unit);
1950         }
1951         if (!size && result_numbers_fallback != QUERY_STYLE_NOTHING) {
1952             size = sp_style_css_size_px_to_units(query_fallback.font_size.computed, unit);
1953         }
1954         if (!size && _text_style_from_prefs) {
1955             size = sp_style_css_size_px_to_units(query.font_size.computed, unit);
1956         }
1957 
1958         auto unit_str = sp_style_get_css_unit_string(unit);
1959         Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", unit_str, ")");
1960 
1961         _font_size_item->set_tooltip(tooltip.c_str());
1962 
1963         Inkscape::CSSOStringStream os;
1964         // We dot want to parse values just show
1965 
1966         _tracker_fs->setActiveUnitByAbbr(sp_style_get_css_unit_string(unit));
1967         int rounded_size = std::round(size);
1968         if (std::abs((size - rounded_size)/size) < 0.0001) {
1969             // We use rounded_size to avoid rounding errors when, say, converting stored 'px' values to displayed 'pt' values.
1970             os << rounded_size;
1971             selection_fontsize = rounded_size;
1972         } else {
1973             os << size;
1974             selection_fontsize = size;
1975         }
1976 
1977         // Freeze to ignore callbacks.
1978         //g_object_freeze_notify( G_OBJECT( fontSizeAction->combobox ) );
1979         sp_text_set_sizes(GTK_LIST_STORE(_font_size_item->get_model()), unit);
1980         //g_object_thaw_notify( G_OBJECT( fontSizeAction->combobox ) );
1981 
1982         _font_size_item->set_active_text( os.str().c_str() );
1983 
1984         // Superscript
1985         gboolean superscriptSet =
1986             ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) &&
1987              query.baseline_shift.set &&
1988              query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
1989              query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUPER );
1990 
1991         _superscript_item->set_active(superscriptSet);
1992 
1993         // Subscript
1994         gboolean subscriptSet =
1995             ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) &&
1996              query.baseline_shift.set &&
1997              query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
1998              query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUB );
1999 
2000         _subscript_item->set_active(subscriptSet);
2001 
2002         // Alignment
2003 
2004         // Note: SVG 1.1 doesn't include text-align, SVG 1.2 Tiny doesn't include text-align="justify"
2005         // text-align="justify" was a draft SVG 1.2 item (along with flowed text).
2006         // Only flowed text can be left and right justified at the same time.
2007         // Disable button if we don't have flowed text.
2008 
2009         Glib::RefPtr<Gtk::ListStore> store = _align_item->get_store();
2010         Gtk::TreeModel::Row row = *(store->get_iter("3"));  // Justify entry
2011         UI::Widget::ComboToolItemColumns columns;
2012         row[columns.col_sensitive] = isFlow;
2013 
2014         int activeButton = 0;
2015         if (query.text_align.computed  == SP_CSS_TEXT_ALIGN_JUSTIFY)
2016         {
2017             activeButton = 3;
2018         } else {
2019             // This should take 'direction' into account
2020             if (query.text_anchor.computed == SP_CSS_TEXT_ANCHOR_START)  activeButton = 0;
2021             if (query.text_anchor.computed == SP_CSS_TEXT_ANCHOR_MIDDLE) activeButton = 1;
2022             if (query.text_anchor.computed == SP_CSS_TEXT_ANCHOR_END)    activeButton = 2;
2023         }
2024         _align_item->set_active( activeButton );
2025 
2026         double height = 0;
2027         gint line_height_unit = 0;
2028 
2029         if (!height && _cusor_numbers != QUERY_STYLE_NOTHING) {
2030             height = _query_cursor.line_height.value;
2031             line_height_unit = _query_cursor.line_height.unit;
2032         }
2033 
2034         if (!height && result_numbers != QUERY_STYLE_NOTHING) {
2035             height = query.line_height.value;
2036             line_height_unit = query.line_height.unit;
2037         }
2038 
2039         if (!height && result_numbers_fallback != QUERY_STYLE_NOTHING) {
2040             height = query_fallback.line_height.value;
2041             line_height_unit = query_fallback.line_height.unit;
2042         }
2043 
2044         if (!height && _text_style_from_prefs) {
2045             height = query.line_height.value;
2046             line_height_unit = query.line_height.unit;
2047         }
2048 
2049         if (line_height_unit == SP_CSS_UNIT_PERCENT) {
2050             height *= 100.0; // Inkscape store % as fraction in .value
2051         }
2052 
2053         // We dot want to parse values just show
2054         if (!is_relative(SPCSSUnit(line_height_unit))) {
2055             gint curunit = prefs->getInt("/tools/text/lineheight/display_unit", 1);
2056             // For backwards comaptibility
2057             if (is_relative(SPCSSUnit(curunit))) {
2058                 prefs->setInt("/tools/text/lineheight/display_unit", 1);
2059                 curunit = 1;
2060             }
2061             height = Quantity::convert(height, "px", sp_style_get_css_unit_string(curunit));
2062             line_height_unit = curunit;
2063         }
2064         _line_height_adj->set_value(height);
2065 
2066 
2067         // Update "climb rate"
2068         if (line_height_unit == SP_CSS_UNIT_PERCENT) {
2069             _line_height_adj->set_step_increment(1.0);
2070             _line_height_adj->set_page_increment(10.0);
2071         } else {
2072             _line_height_adj->set_step_increment(0.1);
2073             _line_height_adj->set_page_increment(1.0);
2074         }
2075 
2076         if( line_height_unit == SP_CSS_UNIT_NONE ) {
2077             // Function 'sp_style_get_css_unit_string' returns 'px' for unit none.
2078             // We need to avoid this.
2079             _tracker->setActiveUnitByAbbr("");
2080         } else {
2081             _tracker->setActiveUnitByAbbr(sp_style_get_css_unit_string(line_height_unit));
2082         }
2083 
2084         // Save unit so we can do conversions between new/old units.
2085         _lineheight_unit = line_height_unit;
2086         // Word spacing
2087         double wordSpacing;
2088         if (query.word_spacing.normal) wordSpacing = 0.0;
2089         else wordSpacing = query.word_spacing.computed; // Assume no units (change in desktop-style.cpp)
2090 
2091         _word_spacing_adj->set_value(wordSpacing);
2092 
2093         // Letter spacing
2094         double letterSpacing;
2095         if (query.letter_spacing.normal) letterSpacing = 0.0;
2096         else letterSpacing = query.letter_spacing.computed; // Assume no units (change in desktop-style.cpp)
2097 
2098         _letter_spacing_adj->set_value(letterSpacing);
2099 
2100         // Writing mode
2101         int activeButton2 = 0;
2102         if (query.writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) activeButton2 = 0;
2103         if (query.writing_mode.computed == SP_CSS_WRITING_MODE_TB_RL) activeButton2 = 1;
2104         if (query.writing_mode.computed == SP_CSS_WRITING_MODE_TB_LR) activeButton2 = 2;
2105 
2106         _writing_mode_item->set_active( activeButton2 );
2107 
2108         // Orientation
2109         int activeButton3 = 0;
2110         if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_MIXED   ) activeButton3 = 0;
2111         if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_UPRIGHT ) activeButton3 = 1;
2112         if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_SIDEWAYS) activeButton3 = 2;
2113 
2114         _orientation_item->set_active( activeButton3 );
2115 
2116         // Disable text orientation for horizontal text...
2117         _orientation_item->set_sensitive( activeButton2 != 0 );
2118 
2119         // Direction
2120         int activeButton4 = 0;
2121         if (query.direction.computed == SP_CSS_DIRECTION_LTR ) activeButton4 = 0;
2122         if (query.direction.computed == SP_CSS_DIRECTION_RTL ) activeButton4 = 1;
2123         _direction_item->set_active( activeButton4 );
2124     }
2125 
2126 #ifdef DEBUG_TEXT
2127     std::cout << "    GUI: fontfamily.value: "     << query.font_family.value()  << std::endl;
2128     std::cout << "    GUI: font_size.computed: "   << query.font_size.computed   << std::endl;
2129     std::cout << "    GUI: font_weight.computed: " << query.font_weight.computed << std::endl;
2130     std::cout << "    GUI: font_style.computed: "  << query.font_style.computed  << std::endl;
2131     std::cout << "    GUI: text_anchor.computed: " << query.text_anchor.computed << std::endl;
2132     std::cout << "    GUI: text_align.computed:  " << query.text_align.computed  << std::endl;
2133     std::cout << "    GUI: line_height.computed: " << query.line_height.computed
2134               << "  line_height.value: "    << query.line_height.value
2135               << "  line_height.unit: "     << query.line_height.unit  << std::endl;
2136     std::cout << "    GUI: word_spacing.computed: " << query.word_spacing.computed
2137               << "  word_spacing.value: "    << query.word_spacing.value
2138               << "  word_spacing.unit: "     << query.word_spacing.unit  << std::endl;
2139     std::cout << "    GUI: letter_spacing.computed: " << query.letter_spacing.computed
2140               << "  letter_spacing.value: "    << query.letter_spacing.value
2141               << "  letter_spacing.unit: "     << query.letter_spacing.unit  << std::endl;
2142     std::cout << "    GUI: writing_mode.computed: " << query.writing_mode.computed << std::endl;
2143 #endif
2144 
2145     // Kerning (xshift), yshift, rotation.  NB: These are not CSS attributes.
2146     if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
2147         Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
2148         if( tc ) {
2149             unsigned char_index = -1;
2150             TextTagAttributes *attributes =
2151                 text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
2152             if( attributes ) {
2153 
2154                 // Dx
2155                 double dx = attributes->getDx( char_index );
2156                 _dx_adj->set_value(dx);
2157 
2158                 // Dy
2159                 double dy = attributes->getDy( char_index );
2160                 _dy_adj->set_value(dy);
2161 
2162                 // Rotation
2163                 double rotation = attributes->getRotate( char_index );
2164                 /* SVG value is between 0 and 360 but we're using -180 to 180 in widget */
2165                 if( rotation > 180.0 ) rotation -= 360.0;
2166                 _rotation_adj->set_value(rotation);
2167 
2168 #ifdef DEBUG_TEXT
2169                 std::cout << "    GUI: Dx: " << dx << std::endl;
2170                 std::cout << "    GUI: Dy: " << dy << std::endl;
2171                 std::cout << "    GUI: Rotation: " << rotation << std::endl;
2172 #endif
2173             }
2174         }
2175     }
2176 
2177     {
2178         // Set these here as we don't always have kerning/rotating attributes
2179         _dx_item->set_sensitive(!isFlow);
2180         _dy_item->set_sensitive(!isFlow);
2181         _rotation_item->set_sensitive(!isFlow);
2182     }
2183 
2184 #ifdef DEBUG_TEXT
2185     std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
2186     std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
2187     std::cout << std::endl;
2188 #endif
2189 
2190     _freeze = false;
2191 }
2192 
2193 void
watch_ec(SPDesktop * desktop,Inkscape::UI::Tools::ToolBase * ec)2194 TextToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec) {
2195     bool is_text_toolbar = SP_IS_TEXT_CONTEXT(ec);
2196     bool is_select_toolbar = !is_text_toolbar && SP_IS_SELECT_CONTEXT(ec);
2197     if (is_text_toolbar) {
2198         // Watch selection
2199         // Ensure FontLister is updated here first..................
2200         c_selection_changed =
2201             desktop->getSelection()->connectChangedFirst(sigc::mem_fun(*this, &TextToolbar::selection_changed));
2202         c_selection_modified = desktop->getSelection()->connectModifiedFirst(sigc::mem_fun(*this, &TextToolbar::selection_modified));
2203         c_subselection_changed = desktop->connectToolSubselectionChanged(sigc::mem_fun(*this, &TextToolbar::subselection_changed));
2204         this->_sub_active_item = nullptr;
2205         this->_cusor_numbers = 0;
2206         selection_changed(desktop->getSelection());
2207     } else if (is_select_toolbar) {
2208         c_selection_modified_select_tool = desktop->getSelection()->connectModifiedFirst(
2209             sigc::mem_fun(*this, &TextToolbar::selection_modified_select_tool));
2210     }
2211 
2212 
2213     if (!is_text_toolbar) {
2214         c_selection_changed.disconnect();
2215         c_selection_modified.disconnect();
2216         c_subselection_changed.disconnect();
2217     }
2218 
2219     if (!is_select_toolbar) {
2220         c_selection_modified_select_tool.disconnect();
2221     }
2222 }
2223 
2224 void
selection_modified(Inkscape::Selection * selection,guint)2225 TextToolbar::selection_modified(Inkscape::Selection *selection, guint /*flags*/)
2226 {
2227     this->_sub_active_item = nullptr;
2228     selection_changed(selection);
2229 
2230 }
2231 
subselection_wrap_toggle(bool start)2232 void TextToolbar::subselection_wrap_toggle(bool start)
2233 {
2234     if (SP_IS_TEXT_CONTEXT(_desktop->event_context)) {
2235         Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
2236         if (tc) {
2237             _updating = true;
2238             Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
2239             if (layout) {
2240                 Inkscape::Text::Layout::iterator start_selection = tc->text_sel_start;
2241                 Inkscape::Text::Layout::iterator end_selection = tc->text_sel_end;
2242                 tc->text_sel_start = wrap_start;
2243                 tc->text_sel_end = wrap_end;
2244                 wrap_start = start_selection;
2245                 wrap_end = end_selection;
2246             }
2247             _updating = start;
2248         }
2249     }
2250 }
2251 
2252 /*
2253 * This function parse the just created line height in one or more lines of a text subselection
2254 * can recibe 2 kinds of input because when we store a text element we apply a fallback that change
2255 * structure. This visualy is not reflected but user maybe want to change a part of this subselection
2256 * once the falback is created, so we need more complex here to fill the gap.
2257 * Basicaly we have a line height changed in the new wraper element/s between wrap_start and wrap_end
2258 * this variables store starting iterator of first char in line and last char in line in a subselection
2259 * this elements are styled well but we can have orphands text nodes before and after the subselection.
2260 * this normaly 3 elements are inside a container as direct child of a text element, so we need to apply
2261 * the container style to the optional previous and last text nodes warping into a new element that get the
2262 * container style (this are not part to the subsselection). After wrap we unindent all child of the container and
2263 * remove the container
2264 *
2265 */
prepare_inner()2266 void TextToolbar::prepare_inner()
2267 {
2268     Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
2269     if (tc) {
2270         Inkscape::Text::Layout *layout = const_cast<Inkscape::Text::Layout *>(te_get_layout(tc->text));
2271         if (layout) {
2272             SPDocument              *doc      = _desktop->getDocument();
2273             SPObject                *spobject = dynamic_cast<SPObject   *>(tc->text);
2274             SPItem                  *spitem   = dynamic_cast<SPItem     *>(tc->text);
2275             SPText                  *text     = dynamic_cast<SPText     *>(tc->text);
2276             SPFlowtext              *flowtext = dynamic_cast<SPFlowtext *>(tc->text);
2277             Inkscape::XML::Document *xml_doc = doc->getReprDoc();
2278             if (!spobject) {
2279                 return;
2280             }
2281 
2282             // We check for external files with text nodes direct children of text elemet
2283             // and wrap it into a tspan elements as inkscape do.
2284             if (text) {
2285                 std::vector<SPObject *> childs = spitem->childList(false);
2286                 for (auto child : childs) {
2287                     SPString *spstring = dynamic_cast<SPString *>(child);
2288                     if (spstring) {
2289                         Glib::ustring content = spstring->string;
2290                         if (content != "\n") {
2291                             Inkscape::XML::Node *rstring = xml_doc->createTextNode(content.c_str());
2292                             Inkscape::XML::Node *rtspan  = xml_doc->createElement("svg:tspan");
2293                             //Inkscape::XML::Node *rnl     = xml_doc->createTextNode("\n");
2294                             rtspan->setAttribute("sodipodi:role", "line");
2295                             rtspan->addChild(rstring, nullptr);
2296                             text->getRepr()->addChild(rtspan, child->getRepr());
2297                             Inkscape::GC::release(rstring);
2298                             Inkscape::GC::release(rtspan);
2299                             text->getRepr()->removeChild(spstring->getRepr());
2300                         }
2301                     }
2302                 }
2303             }
2304 
2305             // Here we remove temporary the shape to allow layout calculate where are the warp_end and warpo_start
2306             // position if one of this are hiden because the previous line height changed
2307             if (text) {
2308                 text->hide_shape_inside();
2309             }
2310             if (flowtext) {
2311                 flowtext->fix_overflow_flowregion(false);
2312             }
2313             SPObject *rawptr_start = nullptr;
2314             SPObject *rawptr_end = nullptr;
2315             layout->getSourceOfCharacter(wrap_start, &rawptr_start);
2316             layout->getSourceOfCharacter(wrap_end, &rawptr_end);
2317             if (text) {
2318                 text->show_shape_inside();
2319             }
2320             if (flowtext) {
2321                 flowtext->fix_overflow_flowregion(true);
2322             }
2323             if (!rawptr_start || !rawptr_end || !SP_IS_OBJECT(rawptr_start)|| !SP_IS_OBJECT(rawptr_end)) {
2324                 return;
2325             }
2326             SPObject *startobj = reinterpret_cast<SPObject *>(rawptr_start);
2327             SPObject *endobj = reinterpret_cast<SPObject *>(rawptr_end);
2328             // here we try to slect the elements where we need to work
2329             // looping parent while text element in start and end
2330             // and geting this and the inbetween elements
2331             SPObject *start = startobj;
2332             SPObject *end   = endobj;
2333             while (start->parent != spobject)
2334             {
2335                 start = start->parent;
2336             }
2337             while (end->parent != spobject)
2338             {
2339                 end = end->parent;
2340             }
2341             std::vector<SPObject *> containers;
2342             while (start && start != end) {
2343                 containers.push_back(start);
2344                 start = start->getNext();
2345             }
2346             if (start) {
2347                 containers.push_back(start);
2348             }
2349             for (auto container : containers) {
2350                 Inkscape::XML::Node *prevchild = container->getRepr();
2351                 std::vector<SPObject*> childs = container->childList(false);
2352                 for (auto child : childs) {
2353                     SPString    *spstring  = dynamic_cast<SPString    *>(child);
2354                     SPFlowtspan *flowtspan = dynamic_cast<SPFlowtspan *>(child);
2355                     SPTSpan     *tspan     = dynamic_cast<SPTSpan     *>(child);
2356                     // we need to upper all flowtspans to container level
2357                     // to this we need to change the element from flowspan to flowpara
2358                     if (flowtspan) {
2359                         Inkscape::XML::Node *flowpara = xml_doc->createElement("svg:flowPara");
2360                         std::vector<SPObject*> fts_childs = flowtspan->childList(false);
2361                         bool hascontent = false;
2362                         // we need to move the contents to the new created element
2363                         // mayve we can move directly but the safer for me is duplicate
2364                         // inject into the new element and delete original
2365                         for (auto fts_child : fts_childs) {
2366                             // is this check necesary?
2367                             if (fts_child) {
2368                                 Inkscape::XML::Node *fts_child_node = fts_child->getRepr()->duplicate(xml_doc);
2369                                 flowtspan->getRepr()->removeChild(fts_child->getRepr());
2370                                 flowpara->addChild(fts_child_node, nullptr);
2371                                 Inkscape::GC::release(fts_child_node);
2372                                 hascontent = true;
2373                             }
2374                         }
2375                         // if no contents we dont want to add
2376                         if (hascontent) {
2377                             flowpara->setAttribute("style", flowtspan->getRepr()->attribute("style"));
2378                             spobject->getRepr()->addChild(flowpara, prevchild);
2379                             Inkscape::GC::release(flowpara);
2380                             prevchild = flowpara;
2381                         }
2382                         container->getRepr()->removeChild(flowtspan->getRepr());
2383                     } else if (tspan) {
2384                         if (child->childList(false).size()) {
2385                             child->getRepr()->setAttribute("sodipodi:role", "line");
2386                             // maybe we need to move unindent function here
2387                             // to be the same as other here
2388                             prevchild = unindent_node(child->getRepr(), prevchild);
2389                         } else {
2390                             // if no contents we dont want to add
2391                             container->getRepr()->removeChild(child->getRepr());
2392                         }
2393                     } else if (spstring) {
2394                         // we are on a text node, we act diferent if in a text or flowtext.
2395                         // wrap a duplicate of the element and unindent after the prevchild
2396                         // and finaly delete original
2397                         Inkscape::XML::Node *string_node = xml_doc->createTextNode(spstring->string.c_str());
2398                         if (text) {
2399                             Inkscape::XML::Node *tspan_node = xml_doc->createElement("svg:tspan");
2400                             tspan_node->setAttribute("style", container->getRepr()->attribute("style"));
2401                             tspan_node->addChild(string_node, nullptr);
2402                             tspan_node->setAttribute("sodipodi:role", "line");
2403                             text->getRepr()->addChild(tspan_node, prevchild);
2404                             Inkscape::GC::release(string_node);
2405                             Inkscape::GC::release(tspan_node);
2406                             prevchild = tspan_node;
2407                         } else if (flowtext) {
2408                             Inkscape::XML::Node *flowpara_node = xml_doc->createElement("svg:flowPara");
2409                             flowpara_node->setAttribute("style", container->getRepr()->attribute("style"));
2410                             flowpara_node->addChild(string_node, nullptr);
2411                             flowtext->getRepr()->addChild(flowpara_node, prevchild);
2412                             Inkscape::GC::release(string_node);
2413                             Inkscape::GC::release(flowpara_node);
2414                             prevchild = flowpara_node;
2415                         }
2416                         container->getRepr()->removeChild(spstring->getRepr());
2417                     }
2418                 }
2419                 tc->text->getRepr()->removeChild(container->getRepr());
2420             }
2421         }
2422     }
2423 }
2424 
unindent_node(Inkscape::XML::Node * repr,Inkscape::XML::Node * prevchild)2425 Inkscape::XML::Node *TextToolbar::unindent_node(Inkscape::XML::Node *repr, Inkscape::XML::Node *prevchild)
2426 {
2427     g_assert(repr != nullptr);
2428 
2429     Inkscape::XML::Node *parent = repr->parent();
2430     if (parent) {
2431         Inkscape::XML::Node *grandparent = parent->parent();
2432         if (grandparent) {
2433             SPDocument *doc = _desktop->getDocument();
2434             Inkscape::XML::Document *xml_doc = doc->getReprDoc();
2435             Inkscape::XML::Node *newrepr = repr->duplicate(xml_doc);
2436             parent->removeChild(repr);
2437             grandparent->addChild(newrepr, prevchild);
2438             Inkscape::GC::release(newrepr);
2439             newrepr->setAttribute("sodipodi:role", "line");
2440             return newrepr;
2441         }
2442     }
2443     std::cout << "error on TextToolbar.cpp::2433" << std::endl;
2444     return repr;
2445 }
2446 
subselection_changed(gpointer texttool)2447 void TextToolbar::subselection_changed(gpointer texttool)
2448 {
2449 #ifdef DEBUG_TEXT
2450     std::cout << std::endl;
2451     std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
2452     std::cout << "subselection_changed: start " << std::endl;
2453 #endif
2454     // quit if run by the _changed callbacks
2455     this->_sub_active_item = nullptr;
2456     if (_updating) {
2457         return;
2458     }
2459     Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(texttool);
2460     if (tc) {
2461         Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
2462         if (layout) {
2463             Inkscape::Text::Layout::iterator start           = layout->begin();
2464             Inkscape::Text::Layout::iterator end             = layout->end();
2465             Inkscape::Text::Layout::iterator start_selection = tc->text_sel_start;
2466             Inkscape::Text::Layout::iterator end_selection   = tc->text_sel_end;
2467 #ifdef DEBUG_TEXT
2468             std::cout << "    GUI: Start of text: " << layout->iteratorToCharIndex(start) << std::endl;
2469             std::cout << "    GUI: End of text: " << layout->iteratorToCharIndex(end) << std::endl;
2470             std::cout << "    GUI: Start of selection: " << layout->iteratorToCharIndex(start_selection) << std::endl;
2471             std::cout << "    GUI: End of selection: " << layout->iteratorToCharIndex(end_selection) << std::endl;
2472             std::cout << "    GUI: Loop Subelements: " << std::endl;
2473             std::cout << "    ::::::::::::::::::::::::::::::::::::::::::::: " << std::endl;
2474 #endif
2475             gint startline = layout->paragraphIndex(start_selection);
2476             if (start_selection == end_selection) {
2477                 this->_outer = true;
2478                 gint counter = 0;
2479                 for (auto child : tc->text->childList(false)) {
2480                     SPItem *item = dynamic_cast<SPItem *>(child);
2481                     if (item && counter == startline) {
2482                         this->_sub_active_item = item;
2483                         int origin_selection = layout->iteratorToCharIndex(start_selection);
2484                         Inkscape::Text::Layout::iterator next = layout->charIndexToIterator(origin_selection + 1);
2485                         Inkscape::Text::Layout::iterator prev = layout->charIndexToIterator(origin_selection - 1);
2486                         //TODO: find a better way to init
2487                         _updating = true;
2488                         SPStyle query(_desktop->getDocument());
2489                         _query_cursor = query;
2490                         Inkscape::Text::Layout::iterator start_line = tc->text_sel_start;
2491                         start_line.thisStartOfLine();
2492                         if (tc->text_sel_start == start_line) {
2493                             tc->text_sel_start = next;
2494                         } else {
2495                             tc->text_sel_start = prev;
2496                         }
2497                         _cusor_numbers = sp_desktop_query_style(_desktop, &_query_cursor, QUERY_STYLE_PROPERTY_FONTNUMBERS);
2498                         tc->text_sel_start = start_selection;
2499                         wrap_start = tc->text_sel_start;
2500                         wrap_end = tc->text_sel_end;
2501                         wrap_start.thisStartOfLine();
2502                         wrap_end.thisEndOfLine();
2503                         _updating = false;
2504                         break;
2505                     }
2506                     ++counter;
2507                 }
2508                 selection_changed(nullptr);
2509             } else if ((start_selection == start && end_selection == end) ||
2510                        (start_selection == end && end_selection == start)) {
2511                 // full subselection
2512                 _cusor_numbers = 0;
2513                 this->_outer = true;
2514                 selection_changed(nullptr);
2515             } else {
2516                 _cusor_numbers = 0;
2517                 this->_outer = false;
2518                 wrap_start = tc->text_sel_start;
2519                 wrap_end = tc->text_sel_end;
2520                 if (tc->text_sel_start > tc->text_sel_end) {
2521                     wrap_start.thisEndOfLine();
2522                     wrap_end.thisStartOfLine();
2523                 } else {
2524                     wrap_start.thisStartOfLine();
2525                     wrap_end.thisEndOfLine();
2526                 }
2527                 selection_changed(nullptr);
2528             }
2529         }
2530     }
2531 #ifdef DEBUG_TEXT
2532     std::cout << "subselection_changed: exit " << std::endl;
2533     std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
2534     std::cout << std::endl;
2535 #endif
2536 }
2537 }
2538 }
2539 }
2540 
2541 /*
2542   Local Variables:
2543   mode:c++
2544   c-file-style:"stroustrup"
2545   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2546   indent-tabs-mode:nil
2547   fill-column:99
2548   End:
2549 */
2550 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
2551