gtkfontchooser.c 41.1 KB
Newer Older
1
/* GTK - The GIMP Toolkit
2
 * Copyright (C) 2011 Alberto Ruiz <aruiz@gnome.org>
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 *
 * 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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

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

#include <atk/atk.h>

28
#include "gtkfontchooser.h"
29 30 31 32 33 34 35 36
#include "gtkcellrenderertext.h"
#include "gtkentry.h"
#include "gtkframe.h"
#include "gtkhbbox.h"
#include "gtkhbox.h"
#include "gtklabel.h"
#include "gtkliststore.h"
#include "gtkstock.h"
37
#include "gtktextview.h"
38 39
#include "gtktreeselection.h"
#include "gtktreeview.h"
40
#include "gtkbox.h"
41 42 43 44 45
#include "gtkscrolledwindow.h"
#include "gtkintl.h"
#include "gtkaccessible.h"
#include "gtkbuildable.h"
#include "gtkprivate.h"
46 47
#include "gtkalignment.h"
#include "gtkscale.h"
48
#include "gtkbox.h"
49
#include "gtkspinbutton.h"
50
#include "gtkwidget.h"
51
#include "gtkgrid.h"
52 53

/**
54
 * SECTION:gtkfontchooser
55
 * @Short_description: A widget for selecting fonts
56 57
 * @Title: GtkFontChooser
 * @See_also: #GtkFontChooserDialog
58
 *
59
 * The #GtkFontChooser widget lists the available fonts, styles and sizes,
60
 * allowing the user to select a font.
61
 * It is used in the #GtkFontChooserDialog widget to provide a dialog box for
62 63 64
 * selecting fonts.
 *
 * To set the font which is initially selected, use
65
 * gtk_font_chooser_set_font_name().
66
 *
67
 * To get the selected font use gtk_font_chooser_get_font_name().
68 69
 *
 * To change the text which is shown in the preview area, use
70
 * gtk_font_chooser_set_preview_text().
71 72
 *
 * Since: 3.2
73 74 75
 */


76
struct _GtkFontChooserPrivate
77
{
78 79
  GtkWidget    *search_entry;
  GtkWidget    *family_face_list;
80 81
  GtkWidget    *list_scrolled_window;
  GtkWidget    *empty_list;
Matthias Clasen's avatar
Matthias Clasen committed
82
  GtkListStore *model;
83 84
  GtkTreeModel *filter;

85 86 87 88 89 90 91
  GtkWidget       *preview;
  gchar           *preview_text;
  gboolean         show_preview_entry;

  GtkWidget *size_spin;
  GtkWidget *size_slider;

92 93 94
  gint             size;
  PangoFontFace   *face;
  PangoFontFamily *family;
95 96

  gulong           cursor_changed_handler;
97 98 99
};

#define DEFAULT_FONT_NAME "Sans 10"
100
#define MAX_FONT_SIZE 999
101

102
/* This is the initial fixed height and the top padding of the preview entry */
103
#define PREVIEW_HEIGHT 72
104
#define PREVIEW_TOP_PADDING 6
105 106

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

112
#define ROW_FORMAT_STRING "<span weight=\"bold\" size=\"small\">%s</span>\n<span size=\"x-large\" font_desc=\"%s\">%s</span>"
113

Matthias Clasen's avatar
Matthias Clasen committed
114
#define NO_FONT_MATCHED_SEARCH "No fonts matched your search. You can revise your search and try again."
115

116 117
/* These are what we use as the standard font sizes, for the size list.
 */
118
static const gint font_sizes[] = {
119
  6, 8, 9, 10, 11, 12, 13, 14, 16, 20, 24, 36, 48, 72
120 121 122 123 124
};

enum {
   PROP_0,
   PROP_FONT_NAME,
125 126
   PROP_PREVIEW_TEXT,
   PROP_SHOW_PREVIEW_ENTRY
127 128 129 130 131 132
};


enum {
  FAMILY_COLUMN,
  FACE_COLUMN,
133
  PREVIEW_TEXT_COLUMN,
134
  PREVIEW_TITLE_COLUMN
135 136
};

137 138 139 140 141 142 143 144 145
static void  gtk_font_chooser_set_property       (GObject         *object,
                                                  guint            prop_id,
                                                  const GValue    *value,
                                                  GParamSpec      *pspec);
static void  gtk_font_chooser_get_property       (GObject         *object,
                                                  guint            prop_id,
                                                  GValue          *value,
                                                  GParamSpec      *pspec);
static void  gtk_font_chooser_finalize           (GObject         *object);
146
static void  gtk_font_chooser_dispose            (GObject         *object);
147

148 149 150
static void  gtk_font_chooser_screen_changed     (GtkWidget       *widget,
                                                  GdkScreen       *previous_screen);
