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