1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Author:
4  *   Tavmjong Bah <tavmjong@free.fr>
5  *
6  * Copyright (C) 2018 Tavmong Bah
7  *
8  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9  */
10 
11 #include <glibmm/i18n.h>
12 #include <glibmm/regex.h>
13 #include <gdkmm/display.h>
14 
15 #include "font-selector-toolbar.h"
16 
17 #include "libnrtype/font-lister.h"
18 #include "libnrtype/font-instance.h"
19 
20 #include "ui/icon-names.h"
21 
22 // For updating from selection
23 #include "inkscape.h"
24 #include "desktop.h"
25 #include "object/sp-text.h"
26 
27 // TEMP TEMP TEMP
28 #include "ui/toolbar/text-toolbar.h"
29 
30 /* To do:
31  *   Fix altx.  The setToolboxFocusTo method now just searches for a named widget.
32  *   We just need to do the following:
33  *   * Set the name of the family_combo child widget
34  *   * Change the setToolboxFocusTo() argument in tools/text-tool to point to that widget name
35  */
36 
family_cell_data_func(const Gtk::TreeModel::const_iterator iter,Gtk::CellRendererText * cell)37 void family_cell_data_func(const Gtk::TreeModel::const_iterator iter, Gtk::CellRendererText* cell ) {
38 
39     Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
40     Glib::ustring markup = font_lister->get_font_family_markup(iter);
41     // std::cout << "Markup: " << markup << std::endl;
42 
43     cell->set_property ("markup", markup);
44 }
45 
46 namespace Inkscape {
47 namespace UI {
48 namespace Widget {
49 
FontSelectorToolbar()50 FontSelectorToolbar::FontSelectorToolbar ()
51     : Gtk::Grid ()
52     , family_combo (true)  // true => with text entry.
53     , style_combo (true)
54     , signal_block (false)
55 {
56 
57     Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
58 
59     // Font family
60     family_combo.set_model (font_lister->get_font_list());
61     family_combo.set_entry_text_column (0);
62     family_combo.set_name ("FontSelectorToolBar: Family");
63     family_combo.set_row_separator_func (&font_lister_separator_func);
64 
65     family_combo.clear(); // Clears all CellRenderer mappings.
66     family_combo.set_cell_data_func (family_cell,
67                                      sigc::bind(sigc::ptr_fun(family_cell_data_func), &family_cell));
68     family_combo.pack_start (family_cell);
69 
70 
71     Gtk::Entry* entry = family_combo.get_entry();
72     entry->signal_icon_press().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_icon_pressed));
73     entry->signal_key_press_event().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_key_press_event), false); // false => connect first
74 
75     Glib::RefPtr<Gtk::EntryCompletion> completion = Gtk::EntryCompletion::create();
76     completion->set_model (font_lister->get_font_list());
77     completion->set_text_column (0);
78     completion->set_popup_completion ();
79     completion->set_inline_completion (false);
80     completion->set_inline_selection ();
81     // completion->signal_match_selected().connect(sigc::mem_fun(*this, &FontSelectorToolbar::on_match_selected), false); // false => connect before default handler.
82     entry->set_completion (completion);
83 
84     // Style
85     style_combo.set_model (font_lister->get_style_list());
86     style_combo.set_name ("FontSelectorToolbar: Style");
87 
88     // Grid
89     set_name ("FontSelectorToolbar: Grid");
90     attach (family_combo,  0, 0, 1, 1);
91     attach (style_combo,   1, 0, 1, 1);
92 
93     // Add signals
94     family_combo.signal_changed().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_family_changed));
95     style_combo.signal_changed().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_style_changed));
96 
97     show_all_children();
98 
99     // Initialize font family lists. (May already be done.) Should be done on document change.
100     font_lister->update_font_list(SP_ACTIVE_DESKTOP->getDocument());
101 
102     // When FontLister is changed, update family and style shown in GUI.
103     font_lister->connectUpdate(sigc::mem_fun(*this, &FontSelectorToolbar::update_font));
104 }
105 
106 
107 // Update GUI based on font-selector values.
108 void
update_font()109 FontSelectorToolbar::update_font ()
110 {
111     if (signal_block) return;
112 
113     signal_block = true;
114 
115     Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
116     Gtk::TreeModel::Row row;
117 
118     // Set font family.
119     try {
120         row = font_lister->get_row_for_font ();
121         family_combo.set_active (row);
122     } catch (...) {
123         std::cerr << "FontSelectorToolbar::update_font: Couldn't find row for family: "
124                   << font_lister->get_font_family() << std::endl;
125     }
126 
127     // Set style.
128     try {
129         row = font_lister->get_row_for_style ();
130         style_combo.set_active (row);
131     } catch (...) {
132         std::cerr << "FontSelectorToolbar::update_font: Couldn't find row for style: "
133                   << font_lister->get_font_style() << std::endl;
134     }
135 
136     // Check for missing fonts.
137     Glib::ustring missing_fonts = get_missing_fonts();
138 
139     // Add an icon to end of entry.
140     Gtk::Entry* entry = family_combo.get_entry();
141     if (missing_fonts.empty()) {
142         // If no missing fonts, add icon for selecting all objects with this font-family.
143         entry->set_icon_from_icon_name (INKSCAPE_ICON("edit-select-all"), Gtk::ENTRY_ICON_SECONDARY);
144         entry->set_icon_tooltip_text (_("Select all text with this text family"), Gtk::ENTRY_ICON_SECONDARY);
145     } else {
146         // If missing fonts, add warning icon.
147         Glib::ustring warning = _("Font not found on system: ") + missing_fonts;
148         entry->set_icon_from_icon_name (INKSCAPE_ICON("dialog-warning"), Gtk::ENTRY_ICON_SECONDARY);
149         entry->set_icon_tooltip_text (warning, Gtk::ENTRY_ICON_SECONDARY);
150     }
151 
152     signal_block = false;
153 }
154 
155 // Get comma separated list of fonts in font-family that are not on system.
156 // To do, move to font-lister.
157 Glib::ustring
get_missing_fonts()158 FontSelectorToolbar::get_missing_fonts ()
159 {
160     // Get font list in text entry which may be a font stack (with fallbacks).
161     Glib::ustring font_list = family_combo.get_entry_text();
162     Glib::ustring missing_font_list;
163     Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
164 
165     std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", font_list);
166 
167     for (auto token: tokens) {
168         bool found = false;
169         Gtk::TreeModel::Children children = font_lister->get_font_list()->children();
170         for (auto iter2: children) {
171             Gtk::TreeModel::Row row2 = *iter2;
172             Glib::ustring family2 = row2[font_lister->FontList.family];
173             bool onSystem2        = row2[font_lister->FontList.onSystem];
174             // CSS dictates that font family names are case insensitive.
175             // This should really implement full Unicode case unfolding.
176             if (onSystem2 && token.casefold().compare(family2.casefold()) == 0) {
177                 found = true;
178                 break;
179             }
180         }
181 
182         if (!found) {
183             missing_font_list += token;
184             missing_font_list += ", ";
185         }
186     }
187 
188     // Remove extra comma and space from end.
189     if (missing_font_list.size() >= 2) {
190         missing_font_list.resize(missing_font_list.size() - 2);
191     }
192 
193     return missing_font_list;
194 }
195 
196 
197 // Callbacks
198 
199 // Need to update style list
200 void
on_family_changed()201 FontSelectorToolbar::on_family_changed() {
202 
203     if (signal_block) return;
204     signal_block = true;
205 
206     Glib::ustring family = family_combo.get_entry_text();
207 
208     Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance();
209     fontlister->set_font_family (family);
210 
211     signal_block = false;
212 
213     // Let world know
214     changed_emit();
215 }
216 
217 void
on_style_changed()218 FontSelectorToolbar::on_style_changed() {
219 
220     if (signal_block) return;
221     signal_block = true;
222 
223     Glib::ustring style = style_combo.get_entry_text();
224 
225     Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance();
226     fontlister->set_font_style (style);
227 
228     signal_block = false;
229 
230     // Let world know
231     changed_emit();
232 }
233 
234 void
on_icon_pressed(Gtk::EntryIconPosition icon_position,const GdkEventButton * event)235 FontSelectorToolbar::on_icon_pressed (Gtk::EntryIconPosition icon_position, const GdkEventButton* event) {
236     std::cout << "FontSelectorToolbar::on_entry_icon_pressed" << std::endl;
237     std::cout << "    .... Should select all items with same font-family. FIXME" << std::endl;
238     // Call equivalent of sp_text_toolbox_select_cb() in text-toolbar.cpp
239     // Should be action!  (Maybe: select_all_fontfamily( Glib::ustring font_family );).
240     // Check how Find dialog works.
241 }
242 
243 // bool
244 // FontSelectorToolbar::on_match_selected (const Gtk::TreeModel::iterator& iter)
245 // {
246 //     std::cout << "on_match_selected" << std::endl;
247 //     std::cout << "   FIXME" << std::endl;
248 //     Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
249 //     Glib::ustring family = (*iter)[font_lister->FontList.family];
250 //     std::cout << "  family: " << family << std::endl;
251 //     return false; // Leave it to default handler to set entry text.
252 // }
253 
254 // Return focus to canvas.
255 bool
on_key_press_event(GdkEventKey * key_event)256 FontSelectorToolbar::on_key_press_event (GdkEventKey* key_event)
257 {
258     bool consumed = false;
259 
260     unsigned int key = 0;
261     gdk_keymap_translate_keyboard_state( Gdk::Display::get_default()->get_keymap(),
262                                          key_event->hardware_keycode,
263                                          (GdkModifierType)key_event->state,
264                                          0, &key, nullptr, nullptr, nullptr );
265 
266     switch ( key ) {
267 
268         case GDK_KEY_Escape:
269         case GDK_KEY_Return:
270         case GDK_KEY_KP_Enter:
271         {
272             // Defocus
273             std::cerr << "FontSelectorToolbar::on_key_press_event: Defocus: FIXME" << std::endl;
274             consumed = true;
275         }
276         break;
277     }
278 
279     return consumed; // Leave it to default handler if false.
280 }
281 
282 void
changed_emit()283 FontSelectorToolbar::changed_emit() {
284     signal_block = true;
285     changed_signal.emit ();
286     signal_block = false;
287 }
288 
289 } // namespace Widget
290 } // namespace UI
291 } // namespace Inkscape
292 
293 /*
294   Local Variables:
295   mode:c++
296   c-file-style:"stroustrup"
297   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
298   indent-tabs-mode:nil
299   fill-column:99
300   End:
301 */
302 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
303