gtkfontchooserwidget.c 82.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* 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
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 17 18 19 20 21 22 23 24 25 26
 */

#include "config.h"

#include <stdlib.h>
#include <glib/gprintf.h>
#include <string.h>

#include <atk/atk.h>

#include "gtkfontchooserwidget.h"
27
#include "gtkfontchooserwidgetprivate.h"
28 29 30 31

#include "gtkadjustment.h"
#include "gtkbuildable.h"
#include "gtkbox.h"
32 33
#include "gtkcellrenderertext.h"
#include "gtkentry.h"
Matthias Clasen's avatar
Matthias Clasen committed
34
#include "gtksearchentry.h"
35 36 37 38
#include "gtkgrid.h"
#include "gtkfontchooser.h"
#include "gtkfontchooserutils.h"
#include "gtkintl.h"
39 40
#include "gtklabel.h"
#include "gtkliststore.h"
41
#include "gtkstack.h"
42 43
#include "gtkprivate.h"
#include "gtkscale.h"
44
#include "gtkscrolledwindow.h"
45
#include "gtkspinbutton.h"
46 47 48
#include "gtktextview.h"
#include "gtktreeselection.h"
#include "gtktreeview.h"
49
#include "gtkwidget.h"
50
#include "gtksettings.h"
51
#include "gtkdialog.h"
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
#include "gtkradiobutton.h"
#include "gtkcombobox.h"
#include "gtkgesturemultipress.h"

#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
#include <pango/pangofc-font.h>
#include <hb.h>
#include <hb-ot.h>
#include <hb-ft.h>
#include <freetype/freetype.h>
#include <freetype/ftmm.h>
#include "language-names.h"
#include "script-names.h"
#endif

#include "open-type-layout.h"
68 69

/**
70
 * SECTION:gtkfontchooserwidget
71 72
 * @Short_description: A widget for selecting fonts
 * @Title: GtkFontChooserWidget
73
 * @See_also: #GtkFontChooserDialog
74
 *
Matthias Clasen's avatar
Matthias Clasen committed
75 76
 * The #GtkFontChooserWidget widget lists the available fonts,
 * styles and sizes, allowing the user to select a font. It is
77
 * used in the #GtkFontChooserDialog widget to provide a
Matthias Clasen's avatar
Matthias Clasen committed
78
 * dialog box for selecting fonts.
79 80
 *
 * To set the font which is initially selected, use
81
 * gtk_font_chooser_set_font() or gtk_font_chooser_set_font_desc().
82
 *
83 84
 * To get the selected font use gtk_font_chooser_get_font() or
 * gtk_font_chooser_get_font_desc().
85 86
 *
 * To change the text which is shown in the preview area, use
87
 * gtk_font_chooser_set_preview_text().
88
 *
89 90 91
 * # CSS nodes
 *
 * GtkFontChooserWidget has a single CSS node with name fontchooser.
92 93
 *
 * Since: 3.2
94 95 96 97 98
 */


struct _GtkFontChooserWidgetPrivate
{
99
  GtkWidget    *stack;
100 101
  GtkWidget    *search_entry;
  GtkWidget    *family_face_list;
102
  GtkTreeViewColumn *family_face_column;
103
  GtkCellRenderer *family_face_cell;
104
  GtkWidget    *list_scrolled_window;
105
  GtkWidget    *list_stack;
106
  GtkTreeModel *model;
107 108 109
  GtkTreeModel *filter_model;

  GtkWidget       *preview;
110 111
  GtkWidget       *preview2;
  GtkWidget       *font_name_label;
112 113 114 115 116
  gchar           *preview_text;
  gboolean         show_preview_entry;

  GtkWidget *size_spin;
  GtkWidget *size_slider;
117
  GtkWidget *size_slider2;
118

119
  GtkWidget *axis_grid;
120 121
  GtkWidget       *feature_box;

122 123
  PangoFontMap         *font_map;

124
  PangoFontDescription *font_desc;
125
  char                 *font_features;
126
  PangoLanguage        *language;
127 128
  GtkTreeIter           font_iter;      /* invalid if font not available or pointer into model
                                           (not filter_model) to the row containing font */
129 130 131
  GtkFontFilterFunc filter_func;
  gpointer          filter_data;
  GDestroyNotify    filter_data_destroy;
132 133

  guint last_fontconfig_timestamp;
134 135

  GtkFontChooserLevel level;
136

137 138 139
  GHashTable *axes;
  gboolean updating_variations;

140 141
  GList *feature_items;

142
  GAction *tweak_action;
143 144
};

145

146 147 148 149 150 151 152 153 154 155
/* This is the initial fixed height and the top padding of the preview entry */
#define PREVIEW_HEIGHT 72
#define PREVIEW_TOP_PADDING 6

/* These are the sizes of the font, style & size lists. */
#define FONT_LIST_HEIGHT  136
#define FONT_LIST_WIDTH   190
#define FONT_STYLE_LIST_WIDTH 170
#define FONT_SIZE_LIST_WIDTH  60

