diff options
Diffstat (limited to 'src/ui/widget/font-selector.cpp')
-rw-r--r-- | src/ui/widget/font-selector.cpp | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/src/ui/widget/font-selector.cpp b/src/ui/widget/font-selector.cpp new file mode 100644 index 0000000..0617e87 --- /dev/null +++ b/src/ui/widget/font-selector.cpp @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2018 Tavmong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/i18n.h> +#include <glibmm/markup.h> + +#include "font-selector.h" + +#include "libnrtype/font-lister.h" +#include "libnrtype/font-instance.h" + +// For updating from selection +#include "inkscape.h" +#include "desktop.h" +#include "object/sp-text.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +FontSelector::FontSelector (bool with_size, bool with_variations) + : Gtk::Grid () + , family_frame (_("Font family")) + , style_frame (C_("Font selector", "Style")) + , size_label (_("Font size")) + , size_combobox (true) // With entry + , signal_block (false) + , font_size (18) +{ + + Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance(); + Glib::RefPtr<Gtk::TreeModel> model = font_lister->get_font_list(); + int total = model->children().size(); + int height = 30; + if (total > 1000) { + height = 30000/total; + g_warning("You have a huge number of font families (%d), " + "and Cairo is limiting the size of widgets you can draw.\n" + "Your preview cell height is capped to %d.", + total, height); + // hope we dont need a forced height because now pango line height + // not add data outside parent rendered expanding it so no naturall cells become over 30 height + } + family_cell.set_fixed_size(-1, height); + // Font family + family_treecolumn.pack_start (family_cell, false); + family_treecolumn.set_fixed_width (120); // limit minimal width to keep entire dialog narrow; column can still grow + family_treecolumn.add_attribute (family_cell, "text", 0); + family_treecolumn.set_cell_data_func (family_cell, &font_lister_cell_data_func); + + family_treeview.set_row_separator_func (&font_lister_separator_func); + family_treeview.set_model (model); + family_treeview.set_name ("FontSelector: Family"); + family_treeview.set_headers_visible (false); + family_treeview.append_column (family_treecolumn); + + family_scroll.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + family_scroll.add (family_treeview); + + family_frame.set_hexpand (true); + family_frame.set_vexpand (true); + family_frame.add (family_scroll); + + // Style + style_treecolumn.pack_start (style_cell, false); + style_treecolumn.add_attribute (style_cell, "text", 0); + style_treecolumn.set_cell_data_func (style_cell, sigc::mem_fun(*this, &FontSelector::style_cell_data_func)); + style_treecolumn.set_title ("Face"); + style_treecolumn.set_resizable (true); + + style_treeview.set_model (font_lister->get_style_list()); + style_treeview.set_name ("FontSelectorStyle"); + style_treeview.append_column ("CSS", font_lister->FontStyleList.cssStyle); + style_treeview.append_column (style_treecolumn); + + style_treeview.get_column(0)->set_resizable (true); + + style_scroll.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + style_scroll.add (style_treeview); + + style_frame.set_hexpand (true); + style_frame.set_vexpand (true); + style_frame.add (style_scroll); + + // Size + size_combobox.set_name ("FontSelectorSize"); + if (auto entry = size_combobox.get_entry()) { + // limit min size of the entry box to 6 chars, so it doesn't inflate entire dialog! + entry->set_width_chars(6); + } + set_sizes(); + size_combobox.set_active_text( "18" ); + + // Font Variations + font_variations.set_vexpand (true); + font_variations_scroll.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + font_variations_scroll.add (font_variations); + + // Grid + set_name ("FontSelectorGrid"); + set_row_spacing(4); + set_column_spacing(4); + // Add extra columns to the "family frame" to change space distribution + // by prioritizing font family over styles + const int extra = 4; + attach (family_frame, 0, 0, 1 + extra, 2); + attach (style_frame, 1 + extra, 0, 2, 1); + if (with_size) { // Glyph panel does not use size. + attach (size_label, 1 + extra, 1, 1, 1); + attach (size_combobox, 2 + extra, 1, 1, 1); + } + if (with_variations) { // Glyphs panel does not use variations. + attach (font_variations_scroll, 0, 2, 3 + extra, 1); + } + + // Add signals + family_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_family_changed)); + style_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_style_changed)); + size_combobox.signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_size_changed)); + font_variations.connectChanged(sigc::mem_fun(*this, &FontSelector::on_variations_changed)); + + show_all_children(); + + // Initialize font family lists. (May already be done.) Should be done on document change. + font_lister->update_font_list(SP_ACTIVE_DESKTOP->getDocument()); +} + +void +FontSelector::set_sizes () +{ + size_combobox.remove_all(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT); + + 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-internal.h + // PX PT PC MM CM IN EM EX % + double ratios[] = {1, 1, 1, 10, 4, 40, 100, 16, 8, 0.16}; + + for (int i : sizes) + { + double size = i/ratios[unit]; + size_combobox.append( Glib::ustring::format(size) ); + } +} + +void +FontSelector::set_fontsize_tooltip() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT); + Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", sp_style_get_css_unit_string(unit), ")"); + size_combobox.set_tooltip_text (tooltip); +} + +// Update GUI. +// We keep a private copy of the style list as the font-family in widget is only temporary +// until the "Apply" button is set so the style list can be different from that in +// FontLister. +void +FontSelector::update_font () +{ + signal_block = true; + + Inkscape::FontLister *font_lister = Inkscape::FontLister::get_instance(); + Gtk::TreePath path; + Glib::ustring family = font_lister->get_font_family(); + Glib::ustring style = font_lister->get_font_style(); + + // Set font family + try { + path = font_lister->get_row_for_font (family); + } catch (...) { + std::cerr << "FontSelector::update_font: Couldn't find row for font-family: " + << family << std::endl; + path.clear(); + path.push_back(0); + } + + Gtk::TreePath currentPath; + Gtk::TreeViewColumn *currentColumn; + family_treeview.get_cursor(currentPath, currentColumn); + if (currentPath.empty() || !font_lister->is_path_for_font(currentPath, family)) { + family_treeview.set_cursor (path); + family_treeview.scroll_to_row (path); + } + + // Get font-lister style list for selected family + Gtk::TreeModel::Row row = *(family_treeview.get_model()->get_iter (path)); + GList *styles; + row.get_value(1, styles); + + // Copy font-lister style list to private list store, searching for match. + Gtk::TreeModel::iterator match; + FontLister::FontStyleListClass FontStyleList; + Glib::RefPtr<Gtk::ListStore> local_style_list_store = Gtk::ListStore::create(FontStyleList); + for ( ; styles; styles = styles->next ) { + Gtk::TreeModel::iterator treeModelIter = local_style_list_store->append(); + (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)styles->data)->CssName; + (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)styles->data)->DisplayName; + if (style == ((StyleNames*)styles->data)->CssName) { + match = treeModelIter; + } + } + + // Attach store to tree view and select row. + style_treeview.set_model (local_style_list_store); + if (match) { + style_treeview.get_selection()->select (match); + } + + Glib::ustring fontspec = font_lister->get_fontspec(); + update_variations(fontspec); + + signal_block = false; +} + +void +FontSelector::update_size (double size) +{ + signal_block = true; + + // Set font size + std::stringstream ss; + ss << size; + size_combobox.get_entry()->set_text( ss.str() ); + font_size = size; // Store value + set_fontsize_tooltip(); + + signal_block = false; +} + + +// If use_variations is true (default), we get variation values from variations widget otherwise we +// get values from CSS widget (we need to be able to keep the two widgets synchronized both ways). +Glib::ustring +FontSelector::get_fontspec(bool use_variations) { + + // Build new fontspec from GUI settings + Glib::ustring family = "Sans"; // Default...family list may not have been constructed. + Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected(); + if (iter) { + (*iter).get_value(0, family); + } + + Glib::ustring style = "Normal"; + iter = style_treeview.get_selection()->get_selected(); + if (iter) { + (*iter).get_value(0, style); + } + + if (family.empty()) { + std::cerr << "FontSelector::get_fontspec: empty family!" << std::endl; + } + + if (style.empty()) { + std::cerr << "FontSelector::get_fontspec: empty style!" << std::endl; + } + + Glib::ustring fontspec = family + ", "; + + if (use_variations) { + // Clip any font_variation data in 'style' as we'll replace it. + auto pos = style.find('@'); + if (pos != Glib::ustring::npos) { + style.erase (pos, style.length()-1); + } + + Glib::ustring variations = font_variations.get_pango_string(); + + if (variations.empty()) { + fontspec += style; + } else { + fontspec += variations; + } + } else { + fontspec += style; + } + + return fontspec; +} + +void +FontSelector::style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter) +{ + Glib::ustring family = "Sans"; // Default...family list may not have been constructed. + Gtk::TreeModel::iterator iter_family = family_treeview.get_selection()->get_selected(); + if (iter_family) { + (*iter_family).get_value(0, family); + } + + Glib::ustring style = "Normal"; + (*iter).get_value(1, style); + + Glib::ustring style_escaped = Glib::Markup::escape_text( style ); + Glib::ustring font_desc = Glib::Markup::escape_text( family + ", " + style ); + Glib::ustring markup; + + markup = "<span font='" + font_desc + "'>" + style_escaped + "</span>"; + + // std::cout << " markup: " << markup << " (" << name << ")" << std::endl; + + renderer->set_property("markup", markup); +} + + +// Callbacks + +// Need to update style list +void +FontSelector::on_family_changed() { + + if (signal_block) return; + signal_block = true; + + Glib::RefPtr<Gtk::TreeModel> model; + Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected(model); + + if (!iter) { + // This can happen just after the family list is recreated. + signal_block = false; + return; + } + + Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance(); + fontlister->ensureRowStyles(model, iter); + + Gtk::TreeModel::Row row = *iter; + + // Get family name + Glib::ustring family; + row.get_value(0, family); + + // Get style list (TO DO: Get rid of GList) + GList *styles; + row.get_value(1, styles); + + // Find best style match for selected family with current style (e.g. of selected text). + Glib::ustring style = fontlister->get_font_style(); + Glib::ustring best = fontlister->get_best_style_match (family, style); + + // Create are own store of styles for selected font-family (the font-family selected + // in the dialog may not be the same as stored in the font-lister class until the + // "Apply" button is triggered). + Gtk::TreeModel::iterator it_best; + FontLister::FontStyleListClass FontStyleList; + Glib::RefPtr<Gtk::ListStore> local_style_list_store = Gtk::ListStore::create(FontStyleList); + + // Build list and find best match. + for ( ; styles; styles = styles->next ) { + Gtk::TreeModel::iterator treeModelIter = local_style_list_store->append(); + (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)styles->data)->CssName; + (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)styles->data)->DisplayName; + if (best == ((StyleNames*)styles->data)->CssName) { + it_best = treeModelIter; + } + } + + // Attach store to tree view and select row. + style_treeview.set_model (local_style_list_store); + if (it_best) { + style_treeview.get_selection()->select (it_best); + } + + signal_block = false; + + // Let world know + changed_emit(); +} + +void +FontSelector::on_style_changed() { + if (signal_block) return; + + // Update variations widget if new style selected from style widget. + signal_block = true; + Glib::ustring fontspec = get_fontspec( false ); + update_variations(fontspec); + signal_block = false; + + // Let world know + changed_emit(); +} + +void +FontSelector::on_size_changed() { + + if (signal_block) return; + + double size; + Glib::ustring input = size_combobox.get_active_text(); + try { + size = std::stod (input); + } + catch (std::invalid_argument) { + std::cerr << "FontSelector::on_size_changed: Invalid input: " << input << std::endl; + size = -1; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + // Arbitrary: Text and Font preview freezes with huge font sizes. + int max_size = prefs->getInt("/dialogs/textandfont/maxFontSize", 10000); + + if (size <= 0) { + return; + } + if (size > max_size) + size = max_size; + + if (fabs(font_size - size) > 0.001) { + font_size = size; + // Let world know + changed_emit(); + } +} + +void +FontSelector::on_variations_changed() { + + if (signal_block) return; + + // Let world know + changed_emit(); +} + +void +FontSelector::changed_emit() { + signal_block = true; + signal_changed.emit (get_fontspec()); + if (initial) { + initial = false; + family_treecolumn.unset_cell_data_func (family_cell); + family_treecolumn.set_cell_data_func (family_cell, &font_lister_cell_data_func_markup); + } + signal_block = false; +} + +void FontSelector::update_variations(const Glib::ustring& fontspec) { + font_variations.update(fontspec); + + // Check if there are any variations available; if not, don't expand font_variations_scroll + bool hasContent = font_variations.variations_present(); + font_variations_scroll.set_vexpand(hasContent); +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +/* + 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 : |