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