static void  gtk_font_chooser_style_updated      (GtkWidget      *widget);
151

152 153 154 155
static void  gtk_font_chooser_ref_family         (GtkFontChooser *fontchooser,
                                                  PangoFontFamily  *family);
static void  gtk_font_chooser_ref_face           (GtkFontChooser *fontchooser,
                                                  PangoFontFace    *face);
156

157
static void gtk_font_chooser_bootstrap_fontlist (GtkFontChooser *fontchooser);
158

159
G_DEFINE_TYPE (GtkFontChooser, gtk_font_chooser, GTK_TYPE_BOX)
160 161

static void
162
gtk_font_chooser_class_init (GtkFontChooserClass *klass)
163 164
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
165
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
166

167 168
  widget_class->screen_changed = gtk_font_chooser_screen_changed;
  widget_class->style_updated = gtk_font_chooser_style_updated;
169

170
  gobject_class->dispose = gtk_font_chooser_dispose;
171 172 173
  gobject_class->finalize = gtk_font_chooser_finalize;
  gobject_class->set_property = gtk_font_chooser_set_property;
  gobject_class->get_property = gtk_font_chooser_get_property;
174 175 176 177 178 179 180 181 182 183 184 185 186

  g_object_class_install_property (gobject_class,
                                   PROP_FONT_NAME,
                                   g_param_spec_string ("font-name",
                                                        P_("Font name"),
                                                        P_("The string that represents this font"),
                                                        DEFAULT_FONT_NAME,
                                                        GTK_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_PREVIEW_TEXT,
                                   g_param_spec_string ("preview-text",
                                                        P_("Preview text"),
                                                        P_("The text to display in order to demonstrate the selected font"),
187
                                                        pango_language_get_sample_string (NULL),
188 189
                                                        GTK_PARAM_READWRITE));

190 191 192 193 194 195 196 197
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_PREVIEW_ENTRY,
                                   g_param_spec_boolean ("show-preview-entry",
                                                        P_("Show preview text entry"),
                                                        P_("Whether the preview text entry is shown or not"),
                                                        TRUE,
                                                        GTK_PARAM_READWRITE));

198
  g_type_class_add_private (klass, sizeof (GtkFontChooserPrivate));
199 200
}

Matthias Clasen's avatar
Matthias Clasen committed
201
static void
202
gtk_font_chooser_set_property (GObject         *object,
203 204 205
                               guint            prop_id,
                               const GValue    *value,
                               GParamSpec      *pspec)