156 157 158 159 160
enum {
  PROP_ZERO,
  PROP_TWEAK_ACTION
};

161
/* Keep in line with GtkTreeStore defined in gtkfontchooserwidget.ui */
162 163 164
enum {
  FAMILY_COLUMN,
  FACE_COLUMN,
165
  FONT_DESC_COLUMN,
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
  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);

182 183 184 185
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);
186

187 188 189
static gchar   *gtk_font_chooser_widget_get_font         (GtkFontChooserWidget *fontchooser);
static void     gtk_font_chooser_widget_set_font         (GtkFontChooserWidget *fontchooser,
                                                          const gchar          *fontname);
190

191
static PangoFontDescription *gtk_font_chooser_widget_get_font_desc  (GtkFontChooserWidget *fontchooser);
192 193 194
static void                  gtk_font_chooser_widget_merge_font_desc(GtkFontChooserWidget       *fontchooser,
                                                                     const PangoFontDescription *font_desc,
                                                                     GtkTreeIter                *iter);
195
static void                  gtk_font_chooser_widget_take_font_desc (GtkFontChooserWidget *fontchooser,
196 197 198
                                                                     PangoFontDescription *font_desc);


199 200 201 202 203 204 205 206
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);

207
static void     gtk_font_chooser_widget_set_cell_size          (GtkFontChooserWidget *fontchooser);
208 209
static void     gtk_font_chooser_widget_load_fonts             (GtkFontChooserWidget *fontchooser,
                                                                gboolean              force);
210 211

static void     gtk_font_chooser_widget_populate_features      (GtkFontChooserWidget *fontchooser);
212 213 214 215 216 217 218 219 220
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);

221 222
static void selection_changed (GtkTreeSelection *selection,
                               GtkFontChooserWidget *fontchooser);
223 224
static void update_font_features (GtkFontChooserWidget *fontchooser);

225

226 227 228
static void                gtk_font_chooser_widget_set_level (GtkFontChooserWidget *fontchooser,
                                                              GtkFontChooserLevel   level);
static GtkFontChooserLevel gtk_font_chooser_widget_get_level (GtkFontChooserWidget *fontchooser);
229 230
static void                gtk_font_chooser_widget_set_language (GtkFontChooserWidget *fontchooser,
                                                                 const char           *language);
231 232
static void selection_changed (GtkTreeSelection *selection,
                               GtkFontChooserWidget *fontchooser);
233
static void update_font_features (GtkFontChooserWidget *fontchooser);
234

235 236 237
static void gtk_font_chooser_widget_iface_init (GtkFontChooserIface *iface);

G_DEFINE_TYPE_WITH_CODE (GtkFontChooserWidget, gtk_font_chooser_widget, GTK_TYPE_BOX,
238
                         G_ADD_PRIVATE (GtkFontChooserWidget)
239 240 241
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_FONT_CHOOSER,
                                                gtk_font_chooser_widget_iface_init))

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
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 ())
G_DEFINE_BOXED_TYPE (GtkDelayedFontDescription, gtk_delayed_font_description,
                     gtk_delayed_font_description_ref,
                     gtk_delayed_font_description_unref)
299 300 301 302 303 304 305 306 307 308
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)
    {
309
    case GTK_FONT_CHOOSER_PROP_FONT:
310
      gtk_font_chooser_widget_set_font (fontchooser, g_value_get_string (value));
311
      break;
312
    case GTK_FONT_CHOOSER_PROP_FONT_DESC:
313
      gtk_font_chooser_widget_take_font_desc (fontchooser, g_value_dup_boxed (value));
314
      break;
315 316 317 318 319 320
    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;
321 322 323
    case GTK_FONT_CHOOSER_PROP_LEVEL:
      gtk_font_chooser_widget_set_level (fontchooser, g_value_get_flags (value));
      break;
324 325 326
    case GTK_FONT_CHOOSER_PROP_LANGUAGE:
      gtk_font_chooser_widget_set_language (fontchooser, g_value_get_string (value));
      break;
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
    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)
    {
343 344 345
    case PROP_TWEAK_ACTION:
      g_value_set_object (value, G_OBJECT (fontchooser->priv->tweak_action));
      break;
346
    case GTK_FONT_CHOOSER_PROP_FONT:
347
      g_value_take_string (value, gtk_font_chooser_widget_get_font (fontchooser));
348
      break;
349
    case GTK_FONT_CHOOSER_PROP_FONT_DESC:
350
      g_value_set_boxed (value, gtk_font_chooser_widget_get_font_desc (fontchooser));
351
      break;
352 353 354 355 356 357
    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;
358 359 360
    case GTK_FONT_CHOOSER_PROP_LEVEL:
      g_value_set_flags (value, gtk_font_chooser_widget_get_level (fontchooser));
      break;
361 362 363 364
    case GTK_FONT_CHOOSER_PROP_FONT_FEATURES:
      g_value_set_string (value, fontchooser->priv->font_features);
      break;
    case GTK_FONT_CHOOSER_PROP_LANGUAGE:
365
      g_value_set_string (value, pango_language_to_string (fontchooser->priv->language));
366
      break;
367 368 369 370 371 372
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

373 374 375 376 377 378 379
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);
}

