summaryrefslogtreecommitdiffstats
path: root/src/ui/toolbar/text-toolbar.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/toolbar/text-toolbar.cpp')
-rw-r--r--src/ui/toolbar/text-toolbar.cpp2576
1 files changed, 2576 insertions, 0 deletions
diff --git a/src/ui/toolbar/text-toolbar.cpp b/src/ui/toolbar/text-toolbar.cpp
new file mode 100644
index 0000000..7a6144e
--- /dev/null
+++ b/src/ui/toolbar/text-toolbar.cpp
@@ -0,0 +1,2576 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Text aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 1999-2013 authors
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+
+#include "text-toolbar.h"
+
+#include "desktop-style.h"
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "inkscape.h"
+#include "selection-chemistry.h"
+
+#include "libnrtype/font-lister.h"
+
+#include "object/sp-flowdiv.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-root.h"
+#include "object/sp-text.h"
+#include "object/sp-tspan.h"
+#include "object/sp-string.h"
+
+#include "svg/css-ostringstream.h"
+#include "ui/icon-names.h"
+#include "ui/tools/select-tool.h"
+#include "ui/tools/text-tool.h"
+#include "ui/widget/canvas.h" // Focus
+#include "ui/widget/combo-box-entry-tool-item.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/unit-tracker.h"
+#include "util/units.h"
+
+#include "widgets/style-utils.h"
+
+using Inkscape::DocumentUndo;
+using Inkscape::Util::Unit;
+using Inkscape::Util::Quantity;
+using Inkscape::Util::unit_table;
+using Inkscape::UI::Widget::UnitTracker;
+
+//#define DEBUG_TEXT
+
+//########################
+//## Text Toolbox ##
+//########################
+
+// Functions for debugging:
+#ifdef DEBUG_TEXT
+static void sp_print_font(SPStyle *query)
+{
+
+
+ bool family_set = query->font_family.set;
+ bool style_set = query->font_style.set;
+ bool fontspec_set = query->font_specification.set;
+
+ std::cout << " Family set? " << family_set
+ << " Style set? " << style_set
+ << " FontSpec set? " << fontspec_set
+ << std::endl;
+}
+
+static void sp_print_fontweight( SPStyle *query ) {
+ const gchar* names[] = {"100", "200", "300", "400", "500", "600", "700", "800", "900",
+ "NORMAL", "BOLD", "LIGHTER", "BOLDER", "Out of range"};
+ // Missing book = 380
+ int index = query->font_weight.computed;
+ if (index < 0 || index > 13)
+ index = 13;
+ std::cout << " Weight: " << names[ index ]
+ << " (" << query->font_weight.computed << ")" << std::endl;
+}
+
+static void sp_print_fontstyle( SPStyle *query ) {
+
+ const gchar* names[] = {"NORMAL", "ITALIC", "OBLIQUE", "Out of range"};
+ int index = query->font_style.computed;
+ if( index < 0 || index > 3 ) index = 3;
+ std::cout << " Style: " << names[ index ] << std::endl;
+
+}
+#endif
+
+static bool is_relative( Unit const *unit ) {
+ return (unit->abbr == "" || unit->abbr == "em" || unit->abbr == "ex" || unit->abbr == "%");
+}
+
+static bool is_relative(SPCSSUnit const unit)
+{
+ return (unit == SP_CSS_UNIT_NONE || unit == SP_CSS_UNIT_EM || unit == SP_CSS_UNIT_EX ||
+ unit == SP_CSS_UNIT_PERCENT);
+}
+
+// Set property for object, but unset all descendents
+// Should probably be moved to desktop_style.cpp
+static void recursively_set_properties(SPObject *object, SPCSSAttr *css, bool unset_descendents = true)
+{
+ object->changeCSS (css, "style");
+
+ SPCSSAttr *css_unset = sp_repr_css_attr_unset_all( css );
+ std::vector<SPObject *> children = object->childList(false);
+ for (auto i: children) {
+ recursively_set_properties(i, unset_descendents ? css_unset : css);
+ }
+ sp_repr_css_attr_unref (css_unset);
+}
+
+/*
+ * Set the default list of font sizes, scaled to the users preferred unit
+ */
+static void sp_text_set_sizes(GtkListStore* model_size, int unit)
+{
+ gtk_list_store_clear(model_size);
+
+ // List of font sizes for dropchange-down menu
+ int sizes[] = {
+ 4, 6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28,
+ 32, 36, 40, 48, 56, 64, 72, 144
+ };
+
+ // Array must be same length as SPCSSUnit in style.h
+ float ratios[] = {1, 1, 1, 10, 4, 40, 100, 16, 8, 0.16};
+
+ for(int i : sizes) {
+ GtkTreeIter iter;
+ Glib::ustring size = Glib::ustring::format(i / (float)ratios[unit]);
+ gtk_list_store_append( model_size, &iter );
+ gtk_list_store_set( model_size, &iter, 0, size.c_str(), -1 );
+ }
+}
+
+
+// TODO: possibly share with font-selector by moving most code to font-lister (passing family name)
+static void sp_text_toolbox_select_cb( GtkEntry* entry, GtkEntryIconPosition /*position*/, GdkEvent /*event*/, gpointer /*data*/ ) {
+
+ Glib::ustring family = gtk_entry_get_text ( entry );
+ //std::cout << "text_toolbox_missing_font_cb: selecting: " << family << std::endl;
+
+ // Get all items with matching font-family set (not inherited!).
+ std::vector<SPItem*> selectList;
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ SPDocument *document = desktop->getDocument();
+ std::vector<SPItem*> x,y;
+ std::vector<SPItem*> allList = get_all_items(x, document->getRoot(), desktop, false, false, true, y);
+ for(std::vector<SPItem*>::const_reverse_iterator i=allList.rbegin();i!=allList.rend(); ++i){
+ SPItem *item = *i;
+ SPStyle *style = item->style;
+
+ if (style) {
+
+ Glib::ustring family_style;
+ if (style->font_family.set) {
+ family_style = style->font_family.value();
+ //std::cout << " family style from font_family: " << family_style << std::endl;
+ }
+ else if (style->font_specification.set) {
+ family_style = style->font_specification.value();
+ //std::cout << " family style from font_spec: " << family_style << std::endl;
+ }
+
+ if (family_style.compare( family ) == 0 ) {
+ //std::cout << " found: " << item->getId() << std::endl;
+ selectList.push_back(item);
+ }
+ }
+ }
+
+ // Update selection
+ Inkscape::Selection *selection = desktop->getSelection();
+ selection->clear();
+ //std::cout << " list length: " << g_slist_length ( selectList ) << std::endl;
+ selection->setList(selectList);
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+TextToolbar::TextToolbar(SPDesktop *desktop)
+ : Toolbar(desktop)
+ , _freeze(false)
+ , _text_style_from_prefs(false)
+ , _outer(true)
+ , _updating(false)
+ , _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR))
+ , _tracker_fs(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR))
+ , _cusor_numbers(0)
+{
+ /* Line height unit tracker */
+ _tracker->prependUnit(unit_table.getUnit("")); // Ratio
+ _tracker->addUnit(unit_table.getUnit("%"));
+ _tracker->addUnit(unit_table.getUnit("em"));
+ _tracker->addUnit(unit_table.getUnit("ex"));
+ _tracker->setActiveUnit(unit_table.getUnit(""));
+ // We change only the display value
+ _tracker->changeLabel("lines", 0, true);
+ _tracker_fs->setActiveUnit(unit_table.getUnit("mm"));
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ /* Font family */
+ {
+ // Font list
+ Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
+ fontlister->update_font_list(desktop->getDocument());
+ Glib::RefPtr<Gtk::ListStore> store = fontlister->get_font_list();
+ GtkListStore* model = store->gobj();
+
+ _font_family_item =
+ Gtk::manage(new UI::Widget::ComboBoxEntryToolItem( "TextFontFamilyAction",
+ _("Font Family"),
+ _("Select Font Family (Alt-X to access)"),
+ GTK_TREE_MODEL(model),
+ -1, // Entry width
+ 50, // Extra list width
+ (gpointer)font_lister_cell_data_func2, // Cell layout
+ (gpointer)font_lister_separator_func2,
+ GTK_WIDGET(desktop->getCanvas()->gobj()))); // Focus widget
+ _font_family_item->popup_enable(); // Enable entry completion
+ gchar *const info = _("Select all text with this font-family");
+ _font_family_item->set_info( info ); // Show selection icon
+ _font_family_item->set_info_cb( (gpointer)sp_text_toolbox_select_cb );
+
+ gchar *const warning = _("Font not found on system");
+ _font_family_item->set_warning( warning ); // Show icon w/ tooltip if font missing
+ _font_family_item->set_warning_cb( (gpointer)sp_text_toolbox_select_cb );
+
+ //ink_comboboxentry_action_set_warning_callback( act, sp_text_fontfamily_select_all );
+ _font_family_item->signal_changed().connect( sigc::mem_fun(*this, &TextToolbar::fontfamily_value_changed) );
+ add(*_font_family_item);
+
+ // Change style of drop-down from menu to list
+ auto css_provider = gtk_css_provider_new();
+ gtk_css_provider_load_from_data(css_provider,
+ "#TextFontFamilyAction_combobox {\n"
+ " -GtkComboBox-appears-as-list: true;\n"
+ "}\n",
+ -1, nullptr);
+
+ auto screen = gdk_screen_get_default();
+ _font_family_item->focus_on_click(false);
+ gtk_style_context_add_provider_for_screen(screen,
+ GTK_STYLE_PROVIDER(css_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_USER);
+ }
+
+ /* Font styles */
+ {
+ Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
+ Glib::RefPtr<Gtk::ListStore> store = fontlister->get_style_list();
+ GtkListStore* model_style = store->gobj();
+
+ _font_style_item =
+ Gtk::manage(new UI::Widget::ComboBoxEntryToolItem( "TextFontStyleAction",
+ _("Font Style"),
+ _("Font style"),
+ GTK_TREE_MODEL(model_style),
+ 12, // Width in characters
+ 0, // Extra list width
+ nullptr, // Cell layout
+ nullptr, // Separator
+ GTK_WIDGET(desktop->getCanvas()->gobj()))); // Focus widget
+
+ _font_style_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::fontstyle_value_changed));
+ _font_style_item->focus_on_click(false);
+ add(*_font_style_item);
+ }
+
+ add_separator();
+
+ /* Font size */
+ {
+ // List of font sizes for drop-down menu
+ GtkListStore* model_size = gtk_list_store_new( 1, G_TYPE_STRING );
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
+
+ sp_text_set_sizes(model_size, unit);
+
+ auto unit_str = sp_style_get_css_unit_string(unit);
+ Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", unit_str, ")");
+
+ _font_size_item =
+ Gtk::manage(new UI::Widget::ComboBoxEntryToolItem( "TextFontSizeAction",
+ _("Font Size"),
+ tooltip,
+ GTK_TREE_MODEL(model_size),
+ 8, // Width in characters
+ 0, // Extra list width
+ nullptr, // Cell layout
+ nullptr, // Separator
+ GTK_WIDGET(desktop->getCanvas()->gobj()))); // Focus widget
+
+ _font_size_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::fontsize_value_changed));
+ _font_size_item->focus_on_click(false);
+ add(*_font_size_item);
+ }
+ /* Font_ size units */
+ {
+ _font_size_units_item = _tracker_fs->create_tool_item(_("Units"), (""));
+ _font_size_units_item->signal_changed_after().connect(
+ sigc::mem_fun(*this, &TextToolbar::fontsize_unit_changed));
+ _font_size_units_item->focus_on_click(false);
+ add(*_font_size_units_item);
+ }
+ {
+ // Drop down menu
+ std::vector<Glib::ustring> labels = {_("Smaller spacing"), "", "", "", "", C_("Text tool", "Normal"), "", "", "", "", "", _("Larger spacing")};
+ 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};
+
+ auto line_height_val = 1.25;
+ _line_height_adj = Gtk::Adjustment::create(line_height_val, 0.0, 1000.0, 0.1, 1.0);
+ _line_height_item =
+ Gtk::manage(new UI::Widget::SpinButtonToolItem("text-line-height", "", _line_height_adj, 0.1, 2));
+ _line_height_item->set_tooltip_text(_("Spacing between baselines"));
+ _line_height_item->set_custom_numeric_menu_data(values, labels);
+ _line_height_item->set_focus_widget(desktop->getCanvas());
+ _line_height_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::lineheight_value_changed));
+ //_tracker->addAdjustment(_line_height_adj->gobj()); // (Alex V) Why is this commented out?
+ _line_height_item->set_sensitive(true);
+ _line_height_item->set_icon(INKSCAPE_ICON("text_line_spacing"));
+ add(*_line_height_item);
+ }
+ /* Line height units */
+ {
+ _line_height_units_item = _tracker->create_tool_item( _("Units"), (""));
+ _line_height_units_item->signal_changed_after().connect(sigc::mem_fun(*this, &TextToolbar::lineheight_unit_changed));
+ _line_height_units_item->focus_on_click(false);
+ add(*_line_height_units_item);
+ }
+
+ /* Alignment */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Align left");
+ row[columns.col_tooltip ] = _("Align left");
+ row[columns.col_icon ] = INKSCAPE_ICON("format-justify-left");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Align center");
+ row[columns.col_tooltip ] = _("Align center");
+ row[columns.col_icon ] = INKSCAPE_ICON("format-justify-center");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Align right");
+ row[columns.col_tooltip ] = _("Align right");
+ row[columns.col_icon ] = INKSCAPE_ICON("format-justify-right");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Justify");
+ row[columns.col_tooltip ] = _("Justify (only flowed text)");
+ row[columns.col_icon ] = INKSCAPE_ICON("format-justify-fill");
+ row[columns.col_sensitive] = false;
+
+ _align_item =
+ UI::Widget::ComboToolItem::create(_("Alignment"), // Label
+ _("Text alignment"), // Tooltip
+ "Not Used", // Icon
+ store ); // Tree store
+ _align_item->use_icon( true );
+ _align_item->use_label( false );
+ gint mode = prefs->getInt("/tools/text/align_mode", 0);
+ _align_item->set_active( mode );
+
+ add(*_align_item);
+ _align_item->focus_on_click(false);
+ _align_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::align_mode_changed));
+ }
+
+ /* Style - Superscript */
+ {
+ _superscript_item = Gtk::manage(new Gtk::ToggleToolButton());
+ _superscript_item->set_label(_("Toggle superscript"));
+ _superscript_item->set_tooltip_text(_("Toggle superscript"));
+ _superscript_item->set_icon_name(INKSCAPE_ICON("text_superscript"));
+ _superscript_item->set_name("text-superscript");
+ add(*_superscript_item);
+ _superscript_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &TextToolbar::script_changed), _superscript_item));
+ _superscript_item->set_active(prefs->getBool("/tools/text/super", false));
+ }
+
+ /* Style - Subscript */
+ {
+ _subscript_item = Gtk::manage(new Gtk::ToggleToolButton());
+ _subscript_item->set_label(_("Toggle subscript"));
+ _subscript_item->set_tooltip_text(_("Toggle subscript"));
+ _subscript_item->set_icon_name(INKSCAPE_ICON("text_subscript"));
+ _subscript_item->set_name("text-subscript");
+ add(*_subscript_item);
+ _subscript_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &TextToolbar::script_changed), _subscript_item));
+ _subscript_item->set_active(prefs->getBool("/tools/text/sub", false));
+ }
+
+ /* Character positioning popover */
+
+ auto positioning_item = Gtk::manage(new Gtk::ToolItem);
+ add(*positioning_item);
+
+ auto positioning_button = Gtk::manage(new Gtk::MenuButton);
+ positioning_button->set_image_from_icon_name(INKSCAPE_ICON("text_horz_kern"));
+ positioning_button->set_always_show_image(true);
+ positioning_button->set_tooltip_text(_("Kerning, word spacing, character positioning"));
+ positioning_button->set_label(_("Spacing"));
+ positioning_item->add(*positioning_button);
+
+ auto positioning_popover = Gtk::manage(new Gtk::Popover(*positioning_button));
+ positioning_popover->set_modal(false); // Stay open until button clicked again.
+ positioning_button->set_popover(*positioning_popover);
+
+ auto positioning_grid = Gtk::manage(new Gtk::Grid);
+ positioning_popover->add(*positioning_grid);
+
+
+ /* Letter spacing */
+ {
+ // Drop down menu
+ std::vector<Glib::ustring> labels = {_("Negative spacing"), "", "", "", C_("Text tool", "Normal"), "", "", "", "", "", "", "", _("Positive spacing")};
+ 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};
+ auto letter_spacing_val = prefs->getDouble("/tools/text/letterspacing", 0.0);
+ _letter_spacing_adj = Gtk::Adjustment::create(letter_spacing_val, -1000.0, 1000.0, 0.01, 0.10);
+ _letter_spacing_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-letter-spacing", _("Letter:"), _letter_spacing_adj, 0.1, 2));
+ _letter_spacing_item->set_tooltip_text(_("Spacing between letters (px)"));
+ _letter_spacing_item->set_custom_numeric_menu_data(values, labels);
+ _letter_spacing_item->set_focus_widget(desktop->getCanvas());
+ _letter_spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::letterspacing_value_changed));
+ _letter_spacing_item->set_sensitive(true);
+ _letter_spacing_item->set_icon(INKSCAPE_ICON("text_letter_spacing"));
+
+ positioning_grid->attach(*_letter_spacing_item, 0, 0);
+ }
+
+ /* Word spacing */
+ {
+ // Drop down menu
+ std::vector<Glib::ustring> labels = {_("Negative spacing"), "", "", "", C_("Text tool", "Normal"), "", "", "", "", "", "", "", _("Positive spacing")};
+ 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};
+ auto word_spacing_val = prefs->getDouble("/tools/text/wordspacing", 0.0);
+ _word_spacing_adj = Gtk::Adjustment::create(word_spacing_val, -1000.0, 1000.0, 0.01, 0.10);
+ _word_spacing_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-word-spacing", _("Word:"), _word_spacing_adj, 0.1, 2));
+ _word_spacing_item->set_tooltip_text(_("Spacing between words (px)"));
+ _word_spacing_item->set_custom_numeric_menu_data(values, labels);
+ _word_spacing_item->set_focus_widget(desktop->getCanvas());
+ _word_spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::wordspacing_value_changed));
+ _word_spacing_item->set_sensitive(true);
+ _word_spacing_item->set_icon(INKSCAPE_ICON("text_word_spacing"));
+
+ positioning_grid->attach(*_word_spacing_item, 1, 0);
+ }
+
+ /* Character kerning (horizontal shift) */
+ {
+ // Drop down menu
+ std::vector<double> values = { -2.0, -1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5, 2.0, 2.5 };
+ auto dx_val = prefs->getDouble("/tools/text/dx", 0.0);
+ _dx_adj = Gtk::Adjustment::create(dx_val, -1000.0, 1000.0, 0.01, 0.1);
+ _dx_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-dx", _("Kern:"), _dx_adj, 0.1, 2));
+ _dx_item->set_custom_numeric_menu_data(values);
+ _dx_item->set_tooltip_text(_("Horizontal kerning (px)"));
+ _dx_item->set_focus_widget(desktop->getCanvas());
+ _dx_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::dx_value_changed));
+ _dx_item->set_sensitive(true);
+ _dx_item->set_icon(INKSCAPE_ICON("text_horz_kern"));
+
+ positioning_grid->attach(*_dx_item, 0, 1);
+ }
+
+ /* Character vertical shift */
+ {
+ // Drop down menu
+ std::vector<double> values = { -2.0, -1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5, 2.0, 2.5 };
+ auto dy_val = prefs->getDouble("/tools/text/dy", 0.0);
+ _dy_adj = Gtk::Adjustment::create(dy_val, -1000.0, 1000.0, 0.01, 0.1);
+ _dy_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-dy", _("Vert:"), _dy_adj, 0.1, 2));
+ _dy_item->set_tooltip_text(_("Vertical kerning (px)"));
+ _dy_item->set_custom_numeric_menu_data(values);
+ _dy_item->set_focus_widget(desktop->getCanvas());
+ _dy_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::dy_value_changed));
+ _dy_item->set_sensitive(true);
+ _dy_item->set_icon(INKSCAPE_ICON("text_vert_kern"));
+
+ positioning_grid->attach(*_dy_item, 1, 1);
+ }
+
+ /* Character rotation */
+ {
+ std::vector<double> values = { -90, -45, -30, -15, 0, 15, 30, 45, 90, 180 };
+ auto rotation_val = prefs->getDouble("/tools/text/rotation", 0.0);
+ _rotation_adj = Gtk::Adjustment::create(rotation_val, -180.0, 180.0, 0.1, 1.0);
+ _rotation_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-rotation", _("Rot:"), _rotation_adj, 0.1, 2));
+ _rotation_item->set_tooltip_text(_("Character rotation (degrees)"));
+ _rotation_item->set_custom_numeric_menu_data(values);
+ _rotation_item->set_focus_widget(desktop->getCanvas());
+ _rotation_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::rotation_value_changed));
+ _rotation_item->set_sensitive();
+ _rotation_item->set_icon(INKSCAPE_ICON("text_rotation"));
+
+ positioning_grid->attach(*_rotation_item, 2, 1);
+ }
+
+ positioning_grid->show_all();
+
+ /* Writing mode (Horizontal, Vertical-LR, Vertical-RL) */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Horizontal");
+ row[columns.col_tooltip ] = _("Horizontal text");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-horizontal");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Vertical — RL");
+ row[columns.col_tooltip ] = _("Vertical text — lines: right to left");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-vertical");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Vertical — LR");
+ row[columns.col_tooltip ] = _("Vertical text — lines: left to right");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-vertical-lr");
+ row[columns.col_sensitive] = true;
+
+ _writing_mode_item =
+ UI::Widget::ComboToolItem::create( _("Writing mode"), // Label
+ _("Block progression"), // Tooltip
+ "Not Used", // Icon
+ store ); // Tree store
+ _writing_mode_item->use_icon(true);
+ _writing_mode_item->use_label( false );
+ gint mode = prefs->getInt("/tools/text/writing_mode", 0);
+ _writing_mode_item->set_active( mode );
+ add(*_writing_mode_item);
+ _writing_mode_item->focus_on_click(false);
+ _writing_mode_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::writing_mode_changed));
+ }
+
+
+ /* Text (glyph) orientation (Auto (mixed), Upright, Sideways) */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Auto");
+ row[columns.col_tooltip ] = _("Auto glyph orientation");
+ row[columns.col_icon ] = INKSCAPE_ICON("text-orientation-auto");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Upright");
+ row[columns.col_tooltip ] = _("Upright glyph orientation");
+ row[columns.col_icon ] = INKSCAPE_ICON("text-orientation-upright");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Sideways");
+ row[columns.col_tooltip ] = _("Sideways glyph orientation");
+ row[columns.col_icon ] = INKSCAPE_ICON("text-orientation-sideways");
+ row[columns.col_sensitive] = true;
+
+ _orientation_item =
+ UI::Widget::ComboToolItem::create(_("Text orientation"), // Label
+ _("Text (glyph) orientation in vertical text."), // Tooltip
+ "Not Used", // Icon
+ store ); // List store
+ _orientation_item->use_icon(true);
+ _orientation_item->use_label(false);
+ gint mode = prefs->getInt("/tools/text/text_orientation", 0);
+ _orientation_item->set_active( mode );
+ _orientation_item->focus_on_click(false);
+ add(*_orientation_item);
+
+ _orientation_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::orientation_changed));
+ }
+
+ // Text direction (predominant direction of horizontal text).
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("LTR");
+ row[columns.col_tooltip ] = _("Left to right text");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-horizontal");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("RTL");
+ row[columns.col_tooltip ] = _("Right to left text");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-r2l");
+ row[columns.col_sensitive] = true;
+
+ _direction_item =
+ UI::Widget::ComboToolItem::create( _("Text direction"), // Label
+ _("Text direction for normally horizontal text."), // Tooltip
+ "Not Used", // Icon
+ store ); // List store
+ _direction_item->use_icon(true);
+ _direction_item->use_label(false);
+ gint mode = prefs->getInt("/tools/text/text_direction", 0);
+ _direction_item->set_active( mode );
+ _direction_item->focus_on_click(false);
+ add(*_direction_item);
+
+ _direction_item->signal_changed_after().connect(sigc::mem_fun(*this, &TextToolbar::direction_changed));
+ }
+
+ show_all();
+
+ // we emit a selection change on tool switch to text
+ desktop->connectEventContextChanged(sigc::mem_fun(*this, &TextToolbar::watch_ec));
+}
+
+/*
+ * Set the style, depending on the inner or outer text being selected
+ */
+void TextToolbar::text_outer_set_style(SPCSSAttr *css)
+{
+ // Calling sp_desktop_set_style will result in a call to TextTool::_styleSet() which
+ // will set the style on selected text inside the <text> element. If we want to set
+ // the style on the outer <text> objects we need to bypass this call.
+ SPDesktop *desktop = _desktop;
+ if(_outer) {
+ // Apply css to parent text objects directly.
+ for (auto i : desktop->getSelection()->items()) {
+ SPItem *item = dynamic_cast<SPItem *>(i);
+ if (dynamic_cast<SPText *>(item) || dynamic_cast<SPFlowtext *>(item)) {
+ // Scale by inverse of accumulated parent transform
+ SPCSSAttr *css_set = sp_repr_css_attr_new();
+ sp_repr_css_merge(css_set, css);
+ Geom::Affine const local(item->i2doc_affine());
+ double const ex(local.descrim());
+ if ((ex != 0.0) && (ex != 1.0)) {
+ sp_css_attr_scale(css_set, 1 / ex);
+ }
+ recursively_set_properties(item, css_set);
+ sp_repr_css_attr_unref(css_set);
+ }
+ }
+ } else {
+ // Apply css to selected inner objects.
+ sp_desktop_set_style (desktop, css, true, false);
+ }
+}
+
+void
+TextToolbar::fontfamily_value_changed()
+{
+#ifdef DEBUG_TEXT
+ std::cout << std::endl;
+ std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" << std::endl;
+ std::cout << "sp_text_fontfamily_value_changed: " << std::endl;
+#endif
+
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+#ifdef DEBUG_TEXT
+ std::cout << "sp_text_fontfamily_value_changed: frozen... return" << std::endl;
+ std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n" << std::endl;
+#endif
+ return;
+ }
+ _freeze = true;
+
+ Glib::ustring new_family = _font_family_item->get_active_text();
+ css_font_family_unquote( new_family ); // Remove quotes around font family names.
+
+ // TODO: Think about how to handle handle multiple selections. While
+ // the font-family may be the same for all, the styles might be different.
+ // See: TextEdit::onApply() for example of looping over selected items.
+ Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
+#ifdef DEBUG_TEXT
+ std::cout << " Old family: " << fontlister->get_font_family() << std::endl;
+ std::cout << " New family: " << new_family << std::endl;
+ std::cout << " Old active: " << fontlister->get_font_family_row() << std::endl;
+ // std::cout << " New active: " << act->active << std::endl;
+#endif
+ if( new_family.compare( fontlister->get_font_family() ) != 0 ) {
+ // Changed font-family
+
+ if( _font_family_item->get_active() == -1 ) {
+ // New font-family, not in document, not on system (could be fallback list)
+ fontlister->insert_font_family( new_family );
+
+ // This just sets a variable in the ComboBoxEntryAction object...
+ // shouldn't we also set the actual active row in the combobox?
+ _font_family_item->set_active(0); // New family is always at top of list.
+ }
+
+ fontlister->set_font_family( _font_family_item->get_active() );
+ // active text set in sp_text_toolbox_selection_changed()
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ fontlister->fill_css( css );
+
+ SPDesktop *desktop = _desktop;
+ if( desktop->getSelection()->isEmpty() ) {
+ // Update default
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ } else {
+ // If there is a selection, update
+ sp_desktop_set_style (desktop, css, true, true); // Results in selection change called twice.
+ DocumentUndo::done(desktop->getDocument(), _("Text: Change font family"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+ }
+
+ // unfreeze
+ _freeze = false;
+
+#ifdef DEBUG_TEXT
+ std::cout << "sp_text_toolbox_fontfamily_changes: exit" << std::endl;
+ std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" << std::endl;
+ std::cout << std::endl;
+#endif
+}
+
+GtkWidget *
+TextToolbar::create(SPDesktop *desktop)
+{
+ auto tb = Gtk::manage(new TextToolbar(desktop));
+ return GTK_WIDGET(tb->gobj());
+}
+
+void
+TextToolbar::fontsize_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ auto active_text = _font_size_item->get_active_text();
+ char const *text = active_text.c_str();
+ gchar *endptr;
+ gdouble size = g_strtod( text, &endptr );
+ if (endptr == text) { // Conversion failed, non-numeric input.
+ g_warning( "Conversion of size text to double failed, input: %s\n", text );
+ _freeze = false;
+ return;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int max_size = prefs->getInt("/dialogs/textandfont/maxFontSize", 10000); // somewhat arbitrary, but text&font preview freezes with too huge fontsizes
+
+ if (size > max_size)
+ size = max_size;
+
+ // Set css font size.
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
+ if (prefs->getBool("/options/font/textOutputPx", true)) {
+ osfs << sp_style_css_size_units_to_px(size, unit) << sp_style_get_css_unit_string(SP_CSS_UNIT_PX);
+ } else {
+ osfs << size << sp_style_get_css_unit_string(unit);
+ }
+ sp_repr_css_set_property (css, "font-size", osfs.str().c_str());
+ double factor = size / selection_fontsize;
+
+ // Apply font size to selected objects.
+ text_outer_set_style(css);
+
+ Unit const *unit_lh = _tracker->getActiveUnit();
+ g_return_if_fail(unit_lh != nullptr);
+ if (!is_relative(unit_lh) && _outer) {
+ double lineheight = _line_height_adj->get_value();
+ _freeze = false;
+ _line_height_adj->set_value(lineheight * factor);
+ _freeze = true;
+ }
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ } else {
+ // Save for undo
+ sp_desktop_set_style (_desktop, css, true, true);
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:size", _("Text: Change font size"), INKSCAPE_ICON("draw-text"));
+ }
+
+ sp_repr_css_attr_unref(css);
+
+ _freeze = false;
+}
+
+void
+TextToolbar::fontstyle_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ Glib::ustring new_style = _font_style_item->get_active_text();
+
+ Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
+
+ if( new_style.compare( fontlister->get_font_style() ) != 0 ) {
+
+ fontlister->set_font_style( new_style );
+ // active text set in sp_text_toolbox_seletion_changed()
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ fontlister->fill_css( css );
+
+ SPDesktop *desktop = _desktop;
+ sp_desktop_set_style (desktop, css, true, true);
+
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_style =
+ sp_desktop_query_style (desktop, &query, QUERY_STYLE_PROPERTY_FONTSTYLE);
+ if (result_style == QUERY_STYLE_NOTHING) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ } else {
+ // Save for undo
+ DocumentUndo::done(desktop->getDocument(), _("Text: Change font style"), INKSCAPE_ICON("draw-text"));
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ }
+
+ _freeze = false;
+}
+
+// Handles both Superscripts and Subscripts
+void
+TextToolbar::script_changed(Gtk::ToggleToolButton *btn)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+
+ _freeze = true;
+
+ // Called by Superscript or Subscript button?
+ auto name = btn->get_name();
+ gint prop = (btn == _superscript_item) ? 0 : 1;
+
+#ifdef DEBUG_TEXT
+ std::cout << "TextToolbar::script_changed: " << prop << std::endl;
+#endif
+
+ // Query baseline
+ SPStyle query(_desktop->getDocument());
+ int result_baseline = sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_BASELINES);
+
+ bool setSuper = false;
+ bool setSub = false;
+
+ if (Inkscape::is_query_style_updateable(result_baseline)) {
+ // If not set or mixed, turn on superscript or subscript
+ if( prop == 0 ) {
+ setSuper = true;
+ } else {
+ setSub = true;
+ }
+ } else {
+ // Superscript
+ gboolean superscriptSet = (query.baseline_shift.set &&
+ query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+ query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUPER );
+
+ // Subscript
+ gboolean subscriptSet = (query.baseline_shift.set &&
+ query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+ query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUB );
+
+ setSuper = !superscriptSet && prop == 0;
+ setSub = !subscriptSet && prop == 1;
+ }
+
+ // Set css properties
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ if( setSuper || setSub ) {
+ // Openoffice 2.3 and Adobe use 58%, Microsoft Word 2002 uses 65%, LaTex about 70%.
+ // 58% looks too small to me, especially if a superscript is placed on a superscript.
+ // If you make a change here, consider making a change to baseline-shift amount
+ // in style.cpp.
+ sp_repr_css_set_property (css, "font-size", "65%");
+ } else {
+ sp_repr_css_set_property (css, "font-size", "");
+ }
+ if( setSuper ) {
+ sp_repr_css_set_property (css, "baseline-shift", "super");
+ } else if( setSub ) {
+ sp_repr_css_set_property (css, "baseline-shift", "sub");
+ } else {
+ sp_repr_css_set_property (css, "baseline-shift", "baseline");
+ }
+
+ // Apply css to selected objects.
+ SPDesktop *desktop = _desktop;
+ sp_desktop_set_style (desktop, css, true, false);
+
+ // Save for undo
+ if(result_baseline != QUERY_STYLE_NOTHING) {
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:script", _("Text: Change superscript or subscript"), INKSCAPE_ICON("draw-text"));
+ }
+ _freeze = false;
+}
+
+void
+TextToolbar::align_mode_changed(int mode)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/text/align_mode", mode);
+
+ SPDesktop *desktop = _desktop;
+
+ // move the x of all texts to preserve the same bbox
+ Inkscape::Selection *selection = desktop->getSelection();
+ auto itemlist= selection->items();
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ // SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text) {
+ SPItem *item = i;
+
+ unsigned writing_mode = item->style->writing_mode.value;
+ // below, variable names suggest horizontal move, but we check the writing direction
+ // and move in the corresponding axis
+ Geom::Dim2 axis;
+ if (writing_mode == SP_CSS_WRITING_MODE_LR_TB || writing_mode == SP_CSS_WRITING_MODE_RL_TB) {
+ axis = Geom::X;
+ } else {
+ axis = Geom::Y;
+ }
+
+ Geom::OptRect bbox = item->geometricBounds();
+ if (!bbox)
+ continue;
+ double width = bbox->dimensions()[axis];
+ // If you want to align within some frame, other than the text's own bbox, calculate
+ // the left and right (or top and bottom for tb text) slacks of the text inside that
+ // frame (currently unused)
+ double left_slack = 0;
+ double right_slack = 0;
+ unsigned old_align = item->style->text_align.value;
+ double move = 0;
+ if (old_align == SP_CSS_TEXT_ALIGN_START || old_align == SP_CSS_TEXT_ALIGN_LEFT) {
+ switch (mode) {
+ case 0:
+ move = -left_slack;
+ break;
+ case 1:
+ move = width/2 + (right_slack - left_slack)/2;
+ break;
+ case 2:
+ move = width + right_slack;
+ break;
+ }
+ } else if (old_align == SP_CSS_TEXT_ALIGN_CENTER) {
+ switch (mode) {
+ case 0:
+ move = -width/2 - left_slack;
+ break;
+ case 1:
+ move = (right_slack - left_slack)/2;
+ break;
+ case 2:
+ move = width/2 + right_slack;
+ break;
+ }
+ } else if (old_align == SP_CSS_TEXT_ALIGN_END || old_align == SP_CSS_TEXT_ALIGN_RIGHT) {
+ switch (mode) {
+ case 0:
+ move = -width - left_slack;
+ break;
+ case 1:
+ move = -width/2 + (right_slack - left_slack)/2;
+ break;
+ case 2:
+ move = right_slack;
+ break;
+ }
+ }
+ Geom::Point XY = SP_TEXT(item)->attributes.firstXY();
+ if (axis == Geom::X) {
+ XY = XY + Geom::Point (move, 0);
+ } else {
+ XY = XY + Geom::Point (0, move);
+ }
+ SP_TEXT(item)->attributes.setFirstXY(XY);
+ item->updateRepr();
+ item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ }
+ }
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ switch (mode)
+ {
+ case 0:
+ {
+ sp_repr_css_set_property (css, "text-anchor", "start");
+ sp_repr_css_set_property (css, "text-align", "start");
+ break;
+ }
+ case 1:
+ {
+ sp_repr_css_set_property (css, "text-anchor", "middle");
+ sp_repr_css_set_property (css, "text-align", "center");
+ break;
+ }
+
+ case 2:
+ {
+ sp_repr_css_set_property (css, "text-anchor", "end");
+ sp_repr_css_set_property (css, "text-align", "end");
+ break;
+ }
+
+ case 3:
+ {
+ sp_repr_css_set_property (css, "text-anchor", "start");
+ sp_repr_css_set_property (css, "text-align", "justify");
+ break;
+ }
+ }
+
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+
+ // If querying returned nothing, update default style.
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_desktop_set_style (desktop, css, true, true);
+ if (result_numbers != QUERY_STYLE_NOTHING)
+ {
+ DocumentUndo::done(_desktop->getDocument(), _("Text: Change alignment"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+
+ desktop->getCanvas()->grab_focus();
+
+ _freeze = false;
+}
+
+void
+TextToolbar::writing_mode_changed(int mode)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ switch (mode)
+ {
+ case 0:
+ {
+ sp_repr_css_set_property (css, "writing-mode", "lr-tb");
+ break;
+ }
+
+ case 1:
+ {
+ sp_repr_css_set_property (css, "writing-mode", "tb-rl");
+ break;
+ }
+
+ case 2:
+ {
+ sp_repr_css_set_property (css, "writing-mode", "vertical-lr");
+ break;
+ }
+ }
+
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
+
+ // If querying returned nothing, update default style.
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_desktop_set_style (_desktop, css, true, true);
+ if(result_numbers != QUERY_STYLE_NOTHING)
+ {
+ DocumentUndo::done(_desktop->getDocument(), _("Text: Change writing mode"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+
+ _desktop->getCanvas()->grab_focus();
+
+ _freeze = false;
+}
+
+void
+TextToolbar::orientation_changed(int mode)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ switch (mode)
+ {
+ case 0:
+ {
+ sp_repr_css_set_property (css, "text-orientation", "auto");
+ break;
+ }
+
+ case 1:
+ {
+ sp_repr_css_set_property (css, "text-orientation", "upright");
+ break;
+ }
+
+ case 2:
+ {
+ sp_repr_css_set_property (css, "text-orientation", "sideways");
+ break;
+ }
+ }
+
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
+
+ // If querying returned nothing, update default style.
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_desktop_set_style (_desktop, css, true, true);
+ if(result_numbers != QUERY_STYLE_NOTHING)
+ {
+ DocumentUndo::done(_desktop->getDocument(), _("Text: Change orientation"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+ _desktop->canvas->grab_focus();
+
+ _freeze = false;
+}
+
+void
+TextToolbar::direction_changed(int mode)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ switch (mode)
+ {
+ case 0:
+ {
+ sp_repr_css_set_property (css, "direction", "ltr");
+ break;
+ }
+
+ case 1:
+ {
+ sp_repr_css_set_property (css, "direction", "rtl");
+ break;
+ }
+ }
+
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
+
+ // If querying returned nothing, update default style.
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_desktop_set_style (_desktop, css, true, true);
+ if(result_numbers != QUERY_STYLE_NOTHING)
+ {
+ DocumentUndo::done(_desktop->getDocument(), _("Text: Change direction"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+
+ _desktop->getCanvas()->grab_focus();
+
+ _freeze = false;
+}
+
+void
+TextToolbar::lineheight_value_changed()
+{
+ // quit if run by the _changed callbacks or is not text tool
+ if (_freeze || !SP_IS_TEXT_CONTEXT(_desktop->event_context)) {
+ return;
+ }
+
+ _freeze = true;
+ SPDesktop *desktop = _desktop;
+ // Get user selected unit and save as preference
+ Unit const *unit = _tracker->getActiveUnit();
+ // @Tav same disabled unit
+ g_return_if_fail(unit != nullptr);
+
+ // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit so
+ // we can save it (allows us to adjust line height value when unit changes).
+
+ // Set css line height.
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ if ( is_relative(unit) ) {
+ osfs << _line_height_adj->get_value() << unit->abbr;
+ } else {
+ // Inside SVG file, always use "px" for absolute units.
+ osfs << Quantity::convert(_line_height_adj->get_value(), unit, "px") << "px";
+ }
+
+ sp_repr_css_set_property (css, "line-height", osfs.str().c_str());
+
+ Inkscape::Selection *selection = desktop->getSelection();
+ auto itemlist = selection->items();
+ if (_outer) {
+ // Special else makes this different from other uses of text_outer_set_style
+ text_outer_set_style(css);
+ } else {
+ SPItem *parent = dynamic_cast<SPItem *>(*itemlist.begin());
+ SPStyle *parent_style = parent->style;
+ SPCSSAttr *parent_cssatr = sp_css_attr_from_style(parent_style, SP_STYLE_FLAG_IFSET);
+ Glib::ustring parent_lineheight = sp_repr_css_property(parent_cssatr, "line-height", "1.25");
+ SPCSSAttr *cssfit = sp_repr_css_attr_new();
+ sp_repr_css_set_property(cssfit, "line-height", parent_lineheight.c_str());
+ double minheight = 0;
+ if (parent_style) {
+ minheight = parent_style->line_height.computed;
+ }
+ if (minheight) {
+ for (auto i : parent->childList(false)) {
+ SPItem *child = dynamic_cast<SPItem *>(i);
+ if (!child) {
+ continue;
+ }
+ recursively_set_properties(child, cssfit);
+ }
+ }
+ sp_repr_css_set_property(cssfit, "line-height", "0");
+ parent->changeCSS(cssfit, "style");
+ subselection_wrap_toggle(true);
+ sp_desktop_set_style(desktop, css, true, true);
+ subselection_wrap_toggle(false);
+ sp_repr_css_attr_unref(cssfit);
+ }
+ // Only need to save for undo if a text item has been changed.
+ itemlist = selection->items();
+ bool modmade = false;
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ modmade = true;
+ break;
+ }
+ }
+
+ // Save for undo
+ if (modmade) {
+ // Call ensureUpToDate() causes rebuild of text layout (with all proper style
+ // cascading, etc.). For multi-line text with sodipodi::role="line", we must explicitly
+ // save new <tspan> 'x' and 'y' attribute values by calling updateRepr().
+ // Partial fix for bug #1590141.
+
+ desktop->getDocument()->ensureUpToDate();
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ (i)->updateRepr();
+ }
+ }
+ if (!_outer) {
+ prepare_inner();
+ }
+ DocumentUndo::maybeDone(desktop->getDocument(), "ttb:line-height", _("Text: Change line-height"), INKSCAPE_ICON("draw-text"));
+ }
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ _freeze = false;
+}
+
+void
+TextToolbar::lineheight_unit_changed(int /* Not Used */)
+{
+ // quit if run by the _changed callbacks or is not text tool
+ if (_freeze || !SP_IS_TEXT_CONTEXT(_desktop->event_context)) {
+ return;
+ }
+ _freeze = true;
+
+ // Get old saved unit
+ int old_unit = _lineheight_unit;
+
+ // Get user selected unit and save as preference
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit.
+ SPILength temp_length;
+ Inkscape::CSSOStringStream temp_stream;
+ temp_stream << 1 << unit->abbr;
+ temp_length.read(temp_stream.str().c_str());
+ prefs->setInt("/tools/text/lineheight/display_unit", temp_length.unit);
+ if (old_unit == temp_length.unit) {
+ _freeze = false;
+ return;
+ } else {
+ _lineheight_unit = temp_length.unit;
+ }
+
+ // Read current line height value
+ double line_height = _line_height_adj->get_value();
+ SPDesktop *desktop = _desktop;
+ Inkscape::Selection *selection = desktop->getSelection();
+ auto itemlist = selection->items();
+
+ // Convert between units
+ double font_size = 0;
+ double doc_scale = 1;
+ int count = 0;
+ bool has_flow = false;
+
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ doc_scale = Geom::Affine(i->i2dt_affine()).descrim();
+ font_size += i->style->font_size.computed * doc_scale;
+ ++count;
+ }
+ if (flowtext) {
+ has_flow = true;
+ }
+ }
+ if (count > 0) {
+ font_size /= count;
+ } else {
+ // ideally use default font-size.
+ font_size = 20;
+ }
+ if ((unit->abbr == "" || unit->abbr == "em") && (old_unit == SP_CSS_UNIT_NONE || old_unit == SP_CSS_UNIT_EM)) {
+ // Do nothing
+ } else if ((unit->abbr == "" || unit->abbr == "em") && old_unit == SP_CSS_UNIT_EX) {
+ line_height *= 0.5;
+ } else if ((unit->abbr) == "ex" && (old_unit == SP_CSS_UNIT_EM || old_unit == SP_CSS_UNIT_NONE)) {
+ line_height *= 2.0;
+ } else if ((unit->abbr == "" || unit->abbr == "em") && old_unit == SP_CSS_UNIT_PERCENT) {
+ line_height /= 100.0;
+ } else if ((unit->abbr) == "%" && (old_unit == SP_CSS_UNIT_EM || old_unit == SP_CSS_UNIT_NONE)) {
+ line_height *= 100;
+ } else if ((unit->abbr) == "ex" && old_unit == SP_CSS_UNIT_PERCENT) {
+ line_height /= 50.0;
+ } else if ((unit->abbr) == "%" && old_unit == SP_CSS_UNIT_EX) {
+ line_height *= 50;
+ } else if (is_relative(unit)) {
+ // Convert absolute to relative... for the moment use average font-size
+ if (old_unit == SP_CSS_UNIT_NONE) old_unit = SP_CSS_UNIT_EM;
+ line_height = Quantity::convert(line_height, sp_style_get_css_unit_string(old_unit), "px");
+
+ if (font_size > 0) {
+ line_height /= font_size;
+ }
+ if ((unit->abbr) == "%") {
+ line_height *= 100;
+ } else if ((unit->abbr) == "ex") {
+ line_height *= 2;
+ }
+ } else if (old_unit == SP_CSS_UNIT_NONE || old_unit == SP_CSS_UNIT_PERCENT || old_unit == SP_CSS_UNIT_EM ||
+ old_unit == SP_CSS_UNIT_EX) {
+ // Convert relative to absolute... for the moment use average font-size
+ if (old_unit == SP_CSS_UNIT_PERCENT) {
+ line_height /= 100.0;
+ } else if (old_unit == SP_CSS_UNIT_EX) {
+ line_height /= 2.0;
+ }
+ line_height *= font_size;
+ line_height = Quantity::convert(line_height, "px", unit);
+ } else {
+ // Convert between different absolute units (only used in GUI)
+ line_height = Quantity::convert(line_height, sp_style_get_css_unit_string(old_unit), unit);
+ }
+ // Set css line height.
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ // Set css line height.
+ if ( is_relative(unit) ) {
+ osfs << line_height << unit->abbr;
+ } else {
+ osfs << Quantity::convert(line_height, unit, "px") << "px";
+ }
+ sp_repr_css_set_property (css, "line-height", osfs.str().c_str());
+
+ // Update GUI with line_height value.
+ _line_height_adj->set_value(line_height);
+ // Update "climb rate" The custom action has a step property but no way to set it.
+ if (unit->abbr == "%") {
+ _line_height_adj->set_step_increment(1.0);
+ _line_height_adj->set_page_increment(10.0);
+ } else {
+ _line_height_adj->set_step_increment(0.1);
+ _line_height_adj->set_page_increment(1.0);
+ }
+ // Internal function to set line-height which is spacing mode dependent.
+ SPItem *parent = itemlist.empty() ? nullptr : dynamic_cast<SPItem *>(*itemlist.begin());
+ SPStyle *parent_style = nullptr;
+ if (parent) {
+ parent_style = parent->style;
+ }
+ bool inside = false;
+ if (_outer) {
+ if (!selection->singleItem() || !parent_style || parent_style->line_height.computed != 0) {
+ for (auto i = itemlist.begin(); i != itemlist.end(); ++i) {
+ if (dynamic_cast<SPText *>(*i) || dynamic_cast<SPFlowtext *>(*i)) {
+ SPItem *item = *i;
+ // Scale by inverse of accumulated parent transform
+ SPCSSAttr *css_set = sp_repr_css_attr_new();
+ sp_repr_css_merge(css_set, css);
+ Geom::Affine const local(item->i2doc_affine());
+ double const ex(local.descrim());
+ if ((ex != 0.0) && (ex != 1.0)) {
+ sp_css_attr_scale(css_set, 1 / ex);
+ }
+ recursively_set_properties(item, css_set);
+ sp_repr_css_attr_unref(css_set);
+ }
+ }
+ } else {
+ inside = true;
+ }
+ }
+ if (!_outer || inside) {
+ SPCSSAttr *parent_cssatr = sp_css_attr_from_style(parent_style, SP_STYLE_FLAG_IFSET);
+ Glib::ustring parent_lineheight = sp_repr_css_property(parent_cssatr, "line-height", "1.25");
+ SPCSSAttr *cssfit = sp_repr_css_attr_new();
+ sp_repr_css_set_property(cssfit, "line-height", parent_lineheight.c_str());
+ double minheight = 0;
+ if (parent_style) {
+ minheight = parent_style->line_height.computed;
+ }
+ if (minheight) {
+ for (auto i : parent->childList(false)) {
+ SPItem *child = dynamic_cast<SPItem *>(i);
+ if (!child) {
+ continue;
+ }
+ recursively_set_properties(child, cssfit);
+ }
+ }
+ sp_repr_css_set_property(cssfit, "line-height", "0");
+ parent->changeCSS(cssfit, "style");
+ subselection_wrap_toggle(true);
+ sp_desktop_set_style(desktop, css, true, true);
+ subselection_wrap_toggle(false);
+ sp_repr_css_attr_unref(cssfit);
+ }
+ itemlist= selection->items();
+ // Only need to save for undo if a text item has been changed.
+ bool modmade = false;
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ modmade = true;
+ break;
+ }
+ }
+ // Save for undo
+ if(modmade) {
+ // Call ensureUpToDate() causes rebuild of text layout (with all proper style
+ // cascading, etc.). For multi-line text with sodipodi::role="line", we must explicitly
+ // save new <tspan> 'x' and 'y' attribute values by calling updateRepr().
+ // Partial fix for bug #1590141.
+
+ desktop->getDocument()->ensureUpToDate();
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ (i)->updateRepr();
+ }
+ }
+ if (_outer) {
+ prepare_inner();
+ }
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:line-height", _("Text: Change line-height unit"), INKSCAPE_ICON("draw-text"));
+ }
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ _freeze = false;
+}
+
+void TextToolbar::fontsize_unit_changed(int /* Not Used */)
+{
+ // quit if run by the _changed callbacks
+ Unit const *unit = _tracker_fs->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit.
+ SPILength temp_size;
+ Inkscape::CSSOStringStream temp_size_stream;
+ temp_size_stream << 1 << unit->abbr;
+ temp_size.read(temp_size_stream.str().c_str());
+ prefs->setInt("/options/font/unitType", temp_size.unit);
+ selection_changed(_desktop->selection);
+}
+
+void
+TextToolbar::wordspacing_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ // At the moment this handles only numerical values (i.e. no em unit).
+ // Set css word-spacing
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ osfs << _word_spacing_adj->get_value() << "px"; // For now always use px
+ sp_repr_css_set_property (css, "word-spacing", osfs.str().c_str());
+ text_outer_set_style(css);
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ } else {
+ // Save for undo
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:word-spacing", _("Text: Change word-spacing"), INKSCAPE_ICON("draw-text"));
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ _freeze = false;
+}
+
+void
+TextToolbar::letterspacing_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ // At the moment this handles only numerical values (i.e. no em unit).
+ // Set css letter-spacing
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ osfs << _letter_spacing_adj->get_value() << "px"; // For now always use px
+ sp_repr_css_set_property (css, "letter-spacing", osfs.str().c_str());
+ text_outer_set_style(css);
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+ else
+ {
+ // Save for undo
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:letter-spacing", _("Text: Change letter-spacing"), INKSCAPE_ICON("draw-text"));
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ _freeze = false;
+}
+
+void
+TextToolbar::dx_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ gdouble new_dx = _dx_adj->get_value();
+ bool modmade = false;
+
+ if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if( tc ) {
+ unsigned char_index = -1;
+ TextTagAttributes *attributes =
+ text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
+ if( attributes ) {
+ double old_dx = attributes->getDx( char_index );
+ double delta_dx = new_dx - old_dx;
+ sp_te_adjust_dx( tc->text, tc->text_sel_start, tc->text_sel_end, _desktop, delta_dx );
+ modmade = true;
+ }
+ }
+ }
+
+ if(modmade) {
+ // Save for undo
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:dx", _("Text: Change dx (kern)"), INKSCAPE_ICON("draw-text"));
+ }
+ _freeze = false;
+}
+
+void
+TextToolbar::dy_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ gdouble new_dy = _dy_adj->get_value();
+ bool modmade = false;
+
+ if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if( tc ) {
+ unsigned char_index = -1;
+ TextTagAttributes *attributes =
+ text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
+ if( attributes ) {
+ double old_dy = attributes->getDy( char_index );
+ double delta_dy = new_dy - old_dy;
+ sp_te_adjust_dy( tc->text, tc->text_sel_start, tc->text_sel_end, _desktop, delta_dy );
+ modmade = true;
+ }
+ }
+ }
+
+ if(modmade) {
+ // Save for undo
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:dy", _("Text: Change dy"), INKSCAPE_ICON("draw-text"));
+ }
+
+ _freeze = false;
+}
+
+void
+TextToolbar::rotation_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ gdouble new_degrees = _rotation_adj->get_value();
+
+ bool modmade = false;
+ if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if( tc ) {
+ unsigned char_index = -1;
+ TextTagAttributes *attributes =
+ text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
+ if( attributes ) {
+ double old_degrees = attributes->getRotate( char_index );
+ double delta_deg = new_degrees - old_degrees;
+ sp_te_adjust_rotation( tc->text, tc->text_sel_start, tc->text_sel_end, _desktop, delta_deg );
+ modmade = true;
+ }
+ }
+ }
+
+ // Save for undo
+ if(modmade) {
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:rotate", _("Text: Change rotate"), INKSCAPE_ICON("draw-text"));
+ }
+
+ _freeze = false;
+}
+
+void TextToolbar::selection_modified_select_tool(Inkscape::Selection *selection, guint flags)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double factor = prefs->getDouble("/options/font/scaleLineHeightFromFontSIze", 1.0);
+ if (factor != 1.0) {
+ Unit const *unit_lh = _tracker->getActiveUnit();
+ g_return_if_fail(unit_lh != nullptr);
+ if (!is_relative(unit_lh) && _outer) {
+ double lineheight = _line_height_adj->get_value();
+ bool is_freeze = _freeze;
+ _freeze = false;
+ _line_height_adj->set_value(lineheight * factor);
+ _freeze = is_freeze;
+ }
+ prefs->setDouble("/options/font/scaleLineHeightFromFontSIze", 1.0);
+ }
+}
+
+void TextToolbar::selection_changed(Inkscape::Selection *selection) // don't bother to update font list if subsel
+ // changed
+{
+#ifdef DEBUG_TEXT
+ static int count = 0;
+ ++count;
+ std::cout << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << "sp_text_toolbox_selection_changed: start " << count << std::endl;
+#endif
+
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+
+#ifdef DEBUG_TEXT
+ std::cout << " Frozen, returning" << std::endl;
+ std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << std::endl;
+#endif
+ return;
+ }
+ _freeze = true;
+
+ // selection defined as argument but not used, argh!!!
+ SPDesktop *desktop = _desktop;
+ SPDocument *document = _desktop->getDocument();
+ selection = desktop->getSelection();
+ auto itemlist = selection->items();
+
+#ifdef DEBUG_TEXT
+ for(auto i : itemlist) {
+ const gchar* id = i->getId();
+ std::cout << " " << id << std::endl;
+ }
+ Glib::ustring selected_text = sp_text_get_selected_text(_desktop->event_context);
+ std::cout << " Selected text: |" << selected_text << "|" << std::endl;
+#endif
+
+ // Only flowed text can be justified, only normal text can be kerned...
+ // Find out if we have flowed text now so we can use it several places
+ gboolean isFlow = false;
+ std::vector<SPItem *> to_work;
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ to_work.push_back(i);
+ }
+ if (flowtext ||
+ (text && text->style && text->style->shape_inside.set)) {
+ isFlow = true;
+ }
+ }
+ bool outside = false;
+ if (selection && to_work.size() == 0) {
+ outside = true;
+ }
+
+ Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance();
+ fontlister->selection_update();
+ // Update font list, but only if widget already created.
+ if (_font_family_item->get_combobox() != nullptr) {
+ _font_family_item->set_active_text(fontlister->get_font_family().c_str(), fontlister->get_font_family_row());
+ _font_style_item->set_active_text(fontlister->get_font_style().c_str());
+ }
+
+ /*
+ * Query from current selection:
+ * Font family (font-family)
+ * Style (font-weight, font-style, font-stretch, font-variant, font-align)
+ * Numbers (font-size, letter-spacing, word-spacing, line-height, text-anchor, writing-mode)
+ * Font specification (Inkscape private attribute)
+ */
+ SPStyle query(document);
+ SPStyle query_fallback(document);
+ int result_family = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTFAMILY);
+ int result_style = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTSTYLE);
+ int result_baseline = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_BASELINES);
+ int result_wmode = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
+
+ // Calling sp_desktop_query_style will result in a call to TextTool::_styleQueried().
+ // This returns the style of the selected text inside the <text> element... which
+ // is often the style of one or more <tspan>s. If we want the style of the outer
+ // <text> objects then we need to bypass the call to TextTool::_styleQueried().
+ // The desktop selection never includes the elements inside the <text> element.
+ int result_numbers = 0;
+ int result_numbers_fallback = 0;
+ if (!outside) {
+ if (_outer && this->_sub_active_item) {
+ std::vector<SPItem *> qactive{ this->_sub_active_item };
+ SPItem *parent = dynamic_cast<SPItem *>(this->_sub_active_item->parent);
+ std::vector<SPItem *> qparent{ parent };
+ result_numbers =
+ sp_desktop_query_style_from_list(qactive, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ result_numbers_fallback =
+ sp_desktop_query_style_from_list(qparent, &query_fallback, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ } else if (_outer) {
+ result_numbers = sp_desktop_query_style_from_list(to_work, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ } else {
+ result_numbers = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ }
+ } else {
+ result_numbers =
+ sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ }
+
+ /*
+ * If no text in selection (querying returned nothing), read the style from
+ * the /tools/text preferences (default style for new texts). Return if
+ * tool bar already set to these preferences.
+ */
+ if (result_family == QUERY_STYLE_NOTHING ||
+ result_style == QUERY_STYLE_NOTHING ||
+ result_numbers == QUERY_STYLE_NOTHING ||
+ result_wmode == QUERY_STYLE_NOTHING ) {
+ // There are no texts in selection, read from preferences.
+ query.readFromPrefs("/tools/text");
+#ifdef DEBUG_TEXT
+ std::cout << " read style from prefs:" << std::endl;
+ sp_print_font( &query );
+#endif
+ if (_text_style_from_prefs) {
+ // Do not reset the toolbar style from prefs if we already did it last time
+ _freeze = false;
+#ifdef DEBUG_TEXT
+ std::cout << " text_style_from_prefs: toolbar already set" << std:: endl;
+ std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << std::endl;
+#endif
+ return;
+ }
+
+ // To ensure the value of the combobox is properly set on start-up, only mark
+ // the prefs set if the combobox has already been constructed.
+ if( _font_family_item->get_combobox() != nullptr ) {
+ _text_style_from_prefs = true;
+ }
+ } else {
+ _text_style_from_prefs = false;
+ }
+
+ // If we have valid query data for text (font-family, font-specification) set toolbar accordingly.
+ {
+ // Size (average of text selected)
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
+ double size = 0;
+ if (!size && _cusor_numbers != QUERY_STYLE_NOTHING) {
+ size = sp_style_css_size_px_to_units(_query_cursor.font_size.computed, unit);
+ }
+ if (!size && result_numbers != QUERY_STYLE_NOTHING) {
+ size = sp_style_css_size_px_to_units(query.font_size.computed, unit);
+ }
+ if (!size && result_numbers_fallback != QUERY_STYLE_NOTHING) {
+ size = sp_style_css_size_px_to_units(query_fallback.font_size.computed, unit);
+ }
+ if (!size && _text_style_from_prefs) {
+ size = sp_style_css_size_px_to_units(query.font_size.computed, unit);
+ }
+
+ auto unit_str = sp_style_get_css_unit_string(unit);
+ Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", unit_str, ")");
+
+ _font_size_item->set_tooltip(tooltip.c_str());
+
+ Inkscape::CSSOStringStream os;
+ // We dot want to parse values just show
+
+ _tracker_fs->setActiveUnitByAbbr(sp_style_get_css_unit_string(unit));
+ int rounded_size = std::round(size);
+ if (std::abs((size - rounded_size)/size) < 0.0001) {
+ // We use rounded_size to avoid rounding errors when, say, converting stored 'px' values to displayed 'pt' values.
+ os << rounded_size;
+ selection_fontsize = rounded_size;
+ } else {
+ os << size;
+ selection_fontsize = size;
+ }
+
+ // Freeze to ignore callbacks.
+ //g_object_freeze_notify( G_OBJECT( fontSizeAction->combobox ) );
+ sp_text_set_sizes(GTK_LIST_STORE(_font_size_item->get_model()), unit);
+ //g_object_thaw_notify( G_OBJECT( fontSizeAction->combobox ) );
+
+ _font_size_item->set_active_text( os.str().c_str() );
+
+ // Superscript
+ gboolean superscriptSet =
+ ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) &&
+ query.baseline_shift.set &&
+ query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+ query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUPER );
+
+ _superscript_item->set_active(superscriptSet);
+
+ // Subscript
+ gboolean subscriptSet =
+ ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) &&
+ query.baseline_shift.set &&
+ query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+ query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUB );
+
+ _subscript_item->set_active(subscriptSet);
+
+ // Alignment
+
+ // Note: SVG 1.1 doesn't include text-align, SVG 1.2 Tiny doesn't include text-align="justify"
+ // text-align="justify" was a draft SVG 1.2 item (along with flowed text).
+ // Only flowed text can be left and right justified at the same time.
+ // Disable button if we don't have flowed text.
+
+ Glib::RefPtr<Gtk::ListStore> store = _align_item->get_store();
+ Gtk::TreeModel::Row row = *(store->get_iter("3")); // Justify entry
+ UI::Widget::ComboToolItemColumns columns;
+ row[columns.col_sensitive] = isFlow;
+
+ int activeButton = 0;
+ if (query.text_align.computed == SP_CSS_TEXT_ALIGN_JUSTIFY)
+ {
+ activeButton = 3;
+ } else {
+ // This should take 'direction' into account
+ if (query.text_anchor.computed == SP_CSS_TEXT_ANCHOR_START) activeButton = 0;
+ if (query.text_anchor.computed == SP_CSS_TEXT_ANCHOR_MIDDLE) activeButton = 1;
+ if (query.text_anchor.computed == SP_CSS_TEXT_ANCHOR_END) activeButton = 2;
+ }
+ _align_item->set_active( activeButton );
+
+ double height = 0;
+ gint line_height_unit = 0;
+
+ if (!height && _cusor_numbers != QUERY_STYLE_NOTHING) {
+ height = _query_cursor.line_height.value;
+ line_height_unit = _query_cursor.line_height.unit;
+ }
+
+ if (!height && result_numbers != QUERY_STYLE_NOTHING) {
+ height = query.line_height.value;
+ line_height_unit = query.line_height.unit;
+ }
+
+ if (!height && result_numbers_fallback != QUERY_STYLE_NOTHING) {
+ height = query_fallback.line_height.value;
+ line_height_unit = query_fallback.line_height.unit;
+ }
+
+ if (!height && _text_style_from_prefs) {
+ height = query.line_height.value;
+ line_height_unit = query.line_height.unit;
+ }
+
+ if (line_height_unit == SP_CSS_UNIT_PERCENT) {
+ height *= 100.0; // Inkscape store % as fraction in .value
+ }
+
+ // We dot want to parse values just show
+ if (!is_relative(SPCSSUnit(line_height_unit))) {
+ gint curunit = prefs->getInt("/tools/text/lineheight/display_unit", 1);
+ // For backwards comaptibility
+ if (is_relative(SPCSSUnit(curunit))) {
+ prefs->setInt("/tools/text/lineheight/display_unit", 1);
+ curunit = 1;
+ }
+ height = Quantity::convert(height, "px", sp_style_get_css_unit_string(curunit));
+ line_height_unit = curunit;
+ }
+ _line_height_adj->set_value(height);
+
+
+ // Update "climb rate"
+ if (line_height_unit == SP_CSS_UNIT_PERCENT) {
+ _line_height_adj->set_step_increment(1.0);
+ _line_height_adj->set_page_increment(10.0);
+ } else {
+ _line_height_adj->set_step_increment(0.1);
+ _line_height_adj->set_page_increment(1.0);
+ }
+
+ if( line_height_unit == SP_CSS_UNIT_NONE ) {
+ // Function 'sp_style_get_css_unit_string' returns 'px' for unit none.
+ // We need to avoid this.
+ _tracker->setActiveUnitByAbbr("");
+ } else {
+ _tracker->setActiveUnitByAbbr(sp_style_get_css_unit_string(line_height_unit));
+ }
+
+ // Save unit so we can do conversions between new/old units.
+ _lineheight_unit = line_height_unit;
+ // Word spacing
+ double wordSpacing;
+ if (query.word_spacing.normal) wordSpacing = 0.0;
+ else wordSpacing = query.word_spacing.computed; // Assume no units (change in desktop-style.cpp)
+
+ _word_spacing_adj->set_value(wordSpacing);
+
+ // Letter spacing
+ double letterSpacing;
+ if (query.letter_spacing.normal) letterSpacing = 0.0;
+ else letterSpacing = query.letter_spacing.computed; // Assume no units (change in desktop-style.cpp)
+
+ _letter_spacing_adj->set_value(letterSpacing);
+
+ // Writing mode
+ int activeButton2 = 0;
+ if (query.writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) activeButton2 = 0;
+ if (query.writing_mode.computed == SP_CSS_WRITING_MODE_TB_RL) activeButton2 = 1;
+ if (query.writing_mode.computed == SP_CSS_WRITING_MODE_TB_LR) activeButton2 = 2;
+
+ _writing_mode_item->set_active( activeButton2 );
+
+ // Orientation
+ int activeButton3 = 0;
+ if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_MIXED ) activeButton3 = 0;
+ if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_UPRIGHT ) activeButton3 = 1;
+ if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_SIDEWAYS) activeButton3 = 2;
+
+ _orientation_item->set_active( activeButton3 );
+
+ // Disable text orientation for horizontal text...
+ _orientation_item->set_sensitive( activeButton2 != 0 );
+
+ // Direction
+ int activeButton4 = 0;
+ if (query.direction.computed == SP_CSS_DIRECTION_LTR ) activeButton4 = 0;
+ if (query.direction.computed == SP_CSS_DIRECTION_RTL ) activeButton4 = 1;
+ _direction_item->set_active( activeButton4 );
+ }
+
+#ifdef DEBUG_TEXT
+ std::cout << " GUI: fontfamily.value: " << query.font_family.value() << std::endl;
+ std::cout << " GUI: font_size.computed: " << query.font_size.computed << std::endl;
+ std::cout << " GUI: font_weight.computed: " << query.font_weight.computed << std::endl;
+ std::cout << " GUI: font_style.computed: " << query.font_style.computed << std::endl;
+ std::cout << " GUI: text_anchor.computed: " << query.text_anchor.computed << std::endl;
+ std::cout << " GUI: text_align.computed: " << query.text_align.computed << std::endl;
+ std::cout << " GUI: line_height.computed: " << query.line_height.computed
+ << " line_height.value: " << query.line_height.value
+ << " line_height.unit: " << query.line_height.unit << std::endl;
+ std::cout << " GUI: word_spacing.computed: " << query.word_spacing.computed
+ << " word_spacing.value: " << query.word_spacing.value
+ << " word_spacing.unit: " << query.word_spacing.unit << std::endl;
+ std::cout << " GUI: letter_spacing.computed: " << query.letter_spacing.computed
+ << " letter_spacing.value: " << query.letter_spacing.value
+ << " letter_spacing.unit: " << query.letter_spacing.unit << std::endl;
+ std::cout << " GUI: writing_mode.computed: " << query.writing_mode.computed << std::endl;
+#endif
+
+ // Kerning (xshift), yshift, rotation. NB: These are not CSS attributes.
+ if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if( tc ) {
+ unsigned char_index = -1;
+ TextTagAttributes *attributes =
+ text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
+ if( attributes ) {
+
+ // Dx
+ double dx = attributes->getDx( char_index );
+ _dx_adj->set_value(dx);
+
+ // Dy
+ double dy = attributes->getDy( char_index );
+ _dy_adj->set_value(dy);
+
+ // Rotation
+ double rotation = attributes->getRotate( char_index );
+ /* SVG value is between 0 and 360 but we're using -180 to 180 in widget */
+ if( rotation > 180.0 ) rotation -= 360.0;
+ _rotation_adj->set_value(rotation);
+
+#ifdef DEBUG_TEXT
+ std::cout << " GUI: Dx: " << dx << std::endl;
+ std::cout << " GUI: Dy: " << dy << std::endl;
+ std::cout << " GUI: Rotation: " << rotation << std::endl;
+#endif
+ }
+ }
+ }
+
+ {
+ // Set these here as we don't always have kerning/rotating attributes
+ _dx_item->set_sensitive(!isFlow);
+ _dy_item->set_sensitive(!isFlow);
+ _rotation_item->set_sensitive(!isFlow);
+ }
+
+#ifdef DEBUG_TEXT
+ std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << std::endl;
+#endif
+
+ _freeze = false;
+}
+
+void
+TextToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec) {
+ bool is_text_toolbar = SP_IS_TEXT_CONTEXT(ec);
+ bool is_select_toolbar = !is_text_toolbar && SP_IS_SELECT_CONTEXT(ec);
+ if (is_text_toolbar) {
+ // Watch selection
+ // Ensure FontLister is updated here first..................
+ c_selection_changed =
+ desktop->getSelection()->connectChangedFirst(sigc::mem_fun(*this, &TextToolbar::selection_changed));
+ c_selection_modified = desktop->getSelection()->connectModifiedFirst(sigc::mem_fun(*this, &TextToolbar::selection_modified));
+ c_subselection_changed = desktop->connect_text_cursor_moved([=](void* sender, Inkscape::UI::Tools::TextTool* tool){
+ subselection_changed(tool);
+ });
+ this->_sub_active_item = nullptr;
+ this->_cusor_numbers = 0;
+ selection_changed(desktop->getSelection());
+ } else if (is_select_toolbar) {
+ c_selection_modified_select_tool = desktop->getSelection()->connectModifiedFirst(
+ sigc::mem_fun(*this, &TextToolbar::selection_modified_select_tool));
+ }
+
+
+ if (!is_text_toolbar) {
+ c_selection_changed.disconnect();
+ c_selection_modified.disconnect();
+ c_subselection_changed.disconnect();
+ }
+
+ if (!is_select_toolbar) {
+ c_selection_modified_select_tool.disconnect();
+ }
+}
+
+void
+TextToolbar::selection_modified(Inkscape::Selection *selection, guint /*flags*/)
+{
+ this->_sub_active_item = nullptr;
+ selection_changed(selection);
+
+}
+
+void TextToolbar::subselection_wrap_toggle(bool start)
+{
+ if (SP_IS_TEXT_CONTEXT(_desktop->event_context)) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if (tc) {
+ _updating = true;
+ Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
+ if (layout) {
+ Inkscape::Text::Layout::iterator start_selection = tc->text_sel_start;
+ Inkscape::Text::Layout::iterator end_selection = tc->text_sel_end;
+ tc->text_sel_start = wrap_start;
+ tc->text_sel_end = wrap_end;
+ wrap_start = start_selection;
+ wrap_end = end_selection;
+ }
+ _updating = start;
+ }
+ }
+}
+
+/*
+* This function parses the just created line height in one or more lines of a text subselection.
+* It can describe 2 kinds of input because when we store a text element we apply a fallback that change
+* structure. This visually is not reflected but user maybe want to change a part of this subselection
+* once the fallback is created, so we need more complex logic here to fill the gap.
+* Basically, we have a line height changed in the new wrapper element/s between wrap_start and wrap_end.
+* These variables store starting iterator of first char in line and last char in line in a subselection.
+* These elements are styled well but we can have orphaned text nodes before and after the subselection.
+* So, normally 3 elements are inside a container as direct child of a text element.
+* We need to apply the container style to the optional first and last text nodes,
+* wrapping into a new element that gets the container style (this is not part to the sub-selection).
+* After wrapping, we unindent all children of the container and remove the container.
+*
+*/
+void TextToolbar::prepare_inner()
+{
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if (!tc) {
+ return;
+ }
+ Inkscape::Text::Layout *layout = const_cast<Inkscape::Text::Layout *>(te_get_layout(tc->text));
+ if (!layout) {
+ return;
+ }
+ SPDocument *doc = _desktop->getDocument();
+ SPObject *spobject = dynamic_cast<SPObject *>(tc->text);
+ SPItem *spitem = dynamic_cast<SPItem *>(tc->text);
+ SPText *text = dynamic_cast<SPText *>(tc->text);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(tc->text);
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+ if (!spobject) {
+ return;
+ }
+
+ // We check for external files with text nodes direct children of text element
+ // and wrap it into a tspan elements as inkscape do.
+ if (text) {
+ bool changed = false;
+ std::vector<SPObject *> childs = spitem->childList(false);
+ for (auto child : childs) {
+ SPString *spstring = dynamic_cast<SPString *>(child);
+ if (spstring) {
+ Glib::ustring content = spstring->string;
+ if (content != "\n") {
+ Inkscape::XML::Node *rstring = xml_doc->createTextNode(content.c_str());
+ Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
+ //Inkscape::XML::Node *rnl = xml_doc->createTextNode("\n");
+ rtspan->setAttribute("sodipodi:role", "line");
+ rtspan->addChild(rstring, nullptr);
+ text->getRepr()->addChild(rtspan, child->getRepr());
+ Inkscape::GC::release(rstring);
+ Inkscape::GC::release(rtspan);
+ text->getRepr()->removeChild(spstring->getRepr());
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ // proper rebuild happens later,
+ // this just updates layout to use now, avoids use after free
+ text->rebuildLayout();
+ }
+ }
+
+ std::vector<SPObject *> containers;
+ {
+ // populate `containers` with objects that will be modified.
+
+ // Temporarily remove the shape so Layout calculates
+ // the position of wrap_end and wrap_start, even if
+ // one of these are hidden because the previous line height was changed
+ if (text) {
+ text->hide_shape_inside();
+ } else if (flowtext) {
+ flowtext->fix_overflow_flowregion(false);
+ }
+ SPObject *rawptr_start = nullptr;
+ SPObject *rawptr_end = nullptr;
+ layout->validateIterator(&wrap_start);
+ layout->validateIterator(&wrap_end);
+ layout->getSourceOfCharacter(wrap_start, &rawptr_start);
+ layout->getSourceOfCharacter(wrap_end, &rawptr_end);
+ if (text) {
+ text->show_shape_inside();
+ } else if (flowtext) {
+ flowtext->fix_overflow_flowregion(true);
+ }
+ if (!rawptr_start || !rawptr_end) {
+ return;
+ }
+
+ // Loop through parents of start and end till we reach
+ // first children of the text element.
+ // Get all objects between start and end (inclusive)
+ SPObject *start = rawptr_start;
+ SPObject *end = rawptr_end;
+ while (start->parent != spobject) {
+ start = start->parent;
+ }
+ while (end->parent != spobject) {
+ end = end->parent;
+ }
+
+ while (start && start != end) {
+ containers.push_back(start);
+ start = start->getNext();
+ }
+ if (start) {
+ containers.push_back(start);
+ }
+ }
+
+ for (auto container : containers) {
+ Inkscape::XML::Node *prevchild = container->getRepr();
+ std::vector<SPObject*> childs = container->childList(false);
+ for (auto child : childs) {
+ SPString *spstring = dynamic_cast<SPString *>(child);
+ SPFlowtspan *flowtspan = dynamic_cast<SPFlowtspan *>(child);
+ SPTSpan *tspan = dynamic_cast<SPTSpan *>(child);
+ // we need to upper all flowtspans to container level
+ // to do this we need to change the element from flowspan to flowpara
+ if (flowtspan) {
+ Inkscape::XML::Node *flowpara = xml_doc->createElement("svg:flowPara");
+ std::vector<SPObject*> fts_childs = flowtspan->childList(false);
+ bool hascontent = false;
+ // we need to move the contents to the new created element
+ // maybe we can move directly but it is safer for me to duplicate,
+ // inject into the new element and delete original
+ for (auto fts_child : fts_childs) {
+ // is this check necessary?
+ if (fts_child) {
+ Inkscape::XML::Node *fts_child_node = fts_child->getRepr()->duplicate(xml_doc);
+ flowtspan->getRepr()->removeChild(fts_child->getRepr());
+ flowpara->addChild(fts_child_node, nullptr);
+ Inkscape::GC::release(fts_child_node);
+ hascontent = true;
+ }
+ }
+ // if no contents we dont want to add
+ if (hascontent) {
+ flowpara->setAttribute("style", flowtspan->getRepr()->attribute("style"));
+ spobject->getRepr()->addChild(flowpara, prevchild);
+ Inkscape::GC::release(flowpara);
+ prevchild = flowpara;
+ }
+ container->getRepr()->removeChild(flowtspan->getRepr());
+ } else if (tspan) {
+ if (child->childList(false).size()) {
+ child->getRepr()->setAttribute("sodipodi:role", "line");
+ // maybe we need to move unindent function here
+ // to be the same as other here
+ prevchild = unindent_node(child->getRepr(), prevchild);
+ } else {
+ // if no contents we dont want to add
+ container->getRepr()->removeChild(child->getRepr());
+ }
+ } else if (spstring) {
+ // we are on a text node, we act different if in a text or flowtext.
+ // wrap a duplicate of the element and unindent after the prevchild
+ // and finally delete original
+ Inkscape::XML::Node *string_node = xml_doc->createTextNode(spstring->string.c_str());
+ if (text) {
+ Inkscape::XML::Node *tspan_node = xml_doc->createElement("svg:tspan");
+ tspan_node->setAttribute("style", container->getRepr()->attribute("style"));
+ tspan_node->addChild(string_node, nullptr);
+ tspan_node->setAttribute("sodipodi:role", "line");
+ text->getRepr()->addChild(tspan_node, prevchild);
+ Inkscape::GC::release(string_node);
+ Inkscape::GC::release(tspan_node);
+ prevchild = tspan_node;
+ } else if (flowtext) {
+ Inkscape::XML::Node *flowpara_node = xml_doc->createElement("svg:flowPara");
+ flowpara_node->setAttribute("style", container->getRepr()->attribute("style"));
+ flowpara_node->addChild(string_node, nullptr);
+ flowtext->getRepr()->addChild(flowpara_node, prevchild);
+ Inkscape::GC::release(string_node);
+ Inkscape::GC::release(flowpara_node);
+ prevchild = flowpara_node;
+ }
+ container->getRepr()->removeChild(spstring->getRepr());
+ }
+ }
+ tc->text->getRepr()->removeChild(container->getRepr());
+ }
+}
+
+Inkscape::XML::Node *TextToolbar::unindent_node(Inkscape::XML::Node *repr, Inkscape::XML::Node *prevchild)
+{
+ g_assert(repr != nullptr);
+
+ Inkscape::XML::Node *parent = repr->parent();
+ if (parent) {
+ Inkscape::XML::Node *grandparent = parent->parent();
+ if (grandparent) {
+ SPDocument *doc = _desktop->getDocument();
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *newrepr = repr->duplicate(xml_doc);
+ parent->removeChild(repr);
+ grandparent->addChild(newrepr, prevchild);
+ Inkscape::GC::release(newrepr);
+ newrepr->setAttribute("sodipodi:role", "line");
+ return newrepr;
+ }
+ }
+ std::cout << "error on TextToolbar.cpp::2433" << std::endl;
+ return repr;
+}
+
+void TextToolbar::subselection_changed(Inkscape::UI::Tools::TextTool* tc)
+{
+#ifdef DEBUG_TEXT
+ std::cout << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << "subselection_changed: start " << std::endl;
+#endif
+ // quit if run by the _changed callbacks
+ this->_sub_active_item = nullptr;
+ if (_updating) {
+ return;
+ }
+ if (tc) {
+ Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
+ if (layout) {
+ Inkscape::Text::Layout::iterator start = layout->begin();
+ Inkscape::Text::Layout::iterator end = layout->end();
+ Inkscape::Text::Layout::iterator start_selection = tc->text_sel_start;
+ Inkscape::Text::Layout::iterator end_selection = tc->text_sel_end;
+#ifdef DEBUG_TEXT
+ std::cout << " GUI: Start of text: " << layout->iteratorToCharIndex(start) << std::endl;
+ std::cout << " GUI: End of text: " << layout->iteratorToCharIndex(end) << std::endl;
+ std::cout << " GUI: Start of selection: " << layout->iteratorToCharIndex(start_selection) << std::endl;
+ std::cout << " GUI: End of selection: " << layout->iteratorToCharIndex(end_selection) << std::endl;
+ std::cout << " GUI: Loop Subelements: " << std::endl;
+ std::cout << " ::::::::::::::::::::::::::::::::::::::::::::: " << std::endl;
+#endif
+ gint startline = layout->paragraphIndex(start_selection);
+ if (start_selection == end_selection) {
+ this->_outer = true;
+ gint counter = 0;
+ for (auto child : tc->text->childList(false)) {
+ SPItem *item = dynamic_cast<SPItem *>(child);
+ if (item && counter == startline) {
+ this->_sub_active_item = item;
+ int origin_selection = layout->iteratorToCharIndex(start_selection);
+ Inkscape::Text::Layout::iterator next = layout->charIndexToIterator(origin_selection + 1);
+ Inkscape::Text::Layout::iterator prev = layout->charIndexToIterator(origin_selection - 1);
+ //TODO: find a better way to init
+ _updating = true;
+ SPStyle query(_desktop->getDocument());
+ _query_cursor = query;
+ Inkscape::Text::Layout::iterator start_line = tc->text_sel_start;
+ start_line.thisStartOfLine();
+ if (tc->text_sel_start == start_line) {
+ tc->text_sel_start = next;
+ } else {
+ tc->text_sel_start = prev;
+ }
+ _cusor_numbers = sp_desktop_query_style(_desktop, &_query_cursor, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ tc->text_sel_start = start_selection;
+ wrap_start = tc->text_sel_start;
+ wrap_end = tc->text_sel_end;
+ wrap_start.thisStartOfLine();
+ wrap_end.thisEndOfLine();
+ _updating = false;
+ break;
+ }
+ ++counter;
+ }
+ selection_changed(nullptr);
+ } else if ((start_selection == start && end_selection == end) ||
+ (start_selection == end && end_selection == start)) {
+ // full subselection
+ _cusor_numbers = 0;
+ this->_outer = true;
+ selection_changed(nullptr);
+ } else {
+ _cusor_numbers = 0;
+ this->_outer = false;
+ wrap_start = tc->text_sel_start;
+ wrap_end = tc->text_sel_end;
+ if (tc->text_sel_start > tc->text_sel_end) {
+ wrap_start.thisEndOfLine();
+ wrap_end.thisStartOfLine();
+ } else {
+ wrap_start.thisStartOfLine();
+ wrap_end.thisEndOfLine();
+ }
+ selection_changed(nullptr);
+ }
+ }
+ }
+#ifdef DEBUG_TEXT
+ std::cout << "subselection_changed: exit " << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << std::endl;
+#endif
+}
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :