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