]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkfont.c
Update to r8614: another -D_FORTIFY_SOURCE=2 fix.
[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  * Future work:
28  * 
29  *  - it would be nice to have a display of the current font name,
30  *    and in particular whether it's client- or server-side,
31  *    during the progress of the font selector.
32  * 
33  *  - all the GDK font functions used in the x11font subclass are
34  *    deprecated, so one day they may go away. When this happens -
35  *    or before, if I'm feeling proactive - it oughtn't to be too
36  *    difficult in principle to convert the whole thing to use
37  *    actual Xlib font calls.
38  * 
39  *  - it would be nice if we could move the processing of
40  *    underline and VT100 double width into this module, so that
41  *    instead of using the ghastly pixmap-stretching technique
42  *    everywhere we could tell the Pango backend to scale its
43  *    fonts to double size properly and at full resolution.
44  *    However, this requires me to learn how to make Pango stretch
45  *    text to an arbitrary aspect ratio (for double-width only
46  *    text, which perversely is harder than DW+DH), and right now
47  *    I haven't the energy.
48  */
49
50 /*
51  * Ad-hoc vtable mechanism to allow font structures to be
52  * polymorphic.
53  * 
54  * Any instance of `unifont' used in the vtable functions will
55  * actually be the first element of a larger structure containing
56  * data specific to the subtype. This is permitted by the ISO C
57  * provision that one may safely cast between a pointer to a
58  * structure and a pointer to its first element.
59  */
60
61 #define FONTFLAG_CLIENTSIDE    0x0001
62 #define FONTFLAG_SERVERSIDE    0x0002
63 #define FONTFLAG_SERVERALIAS   0x0004
64 #define FONTFLAG_NONMONOSPACED 0x0008
65
66 #define FONTFLAG_SORT_MASK     0x0007 /* used to disambiguate font families */
67
68 typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname,
69                                   const char *family, const char *charset,
70                                   const char *style, const char *stylekey,
71                                   int size, int flags,
72                                   const struct unifont_vtable *fontclass);
73
74 struct unifont_vtable {
75     /*
76      * `Methods' of the `class'.
77      */
78     unifont *(*create)(GtkWidget *widget, const char *name, int wide, int bold,
79                        int shadowoffset, int shadowalways);
80     void (*destroy)(unifont *font);
81     void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font,
82                       int x, int y, const char *string, int len, int wide,
83                       int bold, int cellwidth);
84     void (*enum_fonts)(GtkWidget *widget,
85                        fontsel_add_entry callback, void *callback_ctx);
86     char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size,
87                                int *flags, int resolve_aliases);
88     char *(*scale_fontname)(GtkWidget *widget, const char *name, int size);
89
90     /*
91      * `Static data members' of the `class'.
92      */
93     const char *prefix;
94 };
95
96 /* ----------------------------------------------------------------------
97  * GDK-based X11 font implementation.
98  */
99
100 static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
101                               int x, int y, const char *string, int len,
102                               int wide, int bold, int cellwidth);
103 static unifont *x11font_create(GtkWidget *widget, const char *name,
104                                int wide, int bold,
105                                int shadowoffset, int shadowalways);
106 static void x11font_destroy(unifont *font);
107 static void x11font_enum_fonts(GtkWidget *widget,
108                                fontsel_add_entry callback, void *callback_ctx);
109 static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
110                                        int *size, int *flags,
111                                        int resolve_aliases);
112 static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
113                                     int size);
114
115 struct x11font {
116     struct unifont u;
117     /*
118      * Actual font objects. We store a number of these, for
119      * automatically guessed bold and wide variants.
120      * 
121      * The parallel array `allocated' indicates whether we've
122      * tried to fetch a subfont already (thus distinguishing NULL
123      * because we haven't tried yet from NULL because we tried and
124      * failed, so that we don't keep trying and failing
125      * subsequently).
126      */
127     GdkFont *fonts[4];
128     int allocated[4];
129     /*
130      * `sixteen_bit' is true iff the font object is indexed by
131      * values larger than a byte. That is, this flag tells us
132      * whether we use gdk_draw_text_wc() or gdk_draw_text().
133      */
134     int sixteen_bit;
135     /*
136      * `variable' is true iff the font is non-fixed-pitch. This
137      * enables some code which takes greater care over character
138      * positioning during text drawing.
139      */
140     int variable;
141     /*
142      * Data passed in to unifont_create().
143      */
144     int wide, bold, shadowoffset, shadowalways;
145 };
146
147 static const struct unifont_vtable x11font_vtable = {
148     x11font_create,
149     x11font_destroy,
150     x11font_draw_text,
151     x11font_enum_fonts,
152     x11font_canonify_fontname,
153     x11font_scale_fontname,
154     "server",
155 };
156
157 char *x11_guess_derived_font_name(GdkFont *font, int bold, int wide)
158 {
159     XFontStruct *xfs = GDK_FONT_XFONT(font);
160     Display *disp = GDK_FONT_XDISPLAY(font);
161     Atom fontprop = XInternAtom(disp, "FONT", False);
162     unsigned long ret;
163     if (XGetFontProperty(xfs, fontprop, &ret)) {
164         char *name = XGetAtomName(disp, (Atom)ret);
165         if (name && name[0] == '-') {
166             char *strings[13];
167             char *dupname, *extrafree = NULL, *ret;
168             char *p, *q;
169             int nstr;
170
171             p = q = dupname = dupstr(name); /* skip initial minus */
172             nstr = 0;
173
174             while (*p && nstr < lenof(strings)) {
175                 if (*p == '-') {
176                     *p = '\0';
177                     strings[nstr++] = p+1;
178                 }
179                 p++;
180             }
181
182             if (nstr < lenof(strings))
183                 return NULL;           /* XLFD was malformed */
184
185             if (bold)
186                 strings[2] = "bold";
187
188             if (wide) {
189                 /* 4 is `wideness', which obviously may have changed. */
190                 /* 5 is additional style, which may be e.g. `ja' or `ko'. */
191                 strings[4] = strings[5] = "*";
192                 strings[11] = extrafree = dupprintf("%d", 2*atoi(strings[11]));
193             }
194
195             ret = dupcat("-", strings[ 0], "-", strings[ 1], "-", strings[ 2],
196                          "-", strings[ 3], "-", strings[ 4], "-", strings[ 5],
197                          "-", strings[ 6], "-", strings[ 7], "-", strings[ 8],
198                          "-", strings[ 9], "-", strings[10], "-", strings[11],
199                          "-", strings[12], NULL);
200             sfree(extrafree);
201             sfree(dupname);
202
203             return ret;
204         }
205     }
206     return NULL;
207 }
208
209 static int x11_font_width(GdkFont *font, int sixteen_bit)
210 {
211     if (sixteen_bit) {
212         XChar2b space;
213         space.byte1 = 0;
214         space.byte2 = '0';
215         return gdk_text_width(font, (const gchar *)&space, 2);
216     } else {
217         return gdk_char_width(font, '0');
218     }
219 }
220
221 static unifont *x11font_create(GtkWidget *widget, const char *name,
222                                int wide, int bold,
223                                int shadowoffset, int shadowalways)
224 {
225     struct x11font *xfont;
226     GdkFont *font;
227     XFontStruct *xfs;
228     Display *disp;
229     Atom charset_registry, charset_encoding, spacing;
230     unsigned long registry_ret, encoding_ret, spacing_ret;
231     int pubcs, realcs, sixteen_bit, variable;
232     int i;
233
234     font = gdk_font_load(name);
235     if (!font)
236         return NULL;
237
238     xfs = GDK_FONT_XFONT(font);
239     disp = GDK_FONT_XDISPLAY(font);
240
241     charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False);
242     charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False);
243
244     pubcs = realcs = CS_NONE;
245     sixteen_bit = FALSE;
246     variable = TRUE;
247
248     if (XGetFontProperty(xfs, charset_registry, &registry_ret) &&
249         XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
250         char *reg, *enc;
251         reg = XGetAtomName(disp, (Atom)registry_ret);
252         enc = XGetAtomName(disp, (Atom)encoding_ret);
253         if (reg && enc) {
254             char *encoding = dupcat(reg, "-", enc, NULL);
255             pubcs = realcs = charset_from_xenc(encoding);
256
257             /*
258              * iso10646-1 is the only wide font encoding we
259              * support. In this case, we expect clients to give us
260              * UTF-8, which this module must internally convert
261              * into 16-bit Unicode.
262              */
263             if (!strcasecmp(encoding, "iso10646-1")) {
264                 sixteen_bit = TRUE;
265                 pubcs = realcs = CS_UTF8;
266             }
267
268             /*
269              * Hack for X line-drawing characters: if the primary
270              * font is encoded as ISO-8859-1, and has valid glyphs
271              * in the first 32 char positions, it is assumed that
272              * those glyphs are the VT100 line-drawing character
273              * set.
274              * 
275              * Actually, we'll hack even harder by only checking
276              * position 0x19 (vertical line, VT100 linedrawing
277              * `x'). Then we can check it easily by seeing if the
278              * ascent and descent differ.
279              */
280             if (pubcs == CS_ISO8859_1) {
281                 int lb, rb, wid, asc, desc;
282                 gchar text[2];
283
284                 text[1] = '\0';
285                 text[0] = '\x12';
286                 gdk_string_extents(font, text, &lb, &rb, &wid, &asc, &desc);
287                 if (asc != desc)
288                     realcs = CS_ISO8859_1_X11;
289             }
290
291             sfree(encoding);
292         }
293     }
294
295     spacing = XInternAtom(disp, "SPACING", False);
296     if (XGetFontProperty(xfs, spacing, &spacing_ret)) {
297         char *spc;
298         spc = XGetAtomName(disp, (Atom)spacing_ret);
299
300         if (spc && strchr("CcMm", spc[0]))
301             variable = FALSE;
302     }
303
304     xfont = snew(struct x11font);
305     xfont->u.vt = &x11font_vtable;
306     xfont->u.width = x11_font_width(font, sixteen_bit);
307     xfont->u.ascent = font->ascent;
308     xfont->u.descent = font->descent;
309     xfont->u.height = xfont->u.ascent + xfont->u.descent;
310     xfont->u.public_charset = pubcs;
311     xfont->u.real_charset = realcs;
312     xfont->fonts[0] = font;
313     xfont->allocated[0] = TRUE;
314     xfont->sixteen_bit = sixteen_bit;
315     xfont->variable = variable;
316     xfont->wide = wide;
317     xfont->bold = bold;
318     xfont->shadowoffset = shadowoffset;
319     xfont->shadowalways = shadowalways;
320
321     for (i = 1; i < lenof(xfont->fonts); i++) {
322         xfont->fonts[i] = NULL;
323         xfont->allocated[i] = FALSE;
324     }
325
326     return (unifont *)xfont;
327 }
328
329 static void x11font_destroy(unifont *font)
330 {
331     struct x11font *xfont = (struct x11font *)font;
332     int i;
333
334     for (i = 0; i < lenof(xfont->fonts); i++)
335         if (xfont->fonts[i])
336             gdk_font_unref(xfont->fonts[i]);
337     sfree(font);
338 }
339
340 static void x11_alloc_subfont(struct x11font *xfont, int sfid)
341 {
342     char *derived_name = x11_guess_derived_font_name
343         (xfont->fonts[0], sfid & 1, !!(sfid & 2));
344     xfont->fonts[sfid] = gdk_font_load(derived_name);   /* may be NULL */
345     xfont->allocated[sfid] = TRUE;
346     sfree(derived_name);
347 }
348
349 static void x11font_really_draw_text(GdkDrawable *target, GdkFont *font,
350                                      GdkGC *gc, int x, int y,
351                                      const gchar *string, int clen, int nchars,
352                                      int shadowbold, int shadowoffset,
353                                      int fontvariable, int cellwidth)
354 {
355     int step = clen * nchars, nsteps = 1, centre = FALSE;
356
357     if (fontvariable) {
358         /*
359          * In a variable-pitch font, we draw one character at a
360          * time, and centre it in the character cell.
361          */
362         step = clen;
363         nsteps = nchars;
364         centre = TRUE;
365     }
366
367     while (nsteps-- > 0) {
368         int X = x;
369         if (centre)
370             X += (cellwidth - gdk_text_width(font, string, step)) / 2;
371
372         gdk_draw_text(target, font, gc, X, y, string, step);
373         if (shadowbold)
374             gdk_draw_text(target, font, gc, X + shadowoffset, y, string, step);
375
376         x += cellwidth;
377         string += step;
378     }
379 }
380
381 static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
382                               int x, int y, const char *string, int len,
383                               int wide, int bold, int cellwidth)
384 {
385     struct x11font *xfont = (struct x11font *)font;
386     int sfid;
387     int shadowbold = FALSE;
388     int mult = (wide ? 2 : 1);
389
390     wide -= xfont->wide;
391     bold -= xfont->bold;
392
393     /*
394      * Decide which subfont we're using, and whether we have to
395      * use shadow bold.
396      */
397     if (xfont->shadowalways && bold) {
398         shadowbold = TRUE;
399         bold = 0;
400     }
401     sfid = 2 * wide + bold;
402     if (!xfont->allocated[sfid])
403         x11_alloc_subfont(xfont, sfid);
404     if (bold && !xfont->fonts[sfid]) {
405         bold = 0;
406         shadowbold = TRUE;
407         sfid = 2 * wide + bold;
408         if (!xfont->allocated[sfid])
409             x11_alloc_subfont(xfont, sfid);
410     }
411
412     if (!xfont->fonts[sfid])
413         return;                        /* we've tried our best, but no luck */
414
415     if (xfont->sixteen_bit) {
416         /*
417          * This X font has 16-bit character indices, which means
418          * we expect our string to have been passed in UTF-8.
419          */
420         XChar2b *xcs;
421         wchar_t *wcs;
422         int nchars, maxchars, i;
423
424         /*
425          * Convert the input string to wide-character Unicode.
426          */
427         maxchars = 0;
428         for (i = 0; i < len; i++)
429             if ((unsigned char)string[i] <= 0x7F ||
430                 (unsigned char)string[i] >= 0xC0)
431                 maxchars++;
432         wcs = snewn(maxchars+1, wchar_t);
433         nchars = charset_to_unicode((char **)&string, &len, wcs, maxchars,
434                                     CS_UTF8, NULL, NULL, 0);
435         assert(nchars <= maxchars);
436         wcs[nchars] = L'\0';
437
438         xcs = snewn(nchars, XChar2b);
439         for (i = 0; i < nchars; i++) {
440             xcs[i].byte1 = wcs[i] >> 8;
441             xcs[i].byte2 = wcs[i];
442         }
443
444         x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y,
445                                  (gchar *)xcs, 2, nchars,
446                                  shadowbold, xfont->shadowoffset,
447                                  xfont->variable, cellwidth * mult);
448         sfree(xcs);
449         sfree(wcs);
450     } else {
451         x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y,
452                                  string, 1, len,
453                                  shadowbold, xfont->shadowoffset,
454                                  xfont->variable, cellwidth * mult);
455     }
456 }
457
458 static void x11font_enum_fonts(GtkWidget *widget,
459                                fontsel_add_entry callback, void *callback_ctx)
460 {
461     char **fontnames;
462     char *tmp = NULL;
463     int nnames, i, max, tmpsize;
464
465     max = 32768;
466     while (1) {
467         fontnames = XListFonts(GDK_DISPLAY(), "*", max, &nnames);
468         if (nnames >= max) {
469             XFreeFontNames(fontnames);
470             max *= 2;
471         } else
472             break;
473     }
474
475     tmpsize = 0;
476
477     for (i = 0; i < nnames; i++) {
478         if (fontnames[i][0] == '-') {
479             /*
480              * Dismember an XLFD and convert it into the format
481              * we'll be using in the font selector.
482              */
483             char *components[14];
484             char *p, *font, *style, *stylekey, *charset;
485             int j, weightkey, slantkey, setwidthkey;
486             int thistmpsize, fontsize, flags;
487
488             thistmpsize = 4 * strlen(fontnames[i]) + 256;
489             if (tmpsize < thistmpsize) {
490                 tmpsize = thistmpsize;
491                 tmp = sresize(tmp, tmpsize, char);
492             }
493             strcpy(tmp, fontnames[i]);
494
495             p = tmp;
496             for (j = 0; j < 14; j++) {
497                 if (*p)
498                     *p++ = '\0';
499                 components[j] = p;
500                 while (*p && *p != '-')
501                     p++;
502             }
503             *p++ = '\0';
504
505             /*
506              * Font name is made up of fields 0 and 1, in reverse
507              * order with parentheses. (This is what the GTK 1.2 X
508              * font selector does, and it seems to come out
509              * looking reasonably sensible.)
510              */
511             font = p;
512             p += 1 + sprintf(p, "%s (%s)", components[1], components[0]);
513
514             /*
515              * Charset is made up of fields 12 and 13.
516              */
517             charset = p;
518             p += 1 + sprintf(p, "%s-%s", components[12], components[13]);
519
520             /*
521              * Style is a mixture of quite a lot of the fields,
522              * with some strange formatting.
523              */
524             style = p;
525             p += sprintf(p, "%s", components[2][0] ? components[2] :
526                          "regular");
527             if (!g_strcasecmp(components[3], "i"))
528                 p += sprintf(p, " italic");
529             else if (!g_strcasecmp(components[3], "o"))
530                 p += sprintf(p, " oblique");
531             else if (!g_strcasecmp(components[3], "ri"))
532                 p += sprintf(p, " reverse italic");
533             else if (!g_strcasecmp(components[3], "ro"))
534                 p += sprintf(p, " reverse oblique");
535             else if (!g_strcasecmp(components[3], "ot"))
536                 p += sprintf(p, " other-slant");
537             if (components[4][0] && g_strcasecmp(components[4], "normal"))
538                 p += sprintf(p, " %s", components[4]);
539             if (!g_strcasecmp(components[10], "m"))
540                 p += sprintf(p, " [M]");
541             if (!g_strcasecmp(components[10], "c"))
542                 p += sprintf(p, " [C]");
543             if (components[5][0])
544                 p += sprintf(p, " %s", components[5]);
545
546             /*
547              * Style key is the same stuff as above, but with a
548              * couple of transformations done on it to make it
549              * sort more sensibly.
550              */
551             p++;
552             stylekey = p;
553             if (!g_strcasecmp(components[2], "medium") ||
554                 !g_strcasecmp(components[2], "regular") ||
555                 !g_strcasecmp(components[2], "normal") ||
556                 !g_strcasecmp(components[2], "book"))
557                 weightkey = 0;
558             else if (!g_strncasecmp(components[2], "demi", 4) ||
559                      !g_strncasecmp(components[2], "semi", 4))
560                 weightkey = 1;
561             else
562                 weightkey = 2;
563             if (!g_strcasecmp(components[3], "r"))
564                 slantkey = 0;
565             else if (!g_strncasecmp(components[3], "r", 1))
566                 slantkey = 2;
567             else
568                 slantkey = 1;
569             if (!g_strcasecmp(components[4], "normal"))
570                 setwidthkey = 0;
571             else
572                 setwidthkey = 1;
573
574             p += sprintf(p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s",
575                          weightkey,
576                          (int)strlen(components[2]), components[2],
577                          slantkey,
578                          (int)strlen(components[3]), components[3],
579                          setwidthkey,
580                          (int)strlen(components[4]), components[4],
581                          (int)strlen(components[10]), components[10],
582                          (int)strlen(components[5]), components[5]);
583
584             assert(p - tmp < thistmpsize);
585
586             /*
587              * Size is in pixels, for our application, so we
588              * derive it directly from the pixel size field,
589              * number 6.
590              */
591             fontsize = atoi(components[6]);
592
593             /*
594              * Flags: we need to know whether this is a monospaced
595              * font, which we do by examining the spacing field
596              * again.
597              */
598             flags = FONTFLAG_SERVERSIDE;
599             if (!strchr("CcMm", components[10][0]))
600                 flags |= FONTFLAG_NONMONOSPACED;
601
602             /*
603              * Not sure why, but sometimes the X server will
604              * deliver dummy font types in which fontsize comes
605              * out as zero. Filter those out.
606              */
607             if (fontsize)
608                 callback(callback_ctx, fontnames[i], font, charset,
609                          style, stylekey, fontsize, flags, &x11font_vtable);
610         } else {
611             /*
612              * This isn't an XLFD, so it must be an alias.
613              * Transmit it with mostly null data.
614              * 
615              * It would be nice to work out if it's monospaced
616              * here, but at the moment I can't see that being
617              * anything but computationally hideous. Ah well.
618              */
619             callback(callback_ctx, fontnames[i], fontnames[i], NULL,
620                      NULL, NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable);
621         }
622     }
623     XFreeFontNames(fontnames);
624 }
625
626 static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
627                                        int *size, int *flags,
628                                        int resolve_aliases)
629 {
630     /*
631      * When given an X11 font name to try to make sense of for a
632      * font selector, we must attempt to load it (to see if it
633      * exists), and then canonify it by extracting its FONT
634      * property, which should give its full XLFD even if what we
635      * originally had was a wildcard.
636      * 
637      * However, we must carefully avoid canonifying font
638      * _aliases_, unless specifically asked to, because the font
639      * selector treats them as worthwhile in their own right.
640      */
641     GdkFont *font = gdk_font_load(name);
642     XFontStruct *xfs;
643     Display *disp;
644     Atom fontprop, fontprop2;
645     unsigned long ret;
646
647     if (!font)
648         return NULL;                   /* didn't make sense to us, sorry */
649
650     gdk_font_ref(font);
651
652     xfs = GDK_FONT_XFONT(font);
653     disp = GDK_FONT_XDISPLAY(font);
654     fontprop = XInternAtom(disp, "FONT", False);
655
656     if (XGetFontProperty(xfs, fontprop, &ret)) {
657         char *newname = XGetAtomName(disp, (Atom)ret);
658         if (newname) {
659             unsigned long fsize = 12;
660
661             fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False);
662             if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) {
663                 *size = fsize;
664                 gdk_font_unref(font);
665                 if (flags) {
666                     if (name[0] == '-' || resolve_aliases)
667                         *flags = FONTFLAG_SERVERSIDE;
668                     else
669                         *flags = FONTFLAG_SERVERALIAS;
670                 }
671                 return dupstr(name[0] == '-' || resolve_aliases ?
672                               newname : name);
673             }
674         }
675     }
676
677     gdk_font_unref(font);
678     return NULL;                       /* something went wrong */
679 }
680
681 static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
682                                     int size)
683 {
684     return NULL;                       /* shan't */
685 }
686
687 #if GTK_CHECK_VERSION(2,0,0)
688
689 /* ----------------------------------------------------------------------
690  * Pango font implementation (for GTK 2 only).
691  */
692
693 #if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6
694 #define PANGO_PRE_1POINT6              /* make life easier for pre-1.4 folk */
695 #endif
696
697 static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
698                                 int x, int y, const char *string, int len,
699                                 int wide, int bold, int cellwidth);
700 static unifont *pangofont_create(GtkWidget *widget, const char *name,
701                                  int wide, int bold,
702                                  int shadowoffset, int shadowalways);
703 static void pangofont_destroy(unifont *font);
704 static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
705                                  void *callback_ctx);
706 static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
707                                          int *size, int *flags,
708                                          int resolve_aliases);
709 static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
710                                       int size);
711
712 struct pangofont {
713     struct unifont u;
714     /*
715      * Pango objects.
716      */
717     PangoFontDescription *desc;
718     PangoFontset *fset;
719     /*
720      * The containing widget.
721      */
722     GtkWidget *widget;
723     /*
724      * Data passed in to unifont_create().
725      */
726     int bold, shadowoffset, shadowalways;
727 };
728
729 static const struct unifont_vtable pangofont_vtable = {
730     pangofont_create,
731     pangofont_destroy,
732     pangofont_draw_text,
733     pangofont_enum_fonts,
734     pangofont_canonify_fontname,
735     pangofont_scale_fontname,
736     "client",
737 };
738
739 /*
740  * This function is used to rigorously validate a
741  * PangoFontDescription. Later versions of Pango have a nasty
742  * habit of accepting _any_ old string as input to
743  * pango_font_description_from_string and returning a font
744  * description which can actually be used to display text, even if
745  * they have to do it by falling back to their most default font.
746  * This is doubtless helpful in some situations, but not here,
747  * because we need to know if a Pango font string actually _makes
748  * sense_ in order to fall back to treating it as an X font name
749  * if it doesn't. So we check that the font family is actually one
750  * supported by Pango.
751  */
752 static int pangofont_check_desc_makes_sense(PangoContext *ctx,
753                                             PangoFontDescription *desc)
754 {
755 #ifndef PANGO_PRE_1POINT6
756     PangoFontMap *map;
757 #endif
758     PangoFontFamily **families;
759     int i, nfamilies, matched;
760
761     /*
762      * Ask Pango for a list of font families, and iterate through
763      * them to see if one of them matches the family in the
764      * PangoFontDescription.
765      */
766 #ifndef PANGO_PRE_1POINT6
767     map = pango_context_get_font_map(ctx);
768     if (!map)
769         return FALSE;
770     pango_font_map_list_families(map, &families, &nfamilies);
771 #else
772     pango_context_list_families(ctx, &families, &nfamilies);
773 #endif
774
775     matched = FALSE;
776     for (i = 0; i < nfamilies; i++) {
777         if (!g_strcasecmp(pango_font_family_get_name(families[i]),
778                           pango_font_description_get_family(desc))) {
779             matched = TRUE;
780             break;
781         }
782     }
783     g_free(families);
784
785     return matched;
786 }
787
788 static unifont *pangofont_create(GtkWidget *widget, const char *name,
789                                  int wide, int bold,
790                                  int shadowoffset, int shadowalways)
791 {
792     struct pangofont *pfont;
793     PangoContext *ctx;
794 #ifndef PANGO_PRE_1POINT6
795     PangoFontMap *map;
796 #endif
797     PangoFontDescription *desc;
798     PangoFontset *fset;
799     PangoFontMetrics *metrics;
800
801     desc = pango_font_description_from_string(name);
802     if (!desc)
803         return NULL;
804     ctx = gtk_widget_get_pango_context(widget);
805     if (!ctx) {
806         pango_font_description_free(desc);
807         return NULL;
808     }
809     if (!pangofont_check_desc_makes_sense(ctx, desc)) {
810         pango_font_description_free(desc);
811         return NULL;
812     }
813 #ifndef PANGO_PRE_1POINT6
814     map = pango_context_get_font_map(ctx);
815     if (!map) {
816         pango_font_description_free(desc);
817         return NULL;
818     }
819     fset = pango_font_map_load_fontset(map, ctx, desc,
820                                        pango_context_get_language(ctx));
821 #else
822     fset = pango_context_load_fontset(ctx, desc,
823                                       pango_context_get_language(ctx));
824 #endif
825     if (!fset) {
826         pango_font_description_free(desc);
827         return NULL;
828     }
829     metrics = pango_fontset_get_metrics(fset);
830     if (!metrics ||
831         pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
832         pango_font_description_free(desc);
833         g_object_unref(fset);
834         return NULL;
835     }
836
837     pfont = snew(struct pangofont);
838     pfont->u.vt = &pangofont_vtable;
839     pfont->u.width =
840         PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics));
841     pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics));
842     pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics));
843     pfont->u.height = pfont->u.ascent + pfont->u.descent;
844     /* The Pango API is hardwired to UTF-8 */
845     pfont->u.public_charset = CS_UTF8;
846     pfont->u.real_charset = CS_UTF8;
847     pfont->desc = desc;
848     pfont->fset = fset;
849     pfont->widget = widget;
850     pfont->bold = bold;
851     pfont->shadowoffset = shadowoffset;
852     pfont->shadowalways = shadowalways;
853
854     pango_font_metrics_unref(metrics);
855
856     return (unifont *)pfont;
857 }
858
859 static void pangofont_destroy(unifont *font)
860 {
861     struct pangofont *pfont = (struct pangofont *)font;
862     pango_font_description_free(pfont->desc);
863     g_object_unref(pfont->fset);
864     sfree(font);
865 }
866
867 static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
868                                 int x, int y, const char *string, int len,
869                                 int wide, int bold, int cellwidth)
870 {
871     struct pangofont *pfont = (struct pangofont *)font;
872     PangoLayout *layout;
873     PangoRectangle rect;
874     int shadowbold = FALSE;
875
876     if (wide)
877         cellwidth *= 2;
878
879     y -= pfont->u.ascent;
880
881     layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget));
882     pango_layout_set_font_description(layout, pfont->desc);
883     if (bold > pfont->bold) {
884         if (pfont->shadowalways)
885             shadowbold = TRUE;
886         else {
887             PangoFontDescription *desc2 =
888                 pango_font_description_copy_static(pfont->desc);
889             pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD);
890             pango_layout_set_font_description(layout, desc2);
891         }
892     }
893
894     while (len > 0) {
895         int clen, n;
896
897         /*
898          * We want to display every character from this string in
899          * the centre of its own character cell. In the worst case,
900          * this requires a separate text-drawing call for each
901          * character; but in the common case where the font is
902          * properly fixed-width, we can draw many characters in one
903          * go which is much faster.
904          *
905          * This still isn't really ideal. If you look at what
906          * happens in the X protocol as a result of all of this, you
907          * find - naturally enough - that each call to
908          * gdk_draw_layout() generates a separate set of X RENDER
909          * operations involving creating a picture, setting a clip
910          * rectangle, doing some drawing and undoing the whole lot.
911          * In an ideal world, we should _always_ be able to turn the
912          * contents of this loop into a single RenderCompositeGlyphs
913          * operation which internally specifies inter-character
914          * deltas to get the spacing right, which would give us full
915          * speed _even_ in the worst case of a non-fixed-width font.
916          * However, Pango's architecture and documentation are so
917          * unhelpful that I have no idea how if at all to persuade
918          * them to do that.
919          */
920
921         /*
922          * Start by extracting a single UTF-8 character from the
923          * string.
924          */
925         clen = 1;
926         while (clen < len &&
927                (unsigned char)string[clen] >= 0x80 &&
928                (unsigned char)string[clen] < 0xC0)
929             clen++;
930         n = 1;
931
932         /*
933          * See if that character has the width we expect.
934          */
935         pango_layout_set_text(layout, string, clen);
936         pango_layout_get_pixel_extents(layout, NULL, &rect);
937
938         if (rect.width == cellwidth) {
939             /*
940              * Try extracting more characters, for as long as they
941              * stay well-behaved.
942              */
943             while (clen < len) {
944                 int oldclen = clen;
945                 clen++;                /* skip UTF-8 introducer byte */
946                 while (clen < len &&
947                        (unsigned char)string[clen] >= 0x80 &&
948                        (unsigned char)string[clen] < 0xC0)
949                     clen++;
950                 n++;
951                 pango_layout_set_text(layout, string, clen);
952                 pango_layout_get_pixel_extents(layout, NULL, &rect);
953                 if (rect.width != n * cellwidth) {
954                     clen = oldclen;
955                     n--;
956                     break;
957                 }
958             }
959         }
960
961         pango_layout_set_text(layout, string, clen);
962         pango_layout_get_pixel_extents(layout, NULL, &rect);
963         gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2,
964                         y + (pfont->u.height - rect.height)/2, layout);
965         if (shadowbold)
966             gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset,
967                             y + (pfont->u.height - rect.height)/2, layout);
968
969         len -= clen;
970         string += clen;
971         x += n * cellwidth;
972     }
973
974     g_object_unref(layout);
975 }
976
977 /*
978  * Dummy size value to be used when converting a
979  * PangoFontDescription of a scalable font to a string for
980  * internal use.
981  */
982 #define PANGO_DUMMY_SIZE 12
983
984 static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
985                                  void *callback_ctx)
986 {
987     PangoContext *ctx;
988 #ifndef PANGO_PRE_1POINT6
989     PangoFontMap *map;
990 #endif
991     PangoFontFamily **families;
992     int i, nfamilies;
993
994     ctx = gtk_widget_get_pango_context(widget);
995     if (!ctx)
996         return;
997
998     /*
999      * Ask Pango for a list of font families, and iterate through
1000      * them.
1001      */
1002 #ifndef PANGO_PRE_1POINT6
1003     map = pango_context_get_font_map(ctx);
1004     if (!map)
1005         return;
1006     pango_font_map_list_families(map, &families, &nfamilies);
1007 #else
1008     pango_context_list_families(ctx, &families, &nfamilies);
1009 #endif
1010     for (i = 0; i < nfamilies; i++) {
1011         PangoFontFamily *family = families[i];
1012         const char *familyname;
1013         int flags;
1014         PangoFontFace **faces;
1015         int j, nfaces;
1016
1017         /*
1018          * Set up our flags for this font family, and get the name
1019          * string.
1020          */
1021         flags = FONTFLAG_CLIENTSIDE;
1022 #ifndef PANGO_PRE_1POINT4
1023         /*
1024          * In very early versions of Pango, we can't tell
1025          * monospaced fonts from non-monospaced.
1026          */
1027         if (!pango_font_family_is_monospace(family))
1028             flags |= FONTFLAG_NONMONOSPACED;
1029 #endif
1030         familyname = pango_font_family_get_name(family);
1031
1032         /*
1033          * Go through the available font faces in this family.
1034          */
1035         pango_font_family_list_faces(family, &faces, &nfaces);
1036         for (j = 0; j < nfaces; j++) {
1037             PangoFontFace *face = faces[j];
1038             PangoFontDescription *desc;
1039             const char *facename;
1040             int *sizes;
1041             int k, nsizes, dummysize;
1042
1043             /*
1044              * Get the face name string.
1045              */
1046             facename = pango_font_face_get_face_name(face);
1047
1048             /*
1049              * Set up a font description with what we've got so
1050              * far. We'll fill in the size field manually and then
1051              * call pango_font_description_to_string() to give the
1052              * full real name of the specific font.
1053              */
1054             desc = pango_font_face_describe(face);
1055
1056             /*
1057              * See if this font has a list of specific sizes.
1058              */
1059 #ifndef PANGO_PRE_1POINT4
1060             pango_font_face_list_sizes(face, &sizes, &nsizes);
1061 #else
1062             /*
1063              * In early versions of Pango, that call wasn't
1064              * supported; we just have to assume everything is
1065              * scalable.
1066              */
1067             sizes = NULL;
1068 #endif
1069             if (!sizes) {
1070                 /*
1071                  * Write a single entry with a dummy size.
1072                  */
1073                 dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE;
1074                 sizes = &dummysize;
1075                 nsizes = 1;
1076             }
1077
1078             /*
1079              * If so, go through them one by one.
1080              */
1081             for (k = 0; k < nsizes; k++) {
1082                 char *fullname;
1083                 char stylekey[128];
1084
1085                 pango_font_description_set_size(desc, sizes[k]);
1086
1087                 fullname = pango_font_description_to_string(desc);
1088
1089                 /*
1090                  * Construct the sorting key for font styles.
1091                  */
1092                 {
1093                     char *p = stylekey;
1094                     int n;
1095
1096                     n = pango_font_description_get_weight(desc);
1097                     /* Weight: normal, then lighter, then bolder */
1098                     if (n <= PANGO_WEIGHT_NORMAL)
1099                         n = PANGO_WEIGHT_NORMAL - n;
1100                     p += sprintf(p, "%4d", n);
1101
1102                     n = pango_font_description_get_style(desc);
1103                     p += sprintf(p, " %2d", n);
1104
1105                     n = pango_font_description_get_stretch(desc);
1106                     /* Stretch: closer to normal sorts earlier */
1107                     n = 2 * abs(PANGO_STRETCH_NORMAL - n) +
1108                         (n < PANGO_STRETCH_NORMAL);
1109                     p += sprintf(p, " %2d", n);
1110
1111                     n = pango_font_description_get_variant(desc);
1112                     p += sprintf(p, " %2d", n);
1113                     
1114                 }
1115
1116                 /*
1117                  * Got everything. Hand off to the callback.
1118                  * (The charset string is NULL, because only
1119                  * server-side X fonts use it.)
1120                  */
1121                 callback(callback_ctx, fullname, familyname, NULL, facename,
1122                          stylekey,
1123                          (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])),
1124                          flags, &pangofont_vtable);
1125
1126                 g_free(fullname);
1127             }
1128             if (sizes != &dummysize)
1129                 g_free(sizes);
1130
1131             pango_font_description_free(desc);
1132         }
1133         g_free(faces);
1134     }
1135     g_free(families);
1136 }
1137
1138 static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
1139                                          int *size, int *flags,
1140                                          int resolve_aliases)
1141 {
1142     /*
1143      * When given a Pango font name to try to make sense of for a
1144      * font selector, we must normalise it to PANGO_DUMMY_SIZE and
1145      * extract its original size (in pixels) into the `size' field.
1146      */
1147     PangoContext *ctx;
1148 #ifndef PANGO_PRE_1POINT6
1149     PangoFontMap *map;
1150 #endif
1151     PangoFontDescription *desc;
1152     PangoFontset *fset;
1153     PangoFontMetrics *metrics;
1154     char *newname, *retname;
1155
1156     desc = pango_font_description_from_string(name);
1157     if (!desc)
1158         return NULL;
1159     ctx = gtk_widget_get_pango_context(widget);
1160     if (!ctx) {
1161         pango_font_description_free(desc);
1162         return NULL;
1163     }
1164     if (!pangofont_check_desc_makes_sense(ctx, desc)) {
1165         pango_font_description_free(desc);
1166         return NULL;
1167     }
1168 #ifndef PANGO_PRE_1POINT6
1169     map = pango_context_get_font_map(ctx);
1170     if (!map) {
1171         pango_font_description_free(desc);
1172         return NULL;
1173     }
1174     fset = pango_font_map_load_fontset(map, ctx, desc,
1175                                        pango_context_get_language(ctx));
1176 #else
1177     fset = pango_context_load_fontset(ctx, desc,
1178                                       pango_context_get_language(ctx));
1179 #endif
1180     if (!fset) {
1181         pango_font_description_free(desc);
1182         return NULL;
1183     }
1184     metrics = pango_fontset_get_metrics(fset);
1185     if (!metrics ||
1186         pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
1187         pango_font_description_free(desc);
1188         g_object_unref(fset);
1189         return NULL;
1190     }
1191
1192     *size = PANGO_PIXELS(pango_font_description_get_size(desc));
1193     *flags = FONTFLAG_CLIENTSIDE;
1194     pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE);
1195     newname = pango_font_description_to_string(desc);
1196     retname = dupstr(newname);
1197     g_free(newname);
1198
1199     pango_font_metrics_unref(metrics);
1200     pango_font_description_free(desc);
1201     g_object_unref(fset);
1202
1203     return retname;
1204 }
1205
1206 static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
1207                                       int size)
1208 {
1209     PangoFontDescription *desc;
1210     char *newname, *retname;
1211
1212     desc = pango_font_description_from_string(name);
1213     if (!desc)
1214         return NULL;
1215     pango_font_description_set_size(desc, size * PANGO_SCALE);
1216     newname = pango_font_description_to_string(desc);
1217     retname = dupstr(newname);
1218     g_free(newname);
1219     pango_font_description_free(desc);
1220
1221     return retname;
1222 }
1223
1224 #endif /* GTK_CHECK_VERSION(2,0,0) */
1225
1226 /* ----------------------------------------------------------------------
1227  * Outermost functions which do the vtable dispatch.
1228  */
1229
1230 /*
1231  * Complete list of font-type subclasses. Listed in preference
1232  * order for unifont_create(). (That is, in the extremely unlikely
1233  * event that the same font name is valid as both a Pango and an
1234  * X11 font, it will be interpreted as the former in the absence
1235  * of an explicit type-disambiguating prefix.)
1236  */
1237 static const struct unifont_vtable *unifont_types[] = {
1238 #if GTK_CHECK_VERSION(2,0,0)
1239     &pangofont_vtable,
1240 #endif
1241     &x11font_vtable,
1242 };
1243
1244 /*
1245  * Function which takes a font name and processes the optional
1246  * scheme prefix. Returns the tail of the font name suitable for
1247  * passing to individual font scheme functions, and also provides
1248  * a subrange of the unifont_types[] array above.
1249  * 
1250  * The return values `start' and `end' denote a half-open interval
1251  * in unifont_types[]; that is, the correct way to iterate over
1252  * them is
1253  * 
1254  *   for (i = start; i < end; i++) {...}
1255  */
1256 static const char *unifont_do_prefix(const char *name, int *start, int *end)
1257 {
1258     int colonpos = strcspn(name, ":");
1259     int i;
1260
1261     if (name[colonpos]) {
1262         /*
1263          * There's a colon prefix on the font name. Use it to work
1264          * out which subclass to use.
1265          */
1266         for (i = 0; i < lenof(unifont_types); i++) {
1267             if (strlen(unifont_types[i]->prefix) == colonpos &&
1268                 !strncmp(unifont_types[i]->prefix, name, colonpos)) {
1269                 *start = i;
1270                 *end = i+1;
1271                 return name + colonpos + 1;
1272             }
1273         }
1274         /*
1275          * None matched, so return an empty scheme list to prevent
1276          * any scheme from being called at all.
1277          */
1278         *start = *end = 0;
1279         return name + colonpos + 1;
1280     } else {
1281         /*
1282          * No colon prefix, so just use all the subclasses.
1283          */
1284         *start = 0;
1285         *end = lenof(unifont_types);
1286         return name;
1287     }
1288 }
1289
1290 unifont *unifont_create(GtkWidget *widget, const char *name, int wide,
1291                         int bold, int shadowoffset, int shadowalways)
1292 {
1293     int i, start, end;
1294
1295     name = unifont_do_prefix(name, &start, &end);
1296
1297     for (i = start; i < end; i++) {
1298         unifont *ret = unifont_types[i]->create(widget, name, wide, bold,
1299                                                 shadowoffset, shadowalways);
1300         if (ret)
1301             return ret;
1302     }
1303     return NULL;                       /* font not found in any scheme */
1304 }
1305
1306 void unifont_destroy(unifont *font)
1307 {
1308     font->vt->destroy(font);
1309 }
1310
1311 void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
1312                        int x, int y, const char *string, int len,
1313                        int wide, int bold, int cellwidth)
1314 {
1315     font->vt->draw_text(target, gc, font, x, y, string, len,
1316                         wide, bold, cellwidth);
1317 }
1318
1319 #if GTK_CHECK_VERSION(2,0,0)
1320
1321 /* ----------------------------------------------------------------------
1322  * Implementation of a unified font selector. Used on GTK 2 only;
1323  * for GTK 1 we still use the standard font selector.
1324  */
1325
1326 typedef struct fontinfo fontinfo;
1327
1328 typedef struct unifontsel_internal {
1329     /* This must be the structure's first element, for cross-casting */
1330     unifontsel u;
1331     GtkListStore *family_model, *style_model, *size_model;
1332     GtkWidget *family_list, *style_list, *size_entry, *size_list;
1333     GtkWidget *filter_buttons[4];
1334     GtkWidget *preview_area;
1335     GdkPixmap *preview_pixmap;
1336     int preview_width, preview_height;
1337     GdkColor preview_fg, preview_bg;
1338     int filter_flags;
1339     tree234 *fonts_by_realname, *fonts_by_selorder;
1340     fontinfo *selected;
1341     int selsize, intendedsize;
1342     int inhibit_response;  /* inhibit callbacks when we change GUI controls */
1343 } unifontsel_internal;
1344
1345 /*
1346  * The structure held in the tree234s. All the string members are
1347  * part of the same allocated area, so don't need freeing
1348  * separately.
1349  */
1350 struct fontinfo {
1351     char *realname;
1352     char *family, *charset, *style, *stylekey;
1353     int size, flags;
1354     /*
1355      * Fallback sorting key, to permit multiple identical entries
1356      * to exist in the selorder tree.
1357      */
1358     int index;
1359     /*
1360      * Indices mapping fontinfo structures to indices in the list
1361      * boxes. sizeindex is irrelevant if the font is scalable
1362      * (size==0).
1363      */
1364     int familyindex, styleindex, sizeindex;
1365     /*
1366      * The class of font.
1367      */
1368     const struct unifont_vtable *fontclass;
1369 };
1370
1371 struct fontinfo_realname_find {
1372     const char *realname;
1373     int flags;
1374 };
1375
1376 static int strnullcasecmp(const char *a, const char *b)
1377 {
1378     int i;
1379
1380     /*
1381      * If exactly one of the inputs is NULL, it compares before
1382      * the other one.
1383      */
1384     if ((i = (!b) - (!a)) != 0)
1385         return i;
1386
1387     /*
1388      * NULL compares equal.
1389      */
1390     if (!a)
1391         return 0;
1392
1393     /*
1394      * Otherwise, ordinary strcasecmp.
1395      */
1396     return g_strcasecmp(a, b);
1397 }
1398
1399 static int fontinfo_realname_compare(void *av, void *bv)
1400 {
1401     fontinfo *a = (fontinfo *)av;
1402     fontinfo *b = (fontinfo *)bv;
1403     int i;
1404
1405     if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
1406         return i;
1407     if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
1408         return ((a->flags & FONTFLAG_SORT_MASK) <
1409                 (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
1410     return 0;
1411 }
1412
1413 static int fontinfo_realname_find(void *av, void *bv)
1414 {
1415     struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av;
1416     fontinfo *b = (fontinfo *)bv;
1417     int i;
1418
1419     if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
1420         return i;
1421     if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
1422         return ((a->flags & FONTFLAG_SORT_MASK) <
1423                 (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
1424     return 0;
1425 }
1426
1427 static int fontinfo_selorder_compare(void *av, void *bv)
1428 {
1429     fontinfo *a = (fontinfo *)av;
1430     fontinfo *b = (fontinfo *)bv;
1431     int i;
1432     if ((i = strnullcasecmp(a->family, b->family)) != 0)
1433         return i;
1434     /*
1435      * Font class comes immediately after family, so that fonts
1436      * from different classes with the same family
1437      */
1438     if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
1439         return ((a->flags & FONTFLAG_SORT_MASK) <
1440                 (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
1441     if ((i = strnullcasecmp(a->charset, b->charset)) != 0)
1442         return i;
1443     if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0)
1444         return i;
1445     if ((i = strnullcasecmp(a->style, b->style)) != 0)
1446         return i;
1447     if (a->size != b->size)
1448         return (a->size < b->size ? -1 : +1);
1449     if (a->index != b->index)
1450         return (a->index < b->index ? -1 : +1);
1451     return 0;
1452 }
1453
1454 static void unifontsel_deselect(unifontsel_internal *fs)
1455 {
1456     fs->selected = NULL;
1457     gtk_list_store_clear(fs->style_model);
1458     gtk_list_store_clear(fs->size_model);
1459     gtk_widget_set_sensitive(fs->u.ok_button, FALSE);
1460     gtk_widget_set_sensitive(fs->size_entry, FALSE);
1461 }
1462
1463 static void unifontsel_setup_familylist(unifontsel_internal *fs)
1464 {
1465     GtkTreeIter iter;
1466     int i, listindex, minpos = -1, maxpos = -1;
1467     char *currfamily = NULL;
1468     int currflags = -1;
1469     fontinfo *info;
1470
1471     gtk_list_store_clear(fs->family_model);
1472     listindex = 0;
1473
1474     /*
1475      * Search through the font tree for anything matching our
1476      * current filter criteria. When we find one, add its font
1477      * name to the list box.
1478      */
1479     for (i = 0 ;; i++) {
1480         info = (fontinfo *)index234(fs->fonts_by_selorder, i);
1481         /*
1482          * info may be NULL if we've just run off the end of the
1483          * tree. We must still do a processing pass in that
1484          * situation, in case we had an unfinished font record in
1485          * progress.
1486          */
1487         if (info && (info->flags &~ fs->filter_flags)) {
1488             info->familyindex = -1;
1489             continue;                  /* we're filtering out this font */
1490         }
1491         if (!info || strnullcasecmp(currfamily, info->family) ||
1492             currflags != (info->flags & FONTFLAG_SORT_MASK)) {
1493             /*
1494              * We've either finished a family, or started a new
1495              * one, or both.
1496              */
1497             if (currfamily) {
1498                 gtk_list_store_append(fs->family_model, &iter);
1499                 gtk_list_store_set(fs->family_model, &iter,
1500                                    0, currfamily, 1, minpos, 2, maxpos+1, -1);
1501                 listindex++;
1502             }
1503             if (info) {
1504                 minpos = i;
1505                 currfamily = info->family;
1506                 currflags = info->flags & FONTFLAG_SORT_MASK;
1507             }
1508         }
1509         if (!info)
1510             break;                     /* now we're done */
1511         info->familyindex = listindex;
1512         maxpos = i;
1513     }
1514
1515     /*
1516      * If we've just filtered out the previously selected font,
1517      * deselect it thoroughly.
1518      */
1519     if (fs->selected && fs->selected->familyindex < 0)
1520         unifontsel_deselect(fs);
1521 }
1522
1523 static void unifontsel_setup_stylelist(unifontsel_internal *fs,
1524                                        int start, int end)
1525 {
1526     GtkTreeIter iter;
1527     int i, listindex, minpos = -1, maxpos = -1, started = FALSE;
1528     char *currcs = NULL, *currstyle = NULL;
1529     fontinfo *info;
1530
1531     gtk_list_store_clear(fs->style_model);
1532     listindex = 0;
1533     started = FALSE;
1534
1535     /*
1536      * Search through the font tree for anything matching our
1537      * current filter criteria. When we find one, add its charset
1538      * and/or style name to the list box.
1539      */
1540     for (i = start; i <= end; i++) {
1541         if (i == end)
1542             info = NULL;
1543         else
1544             info = (fontinfo *)index234(fs->fonts_by_selorder, i);
1545         /*
1546          * info may be NULL if we've just run off the end of the
1547          * relevant data. We must still do a processing pass in
1548          * that situation, in case we had an unfinished font
1549          * record in progress.
1550          */
1551         if (info && (info->flags &~ fs->filter_flags)) {
1552             info->styleindex = -1;
1553             continue;                  /* we're filtering out this font */
1554         }
1555         if (!info || !started || strnullcasecmp(currcs, info->charset) ||
1556              strnullcasecmp(currstyle, info->style)) {
1557             /*
1558              * We've either finished a style/charset, or started a
1559              * new one, or both.
1560              */
1561             started = TRUE;
1562             if (currstyle) {
1563                 gtk_list_store_append(fs->style_model, &iter);
1564                 gtk_list_store_set(fs->style_model, &iter,
1565                                    0, currstyle, 1, minpos, 2, maxpos+1,
1566                                    3, TRUE, -1);
1567                 listindex++;
1568             }
1569             if (info) {
1570                 minpos = i;
1571                 if (info->charset && strnullcasecmp(currcs, info->charset)) {
1572                     gtk_list_store_append(fs->style_model, &iter);
1573                     gtk_list_store_set(fs->style_model, &iter,
1574                                        0, info->charset, 1, -1, 2, -1,
1575                                        3, FALSE, -1);
1576                     listindex++;
1577                 }
1578                 currcs = info->charset;
1579                 currstyle = info->style;
1580             }
1581         }
1582         if (!info)
1583             break;                     /* now we're done */
1584         info->styleindex = listindex;
1585         maxpos = i;
1586     }
1587 }
1588
1589 static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 };
1590
1591 static void unifontsel_setup_sizelist(unifontsel_internal *fs,
1592                                       int start, int end)
1593 {
1594     GtkTreeIter iter;
1595     int i, listindex;
1596     char sizetext[40];
1597     fontinfo *info;
1598
1599     gtk_list_store_clear(fs->size_model);
1600     listindex = 0;
1601
1602     /*
1603      * Search through the font tree for anything matching our
1604      * current filter criteria. When we find one, add its font
1605      * name to the list box.
1606      */
1607     for (i = start; i < end; i++) {
1608         info = (fontinfo *)index234(fs->fonts_by_selorder, i);
1609         if (info->flags &~ fs->filter_flags) {
1610             info->sizeindex = -1;
1611             continue;                  /* we're filtering out this font */
1612         }
1613         if (info->size) {
1614             sprintf(sizetext, "%d", info->size);
1615             info->sizeindex = listindex;
1616             gtk_list_store_append(fs->size_model, &iter);
1617             gtk_list_store_set(fs->size_model, &iter,
1618                                0, sizetext, 1, i, 2, info->size, -1);
1619             listindex++;
1620         } else {
1621             int j;
1622
1623             assert(i == start);
1624             assert(i+1 == end);
1625
1626             for (j = 0; j < lenof(unifontsel_default_sizes); j++) {
1627                 sprintf(sizetext, "%d", unifontsel_default_sizes[j]);
1628                 gtk_list_store_append(fs->size_model, &iter);
1629                 gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i,
1630                                    2, unifontsel_default_sizes[j], -1);
1631                 listindex++;
1632             }
1633         }
1634     }
1635 }
1636
1637 static void unifontsel_set_filter_buttons(unifontsel_internal *fs)
1638 {
1639     int i;
1640
1641     for (i = 0; i < lenof(fs->filter_buttons); i++) {
1642         int flagbit = GPOINTER_TO_INT(gtk_object_get_data
1643                                       (GTK_OBJECT(fs->filter_buttons[i]),
1644                                        "user-data"));
1645         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]),
1646                                      !!(fs->filter_flags & flagbit));
1647     }
1648 }
1649
1650 static void unifontsel_draw_preview_text(unifontsel_internal *fs)
1651 {
1652     unifont *font;
1653     char *sizename = NULL;
1654     fontinfo *info = fs->selected;
1655
1656     if (info) {
1657         sizename = info->fontclass->scale_fontname
1658             (GTK_WIDGET(fs->u.window), info->realname, fs->selsize);
1659         font = info->fontclass->create(GTK_WIDGET(fs->u.window),
1660                                        sizename ? sizename : info->realname,
1661                                        FALSE, FALSE, 0, 0);
1662     } else
1663         font = NULL;
1664
1665     if (fs->preview_pixmap) {
1666         GdkGC *gc = gdk_gc_new(fs->preview_pixmap);
1667         gdk_gc_set_foreground(gc, &fs->preview_bg);
1668         gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0,
1669                            fs->preview_width, fs->preview_height);
1670         gdk_gc_set_foreground(gc, &fs->preview_fg);
1671         if (font) {
1672             /*
1673              * The pangram used here is rather carefully
1674              * constructed: it contains a sequence of very narrow
1675              * letters (`jil') and a pair of adjacent very wide
1676              * letters (`wm').
1677              *
1678              * If the user selects a proportional font, it will be
1679              * coerced into fixed-width character cells when used
1680              * in the actual terminal window. We therefore display
1681              * it the same way in the preview pane, so as to show
1682              * it the way it will actually be displayed - and we
1683              * deliberately pick a pangram which will show the
1684              * resulting miskerning at its worst.
1685              *
1686              * We aren't trying to sell people these fonts; we're
1687              * trying to let them make an informed choice. Better
1688              * that they find out the problems with using
1689              * proportional fonts in terminal windows here than
1690              * that they go to the effort of selecting their font
1691              * and _then_ realise it was a mistake.
1692              */
1693             info->fontclass->draw_text(fs->preview_pixmap, gc, font,
1694                                        0, font->ascent,
1695                                        "bankrupt jilted showmen quiz convex fogey",
1696                                        41, FALSE, FALSE, font->width);
1697             info->fontclass->draw_text(fs->preview_pixmap, gc, font,
1698                                        0, font->ascent + font->height,
1699                                        "BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY",
1700                                        41, FALSE, FALSE, font->width);
1701             /*
1702              * The ordering of punctuation here is also selected
1703              * with some specific aims in mind. I put ` and '
1704              * together because some software (and people) still
1705              * use them as matched quotes no matter what Unicode
1706              * might say on the matter, so people can quickly
1707              * check whether they look silly in a candidate font.
1708              * The sequence #_@ is there to let people judge the
1709              * suitability of the underscore as an effectively
1710              * alphabetic character (since that's how it's often
1711              * used in practice, at least by programmers).
1712              */
1713             info->fontclass->draw_text(fs->preview_pixmap, gc, font,
1714                                        0, font->ascent + font->height * 2,
1715                                        "0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$",
1716                                        42, FALSE, FALSE, font->width);
1717         }
1718         gdk_gc_unref(gc);
1719         gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE);
1720     }
1721     if (font)
1722         info->fontclass->destroy(font);
1723
1724     sfree(sizename);
1725 }
1726
1727 static void unifontsel_select_font(unifontsel_internal *fs,
1728                                    fontinfo *info, int size, int leftlist,
1729                                    int size_is_explicit)
1730 {
1731     int index;
1732     int minval, maxval;
1733     GtkTreePath *treepath;
1734     GtkTreeIter iter;
1735
1736     fs->inhibit_response = TRUE;
1737
1738     fs->selected = info;
1739     fs->selsize = size;
1740     if (size_is_explicit)
1741         fs->intendedsize = size;
1742
1743     gtk_widget_set_sensitive(fs->u.ok_button, TRUE);
1744
1745     /*
1746      * Find the index of this fontinfo in the selorder list. 
1747      */
1748     index = -1;
1749     findpos234(fs->fonts_by_selorder, info, NULL, &index);
1750     assert(index >= 0);
1751
1752     /*
1753      * Adjust the font selector flags and redo the font family
1754      * list box, if necessary.
1755      */
1756     if (leftlist <= 0 &&
1757         (fs->filter_flags | info->flags) != fs->filter_flags) {
1758         fs->filter_flags |= info->flags;
1759         unifontsel_set_filter_buttons(fs);
1760         unifontsel_setup_familylist(fs);
1761     }
1762
1763     /*
1764      * Find the appropriate family name and select it in the list.
1765      */
1766     assert(info->familyindex >= 0);
1767     treepath = gtk_tree_path_new_from_indices(info->familyindex, -1);
1768     gtk_tree_selection_select_path
1769         (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)),
1770          treepath);
1771     gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list),
1772                                  treepath, NULL, FALSE, 0.0, 0.0);
1773     gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath);
1774     gtk_tree_path_free(treepath);
1775
1776     /*
1777      * Now set up the font style list.
1778      */
1779     gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter,
1780                        1, &minval, 2, &maxval, -1);
1781     if (leftlist <= 1)
1782         unifontsel_setup_stylelist(fs, minval, maxval);
1783
1784     /*
1785      * Find the appropriate style name and select it in the list.
1786      */
1787     if (info->style) {
1788         assert(info->styleindex >= 0);
1789         treepath = gtk_tree_path_new_from_indices(info->styleindex, -1);
1790         gtk_tree_selection_select_path
1791             (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)),
1792              treepath);
1793         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list),
1794                                      treepath, NULL, FALSE, 0.0, 0.0);
1795         gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model),
1796                                 &iter, treepath);
1797         gtk_tree_path_free(treepath);
1798
1799         /*
1800          * And set up the size list.
1801          */
1802         gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter,
1803                            1, &minval, 2, &maxval, -1);
1804         if (leftlist <= 2)
1805             unifontsel_setup_sizelist(fs, minval, maxval);
1806
1807         /*
1808          * Find the appropriate size, and select it in the list.
1809          */
1810         if (info->size) {
1811             assert(info->sizeindex >= 0);
1812             treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1);
1813             gtk_tree_selection_select_path
1814                 (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)),
1815                  treepath);
1816             gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
1817                                          treepath, NULL, FALSE, 0.0, 0.0);
1818             gtk_tree_path_free(treepath);
1819             size = info->size;
1820         } else {
1821             int j;
1822             for (j = 0; j < lenof(unifontsel_default_sizes); j++)
1823                 if (unifontsel_default_sizes[j] == size) {
1824                     treepath = gtk_tree_path_new_from_indices(j, -1);
1825                     gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list),
1826                                              treepath, NULL, FALSE);
1827                     gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
1828                                                  treepath, NULL, FALSE, 0.0,
1829                                                  0.0);
1830                     gtk_tree_path_free(treepath);
1831                 }
1832         }
1833
1834         /*
1835          * And set up the font size text entry box.
1836          */
1837         {
1838             char sizetext[40];
1839             sprintf(sizetext, "%d", size);
1840             gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext);
1841         }
1842     } else {
1843         if (leftlist <= 2)
1844             unifontsel_setup_sizelist(fs, 0, 0);
1845         gtk_entry_set_text(GTK_ENTRY(fs->size_entry), "");
1846     }
1847
1848     /*
1849      * Grey out the font size edit box if we're not using a
1850      * scalable font.
1851      */
1852     gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0);
1853     gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0);
1854
1855     unifontsel_draw_preview_text(fs);
1856
1857     fs->inhibit_response = FALSE;
1858 }
1859
1860 static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data)
1861 {
1862     unifontsel_internal *fs = (unifontsel_internal *)data;
1863     int newstate = gtk_toggle_button_get_active(tb);
1864     int newflags;
1865     int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb),
1866                                                       "user-data"));
1867
1868     if (newstate)
1869         newflags = fs->filter_flags | flagbit;
1870     else
1871         newflags = fs->filter_flags & ~flagbit;
1872
1873     if (fs->filter_flags != newflags) {
1874         fs->filter_flags = newflags;
1875         unifontsel_setup_familylist(fs);
1876     }
1877 }
1878
1879 static void unifontsel_add_entry(void *ctx, const char *realfontname,
1880                                  const char *family, const char *charset,
1881                                  const char *style, const char *stylekey,
1882                                  int size, int flags,
1883                                  const struct unifont_vtable *fontclass)
1884 {
1885     unifontsel_internal *fs = (unifontsel_internal *)ctx;
1886     fontinfo *info;
1887     int totalsize;
1888     char *p;
1889
1890     totalsize = sizeof(fontinfo) + strlen(realfontname) +
1891         (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) +
1892         (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10;
1893     info = (fontinfo *)smalloc(totalsize);
1894     info->fontclass = fontclass;
1895     p = (char *)info + sizeof(fontinfo);
1896     info->realname = p;
1897     strcpy(p, realfontname);
1898     p += 1+strlen(p);
1899     if (family) {
1900         info->family = p;
1901         strcpy(p, family);
1902         p += 1+strlen(p);
1903     } else
1904         info->family = NULL;
1905     if (charset) {
1906         info->charset = p;
1907         strcpy(p, charset);
1908         p += 1+strlen(p);
1909     } else
1910         info->charset = NULL;
1911     if (style) {
1912         info->style = p;
1913         strcpy(p, style);
1914         p += 1+strlen(p);
1915     } else
1916         info->style = NULL;
1917     if (stylekey) {
1918         info->stylekey = p;
1919         strcpy(p, stylekey);
1920         p += 1+strlen(p);
1921     } else
1922         info->stylekey = NULL;
1923     assert(p - (char *)info <= totalsize);
1924     info->size = size;
1925     info->flags = flags;
1926     info->index = count234(fs->fonts_by_selorder);
1927
1928     /*
1929      * It's just conceivable that a misbehaving font enumerator
1930      * might tell us about the same font real name more than once,
1931      * in which case we should silently drop the new one.
1932      */
1933     if (add234(fs->fonts_by_realname, info) != info) {
1934         sfree(info);
1935         return;
1936     }
1937     /*
1938      * However, we should never get a duplicate key in the
1939      * selorder tree, because the index field carefully
1940      * disambiguates otherwise identical records.
1941      */
1942     add234(fs->fonts_by_selorder, info);
1943 }
1944
1945 static fontinfo *update_for_intended_size(unifontsel_internal *fs,
1946                                           fontinfo *info)
1947 {
1948     fontinfo info2, *below, *above;
1949     int pos;
1950
1951     /*
1952      * Copy the info structure. This doesn't copy its dynamic
1953      * string fields, but that's unimportant because all we're
1954      * going to do is to adjust the size field and use it in one
1955      * tree search.
1956      */
1957     info2 = *info;
1958     info2.size = fs->intendedsize;
1959
1960     /*
1961      * Search in the tree to find the fontinfo structure which
1962      * best approximates the size the user last requested.
1963      */
1964     below = findrelpos234(fs->fonts_by_selorder, &info2, NULL,
1965                           REL234_LE, &pos);
1966     above = index234(fs->fonts_by_selorder, pos+1);
1967
1968     /*
1969      * See if we've found it exactly, which is an easy special
1970      * case. If we have, it'll be in `below' and not `above',
1971      * because we did a REL234_LE rather than REL234_LT search.
1972      */
1973     if (!fontinfo_selorder_compare(&info2, below))
1974         return below;
1975
1976     /*
1977      * Now we've either found two suitable fonts, one smaller and
1978      * one larger, or we're at one or other extreme end of the
1979      * scale. Find out which, by NULLing out either of below and
1980      * above if it differs from this one in any respect but size
1981      * (and the disambiguating index field). Bear in mind, also,
1982      * that either one might _already_ be NULL if we're at the
1983      * extreme ends of the font list.
1984      */
1985     if (below) {
1986         info2.size = below->size;
1987         info2.index = below->index;
1988         if (fontinfo_selorder_compare(&info2, below))
1989             below = NULL;
1990     }
1991     if (above) {
1992         info2.size = above->size;
1993         info2.index = above->index;
1994         if (fontinfo_selorder_compare(&info2, above))
1995             above = NULL;
1996     }
1997
1998     /*
1999      * Now return whichever of above and below is non-NULL, if
2000      * that's unambiguous.
2001      */
2002     if (!above)
2003         return below;
2004     if (!below)
2005         return above;
2006
2007     /*
2008      * And now we really do have to make a choice about whether to
2009      * round up or down. We'll do it by rounding to nearest,
2010      * breaking ties by rounding up.
2011      */
2012     if (above->size - fs->intendedsize <= fs->intendedsize - below->size)
2013         return above;
2014     else
2015         return below;
2016 }
2017
2018 static void family_changed(GtkTreeSelection *treeselection, gpointer data)
2019 {
2020     unifontsel_internal *fs = (unifontsel_internal *)data;
2021     GtkTreeModel *treemodel;
2022     GtkTreeIter treeiter;
2023     int minval;
2024     fontinfo *info;
2025
2026     if (fs->inhibit_response)          /* we made this change ourselves */
2027         return;
2028
2029     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
2030         return;
2031
2032     gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
2033     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
2034     info = update_for_intended_size(fs, info);
2035     if (!info)
2036         return; /* _shouldn't_ happen unless font list is completely funted */
2037     if (!info->size)
2038         fs->selsize = fs->intendedsize;   /* font is scalable */
2039     unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
2040                            1, FALSE);
2041 }
2042
2043 static void style_changed(GtkTreeSelection *treeselection, gpointer data)
2044 {
2045     unifontsel_internal *fs = (unifontsel_internal *)data;
2046     GtkTreeModel *treemodel;
2047     GtkTreeIter treeiter;
2048     int minval;
2049     fontinfo *info;
2050
2051     if (fs->inhibit_response)          /* we made this change ourselves */
2052         return;
2053
2054     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
2055         return;
2056
2057     gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
2058     if (minval < 0)
2059         return;                    /* somehow a charset heading got clicked */
2060     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
2061     info = update_for_intended_size(fs, info);
2062     if (!info)
2063         return; /* _shouldn't_ happen unless font list is completely funted */
2064     if (!info->size)
2065         fs->selsize = fs->intendedsize;   /* font is scalable */
2066     unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
2067                            2, FALSE);
2068 }
2069
2070 static void size_changed(GtkTreeSelection *treeselection, gpointer data)
2071 {
2072     unifontsel_internal *fs = (unifontsel_internal *)data;
2073     GtkTreeModel *treemodel;
2074     GtkTreeIter treeiter;
2075     int minval, size;
2076     fontinfo *info;
2077
2078     if (fs->inhibit_response)          /* we made this change ourselves */
2079         return;
2080
2081     if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
2082         return;
2083
2084     gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1);
2085     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
2086     unifontsel_select_font(fs, info, info->size ? info->size : size, 3, TRUE);
2087 }
2088
2089 static void size_entry_changed(GtkEditable *ed, gpointer data)
2090 {
2091     unifontsel_internal *fs = (unifontsel_internal *)data;
2092     const char *text;
2093     int size;
2094
2095     if (fs->inhibit_response)          /* we made this change ourselves */
2096         return;
2097
2098     text = gtk_entry_get_text(GTK_ENTRY(ed));
2099     size = atoi(text);
2100
2101     if (size > 0) {
2102         assert(fs->selected->size == 0);
2103         unifontsel_select_font(fs, fs->selected, size, 3, TRUE);
2104     }
2105 }
2106
2107 static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path,
2108                           GtkTreeViewColumn *column, gpointer data)
2109 {
2110     unifontsel_internal *fs = (unifontsel_internal *)data;
2111     GtkTreeIter iter;
2112     int minval, newsize;
2113     fontinfo *info, *newinfo;
2114     char *newname;
2115
2116     if (fs->inhibit_response)          /* we made this change ourselves */
2117         return;
2118
2119     gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path);
2120     gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1);
2121     info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
2122     if (info) {
2123         int flags;
2124         struct fontinfo_realname_find f;
2125
2126         newname = info->fontclass->canonify_fontname
2127             (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, TRUE);
2128
2129         f.realname = newname;
2130         f.flags = flags;
2131         newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
2132
2133         sfree(newname);
2134         if (!newinfo)
2135             return;                    /* font name not in our index */
2136         if (newinfo == info)
2137             return;   /* didn't change under canonification => not an alias */
2138         unifontsel_select_font(fs, newinfo,
2139                                newinfo->size ? newinfo->size : newsize,
2140                                1, TRUE);
2141     }
2142 }
2143
2144 static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event,
2145                                    gpointer data)
2146 {
2147     unifontsel_internal *fs = (unifontsel_internal *)data;
2148
2149     if (fs->preview_pixmap) {
2150         gdk_draw_pixmap(widget->window,
2151                         widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
2152                         fs->preview_pixmap,
2153                         event->area.x, event->area.y,
2154                         event->area.x, event->area.y,
2155                         event->area.width, event->area.height);
2156     }
2157     return TRUE;
2158 }
2159
2160 static gint unifontsel_configure_area(GtkWidget *widget,
2161                                       GdkEventConfigure *event, gpointer data)
2162 {
2163     unifontsel_internal *fs = (unifontsel_internal *)data;
2164     int ox, oy, nx, ny, x, y;
2165
2166     /*
2167      * Enlarge the pixmap, but never shrink it.
2168      */
2169     ox = fs->preview_width;
2170     oy = fs->preview_height;
2171     x = event->width;
2172     y = event->height;
2173     if (x > ox || y > oy) {
2174         if (fs->preview_pixmap)
2175             gdk_pixmap_unref(fs->preview_pixmap);
2176         
2177         nx = (x > ox ? x : ox);
2178         ny = (y > oy ? y : oy);
2179         fs->preview_pixmap = gdk_pixmap_new(widget->window, nx, ny, -1);
2180         fs->preview_width = nx;
2181         fs->preview_height = ny;
2182
2183         unifontsel_draw_preview_text(fs);
2184     }
2185
2186     gdk_window_invalidate_rect(widget->window, NULL, FALSE);
2187
2188     return TRUE;
2189 }
2190
2191 unifontsel *unifontsel_new(const char *wintitle)
2192 {
2193     unifontsel_internal *fs = snew(unifontsel_internal);
2194     GtkWidget *table, *label, *w, *ww, *scroll;
2195     GtkListStore *model;
2196     GtkTreeViewColumn *column;
2197     int lists_height, preview_height, font_width, style_width, size_width;
2198     int i;
2199
2200     fs->inhibit_response = FALSE;
2201     fs->selected = NULL;
2202
2203     {
2204         /*
2205          * Invent some magic size constants.
2206          */
2207         GtkRequisition req;
2208         label = gtk_label_new("Quite Long Font Name (Foundry)");
2209         gtk_widget_size_request(label, &req);
2210         font_width = req.width;
2211         lists_height = 14 * req.height;
2212         preview_height = 5 * req.height;
2213         gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed");
2214         gtk_widget_size_request(label, &req);
2215         style_width = req.width;
2216         gtk_label_set_text(GTK_LABEL(label), "48000");
2217         gtk_widget_size_request(label, &req);
2218         size_width = req.width;
2219 #if GTK_CHECK_VERSION(2,10,0)
2220         g_object_ref_sink(label);
2221         g_object_unref(label);
2222 #else
2223         gtk_object_sink(GTK_OBJECT(label));
2224 #endif
2225     }
2226
2227     /*
2228      * Create the dialog box and initialise the user-visible
2229      * fields in the returned structure.
2230      */
2231     fs->u.user_data = NULL;
2232     fs->u.window = GTK_WINDOW(gtk_dialog_new());
2233     gtk_window_set_title(fs->u.window, wintitle);
2234     fs->u.cancel_button = gtk_dialog_add_button
2235         (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
2236     fs->u.ok_button = gtk_dialog_add_button
2237         (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK);
2238     gtk_widget_grab_default(fs->u.ok_button);
2239
2240     /*
2241      * Now set up the internal fields, including in particular all
2242      * the controls that actually allow the user to select fonts.
2243      */
2244     table = gtk_table_new(8, 3, FALSE);
2245     gtk_widget_show(table);
2246     gtk_table_set_col_spacings(GTK_TABLE(table), 8);
2247 #if GTK_CHECK_VERSION(2,4,0)
2248     /* GtkAlignment seems to be the simplest way to put padding round things */
2249     w = gtk_alignment_new(0, 0, 1, 1);
2250     gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
2251     gtk_container_add(GTK_CONTAINER(w), table);
2252     gtk_widget_show(w);
2253 #else
2254     w = table;
2255 #endif
2256     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox),
2257                        w, TRUE, TRUE, 0);
2258
2259     label = gtk_label_new_with_mnemonic("_Font:");
2260     gtk_widget_show(label);
2261     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
2262     gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
2263
2264     /*
2265      * The Font list box displays only a string, but additionally
2266      * stores two integers which give the limits within the
2267      * tree234 of the font entries covered by this list entry.
2268      */
2269     model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
2270     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
2271     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
2272     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
2273     gtk_widget_show(w);
2274     column = gtk_tree_view_column_new_with_attributes
2275         ("Font", gtk_cell_renderer_text_new(),
2276          "text", 0, (char *)NULL);
2277     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
2278     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
2279     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
2280                      "changed", G_CALLBACK(family_changed), fs);
2281     g_signal_connect(G_OBJECT(w), "row-activated",
2282                      G_CALLBACK(alias_resolve), fs);
2283
2284     scroll = gtk_scrolled_window_new(NULL, NULL);
2285     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
2286                                         GTK_SHADOW_IN);
2287     gtk_container_add(GTK_CONTAINER(scroll), w);
2288     gtk_widget_show(scroll);
2289     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
2290                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
2291     gtk_widget_set_size_request(scroll, font_width, lists_height);
2292     gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL,
2293                      GTK_EXPAND | GTK_FILL, 0, 0);
2294     fs->family_model = model;
2295     fs->family_list = w;
2296
2297     label = gtk_label_new_with_mnemonic("_Style:");
2298     gtk_widget_show(label);
2299     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
2300     gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
2301
2302     /*
2303      * The Style list box can contain insensitive elements
2304      * (character set headings for server-side fonts), so we add
2305      * an extra column to the list store to hold that information.
2306      */
2307     model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
2308                                G_TYPE_BOOLEAN);
2309     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
2310     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
2311     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
2312     gtk_widget_show(w);
2313     column = gtk_tree_view_column_new_with_attributes
2314         ("Style", gtk_cell_renderer_text_new(),
2315          "text", 0, "sensitive", 3, (char *)NULL);
2316     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
2317     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
2318     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
2319                      "changed", G_CALLBACK(style_changed), fs);
2320
2321     scroll = gtk_scrolled_window_new(NULL, NULL);
2322     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
2323                                         GTK_SHADOW_IN);
2324     gtk_container_add(GTK_CONTAINER(scroll), w);
2325     gtk_widget_show(scroll);
2326     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
2327                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
2328     gtk_widget_set_size_request(scroll, style_width, lists_height);
2329     gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL,
2330                      GTK_EXPAND | GTK_FILL, 0, 0);
2331     fs->style_model = model;
2332     fs->style_list = w;
2333
2334     label = gtk_label_new_with_mnemonic("Si_ze:");
2335     gtk_widget_show(label);
2336     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
2337     gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
2338
2339     /*
2340      * The Size label attaches primarily to a text input box so
2341      * that the user can select a size of their choice. The list
2342      * of available sizes is secondary.
2343      */
2344     fs->size_entry = w = gtk_entry_new();
2345     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
2346     gtk_widget_set_size_request(w, size_width, -1);
2347     gtk_widget_show(w);
2348     gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
2349     g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed),
2350                      fs);
2351
2352     model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
2353     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
2354     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
2355     gtk_widget_show(w);
2356     column = gtk_tree_view_column_new_with_attributes
2357         ("Size", gtk_cell_renderer_text_new(),
2358          "text", 0, (char *)NULL);
2359     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
2360     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
2361     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
2362                      "changed", G_CALLBACK(size_changed), fs);
2363
2364     scroll = gtk_scrolled_window_new(NULL, NULL);
2365     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
2366                                         GTK_SHADOW_IN);
2367     gtk_container_add(GTK_CONTAINER(scroll), w);
2368     gtk_widget_show(scroll);
2369     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
2370                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
2371     gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL,
2372                      GTK_EXPAND | GTK_FILL, 0, 0);
2373     fs->size_model = model;
2374     fs->size_list = w;
2375
2376     /*
2377      * Preview widget.
2378      */
2379     fs->preview_area = gtk_drawing_area_new();
2380     fs->preview_pixmap = NULL;
2381     fs->preview_width = 0;
2382     fs->preview_height = 0;
2383     fs->preview_fg.pixel = fs->preview_bg.pixel = 0;
2384     fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000;
2385     fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF;
2386     gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg,
2387                              FALSE, FALSE);
2388     gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg,
2389                              FALSE, FALSE);
2390     gtk_signal_connect(GTK_OBJECT(fs->preview_area), "expose_event",
2391                        GTK_SIGNAL_FUNC(unifontsel_expose_area), fs);
2392     gtk_signal_connect(GTK_OBJECT(fs->preview_area), "configure_event",
2393                        GTK_SIGNAL_FUNC(unifontsel_configure_area), fs);
2394     gtk_widget_set_size_request(fs->preview_area, 1, preview_height);
2395     gtk_widget_show(fs->preview_area);
2396     ww = fs->preview_area;
2397     w = gtk_frame_new(NULL);
2398     gtk_container_add(GTK_CONTAINER(w), ww);
2399     gtk_widget_show(w);
2400 #if GTK_CHECK_VERSION(2,4,0)
2401     ww = w;
2402     /* GtkAlignment seems to be the simplest way to put padding round things */
2403     w = gtk_alignment_new(0, 0, 1, 1);
2404     gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
2405     gtk_container_add(GTK_CONTAINER(w), ww);
2406     gtk_widget_show(w);
2407 #endif
2408     ww = w;
2409     w = gtk_frame_new("Preview of font");
2410     gtk_container_add(GTK_CONTAINER(w), ww);
2411     gtk_widget_show(w);
2412     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4,
2413                      GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8);
2414
2415     i = 0;
2416     w = gtk_check_button_new_with_label("Show client-side fonts");
2417     gtk_object_set_data(GTK_OBJECT(w), "user-data",
2418                         GINT_TO_POINTER(FONTFLAG_CLIENTSIDE));
2419     gtk_signal_connect(GTK_OBJECT(w), "toggled",
2420                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
2421     gtk_widget_show(w);
2422     fs->filter_buttons[i++] = w;
2423     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0);
2424     w = gtk_check_button_new_with_label("Show server-side fonts");
2425     gtk_object_set_data(GTK_OBJECT(w), "user-data",
2426                         GINT_TO_POINTER(FONTFLAG_SERVERSIDE));
2427     gtk_signal_connect(GTK_OBJECT(w), "toggled",
2428                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
2429     gtk_widget_show(w);
2430     fs->filter_buttons[i++] = w;
2431     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0);
2432     w = gtk_check_button_new_with_label("Show server-side font aliases");
2433     gtk_object_set_data(GTK_OBJECT(w), "user-data",
2434                         GINT_TO_POINTER(FONTFLAG_SERVERALIAS));
2435     gtk_signal_connect(GTK_OBJECT(w), "toggled",
2436                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
2437     gtk_widget_show(w);
2438     fs->filter_buttons[i++] = w;
2439     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0);
2440     w = gtk_check_button_new_with_label("Show non-monospaced fonts");
2441     gtk_object_set_data(GTK_OBJECT(w), "user-data",
2442                         GINT_TO_POINTER(FONTFLAG_NONMONOSPACED));
2443     gtk_signal_connect(GTK_OBJECT(w), "toggled",
2444                        GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
2445     gtk_widget_show(w);
2446     fs->filter_buttons[i++] = w;
2447     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0);
2448
2449     assert(i == lenof(fs->filter_buttons));
2450     fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE |
2451         FONTFLAG_SERVERALIAS;
2452     unifontsel_set_filter_buttons(fs);
2453
2454     /*
2455      * Go and find all the font names, and set up our master font
2456      * list.
2457      */
2458     fs->fonts_by_realname = newtree234(fontinfo_realname_compare);
2459     fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare);
2460     for (i = 0; i < lenof(unifont_types); i++)
2461         unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window),
2462                                      unifontsel_add_entry, fs);
2463
2464     /*
2465      * And set up the initial font names list.
2466      */
2467     unifontsel_setup_familylist(fs);
2468
2469     fs->selsize = fs->intendedsize = 13;   /* random default */
2470     gtk_widget_set_sensitive(fs->u.ok_button, FALSE);
2471
2472     return (unifontsel *)fs;
2473 }
2474
2475 void unifontsel_destroy(unifontsel *fontsel)
2476 {
2477     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
2478     fontinfo *info;
2479
2480     if (fs->preview_pixmap)
2481         gdk_pixmap_unref(fs->preview_pixmap);
2482
2483     freetree234(fs->fonts_by_selorder);
2484     while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL)
2485         sfree(info);
2486     freetree234(fs->fonts_by_realname);
2487
2488     gtk_widget_destroy(GTK_WIDGET(fs->u.window));
2489     sfree(fs);
2490 }
2491
2492 void unifontsel_set_name(unifontsel *fontsel, const char *fontname)
2493 {
2494     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
2495     int i, start, end, size, flags;
2496     const char *fontname2 = NULL;
2497     fontinfo *info;
2498
2499     /*
2500      * Provide a default if given an empty or null font name.
2501      */
2502     if (!fontname || !*fontname)
2503         fontname = "server:fixed";
2504
2505     /*
2506      * Call the canonify_fontname function.
2507      */
2508     fontname = unifont_do_prefix(fontname, &start, &end);
2509     for (i = start; i < end; i++) {
2510         fontname2 = unifont_types[i]->canonify_fontname
2511             (GTK_WIDGET(fs->u.window), fontname, &size, &flags, FALSE);
2512         if (fontname2)
2513             break;
2514     }
2515     if (i == end)
2516         return;                        /* font name not recognised */
2517
2518     /*
2519      * Now look up the canonified font name in our index.
2520      */
2521     {
2522         struct fontinfo_realname_find f;
2523         f.realname = fontname2;
2524         f.flags = flags;
2525         info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
2526     }
2527
2528     /*
2529      * If we've found the font, and its size field is either
2530      * correct or zero (the latter indicating a scalable font),
2531      * then we're done. Otherwise, try looking up the original
2532      * font name instead.
2533      */
2534     if (!info || (info->size != size && info->size != 0)) {
2535         struct fontinfo_realname_find f;
2536         f.realname = fontname;
2537         f.flags = flags;
2538
2539         info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
2540         if (!info || info->size != size)
2541             return;                    /* font name not in our index */
2542     }
2543
2544     /*
2545      * Now we've got a fontinfo structure and a font size, so we
2546      * know everything we need to fill in all the fields in the
2547      * dialog.
2548      */
2549     unifontsel_select_font(fs, info, size, 0, TRUE);
2550 }
2551
2552 char *unifontsel_get_name(unifontsel *fontsel)
2553 {
2554     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
2555     char *name;
2556
2557     if (!fs->selected)
2558         return NULL;
2559
2560     if (fs->selected->size == 0) {
2561         name = fs->selected->fontclass->scale_fontname
2562             (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize);
2563         if (name) {
2564             char *ret = dupcat(fs->selected->fontclass->prefix, ":",
2565                                name, NULL);
2566             sfree(name);
2567             return ret;
2568         }
2569     }
2570
2571     return dupcat(fs->selected->fontclass->prefix, ":",
2572                   fs->selected->realname, NULL);
2573 }
2574
2575 #endif /* GTK_CHECK_VERSION(2,0,0) */