380
static void
Matthias Clasen's avatar
Matthias Clasen committed
381
text_changed_cb (GtkEntry             *entry,
382 383
                 GtkFontChooserWidget *fc)
{
384
  gtk_font_chooser_widget_refilter_font_list (fc);
385 386
}

387 388 389 390
static void
stop_search_cb (GtkEntry             *entry,
                GtkFontChooserWidget *fc)
{
391 392 393 394 395 396 397 398 399 400 401 402 403 404
  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);
    }
405 406
}

407
static void
408
size_change_cb (GtkAdjustment *adjustment,
409 410
                gpointer       user_data)
{
411 412 413
  GtkFontChooserWidget *fontchooser = user_data;
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
  PangoFontDescription *font_desc;
414
  gdouble size = gtk_adjustment_get_value (adjustment);
415

416
  font_desc = pango_font_description_new ();
417
  if (pango_font_description_get_size_is_absolute (priv->font_desc))
418
    pango_font_description_set_absolute_size (font_desc, size * PANGO_SCALE);
419
  else
420
    pango_font_description_set_size (font_desc, size * PANGO_SCALE);
421

422
  gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc);
423 424
}

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
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_entry_set_text (GTK_ENTRY (spin), text);
  g_free (text);

  return TRUE;
}

442
static void
443
gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *fontchooser)
444
{
445
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
446
  GtkAdjustment *adj, *spin_adj;
447 448 449
  const int *sizes;
  gint *font_sizes;
  gint i, n_sizes;
450
  gdouble value, spin_value;
451

452
  if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (priv->model), &priv->font_iter))
453
    {
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
      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;
472 473
    }

474 475 476 477 478
  if (n_sizes < 2)
    {
      static const gint fallback_sizes[] = {
        6, 8, 9, 10, 11, 12, 13, 14, 16, 20, 24, 36, 48, 72
      };
479

480 481 482 483 484 485 486 487 488
      sizes = fallback_sizes;
      n_sizes = G_N_ELEMENTS (fallback_sizes);
    }
  else
    {
      sizes = font_sizes;
    }

  gtk_scale_clear_marks (GTK_SCALE (priv->size_slider));
489
  gtk_scale_clear_marks (GTK_SCALE (priv->size_slider2));
490

491 492 493 494 495 496 497 498 499 500
  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;
501

502 503 504
  /* ensure clamping doesn't callback into font resizing code */
  g_signal_handlers_block_by_func (adj, size_change_cb, fontchooser);
  gtk_adjustment_configure (adj,
505
                            value,
506 507 508 509 510 511 512 513 514 515 516 517
                            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);
518 519 520
      gtk_scale_add_mark (GTK_SCALE (priv->size_slider2),
                          sizes[i],
                          GTK_POS_BOTTOM, NULL);
521
    }
522

523
  g_free (font_sizes);
524 525 526 527 528 529 530 531
}

static void
row_activated_cb (GtkTreeView       *view,
                  GtkTreePath       *path,
                  GtkTreeViewColumn *column,
                  gpointer           user_data)
{
532
  GtkFontChooserWidget *fontchooser = user_data;
533 534
  gchar *fontname;

535 536
  fontname = gtk_font_chooser_widget_get_font (fontchooser);
  _gtk_font_chooser_font_activated (GTK_FONT_CHOOSER (fontchooser), fontname);
537 538 539 540 541 542 543
  g_free (fontname);
}

static void
cursor_changed_cb (GtkTreeView *treeview,
                   gpointer     user_data)
{
544
  GtkFontChooserWidget *fontchooser = user_data;
545
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
546
  GtkDelayedFontDescription *desc;
547
  GtkTreeIter filter_iter, iter;
Benjamin Otte's avatar
Benjamin Otte committed
548
  GtkTreePath *path = NULL;
549 550 551 552 553 554

  gtk_tree_view_get_cursor (treeview, &path, NULL);

  if (!path)
    return;

555
  if (!gtk_tree_model_get_iter (priv->filter_model, &filter_iter, path))
556 557 558 559 560 561 562
    {
      gtk_tree_path_free (path);
      return;
    }

  gtk_tree_path_free (path);

563 564 565
  gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (priv->filter_model),
                                                    &iter,
                                                    &filter_iter);
566
  gtk_tree_model_get (priv->model, &iter,
567 568
                      FONT_DESC_COLUMN, &desc,
                      -1);
569

570
  pango_font_description_set_variations (priv->font_desc, NULL);
571 572 573 574 575
  gtk_font_chooser_widget_merge_font_desc (fontchooser,
                                           gtk_delayed_font_description_get (desc),
                                           &iter);

  gtk_delayed_font_description_unref (desc);
576 577 578
}

static gboolean
579 580 581
resize_by_scroll_cb (GtkWidget      *scrolled_window,
                     GdkEventScroll *event,
                     gpointer        user_data)
