1 /*
2  * Copyright © 2004 Noah Levitt
3  * Copyright © 2008 Christian Persch
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; either version 3 of the License, or (at your
8  * option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
18  */
19 
20 #include <config.h>
21 
22 #include <string.h>
23 
24 #include <glib/gi18n-lib.h>
25 #include <gtk/gtk.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include "gucharmap-mini-fontsel.h"
29 
30 #define I_(string) g_intern_static_string (string)
31 
32 enum
33 {
34   MIN_FONT_SIZE = 5,
35   MAX_FONT_SIZE = 400,
36 };
37 
38 enum
39 {
40   PROP_0,
41   PROP_FONT_DESC
42 };
43 
44 enum
45 {
46   COL_FAMILIY
47 };
48 
49 static void gucharmap_mini_font_selection_class_init (GucharmapMiniFontSelectionClass *klass);
50 static void gucharmap_mini_font_selection_init       (GucharmapMiniFontSelection *fontsel);
51 static void gucharmap_mini_font_selection_finalize   (GObject *object);
52 
G_DEFINE_TYPE(GucharmapMiniFontSelection,gucharmap_mini_font_selection,GTK_TYPE_HBOX)53 G_DEFINE_TYPE (GucharmapMiniFontSelection, gucharmap_mini_font_selection, GTK_TYPE_HBOX)
54 
55 static void
56 fill_font_families_combo (GucharmapMiniFontSelection *fontsel)
57 {
58   PangoFontFamily **families;
59   int n_families, i;
60 
61   pango_context_list_families (
62           gtk_widget_get_pango_context (GTK_WIDGET (fontsel)),
63           &families, &n_families);
64 
65   for (i = 0;  i < n_families;  i++)
66     {
67       PangoFontFamily *family = families[i];
68       GtkTreeIter iter;
69 
70       gtk_list_store_insert_with_values (fontsel->family_store,
71                                          &iter,
72                                          -1,
73                                          COL_FAMILIY, pango_font_family_get_name (family),
74                                          -1);
75     }
76 
77   g_free (families);
78 
79   /* Now turn on sorting in the combo box */
80   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (fontsel->family_store),
81                                         COL_FAMILIY,
82                                         GTK_SORT_ASCENDING);
83 }
84 
85 static void
update_font_family_combo(GucharmapMiniFontSelection * fontsel)86 update_font_family_combo (GucharmapMiniFontSelection *fontsel)
87 {
88   GtkTreeModel *model = GTK_TREE_MODEL (fontsel->family_store);
89   GtkTreeIter iter;
90   const char *font_family;
91   gboolean found = FALSE;
92 
93   font_family = pango_font_description_get_family (fontsel->font_desc);
94   if (!font_family || !font_family[0]) {
95     gtk_combo_box_set_active (GTK_COMBO_BOX (fontsel->family), -1);
96     return;
97   }
98 
99   if (!gtk_tree_model_get_iter_first (model, &iter))
100     return;
101 
102   do {
103     char *family;
104 
105     gtk_tree_model_get (model, &iter, COL_FAMILIY, &family, -1);
106     found = family && strcmp (family, font_family) == 0;
107     g_free (family);
108   } while (!found && gtk_tree_model_iter_next (model, &iter));
109 
110   if (found) {
111     gtk_combo_box_set_active_iter (GTK_COMBO_BOX (fontsel->family), &iter);
112   } else {
113     gtk_combo_box_set_active (GTK_COMBO_BOX (fontsel->family), -1);
114   }
115 }
116 
117 static void
family_combo_changed(GtkComboBox * combo,GucharmapMiniFontSelection * fontsel)118 family_combo_changed (GtkComboBox *combo,
119                       GucharmapMiniFontSelection *fontsel)
120 {
121   GtkTreeIter iter;
122   char *family;
123 
124   if (!gtk_combo_box_get_active_iter (combo, &iter))
125     return;
126 
127   gtk_tree_model_get (GTK_TREE_MODEL (fontsel->family_store),
128                       &iter,
129                       COL_FAMILIY, &family,
130                       -1);
131   if (!family)
132     return;
133 
134   pango_font_description_set_family (fontsel->font_desc, family);
135   g_free (family);
136 
137   g_object_notify (G_OBJECT (fontsel), "font-desc");
138 }
139 
140 static gboolean
match_function(GtkEntryCompletion * completion,const gchar * key,GtkTreeIter * iter,gpointer user_data)141 match_function (GtkEntryCompletion *completion,
142                 const gchar *key,
143                 GtkTreeIter *iter,
144                 gpointer user_data)
145 {
146   GucharmapMiniFontSelection *fontsel = GUCHARMAP_MINI_FONT_SELECTION (user_data);
147   char *family, *family_fold;
148   gchar **sub_match, **p;
149   gboolean all_matches = TRUE;
150 
151   gtk_tree_model_get (GTK_TREE_MODEL (fontsel->family_store),
152                       iter,
153                       COL_FAMILIY, &family,
154                       -1);
155   family_fold = g_utf8_casefold (family, -1);
156 
157   /* Match if all space separated substrings are part of family string */
158   sub_match = g_strsplit (key, " ", -1);
159   p = sub_match;
160   while (*p) {
161       gchar *match_fold = g_utf8_casefold (*p++, -1);
162       if (!g_strstr_len (family_fold, -1, match_fold))
163           all_matches = FALSE;
164       g_free (match_fold);
165       if (!all_matches)
166           break;
167   }
168 
169   g_free (family);
170   g_free (family_fold);
171   g_strfreev (sub_match);
172 
173   return all_matches;
174 }
175 
176 static gboolean
completion_match_selected(GtkEntryCompletion * widget,GtkTreeModel * model,GtkTreeIter * iter,GucharmapMiniFontSelection * fontsel)177 completion_match_selected(GtkEntryCompletion *widget,
178                           GtkTreeModel       *model,
179                           GtkTreeIter        *iter,
180                           GucharmapMiniFontSelection *fontsel)
181 {
182   char *family;
183 
184   gtk_tree_model_get (GTK_TREE_MODEL (fontsel->family_store),
185                       iter,
186                       COL_FAMILIY, &family,
187                       -1);
188   pango_font_description_set_family (fontsel->font_desc, family);
189   g_free (family);
190 
191   g_object_notify (G_OBJECT (fontsel), "font-desc");
192 
193   return FALSE;
194 }
195 
196 
197 /* returns font size in points */
198 static int
get_font_size(GucharmapMiniFontSelection * fontsel)199 get_font_size (GucharmapMiniFontSelection *fontsel)
200 {
201   return PANGO_PIXELS (pango_font_description_get_size (fontsel->font_desc));
202 }
203 
204 /* size is in points */
205 static void
set_font_size(GucharmapMiniFontSelection * fontsel,int size)206 set_font_size (GucharmapMiniFontSelection *fontsel,
207                int size)
208 {
209   size = CLAMP (size, MIN_FONT_SIZE, MAX_FONT_SIZE);
210   pango_font_description_set_size (fontsel->font_desc, PANGO_SCALE * size);
211 
212   gtk_adjustment_set_value (GTK_ADJUSTMENT (fontsel->size_adj), size);
213 
214   g_object_notify (G_OBJECT (fontsel), "font-desc");
215 }
216 
217 static void
font_size_changed(GtkAdjustment * adjustment,GucharmapMiniFontSelection * fontsel)218 font_size_changed (GtkAdjustment *adjustment,
219                    GucharmapMiniFontSelection *fontsel)
220 {
221   int new_size;
222 
223   new_size = gtk_adjustment_get_value (adjustment);
224   if (new_size != get_font_size (fontsel))
225     set_font_size (fontsel, new_size);
226 }
227 
228 static void
gucharmap_mini_font_selection_finalize(GObject * object)229 gucharmap_mini_font_selection_finalize (GObject *object)
230 {
231   GucharmapMiniFontSelection *fontsel = GUCHARMAP_MINI_FONT_SELECTION (object);
232   pango_font_description_free (fontsel->font_desc);
233 
234   G_OBJECT_CLASS (gucharmap_mini_font_selection_parent_class)->finalize (object);
235 }
236 
237 static void
gucharmap_mini_font_selection_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)238 gucharmap_mini_font_selection_set_property (GObject *object,
239                                             guint prop_id,
240                                             const GValue *value,
241                                             GParamSpec *pspec)
242 {
243   GucharmapMiniFontSelection *mini_fontsel = GUCHARMAP_MINI_FONT_SELECTION (object);
244 
245   switch (prop_id) {
246     case PROP_FONT_DESC:
247       gucharmap_mini_font_selection_set_font_desc (mini_fontsel, g_value_get_boxed (value));
248       break;
249     default:
250       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
251       break;
252   }
253 }
254 
255 static void
gucharmap_mini_font_selection_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)256 gucharmap_mini_font_selection_get_property (GObject *object,
257                                             guint prop_id,
258                                             GValue *value,
259                                             GParamSpec *pspec)
260 {
261   GucharmapMiniFontSelection*mini_fontsel = GUCHARMAP_MINI_FONT_SELECTION (object);
262 
263   switch (prop_id) {
264     case PROP_FONT_DESC:
265       g_value_set_boxed (value, gucharmap_mini_font_selection_get_font_desc (mini_fontsel));
266       break;
267     default:
268       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
269       break;
270   }
271 }
272 
273 static void
gucharmap_mini_font_selection_class_init(GucharmapMiniFontSelectionClass * klass)274 gucharmap_mini_font_selection_class_init (GucharmapMiniFontSelectionClass *klass)
275 {
276   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
277 
278   gobject_class->finalize = gucharmap_mini_font_selection_finalize;
279   gobject_class->get_property = gucharmap_mini_font_selection_get_property;
280   gobject_class->set_property = gucharmap_mini_font_selection_set_property;
281 
282   g_object_class_install_property
283     (gobject_class,
284      PROP_FONT_DESC,
285      g_param_spec_boxed ("font-desc", NULL, NULL,
286                          PANGO_TYPE_FONT_DESCRIPTION,
287                          G_PARAM_READWRITE |
288                          G_PARAM_STATIC_NAME |
289                          G_PARAM_STATIC_NICK |
290                          G_PARAM_STATIC_BLURB));
291 }
292 
293 static void
bold_toggled(GtkToggleButton * toggle,GucharmapMiniFontSelection * fontsel)294 bold_toggled (GtkToggleButton *toggle,
295               GucharmapMiniFontSelection *fontsel)
296 {
297   if (gtk_toggle_button_get_active (toggle))
298     pango_font_description_set_weight (fontsel->font_desc, PANGO_WEIGHT_BOLD);
299   else
300     pango_font_description_set_weight (fontsel->font_desc, PANGO_WEIGHT_NORMAL);
301 
302   g_object_notify (G_OBJECT (fontsel), "font-desc");
303 }
304 
305 static void
italic_toggled(GtkToggleButton * toggle,GucharmapMiniFontSelection * fontsel)306 italic_toggled (GtkToggleButton *toggle,
307                 GucharmapMiniFontSelection *fontsel)
308 {
309   if (gtk_toggle_button_get_active (toggle))
310     pango_font_description_set_style (fontsel->font_desc, PANGO_STYLE_ITALIC);
311   else
312     pango_font_description_set_style (fontsel->font_desc, PANGO_STYLE_NORMAL);
313 
314   g_object_notify (G_OBJECT (fontsel), "font-desc");
315 }
316 
317 static void
gucharmap_mini_font_selection_init(GucharmapMiniFontSelection * fontsel)318 gucharmap_mini_font_selection_init (GucharmapMiniFontSelection *fontsel)
319 {
320   GtkStyle *style;
321   AtkObject *accessib;
322 
323   gtk_widget_ensure_style (GTK_WIDGET (fontsel));
324   style = gtk_widget_get_style (GTK_WIDGET (fontsel));
325   fontsel->font_desc = pango_font_description_copy (style->font_desc);
326   fontsel->default_size = -1;
327 
328   fontsel->size_adj = gtk_adjustment_new (MIN_FONT_SIZE,
329                                           MIN_FONT_SIZE, MAX_FONT_SIZE, 1, 8, 0);
330 
331   accessib = gtk_widget_get_accessible (GTK_WIDGET (fontsel));
332   atk_object_set_name (accessib, _("Font"));
333 
334   gtk_box_set_spacing (GTK_BOX (fontsel), 6);
335 
336   fontsel->family_store = gtk_list_store_new (1, G_TYPE_STRING);
337 
338   fontsel->family = gtk_combo_box_new_with_model_and_entry (GTK_TREE_MODEL (fontsel->family_store));
339   gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX(fontsel->family), COL_FAMILIY);
340   fontsel->completion = gtk_entry_completion_new();
341   gtk_entry_completion_set_text_column (fontsel->completion, COL_FAMILIY);
342   gtk_entry_completion_set_model (fontsel->completion, GTK_TREE_MODEL (fontsel->family_store));
343   gtk_entry_completion_set_match_func(fontsel->completion, match_function, fontsel, NULL);
344   g_signal_connect (G_OBJECT (fontsel->completion), "match-selected",
345                     G_CALLBACK (completion_match_selected), fontsel);
346 
347   gtk_entry_set_completion (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (fontsel->family))),
348                             fontsel->completion);
349   gtk_widget_show (fontsel->family);
350   accessib = gtk_widget_get_accessible (fontsel->family);
351   atk_object_set_name (accessib, _("Font Family"));
352 
353   fontsel->bold = gtk_toggle_button_new_with_mnemonic (GTK_STOCK_BOLD);
354   gtk_button_set_use_stock (GTK_BUTTON (fontsel->bold), TRUE);
355   gtk_widget_show (fontsel->bold);
356   g_signal_connect (fontsel->bold, "toggled",
357                     G_CALLBACK (bold_toggled), fontsel);
358 
359   fontsel->italic = gtk_toggle_button_new_with_mnemonic (GTK_STOCK_ITALIC);
360   gtk_button_set_use_stock (GTK_BUTTON (fontsel->italic), TRUE);
361   gtk_widget_show (fontsel->italic);
362   g_signal_connect (fontsel->italic, "toggled",
363                     G_CALLBACK (italic_toggled), fontsel);
364 
365   fontsel->size = gtk_spin_button_new (GTK_ADJUSTMENT (fontsel->size_adj),
366                                        0, 0);
367   gtk_widget_show (fontsel->size);
368   accessib = gtk_widget_get_accessible (fontsel->size);
369   atk_object_set_name (accessib, _("Font Size"));
370   g_signal_connect (fontsel->size_adj, "value-changed",
371                     G_CALLBACK (font_size_changed), fontsel);
372 
373   fill_font_families_combo (fontsel);
374 
375   gtk_combo_box_set_active (GTK_COMBO_BOX (fontsel->family), -1);
376   g_signal_connect (fontsel->family, "changed",
377                     G_CALLBACK (family_combo_changed), fontsel);
378 
379   gtk_box_pack_start (GTK_BOX (fontsel), fontsel->family, FALSE, FALSE, 0);
380   gtk_box_pack_start (GTK_BOX (fontsel), fontsel->bold, FALSE, FALSE, 0);
381   gtk_box_pack_start (GTK_BOX (fontsel), fontsel->italic, FALSE, FALSE, 0);
382   gtk_box_pack_start (GTK_BOX (fontsel), fontsel->size, FALSE, FALSE, 0);
383 
384   gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (fontsel->family), FALSE);
385   gtk_button_set_focus_on_click (GTK_BUTTON (fontsel->bold), FALSE);
386   gtk_button_set_focus_on_click (GTK_BUTTON (fontsel->italic), FALSE);
387 
388   gtk_container_set_border_width (GTK_CONTAINER (fontsel), 6);
389 
390   gtk_widget_show_all (GTK_WIDGET (fontsel));
391 }
392 
393 GtkWidget *
gucharmap_mini_font_selection_new(void)394 gucharmap_mini_font_selection_new (void)
395 {
396   return GTK_WIDGET (g_object_new (gucharmap_mini_font_selection_get_type (),
397                                    NULL));
398 }
399 
400 void
gucharmap_mini_font_selection_set_font_desc(GucharmapMiniFontSelection * fontsel,PangoFontDescription * font_desc)401 gucharmap_mini_font_selection_set_font_desc (GucharmapMiniFontSelection *fontsel,
402                                              PangoFontDescription *font_desc)
403 {
404   GObject *object = G_OBJECT (fontsel);
405   PangoFontDescription *new_font_desc;
406   const char *new_font_family;
407 
408   g_return_if_fail (GUCHARMAP_IS_MINI_FONT_SELECTION (fontsel));
409   g_return_if_fail (font_desc != NULL);
410 
411   g_object_freeze_notify (object);
412 
413   new_font_desc = pango_font_description_copy (font_desc);
414   new_font_family = pango_font_description_get_family (new_font_desc);
415   if (!new_font_family) {
416     pango_font_description_set_family (new_font_desc, "Sans");
417     new_font_family = pango_font_description_get_family (new_font_desc);
418   }
419 
420   if ((!fontsel->font_desc ||
421        strcmp (pango_font_description_get_family (fontsel->font_desc), new_font_family) != 0) &&
422       pango_font_description_get_size (new_font_desc) > 0)
423     fontsel->default_size = pango_font_description_get_size (new_font_desc) / PANGO_SCALE;
424 
425   if (fontsel->font_desc)
426     pango_font_description_free (fontsel->font_desc);
427 
428   fontsel->font_desc = new_font_desc;
429 
430   update_font_family_combo (fontsel);
431 
432   /* treat oblique and italic both as italic */
433   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fontsel->italic), pango_font_description_get_style (fontsel->font_desc) == PANGO_STYLE_ITALIC || pango_font_description_get_style (fontsel->font_desc) == PANGO_STYLE_OBLIQUE);
434 
435   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fontsel->bold), pango_font_description_get_weight (fontsel->font_desc) > PANGO_WEIGHT_NORMAL);
436 
437   gtk_adjustment_set_value (
438           GTK_ADJUSTMENT (fontsel->size_adj),
439           pango_font_description_get_size (fontsel->font_desc) / PANGO_SCALE);
440 
441   g_object_notify (G_OBJECT (fontsel), "font-desc");
442 
443   g_object_thaw_notify (object);
444 }
445 
446 PangoFontDescription *
gucharmap_mini_font_selection_get_font_desc(GucharmapMiniFontSelection * fontsel)447 gucharmap_mini_font_selection_get_font_desc (GucharmapMiniFontSelection *fontsel)
448 {
449   g_return_val_if_fail (GUCHARMAP_IS_MINI_FONT_SELECTION (fontsel), NULL);
450 
451   return fontsel->font_desc;
452 }
453 
454 void
gucharmap_mini_font_selection_change_font_size(GucharmapMiniFontSelection * fontsel,float factor)455 gucharmap_mini_font_selection_change_font_size (GucharmapMiniFontSelection *fontsel,
456                                                 float factor)
457 {
458   int size, new_size;
459 
460   g_return_if_fail (factor > 0.0f);
461 
462   size = get_font_size (fontsel);
463   new_size = (float) size * factor;
464 
465   if (factor > 1.0f)
466     new_size = MAX (new_size, size + 1);
467   else if (factor < 1.0f)
468     new_size = MIN (new_size, size - 1);
469 
470   set_font_size (fontsel, new_size);
471 }
472 
473 void
gucharmap_mini_font_selection_reset_font_size(GucharmapMiniFontSelection * fontsel)474 gucharmap_mini_font_selection_reset_font_size (GucharmapMiniFontSelection *fontsel)
475 {
476   if (fontsel->default_size > 0) {
477     set_font_size (fontsel, fontsel->default_size);
478   } else {
479     GtkStyle *style;
480 
481     style = gtk_widget_get_style (GTK_WIDGET (fontsel));
482     set_font_size (fontsel, pango_font_description_get_size (style->font_desc) * 2.0f / PANGO_SCALE);
483   }
484 }
485