// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * Text aux toolbar */ /* Authors: * MenTaLguY * Lauris Kaplinski * bulia byak * Frank Felfe * John Cliff * David Turner * Josh Andler * Jon A. Cruz * Maximilian Albert * Tavmjong Bah * Abhishek Sharma * Kris De Gussem * Tavmjong Bah * * 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 #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/dialog/dialog-container.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 "util/font-collections.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 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 selectList; SPDesktop *desktop = SP_ACTIVE_DESKTOP; SPDocument *document = desktop->getDocument(); auto allList = get_all_items(document->getRoot(), desktop, false, false, true); for(std::vector::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 Collections popover */ { auto font_collection_item = Gtk::manage(new Gtk::ToolItem); add(*font_collection_item); auto font_collection_button = Gtk::manage(new Gtk::MenuButton); font_collection_button->set_image_from_icon_name(INKSCAPE_ICON("font_collections")); font_collection_button->set_always_show_image(true); font_collection_button->set_tooltip_text(_("Select Font Collections")); font_collection_item->add(*font_collection_button); // Popover. auto font_collection_popover = Gtk::manage(new Gtk::Popover(*font_collection_button)); // font_collection_popover->set_modal(false); // Stay open until button clicked again. font_collection_button->set_popover(*font_collection_popover); // Grid inside the popover. auto popover_grid = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); popover_grid->set_margin_top(4); popover_grid->set_margin_bottom(4); popover_grid->set_margin_start(4); popover_grid->set_margin_end(4); popover_grid->show_all(); // This frame will contain the list of the font collections. auto popover_frame = Gtk::manage(new Gtk::Frame); popover_frame->show_all(); popover_frame->set_label(_("Font Collections")); popover_frame->set_margin_top(4); popover_grid->add(*popover_frame); // The ListBox widget will display the names of the font collections. font_collections_list = Gtk::manage(new Gtk::ListBox); popover_frame->add(*font_collections_list); font_collections_list->show_all(); // To open the Font Collections Manager dialogue. auto fcm_btn = Gtk::manage(new Gtk::Button); fcm_btn->set_tooltip_text(_("Open the Font Collections Manager dialog")); fcm_btn->set_label(_("Open Collections Editor")); fcm_btn->set_margin_top(4); popover_grid->add(*fcm_btn); fcm_btn->show_all(); fcm_btn->signal_clicked().connect([=](){ TextToolbar::on_fcm_button_pressed(); }); // To reset the selected font collections and the font list. auto reset_item = Gtk::manage(new Gtk::ToolItem); add(*reset_item); auto reset_btn = Gtk::manage(new Gtk::Button); reset_btn->set_tooltip_text(_("Show all available fonts")); reset_btn->set_image_from_icon_name(INKSCAPE_ICON("view-refresh")); reset_btn->set_always_show_image(true); reset_item->add(*reset_btn); reset_btn->show_all(); // reset_btn->set_hexpand(false); reset_btn->signal_clicked().connect([=](){ TextToolbar::on_reset_button_pressed(); }); font_collection_popover->add(*popover_grid); // Attach the signal to display the popover. font_collection_popover->signal_show().connect([=](){ display_font_collections(); }, false); FontCollections *font_collections = Inkscape::FontCollections::get(); // This signal will keep both the Text and Font dialogue and // TextToolbar popovers in sync with each other. fc_changed_selection = font_collections->connect_selection_update([=]() { display_font_collections(); }); // This one will keep the text toolbar Font Collections // updated in case of any change in the Font Collections. fc_update = font_collections->connect_update([=]() { display_font_collections(); }); } /* Font family */ { // Font list Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance(); fontlister->update_font_list(desktop->getDocument()); Glib::RefPtr 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([=](){ 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 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([=](){ 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([=](){ 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 labels = {_("Smaller spacing"), "", "", "", "", C_("Text tool", "Normal"), "", "", "", "", "", _("Larger spacing")}; std::vector 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([=](){ 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 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 labels = {_("Negative spacing"), "", "", "", C_("Text tool", "Normal"), "", "", "", "", "", "", "", _("Positive spacing")}; std::vector 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 labels = {_("Negative spacing"), "", "", "", C_("Text tool", "Normal"), "", "", "", "", "", "", "", _("Positive spacing")}; std::vector 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 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 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 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 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 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 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 element. If we want to set // the style on the outer objects we need to bypass this call. SPDesktop *desktop = _desktop; if(_outer) { // Apply css to parent text objects directly. for (auto item : desktop->getSelection()->items()) { if (is(item) || is(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 ); if (mergeDefaultStyle(css)) { // If there is a selection, update DocumentUndo::done(_desktop->getDocument(), _("Text: Change font family"), INKSCAPE_ICON("draw-text")); } sp_repr_css_attr_unref (css); } // unfreeze _freeze = false; SPDocument *document = _desktop->getDocument(); fontlister->add_document_fonts_at_top(document); #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 (mergeDefaultStyle(css)) { 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 (mergeDefaultStyle(css)) { 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) { auto text = cast(i); // auto flowtext = cast(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 = cast(item)->attributes.firstXY(); if (axis == Geom::X) { XY = XY + Geom::Point (move, 0); } else { XY = XY + Geom::Point (0, move); } cast(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; } } if (mergeDefaultStyle(css)) { 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; } } if (mergeDefaultStyle(css)) { 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; } } if (mergeDefaultStyle(css)) { 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; } } if (mergeDefaultStyle(css)) { 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 { auto parent = itemlist.front(); 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)) { auto child = cast(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) { auto text = cast(i); auto flowtext = cast(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 'x' and 'y' attribute values by calling updateRepr(). // Partial fix for bug #1590141. desktop->getDocument()->ensureUpToDate(); for (auto i : itemlist) { auto text = cast(i); auto flowtext = cast(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")); } mergeDefaultStyle(css); sp_repr_css_attr_unref (css); _freeze = false; } /** * Merge the style into either the tool or the desktop style depending on * which one the user has decided to use in the preferences. * * @returns true if style was set to an object. */ bool TextToolbar::mergeDefaultStyle(SPCSSAttr *css) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); // 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) { prefs->mergeStyle("/tools/text/style", css); } // This updates the global style sp_desktop_set_style (_desktop, css, true, true); return result_numbers != QUERY_STYLE_NOTHING; } 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; for (auto i : itemlist) { auto text = cast(i); auto flowtext = cast(i); if (text || flowtext) { doc_scale = Geom::Affine(i->i2dt_affine()).descrim(); font_size += i->style->font_size.computed * doc_scale; ++count; } } 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 : itemlist.front(); 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 (is(*i) || is(*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)) { auto child = cast(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) { auto text = cast(i); auto flowtext = cast(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 'x' and 'y' attribute values by calling updateRepr(). // Partial fix for bug #1590141. desktop->getDocument()->ensureUpToDate(); for (auto i : itemlist) { auto text = cast(i); auto flowtext = cast(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")); } mergeDefaultStyle(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->getSelection()); } 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 (mergeDefaultStyle(css)) { 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 (mergeDefaultStyle(css)) { 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 to_work; for (auto i : itemlist) { auto text = cast(i); auto flowtext = cast(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 element... which // is often the style of one or more s. If we want the style of the outer // objects then we need to bypass the call to TextTool::_styleQueried(). // The desktop selection never includes the elements inside the element. int result_numbers = 0; int result_numbers_fallback = 0; if (!outside) { if (_outer && this->_sub_active_item) { std::vector qactive{ this->_sub_active_item }; auto parent = cast(this->_sub_active_item->parent); std::vector 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); } Inkscape::Preferences *prefs = Inkscape::Preferences::get(); /* * 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. if (prefs->getBool("/tools/text/usecurrent")) { query.mergeCSS(sp_desktop_get_style(desktop, true)); } else { 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) 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 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(te_get_layout(tc->text)); if (!layout) { return; } auto doc = _desktop->getDocument(); auto spobject = tc->text; auto spitem = tc->text; auto text = cast(tc->text); auto flowtext = cast(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 childs = spitem->childList(false); for (auto child : childs) { auto spstring = cast(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 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 childs = container->childList(false); for (auto child : childs) { auto spstring = cast(child); auto flowtspan = cast(child); auto tspan = cast(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 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::cerr << "TextToolbar::unindent_node error: node has no (grand)parent, nothing done.\n"; return repr; } void TextToolbar::display_font_collections() { for (auto row : font_collections_list->get_children()) { if (row) { font_collections_list->remove(*row); } } FontCollections *font_collections = Inkscape::FontCollections::get(); // Insert system collections. for(auto const& col: font_collections->get_collections(true)) { auto btn = Gtk::make_managed(col); btn->set_margin_bottom(2); btn->set_active(font_collections->is_collection_selected(col)); btn->signal_toggled().connect([=](){ // toggle font system collection font_collections->update_selected_collections(col); }); // g_message("tag: %s", tag.display_name.c_str()); auto row = Gtk::make_managed(); row->set_can_focus(false); row->add(*btn); row->show_all(); font_collections_list->append(*row); } // Insert row separator. auto sep = Gtk::manage(new Gtk::Separator()); sep->set_margin_bottom(2); auto sep_row = Gtk::make_managed(); sep_row->set_can_focus(false); sep_row->add(*sep); sep_row->show_all(); font_collections_list->append(*sep_row); // Insert user collections. for (auto const& col: font_collections->get_collections()) { auto btn = Gtk::make_managed(col); btn->set_margin_bottom(2); btn->set_active(font_collections->is_collection_selected(col)); btn->signal_toggled().connect([=](){ // toggle font collection font_collections->update_selected_collections(col); }); // g_message("tag: %s", tag.display_name.c_str()); auto row = Gtk::make_managed(); row->set_can_focus(false); row->add(*btn); row->show_all(); font_collections_list->append(*row); } } void TextToolbar::on_fcm_button_pressed() { // Inkscape::UI::Dialog::FontCollectionsManager::getInstance(); if(auto desktop = SP_ACTIVE_DESKTOP) { if (auto container = desktop->getContainer()) { container->new_floating_dialog("FontCollections"); } } } void TextToolbar::on_reset_button_pressed() { FontCollections *font_collections = Inkscape::FontCollections::get(); font_collections->clear_selected_collections(); Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance(); font_lister->init_font_families(); font_lister->init_default_styles(); SPDocument *document = _desktop->getDocument(); if(!document) { return; } font_lister->add_document_fonts_at_top(document); } 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)) { auto item = cast(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 :