582
{
583 584
  GtkFontChooserWidget *fc = user_data;
  GtkFontChooserWidgetPrivate *priv = fc->priv;
585 586 587 588 589 590 591 592 593 594
  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));
595 596 597 598 599 600 601 602 603
  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);

604 605 606
  return TRUE;
}

607 608 609 610 611 612 613 614 615 616 617 618 619
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));

620
  /* Force current font and features */
621
  pango_attr_list_insert (attrs, pango_attr_font_desc_new (priv->font_desc));
622 623
  if (priv->font_features)
    pango_attr_list_insert (attrs, pango_attr_font_features_new (priv->font_features));
624 625
  if (priv->language)
    pango_attr_list_insert (attrs, pango_attr_language_new (priv->language));
626 627 628 629 630

  gtk_entry_set_attributes (GTK_ENTRY (priv->preview), attrs);
  pango_attr_list_unref (attrs);
}

631 632 633 634 635 636
static void
row_inserted_cb (GtkTreeModel *model,
                 GtkTreePath  *path,
                 GtkTreeIter  *iter,
                 gpointer      user_data)
{
637 638
  GtkFontChooserWidget *fontchooser = user_data;
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
639

640
  gtk_stack_set_visible_child_name (GTK_STACK (priv->list_stack), "list");
641 642 643 644 645 646 647
}

static void
row_deleted_cb  (GtkTreeModel *model,
                 GtkTreePath  *path,
                 gpointer      user_data)
{
648 649
  GtkFontChooserWidget *fontchooser = user_data;
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
650 651

  if (gtk_tree_model_iter_n_children (model, NULL) == 0)
652
    gtk_stack_set_visible_child_name (GTK_STACK (priv->list_stack), "empty");
653 654
}

655 656 657 658 659 660 661 662 663 664 665 666 667
static void
gtk_font_chooser_widget_map (GtkWidget *widget)
{
  GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (widget);
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;

  gtk_entry_set_text (GTK_ENTRY (priv->search_entry), "");
  gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "list");
  g_simple_action_set_state (G_SIMPLE_ACTION (priv->tweak_action), g_variant_new_boolean (FALSE));

  GTK_WIDGET_CLASS (gtk_font_chooser_widget_parent_class)->map (widget);
}

668 669 670 671 672
static void
gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
673
  GParamSpec *pspec;
674

675
  g_type_ensure (GTK_TYPE_DELAYED_FONT_DESCRIPTION);
Matthias Clasen's avatar
Matthias Clasen committed
676
  g_type_ensure (G_TYPE_THEMED_ICON);
677

678
  widget_class->screen_changed = gtk_font_chooser_widget_screen_changed;
679
  widget_class->map = gtk_font_chooser_widget_map;
680 681 682 683 684

  gobject_class->finalize = gtk_font_chooser_widget_finalize;
  gobject_class->set_property = gtk_font_chooser_widget_set_property;
  gobject_class->get_property = gtk_font_chooser_widget_get_property;

685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
  /**
   * 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);

702 703
  _gtk_font_chooser_install_properties (gobject_class);

704
  /* Bind class to template */
705
  gtk_widget_class_set_template_from_resource (widget_class,
706
					       "/org/gtk/libgtk/ui/gtkfontchooserwidget.ui");
707

708 709 710 711 712
  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);
713
  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, list_stack);
714 715 716
  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);
717
  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, preview2);
718 719
  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, size_spin);
  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, size_slider);
720 721 722
  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, size_slider2);
  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, stack);
  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, font_name_label);
723
  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, feature_box);
724
  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, axis_grid);
725 726

  gtk_widget_class_bind_template_callback (widget_class, text_changed_cb);
727
  gtk_widget_class_bind_template_callback (widget_class, stop_search_cb);
728 729 730 731 732 733 734 735
  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);
736
  gtk_widget_class_bind_template_callback (widget_class, output_cb);
737
  gtk_widget_class_bind_template_callback (widget_class, selection_changed);
738

739
  gtk_widget_class_set_css_name (widget_class, I_("fontchooser"));
740 741
}

742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
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->priv->preview2));
      gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->priv->stack), "tweaks");
    }
  else
    {
      gtk_entry_grab_focus_without_selecting (GTK_ENTRY (fontchooser->priv->search_entry));
      gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->priv->stack), "list");
    }

  g_simple_action_set_state (action, state);
}

764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)

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)
{
  Axis *a = value;

  gtk_widget_destroy (a->label);
  gtk_widget_destroy (a->scale);
  gtk_widget_destroy (a->spin);
}

static void
axis_free (gpointer v)
{
  Axis *a = v;

  g_free (a);
}

#endif

