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