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