814 815 816
static void
gtk_font_chooser_widget_init (GtkFontChooserWidget *fontchooser)
{
817
  GtkFontChooserWidgetPrivate *priv;
818

819
  fontchooser->priv = gtk_font_chooser_widget_get_instance_private (fontchooser);
820 821
  priv = fontchooser->priv;

822 823
  gtk_widget_init_template (GTK_WIDGET (fontchooser));

824 825 826 827
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
  priv->axes = g_hash_table_new_full (axis_hash, axis_equal, NULL, axis_free);
#endif

828 829 830
  /* Default preview string  */
  priv->preview_text = g_strdup (pango_language_get_sample_string (NULL));
  priv->show_preview_entry = TRUE;
831
  priv->font_desc = pango_font_description_new ();
832 833 834 835
  priv->level = GTK_FONT_CHOOSER_LEVEL_FAMILY |
                GTK_FONT_CHOOSER_LEVEL_STYLE |
                GTK_FONT_CHOOSER_LEVEL_SIZE;
  priv->language = pango_language_get_default ();
836 837

  /* Set default preview text */
838 839
  gtk_entry_set_text (GTK_ENTRY (priv->preview), priv->preview_text);

840
  gtk_font_chooser_widget_update_preview_attributes (fontchooser);
841

842
  gtk_widget_add_events (priv->preview, GDK_SCROLL_MASK);
843

844 845
  /* Set the upper values of the spin/scale with G_MAXINT / PANGO_SCALE */
  gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->size_spin),
846
                             1.0, (gdouble)(G_MAXINT / PANGO_SCALE));
847
  gtk_adjustment_set_upper (gtk_range_get_adjustment (GTK_RANGE (priv->size_slider)),
848
                            (gdouble)(G_MAXINT / PANGO_SCALE));
849

850 851 852
  /* Setup treeview/model auxilary functions */
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter_model),
                                          visible_func, (gpointer)priv, NULL);
853

854 855 856 857 858
  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);
859

860 861 862
  priv->tweak_action = G_ACTION (g_simple_action_new_stateful ("tweak", NULL, g_variant_new_boolean (FALSE)));
  g_signal_connect (priv->tweak_action, "change-state", G_CALLBACK (change_tweak), fontchooser);

863
  /* Load data and set initial style-dependent parameters */
864
  gtk_font_chooser_widget_load_fonts (fontchooser, TRUE);
865
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
866
  gtk_font_chooser_widget_populate_features (fontchooser);
867
#endif
868
  gtk_font_chooser_widget_set_cell_size (fontchooser);
869
  gtk_font_chooser_widget_take_font_desc (fontchooser, NULL);
870 871 872 873 874 875 876
}

/**
 * gtk_font_chooser_widget_new:
 *
 * Creates a new #GtkFontChooserWidget.
 *
877
 * Returns: a new #GtkFontChooserWidget
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
 *
 * 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
898 899
gtk_font_chooser_widget_load_fonts (GtkFontChooserWidget *fontchooser,
                                    gboolean              force)
900 901
{
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
902
  GtkListStore *list_store;
903 904
  gint n_families, i;
  PangoFontFamily **families;
905
  guint fontconfig_timestamp;
906 907
  gboolean need_reload;
  PangoFontMap *font_map;
908 909 910 911 912 913 914 915 916

  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.
   */
917 918
  need_reload = fontconfig_timestamp == 0 ||
                fontconfig_timestamp != priv->last_fontconfig_timestamp;
919 920

  priv->last_fontconfig_timestamp = fontconfig_timestamp;
921

922 923 924
  if (!need_reload && !force)
    return;

925 926
  list_store = GTK_LIST_STORE (priv->model);

927 928 929 930 931
  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);
932 933 934

  qsort (families, n_families, sizeof (PangoFontFamily *), cmp_families);

935
  g_signal_handlers_block_by_func (priv->family_face_list, cursor_changed_cb, fontchooser);
936
  gtk_list_store_clear (list_store);
937
  g_signal_handlers_unblock_by_func (priv->family_face_list, cursor_changed_cb, fontchooser);
938 939 940 941 942 943 944 945 946 947 948 949 950

  /* 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++)
        {
951
          GtkDelayedFontDescription *desc;
952
          const gchar *face_name;
953
          char *title;
954 955 956

          face_name = pango_font_face_get_face_name (faces[j]);

957 958 959 960 961
          if ((priv->level & GTK_FONT_CHOOSER_LEVEL_STYLE) != 0)
            title = g_strconcat (fam_name, " ", face_name, NULL);
          else
            title = g_strdup (fam_name);

962
          desc = gtk_delayed_font_description_new (faces[j]);
963

964
          gtk_list_store_insert_with_values (list_store, &iter, -1,
965 966
                                             FAMILY_COLUMN, families[i],
                                             FACE_COLUMN, faces[j],
967
                                             FONT_DESC_COLUMN, desc,
968
                                             PREVIEW_TITLE_COLUMN, title,
969
                                             -1);
970

971
          g_free (title);
972
          gtk_delayed_font_description_unref (desc);
973 974 975

          if ((priv->level & GTK_FONT_CHOOSER_LEVEL_STYLE) == 0)
            break;
976 977 978 979 980 981
        }

      g_free (faces);
    }

  g_free (families);
982 983

  /* now make sure the font list looks right */