206
{
207
  GtkFontChooser *fontchooser;
208

209
  fontchooser = GTK_FONT_CHOOSER (object);
210 211 212 213

  switch (prop_id)
    {
    case PROP_FONT_NAME:
214
      gtk_font_chooser_set_font_name (fontchooser, g_value_get_string (value));
215 216
      break;
    case PROP_PREVIEW_TEXT:
217
      gtk_font_chooser_set_preview_text (fontchooser, g_value_get_string (value));
218
      break;
219
    case PROP_SHOW_PREVIEW_ENTRY:
220
      gtk_font_chooser_set_show_preview_entry (fontchooser, g_value_get_boolean (value));
221
      break;
222 223 224 225 226 227
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

228
static void
229
gtk_font_chooser_get_property (GObject         *object,
230 231 232
                               guint            prop_id,
                               GValue          *value,
                               GParamSpec      *pspec)
233
{
234
  GtkFontChooser *fontchooser;
235

236
  fontchooser = GTK_FONT_CHOOSER (object);
237 238 239 240

  switch (prop_id)
    {
    case PROP_FONT_NAME:
241
      g_value_take_string (value, gtk_font_chooser_get_font_name (fontchooser));
242 243
      break;
    case PROP_PREVIEW_TEXT:
244
      g_value_set_string (value, gtk_font_chooser_get_preview_text (fontchooser));
245
      break;
246
    case PROP_SHOW_PREVIEW_ENTRY:
247
      g_value_set_boolean (value, gtk_font_chooser_get_show_preview_entry (fontchooser));
248
      break;
249 250 251 252 253
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}
Matthias Clasen's avatar
Matthias Clasen committed
254

255
static void
Matthias Clasen's avatar
Matthias Clasen committed
256 257 258
text_changed_cb (GtkEntry       *entry,
                 GParamSpec     *pspec,
                 GtkFontChooser *fc)
259
{
Matthias Clasen's avatar
Matthias Clasen committed
260 261 262 263
  GtkFontChooserPrivate *priv = fc->priv;
  const gchar *text;

  text = gtk_entry_get_text (entry);
Matthias Clasen's avatar
Matthias Clasen committed
264

Matthias Clasen's avatar
Matthias Clasen committed
265
  if (text == NULL || text[0] == '\0')
266
    {
Matthias Clasen's avatar
Matthias Clasen committed
267 268 269 270 271 272 273 274
      GIcon *icon;

      icon = g_themed_icon_new_with_default_fallbacks ("edit-find-symbolic");
      g_object_set (G_OBJECT (priv->search_entry),
                    "secondary-icon-gicon", icon,
                    "secondary-icon-activatable", FALSE,
                    "secondary-icon-sensitive", FALSE,
                    NULL);
275
      g_object_unref (icon);
276
    }
Matthias Clasen's avatar
Matthias Clasen committed
277
  else
278
    {
Matthias Clasen's avatar
Matthias Clasen committed
279 280 281 282 283 284 285 286 287 288 289 290
      if (!gtk_entry_get_icon_activatable (GTK_ENTRY (priv->search_entry), GTK_ENTRY_ICON_SECONDARY))
        {
          GIcon *icon;

          icon = g_themed_icon_new_with_default_fallbacks ("edit-clear-symbolic");
          g_object_set (G_OBJECT (priv->search_entry),
                        "secondary-icon-gicon", icon,
                        "secondary-icon-activatable", TRUE,
                        "secondary-icon-sensitive", TRUE,
                        NULL);
          g_object_unref (icon);
        }
291
    }
292

293
  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
294 295
}

296
static void
297 298 299 300 301
icon_press_cb (GtkEntry             *entry,
               GtkEntryIconPosition  pos,
               GdkEvent             *event,
               gpointer              user_data)
{
Matthias Clasen's avatar
Matthias Clasen committed
302
  gtk_entry_set_text (entry, "");
303 304
}

305
static void
306 307
slider_change_cb (GtkAdjustment *adjustment,
                  gpointer       user_data)
308
{
309 310
  GtkFontChooser        *fc    = (GtkFontChooser*)user_data;
  GtkFontChooserPrivate *priv  = fc->priv;
311 312 313 314 315 316 317
  GtkAdjustment         *spin_adj     = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON (priv->size_spin));
  gdouble                slider_value = gtk_adjustment_get_value (adjustment);
  gdouble                spin_value   = gtk_adjustment_get_value (spin_adj);

  if (slider_value != spin_value)
    gtk_adjustment_set_value (spin_adj,
                              gtk_adjustment_get_value (adjustment));
318 319
}

320
static void
321 322
spin_change_cb (GtkAdjustment *adjustment,
                gpointer       user_data)
323
{
324
  PangoFontDescription    *desc;
325
  GtkFontChooser          *fontchooser = (GtkFontChooser*)user_data;
326
  GtkFontChooserPrivate   *priv        = fontchooser->priv;
327
  GtkAdjustment           *slider_adj  = gtk_range_get_adjustment (GTK_RANGE (priv->size_slider));
328 329

  gdouble size = gtk_adjustment_get_value (adjustment);
330 331 332 333 334
  priv->size = ((gint)size) * PANGO_SCALE;

  desc = pango_context_get_font_description (gtk_widget_get_pango_context (priv->preview));
  pango_font_description_set_size (desc, priv->size);
  gtk_widget_override_font (priv->preview, desc);
335

336
  g_object_notify (G_OBJECT (fontchooser), "font-name");
337 338 339 340 341 342

  /* If the new value is lower than the lower bound of the slider, we set
   * the slider adjustment to the lower bound value if it is not already set
   */
  if (size < gtk_adjustment_get_lower (slider_adj) &&
      gtk_adjustment_get_value (slider_adj) != gtk_adjustment_get_lower (slider_adj))
343
    gtk_adjustment_set_value (slider_adj, gtk_adjustment_get_lower (slider_adj));
344 345 346 347 348 349

  /* If the new value is upper than the upper bound of the slider, we set
   * the slider adjustment to the upper bound value if it is not already set
   */
  else if (size > gtk_adjustment_get_upper (slider_adj) &&
           gtk_adjustment_get_value (slider_adj) != gtk_adjustment_get_upper (slider_adj))
350
    gtk_adjustment_set_value (slider_adj, gtk_adjustment_get_upper (slider_adj));
351 352 353

  /* If the new value is not already set on the slider we set it */
  else if (size != gtk_adjustment_get_value (slider_adj))
354
    gtk_adjustment_set_value (slider_adj, size);
355

356
  gtk_widget_queue_draw (priv->preview);
357 358
}

359
static void
360
set_range_marks (GtkFontChooserPrivate *priv,
361 362 363
                 GtkWidget             *size_slider,
                 gint                  *sizes,
                 gint                   length)
364
{
365
  GtkAdjustment *adj;
366
  gint i;
367
  gdouble value;
368

369
  if (length < 2)
370 371
    {
      sizes = (gint*)font_sizes;
372
      length = G_N_ELEMENTS (font_sizes);
373
    }
Matthias Clasen's avatar
Matthias Clasen committed
374

375
  gtk_scale_clear_marks (GTK_SCALE (size_slider));
Matthias Clasen's avatar
Matthias Clasen committed
376

377
  adj = gtk_range_get_adjustment(GTK_RANGE (size_slider));
Matthias Clasen's avatar
Matthias Clasen committed
378

379 380 381 382 383
  gtk_adjustment_set_lower (adj, (gdouble) sizes[0]);
  gtk_adjustment_set_upper (adj, (gdouble) sizes[length-1]);

  value = gtk_adjustment_get_value (adj);
  if (value > (gdouble) sizes[length-1])
384
    gtk_adjustment_set_value (adj, (gdouble) sizes[length-1]);
385
  else if (value < (gdouble) sizes[0])
386
    gtk_adjustment_set_value (adj, (gdouble) sizes[0]);
Matthias Clasen's avatar
Matthias Clasen committed
387

388
  for (i = 0; i < length; i++)
389 390 391
    gtk_scale_add_mark (GTK_SCALE (size_slider),
                        (gdouble) sizes[i],
                        GTK_POS_BOTTOM, NULL);
392 393
}

394
static void
395 396
cursor_changed_cb (GtkTreeView *treeview,
                   gpointer     user_data)
397
{
398
  PangoFontFamily      *family;
399 400
  PangoFontFace        *face;
  PangoFontDescription *desc;
Matthias Clasen's avatar
Matthias Clasen committed
401

402
  gint *sizes;
403
  gint  i, n_sizes;
404

405
  GtkTreeIter  iter;
406
  GtkTreePath *path = gtk_tree_path_new ();
Matthias Clasen's avatar
Matthias Clasen committed
407

408
  GtkFontChooser *fontchooser = (GtkFontChooser*)user_data;
Matthias Clasen's avatar
Matthias Clasen committed
409

410
  gtk_tree_view_get_cursor (treeview, &path, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
411

412 413 414
  if (!path)
    return;

415
  if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (fontchooser->priv->filter), &iter, path))
416 417 418
    {
      gtk_tree_path_free (path);
      return;
Matthias Clasen's avatar
Matthias Clasen committed
419 420 421
    }


422
  gtk_tree_model_get (GTK_TREE_MODEL (fontchooser->priv->filter), &iter,
423
                      FACE_COLUMN, &face,
424
                      FAMILY_COLUMN, &family,
425
                      -1);
426

427
  gtk_tree_view_scroll_to_cell (treeview, path, NULL, FALSE, 0.5, 0.5);
428

429 430
  gtk_tree_path_free (path);
  path = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
431

432
  if (!face || !family)
433
    {
434 435
      g_object_unref (face);
      g_object_unref (family);
436 437 438 439
      return;
    }

  desc = pango_font_face_describe (face);
440 441
  pango_font_description_set_size (desc, fontchooser->priv->size);
  gtk_widget_override_font (fontchooser->priv->preview, desc);
442

443 444
  pango_font_face_list_sizes (face, &sizes, &n_sizes);
  /* It seems not many fonts actually have a sane set of sizes */
445
  for (i = 0; i < n_sizes; i++)
446
    sizes[i] = sizes[i] / PANGO_SCALE;
Matthias Clasen's avatar
Matthias Clasen committed
447

448
  set_range_marks (fontchooser->priv, fontchooser->priv->size_slider, sizes, n_sizes);
449

450 451
  gtk_font_chooser_ref_family (fontchooser, family);
  gtk_font_chooser_ref_face   (fontchooser, face);
452

453
  /* Free resources */
454
  g_object_unref ((gpointer)family);
455 456
  g_object_unref ((gpointer)face);
  pango_font_description_free(desc);
457

458
  g_object_notify (G_OBJECT (fontchooser), "font-name");
459 460
}

461
static gboolean
462 463
zoom_preview_cb (GtkWidget      *scrolled_window,
                 GdkEventScroll *event,
464
                 gpointer        user_data)
465
{
466 467
  GtkFontChooser        *fc    = (GtkFontChooser*)user_data;
  GtkFontChooserPrivate *priv  = fc->priv;
468 469 470 471 472 473 474 475 476 477 478 479 480 481

  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));
  return TRUE;
}

