1 /* font-manager-font-preview.c
2  *
3  * Copyright (C) 2009 - 2021 Jerry Casiano
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.
17  *
18  * If not, see <http://www.gnu.org/licenses/gpl-3.0.txt>.
19 */
20 
21 #include "font-manager-font-preview.h"
22 
23 /**
24  * SECTION: font-manager-font-preview
25  * @short_description: Full featured font preview widget
26  * @title: Font Preview
27  * @include: font-manager-font-preview.h
28  *
29  * This widget allows previewing of font files in various ways.
30  */
31 
32 GType
font_manager_font_preview_mode_get_type(void)33 font_manager_font_preview_mode_get_type (void)
34 {
35   static volatile gsize g_define_type_id__volatile = 0;
36 
37   if (g_once_init_enter (&g_define_type_id__volatile))
38     {
39       static const GEnumValue values[] = {
40         { FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW, "FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW", "preview" },
41         { FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL, "FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL", "waterfall" },
42         { FONT_MANAGER_FONT_PREVIEW_MODE_LOREM_IPSUM, "FONT_MANAGER_FONT_PREVIEW_MODE_LOREM_IPSUM", "lorem-ipsum" },
43         { 0, NULL, NULL }
44       };
45       GType g_define_type_id =
46         g_enum_register_static (g_intern_static_string ("FontManagerFontPreviewMode"), values);
47       g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
48     }
49 
50   return g_define_type_id__volatile;
51 }
52 
53 /**
54  * font_manager_font_preview_mode_to_string:
55  * @mode:   #FontManagerFontPreviewMode
56  *
57  * Returns: (transfer none) (nullable): @mode as a string
58  */
59 const gchar *
font_manager_font_preview_mode_to_string(FontManagerFontPreviewMode mode)60 font_manager_font_preview_mode_to_string (FontManagerFontPreviewMode mode)
61 {
62     switch (mode) {
63         case FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW:
64             return "Preview";
65         case FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL:
66             return "Waterfall";
67         case FONT_MANAGER_FONT_PREVIEW_MODE_LOREM_IPSUM:
68             return "Lorem Ipsum";
69         default:
70             return NULL;
71     }
72 }
73 
74 /**
75  * font_manager_font_preview_mode_to_translatable_string:
76  * @mode:   #FontManagerFontPreviewMode
77  *
78  * Returns: (transfer none) (nullable): @mode as a localized string, if available.
79  */
80 const gchar *
font_manager_font_preview_mode_to_translatable_string(FontManagerFontPreviewMode mode)81 font_manager_font_preview_mode_to_translatable_string (FontManagerFontPreviewMode mode)
82 {
83     switch (mode) {
84         case FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW:
85             return _("Preview");
86         case FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL:
87             return _("Waterfall");
88         case FONT_MANAGER_FONT_PREVIEW_MODE_LOREM_IPSUM:
89             return "Lorem Ipsum";
90         default:
91             return NULL;
92     }
93 }
94 
95 
96 #define MIN_FONT_SIZE FONT_MANAGER_MIN_FONT_SIZE
97 #define MAX_FONT_SIZE FONT_MANAGER_MAX_FONT_SIZE
98 #define DEFAULT_PREVIEW_SIZE FONT_MANAGER_DEFAULT_PREVIEW_SIZE
99 #define DEFAULT_WATERFALL_MAX_SIZE 48.0
100 
101 struct _FontManagerFontPreview
102 {
103     GtkBox   parent_instance;
104 
105     gchar       *pangram;
106     gchar       *default_pangram;
107     gchar       *preview;
108     gchar       *default_preview;
109     gchar       *restore_preview;
110     GtkWidget   *controls;
111     GtkWidget   *fontscale;
112     GtkWidget   *textview;
113     GHashTable  *samples;
114 
115     gint                max_waterfall_size;
116     gdouble             preview_size;
117     gboolean            allow_edit;
118     GtkJustification    justification;
119     FontManagerFontPreviewMode  mode;
120     PangoFontDescription *font_desc;
121 };
122 
123 G_DEFINE_TYPE(FontManagerFontPreview, font_manager_font_preview, GTK_TYPE_BOX)
124 
125 enum
126 {
127     PROP_RESERVED,
128     PROP_PREVIEW_MODE,
129     PROP_PREVIEW_SIZE,
130     PROP_PREVIEW_TEXT,
131     PROP_FONT_DESC,
132     PROP_JUSTIFICATION,
133     PROP_SAMPLES,
134     PROP_WATERFALL_MAX,
135     N_PROPERTIES
136 };
137 
138 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
139 
140 static void
font_manager_font_preview_dispose(GObject * gobject)141 font_manager_font_preview_dispose (GObject *gobject)
142 {
143     g_return_if_fail(gobject != NULL);
144     FontManagerFontPreview *self = FONT_MANAGER_FONT_PREVIEW(gobject);
145     g_clear_pointer(&self->pangram, g_free);
146     g_clear_pointer(&self->default_pangram, g_free);
147     g_clear_pointer(&self->preview, g_free);
148     g_clear_pointer(&self->default_preview, g_free);
149     g_clear_pointer(&self->restore_preview, g_free);
150     g_clear_pointer(&self->font_desc, pango_font_description_free);
151     g_clear_pointer(&self->samples, g_hash_table_unref);
152     G_OBJECT_CLASS(font_manager_font_preview_parent_class)->dispose(gobject);
153     return;
154 }
155 
156 static void
font_manager_font_preview_get_property(GObject * gobject,guint property_id,GValue * value,GParamSpec * pspec)157 font_manager_font_preview_get_property (GObject *gobject,
158                                         guint property_id,
159                                         GValue *value,
160                                         GParamSpec *pspec)
161 {
162     g_return_if_fail(gobject != NULL);
163     FontManagerFontPreview *self = FONT_MANAGER_FONT_PREVIEW(gobject);
164     g_autofree gchar *font = NULL;
165     switch (property_id) {
166         case PROP_PREVIEW_SIZE:
167             g_value_set_double(value, font_manager_font_preview_get_preview_size(self));
168             break;
169         case PROP_PREVIEW_MODE:
170             g_value_set_enum(value, font_manager_font_preview_get_preview_mode(self));
171             break;
172         case PROP_PREVIEW_TEXT:
173             g_value_set_string(value, self->preview);
174             break;
175         case PROP_FONT_DESC:
176             font = font_manager_font_preview_get_font_description(self);
177             g_value_set_string(value, font);
178             break;
179         case PROP_JUSTIFICATION:
180             g_value_set_enum(value, (gint) font_manager_font_preview_get_justification(self));
181             break;
182         case PROP_SAMPLES:
183             g_value_set_boxed(value, self->samples);
184             break;
185         case PROP_WATERFALL_MAX:
186             g_value_set_double(value, self->max_waterfall_size);
187             break;
188         default:
189             G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, property_id, pspec);
190     }
191     return;
192 }
193 
194 static void
font_manager_font_preview_set_property(GObject * gobject,guint property_id,const GValue * value,GParamSpec * pspec)195 font_manager_font_preview_set_property (GObject *gobject,
196                                         guint property_id,
197                                         const GValue *value,
198                                         GParamSpec *pspec)
199 {
200     g_return_if_fail(gobject != NULL);
201     FontManagerFontPreview *self = FONT_MANAGER_FONT_PREVIEW(gobject);
202     switch (property_id) {
203         case PROP_PREVIEW_SIZE:
204             font_manager_font_preview_set_preview_size(self, g_value_get_double(value));
205             break;
206         case PROP_PREVIEW_MODE:
207             font_manager_font_preview_set_preview_mode(self, g_value_get_enum(value));
208             break;
209         case PROP_PREVIEW_TEXT:
210             font_manager_font_preview_set_preview_text(self, g_value_get_string(value));
211             break;
212         case PROP_FONT_DESC:
213             font_manager_font_preview_set_font_description(self, g_value_get_string(value));
214             break;
215         case PROP_JUSTIFICATION:
216             font_manager_font_preview_set_justification(self, (GtkJustification) g_value_get_enum(value));
217             break;
218         case PROP_SAMPLES:
219             font_manager_font_preview_set_sample_strings(self, g_value_get_boxed(value));
220             break;
221         case PROP_WATERFALL_MAX:
222             font_manager_font_preview_set_max_waterfall_size(self, g_value_get_double(value));
223             break;
224         default:
225             G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, property_id, pspec);
226     }
227     return;
228 }
229 
230 static void
font_manager_font_preview_class_init(FontManagerFontPreviewClass * klass)231 font_manager_font_preview_class_init (FontManagerFontPreviewClass *klass)
232 {
233     GObjectClass *object_class = G_OBJECT_CLASS(klass);
234 
235     object_class->dispose = font_manager_font_preview_dispose;
236     object_class->get_property = font_manager_font_preview_get_property;
237     object_class->set_property = font_manager_font_preview_set_property;
238 
239     /**
240      * FontManagerFontPreview:preview-mode:
241      *
242      * The current font preview mode.
243      */
244     obj_properties[PROP_PREVIEW_MODE] = g_param_spec_enum("preview-mode",
245                                                           NULL,
246                                                           "Font preview mode.",
247                                                           FONT_MANAGER_TYPE_FONT_PREVIEW_MODE,
248                                                           (gint) FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL,
249                                                           G_PARAM_STATIC_STRINGS |
250                                                           G_PARAM_READWRITE |
251                                                           G_PARAM_EXPLICIT_NOTIFY);
252 
253     /**
254      * FontManagerFontPreview:preview-size:
255      *
256      * The current font preview size.
257      */
258     obj_properties[PROP_PREVIEW_SIZE] = g_param_spec_double("preview-size",
259                                                             NULL,
260                                                             "Font preview size in points.",
261                                                             MIN_FONT_SIZE,
262                                                             MAX_FONT_SIZE,
263                                                             DEFAULT_PREVIEW_SIZE,
264                                                             G_PARAM_STATIC_STRINGS |
265                                                             G_PARAM_READWRITE |
266                                                             G_PARAM_EXPLICIT_NOTIFY);
267 
268     /**
269      * FontManagerFontPreview:preview-text:
270      *
271      * Current preview text.
272      */
273     obj_properties[PROP_PREVIEW_TEXT] = g_param_spec_string("preview-text",
274                                                              NULL,
275                                                              "Current preview text.",
276                                                              NULL,
277                                                              G_PARAM_STATIC_STRINGS |
278                                                              G_PARAM_READWRITE |
279                                                              G_PARAM_EXPLICIT_NOTIFY);
280 
281     /**
282      * FontManagerFontPreview:font-description:
283      *
284      * Current font dsescription as a string.
285      */
286     obj_properties[PROP_FONT_DESC] = g_param_spec_string("font-description",
287                                                          NULL,
288                                                          "Current font description as a string.",
289                                                          FONT_MANAGER_DEFAULT_FONT,
290                                                          G_PARAM_STATIC_STRINGS |
291                                                          G_PARAM_READWRITE |
292                                                          G_PARAM_EXPLICIT_NOTIFY);
293 
294     /**
295      * FontManagerFontPreview:justification:
296      *
297      * Preview text justification.
298      */
299      obj_properties[PROP_JUSTIFICATION] = g_param_spec_enum("justification",
300                                                             NULL,
301                                                             "Preview text justification.",
302                                                             GTK_TYPE_JUSTIFICATION,
303                                                             GTK_JUSTIFY_CENTER,
304                                                             G_PARAM_STATIC_STRINGS |
305                                                             G_PARAM_READWRITE |
306                                                             G_PARAM_EXPLICIT_NOTIFY);
307 
308     /**
309      * FontManagerFontPreview:sample-strings:
310      *
311      * Dictionary of sample strings
312      */
313     obj_properties[PROP_SAMPLES] = g_param_spec_boxed("samples",
314                                                       NULL,
315                                                       "Dictionary of sample strings",
316                                                       G_TYPE_HASH_TABLE,
317                                                       G_PARAM_STATIC_STRINGS |
318                                                       G_PARAM_READWRITE |
319                                                       G_PARAM_EXPLICIT_NOTIFY);
320 
321     /**
322      * FontManagerFontPreview:max-waterfall-size:
323      *
324      * The current maximum waterfall preview size.
325      */
326     obj_properties[PROP_WATERFALL_MAX] = g_param_spec_double("max-waterfall-size",
327                                                              NULL,
328                                                              "Maximum waterfall preview size in points.",
329                                                              MIN_FONT_SIZE,
330                                                              MAX_FONT_SIZE,
331                                                              DEFAULT_WATERFALL_MAX_SIZE,
332                                                              G_PARAM_STATIC_STRINGS |
333                                                              G_PARAM_READWRITE |
334                                                              G_PARAM_EXPLICIT_NOTIFY);
335 
336     g_object_class_install_properties(object_class, N_PROPERTIES, obj_properties);
337     return;
338 }
339 
340 static void
update_revealer_state(FontManagerFontPreview * self,FontManagerFontPreviewMode mode)341 update_revealer_state (FontManagerFontPreview *self, FontManagerFontPreviewMode mode)
342 {
343     g_return_if_fail(self != NULL);
344     gboolean controls_visible = gtk_revealer_get_child_revealed(GTK_REVEALER(self->controls));
345     GtkRevealerTransitionType trans_type = controls_visible ?
346                                            GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP :
347                                            GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN;
348     gtk_revealer_set_transition_type(GTK_REVEALER(self->controls), trans_type);
349     gboolean fontscale_visible = gtk_revealer_get_child_revealed(GTK_REVEALER(self->controls));
350     trans_type = fontscale_visible ? GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN :
351                                      GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP;
352     gtk_revealer_set_transition_type(GTK_REVEALER(self->fontscale), trans_type);
353     gtk_revealer_set_reveal_child(GTK_REVEALER(self->fontscale),
354                                   (mode == FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW ||
355                                    mode == FONT_MANAGER_FONT_PREVIEW_MODE_LOREM_IPSUM));
356     gtk_revealer_set_reveal_child(GTK_REVEALER(self->controls),
357                                   (mode == FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW));
358     return;
359 }
360 
361 static gint current_line = FONT_MANAGER_MIN_FONT_SIZE;
362 
363 static gboolean
generate_waterfall_line(FontManagerFontPreview * self)364 generate_waterfall_line (FontManagerFontPreview *self)
365 {
366     GtkTextIter iter;
367     GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self->textview));
368     GtkTextTagTable *tag_table = gtk_text_buffer_get_tag_table(buffer);
369     gint i = current_line;
370     g_autofree gchar *size_point = NULL;
371     g_autofree gchar *line = g_strdup_printf("%i", i);
372     size_point = g_strdup_printf(i < 10 ? " %spt.  " : "%spt.  ", line);
373     gtk_text_buffer_get_iter_at_line(buffer, &iter, i);
374     gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, size_point, -1, "SizePoint", NULL);
375     if (!gtk_text_tag_table_lookup(tag_table, line))
376         gtk_text_buffer_create_tag(buffer, line, "size-points", (gdouble) i, NULL);
377     gtk_text_buffer_get_end_iter(buffer, &iter);
378     g_autofree gchar *pangram = g_strdup_printf("%s\n", self->pangram);
379     gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, pangram, -1, line, "FontDescription", NULL);
380     current_line++;
381     return current_line > self->max_waterfall_size ? G_SOURCE_REMOVE : G_SOURCE_CONTINUE;
382 }
383 
384 static void
generate_waterfall_preview(FontManagerFontPreview * self)385 generate_waterfall_preview (FontManagerFontPreview *self)
386 {
387     g_return_if_fail(self != NULL);
388     GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self->textview));
389     gtk_text_buffer_set_text(buffer, "", -1);
390     g_idle_remove_by_data(self);
391     current_line = FONT_MANAGER_MIN_FONT_SIZE;
392     g_idle_add((GSourceFunc) generate_waterfall_line, self);
393     return;
394 }
395 
396 static void
apply_font_description(FontManagerFontPreview * self)397 apply_font_description (FontManagerFontPreview *self)
398 {
399     g_return_if_fail(self != NULL);
400     if (self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL)
401         return;
402     GtkTextIter start, end;
403     GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self->textview));
404     gtk_text_buffer_get_bounds(buffer, &start, &end);
405     gtk_text_buffer_apply_tag_by_name(buffer, "FontDescription", &start, &end);
406     return;
407 }
408 
409 /* Prevent tofu if possible */
410 static void
update_sample_string(FontManagerFontPreview * self)411 update_sample_string (FontManagerFontPreview *self)
412 {
413     g_return_if_fail(self != NULL);
414     g_autofree gchar *description = pango_font_description_to_string(self->font_desc);
415     gboolean pangram_changed = FALSE;
416     if (self->samples && g_hash_table_contains(self->samples, description)) {
417         const gchar *sample = g_hash_table_lookup(self->samples, description);
418         if (sample) {
419             g_free(self->pangram);
420             self->pangram = g_strdup(sample);
421             pangram_changed = TRUE;
422             if (self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW
423                 && g_strcmp0(self->preview, self->default_preview) == 0) {
424                 self->restore_preview = g_strdup(self->preview);
425                 font_manager_font_preview_set_preview_text(self, self->pangram);
426             }
427         }
428     } else {
429         if (g_strcmp0(self->pangram, self->default_pangram) != 0) {
430             g_free(self->pangram);
431             self->pangram = g_strdup(self->default_pangram);
432             pangram_changed = TRUE;
433         }
434         if (self->restore_preview && self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW) {
435             font_manager_font_preview_set_preview_text(self, self->restore_preview);
436             g_clear_pointer(&self->restore_preview, g_free);
437         }
438     }
439     if (pangram_changed && self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL)
440         generate_waterfall_preview(self);
441     return;
442 }
443 
444 static void
update_font_description(FontManagerFontPreview * self)445 update_font_description (FontManagerFontPreview *self)
446 {
447     g_return_if_fail(self != NULL && self->font_desc != NULL);
448     GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self->textview));
449     GtkTextTagTable *tag_table = gtk_text_buffer_get_tag_table(buffer);
450     GtkTextTag *font_description = gtk_text_tag_table_lookup(tag_table, "FontDescription");
451     g_return_if_fail(font_description != NULL);
452     g_object_set(G_OBJECT(font_description),
453                  "font-desc", self->font_desc,
454                  "size-points", self->preview_size,
455                  "fallback", FALSE,
456                  NULL);
457     return;
458 }
459 
460 static void
on_edit_toggled(FontManagerFontPreview * self,gboolean active)461 on_edit_toggled (FontManagerFontPreview *self, gboolean active)
462 {
463     g_return_if_fail(self != NULL);
464     self->allow_edit = active;
465     gtk_text_view_set_editable(GTK_TEXT_VIEW(self->textview), active);
466     return;
467 }
468 
469 static void
on_buffer_changed(FontManagerFontPreview * self,GtkTextBuffer * buffer)470 on_buffer_changed (FontManagerFontPreview *self, GtkTextBuffer *buffer)
471 {
472     g_return_if_fail(self != NULL);
473     gboolean undo_available = FALSE;
474     GtkWidget *controls = gtk_bin_get_child(GTK_BIN(self->controls));
475     if (self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW) {
476         GtkTextIter start, end;
477         gtk_text_buffer_get_bounds(buffer, &start, &end);
478         gchar *current_text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
479         undo_available = (g_strcmp0(self->default_preview, current_text) != 0);
480         g_free(self->preview);
481         self->preview = current_text;
482         g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_PREVIEW_TEXT]);
483     }
484     g_object_set(G_OBJECT(controls), "undo-available", undo_available, NULL);
485     return;
486 }
487 
488 static void
on_undo_clicked(FontManagerFontPreview * self,FontManagerPreviewControls * controls)489 on_undo_clicked (FontManagerFontPreview *self, FontManagerPreviewControls *controls)
490 {
491     g_return_if_fail(self != NULL);
492     g_return_if_fail(self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW);
493     font_manager_font_preview_set_preview_text(self, self->default_preview);
494     return;
495 }
496 
497 static gboolean
on_event(FontManagerFontPreview * self,GdkEvent * event,GtkWidget * widget)498 on_event (FontManagerFontPreview *self, GdkEvent *event, GtkWidget *widget)
499 {
500     g_return_val_if_fail(self != NULL, GDK_EVENT_PROPAGATE);
501     g_return_val_if_fail(event != NULL, GDK_EVENT_PROPAGATE);
502     if (event->type == GDK_SCROLL)
503         return GDK_EVENT_PROPAGATE;
504     if (self->allow_edit && self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW)
505         return GDK_EVENT_PROPAGATE;
506     GdkWindow *text_window = gtk_text_view_get_window(GTK_TEXT_VIEW(self->textview), GTK_TEXT_WINDOW_TEXT);
507     gdk_window_set_cursor(text_window, NULL);
508     return GDK_EVENT_STOP;
509 }
510 
511 static GtkTextTagTable *
font_manager_text_tag_table_new(void)512 font_manager_text_tag_table_new (void)
513 {
514     GtkTextTagTable *tags = gtk_text_tag_table_new();
515     g_autoptr(GtkTextTag) font = gtk_text_tag_new("FontDescription");
516     g_object_set(font, "fallback", FALSE, NULL);
517     if (!gtk_text_tag_table_add(tags, font))
518         g_warning(G_STRLOC" : Failed to add text tag to table: FontDescription");
519     g_autoptr(GtkTextTag) point_size = gtk_text_tag_new("SizePoint");
520     g_object_set(point_size, "family", "Monospace", "rise", 1250, "size-points", 6.5, NULL);
521     if (!gtk_text_tag_table_add(tags, point_size))
522         g_warning(G_STRLOC" : Failed to add text tag to table: size-points");
523     return tags;
524 }
525 
526 static void
font_manager_font_preview_init(FontManagerFontPreview * self)527 font_manager_font_preview_init (FontManagerFontPreview *self)
528 {
529     g_return_if_fail(self != NULL);
530     self->allow_edit = FALSE;
531     self->samples = NULL;
532     self->restore_preview = NULL;
533     self->max_waterfall_size = DEFAULT_WATERFALL_MAX_SIZE;
534     GtkStyleContext *ctx = gtk_widget_get_style_context(GTK_WIDGET(self));
535     gtk_style_context_add_class(ctx, GTK_STYLE_CLASS_VIEW);
536     gtk_widget_set_name(GTK_WIDGET(self), "FontManagerFontPreview");
537     gtk_orientable_set_orientation(GTK_ORIENTABLE(self), GTK_ORIENTATION_VERTICAL);
538     g_autoptr(GtkTextTagTable) tag_table = font_manager_text_tag_table_new();
539     self->pangram = font_manager_get_localized_pangram();
540     self->default_pangram = font_manager_get_localized_pangram();
541     self->preview = g_strdup_printf(FONT_MANAGER_DEFAULT_PREVIEW_TEXT, self->pangram);
542     self->default_preview = g_strdup(self->preview);
543     self->justification = GTK_JUSTIFY_CENTER;
544     g_autoptr(GtkTextBuffer) buffer = gtk_text_buffer_new(tag_table);
545     GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
546     self->textview = gtk_text_view_new_with_buffer(buffer);
547     gtk_drag_dest_unset(self->textview);
548     GtkWidget *controls = font_manager_preview_controls_new();
549     self->controls = gtk_revealer_new();
550     GtkWidget *fontscale = font_manager_font_scale_new();
551     self->fontscale = gtk_revealer_new();
552     gtk_container_add(GTK_CONTAINER(self->controls), controls);
553     gtk_container_add(GTK_CONTAINER(self->fontscale), fontscale);
554     gtk_container_add(GTK_CONTAINER(scroll), self->textview);
555     gtk_box_pack_start(GTK_BOX(self), self->controls, FALSE, TRUE, 0);
556     font_manager_widget_set_expand(scroll, TRUE);
557     gtk_box_pack_start(GTK_BOX(self), scroll, TRUE, TRUE, 0);
558     gtk_box_pack_end(GTK_BOX(self), self->fontscale, FALSE, TRUE, 0);
559     font_manager_widget_set_margin(self->textview, FONT_MANAGER_DEFAULT_MARGIN * 2);
560     gtk_widget_set_margin_top(self->textview, FONT_MANAGER_DEFAULT_MARGIN * 1.5);
561     gtk_widget_set_margin_bottom(self->textview, FONT_MANAGER_DEFAULT_MARGIN * 1.5);
562     font_manager_widget_set_expand(scroll, TRUE);
563     font_manager_font_preview_set_font_description(self, FONT_MANAGER_DEFAULT_FONT);
564     font_manager_font_preview_set_preview_size(self, FONT_MANAGER_DEFAULT_PREVIEW_SIZE);
565     font_manager_font_preview_set_preview_mode(self, FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL);
566     GtkAdjustment *adjustment = font_manager_font_scale_get_adjustment(FONT_MANAGER_FONT_SCALE(fontscale));
567     GBindingFlags flags = G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE;
568     g_object_bind_property(adjustment, "value", self, "preview-size", flags);
569     flags = G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE;
570     g_object_bind_property(self, "font-description", controls, "description", flags);
571     g_object_bind_property(controls, "justification", self, "justification", flags);
572     font_manager_font_preview_set_justification(self, GTK_JUSTIFY_CENTER);
573     g_signal_connect_swapped(controls, "edit-toggled", G_CALLBACK(on_edit_toggled), self);
574     g_signal_connect_swapped(buffer, "changed", G_CALLBACK(on_buffer_changed), self);
575     g_signal_connect_swapped(controls, "undo-clicked", G_CALLBACK(on_undo_clicked), self);
576     g_signal_connect_swapped(self->textview, "event", G_CALLBACK(on_event), self);
577     gtk_widget_show_all(scroll);
578     gtk_widget_show_all(self->controls);
579     gtk_widget_show_all(self->fontscale);
580     return;
581 }
582 
583 /**
584  * font_manager_font_preview_set_preview_mode:
585  * @self:   #FontManagerFontPreview
586  * @mode:   Preview mode.
587  */
588 void
font_manager_font_preview_set_preview_mode(FontManagerFontPreview * self,FontManagerFontPreviewMode mode)589 font_manager_font_preview_set_preview_mode (FontManagerFontPreview *self,
590                                             FontManagerFontPreviewMode mode)
591 {
592     g_return_if_fail(self != NULL);
593     g_idle_remove_by_data(self);
594     self->mode = mode;
595     GtkTextIter start;
596     GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self->textview));
597     gtk_text_buffer_get_start_iter(buffer, &start);
598     gtk_text_view_set_editable(GTK_TEXT_VIEW(self->textview), FALSE);
599     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(self->textview), GTK_WRAP_WORD_CHAR);
600     gtk_text_view_set_justification(GTK_TEXT_VIEW(self->textview), GTK_JUSTIFY_FILL);
601     gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(self->textview), &start, 0.0, TRUE, 0.0, 0.0);
602     gtk_text_view_set_top_margin(GTK_TEXT_VIEW(self->textview), 0);
603     switch (mode) {
604         case FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW:
605             gtk_text_view_set_top_margin(GTK_TEXT_VIEW(self->textview), FONT_MANAGER_DEFAULT_MARGIN * 6);
606             font_manager_font_preview_set_preview_text(self, NULL);
607             gtk_text_view_set_justification(GTK_TEXT_VIEW(self->textview), self->justification);
608             gtk_text_view_set_editable(GTK_TEXT_VIEW(self->textview), self->allow_edit);
609             break;
610         case FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL:
611             generate_waterfall_preview(self);
612             gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(self->textview), GTK_WRAP_NONE);
613             break;
614         case FONT_MANAGER_FONT_PREVIEW_MODE_LOREM_IPSUM:
615             gtk_text_buffer_set_text(buffer, FONT_MANAGER_LOREM_IPSUM, -1);
616             break;
617         default:
618             g_critical("Invalid preview mode : %i", (gint) mode);
619             g_return_if_reached();
620     }
621     update_sample_string(self);
622     apply_font_description(self);
623     update_revealer_state(self, mode);
624     g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_PREVIEW_MODE]);
625     return;
626 }
627 
628 /**
629  * font_manager_font_preview_set_preview_size:
630  * @self:           #FontManagerFontPreview
631  * @size_points:    Preview text size.
632  */
633 void
font_manager_font_preview_set_preview_size(FontManagerFontPreview * self,gdouble size_points)634 font_manager_font_preview_set_preview_size (FontManagerFontPreview *self,
635                                             gdouble size_points)
636 {
637     g_return_if_fail(self != NULL);
638     self->preview_size = CLAMP(size_points, MIN_FONT_SIZE, MAX_FONT_SIZE);
639     update_font_description(self);
640     update_sample_string(self);
641     apply_font_description(self);
642     g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_PREVIEW_SIZE]);
643     return;
644 }
645 
646 /**
647  * font_manager_font_preview_set_preview_text:
648  * @self:           #FontManagerFontPreview
649  * @preview_text:   Preview text.
650  */
651 void
font_manager_font_preview_set_preview_text(FontManagerFontPreview * self,const gchar * preview_text)652 font_manager_font_preview_set_preview_text (FontManagerFontPreview *self,
653                                             const gchar *preview_text)
654 {
655     g_return_if_fail(self != NULL);
656 
657     if (preview_text) {
658         gchar *new_preview = g_strdup(preview_text);
659         g_free(self->preview);
660         self->preview = new_preview;
661     }
662 
663     if (self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW) {
664         g_return_if_fail(self->preview != NULL);
665         GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self->textview));
666         g_autofree gchar *valid = g_utf8_make_valid(self->preview, -1);
667         gtk_text_buffer_set_text(buffer, valid, -1);
668     }
669     apply_font_description(self);
670     return;
671 }
672 
673 /**
674  * font_manager_font_preview_set_font_description:
675  * @self:   #FontManagerFontPreview
676  * @font: (nullable): string representation of a font description.
677  *
678  * See #pango_font_description_from_string() for details on what constitutes a
679  * valid font description string.
680  */
681 void
font_manager_font_preview_set_font_description(FontManagerFontPreview * self,const gchar * font)682 font_manager_font_preview_set_font_description (FontManagerFontPreview *self,
683                                                 const gchar *font)
684 {
685     g_return_if_fail(self != NULL);
686     pango_font_description_free(self->font_desc);
687     self->font_desc = pango_font_description_from_string(font ? font : FONT_MANAGER_DEFAULT_FONT);
688     update_font_description(self);
689     update_sample_string(self);
690     apply_font_description(self);
691     g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_FONT_DESC]);
692     return;
693 }
694 
695 /**
696  * font_manager_font_preview_set_justification:
697  * @self:           #FontManagerFontPreview
698  * @justification:  #GtkJustification
699  *
700  * Set preview text justification.
701  */
702 void
font_manager_font_preview_set_justification(FontManagerFontPreview * self,GtkJustification justification)703 font_manager_font_preview_set_justification (FontManagerFontPreview *self,
704                                              GtkJustification justification)
705 {
706     g_return_if_fail(self != NULL);
707     self->justification = justification;
708     if (self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_PREVIEW)
709         gtk_text_view_set_justification(GTK_TEXT_VIEW(self->textview), justification);
710     g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_JUSTIFICATION]);
711     return;
712 }
713 
714 /**
715  * font_manager_font_preview_set_sample_strings:
716  * @self:           #FontManagerFontPreview
717  * @samples:        #JsonObject containing sample strings
718  *
719  * @samples is expected to have a dictionary like structure,
720  * with the font description as key and sample string as value.
721  */
722 void
font_manager_font_preview_set_sample_strings(FontManagerFontPreview * self,GHashTable * samples)723 font_manager_font_preview_set_sample_strings (FontManagerFontPreview *self, GHashTable *samples)
724 {
725     g_return_if_fail(self != NULL);
726     g_clear_pointer(&self->samples, g_hash_table_unref);
727     if (samples)
728         self->samples = g_hash_table_ref(samples);
729     update_sample_string(self);
730     g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_SAMPLES]);
731     return;
732 }
733 
734 /**
735  * font_manager_font_preview_set_max_waterfall_size:
736  * @self:           #FontManagerFontPreview
737  * @size_points:    Maximum size to use for waterfall previews.
738  */
739 void
font_manager_font_preview_set_max_waterfall_size(FontManagerFontPreview * self,gdouble size_points)740 font_manager_font_preview_set_max_waterfall_size (FontManagerFontPreview *self,
741                                                   gdouble size_points)
742 {
743     g_return_if_fail(self != NULL);
744     self->max_waterfall_size = CLAMP(size_points, MIN_FONT_SIZE * 4, MAX_FONT_SIZE);
745     if (self->mode == FONT_MANAGER_FONT_PREVIEW_MODE_WATERFALL)
746         generate_waterfall_preview(self);
747     g_object_notify_by_pspec(G_OBJECT(self), obj_properties[PROP_WATERFALL_MAX]);
748     return;
749 }
750 
751 /**
752  * font_manager_font_preview_get_preview_size:
753  * @self:   #FontManagerFontPreview
754  *
755  * Returns: Current preview size.
756  */
757 gdouble
font_manager_font_preview_get_preview_size(FontManagerFontPreview * self)758 font_manager_font_preview_get_preview_size (FontManagerFontPreview *self)
759 {
760     g_return_val_if_fail(self != NULL, 0.0);
761     return self->preview_size;
762 }
763 
764 /**
765  * font_manager_font_preview_get_preview_text:
766  * @self:           #FontManagerFontPreview
767  *
768  * Returns:(transfer full) (nullable):
769  * A newly allocated string that must be freed with #g_free or %NULL
770  */
771 gchar *
font_manager_font_preview_get_preview_text(FontManagerFontPreview * self)772 font_manager_font_preview_get_preview_text (FontManagerFontPreview *self)
773 {
774     g_return_val_if_fail(self != NULL, NULL);
775     return g_strdup(self->preview);
776 }
777 
778 /**
779  * font_manager_font_preview_get_font_description:
780  * @self:   #FontManagerFontPreview
781  *
782  * Returns:(transfer full) (nullable):
783  * A newly allocated string that must be freed with #g_free or %NULL
784  */
785 gchar *
font_manager_font_preview_get_font_description(FontManagerFontPreview * self)786 font_manager_font_preview_get_font_description (FontManagerFontPreview *self)
787 {
788     g_return_val_if_fail(self != NULL, NULL);
789     return pango_font_description_to_string(self->font_desc);
790 }
791 
792 /**
793  * font_manager_font_preview_get_preview_mode:
794  * @self:   #FontManagerFontPreview
795  *
796  * Returns: Current preview mode.
797  */
798 FontManagerFontPreviewMode
font_manager_font_preview_get_preview_mode(FontManagerFontPreview * self)799 font_manager_font_preview_get_preview_mode (FontManagerFontPreview *self)
800 {
801     g_return_val_if_fail(self != NULL, 0);
802     return self->mode;
803 }
804 
805 /**
806  * font_manager_font_preview_get_justification:
807  * @self:   #FontManagerFontPreview
808  *
809  * Returns: Current preview text justification.
810  */
811 GtkJustification
font_manager_font_preview_get_justification(FontManagerFontPreview * self)812 font_manager_font_preview_get_justification (FontManagerFontPreview *self)
813 {
814     g_return_val_if_fail(self != NULL, 0);
815     return self->justification;
816 }
817 
818 /**
819  * font_manager_font_preview_new:
820  *
821  * Returns: A newly created #FontManagerFontPreview.
822  * Free the returned object using #g_object_unref().
823  */
824 GtkWidget *
font_manager_font_preview_new(void)825 font_manager_font_preview_new (void)
826 {
827     return g_object_new(FONT_MANAGER_TYPE_FONT_PREVIEW, NULL);
828 }
829