]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkfont.c
Add ifdefs for older versions of GTK2 and Pango. Unfortunately, the
[PuTTY.git] / unix / gtkfont.c
1 /*
2  * Unified font management for GTK.
3  * 
4  * PuTTY is willing to use both old-style X server-side bitmap
5  * fonts _and_ GTK2/Pango client-side fonts. This requires us to
6  * do a bit of work to wrap the two wildly different APIs into
7  * forms the rest of the code can switch between seamlessly, and
8  * also requires a custom font selector capable of handling both
9  * types of font.
10  */
11
12 #include <assert.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <gtk/gtk.h>
16 #include <gdk/gdkkeysyms.h>
17 #include <gdk/gdkx.h>
18 #include <X11/Xlib.h>
19 #include <X11/Xutil.h>
20 #include <X11/Xatom.h>
21
22 #include "putty.h"
23 #include "gtkfont.h"
24 #include "tree234.h"
25
26 /*
27  * TODO on fontsel
28  * ---------------
29  * 
30  *  - implement the preview pane
31  * 
32  *  - extend the font style language for X11 fonts so that we
33  *    never get unexplained double size elements? Or, at least, so
34  *    that _my_ font collection never produces them; that'd be a
35  *    decent start.
36  * 
37  *  - decide what _should_ happen about font aliases. Should we
38  *    resolve them as soon as they're clicked? Or be able to
39  *    resolve them on demand, er, somehow? Or resolve them on exit
40  *    from the function? Or what? If we resolve on demand, should
41  *    we stop canonifying them on input, on the basis that we'd
42  *    prefer to let the user _tell_ us when to canonify them?
43  * 
44  *  - think about points versus pixels, harder than I already have
45  * 
46  *  - work out why the list boxes don't go all the way to the RHS
47  *    of the dialog box
48  * 
49  *  - develop a sensible sorting order for the font styles -
50  *    Regular / Roman / non-bold-or-italic should come at the top!
51  * 
52  *  - big testing and shakedown!
53  */
54
55 /*
56  * Future work:
57  * 
58  *  - all the GDK font functions used in the x11font subclass are
59  *    deprecated, so one day they may go away. When this happens -
60  *    or before, if I'm feeling proactive - it oughtn't to be too
61  *    difficult in principle to convert the whole thing to use
62  *    actual Xlib font calls.
63  * 
64  *  - it would be nice if we could move the processing of
65  *    underline and VT100 double width into this module, so that
66  *    instead of using the ghastly pixmap-stretching technique
67  *    everywhere we could tell the Pango backend to scale its
68  *    fonts to double size properly and at full resolution.
69  *    However, this requires me to learn how to make Pango stretch
70  *    text to an arbitrary aspect ratio (for double-width only
71  *    text, which perversely is harder than DW+DH), and right now
72  *    I haven't the energy.
73  */
74
75 /*
76  * Ad-hoc vtable mechanism to allow font structures to be
77  * polymorphic.
78  * 
79  * Any instance of `unifont' used in the vtable functions will
80  * actually be the first element of a larger structure containing
81  * data specific to the subtype. This is permitted by the ISO C
82  * provision that one may safely cast between a pointer to a
83  * structure and a pointer to its first element.
84  */
85
86 #define FONTFLAG_CLIENTSIDE    0x0001
87 #define FONTFLAG_SERVERSIDE    0x0002
88 #define FONTFLAG_SERVERALIAS   0x0004
89 #define FONTFLAG_NONMONOSPACED 0x0008
90
91 typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname,
92                                   const char *family, const char *charset,
93                                   const char *style, int size, int flags,
94                                   const struct unifont_vtable *fontclass);
95
96 struct unifont_vtable {
97     /*
98      * `Methods' of the `class'.
99      */
100     unifont *(*create)(GtkWidget *widget, const char *name, int wide, int bold,
101                        int shadowoffset, int shadowalways);
102     void (*destroy)(unifont *font);
103     void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font,
104                       int x, int y, const char *string, int len, int wide,
105                       int bold, int cellwidth);
106     void (*enum_fonts)(GtkWidget *widget,
107                        fontsel_add_entry callback, void *callback_ctx);
108     char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size);
109     char *(*scale_fontname)(GtkWidget *widget, const char *name, int size);
110
111     /*
112      * `Static data members' of the `class'.
113      */
114     const char *prefix;
115 };
116
117 /* ----------------------------------------------------------------------
118  * GDK-based X11 font implementation.
119  */
120
121 static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
122                               int x, int y, const char *string, int len,
123                               int wide, int bold, int cellwidth);
124 static unifont *x11font_create(GtkWidget *widget, const char *name,
125                                int wide, int bold,
126                                int shadowoffset, int shadowalways);
127 static void x11font_destroy(unifont *font);
128 static void x11font_enum_fonts(GtkWidget *widget,
129                                fontsel_add_entry callback, void *callback_ctx);
130 static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
131                                        int *size);
132 static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
133                                     int size);
134
135 struct x11font {
136     struct unifont u;
137     /*
138      * Actual font objects. We store a number of these, for
139      * automatically guessed bold and wide variants.
140      * 
141      * The parallel array `allocated' indicates whether we've
142      * tried to fetch a subfont already (thus distinguishing NULL
143      * because we haven't tried yet from NULL because we tried and
144      * failed, so that we don't keep trying and failing
145      * subsequently).
146      */
147     GdkFont *fonts[4];
148     int allocated[4];
149     /*
150      * `sixteen_bit' is true iff the font object is indexed by
151      * values larger than a byte. That is, this flag tells us
152      * whether we use gdk_draw_text_wc() or gdk_draw_text().
153      */
154     int sixteen_bit;
155     /*
156      * Data passed in to unifont_create().
157      */
158     int wide, bold, shadowoffset, shadowalways;
159 };
160
161 static const struct unifont_vtable x11font_vtable = {
162     x11font_create,
163     x11font_destroy,
164     x11font_draw_text,
165     x11font_enum_fonts,
166     x11font_canonify_fontname,
167     x11font_scale_fontname,
168     "server"
169 };
170
171 char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide)
172 {
173     XFontStruct *xfs = GDK_FONT_XFONT(font);
174     Display *disp = GDK_FONT_XDISPLAY(font);
175     Atom fontprop = XInternAtom(disp, "FONT", False);
176     unsigned long ret;
177     if (XGetFontProperty(xfs, fontprop, &ret)) {
178         char *name = XGetAtomName(disp, (Atom)ret);
179         if (name && name[0] == '-') {
180             char *strings[13];
181             char *dupname, *extrafree = NULL, *ret;
182             char *p, *q;
183             int nstr;
184
185             p = q = dupname = dupstr(name); /* skip initial minus */
186             nstr = 0;
187
188             while (*p && nstr < lenof(strings)) {
189                 if (*p == '-') {
190                     *p = '\0';
191                     strings[nstr++] = p+1;
192                 }
193                 p++;
194             }
195
196             if (nstr < lenof(strings))
197                 return NULL;           /* XLFD was malformed */
198
199             if (bold)
200                 strings[2] = "bold";
201
202             if (wide) {
203                 /* 4 is `wideness', which obviously may have changed. */
204                 /* 5 is additional style, which may be e.g. `ja' or `ko'. */
205                 strings[4] = strings[5] = "*";
206                 strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11]));
207             }
208
209             ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2],
210                          "-", strings[ 3], "-", strings[ 4], "-", strings[ 5],
211                          "-", strings[ 6], "-", strings[ 7], "-", strings[ 8],
212                          "-", strings[ 9], "-", strings[10], "-", strings[11],
213                          "-", strings[12], NULL);
214             sfree(extrafree);
215             sfree(dupname);
216
217             return ret;
218         }
219     }
220     return NULL;
221 }
222
223 static int x11_font_width(GdkFont *font, int sixteen_bit)
224 {
225     if (sixteen_bit) {
226         XChar2b space;
227         space.byte1 = 0;
228         space.byte2 = ' ';
229         return gdk_text_width(font, (const gchar *)&space, 2);
230     } else {
231         return gdk_char_width(font, ' ');
232     }
233 }
234
235 static unifont *x11font_create(GtkWidget *widget, const char *name,
236                                int wide, int bold,
237                                int shadowoffset, int shadowalways)
238 {
239     struct x11font *xfont;
240     GdkFont *font;
241     XFontStruct *xfs;
242     Display *disp;
243     Atom charset_registry, charset_encoding;
244     unsigned long registry_ret, encoding_ret;
245     int pubcs, realcs, sixteen_bit;
246     int i;
247
248     font = gdk_font_load(name);
249     if (!font)
250         return NULL;
251
252     xfs = GDK_FONT_XFONT(font);
253     disp = GDK_FONT_XDISPLAY(font);
254
255     charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False);
256     charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False);
257
258     pubcs = realcs = CS_NONE;
259     sixteen_bit = FALSE;
260
261     if (XGetFontProperty(xfs, charset_registry, &registry_ret) &&
262         XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
263         char *reg, *enc;
264         reg = XGetAtomName(disp, (Atom)registry_ret);
265         enc = XGetAtomName(disp, (Atom)encoding_ret);
266         if (reg && enc) {
267             char *encoding = dupcat(reg, "-", enc, NULL);
268             pubcs = realcs = charset_from_xenc(encoding);
269
270             /*
271              * iso10646-1 is the only wide font encoding we
272              * support. In this case, we expect clients to give us
273              * UTF-8, which this module must internally convert
274              * into 16-bit Unicode.
275              */
276             if (!strcasecmp(encoding, "iso10646-1")) {
277                 sixteen_bit = TRUE;
278                 pubcs = realcs = CS_UTF8;
279             }
280
281             /*
282              * Hack for X line-drawing characters: if the primary
283              * font is encoded as ISO-8859-1, and has valid glyphs
284              * in the first 32 char positions, it is assumed that
285              * those glyphs are the VT100 line-drawing character
286              * set.
287              * 
288              * Actually, we'll hack even harder by only checking
289              * position 0x19 (vertical line, VT100 linedrawing
290              * `x'). Then we can check it easily by seeing if the
291              * ascent and descent differ.
292              */
293             if (pubcs == CS_ISO8859_1) {
294                 int lb, rb, wid, asc, desc;
295                 gchar text[2];
296
297                 text[1] = '\0';
298                 text[0] = '\x12';
299                 gdk_string_extents(font, text, &lb, &rb, &wid, &asc, &desc);
300                 if (asc != desc)
301                     realcs = CS_ISO8859_1_X11;
302             }
303
304             sfree(encoding);
305         }
306     }
307
308     xfont = snew(struct x11font);
309     xfont->u.vt = &x11font_vtable;
310     xfont->u.width = x11_font_width(font, sixteen_bit);
311     xfont->u.ascent = font->ascent;
312     xfont->u.descent = font->descent;
313     xfont->u.height = xfont->u.ascent + xfont->u.descent;
314     xfont->u.public_charset = pubcs;
315     xfont->u.real_charset = realcs;
316     xfont->fonts[0] = font;
317     xfont->allocated[0] = TRUE;
318     xfont->sixteen_bit = sixteen_bit;
319     xfont->wide = wide;
320     xfont->bold = bold;
321     xfont->shadowoffset = shadowoffset;
322     xfont->shadowalways = shadowalways;
323
324     for (i = 1; i < lenof(xfont->fonts); i++) {
325         xfont->fonts[i] = NULL;
326         xfont->allocated[i] = FALSE;
327     }
328
329     return (unifont *)xfont;
330 }
331
332 static void x11font_destroy(unifont *font)
333 {
334     struct x11font *xfont = (struct x11font *)font;
335     int i;
336
337     for (i = 0; i < lenof(xfont->fonts); i++)
338         if (xfont->fonts[i])
339             gdk_font_unref(xfont->fonts[i]);
340     sfree(font);
341 }
342
343 static void x11_alloc_subfont(struct x11font *xfont, int sfid)
344 {
345     char *derived_name = x11_guess_derived_font_name
346         (xfont->fonts[0], sfid & 1, !!(sfid & 2));
347     xfont->fonts[sfid] = gdk_font_load(derived_name);   /* may be NULL */
348     xfont->allocated[sfid] = TRUE;
349     sfree(derived_name);
350 }
351
352 static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
353                               int x, int y, const char *string, int len,
354                               int wide, int bold, int cellwidth)
355 {
356     struct x11font *xfont = (struct x11font *)font;
357     int sfid;
358     int shadowbold = FALSE;
359
360     wide -= xfont->wide;
361     bold -= xfont->bold;
362
363     /*
364      * Decide which subfont we're using, and whether we have to
365      * use shadow bold.
366      */
367     if (xfont->shadowalways && bold) {
368         shadowbold = TRUE;
369         bold = 0;
370     }
371     sfid = 2 * wide + bold;
372     if (!xfont->allocated[sfid])
373         x11_alloc_subfont(xfont, sfid);
374     if (bold && !xfont->fonts[sfid]) {
375         bold = 0;
376         shadowbold = TRUE;
377         sfid = 2 * wide + bold;
378         if (!xfont->allocated[sfid])
379             x11_alloc_subfont(xfont, sfid);
380     }
381
382     if (!xfont->fonts[sfid])
383         return;                        /* we've tried our best, but no luck */
384
385     if (xfont->sixteen_bit) {
386         /*
387          * This X font has 16-bit character indices, which means
388          * we expect our string to have been passed in UTF-8.
389          */
390         XChar2b *xcs;
391         wchar_t *wcs;
392         int nchars, maxchars, i;
393
394         /*
395          * Convert the input string to wide-character Unicode.
396          */
397         maxchars = 0;
398         for (i = 0; i < len; i++)
399             if ((unsigned char)string[i] <= 0x7F ||
400                 (unsigned char)string[i] >= 0xC0)
401                 maxchars++;
402         wcs = snewn(maxchars+1, wchar_t);
403         nchars = charset_to_unicode((char **)&string, &len, wcs, maxchars,
404                                     CS_UTF8, NULL, NULL, 0);
405         assert(nchars <= maxchars);
406         wcs[nchars] = L'\0';
407
408         xcs = snewn(nchars, XChar2b);
409         for (i = 0; i < nchars; i++) {
410             xcs[i].byte1 = wcs[i] >> 8;
411             xcs[i].byte2 = wcs[i];
412         }
413
414         gdk_draw_text(target, xfont->fonts[sfid], gc,
415                       x, y, (gchar *)xcs, nchars*2);
416         if (shadowbold)
417             gdk_draw_text(target, xfont->fonts[sfid], gc,
418                           x + xfont->shadowoffset, y, (gchar *)xcs, nchars*2);
419         sfree(xcs);
420         sfree(wcs);
421     } else {
422         gdk_draw_text(target, xfont->fonts[sfid], gc, x, y, string, len);
423         if (shadowbold)
424             gdk_draw_text(target, xfont->fonts[sfid], gc,
425                           x + xfont->shadowoffset, y, string, len);
426     }
427 }
428
429 static void x11font_enum_fonts(GtkWidget *widget,
430                                fontsel_add_entry callback, void *callback_ctx)
431 {
432     char **fontnames;
433     char *tmp = NULL;
434     int nnames, i, max, tmpsize;
435
436     max = 32768;
437     while (1) {
438         fontnames = XListFonts(GDK_DISPLAY(), "*", max, &nnames);
439         if (nnames >= max) {
440             XFreeFontNames(fontnames);
441             max *= 2;
442         } else
443             break;
444     }
445
446     tmpsize = 0;
447
448     for (i = 0; i < nnames; i++) {
449         if (fontnames[i][0] == '-') {
450             /*
451              * Dismember an XLFD and convert it into the format
452              * we'll be using in the font selector.
453              */
454             char *components[14];
455             char *p, *font, *style, *charset;
456             int j, thistmpsize, fontsize, flags;
457
458             thistmpsize = 3 * strlen(fontnames[i]) + 256;
459             if (tmpsize < thistmpsize) {
460                 tmpsize = thistmpsize;
461                 tmp = sresize(tmp, tmpsize, char);
462             }
463             strcpy(tmp, fontnames[i]);
464
465             p = tmp;
466             for (j = 0; j < 14; j++) {
467                 if (*p)
468                     *p++ = '\0';
469                 components[j] = p;
470                 while (*p && *p != '-')
471                     p++;
472             }
473             *p++ = '\0';
474
475             /*
476              * Font name is made up of fields 0 and 1, in reverse
477              * order with parentheses. (This is what the GTK 1.2 X
478              * font selector does, and it seems to come out
479              * looking reasonably sensible.)
480              */
481             font = p;
482             p += 1 + sprintf(p, "%s (%s)", components[1], components[0]);
483
484             /*
485              * Charset is made up of fields 12 and 13.
486              */
487             charset = p;
488             p += 1 + sprintf(p, "%s-%s", components[12], components[13]);
489
490             /*
491              * Style is a mixture of the weight, slant, set_width
492              * and spacing fields (respectively 2, 3, 4 and 10)
493              * with some strange formatting. (Again, cribbed
494              * entirely from the GTK 1.2 font selector.)
495              */
496             style = p;
497             p += sprintf(p, "%s", components[2][0] ? components[2] :
498                          "regular");
499             if (!g_strcasecmp(components[3], "i"))
500                 p += sprintf(p, " italic");
501             else if (!g_strcasecmp(components[3], "o"))
502                 p += sprintf(p, " oblique");
503             else if (!g_strcasecmp(components[3], "ri"))
504                 p += sprintf(p, " reverse italic");
505             else if (!g_strcasecmp(components[3], "ro"))
506                 p += sprintf(p, " reverse oblique");
507             else if (!g_strcasecmp(components[3], "ot"))
508                 p += sprintf(p, " other-slant");
509             if (components[4][0] && g_strcasecmp(components[4], "normal"))
510                 p += sprintf(p, " %s", components[4]);
511             if (!g_strcasecmp(components[10], "m"))
512                 p += sprintf(p, " [M]");
513             if (!g_strcasecmp(components[10], "c"))
514                 p += sprintf(p, " [C]");
515
516             /*
517              * Size is in pixels, for our application, so we
518              * derive it directly from the pixel size field,
519              * number 6.
520              */
521             fontsize = atoi(components[6]);
522
523             /*
524              * Flags: we need to know whether this is a monospaced
525              * font, which we do by examining the spacing field
526              * again.
527              */
528             flags = FONTFLAG_SERVERSIDE;
529             if (!strchr("CcMm", components[10][0]))
530                 flags |= FONTFLAG_NONMONOSPACED;
531
532             /*
533              * Not sure why, but sometimes the X server will
534              * deliver dummy font types in which fontsize comes
535              * out as zero. Filter those out.
536              */
537             if (fontsize)
538                 callback(callback_ctx, fontnames[i], font, charset,
539                          style, fontsize, flags, &x11font_vtable);
540         } else {
541             /*
542              * This isn't an XLFD, so it must be an alias.
543              * Transmit it with mostly null data.
544              * 
545              * It would be nice to work out if it's monospaced
546              * here, but at the moment I can't see that being
547              * anything but computationally hideous. Ah well.
548              */
549             callback(callback_ctx, fontnames[i], fontnames[i], NULL,
550                      NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable);
551         }
552     }
553     XFreeFontNames(fontnames);
554 }
555
556 static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
557                                        int *size)
558 {
559     /*
560      * When given an X11 font name to try to make sense of for a
561      * font selector, we must attempt to load it (to see if it
562      * exists), and then canonify it by extracting its FONT
563      * property, which should give its full XLFD even if what we
564      * originally had was an alias.
565      */
566     GdkFont *font = gdk_font_load(name);
567     XFontStruct *xfs;
568     Display *disp;
569     Atom fontprop, fontprop2;
570     unsigned long ret;
571
572     if (!font)
573         return NULL;                   /* didn't make sense to us, sorry */
574
575     gdk_font_ref(font);
576
577     xfs = GDK_FONT_XFONT(font);
578     disp = GDK_FONT_XDISPLAY(font);
579     fontprop = XInternAtom(disp, "FONT", False);
580
581     if (XGetFontProperty(xfs, fontprop, &ret)) {
582         char *name = XGetAtomName(disp, (Atom)ret);
583         if (name) {
584             unsigned long fsize = 12;
585
586             fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False);
587             if (XGetFontProperty(xfs, fontprop2, &fsize)) {
588                 *size = fsize;
589                 gdk_font_unref(font);
590                 return dupstr(name);
591             }
592         }
593     }
594
595     gdk_font_unref(font);
596     return NULL;                       /* something went wrong */
597 }
598
599 static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
600                                     int size)
601 {
602     return NULL;                       /* shan't */
603 }
604
605 /* ----------------------------------------------------------------------
606  * Pango font implementation.
607  */
608
609 static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
610                                 int x, int y, const char *string, int len,
611                                 int wide, int bold, int cellwidth);
612 static unifont *pangofont_create(GtkWidget *widget, const char *name,
613                                  int wide, int bold,
614                                  int shadowoffset, int shadowalways);
615 static void pangofont_destroy(unifont *font);
616 static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
617                                  void *callback_ctx);
618 static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
619                                          int *size);
620 static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
621                                       int size);
622
623 struct pangofont {
624     struct unifont u;
625     /*
626      * Pango objects.
627      */
628     PangoFontDescription *desc;
629     PangoFontset *fset;
630     /*
631      * The containing widget.
632      */
633     GtkWidget *widget;
634     /*
635      * Data passed in to unifont_create().
636      */
637     int bold, shadowoffset, shadowalways;
638 };
639
640 static const struct unifont_vtable pangofont_vtable = {
641     pangofont_create,
642     pangofont_destroy,
643     pangofont_draw_text,
644     pangofont_enum_fonts,
645     pangofont_canonify_fontname,
646     pangofont_scale_fontname,
647     "client"
648 };
649
650 static unifont *pangofont_create(GtkWidget *widget, const char *name,
651                                  int wide, int bold,
652                                  int shadowoffset, int shadowalways)
653 {
654     struct pangofont *pfont;
655     PangoContext *ctx;
656 #ifndef PANGO_PRE_1POINT6
657     PangoFontMap *map;
658 #endif
659     PangoFontDescription *desc;
660     PangoFontset *fset;
661     PangoFontMetrics *metrics;
662
663     desc = pango_font_description_from_string(name);
664     if (!desc)
665         return NULL;
666     ctx = gtk_widget_get_pango_context(widget);
667     if (!ctx) {
668         pango_font_description_free(desc);
669         return NULL;
670     }
671 #ifndef PANGO_PRE_1POINT6
672     map = pango_context_get_font_map(ctx);
673     if (!map) {
674         pango_font_description_free(desc);
675         return NULL;
676     }
677     fset = pango_font_map_load_fontset(map, ctx, desc,
678                                        pango_context_get_language(ctx));
679 #else
680     fset = pango_context_load_fontset(ctx, desc,
681                                       pango_context_get_language(ctx));
682 #endif
683     if (!fset) {
684         pango_font_description_free(desc);
685         return NULL;
686     }
687     metrics = pango_fontset_get_metrics(fset);
688     if (!metrics ||
689         pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
690         pango_font_description_free(desc);
691         g_object_unref(fset);
692         return NULL;
693     }
694
695     pfont = snew(struct pangofont);
696     pfont->u.vt = &pangofont_vtable;
697     pfont->u.width =
698         PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics));
699     pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics));
700     pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics));
701     pfont->u.height = pfont->u.ascent + pfont->u.descent;
702     /* The Pango API is hardwired to UTF-8 */
703     pfont->u.public_charset = CS_UTF8;
704     pfont->u.real_charset = CS_UTF8;
705     pfont->desc = desc;
706     pfont->fset = fset;
707     pfont->widget = widget;
708     pfont->bold = bold;
709     pfont->shadowoffset = shadowoffset;
710     pfont->shadowalways = shadowalways;
711
712     pango_font_metrics_unref(metrics);
713
714     return (unifont *)pfont;
715 }
716
717 static void pangofont_destroy(unifont *font)
718 {
719     struct pangofont *pfont = (struct pangofont *)font;
720     pango_font_description_free(pfont->desc);
721     g_object_unref(pfont->fset);
722     sfree(font);
723 }
724
725 static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
726                                 int x, int y, const char *string, int len,
727                                 int wide, int bold, int cellwidth)
728 {
729     struct pangofont *pfont = (struct pangofont *)font;
730     PangoLayout *layout;
731     PangoRectangle rect;
732     int shadowbold = FALSE;
733
734     if (wide)
735         cellwidth *= 2;
736
737     y -= pfont->u.ascent;
738
739     layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget));
740     pango_layout_set_font_description(layout, pfont->desc);
741     if (bold > pfont->bold) {
742         if (pfont->shadowalways)
743             shadowbold = TRUE;
744         else {
745             PangoFontDescription *desc2 =
746                 pango_font_description_copy_static(pfont->desc);
747             pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD);
748             pango_layout_set_font_description(layout, desc2);
749         }
750     }
751
752     while (len > 0) {
753         int clen;
754
755         /*
756          * Extract a single UTF-8 character from the string.
757          */
758         clen = 1;
759         while (clen < len &&
760                (unsigned char)string[clen] >= 0x80 &&
761                (unsigned char)string[clen] < 0xC0)
762             clen++;
763
764         pango_layout_set_text(layout, string, clen);
765         pango_layout_get_pixel_extents(layout, NULL, &rect);
766         gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2,
767                         y + (pfont->u.height - rect.height)/2, layout);
768         if (shadowbold)
769             gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2 + pfont->shadowoffset,
770                             y + (pfont->u.height - rect.height)/2, layout);
771
772         len -= clen;
773         string += clen;
774         x += cellwidth;
775     }
776
777     g_object_unref(layout);
778 }
779
780 /*
781  * Dummy size value to be used when converting a
782  * PangoFontDescription of a scalable font to a string for
783  * internal use.
784  */
785 #define PANGO_DUMMY_SIZE 12
786
787 static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
788                                  void *callback_ctx)
789 {
790     PangoContext *ctx;
791 #ifndef PANGO_PRE_1POINT6
792     PangoFontMap *map;
793 #endif
794     PangoFontFamily **families;
795     int i, nfamilies;
796
797     ctx = gtk_widget_get_pango_context(widget);
798     if (!ctx)
799         return;
800
801     /*
802      * Ask Pango for a list of font families, and iterate through
803      * them.
804      */
805 #ifndef PANGO_PRE_1POINT6
806     map = pango_context_get_font_map(ctx);
807     if (!map)
808         return;
809     pango_font_map_list_families(map, &families, &nfamilies);
810 #else
811     pango_context_list_families(ctx, &families, &nfamilies);
812 #endif
813     for (i = 0; i < nfamilies; i++) {
814         PangoFontFamily *family = families[i];
815         const char *familyname;
816         int flags;
817         PangoFontFace **faces;
818         int j, nfaces;
819
820         /*
821          * Set up our flags for this font family, and get the name
822          * string.
823          */
824         flags = FONTFLAG_CLIENTSIDE;
825 #ifndef PANGO_PRE_1POINT4
826         /*
827          * In very early versions of Pango, we can't tell
828          * monospaced fonts from non-monospaced.
829          */
830         if (!pango_font_family_is_monospace(family))
831             flags |= FONTFLAG_NONMONOSPACED;
832 #endif
833         familyname = pango_font_family_get_name(family);
834
835         /*
836          * Go through the available font faces in this family.
837          */
838         pango_font_family_list_faces(family, &faces, &nfaces);
839         for (j = 0; j < nfaces; j++) {
840             PangoFontFace *face = faces[j];
841             PangoFontDescription *desc;
842             const char *facename;
843             int *sizes;
844             int k, nsizes, dummysize;
845
846             /*
847              * Get the face name string.
848              */
849             facename = pango_font_face_get_face_name(face);
850
851             /*
852              * Set up a font description with what we've got so
853              * far. We'll fill in the size field manually and then
854              * call pango_font_description_to_string() to give the
855              * full real name of the specific font.
856              */
857             desc = pango_font_face_describe(face);
858
859             /*
860              * See if this font has a list of specific sizes.
861              */
862 #ifndef PANGO_PRE_1POINT4
863             pango_font_face_list_sizes(face, &sizes, &nsizes);
864 #else
865             /*
866              * In early versions of Pango, that call wasn't
867              * supported; we just have to assume everything is
868              * scalable.
869              */
870             sizes = NULL;
871 #endif
872             if (!sizes) {
873                 /*
874                  * Write a single entry with a dummy size.
875                  */
876                 dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE;
877                 sizes = &dummysize;
878                 nsizes = 1;
879             }
880
881             /*
882              * If so, go through them one by one.
883              */
884             for (k = 0; k < nsizes; k++) {
885                 char *fullname;
886
887                 pango_font_description_set_size(desc, sizes[k]);
888
889                 fullname = pango_font_description_to_string(desc);
890
891                 /*
892                  * Got everything. Hand off to the callback.
893                  * (The charset string is NULL, because only
894                  * server-side X fonts use it.)
895                  */
896                 callback(callback_ctx, fullname, familyname, NULL, facename,
897                          (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])),
898                          flags, &pangofont_vtable);
899
900                 g_free(fullname);
901             }
902             if (sizes != &dummysize)
903                 g_free(sizes);
904
905             pango_font_description_free(desc);
906         }
907         g_free(faces);
908     }
909     g_free(families);
910 }
911
912 static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
913                                          int *size)
914 {
915     /*
916      * When given a Pango font name to try to make sense of for a
917      * font selector, we must normalise it to PANGO_DUMMY_SIZE and
918      * extract its original size (in pixels) into the `size' field.
919      */
920     PangoContext *ctx;
921 #ifndef PANGO_PRE_1POINT6
922     PangoFontMap *map;
923 #endif
924     PangoFontDescription *desc;
925     PangoFontset *fset;
926     PangoFontMetrics *metrics;
927     char *newname, *retname;
928
929     desc = pango_font_description_from_string(name);
930     if (!desc)
931         return NULL;
932     ctx = gtk_widget_get_pango_context(widget);
933     if (!ctx) {
934         pango_font_description_free(desc);
935         return NULL;
936     }
937 #ifndef PANGO_PRE_1POINT6
938     map = pango_context_get_font_map(ctx);
939     if (!map) {
940         pango_font_description_free(desc);
941         return NULL;
942     }
943     fset = pango_font_map_load_fontset(map, ctx, desc,
944                                        pango_context_get_language(ctx));
945 #else
946     fset = pango_context_load_fontset(ctx, desc,
947                                       pango_context_get_language(ctx));
948 #endif
949     if (!fset) {
950         pango_font_description_free(desc);
951         return NULL;
952     }
953     metrics = pango_fontset_get_metrics(fset);
954     if (!metrics ||
955         pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
956         pango_font_description_free(desc);
957         g_object_unref(fset);
958         return NULL;
959     }
960
961     *size = PANGO_PIXELS(pango_font_description_get_size(desc));
962     pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE);
963     newname = pango_font_description_to_string(desc);
964     retname = dupstr(newname);
965     g_free(newname);
966
967     pango_font_metrics_unref(metrics);
968     pango_font_description_free(desc);
969     g_object_unref(fset);
970
971     return retname;
972 }
973
974 static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
975                                       int size)
976 {
977     PangoFontDescription *desc;
978     char *newname, *retname;
979
980     desc = pango_font_description_from_string(name);
981     if (!desc)
982         return NULL;
983     pango_font_description_set_size(desc, size * PANGO_SCALE);
984     newname = pango_font_description_to_string(desc);
985     retname = dupstr(newname);
986     g_free(newname);
987     pango_font_description_free(desc);
988
989     return retname;
990 }
991
992 /* ----------------------------------------------------------------------
993  * Outermost functions which do the vtable dispatch.
994  */
995
996 /*
997  * Complete list of font-type subclasses. Listed in preference
998  * order for unifont_create(). (That is, in the extremely unlikely
999  * event that the same font name is valid as both a Pango and an
1000  * X11 font, it will be interpreted as the former in the absence
1001  * of an explicit type-disambiguating prefix.)
1002  */
1003 static const struct unifont_vtable *unifont_types[] = {
1004     &pangofont_vtable,
1005     &x11font_vtable,
1006 };
1007
1008 /*
1009  * Function which takes a font name and processes the optional
1010  * scheme prefix. Returns the tail of the font name suitable for
1011  * passing to individual font scheme functions, and also provides
1012  * a subrange of the unifont_types[] array above.
1013  * 
1014  * The return values `start' and `end' denote a half-open interval
1015  * in unifont_types[]; that is, the correct way to iterate over
1016  * them is
1017  * 
1018  *   for (i = start; i < end; i++) {...}
1019  */
1020 static const char *unifont_do_prefix(const char *name, int *start, int *end)
1021 {
1022     int colonpos = strcspn(name, ":");
1023     int i;
1024
1025     if (name[colonpos]) {
1026         /*
1027          * There's a colon prefix on the font name. Use it to work
1028          * out which subclass to use.
1029          */
1030         for (i = 0; i < lenof(unifont_types); i++) {
1031             if (strlen(unifont_types[i]->prefix) == colonpos &&
1032                 !strncmp(unifont_types[i]->prefix, name, colonpos)) {
1033                 *start = i;
1034                 *end = i+1;
1035                 return name + colonpos + 1;
1036             }
1037         }
1038         /*
1039          * None matched, so return an empty scheme list to prevent
1040          * any scheme from being called at all.
1041          */
1042         *start = *end = 0;
1043         return name + colonpos + 1;
1044     } else {
1045         /*
1046          * No colon prefix, so just use all the subclasses.
1047          */
1048         *start = 0;
1049         *end = lenof(unifont_types);
1050         return name;
1051     }
1052 }
1053
1054 unifont *unifont_create(GtkWidget *widget, const char *name, int wide,
1055                         int bold, int shadowoffset, int shadowalways)
1056 {
1057     int i, start, end;
1058
1059     name = unifont_do_prefix(name, &start, &end);
1060
1061     for (i = start; i < end; i++) {
1062         unifont *ret = unifont_types[i]->create(widget, name, wide, bold,
1063                                                 shadowoffset, shadowalways);
1064         if (ret)
1065             return ret;
1066     }
1067     return NULL;                       /* font not found in any scheme */
1068 }
1069
1070 void unifont_destroy(unifont *font)
1071 {
1072     font->vt->destroy(font);
1073 }
1074
1075 void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
1076                        int x, int y, const char *string, int len,
1077                        int wide, int bold, int cellwidth)
1078 {
1079     font->vt->draw_text(target, gc, font, x, y, string, len,
1080                         wide, bold, cellwidth);
1081 }
1082
1083 /* ----------------------------------------------------------------------
1084  * Implementation of a unified font selector.
1085  */
1086
1087 typedef struct fontinfo fontinfo;
1088
1089 typedef struct unifontsel_internal {
1090     /* This must be the structure's first element, for cross-casting */
1091     unifontsel u;
1092     GtkListStore *family_model, *style_model, *size_model;
1093     GtkWidget *family_list, *style_list, *size_entry, *size_list;
1094     GtkWidget *filter_buttons[4];
1095     int filter_flags;
1096     tree234 *fonts_by_realname, *fonts_by_selorder;
1097     fontinfo *selected;
1098     int selsize;
1099     int inhibit_response;  /* inhibit callbacks when we change GUI controls */
1100 } unifontsel_internal;
1101
1102 /*
1103  * The structure held in the tree234s. All the string members are
1104  * part of the same allocated area, so don't need freeing
1105  * separately.
1106  */
1107 struct fontinfo {
1108     char *realname;
1109     char *family, *charset, *style;
1110     int size, flags;
1111     /*
1112      * Fallback sorting key, to permit multiple identical entries
1113      * to exist in the selorder tree.
1114      */
1115     int index;
1116     /*
1117      * Indices mapping fontinfo structures to indices in the list
1118      * boxes. sizeindex is irrelevant if the font is scalable
1119      * (size==0).
1120      */
1121     int familyindex, styleindex, sizeindex;
1122     /*
1123      * The class of font.
1124      */
1125     const struct unifont_vtable *fontclass;
1126 };
1127
1128 static int fontinfo_realname_compare(void *av, void *bv)
1129 {
1130     fontinfo *a = (fontinfo *)av;
1131     fontinfo *b = (fontinfo *)bv;
1132     return g_strcasecmp(a->realname, b->realname);
1133 }
1134
1135 static int fontinfo_realname_find(void *av, void *bv)
1136 {
1137     const char *a = (const char *)av;
1138     fontinfo *b = (fontinfo *)bv;
1139     return g_strcasecmp(a, b->realname);
1140 }
1141
1142 static int strnullcasecmp(const char *a, const char *b)
1143 {
1144     int i;
1145
1146     /*
1147      * If exactly one of the inputs is NULL, it compares before
1148      * the other one.
1149      */
1150     if ((i = (!b) - (!a)) != 0)
1151         return i;
1152
1153     /*
1154      * NULL compares equal.
1155      */
1156     if (!a)
1157         return 0;
1158
1159     /*
1160      * Otherwise, ordinary strcasecmp.
1161      */
1162     return g_strcasecmp(a, b);
1163 }
1164
1165 static int fontinfo_selorder_compare(void *av, void *bv)
1166 {
1167     fontinfo *a = (fontinfo *)av;
1168     fontinfo *b = (fontinfo *)bv;
1169     int i;
1170     if ((i = strnullcasecmp(a->family, b->family)) != 0)
1171         return i;
1172     if ((i = strnullcasecmp(a->charset, b->charset)) != 0)
1173         return i;
1174     if ((i = strnullcasecmp(a->style, b->style)) != 0)
1175         return i;
1176     if (a->size != b->size)
1177         return (a->size < b->size ? -1 : +1);
1178     if (a->index != b->index)
1179         return (a->index < b->index ? -1 : +1);
1180     return 0;
1181 }
1182
1183 static void unifontsel_setup_familylist(unifontsel_internal *fs)
1184 {
1185     GtkTreeIter iter;
1186     int i, listindex, minpos = -1, maxpos = -1;
1187     char *currfamily = NULL;
1188     fontinfo *info;
1189
1190     gtk_list_store_clear(fs->family_model);
1191     listindex = 0;
1192
1193     /*
1194      * Search through the font tree for anything matching our
1195      * current filter criteria. When we find one, add its font
1196      * name to the list box.
1197      */
1198     for (i = 0 ;; i++) {
1199         info = (fontinfo *)index234(fs->fonts_by_selorder, i);
1200         /*
1201          * info may be NULL if we've just run off the end of the
1202          * tree. We must still do a processing pass in that
1203          * situation, in case we had an unfinished font record in
1204          * progress.
1205          */
1206         if (info && (info->flags &~ fs->filter_flags)) {
1207             info->familyindex = -1;
1208             continue;                  /* we're filtering out this font */
1209         }
1210         if (!info || strnullcasecmp(currfamily, info->family)) {
1211             /*
1212              * We've either finished a family, or started a new
1213              * one, or both.
1214              */
1215             if (currfamily) {
1216                 gtk_list_store_append(fs->family_model, &iter);
1217                 gtk_list_store_set(fs->family_model, &iter,
1218                                    0, currfamily, 1, minpos, 2, maxpos+1, -1);
1219                 listindex++;
1220             }
1221             if (info) {
1222                 minpos = i;
1223                 currfamily = info->family;
1224             }
1225         }
1226         if (!info)
1227             break;                     /* now we're done */
1228         info->familyindex = listindex;
1229         maxpos = i;
1230     }
1231 }
1232
1233 static void unifontsel_setup_stylelist(unifontsel_internal *fs,
1234                                        int start, int end)
1235 {
1236     GtkTreeIter iter;
1237     int i, listindex, minpos = -1, maxpos = -1, started = FALSE;
1238     char *currcs = NULL, *currstyle = NULL;
1239     fontinfo *info;
1240
1241     gtk_list_store_clear(fs->style_model);
1242     listindex = 0;
1243     started = FALSE;
1244
1245     /*
1246      * Search through the font tree for anything matching our
1247      * current filter criteria. When we find one, add its charset
1248      * and/or style name to the list box.
1249      */
1250     for (i = start; i <= end; i++) {
1251         if (i == end)
1252             info = NULL;
1253         else
1254             info = (fontinfo *)index234(fs->fonts_by_selorder, i);
1255         /*
1256          * info may be NULL if we've just run off the end of the
1257          * relevant data. We must still do a processing pass in
1258          * that situation, in case we had an unfinished font
1259          * record in progress.
1260          */
1261         if (info && (info->flags &~ fs->filter_flags)) {
1262             info->styleindex = -1;
1263             continue;                  /* we're filtering out this font */
1264         }
1265         if (!info || !started || strnullcasecmp(currcs, info->charset) ||
1266              strnullcasecmp(currstyle, info->style)) {
1267             /*
1268              * We've either finished a style/charset, or started a
1269              * new one, or both.
1270              */
1271             started = TRUE;
1272             if (currstyle) {
1273                 gtk_list_store_append(fs->style_model, &iter);
1274                 gtk_list_store_set(fs->style_model, &iter,
1275                                    0, currstyle, 1, minpos, 2, maxpos+1,
1276                                    3, TRUE, -1);
1277                 listindex++;
1278             }
1279             if (info) {
1280                 minpos = i;
1281                 if (info->charset && strnullcasecmp(currcs, info->charset)) {
1282                     gtk_list_store_append(fs->style_model, &iter);
1283                     gtk_list_store_set(fs->style_model, &iter,
1284                                        0, info->charset, 1, -1, 2, -1,
1285                                        3, FALSE, -1);
1286                     listindex++;
1287                 }
1288                 currcs = info->charset;
1289                 currstyle = info->style;
1290             }
1291         }
1292         if (!info)
1293             break;                     /* now we're done */
1294         info->styleindex = listindex;
1295         maxpos = i;
1296     }
1297 }
1298
1299 static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 };
1300
1301 static void unifontsel_setup_sizelist(unifontsel_internal *fs,
1302                                       int start, int end)
1303 {
1304     GtkTreeIter iter;
1305     int i, listindex;
1306     char sizetext[40];
1307     fontinfo *info;
1308
1309     gtk_list_store_clear(fs->size_model);
1310     listindex = 0;
1311
1312     /*
1313      * Search through the font tree for anything matching our
1314      * current filter criteria. When we find one, add its font
1315      * name to the list box.
1316      */
1317     for (i = start; i < end; i++) {
1318         info = (fontinfo *)index234(fs->fonts_by_selorder, i);
1319         if (info->flags &~ fs->filter_flags) {
1320             info->sizeindex = -1;
1321             continue;                  /* we're filtering out this font */
1322         }
1323         if (info->size) {
1324             sprintf(sizetext, "%d", info->size);
1325             info->sizeindex = listindex;
1326             gtk_list_store_append(fs->size_model, &iter);
1327             gtk_list_store_set(fs->size_model, &iter,
1328                                0, sizetext, 1, i, 2, info->size, -1);
1329             listindex++;
1330         } else {
1331             int j;
1332
1333             assert(i == start);
1334             assert(i+1 == end);
1335
1336             for (j = 0; j < lenof(unifontsel_default_sizes); j++) {
1337                 sprintf(sizetext, "%d", unifontsel_default_sizes[j]);
1338                 gtk_list_store_append(fs->size_model, &iter);
1339                 gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i,
1340                                    2, unifontsel_default_sizes[j], -1);
1341                 listindex++;
1342             }
1343         }
1344     }
1345 }
1346
1347 static void unifontsel_set_filter_buttons(unifontsel_internal *fs)
1348 {
1349     int i;
1350
1351     for (i = 0; i < lenof(fs->filter_buttons); i++) {
1352         int flagbit = GPOINTER_TO_INT(gtk_object_get_data
1353                                       (GTK_OBJECT(fs->filter_buttons[i]),
1354                                        "user-data"));
1355         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]),
1356                                      !!(fs->filter_flags & flagbit));
1357     }
1358 }
1359
1360 static void unifontsel_select_font(unifontsel_internal *fs,
1361                                    fontinfo *info, int size, int leftlist)
1362 {
1363     int index;
1364     int minval, maxval;
1365     GtkTreePath *treepath;
1366     GtkTreeIter iter;
1367
1368     fs->inhibit_response = TRUE;
1369
1370     fs->selected = info;
1371     fs->selsize = size;
1372
1373     /*
1374      * Find the index of this fontinfo in the selorder list. 
1375      */
1376     index = -1;
1377     findpos234(fs->fonts_by_selorder, info, NULL, &index);
1378     assert(index >= 0);
1379
1380     /*
1381      * Adjust the font selector flags and redo the font family
1382      * list box, if necessary.
1383      */
1384     if (leftlist <= 0 &&
1385         (fs->filter_flags | info->flags) != fs->filter_flags) {
1386         fs->filter_flags |= info->flags;
1387         unifontsel_set_filter_buttons(fs);
1388         unifontsel_setup_familylist(fs);
1389     }
1390
1391     /*
1392      * Find the appropriate family name and select it in the list.
1393      */
1394     assert(info->familyindex >= 0);
1395     treepath = gtk_tree_path_new_from_indices(info->familyindex, -1);
1396     gtk_tree_selection_select_path
1397         (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)),
1398          treepath);
1399     gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list),
1400                                  treepath, NULL, FALSE, 0.0, 0.0);
1401     gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath);
1402     gtk_tree_path_free(treepath);
1403
1404     /*
1405      * Now set up the font style list.
1406      */
1407     gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter,
1408                        1, &minval, 2, &maxval, -1);
1409     if (leftlist <= 1)
1410         unifontsel_setup_stylelist(fs, minval, maxval);
1411
1412     /*
1413      * Find the appropriate style name and select it in the list.
1414      */
1415     if (info->style) {
1416         assert(info->styleindex >= 0);
1417         treepath = gtk_tree_path_new_from_indices(info->styleindex, -1);
1418         gtk_tree_selection_select_path
1419             (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)),
1420              treepath);
1421         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list),
1422                                      treepath, NULL, FALSE, 0.0, 0.0);
1423         gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model),
1424                                 &iter, treepath);
1425         gtk_tree_path_free(treepath);
1426
1427         /*
1428          * And set up the size list.
1429          */
1430         gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter,
1431                            1, &minval, 2, &maxval, -1);
1432         if (leftlist <= 2)
1433             unifontsel_setup_sizelist(fs, minval, maxval);
1434
1435         /*
1436          * Find the appropriate size, and select it in the list.
1437          */
1438         if (info->size) {
1439             assert(info->sizeindex >= 0);
1440             treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1);
1441             gtk_tree_selection_select_path
1442                 (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)),
1443                  treepath);
1444             gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
1445                                          treepath, NULL, FALSE, 0.0, 0.0);
1446             gtk_tree_path_free(treepath);
1447             size = info->size;
1448         } else {
1449             int j;
1450             for (j = 0; j < lenof(unifontsel_default_sizes); j++)
1451                 if (unifontsel_default_sizes[j] == size) {
1452                     treepath = gtk_tree_path_new_from_indices(j, -1);
1453                     gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list),
1454                                              treepath, NULL, FALSE);
1455                     gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
1456                                                  treepath, NULL, FALSE, 0.0,
1457                                                  0.0);
1458                     gtk_tree_path_free(treepath);
1459                 }
1460         }
1461
1462         /*
1463          * And set up the font size text entry box.
1464          */
1465         {
1466             char sizetext[40];
1467             sprintf(sizetext, "%d", size);
1468             gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext);
1469         }
1470     } else {
1471         if (leftlist <= 2)
1472             unifontsel_setup_sizelist(fs, 0, 0);
1473         gtk_entry_set_text(GTK_ENTRY(fs->size_entry), "");
1474     }
1475
1476     /*
1477      * Grey out the font size edit box if we're not using a
1478      * scalable font.
1479      */
1480     gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0);
1481     gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0);
1482
1483     fs->inhibit_response = FALSE;
1484 }
1485
1486 static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data)
1487 {
1488     unifontsel_internal *fs = (unifontsel_internal *)data;
1489     int newstate = gtk_toggle_button_get_active(tb);
1490     int newflags;
1491     int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb),
1492                                                       "user-data"));
1493
1494     if (newstate)
1495         newflags = fs->filter_flags | flagbit;
1496     else
1497         newflags = fs->filter_flags & ~flagbit;
1498
1499     if (fs->filter_flags != newflags) {
1500         fs->filter_flags = newflags;
1501         unifontsel_setup_familylist(fs);
1502     }
1503 }
1504
1505 static void unifontsel_add_entry(void *ctx, const char *realfontname,
1506                                  const char *family, const char *charset,
1507                                  const char *style, int size, int flags,
1508                                  const struct unifont_vtable *fontclass)
1509 {
1510     unifontsel_internal *fs = (unifontsel_internal *)ctx;
1511     fontinfo *info;
1512     int totalsize;
1513     char *p;
1514
1515     totalsize = sizeof(fontinfo) + strlen(realfontname) +
1516         (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) +
1517         (style ? strlen(style) : 0) + 10;
1518     info = (fontinfo *)smalloc(totalsize);
1519     info->fontclass = fontclass;
1520     p = (char *)info + sizeof(fontinfo);
1521     info->realname = p;
1522     strcpy(p, realfontname);
1523     p += 1+strlen(p);
1524     if (family) {
1525         info->family = p;
1526         strcpy(p, family);
1527         p += 1+strlen(p);
1528     } else
1529         info->family = NULL;
1530     if (charset) {
1531         info->charset = p;
1532         strcpy(p, charset);
1533         p += 1+strlen(p);
1534     } else
1535         info->charset = NULL;
1536     if (style) {
1537         info->style = p;
1538         strcpy(p, style);
1539         p += 1+strlen(p);
1540     } else
1541         info->style = NULL;
1542     assert(p - (char *)info <= totalsize);
1543     info->size = size;
1544     info->flags = flags;
1545     info->index = count234(fs->fonts_by_selorder);
1546
1547     /*
1548      * It's just conceivable that a misbehaving font enumerator
1549      * might tell us about the same font real name more than once,
1550      * in which case we should silently drop the new one.
1551      */
1552     if (add234(fs->fonts_by_realname, info) != info) {
1553         sfree(info);
1554         return;
1555     }
1556     /*
1557      * However, we should never get a duplicate key in the
1558      * selorder tree, because the index field carefully
1559      * disambiguates otherwise identical records.
1560      */
1561     add234(fs->fonts_by_selorder, info);
1562 }
1563
1564 static void family_changed(GtkTreeSelection *treeselection, gpointer data)
1565 {
1566     unifontsel_internal *fs = (unifontsel_internal *)data;
1567     GtkTreeModel *treemodel;
1568     GtkTreeIter treeiter;
1569     int minval;
1570     fontinfo *info;
1571
1572     if (fs->inhibit_response)          /* we made this change ourselves */
1573         return;
1574
1575     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
1576         return;
1577
1578     gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
1579     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
1580     unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, 1);
1581 }
1582
1583 static void style_changed(GtkTreeSelection *treeselection, gpointer data)
1584 {
1585     unifontsel_internal *fs = (unifontsel_internal *)data;
1586     GtkTreeModel *treemodel;
1587     GtkTreeIter treeiter;
1588     int minval;
1589     fontinfo *info;
1590
1591     if (fs->inhibit_response)          /* we made this change ourselves */
1592         return;
1593
1594     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
1595         return;
1596
1597     gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
1598     if (minval < 0)
1599         return;                    /* somehow a charset heading got clicked */
1600     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
1601     unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, 2);
1602 }
1603
1604 static void size_changed(GtkTreeSelection *treeselection, gpointer data)
1605 {
1606     unifontsel_internal *fs = (unifontsel_internal *)data;
1607     GtkTreeModel *treemodel;
1608     GtkTreeIter treeiter;
1609     int minval, size;
1610     fontinfo *info;
1611
1612     if (fs->inhibit_response)          /* we made this change ourselves */
1613         return;
1614
1615     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
1616         return;
1617
1618     gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1);
1619     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
1620     unifontsel_select_font(fs, info, info->size ? info->size : size, 3);
1621 }
1622
1623 static void size_entry_changed(GtkEditable *ed, gpointer data)
1624 {
1625     unifontsel_internal *fs = (unifontsel_internal *)data;
1626     const char *text;
1627     int size;
1628
1629     if (fs->inhibit_response)          /* we made this change ourselves */
1630         return;
1631
1632     text = gtk_entry_get_text(GTK_ENTRY(ed));
1633     size = atoi(text);
1634
1635     if (size > 0) {
1636         assert(fs->selected->size == 0);
1637         unifontsel_select_font(fs, fs->selected, size, 3);
1638     }
1639 }
1640
1641 unifontsel *unifontsel_new(const char *wintitle)
1642 {
1643     unifontsel_internal *fs = snew(unifontsel_internal);
1644     GtkWidget *table, *label, *w, *scroll;
1645     GtkListStore *model;
1646     GtkTreeViewColumn *column;
1647     int lists_height, font_width, style_width, size_width;
1648     int i;
1649
1650     fs->inhibit_response = FALSE;
1651
1652     {
1653         /*
1654          * Invent some magic size constants.
1655          */
1656         GtkRequisition req;
1657         label = gtk_label_new("Quite Long Font Name (Foundry)");
1658         gtk_widget_size_request(label, &req);
1659         font_width = req.width;
1660         lists_height = 14 * req.height;
1661         gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed");
1662         gtk_widget_size_request(label, &req);
1663         style_width = req.width;
1664         gtk_label_set_text(GTK_LABEL(label), "48000");
1665         gtk_widget_size_request(label, &req);
1666         size_width = req.width;
1667 #if GTK_CHECK_VERSION(2,10,0)
1668         g_object_ref_sink(label);
1669         g_object_unref(label);
1670 #else
1671         gtk_object_sink(GTK_OBJECT(label));
1672 #endif
1673     }
1674
1675     /*
1676      * Create the dialog box and initialise the user-visible
1677      * fields in the returned structure.
1678      */
1679     fs->u.user_data = NULL;
1680     fs->u.window = GTK_WINDOW(gtk_dialog_new());
1681     gtk_window_set_title(fs->u.window, wintitle);
1682     fs->u.cancel_button = gtk_dialog_add_button
1683         (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1684     fs->u.ok_button = gtk_dialog_add_button
1685         (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK);
1686     gtk_widget_grab_default(fs->u.ok_button);
1687
1688     /*
1689      * Now set up the internal fields, including in particular all
1690      * the controls that actually allow the user to select fonts.
1691      */
1692     table = gtk_table_new(3, 8, FALSE);
1693     gtk_widget_show(table);
1694     gtk_table_set_col_spacings(GTK_TABLE(table), 8);
1695     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox),
1696                        table, TRUE, TRUE, 0);
1697
1698     label = gtk_label_new_with_mnemonic("_Font:");
1699     gtk_widget_show(label);
1700     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1701     gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
1702
1703     /*
1704      * The Font list box displays only a string, but additionally
1705      * stores two integers which give the limits within the
1706      * tree234 of the font entries covered by this list entry.
1707      */
1708     model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
1709     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1710     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
1711     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
1712     gtk_widget_show(w);
1713     column = gtk_tree_view_column_new_with_attributes
1714         ("Font", gtk_cell_renderer_text_new(),
1715          "text", 0, (char *)NULL);
1716     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1717     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
1718     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
1719                      "changed", G_CALLBACK(family_changed), fs);
1720
1721     scroll = gtk_scrolled_window_new(NULL, NULL);
1722     gtk_container_add(GTK_CONTAINER(scroll), w);
1723     gtk_widget_show(scroll);
1724     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
1725                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
1726     gtk_widget_set_size_request(scroll, font_width, lists_height);
1727     gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, 0, 0, 0);
1728     fs->family_model = model;
1729     fs->family_list = w;
1730
1731     label = gtk_label_new_with_mnemonic("_Style:");
1732     gtk_widget_show(label);
1733     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1734     gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
1735
1736     /*
1737      * The Style list box can contain insensitive elements
1738      * (character set headings for server-side fonts), so we add
1739      * an extra column to the list store to hold that information.
1740      */
1741     model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
1742                                G_TYPE_BOOLEAN);
1743     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1744     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
1745     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
1746     gtk_widget_show(w);
1747     column = gtk_tree_view_column_new_with_attributes
1748         ("Style", gtk_cell_renderer_text_new(),
1749          "text", 0, "sensitive", 3, (char *)NULL);
1750     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1751     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
1752     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
1753                      "changed", G_CALLBACK(style_changed), fs);
1754
1755     scroll = gtk_scrolled_window_new(NULL, NULL);
1756     gtk_container_add(GTK_CONTAINER(scroll), w);
1757     gtk_widget_show(scroll);
1758     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
1759                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
1760     gtk_widget_set_size_request(scroll, style_width, lists_height);
1761     gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, 0, 0, 0);
1762     fs->style_model = model;
1763     fs->style_list = w;
1764
1765     label = gtk_label_new_with_mnemonic("Si_ze:");
1766     gtk_widget_show(label);
1767     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1768     gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
1769
1770     /*
1771      * The Size label attaches primarily to a text input box so
1772      * that the user can select a size of their choice. The list
1773      * of available sizes is secondary.
1774      */
1775     fs->size_entry = w = gtk_entry_new();
1776     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
1777     gtk_widget_set_size_request(w, size_width, -1);
1778     gtk_widget_show(w);
1779     gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
1780     g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed),
1781                      fs);
1782
1783     model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
1784     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1785     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
1786     gtk_widget_show(w);
1787     column = gtk_tree_view_column_new_with_attributes
1788         ("Size", gtk_cell_renderer_text_new(),
1789          "text", 0, (char *)NULL);
1790     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1791     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
1792     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
1793                      "changed", G_CALLBACK(size_changed), fs);
1794
1795     scroll = gtk_scrolled_window_new(NULL, NULL);
1796     gtk_container_add(GTK_CONTAINER(scroll), w);
1797     gtk_widget_show(scroll);
1798     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
1799                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
1800     gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL,
1801                      GTK_EXPAND | GTK_FILL, 0, 0);
1802     fs->size_model = model;
1803     fs->size_list = w;
1804
1805     /*
1806      * FIXME: preview widget
1807      */
1808     i = 0;
1809     w = gtk_check_button_new_with_label("Show client-side fonts");
1810     gtk_object_set_data(GTK_OBJECT(w), "user-data",
1811                         GINT_TO_POINTER(FONTFLAG_CLIENTSIDE));
1812     gtk_signal_connect(GTK_OBJECT(w), "toggled",
1813                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
1814     gtk_widget_show(w);
1815     fs->filter_buttons[i++] = w;
1816     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0);
1817     w = gtk_check_button_new_with_label("Show server-side fonts");
1818     gtk_object_set_data(GTK_OBJECT(w), "user-data",
1819                         GINT_TO_POINTER(FONTFLAG_SERVERSIDE));
1820     gtk_signal_connect(GTK_OBJECT(w), "toggled",
1821                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
1822     gtk_widget_show(w);
1823     fs->filter_buttons[i++] = w;
1824     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0);
1825     w = gtk_check_button_new_with_label("Show server-side font aliases");
1826     gtk_object_set_data(GTK_OBJECT(w), "user-data",
1827                         GINT_TO_POINTER(FONTFLAG_SERVERALIAS));
1828     gtk_signal_connect(GTK_OBJECT(w), "toggled",
1829                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
1830     gtk_widget_show(w);
1831     fs->filter_buttons[i++] = w;
1832     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0);
1833     w = gtk_check_button_new_with_label("Show non-monospaced fonts");
1834     gtk_object_set_data(GTK_OBJECT(w), "user-data",
1835                         GINT_TO_POINTER(FONTFLAG_NONMONOSPACED));
1836     gtk_signal_connect(GTK_OBJECT(w), "toggled",
1837                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
1838     gtk_widget_show(w);
1839     fs->filter_buttons[i++] = w;
1840     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0);
1841
1842     assert(i == lenof(fs->filter_buttons));
1843     fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE;
1844     unifontsel_set_filter_buttons(fs);
1845
1846     /*
1847      * Go and find all the font names, and set up our master font
1848      * list.
1849      */
1850     fs->fonts_by_realname = newtree234(fontinfo_realname_compare);
1851     fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare);
1852     for (i = 0; i < lenof(unifont_types); i++)
1853         unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window),
1854                                      unifontsel_add_entry, fs);
1855
1856     /*
1857      * And set up the initial font names list.
1858      */
1859     unifontsel_setup_familylist(fs);
1860
1861     fs->selected = NULL;
1862
1863     return (unifontsel *)fs;
1864 }
1865
1866 void unifontsel_destroy(unifontsel *fontsel)
1867 {
1868     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
1869     fontinfo *info;
1870
1871     freetree234(fs->fonts_by_selorder);
1872     while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL)
1873         sfree(info);
1874     freetree234(fs->fonts_by_realname);
1875
1876     gtk_widget_destroy(GTK_WIDGET(fs->u.window));
1877     sfree(fs);
1878 }
1879
1880 void unifontsel_set_name(unifontsel *fontsel, const char *fontname)
1881 {
1882     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
1883     int i, start, end, size;
1884     const char *fontname2 = NULL;
1885     fontinfo *info;
1886
1887     /*
1888      * Provide a default if given an empty or null font name.
1889      */
1890     if (!fontname || !*fontname)
1891         fontname = "fixed";   /* Pango zealots might prefer "Monospace 12" */
1892
1893     /*
1894      * Call the canonify_fontname function.
1895      */
1896     fontname = unifont_do_prefix(fontname, &start, &end);
1897     for (i = start; i < end; i++) {
1898         fontname2 = unifont_types[i]->canonify_fontname
1899             (GTK_WIDGET(fs->u.window), fontname, &size);
1900         if (fontname2)
1901             break;
1902     }
1903     if (i == end)
1904         return;                        /* font name not recognised */
1905
1906     /*
1907      * Now look up the canonified font name in our index.
1908      */
1909     info = find234(fs->fonts_by_realname, (char *)fontname2,
1910                    fontinfo_realname_find);
1911
1912     /*
1913      * If we've found the font, and its size field is either
1914      * correct or zero (the latter indicating a scalable font),
1915      * then we're done. Otherwise, try looking up the original
1916      * font name instead.
1917      */
1918     if (!info || (info->size != size && info->size != 0)) {
1919         info = find234(fs->fonts_by_realname, (char *)fontname,
1920                        fontinfo_realname_find);
1921         if (!info || info->size != size)
1922             return;                    /* font name not in our index */
1923     }
1924
1925     /*
1926      * Now we've got a fontinfo structure and a font size, so we
1927      * know everything we need to fill in all the fields in the
1928      * dialog.
1929      */
1930     unifontsel_select_font(fs, info, size, 0);
1931 }
1932
1933 char *unifontsel_get_name(unifontsel *fontsel)
1934 {
1935     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
1936     char *name;
1937
1938     assert(fs->selected);
1939
1940     if (fs->selected->size == 0) {
1941         name = fs->selected->fontclass->scale_fontname
1942             (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize);
1943         if (name)
1944             return name;
1945     }
1946
1947     return dupstr(fs->selected->realname);
1948 }