OSDN Git Service

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