984
  if (!gtk_font_chooser_widget_find_font (fontchooser, priv->font_desc, &priv->font_iter))
985 986 987
    memset (&priv->font_iter, 0, sizeof (GtkTreeIter));

  gtk_font_chooser_widget_ensure_selection (fontchooser);
988 989 990 991 992 993 994
}

static gboolean
visible_func (GtkTreeModel *model,
              GtkTreeIter  *iter,
              gpointer      user_data)
{
995
  GtkFontChooserWidgetPrivate *priv = user_data;
996
  gboolean result = TRUE;
997 998
  const gchar *search_text;
  gchar **split_terms;
999
  gchar *font_name, *font_name_casefold;
1000
  guint i;
1001

1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
  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;
    }

1021
  /* If there's no filter string we show the item */
1022
  search_text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
  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);
1034
  font_name_casefold = g_utf8_casefold (font_name, -1);
1035

1036 1037 1038
  for (i = 0; split_terms[i] && result; i++)
    {
      gchar* term_casefold = g_utf8_casefold (split_terms[i], -1);
1039

1040 1041
      if (!strstr (font_name_casefold, term_casefold))
        result = FALSE;
1042

1043 1044
      g_free (term_casefold);
    }
1045

1046
  g_free (font_name_casefold);
1047 1048 1049 1050 1051 1052
  g_free (font_name);
  g_strfreev (split_terms);

  return result;
}

1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
/* in pango units */
static int
gtk_font_chooser_widget_get_preview_text_height (GtkFontChooserWidget *fontchooser)
{
  GtkWidget *treeview = fontchooser->priv->family_face_list;
  double dpi, font_size;

  dpi = gdk_screen_get_resolution (gtk_widget_get_screen (treeview));
  gtk_style_context_get (gtk_widget_get_style_context (treeview),
                         gtk_widget_get_state_flags (treeview),
                         "font-size", &font_size,
                         NULL);

1066
  return (dpi < 0.0 ? 96.0 : dpi) / 72.0 * PANGO_SCALE_X_LARGE * font_size * PANGO_SCALE;
1067 1068
}

1069 1070
static PangoAttrList *
gtk_font_chooser_widget_get_preview_attributes (GtkFontChooserWidget       *fontchooser,
1071
                                                const PangoFontDescription *font_desc)
1072 1073 1074 1075 1076 1077
{
  PangoAttribute *attribute;
  PangoAttrList *attrs;

  attrs = pango_attr_list_new ();

1078 1079 1080 1081 1082
  if (font_desc)
    {
      attribute = pango_attr_font_desc_new (font_desc);
      pango_attr_list_insert (attrs, attribute);
    }
1083 1084 1085 1086 1087 1088 1089

  attribute = pango_attr_size_new_absolute (gtk_font_chooser_widget_get_preview_text_height (fontchooser));
  pango_attr_list_insert (attrs, attribute);

  return attrs;
}

1090 1091 1092 1093 1094 1095 1096 1097
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;
1098
  GtkDelayedFontDescription *desc;
1099
  PangoAttrList *attrs;
1100
  char *preview_title;
1101

1102 1103
  gtk_tree_model_get (tree_model, iter,
                      PREVIEW_TITLE_COLUMN, &preview_title,
1104
                      FONT_DESC_COLUMN, &desc,
1105 1106
                      -1);

1107 1108
  attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser,
                                                          gtk_delayed_font_description_get (desc));
1109 1110

  g_object_set (cell,
1111 1112
                "xpad", 20,
                "ypad", 10,
1113
                "attributes", attrs,
1114
                "text", preview_title,
1115 1116
                NULL);

1117
  gtk_delayed_font_description_unref (desc);
1118
  pango_attr_list_unref (attrs);
1119
  g_free (preview_title);
1120 1121
}

1122 1123 1124 1125 1126 1127 1128 1129 1130
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);

1131 1132
  attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser, NULL);

1133
  g_object_set (priv->family_face_cell,
1134 1135
                "xpad", 20,
                "ypad", 10,
1136
                "attributes", attrs,
1137
                "text", "x",
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148
                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);
}

1149 1150 1151 1152 1153 1154
static void
gtk_font_chooser_widget_finalize (GObject *object)
{
  GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (object);
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;

1155 1156
  if (priv->font_desc)
    pango_font_description_free (priv->font_desc);
1157 1158 1159 1160

  if (priv->filter_data_destroy)
    priv->filter_data_destroy (priv->filter_data);

1161 1162
  g_free (priv->preview_text);

1163 1164
  g_clear_object (&priv->font_map);

1165 1166
  g_object_unref (priv->tweak_action);

1167 1168
  g_list_free_full (priv->feature_items, g_free);

1169 1170 1171
  if (priv->axes)
    g_hash_table_unref (priv->axes);

1172 1173
  g_free (priv->font_features);

1174 1175 1176
  G_OBJECT_CLASS (gtk_font_chooser_widget_parent_class)->finalize (object);
}