482 483 484 485 486 487 488 489 490
#if 0
static void
row_inserted_cb (GtkTreeModel *model,
                 GtkTreePath  *path,
                 GtkTreeIter  *iter,
                 gpointer      user_data)
{
  GtkFontChooser        *fontchooser = (GtkFontChooser*)user_data;
  GtkFontChooserPrivate *priv        = fontchooser->priv;
Matthias Clasen's avatar
Matthias Clasen committed
491

492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
  if (gtk_bin_get_child (GTK_BIN (priv->list_scrolled_window)) ==
      priv->empty_list)
    {
      g_object_ref (priv->empty_list);
      gtk_container_remove (GTK_CONTAINER (priv->list_scrolled_window),
                            priv->empty_list);
      gtk_container_add (GTK_CONTAINER (priv->list_scrolled_window),
                         priv->family_face_list);
    }
}

static void
row_deleted_cb  (GtkTreeModel *model,
                 GtkTreePath  *path,
                 gpointer      user_data)
{
  GtkFontChooser        *fontchooser = (GtkFontChooser*)user_data;
  GtkFontChooserPrivate *priv        = fontchooser->priv;

  if (gtk_tree_model_iter_n_children (model, NULL) == 0)
    {
      if (gtk_bin_get_child (GTK_BIN (priv->list_scrolled_window)) ==
          priv->family_face_list)
        {
          g_object_ref (priv->family_face_list);
          gtk_container_remove (GTK_CONTAINER (priv->list_scrolled_window),
                                priv->family_face_list);
          gtk_container_add (GTK_CONTAINER (priv->list_scrolled_window),
                             priv->empty_list);
        }
    }
}
#endif

