/* GTK - The GIMP Toolkit * Copyright (C) 2011 Alberto Ruiz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "config.h" #include #include #include #include #include "gtkfontchooserwidget.h" #include "gtkadjustment.h" #include "gtkbuildable.h" #include "gtkbox.h" #include "gtkcellrenderertext.h" #include "gtkcssnumbervalueprivate.h" #include "gtkentry.h" #include "gtksearchentry.h" #include "gtkgrid.h" #include "gtkfontchooser.h" #include "gtkfontchooserutils.h" #include "gtkintl.h" #include "gtklabel.h" #include "gtkliststore.h" #include "gtkstack.h" #include "gtkprivate.h" #include "gtkscale.h" #include "gtkscrolledwindow.h" #include "gtkspinbutton.h" #include "gtkstylecontextprivate.h" #include "gtktextview.h" #include "gtktreeselection.h" #include "gtktreeview.h" #include "gtkwidget.h" #include "gtksettings.h" #include "gtkdialog.h" /** * SECTION:gtkfontchooserwidget * @Short_description: A widget for selecting fonts * @Title: GtkFontChooserWidget * @See_also: #GtkFontChooserDialog * * The #GtkFontChooserWidget widget lists the available fonts, * styles and sizes, allowing the user to select a font. It is * used in the #GtkFontChooserDialog widget to provide a * dialog box for selecting fonts. * * To set the font which is initially selected, use * gtk_font_chooser_set_font() or gtk_font_chooser_set_font_desc(). * * To get the selected font use gtk_font_chooser_get_font() or * gtk_font_chooser_get_font_desc(). * * To change the text which is shown in the preview area, use * gtk_font_chooser_set_preview_text(). * * # CSS nodes * * GtkFontChooserWidget has a single CSS node with name fontchooser. * * Since: 3.2 */ struct _GtkFontChooserWidgetPrivate { GtkWidget *grid; GtkWidget *search_entry; GtkWidget *family_face_list; GtkTreeViewColumn *family_face_column; GtkCellRenderer *family_face_cell; GtkWidget *list_scrolled_window; GtkWidget *list_stack; GtkTreeModel *model; GtkTreeModel *filter_model; GtkWidget *preview; gchar *preview_text; gboolean show_preview_entry; GtkWidget *size_spin; GtkWidget *size_slider; PangoFontMap *font_map; PangoFontDescription *font_desc; GtkTreeIter font_iter; /* invalid if font not available or pointer into model (not filter_model) to the row containing font */ GtkFontFilterFunc filter_func; gpointer filter_data; GDestroyNotify filter_data_destroy; guint last_fontconfig_timestamp; }; /* Keep in line with GtkTreeStore defined in gtkfontchooserwidget.ui */ enum { FAMILY_COLUMN, FACE_COLUMN, FONT_DESC_COLUMN, PREVIEW_TITLE_COLUMN }; static void gtk_font_chooser_widget_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gtk_font_chooser_widget_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gtk_font_chooser_widget_finalize (GObject *object); static void gtk_font_chooser_widget_screen_changed (GtkWidget *widget, GdkScreen *previous_screen); static void gtk_font_chooser_widget_style_updated (GtkWidget *widget); static gboolean gtk_font_chooser_widget_find_font (GtkFontChooserWidget *fontchooser, const PangoFontDescription *font_desc, GtkTreeIter *iter); static void gtk_font_chooser_widget_ensure_selection (GtkFontChooserWidget *fontchooser); static gchar *gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser); static void gtk_font_chooser_widget_set_font (GtkFontChooserWidget *fontchooser, const gchar *fontname); static PangoFontDescription *gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *fontchooser); static void gtk_font_chooser_widget_merge_font_desc(GtkFontChooserWidget *fontchooser, const PangoFontDescription *font_desc, GtkTreeIter *iter); static void gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser, PangoFontDescription *font_desc); static const gchar *gtk_font_chooser_widget_get_preview_text (GtkFontChooserWidget *fontchooser); static void gtk_font_chooser_widget_set_preview_text (GtkFontChooserWidget *fontchooser, const gchar *text); static gboolean gtk_font_chooser_widget_get_show_preview_entry (GtkFontChooserWidget *fontchooser); static void gtk_font_chooser_widget_set_show_preview_entry (GtkFontChooserWidget *fontchooser, gboolean show_preview_entry); static void gtk_font_chooser_widget_set_cell_size (GtkFontChooserWidget *fontchooser); static void gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser, gboolean force); static gboolean visible_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data); static void gtk_font_chooser_widget_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data); static void gtk_font_chooser_widget_iface_init (GtkFontChooserIface *iface); G_DEFINE_TYPE_WITH_CODE (GtkFontChooserWidget, gtk_font_chooser_widget, GTK_TYPE_WIDGET, G_ADD_PRIVATE (GtkFontChooserWidget) G_IMPLEMENT_INTERFACE (GTK_TYPE_FONT_CHOOSER, gtk_font_chooser_widget_iface_init)) typedef struct _GtkDelayedFontDescription GtkDelayedFontDescription; struct _GtkDelayedFontDescription { PangoFontFace *face; PangoFontDescription *desc; guint ref_count; }; static GtkDelayedFontDescription * gtk_delayed_font_description_new (PangoFontFace *face) { GtkDelayedFontDescription *result; result = g_slice_new0 (GtkDelayedFontDescription); result->face = g_object_ref (face); result->desc = NULL; result->ref_count = 1; return result; } static GtkDelayedFontDescription * gtk_delayed_font_description_ref (GtkDelayedFontDescription *desc) { desc->ref_count++; return desc; } static void gtk_delayed_font_description_unref (GtkDelayedFontDescription *desc) { desc->ref_count--; if (desc->ref_count > 0) return; g_object_unref (desc->face); if (desc->desc) pango_font_description_free (desc->desc); g_slice_free (GtkDelayedFontDescription, desc); } static const PangoFontDescription * gtk_delayed_font_description_get (GtkDelayedFontDescription *desc) { if (desc->desc == NULL) desc->desc = pango_font_face_describe (desc->face); return desc->desc; } #define GTK_TYPE_DELAYED_FONT_DESCRIPTION (gtk_delayed_font_description_get_type ()) GType gtk_delayed_font_description_get_type (void); G_DEFINE_BOXED_TYPE (GtkDelayedFontDescription, gtk_delayed_font_description, gtk_delayed_font_description_ref, gtk_delayed_font_description_unref) static void gtk_font_chooser_widget_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (object); switch (prop_id) { case GTK_FONT_CHOOSER_PROP_FONT: gtk_font_chooser_widget_set_font (fontchooser, g_value_get_string (value)); break; case GTK_FONT_CHOOSER_PROP_FONT_DESC: gtk_font_chooser_widget_take_font_desc (fontchooser, g_value_dup_boxed (value)); break; case GTK_FONT_CHOOSER_PROP_PREVIEW_TEXT: gtk_font_chooser_widget_set_preview_text (fontchooser, g_value_get_string (value)); break; case GTK_FONT_CHOOSER_PROP_SHOW_PREVIEW_ENTRY: gtk_font_chooser_widget_set_show_preview_entry (fontchooser, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_font_chooser_widget_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (object); switch (prop_id) { case GTK_FONT_CHOOSER_PROP_FONT: g_value_take_string (value, gtk_font_chooser_widget_get_font (fontchooser)); break; case GTK_FONT_CHOOSER_PROP_FONT_DESC: g_value_set_boxed (value, gtk_font_chooser_widget_get_font_desc (fontchooser)); break; case GTK_FONT_CHOOSER_PROP_PREVIEW_TEXT: g_value_set_string (value, gtk_font_chooser_widget_get_preview_text (fontchooser)); break; case GTK_FONT_CHOOSER_PROP_SHOW_PREVIEW_ENTRY: g_value_set_boolean (value, gtk_font_chooser_widget_get_show_preview_entry (fontchooser)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_font_chooser_widget_refilter_font_list (GtkFontChooserWidget *fontchooser) { gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (fontchooser->priv->filter_model)); gtk_font_chooser_widget_ensure_selection (fontchooser); } static void text_changed_cb (GtkEntry *entry, GtkFontChooserWidget *fc) { gtk_font_chooser_widget_refilter_font_list (fc); } static void stop_search_cb (GtkEntry *entry, GtkFontChooserWidget *fc) { if (gtk_entry_get_text (entry)[0] != 0) gtk_entry_set_text (entry, ""); else { GtkWidget *dialog; GtkWidget *button = NULL; dialog = gtk_widget_get_ancestor (GTK_WIDGET (fc), GTK_TYPE_DIALOG); if (dialog) button = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); if (button) gtk_widget_activate (button); } } static void size_change_cb (GtkAdjustment *adjustment, gpointer user_data) { GtkFontChooserWidget *fontchooser = user_data; GtkFontChooserWidgetPrivate *priv = fontchooser->priv; PangoFontDescription *font_desc; gdouble size = gtk_adjustment_get_value (adjustment); font_desc = pango_font_description_new (); if (pango_font_description_get_size_is_absolute (priv->font_desc)) pango_font_description_set_absolute_size (font_desc, size * PANGO_SCALE); else pango_font_description_set_size (font_desc, size * PANGO_SCALE); gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc); } static gboolean output_cb (GtkSpinButton *spin, gpointer data) { GtkAdjustment *adjustment; gchar *text; gdouble value; adjustment = gtk_spin_button_get_adjustment (spin); value = gtk_adjustment_get_value (adjustment); text = g_strdup_printf ("%2.4g", value); gtk_spin_button_set_text (spin, text); g_free (text); return TRUE; } static void gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser) { GtkFontChooserWidgetPrivate *priv = fontchooser->priv; GtkAdjustment *adj, *spin_adj; const int *sizes; gint *font_sizes; gint i, n_sizes; gdouble value, spin_value; if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (priv->model), &priv->font_iter)) { PangoFontFace *face; gtk_tree_model_get (priv->model, &priv->font_iter, FACE_COLUMN, &face, -1); pango_font_face_list_sizes (face, &font_sizes, &n_sizes); /* It seems not many fonts actually have a sane set of sizes */ for (i = 0; i < n_sizes; i++) font_sizes[i] = font_sizes[i] / PANGO_SCALE; g_object_unref (face); } else { font_sizes = NULL; n_sizes = 0; } if (n_sizes < 2) { static const gint fallback_sizes[] = { 6, 8, 9, 10, 11, 12, 13, 14, 16, 20, 24, 36, 48, 72 }; sizes = fallback_sizes; n_sizes = G_N_ELEMENTS (fallback_sizes); } else { sizes = font_sizes; } gtk_scale_clear_marks (GTK_SCALE (priv->size_slider)); adj = gtk_range_get_adjustment (GTK_RANGE (priv->size_slider)); spin_adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->size_spin)); spin_value = gtk_adjustment_get_value (spin_adj); if (spin_value < sizes[0]) value = (gdouble) sizes[0]; else if (spin_value > sizes[n_sizes - 1]) value = (gdouble)sizes[n_sizes - 1]; else value = (gdouble)spin_value; /* ensure clamping doesn't callback into font resizing code */ g_signal_handlers_block_by_func (adj, size_change_cb, fontchooser); gtk_adjustment_configure (adj, value, sizes[0], sizes[n_sizes - 1], gtk_adjustment_get_step_increment (adj), gtk_adjustment_get_page_increment (adj), gtk_adjustment_get_page_size (adj)); g_signal_handlers_unblock_by_func (adj, size_change_cb, fontchooser); for (i = 0; i < n_sizes; i++) { gtk_scale_add_mark (GTK_SCALE (priv->size_slider), sizes[i], GTK_POS_BOTTOM, NULL); } g_free (font_sizes); } static void row_activated_cb (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) { GtkFontChooserWidget *fontchooser = user_data; gchar *fontname; fontname = gtk_font_chooser_widget_get_font (fontchooser); _gtk_font_chooser_font_activated (GTK_FONT_CHOOSER (fontchooser), fontname); g_free (fontname); } static void cursor_changed_cb (GtkTreeView *treeview, gpointer user_data) { GtkFontChooserWidget *fontchooser = user_data; GtkFontChooserWidgetPrivate *priv = fontchooser->priv; GtkDelayedFontDescription *desc; GtkTreeIter filter_iter, iter; GtkTreePath *path = NULL; gtk_tree_view_get_cursor (treeview, &path, NULL); if (!path) return; if (!gtk_tree_model_get_iter (priv->filter_model, &filter_iter, path)) { gtk_tree_path_free (path); return; } gtk_tree_path_free (path); gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (priv->filter_model), &iter, &filter_iter); gtk_tree_model_get (priv->model, &iter, FONT_DESC_COLUMN, &desc, -1); gtk_font_chooser_widget_merge_font_desc (fontchooser, gtk_delayed_font_description_get (desc), &iter); gtk_delayed_font_description_unref (desc); } static gboolean resize_by_scroll_cb (GtkWidget *scrolled_window, GdkEventScroll *event, gpointer user_data) { GtkFontChooserWidget *fc = user_data; GtkFontChooserWidgetPrivate *priv = fc->priv; GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->size_spin)); if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_RIGHT) gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) + gtk_adjustment_get_step_increment (adj)); else if (event->direction == GDK_SCROLL_DOWN || event->direction == GDK_SCROLL_LEFT) gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) - gtk_adjustment_get_step_increment (adj)); else if (event->direction == GDK_SCROLL_SMOOTH && event->delta_x != 0.0) gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) + gtk_adjustment_get_step_increment (adj) * event->delta_x); else if (event->direction == GDK_SCROLL_SMOOTH && event->delta_y != 0.0) gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) - gtk_adjustment_get_step_increment (adj) * event->delta_y); return TRUE; } static void gtk_font_chooser_widget_update_preview_attributes (GtkFontChooserWidget *fontchooser) { GtkFontChooserWidgetPrivate *priv; PangoAttrList *attrs; priv = fontchooser->priv; attrs = pango_attr_list_new (); /* Prevent font fallback */ pango_attr_list_insert (attrs, pango_attr_fallback_new (FALSE)); /* Force current font */ pango_attr_list_insert (attrs, pango_attr_font_desc_new (priv->font_desc)); gtk_entry_set_attributes (GTK_ENTRY (priv->preview), attrs); pango_attr_list_unref (attrs); } static void row_inserted_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { GtkFontChooserWidget *fontchooser = user_data; GtkFontChooserWidgetPrivate *priv = fontchooser->priv; gtk_stack_set_visible_child_name (GTK_STACK (priv->list_stack), "list"); } static void row_deleted_cb (GtkTreeModel *model, GtkTreePath *path, gpointer user_data) { GtkFontChooserWidget *fontchooser = user_data; GtkFontChooserWidgetPrivate *priv = fontchooser->priv; if (gtk_tree_model_iter_n_children (model, NULL) == 0) gtk_stack_set_visible_child_name (GTK_STACK (priv->list_stack), "empty"); } static void gtk_font_chooser_widget_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (widget); GtkFontChooserWidgetPrivate *priv = gtk_font_chooser_widget_get_instance_private (self); gtk_widget_measure (priv->grid, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); } static void gtk_font_chooser_widget_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (widget); GtkFontChooserWidgetPrivate *priv = gtk_font_chooser_widget_get_instance_private (self); gtk_widget_snapshot_child (widget, priv->grid, snapshot); } static void gtk_font_chooser_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (widget); GtkFontChooserWidgetPrivate *priv = gtk_font_chooser_widget_get_instance_private (self); GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->size_allocate (widget, allocation); gtk_widget_size_allocate (priv->grid, allocation); } static void gtk_font_chooser_widget_dispose (GObject *object) { GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (object); GtkFontChooserWidgetPrivate *priv = gtk_font_chooser_widget_get_instance_private (self); if (priv->grid) { gtk_widget_unparent (priv->grid); priv->grid = NULL; } G_OBJECT_CLASS (gtk_font_chooser_widget_parent_class)->dispose (object); } static void gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); g_type_ensure (GTK_TYPE_DELAYED_FONT_DESCRIPTION); g_type_ensure (G_TYPE_THEMED_ICON); widget_class->screen_changed = gtk_font_chooser_widget_screen_changed; widget_class->style_updated = gtk_font_chooser_widget_style_updated; widget_class->measure = gtk_font_chooser_widget_measure; widget_class->size_allocate = gtk_font_chooser_widget_size_allocate; widget_class->snapshot = gtk_font_chooser_widget_snapshot; gobject_class->finalize = gtk_font_chooser_widget_finalize; gobject_class->dispose = gtk_font_chooser_widget_dispose; gobject_class->set_property = gtk_font_chooser_widget_set_property; gobject_class->get_property = gtk_font_chooser_widget_get_property; _gtk_font_chooser_install_properties (gobject_class); /* Bind class to template */ gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkfontchooserwidget.ui"); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, search_entry); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, family_face_list); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, family_face_column); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, family_face_cell); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, list_scrolled_window); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, list_stack); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, model); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, filter_model); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, preview); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, size_spin); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, size_slider); gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, grid); gtk_widget_class_bind_template_callback (widget_class, text_changed_cb); gtk_widget_class_bind_template_callback (widget_class, stop_search_cb); gtk_widget_class_bind_template_callback (widget_class, cursor_changed_cb); gtk_widget_class_bind_template_callback (widget_class, row_activated_cb); gtk_widget_class_bind_template_callback (widget_class, gtk_font_chooser_widget_set_cell_size); gtk_widget_class_bind_template_callback (widget_class, resize_by_scroll_cb); gtk_widget_class_bind_template_callback (widget_class, row_deleted_cb); gtk_widget_class_bind_template_callback (widget_class, row_inserted_cb); gtk_widget_class_bind_template_callback (widget_class, row_deleted_cb); gtk_widget_class_bind_template_callback (widget_class, size_change_cb); gtk_widget_class_bind_template_callback (widget_class, output_cb); gtk_widget_class_set_css_name (widget_class, "fontchooser"); } static void gtk_font_chooser_widget_init (GtkFontChooserWidget *fontchooser) { GtkFontChooserWidgetPrivate *priv; fontchooser->priv = gtk_font_chooser_widget_get_instance_private (fontchooser); priv = fontchooser->priv; gtk_widget_set_has_window (GTK_WIDGET (fontchooser), FALSE); gtk_widget_init_template (GTK_WIDGET (fontchooser)); /* Default preview string */ priv->preview_text = g_strdup (pango_language_get_sample_string (NULL)); priv->show_preview_entry = TRUE; priv->font_desc = pango_font_description_new (); /* Set default preview text */ gtk_entry_set_text (GTK_ENTRY (priv->preview), priv->preview_text); gtk_font_chooser_widget_update_preview_attributes (fontchooser); /* Set the upper values of the spin/scale with G_MAXINT / PANGO_SCALE */ gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->size_spin), 1.0, (gdouble)(G_MAXINT / PANGO_SCALE)); gtk_adjustment_set_upper (gtk_range_get_adjustment (GTK_RANGE (priv->size_slider)), (gdouble)(G_MAXINT / PANGO_SCALE)); /* Setup treeview/model auxilary functions */ gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter_model), visible_func, (gpointer)priv, NULL); gtk_tree_view_column_set_cell_data_func (priv->family_face_column, priv->family_face_cell, gtk_font_chooser_widget_cell_data_func, fontchooser, NULL); /* Load data and set initial style-dependent parameters */ gtk_font_chooser_widget_load_fonts (fontchooser, TRUE); gtk_font_chooser_widget_set_cell_size (fontchooser); gtk_font_chooser_widget_take_font_desc (fontchooser, NULL); } /** * gtk_font_chooser_widget_new: * * Creates a new #GtkFontChooserWidget. * * Returns: a new #GtkFontChooserWidget * * Since: 3.2 */ GtkWidget * gtk_font_chooser_widget_new (void) { return g_object_new (GTK_TYPE_FONT_CHOOSER_WIDGET, NULL); } static int cmp_families (const void *a, const void *b) { const char *a_name = pango_font_family_get_name (*(PangoFontFamily **)a); const char *b_name = pango_font_family_get_name (*(PangoFontFamily **)b); return g_utf8_collate (a_name, b_name); } static void gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser, gboolean force) { GtkFontChooserWidgetPrivate *priv = fontchooser->priv; GtkListStore *list_store; gint n_families, i; PangoFontFamily **families; gchar *family_and_face; guint fontconfig_timestamp; gboolean need_reload; PangoFontMap *font_map; g_object_get (gtk_widget_get_settings (GTK_WIDGET (fontchooser)), "gtk-fontconfig-timestamp", &fontconfig_timestamp, NULL); /* The fontconfig timestamp is only set on systems with fontconfig; every * other platform will set it to 0. For those systems, we fall back to * reloading the fonts every time. */ need_reload = fontconfig_timestamp == 0 || fontconfig_timestamp != priv->last_fontconfig_timestamp; priv->last_fontconfig_timestamp = fontconfig_timestamp; if (!need_reload && !force) return; list_store = GTK_LIST_STORE (priv->model); if (priv->font_map) font_map = priv->font_map; else font_map = pango_cairo_font_map_get_default (); pango_font_map_list_families (font_map, &families, &n_families); qsort (families, n_families, sizeof (PangoFontFamily *), cmp_families); g_signal_handlers_block_by_func (priv->family_face_list, cursor_changed_cb, fontchooser); gtk_list_store_clear (list_store); g_signal_handlers_unblock_by_func (priv->family_face_list, cursor_changed_cb, fontchooser); /* Iterate over families and faces */ for (i = 0; i < n_families; i++) { GtkTreeIter iter; PangoFontFace **faces; int j, n_faces; const gchar *fam_name = pango_font_family_get_name (families[i]); pango_font_family_list_faces (families[i], &faces, &n_faces); for (j = 0; j < n_faces; j++) { GtkDelayedFontDescription *desc; const gchar *face_name; face_name = pango_font_face_get_face_name (faces[j]); family_and_face = g_strconcat (fam_name, " ", face_name, NULL); desc = gtk_delayed_font_description_new (faces[j]); gtk_list_store_insert_with_values (list_store, &iter, -1, FAMILY_COLUMN, families[i], FACE_COLUMN, faces[j], FONT_DESC_COLUMN, desc, PREVIEW_TITLE_COLUMN, family_and_face, -1); g_free (family_and_face); gtk_delayed_font_description_unref (desc); } g_free (faces); } g_free (families); /* now make sure the font list looks right */ if (!gtk_font_chooser_widget_find_font (fontchooser, priv->font_desc, &priv->font_iter)) memset (&priv->font_iter, 0, sizeof (GtkTreeIter)); gtk_font_chooser_widget_ensure_selection (fontchooser); } static gboolean visible_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { GtkFontChooserWidgetPrivate *priv = user_data; gboolean result = TRUE; const gchar *search_text; gchar **split_terms; gchar *font_name, *font_name_casefold; guint i; if (priv->filter_func != NULL) { PangoFontFamily *family; PangoFontFace *face; gtk_tree_model_get (model, iter, FAMILY_COLUMN, &family, FACE_COLUMN, &face, -1); result = priv->filter_func (family, face, priv->filter_data); g_object_unref (family); g_object_unref (face); if (!result) return FALSE; } /* If there's no filter string we show the item */ search_text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry)); if (strlen (search_text) == 0) return TRUE; gtk_tree_model_get (model, iter, PREVIEW_TITLE_COLUMN, &font_name, -1); if (font_name == NULL) return FALSE; split_terms = g_strsplit (search_text, " ", 0); font_name_casefold = g_utf8_casefold (font_name, -1); for (i = 0; split_terms[i] && result; i++) { gchar* term_casefold = g_utf8_casefold (split_terms[i], -1); if (!strstr (font_name_casefold, term_casefold)) result = FALSE; g_free (term_casefold); } g_free (font_name_casefold); g_free (font_name); g_strfreev (split_terms); return result; } /* in pango units */ static int gtk_font_chooser_widget_get_preview_text_height (GtkFontChooserWidget *fontchooser) { GtkWidget *treeview = fontchooser->priv->family_face_list; GtkStyleContext *context; double dpi, font_size; context = gtk_widget_get_style_context (treeview); dpi = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_DPI), 100); gtk_style_context_get (context, "font-size", &font_size, NULL); return (dpi < 0.0 ? 96.0 : dpi) / 72.0 * PANGO_SCALE_X_LARGE * font_size * PANGO_SCALE; } static PangoAttrList * gtk_font_chooser_widget_get_preview_attributes (GtkFontChooserWidget *fontchooser, const PangoFontDescription *font_desc, gsize first_line_len) { PangoAttribute *attribute; PangoAttrList *attrs; attrs = pango_attr_list_new (); attribute = pango_attr_weight_new (PANGO_WEIGHT_BOLD); attribute->end_index = first_line_len; pango_attr_list_insert (attrs, attribute); attribute = pango_attr_scale_new (PANGO_SCALE_SMALL); attribute->end_index = first_line_len; pango_attr_list_insert (attrs, attribute); if (font_desc) { attribute = pango_attr_font_desc_new (font_desc); attribute->start_index = first_line_len; pango_attr_list_insert (attrs, attribute); } attribute = pango_attr_fallback_new (FALSE); attribute->start_index = first_line_len; pango_attr_list_insert (attrs, attribute); attribute = pango_attr_size_new_absolute (gtk_font_chooser_widget_get_preview_text_height (fontchooser)); attribute->start_index = first_line_len; pango_attr_list_insert (attrs, attribute); return attrs; } static void gtk_font_chooser_widget_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data) { GtkFontChooserWidget *fontchooser = user_data; GtkDelayedFontDescription *desc; PangoAttrList *attrs; char *preview_title, *text; gsize first_line_len; gtk_tree_model_get (tree_model, iter, PREVIEW_TITLE_COLUMN, &preview_title, FONT_DESC_COLUMN, &desc, -1); text = g_strconcat (preview_title, "\n", fontchooser->priv->preview_text, NULL); first_line_len = strlen (preview_title) + 1; attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser, gtk_delayed_font_description_get (desc), first_line_len); g_object_set (cell, "attributes", attrs, "text", text, NULL); gtk_delayed_font_description_unref (desc); pango_attr_list_unref (attrs); g_free (text); g_free (preview_title); } static void gtk_font_chooser_widget_set_cell_size (GtkFontChooserWidget *fontchooser) { GtkFontChooserWidgetPrivate *priv = fontchooser->priv; PangoAttrList *attrs; GtkRequisition size; gtk_cell_renderer_set_fixed_size (priv->family_face_cell, -1, -1); attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser, NULL, 1); g_object_set (priv->family_face_cell, "attributes", attrs, "text", "x\nx", NULL); pango_attr_list_unref (attrs); gtk_cell_renderer_get_preferred_size (priv->family_face_cell, priv->family_face_list, &size, NULL); gtk_cell_renderer_set_fixed_size (priv->family_face_cell, size.width, size.height); } static void gtk_font_chooser_widget_finalize (GObject *object) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (object); GtkFontChooserWidgetPrivate *priv = fontchooser->priv; if (priv->font_desc) pango_font_description_free (priv->font_desc); if (priv->filter_data_destroy) priv->filter_data_destroy (priv->filter_data); g_free (priv->preview_text); g_clear_object (&priv->font_map); G_OBJECT_CLASS (gtk_font_chooser_widget_parent_class)->finalize (object); } static gboolean my_pango_font_family_equal (const char *familya, const char *familyb) { return g_ascii_strcasecmp (familya, familyb) == 0; } static gboolean gtk_font_chooser_widget_find_font (GtkFontChooserWidget *fontchooser, const PangoFontDescription *font_desc, /* out arguments */ GtkTreeIter *iter) { GtkFontChooserWidgetPrivate *priv = fontchooser->priv; gboolean valid; if (pango_font_description_get_family (font_desc) == NULL) return FALSE; for (valid = gtk_tree_model_get_iter_first (priv->model, iter); valid; valid = gtk_tree_model_iter_next (priv->model, iter)) { GtkDelayedFontDescription *desc; PangoFontDescription *merged; PangoFontFamily *family; gtk_tree_model_get (priv->model, iter, FAMILY_COLUMN, &family, FONT_DESC_COLUMN, &desc, -1); if (!my_pango_font_family_equal (pango_font_description_get_family (font_desc), pango_font_family_get_name (family))) { gtk_delayed_font_description_unref (desc); g_object_unref (family); continue; } merged = pango_font_description_copy_static (gtk_delayed_font_description_get (desc)); pango_font_description_merge_static (merged, font_desc, FALSE); if (pango_font_description_equal (merged, font_desc)) { gtk_delayed_font_description_unref (desc); pango_font_description_free (merged); g_object_unref (family); break; } gtk_delayed_font_description_unref (desc); pango_font_description_free (merged); g_object_unref (family); } return valid; } static void fontconfig_changed (GtkFontChooserWidget *fontchooser) { gtk_font_chooser_widget_load_fonts (fontchooser, TRUE); } static void gtk_font_chooser_widget_screen_changed (GtkWidget *widget, GdkScreen *previous_screen) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (widget); GtkSettings *settings; if (GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->screen_changed) GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->screen_changed (widget, previous_screen); if (previous_screen) { settings = gtk_settings_get_for_screen (previous_screen); g_signal_handlers_disconnect_by_func (settings, fontconfig_changed, widget); } settings = gtk_widget_get_settings (widget); g_signal_connect_object (settings, "notify::gtk-fontconfig-timestamp", G_CALLBACK (fontconfig_changed), widget, G_CONNECT_SWAPPED); if (previous_screen == NULL) previous_screen = gdk_screen_get_default (); if (previous_screen == gtk_widget_get_screen (widget)) return; gtk_font_chooser_widget_load_fonts (fontchooser, FALSE); } static void gtk_font_chooser_widget_style_updated (GtkWidget *widget) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (widget); GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->style_updated (widget); gtk_font_chooser_widget_load_fonts (fontchooser, FALSE); } static PangoFontFamily * gtk_font_chooser_widget_get_family (GtkFontChooser *chooser) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); GtkFontChooserWidgetPrivate *priv = fontchooser->priv; PangoFontFamily *family; if (!gtk_list_store_iter_is_valid (GTK_LIST_STORE (priv->model), &priv->font_iter)) return NULL; gtk_tree_model_get (priv->model, &priv->font_iter, FAMILY_COLUMN, &family, -1); g_object_unref (family); return family; } static PangoFontFace * gtk_font_chooser_widget_get_face (GtkFontChooser *chooser) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); GtkFontChooserWidgetPrivate *priv = fontchooser->priv; PangoFontFace *face; if (!gtk_list_store_iter_is_valid (GTK_LIST_STORE (priv->model), &priv->font_iter)) return NULL; gtk_tree_model_get (priv->model, &priv->font_iter, FACE_COLUMN, &face, -1); g_object_unref (face); return face; } static gint gtk_font_chooser_widget_get_size (GtkFontChooser *chooser) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); return pango_font_description_get_size (fontchooser->priv->font_desc); } static gchar * gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser) { return pango_font_description_to_string (fontchooser->priv->font_desc); } static PangoFontDescription * gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *fontchooser) { return fontchooser->priv->font_desc; } static void gtk_font_chooser_widget_set_font (GtkFontChooserWidget *fontchooser, const gchar *fontname) { PangoFontDescription *font_desc; font_desc = pango_font_description_from_string (fontname); gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc); } static void gtk_font_chooser_widget_ensure_selection (GtkFontChooserWidget *fontchooser) { GtkFontChooserWidgetPrivate *priv = fontchooser->priv; GtkTreeSelection *selection; GtkTreeIter filter_iter; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->family_face_list)); if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (priv->model), &priv->font_iter) && gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter_model), &filter_iter, &priv->font_iter)) { GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->filter_model), &filter_iter); gtk_tree_selection_select_iter (selection, &filter_iter); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->family_face_list), path, NULL, FALSE, 0.0, 0.0); gtk_tree_path_free (path); } else { gtk_tree_selection_unselect_all (selection); } } static void gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget *fontchooser, const PangoFontDescription *font_desc, GtkTreeIter *iter) { GtkFontChooserWidgetPrivate *priv = fontchooser->priv; PangoFontMask mask; g_assert (font_desc != NULL); /* iter may be NULL if the font doesn't exist on the list */ mask = pango_font_description_get_set_fields (font_desc); /* sucky test, because we can't restrict the comparison to * only the parts that actually do get merged */ if (pango_font_description_equal (font_desc, priv->font_desc)) return; pango_font_description_merge (priv->font_desc, font_desc, TRUE); if (mask & PANGO_FONT_MASK_SIZE) { double font_size = (double) pango_font_description_get_size (priv->font_desc) / PANGO_SCALE; /* XXX: This clamps, which can cause it to reloop into here, do we need * to block its signal handler? */ gtk_range_set_value (GTK_RANGE (priv->size_slider), font_size); gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->size_spin), font_size); } if (mask & (PANGO_FONT_MASK_FAMILY | PANGO_FONT_MASK_STYLE | PANGO_FONT_MASK_VARIANT | PANGO_FONT_MASK_WEIGHT | PANGO_FONT_MASK_STRETCH)) { if (&priv->font_iter != iter) { if (iter == NULL) memset (&priv->font_iter, 0, sizeof (GtkTreeIter)); else memcpy (&priv->font_iter, iter, sizeof (GtkTreeIter)); gtk_font_chooser_widget_ensure_selection (fontchooser); } gtk_font_chooser_widget_update_marks (fontchooser); } gtk_font_chooser_widget_update_preview_attributes (fontchooser); g_object_notify (G_OBJECT (fontchooser), "font"); g_object_notify (G_OBJECT (fontchooser), "font-desc"); } static void gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser, PangoFontDescription *font_desc) { GtkFontChooserWidgetPrivate *priv = fontchooser->priv; PangoFontMask mask; if (font_desc == NULL) font_desc = pango_font_description_from_string (GTK_FONT_CHOOSER_DEFAULT_FONT_NAME); mask = pango_font_description_get_set_fields (font_desc); if (mask & (PANGO_FONT_MASK_FAMILY | PANGO_FONT_MASK_STYLE | PANGO_FONT_MASK_VARIANT | PANGO_FONT_MASK_WEIGHT | PANGO_FONT_MASK_STRETCH)) { GtkTreeIter iter; if (gtk_font_chooser_widget_find_font (fontchooser, font_desc, &iter)) gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, &iter); else gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, NULL); } else { gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc, &priv->font_iter); } pango_font_description_free (font_desc); } static const gchar* gtk_font_chooser_widget_get_preview_text (GtkFontChooserWidget *fontchooser) { return fontchooser->priv->preview_text; } static void gtk_font_chooser_widget_set_preview_text (GtkFontChooserWidget *fontchooser, const gchar *text) { GtkFontChooserWidgetPrivate *priv = fontchooser->priv; g_free (priv->preview_text); priv->preview_text = g_strdup (text); gtk_entry_set_text (GTK_ENTRY (priv->preview), text); g_object_notify (G_OBJECT (fontchooser), "preview-text"); /* XXX: There's no API to tell the treeview that a column has changed, * so we just */ gtk_widget_queue_draw (priv->family_face_list); } static gboolean gtk_font_chooser_widget_get_show_preview_entry (GtkFontChooserWidget *fontchooser) { return fontchooser->priv->show_preview_entry; } static void gtk_font_chooser_widget_set_show_preview_entry (GtkFontChooserWidget *fontchooser, gboolean show_preview_entry) { GtkFontChooserWidgetPrivate *priv = fontchooser->priv; if (priv->show_preview_entry != show_preview_entry) { fontchooser->priv->show_preview_entry = show_preview_entry; if (show_preview_entry) gtk_widget_show (fontchooser->priv->preview); else gtk_widget_hide (fontchooser->priv->preview); g_object_notify (G_OBJECT (fontchooser), "show-preview-entry"); } } static void gtk_font_chooser_widget_set_font_map (GtkFontChooser *chooser, PangoFontMap *fontmap) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); GtkFontChooserWidgetPrivate *priv = fontchooser->priv; if (g_set_object (&priv->font_map, fontmap)) { PangoContext *context; if (!fontmap) fontmap = pango_cairo_font_map_get_default (); context = gtk_widget_get_pango_context (priv->family_face_list); pango_context_set_font_map (context, fontmap); context = gtk_widget_get_pango_context (priv->preview); pango_context_set_font_map (context, fontmap); gtk_font_chooser_widget_load_fonts (fontchooser, TRUE); } } static PangoFontMap * gtk_font_chooser_widget_get_font_map (GtkFontChooser *chooser) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); GtkFontChooserWidgetPrivate *priv = fontchooser->priv; return priv->font_map; } static void gtk_font_chooser_widget_set_filter_func (GtkFontChooser *chooser, GtkFontFilterFunc filter, gpointer data, GDestroyNotify destroy) { GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser); GtkFontChooserWidgetPrivate *priv = fontchooser->priv; if (priv->filter_data_destroy) priv->filter_data_destroy (priv->filter_data); priv->filter_func = filter; priv->filter_data = data; priv->filter_data_destroy = destroy; gtk_font_chooser_widget_refilter_font_list (fontchooser); } static void gtk_font_chooser_widget_iface_init (GtkFontChooserIface *iface) { iface->get_font_family = gtk_font_chooser_widget_get_family; iface->get_font_face = gtk_font_chooser_widget_get_face; iface->get_font_size = gtk_font_chooser_widget_get_size; iface->set_filter_func = gtk_font_chooser_widget_set_filter_func; iface->set_font_map = gtk_font_chooser_widget_set_font_map; iface->get_font_map = gtk_font_chooser_widget_get_font_map; }