1177 1178 1179 1180 1181 1182 1183
static gboolean
my_pango_font_family_equal (const char *familya,
                            const char *familyb)
{
  return g_ascii_strcasecmp (familya, familyb) == 0;
}

1184 1185 1186 1187
static gboolean
gtk_font_chooser_widget_find_font (GtkFontChooserWidget        *fontchooser,
                                   const PangoFontDescription  *font_desc,
                                   /* out arguments */
1188
                                   GtkTreeIter                 *iter)
1189 1190
{
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
1191
  gboolean valid;
1192

1193 1194 1195
  if (pango_font_description_get_family (font_desc) == NULL)
    return FALSE;

1196
  for (valid = gtk_tree_model_get_iter_first (priv->model, iter);
1197
       valid;
1198
       valid = gtk_tree_model_iter_next (priv->model, iter))
1199
    {
1200 1201
      GtkDelayedFontDescription *desc;
      PangoFontDescription *merged;
1202 1203
      PangoFontFamily *family;

1204 1205
      gtk_tree_model_get (priv->model, iter,
                          FAMILY_COLUMN, &family,
1206
                          FONT_DESC_COLUMN, &desc,
1207 1208 1209 1210
                          -1);

      if (!my_pango_font_family_equal (pango_font_description_get_family (font_desc),
                                       pango_font_family_get_name (family)))
1211 1212 1213 1214 1215
        {
          gtk_delayed_font_description_unref (desc);
          g_object_unref (family);
          continue;
        }
1216

1217
      merged = pango_font_description_copy_static (gtk_delayed_font_description_get (desc));
1218

1219 1220 1221 1222 1223 1224 1225 1226
      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;
        }
1227

1228 1229 1230
      gtk_delayed_font_description_unref (desc);
      pango_font_description_free (merged);
      g_object_unref (family);
1231 1232
    }
  
1233
  return valid;
1234 1235
}

1236 1237 1238 1239 1240 1241
static void
fontconfig_changed (GtkFontChooserWidget *fontchooser)
{
  gtk_font_chooser_widget_load_fonts (fontchooser, TRUE);
}

1242 1243
static void
gtk_font_chooser_widget_screen_changed (GtkWidget *widget,
1244
                                        GdkScreen *previous_screen)
1245 1246
{
  GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (widget);
1247
  GtkSettings *settings;
1248

1249 1250
  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);
1251

1252 1253 1254
  if (previous_screen)
    {
      settings = gtk_settings_get_for_screen (previous_screen);
1255
      g_signal_handlers_disconnect_by_func (settings, fontconfig_changed, widget);
1256 1257 1258
    }
  settings = gtk_widget_get_settings (widget);
  g_signal_connect_object (settings, "notify::gtk-fontconfig-timestamp",
1259
                           G_CALLBACK (fontconfig_changed), widget, G_CONNECT_SWAPPED);
1260

1261 1262 1263 1264 1265 1266
  if (previous_screen == NULL)
    previous_screen = gdk_screen_get_default ();

  if (previous_screen == gtk_widget_get_screen (widget))
    return;

1267
  gtk_font_chooser_widget_load_fonts (fontchooser, FALSE);
1268 1269 1270 1271 1272 1273
}

static PangoFontFamily *
gtk_font_chooser_widget_get_family (GtkFontChooser *chooser)
{
  GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser);
1274 1275 1276 1277 1278 1279 1280 1281 1282 1283
  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);
1284

1285
  return family;
1286 1287 1288 1289 1290 1291
}

static PangoFontFace *
gtk_font_chooser_widget_get_face (GtkFontChooser *chooser)
{
  GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser);
1292 1293
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
  PangoFontFace *face;
1294

1295 1296 1297 1298 1299 1300 1301 1302 1303
  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;
1304 1305 1306 1307 1308 1309
}

static gint
gtk_font_chooser_widget_get_size (GtkFontChooser *chooser)
{
  GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (chooser);
1310 1311 1312 1313
  PangoFontDescription *desc = gtk_font_chooser_widget_get_font_desc (fontchooser);

  if (desc)
    return pango_font_description_get_size (desc);
1314

1315
  return -1;
1316 1317 1318
}

static gchar *
1319
gtk_font_chooser_widget_get_font (GtkFontChooserWidget *fontchooser)
1320
{
1321 1322 1323 1324 1325 1326
  PangoFontDescription *desc = gtk_font_chooser_widget_get_font_desc (fontchooser);

  if (desc)
    return pango_font_description_to_string (desc);

  return NULL;
1327
}
1328

1329
static PangoFontDescription *
1330
gtk_font_chooser_widget_get_font_desc (GtkFontChooserWidget *fontchooser)
1331
{
1332 1333 1334 1335 1336 1337 1338 1339
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
  GtkTreeSelection *selection;

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->family_face_list));
  if (gtk_tree_selection_count_selected_rows (selection) > 0)
    return fontchooser->priv->font_desc;

  return NULL;
1340 1341
}