526
static void
527
gtk_font_chooser_init (GtkFontChooser *fontchooser)
528
{
529
  GIcon                   *icon;
530
  GtkFontChooserPrivate   *priv;
531
  PangoFontDescription    *font_desc;
532
  GtkWidget               *scrolled_win;
533
  GtkWidget               *grid;
534
  GtkWidget               *sub_grid;
535

536 537 538
  fontchooser->priv = G_TYPE_INSTANCE_GET_PRIVATE (fontchooser,
                                                   GTK_TYPE_FONT_CHOOSER,
                                                   GtkFontChooserPrivate);
539

540
  priv = fontchooser->priv;
541

542 543
  /* Default preview string  */
  priv->preview_text = g_strdup (pango_language_get_sample_string (NULL));
544
  priv->show_preview_entry = TRUE;
545

546
  /* Getting the default size */
547
  font_desc  = pango_context_get_font_description (gtk_widget_get_pango_context (GTK_WIDGET (fontchooser)));
548 549 550 551
  priv->size = pango_font_description_get_size (font_desc);
  priv->face = NULL;
  priv->family = NULL;

552
  gtk_widget_push_composite_child ();
553

554
  /* Creating fundamental widgets for the private struct */
555 556
  priv->search_entry = gtk_entry_new ();
  priv->family_face_list = gtk_tree_view_new ();
557
  priv->preview = gtk_entry_new ();
558
  priv->size_slider = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL,
559
                                                (gdouble) font_sizes[0],
560
                                                (gdouble) font_sizes[G_N_ELEMENTS (font_sizes) - 1],
561
                                                1.0);
562

563
  priv->size_spin = gtk_spin_button_new_with_range (0.0, (gdouble)(G_MAXINT / PANGO_SCALE), 1.0);
564

565
  /** Bootstrapping widget layout **/
566
  gtk_box_set_spacing (GTK_BOX (fontchooser), 6);
567

568
  /* Main font family/face view */
569 570
  priv->list_scrolled_window = gtk_scrolled_window_new (NULL, NULL);
  scrolled_win = priv->list_scrolled_window;
571 572
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
                                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
573 574
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win),
                                       GTK_SHADOW_ETCHED_IN);
575 576 577 578 579 580 581 582
  gtk_container_add (GTK_CONTAINER (scrolled_win),
                     priv->family_face_list);

  /* Text to display when list is empty */
  priv->empty_list = gtk_text_view_new ();
  gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->empty_list)),
                            _(NO_FONT_MATCHED_SEARCH),
                            -1);
583

584 585
  /* Basic layout */
  grid = gtk_grid_new ();
586
  sub_grid = gtk_grid_new ();
Matthias Clasen's avatar
Matthias Clasen committed
587

Matthias Clasen's avatar
Matthias Clasen committed
588 589
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_row_spacing (GTK_GRID (sub_grid), 6);
590

591
  gtk_grid_attach (GTK_GRID (grid), priv->search_entry, 0, 0, 3, 1);
Matthias Clasen's avatar
Matthias Clasen committed
592
  gtk_grid_attach (GTK_GRID (grid), scrolled_win,       0, 1, 3, 1);
593
  gtk_grid_attach (GTK_GRID (grid), priv->preview,      0, 2, 3, 1);
594
  gtk_grid_attach (GTK_GRID (grid), sub_grid,           0, 4, 3, 1);
Matthias Clasen's avatar
Matthias Clasen committed
595 596

  gtk_widget_set_hexpand  (GTK_WIDGET (sub_grid),          TRUE);
