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) { |
8412ec80 |
895 | int clen, n; |
f96e9a61 |
896 | |
897 | /* |
8412ec80 |
898 | * We want to display every character from this string in |
899 | * the centre of its own character cell. In the worst case, |
900 | * this requires a separate text-drawing call for each |
901 | * character; but in the common case where the font is |
902 | * properly fixed-width, we can draw many characters in one |
903 | * go which is much faster. |
904 | * |
905 | * This still isn't really ideal. If you look at what |
906 | * happens in the X protocol as a result of all of this, you |
907 | * find - naturally enough - that each call to |
908 | * gdk_draw_layout() generates a separate set of X RENDER |
909 | * operations involving creating a picture, setting a clip |
910 | * rectangle, doing some drawing and undoing the whole lot. |
911 | * In an ideal world, we should _always_ be able to turn the |
912 | * contents of this loop into a single RenderCompositeGlyphs |
913 | * operation which internally specifies inter-character |
914 | * deltas to get the spacing right, which would give us full |
915 | * speed _even_ in the worst case of a non-fixed-width font. |
916 | * However, Pango's architecture and documentation are so |
917 | * unhelpful that I have no idea how if at all to persuade |
918 | * them to do that. |
919 | */ |
920 | |
921 | /* |
922 | * Start by extracting a single UTF-8 character from the |
923 | * string. |
f96e9a61 |
924 | */ |
925 | clen = 1; |
926 | while (clen < len && |
927 | (unsigned char)string[clen] >= 0x80 && |
928 | (unsigned char)string[clen] < 0xC0) |
929 | clen++; |
8412ec80 |
930 | n = 1; |
f96e9a61 |
931 | |
8412ec80 |
932 | /* |
933 | * See if that character has the width we expect. |
934 | */ |
f96e9a61 |
935 | pango_layout_set_text(layout, string, clen); |
936 | pango_layout_get_pixel_extents(layout, NULL, &rect); |
8412ec80 |
937 | |
938 | if (rect.width == cellwidth) { |
939 | /* |
940 | * Try extracting more characters, for as long as they |
941 | * stay well-behaved. |
942 | */ |
943 | while (clen < len) { |
944 | int oldclen = clen; |
945 | clen++; /* skip UTF-8 introducer byte */ |
946 | while (clen < len && |
947 | (unsigned char)string[clen] >= 0x80 && |
948 | (unsigned char)string[clen] < 0xC0) |
949 | clen++; |
950 | n++; |
951 | pango_layout_set_text(layout, string, clen); |
952 | pango_layout_get_pixel_extents(layout, NULL, &rect); |
953 | if (rect.width != n * cellwidth) { |
954 | clen = oldclen; |
955 | n--; |
956 | break; |
957 | } |
958 | } |
959 | } |
960 | |
961 | pango_layout_set_text(layout, string, clen); |
962 | pango_layout_get_pixel_extents(layout, NULL, &rect); |
963 | gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2, |
f96e9a61 |
964 | y + (pfont->u.height - rect.height)/2, layout); |
965 | if (shadowbold) |
8412ec80 |
966 | gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset, |
f96e9a61 |
967 | y + (pfont->u.height - rect.height)/2, layout); |
968 | |
969 | len -= clen; |
970 | string += clen; |
8412ec80 |
971 | x += n * cellwidth; |
f96e9a61 |
972 | } |
973 | |
974 | g_object_unref(layout); |
975 | } |
976 | |
977 | /* |
978 | * Dummy size value to be used when converting a |
979 | * PangoFontDescription of a scalable font to a string for |
980 | * internal use. |
981 | */ |
982 | #define PANGO_DUMMY_SIZE 12 |
983 | |
984 | static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, |
985 | void *callback_ctx) |
986 | { |
987 | PangoContext *ctx; |
988 | #ifndef PANGO_PRE_1POINT6 |
989 | PangoFontMap *map; |
990 | #endif |
991 | PangoFontFamily **families; |
992 | int i, nfamilies; |
993 | |
994 | ctx = gtk_widget_get_pango_context(widget); |
995 | if (!ctx) |
996 | return; |
997 | |
998 | /* |
999 | * Ask Pango for a list of font families, and iterate through |
1000 | * them. |
1001 | */ |
1002 | #ifndef PANGO_PRE_1POINT6 |
1003 | map = pango_context_get_font_map(ctx); |
1004 | if (!map) |
1005 | return; |
1006 | pango_font_map_list_families(map, &families, &nfamilies); |
1007 | #else |
1008 | pango_context_list_families(ctx, &families, &nfamilies); |
1009 | #endif |
1010 | for (i = 0; i < nfamilies; i++) { |
1011 | PangoFontFamily *family = families[i]; |
1012 | const char *familyname; |
1013 | int flags; |
1014 | PangoFontFace **faces; |
1015 | int j, nfaces; |
1016 | |
1017 | /* |
1018 | * Set up our flags for this font family, and get the name |
1019 | * string. |
1020 | */ |
1021 | flags = FONTFLAG_CLIENTSIDE; |
1022 | #ifndef PANGO_PRE_1POINT4 |
1023 | /* |
1024 | * In very early versions of Pango, we can't tell |
1025 | * monospaced fonts from non-monospaced. |
1026 | */ |
1027 | if (!pango_font_family_is_monospace(family)) |
1028 | flags |= FONTFLAG_NONMONOSPACED; |
1029 | #endif |
1030 | familyname = pango_font_family_get_name(family); |
1031 | |
1032 | /* |
1033 | * Go through the available font faces in this family. |
1034 | */ |
1035 | pango_font_family_list_faces(family, &faces, &nfaces); |
1036 | for (j = 0; j < nfaces; j++) { |
1037 | PangoFontFace *face = faces[j]; |
1038 | PangoFontDescription *desc; |
1039 | const char *facename; |
1040 | int *sizes; |
1041 | int k, nsizes, dummysize; |
1042 | |
1043 | /* |
1044 | * Get the face name string. |
1045 | */ |
1046 | facename = pango_font_face_get_face_name(face); |
1047 | |
1048 | /* |
1049 | * Set up a font description with what we've got so |
1050 | * far. We'll fill in the size field manually and then |
1051 | * call pango_font_description_to_string() to give the |
1052 | * full real name of the specific font. |
1053 | */ |
1054 | desc = pango_font_face_describe(face); |
1055 | |
1056 | /* |
1057 | * See if this font has a list of specific sizes. |
1058 | */ |
1059 | #ifndef PANGO_PRE_1POINT4 |
1060 | pango_font_face_list_sizes(face, &sizes, &nsizes); |
1061 | #else |
1062 | /* |
1063 | * In early versions of Pango, that call wasn't |
1064 | * supported; we just have to assume everything is |
1065 | * scalable. |
1066 | */ |
1067 | sizes = NULL; |
1068 | #endif |
1069 | if (!sizes) { |
1070 | /* |
1071 | * Write a single entry with a dummy size. |
1072 | */ |
1073 | dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE; |
1074 | sizes = &dummysize; |
1075 | nsizes = 1; |
1076 | } |
1077 | |
1078 | /* |
1079 | * If so, go through them one by one. |
1080 | */ |
1081 | for (k = 0; k < nsizes; k++) { |
1082 | char *fullname; |
1083 | char stylekey[128]; |
1084 | |
1085 | pango_font_description_set_size(desc, sizes[k]); |
1086 | |
1087 | fullname = pango_font_description_to_string(desc); |
1088 | |
1089 | /* |
1090 | * Construct the sorting key for font styles. |
1091 | */ |
1092 | { |
1093 | char *p = stylekey; |
1094 | int n; |
1095 | |
1096 | n = pango_font_description_get_weight(desc); |
1097 | /* Weight: normal, then lighter, then bolder */ |
1098 | if (n <= PANGO_WEIGHT_NORMAL) |
1099 | n = PANGO_WEIGHT_NORMAL - n; |
1100 | p += sprintf(p, "%4d", n); |
1101 | |
1102 | n = pango_font_description_get_style(desc); |
1103 | p += sprintf(p, " %2d", n); |
1104 | |
1105 | n = pango_font_description_get_stretch(desc); |
1106 | /* Stretch: closer to normal sorts earlier */ |
1107 | n = 2 * abs(PANGO_STRETCH_NORMAL - n) + |
1108 | (n < PANGO_STRETCH_NORMAL); |
1109 | p += sprintf(p, " %2d", n); |
1110 | |
1111 | n = pango_font_description_get_variant(desc); |
1112 | p += sprintf(p, " %2d", n); |
1113 | |
1114 | } |
1115 | |
1116 | /* |
1117 | * Got everything. Hand off to the callback. |
1118 | * (The charset string is NULL, because only |
1119 | * server-side X fonts use it.) |
1120 | */ |
1121 | callback(callback_ctx, fullname, familyname, NULL, facename, |
1122 | stylekey, |
1123 | (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])), |
1124 | flags, &pangofont_vtable); |
1125 | |
1126 | g_free(fullname); |
1127 | } |
1128 | if (sizes != &dummysize) |
1129 | g_free(sizes); |
1130 | |
1131 | pango_font_description_free(desc); |
1132 | } |
1133 | g_free(faces); |
1134 | } |
1135 | g_free(families); |
1136 | } |
1137 | |
1138 | static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, |
1139 | int *size, int *flags, |
1140 | int resolve_aliases) |
1141 | { |
1142 | /* |
1143 | * When given a Pango font name to try to make sense of for a |
1144 | * font selector, we must normalise it to PANGO_DUMMY_SIZE and |
1145 | * extract its original size (in pixels) into the `size' field. |
1146 | */ |
1147 | PangoContext *ctx; |
1148 | #ifndef PANGO_PRE_1POINT6 |
1149 | PangoFontMap *map; |
1150 | #endif |
1151 | PangoFontDescription *desc; |
1152 | PangoFontset *fset; |
1153 | PangoFontMetrics *metrics; |
1154 | char *newname, *retname; |
1155 | |
1156 | desc = pango_font_description_from_string(name); |
1157 | if (!desc) |
1158 | return NULL; |
1159 | ctx = gtk_widget_get_pango_context(widget); |
1160 | if (!ctx) { |
1161 | pango_font_description_free(desc); |
1162 | return NULL; |
1163 | } |
1164 | if (!pangofont_check_desc_makes_sense(ctx, desc)) { |
1165 | pango_font_description_free(desc); |
1166 | return NULL; |
1167 | } |
1168 | #ifndef PANGO_PRE_1POINT6 |
1169 | map = pango_context_get_font_map(ctx); |
1170 | if (!map) { |
1171 | pango_font_description_free(desc); |
1172 | return NULL; |
1173 | } |
1174 | fset = pango_font_map_load_fontset(map, ctx, desc, |
1175 | pango_context_get_language(ctx)); |
1176 | #else |
1177 | fset = pango_context_load_fontset(ctx, desc, |
1178 | pango_context_get_language(ctx)); |
1179 | #endif |
1180 | if (!fset) { |
1181 | pango_font_description_free(desc); |
1182 | return NULL; |
1183 | } |
1184 | metrics = pango_fontset_get_metrics(fset); |
1185 | if (!metrics || |
1186 | pango_font_metrics_get_approximate_digit_width(metrics) == 0) { |
1187 | pango_font_description_free(desc); |
1188 | g_object_unref(fset); |
1189 | return NULL; |
1190 | } |
1191 | |
1192 | *size = PANGO_PIXELS(pango_font_description_get_size(desc)); |
1193 | *flags = FONTFLAG_CLIENTSIDE; |
1194 | pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE); |
1195 | newname = pango_font_description_to_string(desc); |
1196 | retname = dupstr(newname); |
1197 | g_free(newname); |
1198 | |
1199 | pango_font_metrics_unref(metrics); |
1200 | pango_font_description_free(desc); |
1201 | g_object_unref(fset); |
1202 | |
1203 | return retname; |
1204 | } |
1205 | |
1206 | static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, |
1207 | int size) |
1208 | { |
1209 | PangoFontDescription *desc; |
1210 | char *newname, *retname; |
1211 | |
1212 | desc = pango_font_description_from_string(name); |
1213 | if (!desc) |
1214 | return NULL; |
1215 | pango_font_description_set_size(desc, size * PANGO_SCALE); |
1216 | newname = pango_font_description_to_string(desc); |
1217 | retname = dupstr(newname); |
1218 | g_free(newname); |
1219 | pango_font_description_free(desc); |
1220 | |
1221 | return retname; |
1222 | } |
1223 | |
1224 | #endif /* GTK_CHECK_VERSION(2,0,0) */ |
1225 | |
1226 | /* ---------------------------------------------------------------------- |
1227 | * Outermost functions which do the vtable dispatch. |
1228 | */ |
1229 | |
1230 | /* |
1231 | * Complete list of font-type subclasses. Listed in preference |
1232 | * order for unifont_create(). (That is, in the extremely unlikely |
1233 | * event that the same font name is valid as both a Pango and an |
1234 | * X11 font, it will be interpreted as the former in the absence |
1235 | * of an explicit type-disambiguating prefix.) |
1236 | */ |
1237 | static const struct unifont_vtable *unifont_types[] = { |
1238 | #if GTK_CHECK_VERSION(2,0,0) |
1239 | &pangofont_vtable, |
1240 | #endif |
1241 | &x11font_vtable, |
1242 | }; |
1243 | |
1244 | /* |
1245 | * Function which takes a font name and processes the optional |
1246 | * scheme prefix. Returns the tail of the font name suitable for |
1247 | * passing to individual font scheme functions, and also provides |
1248 | * a subrange of the unifont_types[] array above. |
1249 | * |
1250 | * The return values `start' and `end' denote a half-open interval |
1251 | * in unifont_types[]; that is, the correct way to iterate over |
1252 | * them is |
1253 | * |
1254 | * for (i = start; i < end; i++) {...} |
1255 | */ |
1256 | static const char *unifont_do_prefix(const char *name, int *start, int *end) |
1257 | { |
1258 | int colonpos = strcspn(name, ":"); |
1259 | int i; |
1260 | |
1261 | if (name[colonpos]) { |
1262 | /* |
1263 | * There's a colon prefix on the font name. Use it to work |
1264 | * out which subclass to use. |
1265 | */ |
1266 | for (i = 0; i < lenof(unifont_types); i++) { |
1267 | if (strlen(unifont_types[i]->prefix) == colonpos && |
1268 | !strncmp(unifont_types[i]->prefix, name, colonpos)) { |
1269 | *start = i; |
1270 | *end = i+1; |
1271 | return name + colonpos + 1; |
1272 | } |
1273 | } |
1274 | /* |
1275 | * None matched, so return an empty scheme list to prevent |
1276 | * any scheme from being called at all. |
1277 | */ |
1278 | *start = *end = 0; |
1279 | return name + colonpos + 1; |
1280 | } else { |
1281 | /* |
1282 | * No colon prefix, so just use all the subclasses. |
1283 | */ |
1284 | *start = 0; |
1285 | *end = lenof(unifont_types); |
1286 | return name; |
1287 | } |
1288 | } |
1289 | |
1290 | unifont *unifont_create(GtkWidget *widget, const char *name, int wide, |
1291 | int bold, int shadowoffset, int shadowalways) |
1292 | { |
1293 | int i, start, end; |
1294 | |
1295 | name = unifont_do_prefix(name, &start, &end); |
1296 | |
1297 | for (i = start; i < end; i++) { |
1298 | unifont *ret = unifont_types[i]->create(widget, name, wide, bold, |
1299 | shadowoffset, shadowalways); |
1300 | if (ret) |
1301 | return ret; |
1302 | } |
1303 | return NULL; /* font not found in any scheme */ |
1304 | } |
1305 | |
1306 | void unifont_destroy(unifont *font) |
1307 | { |
1308 | font->vt->destroy(font); |
1309 | } |
1310 | |
1311 | void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font, |
1312 | int x, int y, const char *string, int len, |
1313 | int wide, int bold, int cellwidth) |
1314 | { |
1315 | font->vt->draw_text(target, gc, font, x, y, string, len, |
1316 | wide, bold, cellwidth); |
1317 | } |
1318 | |
1319 | #if GTK_CHECK_VERSION(2,0,0) |
1320 | |
1321 | /* ---------------------------------------------------------------------- |
1322 | * Implementation of a unified font selector. Used on GTK 2 only; |
1323 | * for GTK 1 we still use the standard font selector. |
1324 | */ |
1325 | |
1326 | typedef struct fontinfo fontinfo; |
1327 | |
1328 | typedef struct unifontsel_internal { |
1329 | /* This must be the structure's first element, for cross-casting */ |
1330 | unifontsel u; |
1331 | GtkListStore *family_model, *style_model, *size_model; |
1332 | GtkWidget *family_list, *style_list, *size_entry, *size_list; |
1333 | GtkWidget *filter_buttons[4]; |
1334 | GtkWidget *preview_area; |
1335 | GdkPixmap *preview_pixmap; |
1336 | int preview_width, preview_height; |
1337 | GdkColor preview_fg, preview_bg; |
1338 | int filter_flags; |
1339 | tree234 *fonts_by_realname, *fonts_by_selorder; |
1340 | fontinfo *selected; |
1341 | int selsize, intendedsize; |
1342 | int inhibit_response; /* inhibit callbacks when we change GUI controls */ |
1343 | } unifontsel_internal; |
1344 | |
1345 | /* |
1346 | * The structure held in the tree234s. All the string members are |
1347 | * part of the same allocated area, so don't need freeing |
1348 | * separately. |
1349 | */ |
1350 | struct fontinfo { |
1351 | char *realname; |
1352 | char *family, *charset, *style, *stylekey; |
1353 | int size, flags; |
1354 | /* |
1355 | * Fallback sorting key, to permit multiple identical entries |
1356 | * to exist in the selorder tree. |
1357 | */ |
1358 | int index; |
1359 | /* |
1360 | * Indices mapping fontinfo structures to indices in the list |
1361 | * boxes. sizeindex is irrelevant if the font is scalable |
1362 | * (size==0). |
1363 | */ |
1364 | int familyindex, styleindex, sizeindex; |
1365 | /* |
1366 | * The class of font. |
1367 | */ |
1368 | const struct unifont_vtable *fontclass; |
1369 | }; |
1370 | |
1371 | struct fontinfo_realname_find { |
1372 | const char *realname; |
1373 | int flags; |
1374 | }; |
1375 | |
1376 | static int strnullcasecmp(const char *a, const char *b) |
1377 | { |
1378 | int i; |
1379 | |
1380 | /* |
1381 | * If exactly one of the inputs is NULL, it compares before |
1382 | * the other one. |
1383 | */ |
1384 | if ((i = (!b) - (!a)) != 0) |
1385 | return i; |
1386 | |
1387 | /* |
1388 | * NULL compares equal. |
1389 | */ |
1390 | if (!a) |
1391 | return 0; |
1392 | |
1393 | /* |
1394 | * Otherwise, ordinary strcasecmp. |
1395 | */ |
1396 | return g_strcasecmp(a, b); |
1397 | } |
1398 | |
1399 | static int fontinfo_realname_compare(void *av, void *bv) |
1400 | { |
1401 | fontinfo *a = (fontinfo *)av; |
1402 | fontinfo *b = (fontinfo *)bv; |
1403 | int i; |
1404 | |
1405 | if ((i = strnullcasecmp(a->realname, b->realname)) != 0) |
1406 | return i; |
1407 | if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) |
1408 | return ((a->flags & FONTFLAG_SORT_MASK) < |
1409 | (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); |
1410 | return 0; |
1411 | } |
1412 | |
1413 | static int fontinfo_realname_find(void *av, void *bv) |
1414 | { |
1415 | struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av; |
1416 | fontinfo *b = (fontinfo *)bv; |
1417 | int i; |
1418 | |
1419 | if ((i = strnullcasecmp(a->realname, b->realname)) != 0) |
1420 | return i; |
1421 | if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) |
1422 | return ((a->flags & FONTFLAG_SORT_MASK) < |
1423 | (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); |
1424 | return 0; |
1425 | } |
1426 | |
1427 | static int fontinfo_selorder_compare(void *av, void *bv) |
1428 | { |
1429 | fontinfo *a = (fontinfo *)av; |
1430 | fontinfo *b = (fontinfo *)bv; |
1431 | int i; |
1432 | if ((i = strnullcasecmp(a->family, b->family)) != 0) |
1433 | return i; |
1434 | /* |
1435 | * Font class comes immediately after family, so that fonts |
1436 | * from different classes with the same family |
1437 | */ |
1438 | if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK)) |
1439 | return ((a->flags & FONTFLAG_SORT_MASK) < |
1440 | (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1); |
1441 | if ((i = strnullcasecmp(a->charset, b->charset)) != 0) |
1442 | return i; |
1443 | if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0) |
1444 | return i; |
1445 | if ((i = strnullcasecmp(a->style, b->style)) != 0) |
1446 | return i; |
1447 | if (a->size != b->size) |
1448 | return (a->size < b->size ? -1 : +1); |
1449 | if (a->index != b->index) |
1450 | return (a->index < b->index ? -1 : +1); |
1451 | return 0; |
1452 | } |
1453 | |
1454 | static void unifontsel_deselect(unifontsel_internal *fs) |
1455 | { |
1456 | fs->selected = NULL; |
1457 | gtk_list_store_clear(fs->style_model); |
1458 | gtk_list_store_clear(fs->size_model); |
1459 | gtk_widget_set_sensitive(fs->u.ok_button, FALSE); |
1460 | gtk_widget_set_sensitive(fs->size_entry, FALSE); |
1461 | } |
1462 | |
1463 | static void unifontsel_setup_familylist(unifontsel_internal *fs) |
1464 | { |
1465 | GtkTreeIter iter; |
1466 | int i, listindex, minpos = -1, maxpos = -1; |
1467 | char *currfamily = NULL; |
1468 | int currflags = -1; |
1469 | fontinfo *info; |
1470 | |
1471 | gtk_list_store_clear(fs->family_model); |
1472 | listindex = 0; |
1473 | |
1474 | /* |
1475 | * Search through the font tree for anything matching our |
1476 | * current filter criteria. When we find one, add its font |
1477 | * name to the list box. |
1478 | */ |
1479 | for (i = 0 ;; i++) { |
1480 | info = (fontinfo *)index234(fs->fonts_by_selorder, i); |
1481 | /* |
1482 | * info may be NULL if we've just run off the end of the |
1483 | * tree. We must still do a processing pass in that |
1484 | * situation, in case we had an unfinished font record in |
1485 | * progress. |
1486 | */ |
1487 | if (info && (info->flags &~ fs->filter_flags)) { |
1488 | info->familyindex = -1; |
1489 | continue; /* we're filtering out this font */ |
1490 | } |
1491 | if (!info || strnullcasecmp(currfamily, info->family) || |
1492 | currflags != (info->flags & FONTFLAG_SORT_MASK)) { |
1493 | /* |
1494 | * We've either finished a family, or started a new |
1495 | * one, or both. |
1496 | */ |
1497 | if (currfamily) { |
1498 | gtk_list_store_append(fs->family_model, &iter); |
1499 | gtk_list_store_set(fs->family_model, &iter, |
1500 | 0, currfamily, 1, minpos, 2, maxpos+1, -1); |
1501 | listindex++; |
1502 | } |
1503 | if (info) { |
1504 | minpos = i; |
1505 | currfamily = info->family; |
1506 | currflags = info->flags & FONTFLAG_SORT_MASK; |
1507 | } |
1508 | } |
1509 | if (!info) |
1510 | break; /* now we're done */ |
1511 | info->familyindex = listindex; |
1512 | maxpos = i; |
1513 | } |
1514 | |
1515 | /* |
1516 | * If we've just filtered out the previously selected font, |
1517 | * deselect it thoroughly. |
1518 | */ |
1519 | if (fs->selected && fs->selected->familyindex < 0) |
1520 | unifontsel_deselect(fs); |
1521 | } |
1522 | |
1523 | static void unifontsel_setup_stylelist(unifontsel_internal *fs, |
1524 | int start, int end) |
1525 | { |
1526 | GtkTreeIter iter; |
1527 | int i, listindex, minpos = -1, maxpos = -1, started = FALSE; |
1528 | char *currcs = NULL, *currstyle = NULL; |
1529 | fontinfo *info; |
1530 | |
1531 | gtk_list_store_clear(fs->style_model); |
1532 | listindex = 0; |
1533 | started = FALSE; |
1534 | |
1535 | /* |
1536 | * Search through the font tree for anything matching our |
1537 | * current filter criteria. When we find one, add its charset |
1538 | * and/or style name to the list box. |
1539 | */ |
1540 | for (i = start; i <= end; i++) { |
1541 | if (i == end) |
1542 | info = NULL; |
1543 | else |
1544 | info = (fontinfo *)index234(fs->fonts_by_selorder, i); |
1545 | /* |
1546 | * info may be NULL if we've just run off the end of the |
1547 | * relevant data. We must still do a processing pass in |
1548 | * that situation, in case we had an unfinished font |
1549 | * record in progress. |
1550 | */ |
1551 | if (info && (info->flags &~ fs->filter_flags)) { |
1552 | info->styleindex = -1; |
1553 | continue; /* we're filtering out this font */ |
1554 | } |
1555 | if (!info || !started || strnullcasecmp(currcs, info->charset) || |
1556 | strnullcasecmp(currstyle, info->style)) { |
1557 | /* |
1558 | * We've either finished a style/charset, or started a |
1559 | * new one, or both. |
1560 | */ |
1561 | started = TRUE; |
1562 | if (currstyle) { |
1563 | gtk_list_store_append(fs->style_model, &iter); |
1564 | gtk_list_store_set(fs->style_model, &iter, |
1565 | 0, currstyle, 1, minpos, 2, maxpos+1, |
1566 | 3, TRUE, -1); |
1567 | listindex++; |
1568 | } |
1569 | if (info) { |
1570 | minpos = i; |
1571 | if (info->charset && strnullcasecmp(currcs, info->charset)) { |
1572 | gtk_list_store_append(fs->style_model, &iter); |
1573 | gtk_list_store_set(fs->style_model, &iter, |
1574 | 0, info->charset, 1, -1, 2, -1, |
1575 | 3, FALSE, -1); |
1576 | listindex++; |
1577 | } |
1578 | currcs = info->charset; |
1579 | currstyle = info->style; |
1580 | } |
1581 | } |
1582 | if (!info) |
1583 | break; /* now we're done */ |
1584 | info->styleindex = listindex; |
1585 | maxpos = i; |
1586 | } |
1587 | } |
1588 | |
1589 | static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 }; |
1590 | |
1591 | static void unifontsel_setup_sizelist(unifontsel_internal *fs, |
1592 | int start, int end) |
1593 | { |
1594 | GtkTreeIter iter; |
1595 | int i, listindex; |
1596 | char sizetext[40]; |
1597 | fontinfo *info; |
1598 | |
1599 | gtk_list_store_clear(fs->size_model); |
1600 | listindex = 0; |
1601 | |
1602 | /* |
1603 | * Search through the font tree for anything matching our |
1604 | * current filter criteria. When we find one, add its font |
1605 | * name to the list box. |
1606 | */ |
1607 | for (i = start; i < end; i++) { |
1608 | info = (fontinfo *)index234(fs->fonts_by_selorder, i); |
1609 | if (info->flags &~ fs->filter_flags) { |
1610 | info->sizeindex = -1; |
1611 | continue; /* we're filtering out this font */ |
1612 | } |
1613 | if (info->size) { |
1614 | sprintf(sizetext, "%d", info->size); |
1615 | info->sizeindex = listindex; |
1616 | gtk_list_store_append(fs->size_model, &iter); |
1617 | gtk_list_store_set(fs->size_model, &iter, |
1618 | 0, sizetext, 1, i, 2, info->size, -1); |
1619 | listindex++; |
1620 | } else { |
1621 | int j; |
1622 | |
1623 | assert(i == start); |
1624 | assert(i+1 == end); |
1625 | |
1626 | for (j = 0; j < lenof(unifontsel_default_sizes); j++) { |
1627 | sprintf(sizetext, "%d", unifontsel_default_sizes[j]); |
1628 | gtk_list_store_append(fs->size_model, &iter); |
1629 | gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i, |
1630 | 2, unifontsel_default_sizes[j], -1); |
1631 | listindex++; |
1632 | } |
1633 | } |
1634 | } |
1635 | } |
1636 | |
1637 | static void unifontsel_set_filter_buttons(unifontsel_internal *fs) |
1638 | { |
1639 | int i; |
1640 | |
1641 | for (i = 0; i < lenof(fs->filter_buttons); i++) { |
1642 | int flagbit = GPOINTER_TO_INT(gtk_object_get_data |
1643 | (GTK_OBJECT(fs->filter_buttons[i]), |
1644 | "user-data")); |
1645 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]), |
1646 | !!(fs->filter_flags & flagbit)); |
1647 | } |
1648 | } |
1649 | |
1650 | static void unifontsel_draw_preview_text(unifontsel_internal *fs) |
1651 | { |
1652 | unifont *font; |
1653 | char *sizename = NULL; |
1654 | fontinfo *info = fs->selected; |
1655 | |
1656 | if (info) { |
1657 | sizename = info->fontclass->scale_fontname |
1658 | (GTK_WIDGET(fs->u.window), info->realname, fs->selsize); |
1659 | font = info->fontclass->create(GTK_WIDGET(fs->u.window), |
1660 | sizename ? sizename : info->realname, |
1661 | FALSE, FALSE, 0, 0); |
1662 | } else |
1663 | font = NULL; |
1664 | |
1665 | if (fs->preview_pixmap) { |
1666 | GdkGC *gc = gdk_gc_new(fs->preview_pixmap); |
1667 | gdk_gc_set_foreground(gc, &fs->preview_bg); |
1668 | gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0, |
1669 | fs->preview_width, fs->preview_height); |
1670 | gdk_gc_set_foreground(gc, &fs->preview_fg); |
1671 | if (font) { |
1672 | /* |
1673 | * The pangram used here is rather carefully |
1674 | * constructed: it contains a sequence of very narrow |
1675 | * letters (`jil') and a pair of adjacent very wide |
1676 | * letters (`wm'). |
1677 | * |
1678 | * If the user selects a proportional font, it will be |
1679 | * coerced into fixed-width character cells when used |
1680 | * in the actual terminal window. We therefore display |
1681 | * it the same way in the preview pane, so as to show |
1682 | * it the way it will actually be displayed - and we |
1683 | * deliberately pick a pangram which will show the |
1684 | * resulting miskerning at its worst. |
1685 | * |
1686 | * We aren't trying to sell people these fonts; we're |
1687 | * trying to let them make an informed choice. Better |
1688 | * that they find out the problems with using |
1689 | * proportional fonts in terminal windows here than |
1690 | * that they go to the effort of selecting their font |
1691 | * and _then_ realise it was a mistake. |
1692 | */ |
1693 | info->fontclass->draw_text(fs->preview_pixmap, gc, font, |
1694 | 0, font->ascent, |
1695 | "bankrupt jilted showmen quiz convex fogey", |
1696 | 41, FALSE, FALSE, font->width); |
1697 | info->fontclass->draw_text(fs->preview_pixmap, gc, font, |
1698 | 0, font->ascent + font->height, |
1699 | "BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY", |
1700 | 41, FALSE, FALSE, font->width); |
1701 | /* |
1702 | * The ordering of punctuation here is also selected |
1703 | * with some specific aims in mind. I put ` and ' |
1704 | * together because some software (and people) still |
1705 | * use them as matched quotes no matter what Unicode |
1706 | * might say on the matter, so people can quickly |
1707 | * check whether they look silly in a candidate font. |
1708 | * The sequence #_@ is there to let people judge the |
1709 | * suitability of the underscore as an effectively |
1710 | * alphabetic character (since that's how it's often |
1711 | * used in practice, at least by programmers). |
1712 | */ |
1713 | info->fontclass->draw_text(fs->preview_pixmap, gc, font, |
1714 | 0, font->ascent + font->height * 2, |
1715 | "0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", |
1716 | 42, FALSE, FALSE, font->width); |
1717 | } |
1718 | gdk_gc_unref(gc); |
1719 | gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE); |
1720 | } |
1721 | if (font) |
1722 | info->fontclass->destroy(font); |
1723 | |
1724 | sfree(sizename); |
1725 | } |
1726 | |
1727 | static void unifontsel_select_font(unifontsel_internal *fs, |
1728 | fontinfo *info, int size, int leftlist, |
1729 | int size_is_explicit) |
1730 | { |
1731 | int index; |
1732 | int minval, maxval; |
1733 | GtkTreePath *treepath; |
1734 | GtkTreeIter iter; |
1735 | |
1736 | fs->inhibit_response = TRUE; |
1737 | |
1738 | fs->selected = info; |
1739 | fs->selsize = size; |
1740 | if (size_is_explicit) |
1741 | fs->intendedsize = size; |
1742 | |
1743 | gtk_widget_set_sensitive(fs->u.ok_button, TRUE); |
1744 | |
1745 | /* |
1746 | * Find the index of this fontinfo in the selorder list. |
1747 | */ |
1748 | index = -1; |
1749 | findpos234(fs->fonts_by_selorder, info, NULL, &index); |
1750 | assert(index >= 0); |
1751 | |
1752 | /* |
1753 | * Adjust the font selector flags and redo the font family |
1754 | * list box, if necessary. |
1755 | */ |
1756 | if (leftlist <= 0 && |
1757 | (fs->filter_flags | info->flags) != fs->filter_flags) { |
1758 | fs->filter_flags |= info->flags; |
1759 | unifontsel_set_filter_buttons(fs); |
1760 | unifontsel_setup_familylist(fs); |
1761 | } |
1762 | |
1763 | /* |
1764 | * Find the appropriate family name and select it in the list. |
1765 | */ |
1766 | assert(info->familyindex >= 0); |
1767 | treepath = gtk_tree_path_new_from_indices(info->familyindex, -1); |
1768 | gtk_tree_selection_select_path |
1769 | (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)), |
1770 | treepath); |
1771 | gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list), |
1772 | treepath, NULL, FALSE, 0.0, 0.0); |
1773 | gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath); |
1774 | gtk_tree_path_free(treepath); |
1775 | |
1776 | /* |
1777 | * Now set up the font style list. |
1778 | */ |
1779 | gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, |
1780 | 1, &minval, 2, &maxval, -1); |
1781 | if (leftlist <= 1) |
1782 | unifontsel_setup_stylelist(fs, minval, maxval); |
1783 | |
1784 | /* |
1785 | * Find the appropriate style name and select it in the list. |
1786 | */ |
1787 | if (info->style) { |
1788 | assert(info->styleindex >= 0); |
1789 | treepath = gtk_tree_path_new_from_indices(info->styleindex, -1); |
1790 | gtk_tree_selection_select_path |
1791 | (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)), |
1792 | treepath); |
1793 | gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list), |
1794 | treepath, NULL, FALSE, 0.0, 0.0); |
1795 | gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model), |
1796 | &iter, treepath); |
1797 | gtk_tree_path_free(treepath); |
1798 | |
1799 | /* |
1800 | * And set up the size list. |
1801 | */ |
1802 | gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter, |
1803 | 1, &minval, 2, &maxval, -1); |
1804 | if (leftlist <= 2) |
1805 | unifontsel_setup_sizelist(fs, minval, maxval); |
1806 | |
1807 | /* |
1808 | * Find the appropriate size, and select it in the list. |
1809 | */ |
1810 | if (info->size) { |
1811 | assert(info->sizeindex >= 0); |
1812 | treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1); |
1813 | gtk_tree_selection_select_path |
1814 | (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)), |
1815 | treepath); |
1816 | gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), |
1817 | treepath, NULL, FALSE, 0.0, 0.0); |
1818 | gtk_tree_path_free(treepath); |
1819 | size = info->size; |
1820 | } else { |
1821 | int j; |
1822 | for (j = 0; j < lenof(unifontsel_default_sizes); j++) |
1823 | if (unifontsel_default_sizes[j] == size) { |
1824 | treepath = gtk_tree_path_new_from_indices(j, -1); |
1825 | gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list), |
1826 | treepath, NULL, FALSE); |
1827 | gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), |
1828 | treepath, NULL, FALSE, 0.0, |
1829 | 0.0); |
1830 | gtk_tree_path_free(treepath); |
1831 | } |
1832 | } |
1833 | |
1834 | /* |
1835 | * And set up the font size text entry box. |
1836 | */ |
1837 | { |
1838 | char sizetext[40]; |
1839 | sprintf(sizetext, "%d", size); |
1840 | gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext); |
1841 | } |
1842 | } else { |
1843 | if (leftlist <= 2) |
1844 | unifontsel_setup_sizelist(fs, 0, 0); |
1845 | gtk_entry_set_text(GTK_ENTRY(fs->size_entry), ""); |
1846 | } |
1847 | |
1848 | /* |
1849 | * Grey out the font size edit box if we're not using a |
1850 | * scalable font. |
1851 | */ |
1852 | gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0); |
1853 | gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0); |
1854 | |
1855 | unifontsel_draw_preview_text(fs); |
1856 | |
1857 | fs->inhibit_response = FALSE; |
1858 | } |
1859 | |
1860 | static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data) |
1861 | { |
1862 | unifontsel_internal *fs = (unifontsel_internal *)data; |
1863 | int newstate = gtk_toggle_button_get_active(tb); |
1864 | int newflags; |
1865 | int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb), |
1866 | "user-data")); |
1867 | |
1868 | if (newstate) |
1869 | newflags = fs->filter_flags | flagbit; |
1870 | else |
1871 | newflags = fs->filter_flags & ~flagbit; |
1872 | |
1873 | if (fs->filter_flags != newflags) { |
1874 | fs->filter_flags = newflags; |
1875 | unifontsel_setup_familylist(fs); |
1876 | } |
1877 | } |
1878 | |
1879 | static void unifontsel_add_entry(void *ctx, const char *realfontname, |
1880 | const char *family, const char *charset, |
1881 | const char *style, const char *stylekey, |
1882 | int size, int flags, |
1883 | const struct unifont_vtable *fontclass) |
1884 | { |
1885 | unifontsel_internal *fs = (unifontsel_internal *)ctx; |
1886 | fontinfo *info; |
1887 | int totalsize; |
1888 | char *p; |
1889 | |
1890 | totalsize = sizeof(fontinfo) + strlen(realfontname) + |
1891 | (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) + |
1892 | (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10; |
1893 | info = (fontinfo *)smalloc(totalsize); |
1894 | info->fontclass = fontclass; |
1895 | p = (char *)info + sizeof(fontinfo); |
1896 | info->realname = p; |
1897 | strcpy(p, realfontname); |
1898 | p += 1+strlen(p); |
1899 | if (family) { |
1900 | info->family = p; |
1901 | strcpy(p, family); |
1902 | p += 1+strlen(p); |
1903 | } else |
1904 | info->family = NULL; |
1905 | if (charset) { |
1906 | info->charset = p; |
1907 | strcpy(p, charset); |
1908 | p += 1+strlen(p); |
1909 | } else |
1910 | info->charset = NULL; |
1911 | if (style) { |
1912 | info->style = p; |
1913 | strcpy(p, style); |
1914 | p += 1+strlen(p); |
1915 | } else |
1916 | info->style = NULL; |
1917 | if (stylekey) { |
1918 | info->stylekey = p; |
1919 | strcpy(p, stylekey); |
1920 | p += 1+strlen(p); |
1921 | } else |
1922 | info->stylekey = NULL; |
1923 | assert(p - (char *)info <= totalsize); |
1924 | info->size = size; |
1925 | info->flags = flags; |
1926 | info->index = count234(fs->fonts_by_selorder); |
1927 | |
1928 | /* |
1929 | * It's just conceivable that a misbehaving font enumerator |
1930 | * might tell us about the same font real name more than once, |
1931 | * in which case we should silently drop the new one. |
1932 | */ |
1933 | if (add234(fs->fonts_by_realname, info) != info) { |
1934 | sfree(info); |
1935 | return; |
1936 | } |
1937 | /* |
1938 | * However, we should never get a duplicate key in the |
1939 | * selorder tree, because the index field carefully |
1940 | * disambiguates otherwise identical records. |
1941 | */ |
1942 | add234(fs->fonts_by_selorder, info); |
1943 | } |
1944 | |
1945 | static fontinfo *update_for_intended_size(unifontsel_internal *fs, |
1946 | fontinfo *info) |
1947 | { |
1948 | fontinfo info2, *below, *above; |
1949 | int pos; |
1950 | |
1951 | /* |
1952 | * Copy the info structure. This doesn't copy its dynamic |
1953 | * string fields, but that's unimportant because all we're |
1954 | * going to do is to adjust the size field and use it in one |
1955 | * tree search. |
1956 | */ |
1957 | info2 = *info; |
1958 | info2.size = fs->intendedsize; |
1959 | |
1960 | /* |
1961 | * Search in the tree to find the fontinfo structure which |
1962 | * best approximates the size the user last requested. |
1963 | */ |
1964 | below = findrelpos234(fs->fonts_by_selorder, &info2, NULL, |
1965 | REL234_LE, &pos); |
1966 | above = index234(fs->fonts_by_selorder, pos+1); |
1967 | |
1968 | /* |
1969 | * See if we've found it exactly, which is an easy special |
1970 | * case. If we have, it'll be in `below' and not `above', |
1971 | * because we did a REL234_LE rather than REL234_LT search. |
1972 | */ |
1973 | if (!fontinfo_selorder_compare(&info2, below)) |
1974 | return below; |
1975 | |
1976 | /* |
1977 | * Now we've either found two suitable fonts, one smaller and |
1978 | * one larger, or we're at one or other extreme end of the |
1979 | * scale. Find out which, by NULLing out either of below and |
1980 | * above if it differs from this one in any respect but size |
1981 | * (and the disambiguating index field). Bear in mind, also, |
1982 | * that either one might _already_ be NULL if we're at the |
1983 | * extreme ends of the font list. |
1984 | */ |
1985 | if (below) { |
1986 | info2.size = below->size; |
1987 | info2.index = below->index; |
1988 | if (fontinfo_selorder_compare(&info2, below)) |
1989 | below = NULL; |
1990 | } |
1991 | if (above) { |
1992 | info2.size = above->size; |
1993 | info2.index = above->index; |
1994 | if (fontinfo_selorder_compare(&info2, above)) |
1995 | above = NULL; |
1996 | } |
1997 | |
1998 | /* |
1999 | * Now return whichever of above and below is non-NULL, if |
2000 | * that's unambiguous. |
2001 | */ |
2002 | if (!above) |
2003 | return below; |
2004 | if (!below) |
2005 | return above; |
2006 | |
2007 | /* |
2008 | * And now we really do have to make a choice about whether to |
2009 | * round up or down. We'll do it by rounding to nearest, |
2010 | * breaking ties by rounding up. |
2011 | */ |
2012 | if (above->size - fs->intendedsize <= fs->intendedsize - below->size) |
2013 | return above; |
2014 | else |
2015 | return below; |
2016 | } |
2017 | |
2018 | static void family_changed(GtkTreeSelection *treeselection, gpointer data) |
2019 | { |
2020 | unifontsel_internal *fs = (unifontsel_internal *)data; |
2021 | GtkTreeModel *treemodel; |
2022 | GtkTreeIter treeiter; |
2023 | int minval; |
2024 | fontinfo *info; |
2025 | |
2026 | if (fs->inhibit_response) /* we made this change ourselves */ |
2027 | return; |
2028 | |
2029 | if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) |
2030 | return; |
2031 | |
2032 | gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); |
2033 | info = (fontinfo *)index234(fs->fonts_by_selorder, minval); |
2034 | info = update_for_intended_size(fs, info); |
2035 | if (!info) |
2036 | return; /* _shouldn't_ happen unless font list is completely funted */ |
2037 | if (!info->size) |
2038 | fs->selsize = fs->intendedsize; /* font is scalable */ |
2039 | unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, |
2040 | 1, FALSE); |
2041 | } |
2042 | |
2043 | static void style_changed(GtkTreeSelection *treeselection, gpointer data) |
2044 | { |
2045 | unifontsel_internal *fs = (unifontsel_internal *)data; |
2046 | GtkTreeModel *treemodel; |
2047 | GtkTreeIter treeiter; |
2048 | int minval; |
2049 | fontinfo *info; |
2050 | |
2051 | if (fs->inhibit_response) /* we made this change ourselves */ |
2052 | return; |
2053 | |
2054 | if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) |
2055 | return; |
2056 | |
2057 | gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1); |
2058 | if (minval < 0) |
2059 | return; /* somehow a charset heading got clicked */ |
2060 | info = (fontinfo *)index234(fs->fonts_by_selorder, minval); |
2061 | info = update_for_intended_size(fs, info); |
2062 | if (!info) |
2063 | return; /* _shouldn't_ happen unless font list is completely funted */ |
2064 | if (!info->size) |
2065 | fs->selsize = fs->intendedsize; /* font is scalable */ |
2066 | unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, |
2067 | 2, FALSE); |
2068 | } |
2069 | |
2070 | static void size_changed(GtkTreeSelection *treeselection, gpointer data) |
2071 | { |
2072 | unifontsel_internal *fs = (unifontsel_internal *)data; |
2073 | GtkTreeModel *treemodel; |
2074 | GtkTreeIter treeiter; |
2075 | int minval, size; |
2076 | fontinfo *info; |
2077 | |
2078 | if (fs->inhibit_response) /* we made this change ourselves */ |
2079 | return; |
2080 | |
2081 | if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter)) |
2082 | return; |
2083 | |
2084 | gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1); |
2085 | info = (fontinfo *)index234(fs->fonts_by_selorder, minval); |
2086 | unifontsel_select_font(fs, info, info->size ? info->size : size, 3, TRUE); |
2087 | } |
2088 | |
2089 | static void size_entry_changed(GtkEditable *ed, gpointer data) |
2090 | { |
2091 | unifontsel_internal *fs = (unifontsel_internal *)data; |
2092 | const char *text; |
2093 | int size; |
2094 | |
2095 | if (fs->inhibit_response) /* we made this change ourselves */ |
2096 | return; |
2097 | |
2098 | text = gtk_entry_get_text(GTK_ENTRY(ed)); |
2099 | size = atoi(text); |
2100 | |
2101 | if (size > 0) { |
2102 | assert(fs->selected->size == 0); |
2103 | unifontsel_select_font(fs, fs->selected, size, 3, TRUE); |
2104 | } |
2105 | } |
2106 | |
2107 | static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, |
2108 | GtkTreeViewColumn *column, gpointer data) |
2109 | { |
2110 | unifontsel_internal *fs = (unifontsel_internal *)data; |
2111 | GtkTreeIter iter; |
2112 | int minval, newsize; |
2113 | fontinfo *info, *newinfo; |
2114 | char *newname; |
2115 | |
2116 | if (fs->inhibit_response) /* we made this change ourselves */ |
2117 | return; |
2118 | |
2119 | gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path); |
2120 | gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1); |
2121 | info = (fontinfo *)index234(fs->fonts_by_selorder, minval); |
2122 | if (info) { |
2123 | int flags; |
2124 | struct fontinfo_realname_find f; |
2125 | |
2126 | newname = info->fontclass->canonify_fontname |
2127 | (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, TRUE); |
2128 | |
2129 | f.realname = newname; |
2130 | f.flags = flags; |
2131 | newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); |
2132 | |
2133 | sfree(newname); |
2134 | if (!newinfo) |
2135 | return; /* font name not in our index */ |
2136 | if (newinfo == info) |
2137 | return; /* didn't change under canonification => not an alias */ |
2138 | unifontsel_select_font(fs, newinfo, |
2139 | newinfo->size ? newinfo->size : newsize, |
2140 | 1, TRUE); |
2141 | } |
2142 | } |
2143 | |
2144 | static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, |
2145 | gpointer data) |
2146 | { |
2147 | unifontsel_internal *fs = (unifontsel_internal *)data; |
2148 | |
2149 | if (fs->preview_pixmap) { |
2150 | gdk_draw_pixmap(widget->window, |
2151 | widget->style->fg_gc[GTK_WIDGET_STATE(widget)], |
2152 | fs->preview_pixmap, |
2153 | event->area.x, event->area.y, |
2154 | event->area.x, event->area.y, |
2155 | event->area.width, event->area.height); |
2156 | } |
2157 | return TRUE; |
2158 | } |
2159 | |
2160 | static gint unifontsel_configure_area(GtkWidget *widget, |
2161 | GdkEventConfigure *event, gpointer data) |
2162 | { |
2163 | unifontsel_internal *fs = (unifontsel_internal *)data; |
2164 | int ox, oy, nx, ny, x, y; |
2165 | |
2166 | /* |
2167 | * Enlarge the pixmap, but never shrink it. |
2168 | */ |
2169 | ox = fs->preview_width; |
2170 | oy = fs->preview_height; |
2171 | x = event->width; |
2172 | y = event->height; |
2173 | if (x > ox || y > oy) { |
2174 | if (fs->preview_pixmap) |
2175 | gdk_pixmap_unref(fs->preview_pixmap); |
2176 | |
2177 | nx = (x > ox ? x : ox); |
2178 | ny = (y > oy ? y : oy); |
2179 | fs->preview_pixmap = gdk_pixmap_new(widget->window, nx, ny, -1); |
2180 | fs->preview_width = nx; |
2181 | fs->preview_height = ny; |
2182 | |
2183 | unifontsel_draw_preview_text(fs); |
2184 | } |
2185 | |
2186 | gdk_window_invalidate_rect(widget->window, NULL, FALSE); |
2187 | |
2188 | return TRUE; |
2189 | } |
2190 | |
2191 | unifontsel *unifontsel_new(const char *wintitle) |
2192 | { |
2193 | unifontsel_internal *fs = snew(unifontsel_internal); |
2194 | GtkWidget *table, *label, *w, *ww, *scroll; |
2195 | GtkListStore *model; |
2196 | GtkTreeViewColumn *column; |
2197 | int lists_height, preview_height, font_width, style_width, size_width; |
2198 | int i; |
2199 | |
2200 | fs->inhibit_response = FALSE; |
2201 | fs->selected = NULL; |
2202 | |
2203 | { |
2204 | /* |
2205 | * Invent some magic size constants. |
2206 | */ |
2207 | GtkRequisition req; |
2208 | label = gtk_label_new("Quite Long Font Name (Foundry)"); |
2209 | gtk_widget_size_request(label, &req); |
2210 | font_width = req.width; |
2211 | lists_height = 14 * req.height; |
2212 | preview_height = 5 * req.height; |
2213 | gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed"); |
2214 | gtk_widget_size_request(label, &req); |
2215 | style_width = req.width; |
2216 | gtk_label_set_text(GTK_LABEL(label), "48000"); |
2217 | gtk_widget_size_request(label, &req); |
2218 | size_width = req.width; |
2219 | #if GTK_CHECK_VERSION(2,10,0) |
2220 | g_object_ref_sink(label); |
2221 | g_object_unref(label); |
2222 | #else |
2223 | gtk_object_sink(GTK_OBJECT(label)); |
2224 | #endif |
2225 | } |
2226 | |
2227 | /* |
2228 | * Create the dialog box and initialise the user-visible |
2229 | * fields in the returned structure. |
2230 | */ |
2231 | fs->u.user_data = NULL; |
2232 | fs->u.window = GTK_WINDOW(gtk_dialog_new()); |
2233 | gtk_window_set_title(fs->u.window, wintitle); |
2234 | fs->u.cancel_button = gtk_dialog_add_button |
2235 | (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); |
2236 | fs->u.ok_button = gtk_dialog_add_button |
2237 | (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK); |
2238 | gtk_widget_grab_default(fs->u.ok_button); |
2239 | |
2240 | /* |
2241 | * Now set up the internal fields, including in particular all |
2242 | * the controls that actually allow the user to select fonts. |
2243 | */ |
2244 | table = gtk_table_new(8, 3, FALSE); |
2245 | gtk_widget_show(table); |
2246 | gtk_table_set_col_spacings(GTK_TABLE(table), 8); |
2247 | #if GTK_CHECK_VERSION(2,4,0) |
2248 | /* GtkAlignment seems to be the simplest way to put padding round things */ |
2249 | w = gtk_alignment_new(0, 0, 1, 1); |
2250 | gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); |
2251 | gtk_container_add(GTK_CONTAINER(w), table); |
2252 | gtk_widget_show(w); |
2253 | #else |
2254 | w = table; |
2255 | #endif |
2256 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox), |
2257 | w, TRUE, TRUE, 0); |
2258 | |
2259 | label = gtk_label_new_with_mnemonic("_Font:"); |
2260 | gtk_widget_show(label); |
2261 | gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); |
2262 | gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); |
2263 | |
2264 | /* |
2265 | * The Font list box displays only a string, but additionally |
2266 | * stores two integers which give the limits within the |
2267 | * tree234 of the font entries covered by this list entry. |
2268 | */ |
2269 | model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); |
2270 | w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); |
2271 | gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); |
2272 | gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); |
2273 | gtk_widget_show(w); |
2274 | column = gtk_tree_view_column_new_with_attributes |
2275 | ("Font", gtk_cell_renderer_text_new(), |
2276 | "text", 0, (char *)NULL); |
2277 | gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); |
2278 | gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); |
2279 | g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), |
2280 | "changed", G_CALLBACK(family_changed), fs); |
2281 | g_signal_connect(G_OBJECT(w), "row-activated", |
2282 | G_CALLBACK(alias_resolve), fs); |
2283 | |
2284 | scroll = gtk_scrolled_window_new(NULL, NULL); |
2285 | gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), |
2286 | GTK_SHADOW_IN); |
2287 | gtk_container_add(GTK_CONTAINER(scroll), w); |
2288 | gtk_widget_show(scroll); |
2289 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), |
2290 | GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); |
2291 | gtk_widget_set_size_request(scroll, font_width, lists_height); |
2292 | gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, |
2293 | GTK_EXPAND | GTK_FILL, 0, 0); |
2294 | fs->family_model = model; |
2295 | fs->family_list = w; |
2296 | |
2297 | label = gtk_label_new_with_mnemonic("_Style:"); |
2298 | gtk_widget_show(label); |
2299 | gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); |
2300 | gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); |
2301 | |
2302 | /* |
2303 | * The Style list box can contain insensitive elements |
2304 | * (character set headings for server-side fonts), so we add |
2305 | * an extra column to the list store to hold that information. |
2306 | */ |
2307 | model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, |
2308 | G_TYPE_BOOLEAN); |
2309 | w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); |
2310 | gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); |
2311 | gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); |
2312 | gtk_widget_show(w); |
2313 | column = gtk_tree_view_column_new_with_attributes |
2314 | ("Style", gtk_cell_renderer_text_new(), |
2315 | "text", 0, "sensitive", 3, (char *)NULL); |
2316 | gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); |
2317 | gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); |
2318 | g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), |
2319 | "changed", G_CALLBACK(style_changed), fs); |
2320 | |
2321 | scroll = gtk_scrolled_window_new(NULL, NULL); |
2322 | gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), |
2323 | GTK_SHADOW_IN); |
2324 | gtk_container_add(GTK_CONTAINER(scroll), w); |
2325 | gtk_widget_show(scroll); |
2326 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), |
2327 | GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); |
2328 | gtk_widget_set_size_request(scroll, style_width, lists_height); |
2329 | gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, |
2330 | GTK_EXPAND | GTK_FILL, 0, 0); |
2331 | fs->style_model = model; |
2332 | fs->style_list = w; |
2333 | |
2334 | label = gtk_label_new_with_mnemonic("Si_ze:"); |
2335 | gtk_widget_show(label); |
2336 | gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); |
2337 | gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); |
2338 | |
2339 | /* |
2340 | * The Size label attaches primarily to a text input box so |
2341 | * that the user can select a size of their choice. The list |
2342 | * of available sizes is secondary. |
2343 | */ |
2344 | fs->size_entry = w = gtk_entry_new(); |
2345 | gtk_label_set_mnemonic_widget(GTK_LABEL(label), w); |
2346 | gtk_widget_set_size_request(w, size_width, -1); |
2347 | gtk_widget_show(w); |
2348 | gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); |
2349 | g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed), |
2350 | fs); |
2351 | |
2352 | model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); |
2353 | w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); |
2354 | gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE); |
2355 | gtk_widget_show(w); |
2356 | column = gtk_tree_view_column_new_with_attributes |
2357 | ("Size", gtk_cell_renderer_text_new(), |
2358 | "text", 0, (char *)NULL); |
2359 | gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); |
2360 | gtk_tree_view_append_column(GTK_TREE_VIEW(w), column); |
2361 | g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))), |
2362 | "changed", G_CALLBACK(size_changed), fs); |
2363 | |
2364 | scroll = gtk_scrolled_window_new(NULL, NULL); |
2365 | gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), |
2366 | GTK_SHADOW_IN); |
2367 | gtk_container_add(GTK_CONTAINER(scroll), w); |
2368 | gtk_widget_show(scroll); |
2369 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), |
2370 | GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); |
2371 | gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL, |
2372 | GTK_EXPAND | GTK_FILL, 0, 0); |
2373 | fs->size_model = model; |
2374 | fs->size_list = w; |
2375 | |
2376 | /* |
2377 | * Preview widget. |
2378 | */ |
2379 | fs->preview_area = gtk_drawing_area_new(); |
2380 | fs->preview_pixmap = NULL; |
2381 | fs->preview_width = 0; |
2382 | fs->preview_height = 0; |
2383 | fs->preview_fg.pixel = fs->preview_bg.pixel = 0; |
2384 | fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000; |
2385 | fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF; |
2386 | gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg, |
2387 | FALSE, FALSE); |
2388 | gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg, |
2389 | FALSE, FALSE); |
2390 | gtk_signal_connect(GTK_OBJECT(fs->preview_area), "expose_event", |
2391 | GTK_SIGNAL_FUNC(unifontsel_expose_area), fs); |
2392 | gtk_signal_connect(GTK_OBJECT(fs->preview_area), "configure_event", |
2393 | GTK_SIGNAL_FUNC(unifontsel_configure_area), fs); |
2394 | gtk_widget_set_size_request(fs->preview_area, 1, preview_height); |
2395 | gtk_widget_show(fs->preview_area); |
2396 | ww = fs->preview_area; |
2397 | w = gtk_frame_new(NULL); |
2398 | gtk_container_add(GTK_CONTAINER(w), ww); |
2399 | gtk_widget_show(w); |
2400 | #if GTK_CHECK_VERSION(2,4,0) |
2401 | ww = w; |
2402 | /* GtkAlignment seems to be the simplest way to put padding round things */ |
2403 | w = gtk_alignment_new(0, 0, 1, 1); |
2404 | gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8); |
2405 | gtk_container_add(GTK_CONTAINER(w), ww); |
2406 | gtk_widget_show(w); |
2407 | #endif |
2408 | ww = w; |
2409 | w = gtk_frame_new("Preview of font"); |
2410 | gtk_container_add(GTK_CONTAINER(w), ww); |
2411 | gtk_widget_show(w); |
2412 | gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4, |
2413 | GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8); |
2414 | |
2415 | i = 0; |
2416 | w = gtk_check_button_new_with_label("Show client-side fonts"); |
2417 | gtk_object_set_data(GTK_OBJECT(w), "user-data", |
2418 | GINT_TO_POINTER(FONTFLAG_CLIENTSIDE)); |
2419 | gtk_signal_connect(GTK_OBJECT(w), "toggled", |
2420 | GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); |
2421 | gtk_widget_show(w); |
2422 | fs->filter_buttons[i++] = w; |
2423 | gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0); |
2424 | w = gtk_check_button_new_with_label("Show server-side fonts"); |
2425 | gtk_object_set_data(GTK_OBJECT(w), "user-data", |
2426 | GINT_TO_POINTER(FONTFLAG_SERVERSIDE)); |
2427 | gtk_signal_connect(GTK_OBJECT(w), "toggled", |
2428 | GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); |
2429 | gtk_widget_show(w); |
2430 | fs->filter_buttons[i++] = w; |
2431 | gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0); |
2432 | w = gtk_check_button_new_with_label("Show server-side font aliases"); |
2433 | gtk_object_set_data(GTK_OBJECT(w), "user-data", |
2434 | GINT_TO_POINTER(FONTFLAG_SERVERALIAS)); |
2435 | gtk_signal_connect(GTK_OBJECT(w), "toggled", |
2436 | GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); |
2437 | gtk_widget_show(w); |
2438 | fs->filter_buttons[i++] = w; |
2439 | gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0); |
2440 | w = gtk_check_button_new_with_label("Show non-monospaced fonts"); |
2441 | gtk_object_set_data(GTK_OBJECT(w), "user-data", |
2442 | GINT_TO_POINTER(FONTFLAG_NONMONOSPACED)); |
2443 | gtk_signal_connect(GTK_OBJECT(w), "toggled", |
2444 | GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs); |
2445 | gtk_widget_show(w); |
2446 | fs->filter_buttons[i++] = w; |
2447 | gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0); |
2448 | |
2449 | assert(i == lenof(fs->filter_buttons)); |
2450 | fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE | |
2451 | FONTFLAG_SERVERALIAS; |
2452 | unifontsel_set_filter_buttons(fs); |
2453 | |
2454 | /* |
2455 | * Go and find all the font names, and set up our master font |
2456 | * list. |
2457 | */ |
2458 | fs->fonts_by_realname = newtree234(fontinfo_realname_compare); |
2459 | fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare); |
2460 | for (i = 0; i < lenof(unifont_types); i++) |
2461 | unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window), |
2462 | unifontsel_add_entry, fs); |
2463 | |
2464 | /* |
2465 | * And set up the initial font names list. |
2466 | */ |
2467 | unifontsel_setup_familylist(fs); |
2468 | |
2469 | fs->selsize = fs->intendedsize = 13; /* random default */ |
2470 | gtk_widget_set_sensitive(fs->u.ok_button, FALSE); |
2471 | |
2472 | return (unifontsel *)fs; |
2473 | } |
2474 | |
2475 | void unifontsel_destroy(unifontsel *fontsel) |
2476 | { |
2477 | unifontsel_internal *fs = (unifontsel_internal *)fontsel; |
2478 | fontinfo *info; |
2479 | |
2480 | if (fs->preview_pixmap) |
2481 | gdk_pixmap_unref(fs->preview_pixmap); |
2482 | |
2483 | freetree234(fs->fonts_by_selorder); |
2484 | while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL) |
2485 | sfree(info); |
2486 | freetree234(fs->fonts_by_realname); |
2487 | |
2488 | gtk_widget_destroy(GTK_WIDGET(fs->u.window)); |
2489 | sfree(fs); |
2490 | } |
2491 | |
2492 | void unifontsel_set_name(unifontsel *fontsel, const char *fontname) |
2493 | { |
2494 | unifontsel_internal *fs = (unifontsel_internal *)fontsel; |
2495 | int i, start, end, size, flags; |
2496 | const char *fontname2 = NULL; |
2497 | fontinfo *info; |
2498 | |
2499 | /* |
2500 | * Provide a default if given an empty or null font name. |
2501 | */ |
2502 | if (!fontname || !*fontname) |
2503 | fontname = "server:fixed"; |
2504 | |
2505 | /* |
2506 | * Call the canonify_fontname function. |
2507 | */ |
2508 | fontname = unifont_do_prefix(fontname, &start, &end); |
2509 | for (i = start; i < end; i++) { |
2510 | fontname2 = unifont_types[i]->canonify_fontname |
2511 | (GTK_WIDGET(fs->u.window), fontname, &size, &flags, FALSE); |
2512 | if (fontname2) |
2513 | break; |
2514 | } |
2515 | if (i == end) |
2516 | return; /* font name not recognised */ |
2517 | |
2518 | /* |
2519 | * Now look up the canonified font name in our index. |
2520 | */ |
2521 | { |
2522 | struct fontinfo_realname_find f; |
2523 | f.realname = fontname2; |
2524 | f.flags = flags; |
2525 | info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); |
2526 | } |
2527 | |
2528 | /* |
2529 | * If we've found the font, and its size field is either |
2530 | * correct or zero (the latter indicating a scalable font), |
2531 | * then we're done. Otherwise, try looking up the original |
2532 | * font name instead. |
2533 | */ |
2534 | if (!info || (info->size != size && info->size != 0)) { |
2535 | struct fontinfo_realname_find f; |
2536 | f.realname = fontname; |
2537 | f.flags = flags; |
2538 | |
2539 | info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find); |
2540 | if (!info || info->size != size) |
2541 | return; /* font name not in our index */ |
2542 | } |
2543 | |
2544 | /* |
2545 | * Now we've got a fontinfo structure and a font size, so we |
2546 | * know everything we need to fill in all the fields in the |
2547 | * dialog. |
2548 | */ |
2549 | unifontsel_select_font(fs, info, size, 0, TRUE); |
2550 | } |
2551 | |
2552 | char *unifontsel_get_name(unifontsel *fontsel) |
2553 | { |
2554 | unifontsel_internal *fs = (unifontsel_internal *)fontsel; |
2555 | char *name; |
2556 | |
2557 | if (!fs->selected) |
2558 | return NULL; |
2559 | |
2560 | if (fs->selected->size == 0) { |
2561 | name = fs->selected->fontclass->scale_fontname |
2562 | (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize); |
2563 | if (name) { |
2564 | char *ret = dupcat(fs->selected->fontclass->prefix, ":", |
2565 | name, NULL); |
2566 | sfree(name); |
2567 | return ret; |
2568 | } |
2569 | } |
2570 | |
2571 | return dupcat(fs->selected->fontclass->prefix, ":", |
2572 | fs->selected->realname, NULL); |
2573 | } |
2574 | |
2575 | #endif /* GTK_CHECK_VERSION(2,0,0) */ |