gtk2/gtk/gtkfontchooserwidget.c
Benjamin Otte 58b65d1bf6 fontchooserwidget: Port to listmodels
The port is kind of evil, in that it stores either a PangoFontFamily or a
PangoFontFace in the list, depending on if the fontchooser is configured
to select fonts or faces.
It also does not cache the font description anymore, so more calls to
pango_font_describe() may happen.

If both of these issues turn out problematic, the fontchooser would need
to resurrect GtkDelayedFontDescription again and put objects of that
type through the model.

These changes depend on Pango 1.46's introduction of listmodels and
various new getters, so the dependency has been upgraded.
2020-05-30 19:26:46 -04:00

2067 lines
65 KiB
C

/* GTK - The GIMP Toolkit
* Copyright (C) 2011 Alberto Ruiz <aruiz@gnome.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdlib.h>
#include <glib/gprintf.h>
#include <string.h>
#include <atk/atk.h>
#include "gtkfontchooserwidget.h"
#include "gtkfontchooserwidgetprivate.h"
#include "gtkadjustment.h"
#include "gtkbuildable.h"
#include "gtkbox.h"
#include "gtkbinlayout.h"
#include "gtkcustomfilter.h"
#include "gtkentry.h"
#include "gtkfilter.h"
#include "gtkgrid.h"
#include "gtkfontchooser.h"
#include "gtkfontchooserutils.h"
#include "gtkintl.h"
#include "gtklabel.h"
#include "gtksingleselection.h"
#include "gtkstack.h"
#include "gtkprivate.h"
#include "gtkscale.h"
#include "gtkscrolledwindow.h"
#include "gtksearchentry.h"
#include "gtkspinbutton.h"
#include "gtkstylecontextprivate.h"
#include "gtktextview.h"
#include "gtktreeselection.h"
#include "gtktreeview.h"
#include "gtkwidgetprivate.h"
#include "gtksettings.h"
#include "gtkdialog.h"
#include "gtkradiobutton.h"
#include "gtkcombobox.h"
#include "gtkgestureclick.h"
#include "gtkeventcontrollerscroll.h"
#include "gtkroot.h"
#include "gtkfilterlistmodel.h"
#include "gtkflattenlistmodel.h"
#include "gtkmaplistmodel.h"
#include <hb-ot.h>
#include "language-names.h"
#include "script-names.h"
#include "open-type-layout.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.
*/
typedef struct _GtkFontChooserWidgetClass GtkFontChooserWidgetClass;
struct _GtkFontChooserWidget
{
GtkWidget parent_instance;
GtkWidget *stack;
GtkWidget *grid;
GtkWidget *search_entry;
GtkWidget *family_face_list;
GtkWidget *list_stack;
GtkSingleSelection *selection;
GtkCustomFilter *custom_filter;
GtkFilterListModel *filter_model;
GtkWidget *preview;
GtkWidget *preview2;
GtkWidget *font_name_label;
gchar *preview_text;
gboolean show_preview_entry;
GtkWidget *size_label;
GtkWidget *size_spin;
GtkWidget *size_slider;
GtkWidget *size_slider2;
GtkWidget *axis_grid;
GtkWidget *feature_box;
PangoFontMap *font_map;
PangoFontDescription *font_desc;
char *font_features;
PangoLanguage *language;
GtkFontFilterFunc filter_func;
gpointer filter_data;
GDestroyNotify filter_data_destroy;
guint last_fontconfig_timestamp;
GtkFontChooserLevel level;
GHashTable *axes;
gboolean updating_variations;
GList *feature_items;
GAction *tweak_action;
};
struct _GtkFontChooserWidgetClass
{
GtkWidgetClass parent_class;
};
enum {
PROP_ZERO,
PROP_TWEAK_ACTION
};
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 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);
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_populate_features (GtkFontChooserWidget *fontchooser);
static void gtk_font_chooser_widget_set_level (GtkFontChooserWidget *fontchooser,
GtkFontChooserLevel level);
static GtkFontChooserLevel gtk_font_chooser_widget_get_level (GtkFontChooserWidget *fontchooser);
static void gtk_font_chooser_widget_set_language (GtkFontChooserWidget *fontchooser,
const char *language);
static void update_font_features (GtkFontChooserWidget *fontchooser);
static void gtk_font_chooser_widget_iface_init (GtkFontChooserIface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkFontChooserWidget, gtk_font_chooser_widget, GTK_TYPE_WIDGET,
G_IMPLEMENT_INTERFACE (GTK_TYPE_FONT_CHOOSER,
gtk_font_chooser_widget_iface_init))
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;
case GTK_FONT_CHOOSER_PROP_LEVEL:
gtk_font_chooser_widget_set_level (fontchooser, g_value_get_flags (value));
break;
case GTK_FONT_CHOOSER_PROP_LANGUAGE:
gtk_font_chooser_widget_set_language (fontchooser, g_value_get_string (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 PROP_TWEAK_ACTION:
g_value_set_object (value, G_OBJECT (fontchooser->tweak_action));
break;
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;
case GTK_FONT_CHOOSER_PROP_LEVEL:
g_value_set_flags (value, gtk_font_chooser_widget_get_level (fontchooser));
break;
case GTK_FONT_CHOOSER_PROP_FONT_FEATURES:
g_value_set_string (value, fontchooser->font_features);
break;
case GTK_FONT_CHOOSER_PROP_LANGUAGE:
g_value_set_string (value, pango_language_to_string (fontchooser->language));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
stop_search_cb (GtkSearchEntry *entry,
GtkFontChooserWidget *fc)
{
if (gtk_editable_get_text (GTK_EDITABLE (entry))[0] != 0)
gtk_editable_set_text (GTK_EDITABLE (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;
PangoFontDescription *font_desc;
gdouble size = gtk_adjustment_get_value (adjustment);
font_desc = pango_font_description_new ();
if (pango_font_description_get_size_is_absolute (fontchooser->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_editable_set_text (GTK_EDITABLE (spin), text);
g_free (text);
return TRUE;
}
static void
gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *self)
{
GtkAdjustment *adj, *spin_adj;
const int *sizes;
gint *font_sizes;
gint i, n_sizes;
gdouble value, spin_value;
gpointer item;
item = gtk_single_selection_get_selected_item (self->selection);
if (item)
{
PangoFontFace *face;
if (PANGO_IS_FONT_FAMILY (item))
face = pango_font_family_get_face (item, NULL);
else
face = item;
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;
}
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 (self->size_slider));
gtk_scale_clear_marks (GTK_SCALE (self->size_slider2));
adj = gtk_range_get_adjustment (GTK_RANGE (self->size_slider));
spin_adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (self->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, self);
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, self);
for (i = 0; i < n_sizes; i++)
{
gtk_scale_add_mark (GTK_SCALE (self->size_slider),
sizes[i],
GTK_POS_BOTTOM, NULL);
gtk_scale_add_mark (GTK_SCALE (self->size_slider2),
sizes[i],
GTK_POS_BOTTOM, NULL);
}
g_free (font_sizes);
}
static void
row_activated_cb (GtkWidget *view,
guint pos,
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
resize_by_scroll_cb (GtkEventControllerScroll *controller,
double dx,
double dy,
gpointer user_data)
{
GtkFontChooserWidget *self = user_data;
GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (self->size_spin));
gtk_adjustment_set_value (adj,
gtk_adjustment_get_value (adj) +
gtk_adjustment_get_step_increment (adj) * dx);
}
static void
selection_changed_cb (GtkSingleSelection *selection,
GParamSpec *pspec,
GtkFontChooserWidget *self)
{
gpointer item;
item = gtk_single_selection_get_selected_item (selection);
if (item)
{
PangoFontFace *face;
PangoFontDescription *desc;
if (PANGO_IS_FONT_FAMILY (item))
face = pango_font_family_get_face (item, NULL);
else
face = item;
desc = pango_font_face_describe (face);
pango_font_description_set_variations (self->font_desc, NULL);
gtk_font_chooser_widget_merge_font_desc (self, desc);
pango_font_description_free (desc);
g_simple_action_set_enabled (G_SIMPLE_ACTION (self->tweak_action), TRUE);
}
else
{
g_simple_action_set_state (G_SIMPLE_ACTION (self->tweak_action), g_variant_new_boolean (FALSE));
g_simple_action_set_enabled (G_SIMPLE_ACTION (self->tweak_action), FALSE);
}
g_object_notify (G_OBJECT (self), "font");
g_object_notify (G_OBJECT (self), "font-desc");
}
static char *
get_font_name (GObject *ignore,
gpointer item)
{
if (item == NULL)
return NULL;
if (PANGO_IS_FONT_FACE (item))
{
return g_strconcat (pango_font_family_get_name (pango_font_face_get_family (item)),
" ",
pango_font_face_get_face_name (item),
NULL);
}
else
{
return g_strdup (pango_font_family_get_name (item));
}
}
static PangoAttrList *
get_font_attributes (GObject *ignore,
gpointer item)
{
PangoAttribute *attribute;
PangoAttrList *attrs;
attrs = pango_attr_list_new ();
if (item)
{
PangoFontFace *face;
PangoFontDescription *font_desc;
if (PANGO_IS_FONT_FAMILY (item))
face = pango_font_family_get_face (item, NULL);
else
face = item;
font_desc = pango_font_face_describe (face);
attribute = pango_attr_font_desc_new (font_desc);
pango_attr_list_insert (attrs, attribute);
}
return attrs;
}
static void
gtk_font_chooser_widget_update_preview_attributes (GtkFontChooserWidget *fontchooser)
{
PangoAttrList *attrs;
attrs = pango_attr_list_new ();
/* Prevent font fallback */
pango_attr_list_insert (attrs, pango_attr_fallback_new (FALSE));
/* Force current font and features */
pango_attr_list_insert (attrs, pango_attr_font_desc_new (fontchooser->font_desc));
if (fontchooser->font_features)
pango_attr_list_insert (attrs, pango_attr_font_features_new (fontchooser->font_features));
if (fontchooser->language)
pango_attr_list_insert (attrs, pango_attr_language_new (fontchooser->language));
gtk_entry_set_attributes (GTK_ENTRY (fontchooser->preview), attrs);
pango_attr_list_unref (attrs);
}
static void
rows_changed_cb (GtkFontChooserWidget *self)
{
const char *page;
if (g_list_model_get_n_items (G_LIST_MODEL (self->selection)) == 0)
page = "empty";
else
page = "list";
if (strcmp (gtk_stack_get_visible_child_name (GTK_STACK (self->list_stack)), page) != 0)
gtk_stack_set_visible_child_name (GTK_STACK (self->list_stack), page);
}
static void
update_key_capture (GtkWidget *chooser)
{
GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser);
GtkWidget *capture_widget;
if (gtk_widget_get_mapped (chooser) &&
g_str_equal (gtk_stack_get_visible_child_name (GTK_STACK (fontchooser->stack)), "list"))
{
GtkWidget *toplevel;
GtkWidget *focus;
toplevel = GTK_WIDGET (gtk_widget_get_root (chooser));
focus = gtk_root_get_focus (GTK_ROOT (toplevel));
if (GTK_IS_EDITABLE (focus) && focus != fontchooser->search_entry)
capture_widget = NULL;
else
capture_widget = chooser;
}
else
capture_widget = NULL;
gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (fontchooser->search_entry),
capture_widget);
}
static void
gtk_font_chooser_widget_map (GtkWidget *widget)
{
GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (widget);
gtk_editable_set_text (GTK_EDITABLE (fontchooser->search_entry), "");
gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->stack), "list");
g_simple_action_set_state (G_SIMPLE_ACTION (fontchooser->tweak_action), g_variant_new_boolean (FALSE));
GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->map (widget);
update_key_capture (widget);
}
static void
gtk_font_chooser_widget_unmap (GtkWidget *widget)
{
update_key_capture (widget);
GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->unmap (widget);
}
static void
gtk_font_chooser_widget_root (GtkWidget *widget)
{
GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->root (widget);
g_signal_connect_swapped (gtk_widget_get_root (widget), "notify::focus-widget",
G_CALLBACK (update_key_capture), widget);
}
static void
gtk_font_chooser_widget_unroot (GtkWidget *widget)
{
g_signal_handlers_disconnect_by_func (gtk_widget_get_root (widget),
update_key_capture, widget);
GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->unroot (widget);
}
static void
gtk_font_chooser_widget_dispose (GObject *object)
{
GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (object);
self->filter_func = NULL;
g_clear_pointer (&self->filter_data, self->filter_data_destroy);
g_clear_pointer (&self->stack, gtk_widget_unparent);
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);
GParamSpec *pspec;
g_type_ensure (G_TYPE_THEMED_ICON);
widget_class->root = gtk_font_chooser_widget_root;
widget_class->unroot = gtk_font_chooser_widget_unroot;
widget_class->map = gtk_font_chooser_widget_map;
widget_class->unmap = gtk_font_chooser_widget_unmap;
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;
/**
* GtkFontChooserWidget:tweak-action:
*
* A toggle action that can be used to switch to the tweak page
* of the font chooser widget, which lets the user tweak the
* OpenType features and variation axes of the selected font.
*
* The action will be enabled or disabled depending on whether
* the selected font has any features or axes.
*/
pspec = g_param_spec_object ("tweak-action",
P_("The tweak action"),
P_("The toggle action to switch to the tweak page"),
G_TYPE_ACTION,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (gobject_class, PROP_TWEAK_ACTION, pspec);
_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 (widget_class, GtkFontChooserWidget, search_entry);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, family_face_list);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, list_stack);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, filter_model);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, selection);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, custom_filter);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, preview);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, preview2);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_label);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_spin);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_slider);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_slider2);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, stack);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, grid);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, font_name_label);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, feature_box);
gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, axis_grid);
gtk_widget_class_bind_template_callback (widget_class, get_font_name);
gtk_widget_class_bind_template_callback (widget_class, get_font_attributes);
gtk_widget_class_bind_template_callback (widget_class, stop_search_cb);
gtk_widget_class_bind_template_callback (widget_class, row_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, rows_changed_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_bind_template_callback (widget_class, selection_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, resize_by_scroll_cb);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("fontchooser"));
}
static void
change_tweak (GSimpleAction *action,
GVariant *state,
gpointer data)
{
GtkFontChooserWidget *fontchooser = data;
gboolean tweak = g_variant_get_boolean (state);
if (tweak)
{
gtk_entry_grab_focus_without_selecting (GTK_ENTRY (fontchooser->preview2));
gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->stack), "tweaks");
}
else
{
gtk_widget_grab_focus (fontchooser->search_entry);
gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->stack), "list");
}
g_simple_action_set_state (action, state);
}
typedef struct {
guint32 tag;
GtkAdjustment *adjustment;
GtkWidget *label;
GtkWidget *scale;
GtkWidget *spin;
GtkWidget *fontchooser;
} Axis;
static guint
axis_hash (gconstpointer v)
{
const Axis *a = v;
return a->tag;
}
static gboolean
axis_equal (gconstpointer v1, gconstpointer v2)
{
const Axis *a1 = v1;
const Axis *a2 = v2;
return a1->tag == a2->tag;
}
static void
axis_remove (gpointer key,
gpointer value,
gpointer data)
{
GtkFontChooserWidget *fontchooser = data;
Axis *a = value;
gtk_grid_remove (GTK_GRID (fontchooser->axis_grid), a->label);
gtk_grid_remove (GTK_GRID (fontchooser->axis_grid), a->scale);
gtk_grid_remove (GTK_GRID (fontchooser->axis_grid), a->spin);
}
static void
axis_free (gpointer v)
{
Axis *a = v;
g_free (a);
}
static void
update_fontlist (GtkFontChooserWidget *self)
{
PangoFontMap *fontmap;
GListModel *model;
fontmap = self->font_map;
if (!fontmap)
fontmap = pango_cairo_font_map_get_default ();
if ((self->level & GTK_FONT_CHOOSER_LEVEL_STYLE) == 0)
model = g_object_ref (G_LIST_MODEL (fontmap));
else
model = G_LIST_MODEL (gtk_flatten_list_model_new (PANGO_TYPE_FONT_FACE, G_LIST_MODEL (fontmap)));
gtk_filter_list_model_set_model (self->filter_model, model);
g_object_unref (model);
}
static void
gtk_font_chooser_widget_init (GtkFontChooserWidget *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
self->axes = g_hash_table_new_full (axis_hash, axis_equal, NULL, axis_free);
/* Default preview string */
self->preview_text = g_strdup (pango_language_get_sample_string (NULL));
self->show_preview_entry = TRUE;
self->font_desc = pango_font_description_new ();
self->level = GTK_FONT_CHOOSER_LEVEL_FAMILY |
GTK_FONT_CHOOSER_LEVEL_STYLE |
GTK_FONT_CHOOSER_LEVEL_SIZE;
self->language = pango_language_get_default ();
/* Set default preview text */
gtk_editable_set_text (GTK_EDITABLE (self->preview), self->preview_text);
gtk_font_chooser_widget_update_preview_attributes (self);
/* Set the upper values of the spin/scale with G_MAXINT / PANGO_SCALE */
gtk_spin_button_set_range (GTK_SPIN_BUTTON (self->size_spin),
1.0, (gdouble)(G_MAXINT / PANGO_SCALE));
gtk_adjustment_set_upper (gtk_range_get_adjustment (GTK_RANGE (self->size_slider)),
(gdouble)(G_MAXINT / PANGO_SCALE));
self->tweak_action = G_ACTION (g_simple_action_new_stateful ("tweak", NULL, g_variant_new_boolean (FALSE)));
g_signal_connect (self->tweak_action, "change-state", G_CALLBACK (change_tweak), self);
update_fontlist (self);
/* Load data and set initial style-dependent parameters */
gtk_font_chooser_widget_populate_features (self);
gtk_font_chooser_widget_take_font_desc (self, NULL);
}
/**
* gtk_font_chooser_widget_new:
*
* Creates a new #GtkFontChooserWidget.
*
* Returns: a new #GtkFontChooserWidget
*/
GtkWidget *
gtk_font_chooser_widget_new (void)
{
return g_object_new (GTK_TYPE_FONT_CHOOSER_WIDGET, NULL);
}
static void
gtk_font_chooser_widget_finalize (GObject *object)
{
GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (object);
if (fontchooser->font_desc)
pango_font_description_free (fontchooser->font_desc);
if (fontchooser->filter_data_destroy)
fontchooser->filter_data_destroy (fontchooser->filter_data);
g_free (fontchooser->preview_text);
g_clear_object (&fontchooser->font_map);
g_object_unref (fontchooser->tweak_action);
g_list_free_full (fontchooser->feature_items, g_free);
g_hash_table_unref (fontchooser->axes);
g_free (fontchooser->font_features);
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 void
gtk_font_chooser_widget_ensure_matching_selection (GtkFontChooserWidget *self)
{
const char *desc_family;
guint i, n;
desc_family = pango_font_description_get_family (self->font_desc);
if (desc_family == NULL)
{
gtk_single_selection_set_selected (self->selection, GTK_INVALID_LIST_POSITION);
return;
}
n = g_list_model_get_n_items (G_LIST_MODEL (self->selection));
for (i = 0; i < n; i++)
{
gpointer item = g_list_model_get_item (G_LIST_MODEL (self->selection), i);
PangoFontFace *face;
PangoFontFamily *family;
PangoFontDescription *merged;
if (PANGO_IS_FONT_FAMILY (item))
{
family = item;
face = pango_font_family_get_face (family, NULL);
}
else
{
face = item;
family = pango_font_face_get_family (face);
}
if (!my_pango_font_family_equal (desc_family, pango_font_family_get_name (family)))
{
g_object_unref (face);
continue;
}
merged = pango_font_face_describe (face);
pango_font_description_merge_static (merged, self->font_desc, FALSE);
g_object_unref (face);
if (pango_font_description_equal (merged, self->font_desc))
{
pango_font_description_free (merged);
break;
}
pango_font_description_free (merged);
}
gtk_single_selection_set_selected (self->selection, i);
}
static PangoFontFace *
gtk_font_chooser_widget_get_face (GtkFontChooser *chooser)
{
GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (chooser);
gpointer item;
item = gtk_single_selection_get_selected_item (self->selection);
if (PANGO_IS_FONT_FAMILY (item))
return pango_font_family_get_face (item, NULL);
else
return item;
}
static PangoFontFamily *
gtk_font_chooser_widget_get_family (GtkFontChooser *chooser)
{
GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (chooser);
gpointer item;
item = gtk_single_selection_get_selected_item (self->selection);
if (item == NULL)
return NULL;
if (PANGO_IS_FONT_FAMILY (item))
return item;
else
return pango_font_face_get_family (item);
}
static gint
gtk_font_chooser_widget_get_size (GtkFontChooser *chooser)
{
GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser);
PangoFontDescription *desc = gtk_font_chooser_widget_get_font_desc (fontchooser);
if (desc)
return pango_font_description_get_size (desc);
return -1;
}
static gchar *
gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser)
{
PangoFontDescription *desc = gtk_font_chooser_widget_get_font_desc (fontchooser);
if (desc)
return pango_font_description_to_string (desc);
return NULL;
}
static PangoFontDescription *
gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *self)
{
if (gtk_single_selection_get_selected_item (self->selection))
return self->font_desc;
return NULL;
}
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);
}
/* OpenType variations */
static void
add_font_variations (GtkFontChooserWidget *fontchooser,
GString *s)
{
GHashTableIter iter;
Axis *axis;
const char *sep = "";
char buf[G_ASCII_DTOSTR_BUF_SIZE];
g_hash_table_iter_init (&iter, fontchooser->axes);
while (g_hash_table_iter_next (&iter, (gpointer *)NULL, (gpointer *)&axis))
{
char tag[5];
double value;
tag[0] = (axis->tag >> 24) & 0xff;
tag[1] = (axis->tag >> 16) & 0xff;
tag[2] = (axis->tag >> 8) & 0xff;
tag[3] = (axis->tag >> 0) & 0xff;
tag[4] = '\0';
value = gtk_adjustment_get_value (axis->adjustment);
g_string_append_printf (s, "%s%s=%s", sep, tag, g_ascii_dtostr (buf, sizeof(buf), value));
sep = ",";
}
}
static void
adjustment_changed (GtkAdjustment *adjustment,
Axis *axis)
{
GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (axis->fontchooser);
PangoFontDescription *font_desc;
GString *s;
fontchooser->updating_variations = TRUE;
s = g_string_new ("");
add_font_variations (fontchooser, s);
if (s->len > 0)
{
font_desc = pango_font_description_new ();
pango_font_description_set_variations (font_desc, s->str);
gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc);
}
g_string_free (s, TRUE);
fontchooser->updating_variations = FALSE;
}
static gboolean
should_show_axis (hb_ot_var_axis_info_t *ax)
{
if (ax->flags & HB_OT_VAR_AXIS_FLAG_HIDDEN)
return FALSE;
return TRUE;
}
static gboolean
is_named_instance (hb_font_t *font)
{
/* FIXME */
return FALSE;
}
static struct {
guint32 tag;
const char *name;
} axis_names[] = {
{ HB_OT_TAG_VAR_AXIS_WIDTH, N_("Width") },
{ HB_OT_TAG_VAR_AXIS_WEIGHT, N_("Weight") },
{ HB_OT_TAG_VAR_AXIS_ITALIC, N_("Italic") },
{ HB_OT_TAG_VAR_AXIS_SLANT, N_("Slant") },
{ HB_OT_TAG_VAR_AXIS_OPTICAL_SIZE, N_("Optical Size") },
};
static gboolean
add_axis (GtkFontChooserWidget *fontchooser,
hb_font_t *hb_font,
hb_ot_var_axis_info_t *ax,
int value,
int row)
{
hb_face_t *hb_face;
Axis *axis;
const char *name;
char buffer[20];
unsigned int buffer_len = 20;
int i;
hb_face = hb_font_get_face (hb_font);
axis = g_new (Axis, 1);
axis->tag = ax->tag;
axis->fontchooser = GTK_WIDGET (fontchooser);
hb_ot_name_get_utf8 (hb_face, ax->name_id, HB_LANGUAGE_INVALID, &buffer_len, buffer);
name = buffer;
for (i = 0; i < G_N_ELEMENTS (axis_names); i++)
{
if (axis_names[i].tag == ax->tag)
{
name = _(axis_names[i].name);
break;
}
}
axis->label = gtk_label_new (name);
gtk_widget_set_halign (axis->label, GTK_ALIGN_START);
gtk_widget_set_valign (axis->label, GTK_ALIGN_BASELINE);
gtk_grid_attach (GTK_GRID (fontchooser->axis_grid), axis->label, 0, row, 1, 1);
axis->adjustment = gtk_adjustment_new ((double)value,
(double)ax->min_value,
(double)ax->max_value,
1.0, 10.0, 0.0);
axis->scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, axis->adjustment);
gtk_scale_add_mark (GTK_SCALE (axis->scale), (double)ax->default_value, GTK_POS_TOP, NULL);
gtk_widget_set_valign (axis->scale, GTK_ALIGN_BASELINE);
gtk_widget_set_hexpand (axis->scale, TRUE);
gtk_widget_set_size_request (axis->scale, 100, -1);
gtk_scale_set_draw_value (GTK_SCALE (axis->scale), FALSE);
gtk_grid_attach (GTK_GRID (fontchooser->axis_grid), axis->scale, 1, row, 1, 1);
axis->spin = gtk_spin_button_new (axis->adjustment, 0, 0);
g_signal_connect (axis->spin, "output", G_CALLBACK (output_cb), fontchooser);
gtk_widget_set_valign (axis->spin, GTK_ALIGN_BASELINE);
gtk_grid_attach (GTK_GRID (fontchooser->axis_grid), axis->spin, 2, row, 1, 1);
g_hash_table_add (fontchooser->axes, axis);
adjustment_changed (axis->adjustment, axis);
g_signal_connect (axis->adjustment, "value-changed", G_CALLBACK (adjustment_changed), axis);
if (is_named_instance (hb_font) || !should_show_axis (ax))
{
gtk_widget_hide (axis->label);
gtk_widget_hide (axis->scale);
gtk_widget_hide (axis->spin);
return FALSE;
}
return TRUE;
}
/* FIXME: This doesn't work if the font has an avar table */
static float
denorm_coord (hb_ot_var_axis_info_t *axis, int coord)
{
float r = coord / 16384.0;
if (coord < 0)
return axis->default_value + r * (axis->default_value - axis->min_value);
else
return axis->default_value + r * (axis->max_value - axis->default_value);
}
static gboolean
gtk_font_chooser_widget_update_font_variations (GtkFontChooserWidget *fontchooser)
{
PangoFont *pango_font;
hb_font_t *hb_font;
hb_face_t *hb_face;
const int *coords;
unsigned int n_coords;
unsigned int n_axes;
hb_ot_var_axis_info_t *axes;
gboolean has_axis = FALSE;
int i;
if (fontchooser->updating_variations)
return FALSE;
g_hash_table_foreach (fontchooser->axes, axis_remove, fontchooser);
g_hash_table_remove_all (fontchooser->axes);
if ((fontchooser->level & GTK_FONT_CHOOSER_LEVEL_VARIATIONS) == 0)
return FALSE;
pango_font = pango_context_load_font (gtk_widget_get_pango_context (GTK_WIDGET (fontchooser)),
fontchooser->font_desc);
hb_font = pango_font_get_hb_font (pango_font);
hb_face = hb_font_get_face (hb_font);
if (!hb_ot_var_has_data (hb_face))
return FALSE;
coords = hb_font_get_var_coords_normalized (hb_font, &n_coords);
n_axes = hb_ot_var_get_axis_count (hb_face);
axes = g_new0 (hb_ot_var_axis_info_t, n_axes);
hb_ot_var_get_axis_infos (hb_face, 0, &n_axes, axes);
for (i = 0; i < n_axes; i++)
{
float value;
if (coords && i < n_coords)
value = denorm_coord (&axes[i], coords[i]);
else
value = axes[i].default_value;
if (add_axis (fontchooser, hb_font, &axes[i], value, i + 4))
has_axis = TRUE;
}
g_free (axes);
g_object_unref (pango_font);
return has_axis;
}
/* OpenType features */
/* look for a lang / script combination that matches the
* language property and is supported by the hb_face. If
* none is found, return the default lang / script tags.
*/
static void
find_language_and_script (GtkFontChooserWidget *fontchooser,
hb_face_t *hb_face,
hb_tag_t *lang_tag,
hb_tag_t *script_tag)
{
gint i, j, k;
hb_tag_t scripts[80];
unsigned int n_scripts;
unsigned int count;
hb_tag_t table[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
hb_language_t lang;
const char *langname, *p;
langname = pango_language_to_string (fontchooser->language);
p = strchr (langname, '-');
lang = hb_language_from_string (langname, p ? p - langname : -1);
n_scripts = 0;
for (i = 0; i < 2; i++)
{
count = G_N_ELEMENTS (scripts);
hb_ot_layout_table_get_script_tags (hb_face, table[i], n_scripts, &count, scripts);
n_scripts += count;
}
for (j = 0; j < n_scripts; j++)
{
hb_tag_t languages[80];
unsigned int n_languages;
n_languages = 0;
for (i = 0; i < 2; i++)
{
count = G_N_ELEMENTS (languages);
hb_ot_layout_script_get_language_tags (hb_face, table[i], j, n_languages, &count, languages);
n_languages += count;
}
for (k = 0; k < n_languages; k++)
{
if (lang == hb_ot_tag_to_language (languages[k]))
{
*script_tag = scripts[j];
*lang_tag = languages[k];
return;
}
}
}
*lang_tag = HB_OT_TAG_DEFAULT_LANGUAGE;
*script_tag = HB_OT_TAG_DEFAULT_SCRIPT;
}
typedef struct {
hb_tag_t tag;
const char *name;
GtkWidget *top;
GtkWidget *feat;
GtkWidget *example;
} FeatureItem;
static const char *
get_feature_display_name (hb_tag_t tag)
{
int i;
for (i = 0; i < G_N_ELEMENTS (open_type_layout_features); i++)
{
if (tag == open_type_layout_features[i].tag)
return g_dpgettext2 (NULL, "OpenType layout", open_type_layout_features[i].name);
}
return NULL;
}
static void
set_inconsistent (GtkCheckButton *button,
gboolean inconsistent)
{
gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (button), inconsistent);
gtk_widget_set_opacity (gtk_widget_get_first_child (GTK_WIDGET (button)), inconsistent ? 0.0 : 1.0);
}
static void
feat_clicked (GtkWidget *feat,
gpointer data)
{
g_signal_handlers_block_by_func (feat, feat_clicked, NULL);
if (gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (feat)))
{
set_inconsistent (GTK_CHECK_BUTTON (feat), FALSE);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (feat), TRUE);
}
g_signal_handlers_unblock_by_func (feat, feat_clicked, NULL);
}
static void
feat_pressed (GtkGesture *gesture,
int n_press,
double x,
double y,
GtkWidget *feat)
{
gboolean inconsistent;
inconsistent = gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (feat));
set_inconsistent (GTK_CHECK_BUTTON (feat), !inconsistent);
}
static char *
find_affected_text (hb_tag_t feature_tag,
hb_font_t *hb_font,
hb_tag_t script_tag,
hb_tag_t lang_tag,
int max_chars)
{
hb_face_t *hb_face;
unsigned int script_index = 0;
unsigned int lang_index = 0;
unsigned int feature_index = 0;
GString *chars;
hb_face = hb_font_get_face (hb_font);
chars = g_string_new ("");
hb_ot_layout_table_find_script (hb_face, HB_OT_TAG_GSUB, script_tag, &script_index);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
hb_ot_layout_script_find_language (hb_face, HB_OT_TAG_GSUB, script_index, lang_tag, &lang_index);
G_GNUC_END_IGNORE_DEPRECATIONS
if (hb_ot_layout_language_find_feature (hb_face, HB_OT_TAG_GSUB, script_index, lang_index, feature_tag, &feature_index))
{
unsigned int lookup_indexes[32];
unsigned int lookup_count = 32;
int count;
int n_chars = 0;
count = hb_ot_layout_feature_get_lookups (hb_face,
HB_OT_TAG_GSUB,
feature_index,
0,
&lookup_count,
lookup_indexes);
if (count > 0)
{
hb_set_t* glyphs_before = NULL;
hb_set_t* glyphs_input = NULL;
hb_set_t* glyphs_after = NULL;
hb_set_t* glyphs_output = NULL;
hb_codepoint_t gid;
glyphs_input = hb_set_create ();
// XXX For now, just look at first index
hb_ot_layout_lookup_collect_glyphs (hb_face,
HB_OT_TAG_GSUB,
lookup_indexes[0],
glyphs_before,
glyphs_input,
glyphs_after,
glyphs_output);
gid = -1;
while (hb_set_next (glyphs_input, &gid)) {
hb_codepoint_t ch;
if (n_chars == max_chars)
{
g_string_append (chars, "");
break;
}
for (ch = 0; ch < 0xffff; ch++) {
hb_codepoint_t glyph = 0;
hb_font_get_nominal_glyph (hb_font, ch, &glyph);
if (glyph == gid) {
g_string_append_unichar (chars, (gunichar)ch);
n_chars++;
break;
}
}
}
hb_set_destroy (glyphs_input);
}
}
return g_string_free (chars, FALSE);
}
static void
update_feature_example (FeatureItem *item,
hb_font_t *hb_font,
hb_tag_t script_tag,
hb_tag_t lang_tag,
PangoFontDescription *font_desc)
{
const char *letter_case[] = { "smcp", "c2sc", "pcap", "c2pc", "unic", "cpsp", "case", NULL };
const char *number_case[] = { "xxxx", "lnum", "onum", NULL };
const char *number_spacing[] = { "xxxx", "pnum", "tnum", NULL };
const char *number_formatting[] = { "zero", "nalt", "frac", NULL };
const char *char_variants[] = {
"swsh", "cswh", "calt", "falt", "hist", "salt", "jalt", "titl", "rand",
"ss01", "ss02", "ss03", "ss04", "ss05", "ss06", "ss07", "ss08", "ss09", "ss10",
"ss11", "ss12", "ss13", "ss14", "ss15", "ss16", "ss17", "ss18", "ss19", "ss20",
NULL };
if (g_strv_contains (number_case, item->name) ||
g_strv_contains (number_spacing, item->name))
{
PangoAttrList *attrs;
PangoAttribute *attr;
PangoFontDescription *desc;
char *str;
attrs = pango_attr_list_new ();
desc = pango_font_description_copy (font_desc);
pango_font_description_unset_fields (desc, PANGO_FONT_MASK_SIZE);
pango_attr_list_insert (attrs, pango_attr_font_desc_new (desc));
pango_font_description_free (desc);
str = g_strconcat (item->name, " 1", NULL);
attr = pango_attr_font_features_new (str);
pango_attr_list_insert (attrs, attr);
gtk_label_set_text (GTK_LABEL (item->example), "0123456789");
gtk_label_set_attributes (GTK_LABEL (item->example), attrs);
pango_attr_list_unref (attrs);
}
else if (g_strv_contains (letter_case, item->name) ||
g_strv_contains (number_formatting, item->name) ||
g_strv_contains (char_variants, item->name))
{
char *input = NULL;
char *text;
if (strcmp (item->name, "case") == 0)
input = g_strdup ("A-B[Cq]");
else if (g_strv_contains (letter_case, item->name))
input = g_strdup ("AaBbCc…");
else if (strcmp (item->name, "zero") == 0)
input = g_strdup ("0");
else if (strcmp (item->name, "frac") == 0)
input = g_strdup ("1/2 2/3 7/8");
else if (strcmp (item->name, "nalt") == 0)
input = find_affected_text (item->tag, hb_font, script_tag, lang_tag, 3);
else
input = find_affected_text (item->tag, hb_font, script_tag, lang_tag, 10);
if (input[0] != '\0')
{
PangoAttrList *attrs;
PangoAttribute *attr;
PangoFontDescription *desc;
char *str;
text = g_strconcat (input, "", input, NULL);
attrs = pango_attr_list_new ();
desc = pango_font_description_copy (font_desc);
pango_font_description_unset_fields (desc, PANGO_FONT_MASK_SIZE);
pango_attr_list_insert (attrs, pango_attr_font_desc_new (desc));
pango_font_description_free (desc);
str = g_strconcat (item->name, " 0", NULL);
attr = pango_attr_font_features_new (str);
attr->start_index = 0;
attr->end_index = strlen (input);
pango_attr_list_insert (attrs, attr);
str = g_strconcat (item->name, " 1", NULL);
attr = pango_attr_font_features_new (str);
attr->start_index = strlen (input) + strlen ("");
attr->end_index = attr->start_index + strlen (input);
pango_attr_list_insert (attrs, attr);
gtk_label_set_text (GTK_LABEL (item->example), text);
gtk_label_set_attributes (GTK_LABEL (item->example), attrs);
g_free (text);
pango_attr_list_unref (attrs);
}
else
gtk_label_set_markup (GTK_LABEL (item->example), "");
g_free (input);
}
}
static void
add_check_group (GtkFontChooserWidget *fontchooser,
const char *title,
const char **tags)
{
GtkWidget *label;
GtkWidget *group;
PangoAttrList *attrs;
int i;
group = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_halign (group, GTK_ALIGN_FILL);
label = gtk_label_new (title);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_widget_set_halign (label, GTK_ALIGN_START);
g_object_set (label, "margin-top", 10, "margin-bottom", 10, NULL);
attrs = pango_attr_list_new ();
pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
gtk_label_set_attributes (GTK_LABEL (label), attrs);
pango_attr_list_unref (attrs);
gtk_box_append (GTK_BOX (group), label);
for (i = 0; tags[i]; i++)
{
hb_tag_t tag;
GtkWidget *feat;
FeatureItem *item;
GtkGesture *gesture;
GtkWidget *box;
GtkWidget *example;
tag = hb_tag_from_string (tags[i], -1);
feat = gtk_check_button_new_with_label (get_feature_display_name (tag));
set_inconsistent (GTK_CHECK_BUTTON (feat), TRUE);
g_signal_connect_swapped (feat, "notify::active", G_CALLBACK (update_font_features), fontchooser);
g_signal_connect_swapped (feat, "notify::inconsistent", G_CALLBACK (update_font_features), fontchooser);
g_signal_connect (feat, "clicked", G_CALLBACK (feat_clicked), NULL);
gesture = gtk_gesture_click_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_SECONDARY);
g_signal_connect (gesture, "pressed", G_CALLBACK (feat_pressed), feat);
gtk_widget_add_controller (feat, GTK_EVENT_CONTROLLER (gesture));
example = gtk_label_new ("");
gtk_label_set_selectable (GTK_LABEL (example), TRUE);
gtk_widget_set_halign (example, GTK_ALIGN_START);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
gtk_box_append (GTK_BOX (box), feat);
gtk_box_append (GTK_BOX (box), example);
gtk_box_append (GTK_BOX (group), box);
item = g_new (FeatureItem, 1);
item->name = tags[i];
item->tag = tag;
item->top = box;
item->feat = feat;
item->example = example;
fontchooser->feature_items = g_list_prepend (fontchooser->feature_items, item);
}
gtk_box_append (GTK_BOX (fontchooser->feature_box), group);
}
static void
add_radio_group (GtkFontChooserWidget *fontchooser,
const char *title,
const char **tags)
{
GtkWidget *label;
GtkWidget *group;
int i;
GtkWidget *group_button = NULL;
PangoAttrList *attrs;
group = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_halign (group, GTK_ALIGN_FILL);
label = gtk_label_new (title);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_widget_set_halign (label, GTK_ALIGN_START);
g_object_set (label, "margin-top", 10, "margin-bottom", 10, NULL);
attrs = pango_attr_list_new ();
pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
gtk_label_set_attributes (GTK_LABEL (label), attrs);
pango_attr_list_unref (attrs);
gtk_box_append (GTK_BOX (group), label);
for (i = 0; tags[i]; i++)
{
hb_tag_t tag;
GtkWidget *feat;
FeatureItem *item;
const char *name;
GtkWidget *box;
GtkWidget *example;
tag = hb_tag_from_string (tags[i], -1);
name = get_feature_display_name (tag);
feat = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (group_button),
name ? name : _("Default"));
if (group_button == NULL)
group_button = feat;
g_signal_connect_swapped (feat, "notify::active", G_CALLBACK (update_font_features), fontchooser);
g_object_set_data (G_OBJECT (feat), "default", group_button);
example = gtk_label_new ("");
gtk_label_set_selectable (GTK_LABEL (example), TRUE);
gtk_widget_set_halign (example, GTK_ALIGN_START);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
gtk_box_append (GTK_BOX (box), feat);
gtk_box_append (GTK_BOX (box), example);
gtk_box_append (GTK_BOX (group), box);
item = g_new (FeatureItem, 1);
item->name = tags[i];
item->tag = tag;
item->top = box;
item->feat = feat;
item->example = example;
fontchooser->feature_items = g_list_prepend (fontchooser->feature_items, item);
}
gtk_box_append (GTK_BOX (fontchooser->feature_box), group);
}
static void
gtk_font_chooser_widget_populate_features (GtkFontChooserWidget *fontchooser)
{
const char *ligatures[] = { "liga", "dlig", "hlig", "clig", NULL };
const char *letter_case[] = { "smcp", "c2sc", "pcap", "c2pc", "unic", "cpsp", "case", NULL };
const char *number_case[] = { "xxxx", "lnum", "onum", NULL };
const char *number_spacing[] = { "xxxx", "pnum", "tnum", NULL };
const char *number_formatting[] = { "zero", "nalt", "frac", NULL };
const char *char_variants[] = {
"swsh", "cswh", "calt", "falt", "hist", "salt", "jalt", "titl", "rand",
"ss01", "ss02", "ss03", "ss04", "ss05", "ss06", "ss07", "ss08", "ss09", "ss10",
"ss11", "ss12", "ss13", "ss14", "ss15", "ss16", "ss17", "ss18", "ss19", "ss20",
NULL };
add_check_group (fontchooser, _("Ligatures"), ligatures);
add_check_group (fontchooser, _("Letter Case"), letter_case);
add_radio_group (fontchooser, _("Number Case"), number_case);
add_radio_group (fontchooser, _("Number Spacing"), number_spacing);
add_check_group (fontchooser, _("Number Formatting"), number_formatting);
add_check_group (fontchooser, _("Character Variants"), char_variants);
update_font_features (fontchooser);
}
static gboolean
gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser)
{
PangoFont *pango_font;
hb_font_t *hb_font;
hb_tag_t script_tag;
hb_tag_t lang_tag;
guint script_index = 0;
guint lang_index = 0;
int i, j;
GList *l;
gboolean has_feature = FALSE;
for (l = fontchooser->feature_items; l; l = l->next)
{
FeatureItem *item = l->data;
gtk_widget_hide (item->top);
gtk_widget_hide (gtk_widget_get_parent (item->top));
}
if ((fontchooser->level & GTK_FONT_CHOOSER_LEVEL_FEATURES) == 0)
return FALSE;
pango_font = pango_context_load_font (gtk_widget_get_pango_context (GTK_WIDGET (fontchooser)),
fontchooser->font_desc);
hb_font = pango_font_get_hb_font (pango_font);
if (hb_font)
{
hb_tag_t table[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
hb_face_t *hb_face;
hb_tag_t features[80];
unsigned int count;
unsigned int n_features;
hb_face = hb_font_get_face (hb_font);
find_language_and_script (fontchooser, hb_face, &lang_tag, &script_tag);
n_features = 0;
for (i = 0; i < 2; i++)
{
hb_ot_layout_table_find_script (hb_face, table[i], script_tag, &script_index);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
hb_ot_layout_script_find_language (hb_face, table[i], script_index, lang_tag, &lang_index);
G_GNUC_END_IGNORE_DEPRECATIONS
count = G_N_ELEMENTS (features);
hb_ot_layout_language_get_feature_tags (hb_face,
table[i],
script_index,
lang_index,
n_features,
&count,
features);
n_features += count;
}
for (j = 0; j < n_features; j++)
{
for (l = fontchooser->feature_items; l; l = l->next)
{
FeatureItem *item = l->data;
if (item->tag != features[j])
continue;
has_feature = TRUE;
gtk_widget_show (item->top);
gtk_widget_show (gtk_widget_get_parent (item->top));
update_feature_example (item, hb_font, script_tag, lang_tag, fontchooser->font_desc);
if (GTK_IS_RADIO_BUTTON (item->feat))
{
GtkWidget *def = GTK_WIDGET (g_object_get_data (G_OBJECT (item->feat), "default"));
gtk_widget_show (gtk_widget_get_parent (def));
}
else if (GTK_IS_CHECK_BUTTON (item->feat))
{
set_inconsistent (GTK_CHECK_BUTTON (item->feat), TRUE);
}
}
}
}
g_object_unref (pango_font);
return has_feature;
}
static void
update_font_features (GtkFontChooserWidget *fontchooser)
{
GString *s;
GList *l;
s = g_string_new ("");
for (l = fontchooser->feature_items; l; l = l->next)
{
FeatureItem *item = l->data;
if (!gtk_widget_is_sensitive (item->feat))
continue;
if (GTK_IS_RADIO_BUTTON (item->feat))
{
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (item->feat)) &&
strcmp (item->name, "xxxx") != 0)
{
g_string_append_printf (s, "%s\"%s\" %d", s->len > 0 ? ", " : "", item->name, 1);
}
}
else if (GTK_IS_CHECK_BUTTON (item->feat))
{
if (gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (item->feat)))
continue;
g_string_append_printf (s, "%s\"%s\" %d",
s->len > 0 ? ", " : "", item->name,
gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (item->feat)));
}
}
if (g_strcmp0 (fontchooser->font_features, s->str) != 0)
{
g_free (fontchooser->font_features);
fontchooser->font_features = g_string_free (s, FALSE);
g_object_notify (G_OBJECT (fontchooser), "font-features");
}
else
g_string_free (s, TRUE);
gtk_font_chooser_widget_update_preview_attributes (fontchooser);
}
static void
gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget *fontchooser,
const PangoFontDescription *font_desc)
{
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, fontchooser->font_desc))
return;
pango_font_description_merge (fontchooser->font_desc, font_desc, TRUE);
if (mask & PANGO_FONT_MASK_SIZE)
{
double font_size = (double) pango_font_description_get_size (fontchooser->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 (fontchooser->size_slider), font_size);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (fontchooser->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))
{
gboolean has_tweak = FALSE;
gtk_font_chooser_widget_update_marks (fontchooser);
if (gtk_font_chooser_widget_update_font_features (fontchooser))
has_tweak = TRUE;
if (gtk_font_chooser_widget_update_font_variations (fontchooser))
has_tweak = TRUE;
g_simple_action_set_enabled (G_SIMPLE_ACTION (fontchooser->tweak_action), has_tweak);
}
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)
{
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);
gtk_font_chooser_widget_merge_font_desc (fontchooser, font_desc);
if (mask & (PANGO_FONT_MASK_FAMILY | PANGO_FONT_MASK_STYLE | PANGO_FONT_MASK_VARIANT |
PANGO_FONT_MASK_WEIGHT | PANGO_FONT_MASK_STRETCH))
{
gtk_font_chooser_widget_ensure_matching_selection (fontchooser);
}
pango_font_description_free (font_desc);
}
static const gchar*
gtk_font_chooser_widget_get_preview_text (GtkFontChooserWidget *fontchooser)
{
return fontchooser->preview_text;
}
static void
gtk_font_chooser_widget_set_preview_text (GtkFontChooserWidget *fontchooser,
const gchar *text)
{
g_free (fontchooser->preview_text);
fontchooser->preview_text = g_strdup (text);
gtk_editable_set_text (GTK_EDITABLE (fontchooser->preview), text);
g_object_notify (G_OBJECT (fontchooser), "preview-text");
}
static gboolean
gtk_font_chooser_widget_get_show_preview_entry (GtkFontChooserWidget *fontchooser)
{
return fontchooser->show_preview_entry;
}
static void
gtk_font_chooser_widget_set_show_preview_entry (GtkFontChooserWidget *fontchooser,
gboolean show_preview_entry)
{
if (fontchooser->show_preview_entry != show_preview_entry)
{
fontchooser->show_preview_entry = show_preview_entry;
if (show_preview_entry)
gtk_widget_show (fontchooser->preview);
else
gtk_widget_hide (fontchooser->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);
if (g_set_object (&fontchooser->font_map, fontmap))
{
PangoContext *context;
if (!fontmap)
fontmap = pango_cairo_font_map_get_default ();
context = gtk_widget_get_pango_context (fontchooser->family_face_list);
pango_context_set_font_map (context, fontmap);
context = gtk_widget_get_pango_context (fontchooser->preview);
pango_context_set_font_map (context, fontmap);
update_fontlist (fontchooser);
}
}
static PangoFontMap *
gtk_font_chooser_widget_get_font_map (GtkFontChooser *chooser)
{
GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser);
return fontchooser->font_map;
}
static gboolean
gtk_font_chooser_widget_filter_cb (gpointer item,
gpointer data)
{
GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (data);
PangoFontFamily *family;
PangoFontFace *face;
if (PANGO_IS_FONT_FAMILY (item))
{
family = item;
face = pango_font_family_get_face (family, NULL);
}
else
{
face = item;
family = pango_font_face_get_family (face);
}
return self->filter_func (family, face, self->filter_data);
}
static void
gtk_font_chooser_widget_set_filter_func (GtkFontChooser *chooser,
GtkFontFilterFunc filter,
gpointer data,
GDestroyNotify destroy)
{
GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (chooser);
if (self->filter_data_destroy)
self->filter_data_destroy (self->filter_data);
self->filter_func = filter;
self->filter_data = data;
self->filter_data_destroy = destroy;
if (filter)
{
gtk_custom_filter_set_filter_func (self->custom_filter,
gtk_font_chooser_widget_filter_cb,
self,
NULL);
}
else
{
gtk_custom_filter_set_filter_func (self->custom_filter, NULL, NULL, NULL);
}
}
static void
gtk_font_chooser_widget_set_level (GtkFontChooserWidget *fontchooser,
GtkFontChooserLevel level)
{
if (fontchooser->level == level)
return;
fontchooser->level = level;
if ((level & GTK_FONT_CHOOSER_LEVEL_SIZE) != 0)
{
gtk_widget_show (fontchooser->size_label);
gtk_widget_show (fontchooser->size_slider);
gtk_widget_show (fontchooser->size_spin);
}
else
{
gtk_widget_hide (fontchooser->size_label);
gtk_widget_hide (fontchooser->size_slider);
gtk_widget_hide (fontchooser->size_spin);
}
update_fontlist (fontchooser);
g_object_notify (G_OBJECT (fontchooser), "level");
}
static GtkFontChooserLevel
gtk_font_chooser_widget_get_level (GtkFontChooserWidget *fontchooser)
{
return fontchooser->level;
}
static void
gtk_font_chooser_widget_set_language (GtkFontChooserWidget *fontchooser,
const char *language)
{
PangoLanguage *lang;
lang = pango_language_from_string (language);
if (fontchooser->language == lang)
return;
fontchooser->language = lang;
g_object_notify (G_OBJECT (fontchooser), "language");
gtk_font_chooser_widget_update_preview_attributes (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;
}
GAction *
gtk_font_chooser_widget_get_tweak_action (GtkWidget *widget)
{
GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (widget);
return fontchooser->tweak_action;
}