597 598
  gtk_grid_attach (GTK_GRID (sub_grid), priv->size_slider,  0, 3, 2, 1);
  gtk_grid_attach (GTK_GRID (sub_grid), priv->size_spin,    2, 3, 1, 1);
599

600 601
  gtk_widget_set_hexpand  (GTK_WIDGET (scrolled_win),      TRUE);
  gtk_widget_set_vexpand  (GTK_WIDGET (scrolled_win),      TRUE);
602 603 604 605
  gtk_widget_set_hexpand  (GTK_WIDGET (priv->search_entry), TRUE);

  gtk_widget_set_hexpand  (GTK_WIDGET (priv->size_slider), TRUE);
  gtk_widget_set_hexpand  (GTK_WIDGET (priv->size_spin),   FALSE);
606

607
  gtk_grid_set_column_homogeneous (GTK_GRID (sub_grid), FALSE);
608
  gtk_box_pack_start (GTK_BOX (fontchooser), grid, TRUE, TRUE, 0);
609

610
  /* Setting the adjustment values for the size slider */
611 612 613 614 615
  gtk_adjustment_set_value (gtk_range_get_adjustment (GTK_RANGE (priv->size_slider)),
                            (gdouble)(priv->size / PANGO_SCALE));
  gtk_adjustment_set_value (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->size_spin)),
                            (gdouble)(priv->size / PANGO_SCALE));

616 617
  gtk_widget_show_all (GTK_WIDGET (fontchooser));
  gtk_widget_hide     (GTK_WIDGET (fontchooser));
618

619
  /* Treeview column and model bootstrapping */
620
  gtk_font_chooser_bootstrap_fontlist (fontchooser);
621

622
  /* Set default preview text */
623 624
  gtk_entry_set_text (GTK_ENTRY (priv->preview),
                      pango_language_get_sample_string (NULL));
625

626
  /* Set search icon and place holder text */
627
  icon = g_themed_icon_new_with_default_fallbacks ("edit-find-symbolic");
Matthias Clasen's avatar
Matthias Clasen committed
628 629 630 631 632
  g_object_set (G_OBJECT (priv->search_entry),
                "secondary-icon-gicon", icon,
                "secondary-icon-activatable", FALSE,
                "secondary-icon-sensitive", FALSE,
                NULL);
633 634
  g_object_unref (icon);

635
  gtk_entry_set_placeholder_text (GTK_ENTRY (priv->search_entry), _("Search font name"));
636

637
  /** Callback connections **/
Matthias Clasen's avatar
Matthias Clasen committed
638 639 640
  g_signal_connect (priv->search_entry, "notify::text",
                    G_CALLBACK (text_changed_cb), fontchooser);
  g_signal_connect (priv->search_entry,
641
                    "icon-press", G_CALLBACK (icon_press_cb), NULL);
642

Matthias Clasen's avatar
Matthias Clasen committed
643
  g_signal_connect (gtk_range_get_adjustment (GTK_RANGE (priv->size_slider)),
644
                    "value-changed", G_CALLBACK (slider_change_cb), fontchooser);
Matthias Clasen's avatar
Matthias Clasen committed
645
  g_signal_connect (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->size_spin)),
646
                    "value-changed", G_CALLBACK (spin_change_cb), fontchooser);
647

648 649 650
  priv->cursor_changed_handler =
      g_signal_connect (priv->family_face_list, "cursor-changed",
                        G_CALLBACK (cursor_changed_cb), fontchooser);
651 652

  /* Zoom on preview scroll*/
Matthias Clasen's avatar
Matthias Clasen committed
653 654
  g_signal_connect (priv->preview, "scroll-event",
                    G_CALLBACK (zoom_preview_cb), fontchooser);
655

Matthias Clasen's avatar
Matthias Clasen committed
656
  g_signal_connect (priv->size_slider, "scroll-event",
657
                    G_CALLBACK (zoom_preview_cb), fontchooser);
658

659
  set_range_marks (priv, priv->size_slider, (gint*)font_sizes, G_N_ELEMENTS (font_sizes));
Matthias Clasen's avatar
Matthias Clasen committed
660

661 662 663 664 665 666 667
  /* Font list empty hides the scrolledwindow */
  /*
  g_signal_connect (G_OBJECT (priv->filter), "row-deleted",
                    G_CALLBACK (row_deleted_cb), fontchooser);
  g_signal_connect (G_OBJECT (priv->filter), "row-inserted",
                    G_CALLBACK (row_inserted_cb), fontchooser);
  */
668

669
  /* Set default focus */
Matthias Clasen's avatar
Matthias Clasen committed
670
  gtk_widget_pop_composite_child ();
671 672 673
}

