]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkfont.c
Unified font selector dialog box. _Extremely_ unfinished - there's a
[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     PangoFontMap *map;
657     PangoFontDescription *desc;
658     PangoFontset *fset;
659     PangoFontMetrics *metrics;
660
661     desc = pango_font_description_from_string(name);
662     if (!desc)
663         return NULL;
664     ctx = gtk_widget_get_pango_context(widget);
665     if (!ctx) {
666         pango_font_description_free(desc);
667         return NULL;
668     }
669     map = pango_context_get_font_map(ctx);
670     if (!map) {
671         pango_font_description_free(desc);
672         return NULL;
673     }
674     fset = pango_font_map_load_fontset(map, ctx, desc,
675                                        pango_context_get_language(ctx));
676     if (!fset) {
677         pango_font_description_free(desc);
678         return NULL;
679     }
680     metrics = pango_fontset_get_metrics(fset);
681     if (!metrics ||
682         pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
683         pango_font_description_free(desc);
684         g_object_unref(fset);
685         return NULL;
686     }
687
688     pfont = snew(struct pangofont);
689     pfont->u.vt = &pangofont_vtable;
690     pfont->u.width =
691         PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics));
692     pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics));
693     pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics));
694     pfont->u.height = pfont->u.ascent + pfont->u.descent;
695     /* The Pango API is hardwired to UTF-8 */
696     pfont->u.public_charset = CS_UTF8;
697     pfont->u.real_charset = CS_UTF8;
698     pfont->desc = desc;
699     pfont->fset = fset;
700     pfont->widget = widget;
701     pfont->bold = bold;
702     pfont->shadowoffset = shadowoffset;
703     pfont->shadowalways = shadowalways;
704
705     pango_font_metrics_unref(metrics);
706
707     return (unifont *)pfont;
708 }
709
710 static void pangofont_destroy(unifont *font)
711 {
712     struct pangofont *pfont = (struct pangofont *)font;
713     pango_font_description_free(pfont->desc);
714     g_object_unref(pfont->fset);
715     sfree(font);
716 }
717
718 static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
719                                 int x, int y, const char *string, int len,
720                                 int wide, int bold, int cellwidth)
721 {
722     struct pangofont *pfont = (struct pangofont *)font;
723     PangoLayout *layout;
724     PangoRectangle rect;
725     int shadowbold = FALSE;
726
727     if (wide)
728         cellwidth *= 2;
729
730     y -= pfont->u.ascent;
731
732     layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget));
733     pango_layout_set_font_description(layout, pfont->desc);
734     if (bold > pfont->bold) {
735         if (pfont->shadowalways)
736             shadowbold = TRUE;
737         else {
738             PangoFontDescription *desc2 =
739                 pango_font_description_copy_static(pfont->desc);
740             pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD);
741             pango_layout_set_font_description(layout, desc2);
742         }
743     }
744
745     while (len > 0) {
746         int clen;
747
748         /*
749          * Extract a single UTF-8 character from the string.
750          */
751         clen = 1;
752         while (clen < len &&
753                (unsigned char)string[clen] >= 0x80 &&
754                (unsigned char)string[clen] < 0xC0)
755             clen++;
756
757         pango_layout_set_text(layout, string, clen);
758         pango_layout_get_pixel_extents(layout, NULL, &rect);
759         gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2,
760                         y + (pfont->u.height - rect.height)/2, layout);
761         if (shadowbold)
762             gdk_draw_layout(target, gc, x + (cellwidth - rect.width)/2 + pfont->shadowoffset,
763                             y + (pfont->u.height - rect.height)/2, layout);
764
765         len -= clen;
766         string += clen;
767         x += cellwidth;
768     }
769
770     g_object_unref(layout);
771 }
772
773 /*
774  * Dummy size value to be used when converting a
775  * PangoFontDescription of a scalable font to a string for
776  * internal use.
777  */
778 #define PANGO_DUMMY_SIZE 12
779
780 static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
781                                  void *callback_ctx)
782 {
783     PangoContext *ctx;
784     PangoFontMap *map;
785     PangoFontFamily **families;
786     int i, nfamilies;
787
788     /*
789      * Find the active font map.
790      */
791     ctx = gtk_widget_get_pango_context(widget);
792     if (!ctx)
793         return;
794     map = pango_context_get_font_map(ctx);
795     if (!map)
796         return;
797
798     /*
799      * Ask the font map for a list of font families, and iterate
800      * through them.
801      */
802     pango_font_map_list_families(map, &families, &nfamilies);
803     for (i = 0; i < nfamilies; i++) {
804         PangoFontFamily *family = families[i];
805         const char *familyname;
806         int flags;
807         PangoFontFace **faces;
808         int j, nfaces;
809
810         /*
811          * Set up our flags for this font family, and get the name
812          * string.
813          */
814         flags = FONTFLAG_CLIENTSIDE;
815         if (!pango_font_family_is_monospace(family))
816             flags |= FONTFLAG_NONMONOSPACED;
817         familyname = pango_font_family_get_name(family);
818
819         /*
820          * Go through the available font faces in this family.
821          */
822         pango_font_family_list_faces(family, &faces, &nfaces);
823         for (j = 0; j < nfaces; j++) {
824             PangoFontFace *face = faces[j];
825             PangoFontDescription *desc;
826             const char *facename;
827             int *sizes;
828             int k, nsizes, dummysize;
829
830             /*
831              * Get the face name string.
832              */
833             facename = pango_font_face_get_face_name(face);
834
835             /*
836              * Set up a font description with what we've got so
837              * far. We'll fill in the size field manually and then
838              * call pango_font_description_to_string() to give the
839              * full real name of the specific font.
840              */
841             desc = pango_font_face_describe(face);
842
843             /*
844              * See if this font has a list of specific sizes.
845              */
846             pango_font_face_list_sizes(face, &sizes, &nsizes);
847             if (!sizes) {
848                 /*
849                  * Write a single entry with a dummy size.
850                  */
851                 dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE;
852                 sizes = &dummysize;
853                 nsizes = 1;
854             }
855
856             /*
857              * If so, go through them one by one.
858              */
859             for (k = 0; k < nsizes; k++) {
860                 char *fullname;
861
862                 pango_font_description_set_size(desc, sizes[k]);
863
864                 fullname = pango_font_description_to_string(desc);
865
866                 /*
867                  * Got everything. Hand off to the callback.
868                  * (The charset string is NULL, because only
869                  * server-side X fonts use it.)
870                  */
871                 callback(callback_ctx, fullname, familyname, NULL, facename,
872                          (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])),
873                          flags, &pangofont_vtable);
874
875                 g_free(fullname);
876             }
877             if (sizes != &dummysize)
878                 g_free(sizes);
879
880             pango_font_description_free(desc);
881         }
882         g_free(faces);
883     }
884     g_free(families);
885 }
886
887 static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
888                                          int *size)
889 {
890     /*
891      * When given a Pango font name to try to make sense of for a
892      * font selector, we must normalise it to PANGO_DUMMY_SIZE and
893      * extract its original size (in pixels) into the `size' field.
894      */
895     PangoContext *ctx;
896     PangoFontMap *map;
897     PangoFontDescription *desc;
898     PangoFontset *fset;
899     PangoFontMetrics *metrics;
900     char *newname, *retname;
901
902     desc = pango_font_description_from_string(name);
903     if (!desc)
904         return NULL;
905     ctx = gtk_widget_get_pango_context(widget);
906     if (!ctx) {
907         pango_font_description_free(desc);
908         return NULL;
909     }
910     map = pango_context_get_font_map(ctx);
911     if (!map) {
912         pango_font_description_free(desc);
913         return NULL;
914     }
915     fset = pango_font_map_load_fontset(map, ctx, desc,
916                                        pango_context_get_language(ctx));
917     if (!fset) {
918         pango_font_description_free(desc);
919         return NULL;
920     }
921     metrics = pango_fontset_get_metrics(fset);
922     if (!metrics ||
923         pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
924         pango_font_description_free(desc);
925         g_object_unref(fset);
926         return NULL;
927     }
928
929     *size = PANGO_PIXELS(pango_font_description_get_size(desc));
930     pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE);
931     newname = pango_font_description_to_string(desc);
932     retname = dupstr(newname);
933     g_free(newname);
934
935     pango_font_metrics_unref(metrics);
936     pango_font_description_free(desc);
937     g_object_unref(fset);
938
939     return retname;
940 }
941
942 static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
943                                       int size)
944 {
945     PangoFontDescription *desc;
946     char *newname, *retname;
947
948     desc = pango_font_description_from_string(name);
949     if (!desc)
950         return NULL;
951     pango_font_description_set_size(desc, size * PANGO_SCALE);
952     newname = pango_font_description_to_string(desc);
953     retname = dupstr(newname);
954     g_free(newname);
955     pango_font_description_free(desc);
956
957     return retname;
958 }
959
960 /* ----------------------------------------------------------------------
961  * Outermost functions which do the vtable dispatch.
962  */
963
964 /*
965  * Complete list of font-type subclasses. Listed in preference
966  * order for unifont_create(). (That is, in the extremely unlikely
967  * event that the same font name is valid as both a Pango and an
968  * X11 font, it will be interpreted as the former in the absence
969  * of an explicit type-disambiguating prefix.)
970  */
971 static const struct unifont_vtable *unifont_types[] = {
972     &pangofont_vtable,
973     &x11font_vtable,
974 };
975
976 /*
977  * Function which takes a font name and processes the optional
978  * scheme prefix. Returns the tail of the font name suitable for
979  * passing to individual font scheme functions, and also provides
980  * a subrange of the unifont_types[] array above.
981  * 
982  * The return values `start' and `end' denote a half-open interval
983  * in unifont_types[]; that is, the correct way to iterate over
984  * them is
985  * 
986  *   for (i = start; i < end; i++) {...}
987  */
988 static const char *unifont_do_prefix(const char *name, int *start, int *end)
989 {
990     int colonpos = strcspn(name, ":");
991     int i;
992
993     if (name[colonpos]) {
994         /*
995          * There's a colon prefix on the font name. Use it to work
996          * out which subclass to use.
997          */
998         for (i = 0; i < lenof(unifont_types); i++) {
999             if (strlen(unifont_types[i]->prefix) == colonpos &&
1000                 !strncmp(unifont_types[i]->prefix, name, colonpos)) {
1001                 *start = i;
1002                 *end = i+1;
1003                 return name + colonpos + 1;
1004             }
1005         }
1006         /*
1007          * None matched, so return an empty scheme list to prevent
1008          * any scheme from being called at all.
1009          */
1010         *start = *end = 0;
1011         return name + colonpos + 1;
1012     } else {
1013         /*
1014          * No colon prefix, so just use all the subclasses.
1015          */
1016         *start = 0;
1017         *end = lenof(unifont_types);
1018         return name;
1019     }
1020 }
1021
1022 unifont *unifont_create(GtkWidget *widget, const char *name, int wide,
1023                         int bold, int shadowoffset, int shadowalways)
1024 {
1025     int i, start, end;
1026
1027     name = unifont_do_prefix(name, &start, &end);
1028
1029     for (i = start; i < end; i++) {
1030         unifont *ret = unifont_types[i]->create(widget, name, wide, bold,
1031                                                 shadowoffset, shadowalways);
1032         if (ret)
1033             return ret;
1034     }
1035     return NULL;                       /* font not found in any scheme */
1036 }
1037
1038 void unifont_destroy(unifont *font)
1039 {
1040     font->vt->destroy(font);
1041 }
1042
1043 void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
1044                        int x, int y, const char *string, int len,
1045                        int wide, int bold, int cellwidth)
1046 {
1047     font->vt->draw_text(target, gc, font, x, y, string, len,
1048                         wide, bold, cellwidth);
1049 }
1050
1051 /* ----------------------------------------------------------------------
1052  * Implementation of a unified font selector.
1053  */
1054
1055 typedef struct fontinfo fontinfo;
1056
1057 typedef struct unifontsel_internal {
1058     /* This must be the structure's first element, for cross-casting */
1059     unifontsel u;
1060     GtkListStore *family_model, *style_model, *size_model;
1061     GtkWidget *family_list, *style_list, *size_entry, *size_list;
1062     GtkWidget *filter_buttons[4];
1063     int filter_flags;
1064     tree234 *fonts_by_realname, *fonts_by_selorder;
1065     fontinfo *selected;
1066     int selsize;
1067     int inhibit_response;  /* inhibit callbacks when we change GUI controls */
1068 } unifontsel_internal;
1069
1070 /*
1071  * The structure held in the tree234s. All the string members are
1072  * part of the same allocated area, so don't need freeing
1073  * separately.
1074  */
1075 struct fontinfo {
1076     char *realname;
1077     char *family, *charset, *style;
1078     int size, flags;
1079     /*
1080      * Fallback sorting key, to permit multiple identical entries
1081      * to exist in the selorder tree.
1082      */
1083     int index;
1084     /*
1085      * Indices mapping fontinfo structures to indices in the list
1086      * boxes. sizeindex is irrelevant if the font is scalable
1087      * (size==0).
1088      */
1089     int familyindex, styleindex, sizeindex;
1090     /*
1091      * The class of font.
1092      */
1093     const struct unifont_vtable *fontclass;
1094 };
1095
1096 static int fontinfo_realname_compare(void *av, void *bv)
1097 {
1098     fontinfo *a = (fontinfo *)av;
1099     fontinfo *b = (fontinfo *)bv;
1100     return g_strcasecmp(a->realname, b->realname);
1101 }
1102
1103 static int fontinfo_realname_find(void *av, void *bv)
1104 {
1105     const char *a = (const char *)av;
1106     fontinfo *b = (fontinfo *)bv;
1107     return g_strcasecmp(a, b->realname);
1108 }
1109
1110 static int strnullcasecmp(const char *a, const char *b)
1111 {
1112     int i;
1113
1114     /*
1115      * If exactly one of the inputs is NULL, it compares before
1116      * the other one.
1117      */
1118     if ((i = (!b) - (!a)) != 0)
1119         return i;
1120
1121     /*
1122      * NULL compares equal.
1123      */
1124     if (!a)
1125         return 0;
1126
1127     /*
1128      * Otherwise, ordinary strcasecmp.
1129      */
1130     return g_strcasecmp(a, b);
1131 }
1132
1133 static int fontinfo_selorder_compare(void *av, void *bv)
1134 {
1135     fontinfo *a = (fontinfo *)av;
1136     fontinfo *b = (fontinfo *)bv;
1137     int i;
1138     if ((i = strnullcasecmp(a->family, b->family)) != 0)
1139         return i;
1140     if ((i = strnullcasecmp(a->charset, b->charset)) != 0)
1141         return i;
1142     if ((i = strnullcasecmp(a->style, b->style)) != 0)
1143         return i;
1144     if (a->size != b->size)
1145         return (a->size < b->size ? -1 : +1);
1146     if (a->index != b->index)
1147         return (a->index < b->index ? -1 : +1);
1148     return 0;
1149 }
1150
1151 static void unifontsel_setup_familylist(unifontsel_internal *fs)
1152 {
1153     GtkTreeIter iter;
1154     int i, listindex, minpos = -1, maxpos = -1;
1155     char *currfamily = NULL;
1156     fontinfo *info;
1157
1158     gtk_list_store_clear(fs->family_model);
1159     listindex = 0;
1160
1161     /*
1162      * Search through the font tree for anything matching our
1163      * current filter criteria. When we find one, add its font
1164      * name to the list box.
1165      */
1166     for (i = 0 ;; i++) {
1167         info = (fontinfo *)index234(fs->fonts_by_selorder, i);
1168         /*
1169          * info may be NULL if we've just run off the end of the
1170          * tree. We must still do a processing pass in that
1171          * situation, in case we had an unfinished font record in
1172          * progress.
1173          */
1174         if (info && (info->flags &~ fs->filter_flags)) {
1175             info->familyindex = -1;
1176             continue;                  /* we're filtering out this font */
1177         }
1178         if (!info || strnullcasecmp(currfamily, info->family)) {
1179             /*
1180              * We've either finished a family, or started a new
1181              * one, or both.
1182              */
1183             if (currfamily) {
1184                 gtk_list_store_append(fs->family_model, &iter);
1185                 gtk_list_store_set(fs->family_model, &iter,
1186                                    0, currfamily, 1, minpos, 2, maxpos+1, -1);
1187                 listindex++;
1188             }
1189             if (info) {
1190                 minpos = i;
1191                 currfamily = info->family;
1192             }
1193         }
1194         if (!info)
1195             break;                     /* now we're done */
1196         info->familyindex = listindex;
1197         maxpos = i;
1198     }
1199 }
1200
1201 static void unifontsel_setup_stylelist(unifontsel_internal *fs,
1202                                        int start, int end)
1203 {
1204     GtkTreeIter iter;
1205     int i, listindex, minpos = -1, maxpos = -1, started = FALSE;
1206     char *currcs = NULL, *currstyle = NULL;
1207     fontinfo *info;
1208
1209     gtk_list_store_clear(fs->style_model);
1210     listindex = 0;
1211     started = FALSE;
1212
1213     /*
1214      * Search through the font tree for anything matching our
1215      * current filter criteria. When we find one, add its charset
1216      * and/or style name to the list box.
1217      */
1218     for (i = start; i <= end; i++) {
1219         if (i == end)
1220             info = NULL;
1221         else
1222             info = (fontinfo *)index234(fs->fonts_by_selorder, i);
1223         /*
1224          * info may be NULL if we've just run off the end of the
1225          * relevant data. We must still do a processing pass in
1226          * that situation, in case we had an unfinished font
1227          * record in progress.
1228          */
1229         if (info && (info->flags &~ fs->filter_flags)) {
1230             info->styleindex = -1;
1231             continue;                  /* we're filtering out this font */
1232         }
1233         if (!info || !started || strnullcasecmp(currcs, info->charset) ||
1234              strnullcasecmp(currstyle, info->style)) {
1235             /*
1236              * We've either finished a style/charset, or started a
1237              * new one, or both.
1238              */
1239             started = TRUE;
1240             if (currstyle) {
1241                 gtk_list_store_append(fs->style_model, &iter);
1242                 gtk_list_store_set(fs->style_model, &iter,
1243                                    0, currstyle, 1, minpos, 2, maxpos+1,
1244                                    3, TRUE, -1);
1245                 listindex++;
1246             }
1247             if (info) {
1248                 minpos = i;
1249                 if (info->charset && strnullcasecmp(currcs, info->charset)) {
1250                     gtk_list_store_append(fs->style_model, &iter);
1251                     gtk_list_store_set(fs->style_model, &iter,
1252                                        0, info->charset, 1, -1, 2, -1,
1253                                        3, FALSE, -1);
1254                     listindex++;
1255                 }
1256                 currcs = info->charset;
1257                 currstyle = info->style;
1258             }
1259         }
1260         if (!info)
1261             break;                     /* now we're done */
1262         info->styleindex = listindex;
1263         maxpos = i;
1264     }
1265 }
1266
1267 static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 };
1268
1269 static void unifontsel_setup_sizelist(unifontsel_internal *fs,
1270                                       int start, int end)
1271 {
1272     GtkTreeIter iter;
1273     int i, listindex;
1274     char sizetext[40];
1275     fontinfo *info;
1276
1277     gtk_list_store_clear(fs->size_model);
1278     listindex = 0;
1279
1280     /*
1281      * Search through the font tree for anything matching our
1282      * current filter criteria. When we find one, add its font
1283      * name to the list box.
1284      */
1285     for (i = start; i < end; i++) {
1286         info = (fontinfo *)index234(fs->fonts_by_selorder, i);
1287         if (info->flags &~ fs->filter_flags) {
1288             info->sizeindex = -1;
1289             continue;                  /* we're filtering out this font */
1290         }
1291         if (info->size) {
1292             sprintf(sizetext, "%d", info->size);
1293             info->sizeindex = listindex;
1294             gtk_list_store_append(fs->size_model, &iter);
1295             gtk_list_store_set(fs->size_model, &iter,
1296                                0, sizetext, 1, i, 2, info->size, -1);
1297             listindex++;
1298         } else {
1299             int j;
1300
1301             assert(i == start);
1302             assert(i+1 == end);
1303
1304             for (j = 0; j < lenof(unifontsel_default_sizes); j++) {
1305                 sprintf(sizetext, "%d", unifontsel_default_sizes[j]);
1306                 gtk_list_store_append(fs->size_model, &iter);
1307                 gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i,
1308                                    2, unifontsel_default_sizes[j], -1);
1309                 listindex++;
1310             }
1311         }
1312     }
1313 }
1314
1315 static void unifontsel_set_filter_buttons(unifontsel_internal *fs)
1316 {
1317     int i;
1318
1319     for (i = 0; i < lenof(fs->filter_buttons); i++) {
1320         int flagbit = GPOINTER_TO_INT(gtk_object_get_data
1321                                       (GTK_OBJECT(fs->filter_buttons[i]),
1322                                        "user-data"));
1323         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]),
1324                                      !!(fs->filter_flags & flagbit));
1325     }
1326 }
1327
1328 static void unifontsel_select_font(unifontsel_internal *fs,
1329                                    fontinfo *info, int size, int leftlist)
1330 {
1331     int index;
1332     int minval, maxval;
1333     GtkTreePath *treepath;
1334     GtkTreeIter iter;
1335
1336     fs->inhibit_response = TRUE;
1337
1338     fs->selected = info;
1339     fs->selsize = size;
1340
1341     /*
1342      * Find the index of this fontinfo in the selorder list. 
1343      */
1344     index = -1;
1345     findpos234(fs->fonts_by_selorder, info, NULL, &index);
1346     assert(index >= 0);
1347
1348     /*
1349      * Adjust the font selector flags and redo the font family
1350      * list box, if necessary.
1351      */
1352     if (leftlist <= 0 &&
1353         (fs->filter_flags | info->flags) != fs->filter_flags) {
1354         fs->filter_flags |= info->flags;
1355         unifontsel_set_filter_buttons(fs);
1356         unifontsel_setup_familylist(fs);
1357     }
1358
1359     /*
1360      * Find the appropriate family name and select it in the list.
1361      */
1362     assert(info->familyindex >= 0);
1363     treepath = gtk_tree_path_new_from_indices(info->familyindex, -1);
1364     gtk_tree_selection_select_path
1365         (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)),
1366          treepath);
1367     gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list),
1368                                  treepath, NULL, FALSE, 0.0, 0.0);
1369     gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath);
1370     gtk_tree_path_free(treepath);
1371
1372     /*
1373      * Now set up the font style list.
1374      */
1375     gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter,
1376                        1, &minval, 2, &maxval, -1);
1377     if (leftlist <= 1)
1378         unifontsel_setup_stylelist(fs, minval, maxval);
1379
1380     /*
1381      * Find the appropriate style name and select it in the list.
1382      */
1383     if (info->style) {
1384         assert(info->styleindex >= 0);
1385         treepath = gtk_tree_path_new_from_indices(info->styleindex, -1);
1386         gtk_tree_selection_select_path
1387             (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)),
1388              treepath);
1389         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list),
1390                                      treepath, NULL, FALSE, 0.0, 0.0);
1391         gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model),
1392                                 &iter, treepath);
1393         gtk_tree_path_free(treepath);
1394
1395         /*
1396          * And set up the size list.
1397          */
1398         gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter,
1399                            1, &minval, 2, &maxval, -1);
1400         if (leftlist <= 2)
1401             unifontsel_setup_sizelist(fs, minval, maxval);
1402
1403         /*
1404          * Find the appropriate size, and select it in the list.
1405          */
1406         if (info->size) {
1407             assert(info->sizeindex >= 0);
1408             treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1);
1409             gtk_tree_selection_select_path
1410                 (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)),
1411                  treepath);
1412             gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
1413                                          treepath, NULL, FALSE, 0.0, 0.0);
1414             gtk_tree_path_free(treepath);
1415             size = info->size;
1416         } else {
1417             int j;
1418             for (j = 0; j < lenof(unifontsel_default_sizes); j++)
1419                 if (unifontsel_default_sizes[j] == size) {
1420                     treepath = gtk_tree_path_new_from_indices(j, -1);
1421                     gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list),
1422                                              treepath, NULL, FALSE);
1423                     gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
1424                                                  treepath, NULL, FALSE, 0.0,
1425                                                  0.0);
1426                     gtk_tree_path_free(treepath);
1427                 }
1428         }
1429
1430         /*
1431          * And set up the font size text entry box.
1432          */
1433         {
1434             char sizetext[40];
1435             sprintf(sizetext, "%d", size);
1436             gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext);
1437         }
1438     } else {
1439         if (leftlist <= 2)
1440             unifontsel_setup_sizelist(fs, 0, 0);
1441         gtk_entry_set_text(GTK_ENTRY(fs->size_entry), "");
1442     }
1443
1444     /*
1445      * Grey out the font size edit box if we're not using a
1446      * scalable font.
1447      */
1448     gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0);
1449     gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0);
1450
1451     fs->inhibit_response = FALSE;
1452 }
1453
1454 static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data)
1455 {
1456     unifontsel_internal *fs = (unifontsel_internal *)data;
1457     int newstate = gtk_toggle_button_get_active(tb);
1458     int newflags;
1459     int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb),
1460                                                       "user-data"));
1461
1462     if (newstate)
1463         newflags = fs->filter_flags | flagbit;
1464     else
1465         newflags = fs->filter_flags & ~flagbit;
1466
1467     if (fs->filter_flags != newflags) {
1468         fs->filter_flags = newflags;
1469         unifontsel_setup_familylist(fs);
1470     }
1471 }
1472
1473 static void unifontsel_add_entry(void *ctx, const char *realfontname,
1474                                  const char *family, const char *charset,
1475                                  const char *style, int size, int flags,
1476                                  const struct unifont_vtable *fontclass)
1477 {
1478     unifontsel_internal *fs = (unifontsel_internal *)ctx;
1479     fontinfo *info;
1480     int totalsize;
1481     char *p;
1482
1483     totalsize = sizeof(fontinfo) + strlen(realfontname) +
1484         (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) +
1485         (style ? strlen(style) : 0) + 10;
1486     info = (fontinfo *)smalloc(totalsize);
1487     info->fontclass = fontclass;
1488     p = (char *)info + sizeof(fontinfo);
1489     info->realname = p;
1490     strcpy(p, realfontname);
1491     p += 1+strlen(p);
1492     if (family) {
1493         info->family = p;
1494         strcpy(p, family);
1495         p += 1+strlen(p);
1496     } else
1497         info->family = NULL;
1498     if (charset) {
1499         info->charset = p;
1500         strcpy(p, charset);
1501         p += 1+strlen(p);
1502     } else
1503         info->charset = NULL;
1504     if (style) {
1505         info->style = p;
1506         strcpy(p, style);
1507         p += 1+strlen(p);
1508     } else
1509         info->style = NULL;
1510     assert(p - (char *)info <= totalsize);
1511     info->size = size;
1512     info->flags = flags;
1513     info->index = count234(fs->fonts_by_selorder);
1514
1515     /*
1516      * It's just conceivable that a misbehaving font enumerator
1517      * might tell us about the same font real name more than once,
1518      * in which case we should silently drop the new one.
1519      */
1520     if (add234(fs->fonts_by_realname, info) != info) {
1521         sfree(info);
1522         return;
1523     }
1524     /*
1525      * However, we should never get a duplicate key in the
1526      * selorder tree, because the index field carefully
1527      * disambiguates otherwise identical records.
1528      */
1529     add234(fs->fonts_by_selorder, info);
1530 }
1531
1532 static void family_changed(GtkTreeSelection *treeselection, gpointer data)
1533 {
1534     unifontsel_internal *fs = (unifontsel_internal *)data;
1535     GtkTreeModel *treemodel;
1536     GtkTreeIter treeiter;
1537     int minval;
1538     fontinfo *info;
1539
1540     if (fs->inhibit_response)          /* we made this change ourselves */
1541         return;
1542
1543     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
1544         return;
1545
1546     gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
1547     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
1548     unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, 1);
1549 }
1550
1551 static void style_changed(GtkTreeSelection *treeselection, gpointer data)
1552 {
1553     unifontsel_internal *fs = (unifontsel_internal *)data;
1554     GtkTreeModel *treemodel;
1555     GtkTreeIter treeiter;
1556     int minval;
1557     fontinfo *info;
1558
1559     if (fs->inhibit_response)          /* we made this change ourselves */
1560         return;
1561
1562     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
1563         return;
1564
1565     gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
1566     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
1567     unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, 2);
1568 }
1569
1570 static void size_changed(GtkTreeSelection *treeselection, gpointer data)
1571 {
1572     unifontsel_internal *fs = (unifontsel_internal *)data;
1573     GtkTreeModel *treemodel;
1574     GtkTreeIter treeiter;
1575     int minval, size;
1576     fontinfo *info;
1577
1578     if (fs->inhibit_response)          /* we made this change ourselves */
1579         return;
1580
1581     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
1582         return;
1583
1584     gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1);
1585     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
1586     unifontsel_select_font(fs, info, info->size ? info->size : size, 3);
1587 }
1588
1589 static void size_entry_changed(GtkEditable *ed, gpointer data)
1590 {
1591     unifontsel_internal *fs = (unifontsel_internal *)data;
1592     const char *text;
1593     int size;
1594
1595     if (fs->inhibit_response)          /* we made this change ourselves */
1596         return;
1597
1598     text = gtk_entry_get_text(GTK_ENTRY(ed));
1599     size = atoi(text);
1600
1601     if (size > 0) {
1602         assert(fs->selected->size == 0);
1603         unifontsel_select_font(fs, fs->selected, size, 3);
1604     }
1605 }
1606
1607 unifontsel *unifontsel_new(const char *wintitle)
1608 {
1609     unifontsel_internal *fs = snew(unifontsel_internal);
1610     GtkWidget *table, *label, *w, *scroll;
1611     GtkListStore *model;
1612     GtkTreeViewColumn *column;
1613     int lists_height, font_width, style_width, size_width;
1614     int i;
1615
1616     fs->inhibit_response = FALSE;
1617
1618     {
1619         /*
1620          * Invent some magic size constants.
1621          */
1622         GtkRequisition req;
1623         label = gtk_label_new("Quite Long Font Name (Foundry)");
1624         gtk_widget_size_request(label, &req);
1625         font_width = req.width;
1626         lists_height = 14 * req.height;
1627         gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed");
1628         gtk_widget_size_request(label, &req);
1629         style_width = req.width;
1630         gtk_label_set_text(GTK_LABEL(label), "48000");
1631         gtk_widget_size_request(label, &req);
1632         size_width = req.width;
1633         g_object_ref_sink(label);
1634         g_object_unref(label);
1635     }
1636
1637     /*
1638      * Create the dialog box and initialise the user-visible
1639      * fields in the returned structure.
1640      */
1641     fs->u.user_data = NULL;
1642     fs->u.window = GTK_WINDOW(gtk_dialog_new());
1643     gtk_window_set_title(fs->u.window, wintitle);
1644     fs->u.cancel_button = gtk_dialog_add_button
1645         (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1646     fs->u.ok_button = gtk_dialog_add_button
1647         (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK);
1648     gtk_widget_grab_default(fs->u.ok_button);
1649
1650     /*
1651      * Now set up the internal fields, including in particular all
1652      * the controls that actually allow the user to select fonts.
1653      */
1654     table = gtk_table_new(3, 8, FALSE);
1655     gtk_widget_show(table);
1656     gtk_table_set_col_spacings(GTK_TABLE(table), 8);
1657     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox),
1658                        table, TRUE, TRUE, 0);
1659
1660     label = gtk_label_new_with_mnemonic("_Font:");
1661     gtk_widget_show(label);
1662     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1663     gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
1664
1665     /*
1666      * The Font list box displays only a string, but additionally
1667      * stores two integers which give the limits within the
1668      * tree234 of the font entries covered by this list entry.
1669      */
1670     model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
1671     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1672     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
1673     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
1674     gtk_widget_show(w);
1675     column = gtk_tree_view_column_new_with_attributes
1676         ("Font", gtk_cell_renderer_text_new(),
1677          "text", 0, (char *)NULL);
1678     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1679     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
1680     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
1681                      "changed", G_CALLBACK(family_changed), fs);
1682
1683     scroll = gtk_scrolled_window_new(NULL, NULL);
1684     gtk_container_add(GTK_CONTAINER(scroll), w);
1685     gtk_widget_show(scroll);
1686     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
1687                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
1688     gtk_widget_set_size_request(scroll, font_width, lists_height);
1689     gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, 0, 0, 0);
1690     fs->family_model = model;
1691     fs->family_list = w;
1692
1693     label = gtk_label_new_with_mnemonic("_Style:");
1694     gtk_widget_show(label);
1695     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1696     gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
1697
1698     /*
1699      * The Style list box can contain insensitive elements
1700      * (character set headings for server-side fonts), so we add
1701      * an extra column to the list store to hold that information.
1702      */
1703     model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
1704                                G_TYPE_BOOLEAN);
1705     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1706     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
1707     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
1708     gtk_widget_show(w);
1709     column = gtk_tree_view_column_new_with_attributes
1710         ("Style", gtk_cell_renderer_text_new(),
1711          "text", 0, "sensitive", 3, (char *)NULL);
1712     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1713     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
1714     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
1715                      "changed", G_CALLBACK(style_changed), fs);
1716
1717     scroll = gtk_scrolled_window_new(NULL, NULL);
1718     gtk_container_add(GTK_CONTAINER(scroll), w);
1719     gtk_widget_show(scroll);
1720     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
1721                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
1722     gtk_widget_set_size_request(scroll, style_width, lists_height);
1723     gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, 0, 0, 0);
1724     fs->style_model = model;
1725     fs->style_list = w;
1726
1727     label = gtk_label_new_with_mnemonic("Si_ze:");
1728     gtk_widget_show(label);
1729     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1730     gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
1731
1732     /*
1733      * The Size label attaches primarily to a text input box so
1734      * that the user can select a size of their choice. The list
1735      * of available sizes is secondary.
1736      */
1737     fs->size_entry = w = gtk_entry_new();
1738     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
1739     gtk_widget_set_size_request(w, size_width, -1);
1740     gtk_widget_show(w);
1741     gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
1742     g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed),
1743                      fs);
1744
1745     model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
1746     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1747     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
1748     gtk_widget_show(w);
1749     column = gtk_tree_view_column_new_with_attributes
1750         ("Size", gtk_cell_renderer_text_new(),
1751          "text", 0, (char *)NULL);
1752     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1753     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
1754     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
1755                      "changed", G_CALLBACK(size_changed), fs);
1756
1757     scroll = gtk_scrolled_window_new(NULL, NULL);
1758     gtk_container_add(GTK_CONTAINER(scroll), w);
1759     gtk_widget_show(scroll);
1760     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
1761                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
1762     gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL,
1763                      GTK_EXPAND | GTK_FILL, 0, 0);
1764     fs->size_model = model;
1765     fs->size_list = w;
1766
1767     /*
1768      * FIXME: preview widget
1769      */
1770     i = 0;
1771     w = gtk_check_button_new_with_label("Show client-side fonts");
1772     gtk_object_set_data(GTK_OBJECT(w), "user-data",
1773                         GINT_TO_POINTER(FONTFLAG_CLIENTSIDE));
1774     gtk_signal_connect(GTK_OBJECT(w), "toggled",
1775                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
1776     gtk_widget_show(w);
1777     fs->filter_buttons[i++] = w;
1778     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0);
1779     w = gtk_check_button_new_with_label("Show server-side fonts");
1780     gtk_object_set_data(GTK_OBJECT(w), "user-data",
1781                         GINT_TO_POINTER(FONTFLAG_SERVERSIDE));
1782     gtk_signal_connect(GTK_OBJECT(w), "toggled",
1783                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
1784     gtk_widget_show(w);
1785     fs->filter_buttons[i++] = w;
1786     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0);
1787     w = gtk_check_button_new_with_label("Show server-side font aliases");
1788     gtk_object_set_data(GTK_OBJECT(w), "user-data",
1789                         GINT_TO_POINTER(FONTFLAG_SERVERALIAS));
1790     gtk_signal_connect(GTK_OBJECT(w), "toggled",
1791                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
1792     gtk_widget_show(w);
1793     fs->filter_buttons[i++] = w;
1794     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0);
1795     w = gtk_check_button_new_with_label("Show non-monospaced fonts");
1796     gtk_object_set_data(GTK_OBJECT(w), "user-data",
1797                         GINT_TO_POINTER(FONTFLAG_NONMONOSPACED));
1798     gtk_signal_connect(GTK_OBJECT(w), "toggled",
1799                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
1800     gtk_widget_show(w);
1801     fs->filter_buttons[i++] = w;
1802     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0);
1803
1804     assert(i == lenof(fs->filter_buttons));
1805     fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE;
1806     unifontsel_set_filter_buttons(fs);
1807
1808     /*
1809      * Go and find all the font names, and set up our master font
1810      * list.
1811      */
1812     fs->fonts_by_realname = newtree234(fontinfo_realname_compare);
1813     fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare);
1814     for (i = 0; i < lenof(unifont_types); i++)
1815         unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window),
1816                                      unifontsel_add_entry, fs);
1817
1818     /*
1819      * And set up the initial font names list.
1820      */
1821     unifontsel_setup_familylist(fs);
1822
1823     fs->selected = NULL;
1824
1825     return (unifontsel *)fs;
1826 }
1827
1828 void unifontsel_destroy(unifontsel *fontsel)
1829 {
1830     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
1831     fontinfo *info;
1832
1833     freetree234(fs->fonts_by_selorder);
1834     while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL)
1835         sfree(info);
1836     freetree234(fs->fonts_by_realname);
1837
1838     gtk_widget_destroy(GTK_WIDGET(fs->u.window));
1839     sfree(fs);
1840 }
1841
1842 void unifontsel_set_name(unifontsel *fontsel, const char *fontname)
1843 {
1844     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
1845     int i, start, end, size;
1846     const char *fontname2;
1847     fontinfo *info;
1848
1849     /*
1850      * Provide a default if given an empty or null font name.
1851      */
1852     if (!fontname || !*fontname)
1853         fontname = "fixed";   /* Pango zealots might prefer "Monospace 12" */
1854
1855     /*
1856      * Call the canonify_fontname function.
1857      */
1858     fontname = unifont_do_prefix(fontname, &start, &end);
1859     for (i = start; i < end; i++) {
1860         fontname2 = unifont_types[i]->canonify_fontname
1861             (GTK_WIDGET(fs->u.window), fontname, &size);
1862         if (fontname2)
1863             break;
1864     }
1865     if (i == end)
1866         return;                        /* font name not recognised */
1867
1868     /*
1869      * Now look up the canonified font name in our index.
1870      */
1871     info = find234(fs->fonts_by_realname, (char *)fontname2,
1872                    fontinfo_realname_find);
1873
1874     /*
1875      * If we've found the font, and its size field is either
1876      * correct or zero (the latter indicating a scalable font),
1877      * then we're done. Otherwise, try looking up the original
1878      * font name instead.
1879      */
1880     if (!info || (info->size != size && info->size != 0)) {
1881         info = find234(fs->fonts_by_realname, (char *)fontname,
1882                        fontinfo_realname_find);
1883         if (!info || info->size != size)
1884             return;                    /* font name not in our index */
1885     }
1886
1887     /*
1888      * Now we've got a fontinfo structure and a font size, so we
1889      * know everything we need to fill in all the fields in the
1890      * dialog.
1891      */
1892     unifontsel_select_font(fs, info, size, 0);
1893 }
1894
1895 char *unifontsel_get_name(unifontsel *fontsel)
1896 {
1897     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
1898     char *name;
1899
1900     assert(fs->selected);
1901
1902     if (fs->selected->size == 0) {
1903         name = fs->selected->fontclass->scale_fontname
1904             (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize);
1905         if (name)
1906             return name;
1907     }
1908
1909     return dupstr(fs->selected->realname);
1910 }