1342
static void
1343 1344
gtk_font_chooser_widget_set_font (GtkFontChooserWidget *fontchooser,
                                  const gchar          *fontname)
1345 1346 1347 1348
{
  PangoFontDescription *font_desc;

  font_desc = pango_font_description_from_string (fontname);
1349
  gtk_font_chooser_widget_take_font_desc (fontchooser, font_desc);
1350 1351
}

1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395
static void
gtk_font_chooser_widget_update_font_name (GtkFontChooserWidget *fontchooser,
                                          GtkTreeSelection     *selection)
{
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
  GtkTreeModel *model;
  GtkTreeIter iter;
  PangoFontFamily *family;
  PangoFontFace *face;
  GtkDelayedFontDescription *desc;
  const PangoFontDescription *font_desc;
  PangoAttrList *attrs;
  const char *fam_name;
  const char *face_name;
  char *title;

  gtk_tree_selection_get_selected (selection, &model, &iter);
  gtk_tree_model_get (model, &iter,
                      FAMILY_COLUMN, &family,
                      FACE_COLUMN, &face,
                      FONT_DESC_COLUMN, &desc,
                      -1);

  fam_name = pango_font_family_get_name (family);
  face_name = pango_font_face_get_face_name (face);
  font_desc = gtk_delayed_font_description_get (desc);

  g_object_unref (family);
  g_object_unref (face);
  gtk_delayed_font_description_unref (desc);

  if (priv->level == GTK_FONT_CHOOSER_LEVEL_FAMILY)
    title = g_strdup (fam_name);
  else
    title = g_strconcat (fam_name, " ", face_name, NULL);

  attrs = gtk_font_chooser_widget_get_preview_attributes (fontchooser, font_desc);
  gtk_label_set_attributes (GTK_LABEL (priv->font_name_label), attrs);
  pango_attr_list_unref (attrs);

  gtk_label_set_label (GTK_LABEL (priv->font_name_label), title);
  g_free (title);
}

1396 1397 1398 1399
static void
selection_changed (GtkTreeSelection     *selection,
                   GtkFontChooserWidget *fontchooser)
{
1400 1401
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;

1402 1403
  g_object_notify (G_OBJECT (fontchooser), "font");
  g_object_notify (G_OBJECT (fontchooser), "font-desc");
1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414

  if (gtk_tree_selection_count_selected_rows (selection) > 0)
    {
      gtk_font_chooser_widget_update_font_name (fontchooser, selection);
      g_simple_action_set_enabled (G_SIMPLE_ACTION (priv->tweak_action), TRUE);
    }
  else
    {
      g_simple_action_set_state (G_SIMPLE_ACTION (priv->tweak_action), g_variant_new_boolean (FALSE));
      g_simple_action_set_enabled (G_SIMPLE_ACTION (priv->tweak_action), FALSE);
    }
1415 1416
}

1417
static void
1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430
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))
    {
1431 1432 1433
      GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->filter_model),
                                                   &filter_iter);

1434
      gtk_tree_selection_select_iter (selection, &filter_iter);
1435 1436 1437
      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);
1438 1439 1440 1441 1442 1443 1444
    }
  else
    {
      gtk_tree_selection_unselect_all (selection);
    }
}

1445 1446
#if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)

1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654
/* OpenType variations */

#define FixedToFloat(f) (((float)(f))/65536.0)

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->priv->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);
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
  PangoFontDescription *font_desc;
  GString *s;

  priv->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);

  priv->updating_variations = FALSE;
}

static gboolean
should_show_axis (FT_Var_Axis *ax)
{
  /* FIXME use FT_Get_Var_Axis_Flags */
  if (ax->tag == FT_MAKE_TAG ('o', 'p', 's', 'z'))
    return FALSE;

  return TRUE;
}

static gboolean
is_named_instance (FT_Face face)
{
  return (face->face_index >> 16) > 0;
}

static struct {
  guint32 tag;
  const char *name;
} axis_names[] = {
  { FT_MAKE_TAG ('w', 'd', 't', 'h'), N_("Width") },
  { FT_MAKE_TAG ('w', 'g', 'h', 't'), N_("Weight") },
  { FT_MAKE_TAG ('i', 't', 'a', 'l'), N_("Italic") },
  { FT_MAKE_TAG ('s', 'l', 'n', 't'), N_("Slant") },
  { FT_MAKE_TAG ('o', 'p', 's', 'z'), N_("Optical Size") },
};

static gboolean
add_axis (GtkFontChooserWidget *fontchooser,
          FT_Face               face,
          FT_Var_Axis          *ax,
          FT_Fixed              value,
          int                   row)
{
  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
  Axis *axis;
  const char *name;
  int i;

  axis = g_new (Axis, 1);
  axis->tag = ax->tag;
  axis->fontchooser = GTK_WIDGET (fontchooser);

  name = ax->name;
  for (i = 0; i < G_N_ELEMENTS (axis_names); i++)
    {
      if (axis_names[i].tag == ax->tag)
        {
          name = _(axis_names[i].name);
          break;