/**
674
 * gtk_font_chooser_new:
675
 *
676
 * Creates a new #GtkFontChooser.
677
 *
678
 * Return value: a new #GtkFontChooser
679 680
 *
 * Since: 3.2
681 682
 */
GtkWidget *
683
gtk_font_chooser_new (void)
684
{
685
  GtkFontChooser *fontchooser;
686

687
  fontchooser = g_object_new (GTK_TYPE_FONT_CHOOSER, NULL);
688

689
  return GTK_WIDGET (fontchooser);
690 691
}

692
static int
Matthias Clasen's avatar
Matthias Clasen committed
693
cmp_families (const void *a,
694
              const void *b)
695 696 697 698 699 700 701
{
  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);
}

Matthias Clasen's avatar
Matthias Clasen committed
702
static void
703 704 705
populate_list (GtkFontChooser *fontchooser,
               GtkTreeView    *treeview,
               GtkListStore   *model)
706
{
707 708
  GtkStyleContext      *style_context;
  PangoFontDescription *default_font;
709

710
  GtkTreeIter   match_row;
711
  GtkTreePath  *path;
712

Matthias Clasen's avatar
Matthias Clasen committed
713
  gint n_families, i;
714
  PangoFontFamily **families;
715 716 717 718 719
  GString     *tmp;
  GString     *family_and_face;

  if (!gtk_widget_has_screen (GTK_WIDGET (fontchooser)))
    return;
720

721 722
  tmp = g_string_new (NULL);
  family_and_face = g_string_new (NULL);
723

724 725 726 727 728 729 730 731
  pango_context_list_families (gtk_widget_get_pango_context (GTK_WIDGET (treeview)),
                               &families,
                               &n_families);

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

  gtk_list_store_clear (model);

732 733 734
  /* Get row header font color */
  style_context = gtk_widget_get_style_context (GTK_WIDGET (treeview));

735
  /* Get theme font */
736 737
  default_font  = (PangoFontDescription*) gtk_style_context_get_font (style_context,
                                                                      GTK_STATE_NORMAL);
738

739
  /* Iterate over families and faces */
740
  for (i = 0; i < n_families; i++)
741
    {
742 743
      GtkTreeIter     iter;
      PangoFontFace **faces;
Matthias Clasen's avatar
Matthias Clasen committed
744

745 746 747 748
      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);
Matthias Clasen's avatar
Matthias Clasen committed
749

750
      for (j = 0; j < n_faces; j++)
751
        {
752 753 754
          PangoFontDescription *pango_desc = pango_font_face_describe (faces[j]);
          const gchar *face_name = pango_font_face_get_face_name (faces[j]);
          gchar       *font_desc = pango_font_description_to_string (pango_desc);
Matthias Clasen's avatar
Matthias Clasen committed
755

756
          /* foreground_color, family_name, face_name, desc, sample string */
Matthias Clasen's avatar
Matthias Clasen committed
757 758
          g_string_printf (family_and_face, "%s %s", fam_name, face_name);

759
          g_string_printf (tmp, ROW_FORMAT_STRING,
760 761 762
                           family_and_face->str,
                           font_desc,
                           fontchooser->priv->preview_text);
763 764 765 766 767

          gtk_list_store_append (model, &iter);
          gtk_list_store_set (model, &iter,
                              FAMILY_COLUMN, families[i],
                              FACE_COLUMN, faces[j],
768 769
                              PREVIEW_TITLE_COLUMN, family_and_face->str,
                              PREVIEW_TEXT_COLUMN, tmp->str,
770 771
                              -1);

772
          /* Select the first font or the default font/face from the style context */
773
          if ((i == 0 && j == 0) ||
774
              (!strcmp (fam_name, pango_font_description_get_family (default_font)) && j == 0))
775
            match_row = iter;
776 777 778

          pango_font_description_free(pango_desc);
          g_free (font_desc);
779
        }
780 781

      g_free (faces);
782 783
    }

784
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &match_row);
785

786
  if (path)
787 788
    {
      gtk_tree_view_set_cursor (treeview, path, NULL, FALSE);
789
      gtk_tree_view_scroll_to_cell (treeview, path, NULL, FALSE, 0.5, 0.5);
790 791
      gtk_tree_path_free(path);
    }
792 793

  g_string_free (family_and_face, TRUE);
794
  g_string_free (tmp, TRUE);
795
  g_free (families);
796 797
}

798
static gboolean
799 800 801
visible_func (GtkTreeModel *model,
              GtkTreeIter  *iter,
              gpointer      user_data)
802
{
803
  gboolean result = TRUE;
804
  GtkFontChooserPrivate *priv = (GtkFontChooserPrivate*)user_data;
805 806 807

  const gchar *search_text = (const gchar*)gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
  gchar       *font_name;
808 809 810 811 812 813 814
  gchar       *term;
  gchar      **split_terms;
  gint         n_terms = 0;

  /* If there's no filter string we show the item */
  if (strlen (search_text) == 0)
    return TRUE;
815 816

  gtk_tree_model_get (model, iter,
817
                      PREVIEW_TITLE_COLUMN, &font_name,
818 819
                      -1);

820
  if (font_name == NULL)
Matthias Clasen's avatar
Matthias Clasen committed
821
    return FALSE;
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841

  split_terms = g_strsplit (search_text, " ", 0);
  term = split_terms[0];

  while (term && result)
  {
    gchar* font_name_casefold = g_utf8_casefold (font_name, -1);
    gchar* term_casefold = g_utf8_casefold (term, -1);

    if (g_strrstr (font_name_casefold, term_casefold))
      result = result && TRUE;
    else
      result = FALSE;

    n_terms++;
    term = split_terms[n_terms];

    g_free (term_casefold);
    g_free (font_name_casefold);
  }
842 843

  g_free (font_name);
844 845
  g_strfreev (split_terms);

846
  return result;
847 848
}

849
static void
Matthias Clasen's avatar
Matthias Clasen committed
850
gtk_font_chooser_bootstrap_fontlist (GtkFontChooser *fontchooser)
851
{
852
  GtkTreeView       *treeview = GTK_TREE_VIEW (fontchooser->priv->family_face_list);
853
  GtkCellRenderer   *cell;
854 855
  GtkTreeViewColumn *col;

856
  fontchooser->priv->model = gtk_list_store_new (4,
857 858 859 860
                                                 PANGO_TYPE_FONT_FAMILY,
                                                 PANGO_TYPE_FONT_FACE,
                                                 G_TYPE_STRING,
                                                 G_TYPE_STRING);
861

862
  fontchooser->priv->filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fontchooser->priv->model),
863
                                                         NULL);
864
  g_object_unref (fontchooser->priv->model);
865

866
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (fontchooser->priv->filter),
867
                                          visible_func,
868
                                          (gpointer)fontchooser->priv,
869 870
                                          NULL);

871 872
  gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (fontchooser->priv->filter));
  g_object_unref (fontchooser->priv->filter);
873

874 875
  gtk_tree_view_set_rules_hint      (treeview, TRUE);
  gtk_tree_view_set_headers_visible (treeview, FALSE);
876

877
  cell = gtk_cell_renderer_text_new ();
878
  col = gtk_tree_view_column_new_with_attributes ("Family",
879
                                                  cell,
880
                                                  "markup", PREVIEW_TEXT_COLUMN,
881
                                                  NULL);
Matthias Clasen's avatar
Matthias Clasen committed
882

883
  g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
884

885 886
  gtk_tree_view_append_column (treeview, col);

887
  populate_list (fontchooser, treeview, fontchooser->priv->model);
888 889
}

890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
static void
gtk_font_chooser_dispose (GObject *object)
{
  GtkFontChooser *fontchooser = GTK_FONT_CHOOSER (object);
  GtkFontChooserPrivate *priv = fontchooser->priv;

  if (priv->cursor_changed_handler != 0)
    {
      g_signal_handler_disconnect (priv->family_face_list,
                                   priv->cursor_changed_handler);
      priv->cursor_changed_handler = 0;
    }

  G_OBJECT_CLASS (gtk_font_chooser_parent_class)->dispose (object);
}
905

906
static void
907
gtk_font_chooser_finalize (GObject *object)
908
{
909
  GtkFontChooser *fontchooser = GTK_FONT_CHOOSER (object);
910

911 912
  gtk_font_chooser_ref_family (fontchooser, NULL);
  gtk_font_chooser_ref_face (fontchooser, NULL);
913

914
  G_OBJECT_CLASS (gtk_font_chooser_parent_class)->finalize (object);
915 916 917
}

static void
918
gtk_font_chooser_screen_changed (GtkWidget *widget,
919
                                 GdkScreen *previous_screen)
920
{
921
  GtkFontChooser *fontchooser = GTK_FONT_CHOOSER (widget);
922

923 924 925
  populate_list (fontchooser,
                 GTK_TREE_VIEW (fontchooser->priv->family_face_list),
                 fontchooser->priv->model);
926 927 928
}

static void
929
gtk_font_chooser_style_updated (GtkWidget *widget)
930
{
931
  GtkFontChooser *fontchooser = GTK_FONT_CHOOSER (widget);
932

933
  GTK_WIDGET_CLASS (gtk_font_chooser_parent_class)->style_updated (widget);
934

935 936 937
  populate_list (fontchooser,
                 GTK_TREE_VIEW (fontchooser->priv->family_face_list),
                 fontchooser->priv->model);
938 939 940