Skip to content
Newer
Older
100644 336 lines (281 sloc) 11.6 KB
a2afd73 @nickg Move bubble generation into a separate file
authored
1 /* bubblegen.c -- Generate various sorts of bubbles.
4dc9971 @nickg Don't print a warning when we can't parse Pango attributes
authored
2 * Copyright (C) 2008-2010 Nick Gasson
a2afd73 @nickg Move bubble generation into a separate file
authored
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
32ccbe2 @nickg Trigger rebuild on install prefix change
authored
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
a2afd73 @nickg Move bubble generation into a separate file
authored
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdbool.h>
25 #include <string.h>
26
27 #include <gtk/gtk.h>
d529976 @nickg Working dream mode
authored
28 #include <gdk-pixbuf/gdk-pixbuf.h>
a2afd73 @nickg Move bubble generation into a separate file
authored
29
30 #include "floating_shape.h"
baba3ac @nickg Add --think switch
authored
31 #include "display_cow.h"
a2afd73 @nickg Move bubble generation into a separate file
authored
32 #include "settings.h"
33 #include "i18n.h"
34
35 #define LEFT_BUF 5 // Amount of pixels to leave after cow's tail
36 #define TIP_WIDTH 20 // Length of the triangle bit on the speech bubble
e4384ee @nickg Draw thought bubbles properly
authored
37 #define THINK_WIDTH 80 // Spaces for thinking circles
a2afd73 @nickg Move bubble generation into a separate file
authored
38 #define CORNER_RADIUS 30 // Radius of corners on the speech bubble
39 #define CORNER_DIAM CORNER_RADIUS*2
40 #define BUBBLE_BORDER 5 // Pixels to leave free around edge of bubble
41 #define MIN_TIP_HEIGHT 15
42
0146a3f @nickg Start drawing think bubbles
authored
43 // These next ones control the size and position of the "thinking circles"
44 // (or whatever you call them)
f954ba5 @nickg Fix cow vertical positioning bug
authored
45 #define BIG_KIRCLE_X 38
e4384ee @nickg Draw thought bubbles properly
authored
46 #define BIG_KIRCLE_Y 70
47 #define BIG_KIRCLE_DIAM 35
0146a3f @nickg Start drawing think bubbles
authored
48
e4384ee @nickg Draw thought bubbles properly
authored
49 #define SMALL_KIRCLE_X 5
50 #define SMALL_KIRCLE_Y 40
51 #define SMALL_KIRCLE_DIAM 20
0146a3f @nickg Start drawing think bubbles
authored
52
bd41309 @nickg Line up kircles when bubble is small
authored
53 // Min distance from top of the big kircle to the top of the bubble
54 #define KIRCLE_TOP_MIN 10
55
8e056a1 @nickg Refactor bubble drawing code
authored
56 typedef struct {
57 int width, height;
58 GdkPixmap *pixmap;
59 GdkGC *gc;
60 } bubble_t;
a2afd73 @nickg Move bubble generation into a separate file
authored
61
0146a3f @nickg Start drawing think bubbles
authored
62 typedef enum { NORMAL, THOUGHT } bubble_style_t;
63
64 static void bubble_init(bubble_t *b, bubble_style_t style)
a2afd73 @nickg Move bubble generation into a separate file
authored
65 {
66 GdkColor black, white, bright_green;
67 GdkColormap *colormap;
68 GdkPoint tip_points[5];
4e175a0 Fix xcowsay on screens with depth != 24
Nick Gasson authored
69 GdkVisual *root_visual;
70
71 root_visual = gdk_visual_get_system();
72 b->pixmap = gdk_pixmap_new(NULL, b->width, b->height, root_visual->depth);
73 g_assert(b->pixmap);
8e056a1 @nickg Refactor bubble drawing code
authored
74 b->gc = gdk_gc_new(b->pixmap);
a2afd73 @nickg Move bubble generation into a separate file
authored
75
76 colormap = gdk_colormap_get_system();
77 gdk_color_black(colormap, &black);
78 gdk_color_white(colormap, &white);
79
80 bright_green.red = 0;
81 bright_green.green = 65535; // Bright green is alpha
82 bright_green.blue = 0;
8e056a1 @nickg Refactor bubble drawing code
authored
83 gdk_gc_set_background(b->gc, &black);
84 gdk_gc_set_rgb_fg_color(b->gc, &bright_green);
a2afd73 @nickg Move bubble generation into a separate file
authored
85
8e056a1 @nickg Refactor bubble drawing code
authored
86 gdk_draw_rectangle(b->pixmap, b->gc, TRUE, 0, 0, b->width, b->height);
a2afd73 @nickg Move bubble generation into a separate file
authored
87
8e056a1 @nickg Refactor bubble drawing code
authored
88 b->width -= BUBBLE_BORDER;
89 b->height -= BUBBLE_BORDER;
a2afd73 @nickg Move bubble generation into a separate file
authored
90
42fe255 @nickg Add middle variable for cow->bubble distance
authored
91 // Space between cow and bubble
0146a3f @nickg Start drawing think bubbles
authored
92 int middle = style == NORMAL ? TIP_WIDTH : THINK_WIDTH;
42fe255 @nickg Add middle variable for cow->bubble distance
authored
93
a2afd73 @nickg Move bubble generation into a separate file
authored
94 // Draw the white corners
8e056a1 @nickg Refactor bubble drawing code
authored
95 gdk_gc_set_foreground(b->gc, &white);
42fe255 @nickg Add middle variable for cow->bubble distance
authored
96 gdk_draw_arc(b->pixmap, b->gc, TRUE, middle + BUBBLE_BORDER,
a2afd73 @nickg Move bubble generation into a separate file
authored
97 BUBBLE_BORDER, CORNER_DIAM, CORNER_DIAM, 90*64, 90*64);
42fe255 @nickg Add middle variable for cow->bubble distance
authored
98 gdk_draw_arc(b->pixmap, b->gc, TRUE, middle + BUBBLE_BORDER,
8e056a1 @nickg Refactor bubble drawing code
authored
99 b->height - CORNER_DIAM, CORNER_DIAM,
a2afd73 @nickg Move bubble generation into a separate file
authored
100 CORNER_DIAM, 180*64, 90*64);
8e056a1 @nickg Refactor bubble drawing code
authored
101 gdk_draw_arc(b->pixmap, b->gc, TRUE,
102 b->width - CORNER_DIAM - BUBBLE_BORDER,
103 b->height - CORNER_DIAM, CORNER_DIAM,
a2afd73 @nickg Move bubble generation into a separate file
authored
104 CORNER_DIAM, 270*64, 90*64);
8e056a1 @nickg Refactor bubble drawing code
authored
105 gdk_draw_arc(b->pixmap, b->gc, TRUE,
106 b->width - CORNER_DIAM - BUBBLE_BORDER,
a2afd73 @nickg Move bubble generation into a separate file
authored
107 BUBBLE_BORDER, CORNER_DIAM, CORNER_DIAM, 0*64, 90*64);
108
109 // Fill in the middle of the bubble
8e056a1 @nickg Refactor bubble drawing code
authored
110 gdk_draw_rectangle(b->pixmap, b->gc, TRUE,
42fe255 @nickg Add middle variable for cow->bubble distance
authored
111 CORNER_RADIUS + middle + BUBBLE_BORDER,
a2afd73 @nickg Move bubble generation into a separate file
authored
112 BUBBLE_BORDER,
42fe255 @nickg Add middle variable for cow->bubble distance
authored
113 b->width - middle - BUBBLE_BORDER - CORNER_DIAM,
8e056a1 @nickg Refactor bubble drawing code
authored
114 b->height - BUBBLE_BORDER);
115 gdk_draw_rectangle(b->pixmap, b->gc, TRUE,
42fe255 @nickg Add middle variable for cow->bubble distance
authored
116 middle + BUBBLE_BORDER, BUBBLE_BORDER + CORNER_RADIUS,
117 b->width - middle - BUBBLE_BORDER*2,
8e056a1 @nickg Refactor bubble drawing code
authored
118 b->height - BUBBLE_BORDER - CORNER_DIAM);
a2afd73 @nickg Move bubble generation into a separate file
authored
119
0146a3f @nickg Start drawing think bubbles
authored
120 if (style == NORMAL) {
121 // The points on the tip part
122 int tip_compute_offset = (b->height - BUBBLE_BORDER - CORNER_DIAM)/3;
123 int tip_offset[3] = { tip_compute_offset, tip_compute_offset, tip_compute_offset };
124 if (tip_compute_offset < MIN_TIP_HEIGHT) {
125 int new_offset = (b->height - BUBBLE_BORDER - CORNER_DIAM - MIN_TIP_HEIGHT)/2;
126 tip_offset[0] = new_offset;
127 tip_offset[1] = MIN_TIP_HEIGHT;
128 tip_offset[2] = new_offset;
129 }
130
131 tip_points[0].x = middle + BUBBLE_BORDER;
132 tip_points[0].y = BUBBLE_BORDER + CORNER_RADIUS;
133 tip_points[1].x = middle + BUBBLE_BORDER;
134 tip_points[1].y = BUBBLE_BORDER + CORNER_RADIUS + tip_offset[0];
135 tip_points[2].x = BUBBLE_BORDER;
136 tip_points[2].y = BUBBLE_BORDER + CORNER_RADIUS + tip_offset[0] + tip_offset[1]/2;
137 tip_points[3].x = middle + BUBBLE_BORDER;
138 tip_points[3].y = BUBBLE_BORDER + CORNER_RADIUS + tip_offset[0] + tip_offset[1];
139 tip_points[4].x = middle + BUBBLE_BORDER;
140 tip_points[4].y = b->height - CORNER_RADIUS;
141
142 gdk_draw_polygon(b->pixmap, b->gc, TRUE, tip_points, 5);
143 }
144 else {
bd41309 @nickg Line up kircles when bubble is small
authored
145 // Incrementally move the top kircle down so it's within the
146 // bubble's border
147 int big_y = BIG_KIRCLE_Y;
148 int small_y = SMALL_KIRCLE_Y;
149
150 while (big_y + KIRCLE_TOP_MIN > b->height/2) {
151 big_y /= 2;
152 small_y /= 2;
153 }
154
0146a3f @nickg Start drawing think bubbles
authored
155 // Draw two think kircles
156 gdk_draw_arc(b->pixmap, b->gc, TRUE,
157 BIG_KIRCLE_X,
bd41309 @nickg Line up kircles when bubble is small
authored
158 b->height/2 - big_y, BIG_KIRCLE_DIAM,
0146a3f @nickg Start drawing think bubbles
authored
159 BIG_KIRCLE_DIAM, 0, 360*64);
160
161 gdk_draw_arc(b->pixmap, b->gc, TRUE,
162 SMALL_KIRCLE_X,
bd41309 @nickg Line up kircles when bubble is small
authored
163 b->height/2 - small_y, SMALL_KIRCLE_DIAM,
e4384ee @nickg Draw thought bubbles properly
authored
164 SMALL_KIRCLE_DIAM, 0, 360*64);
165
166 gdk_gc_set_line_attributes(b->gc, 4, GDK_LINE_SOLID,
167 GDK_CAP_ROUND, GDK_JOIN_ROUND);
168 gdk_gc_set_foreground(b->gc, &black);
169 gdk_draw_arc(b->pixmap, b->gc, FALSE,
170 BIG_KIRCLE_X,
bd41309 @nickg Line up kircles when bubble is small
authored
171 b->height/2 - big_y, BIG_KIRCLE_DIAM,
e4384ee @nickg Draw thought bubbles properly
authored
172 BIG_KIRCLE_DIAM, 0, 360*64);
173
174 gdk_draw_arc(b->pixmap, b->gc, FALSE,
175 SMALL_KIRCLE_X,
bd41309 @nickg Line up kircles when bubble is small
authored
176 b->height/2 - small_y, SMALL_KIRCLE_DIAM,
0146a3f @nickg Start drawing think bubbles
authored
177 SMALL_KIRCLE_DIAM, 0, 360*64);
a2afd73 @nickg Move bubble generation into a separate file
authored
178 }
179
180 // Draw the black rounded corners
8e056a1 @nickg Refactor bubble drawing code
authored
181 gdk_gc_set_line_attributes(b->gc, 4, GDK_LINE_SOLID,
a2afd73 @nickg Move bubble generation into a separate file
authored
182 GDK_CAP_ROUND, GDK_JOIN_ROUND);
8e056a1 @nickg Refactor bubble drawing code
authored
183 gdk_gc_set_foreground(b->gc, &black);
42fe255 @nickg Add middle variable for cow->bubble distance
authored
184 gdk_draw_arc(b->pixmap, b->gc, FALSE, middle + BUBBLE_BORDER,
a2afd73 @nickg Move bubble generation into a separate file
authored
185 BUBBLE_BORDER, CORNER_DIAM, CORNER_DIAM, 90*64, 90*64);
42fe255 @nickg Add middle variable for cow->bubble distance
authored
186 gdk_draw_arc(b->pixmap, b->gc, FALSE, middle + BUBBLE_BORDER,
8e056a1 @nickg Refactor bubble drawing code
authored
187 b->height - CORNER_DIAM, CORNER_DIAM,
a2afd73 @nickg Move bubble generation into a separate file
authored
188 CORNER_DIAM, 180*64, 90*64);
8e056a1 @nickg Refactor bubble drawing code
authored
189 gdk_draw_arc(b->pixmap, b->gc, FALSE,
190 b->width - CORNER_DIAM - BUBBLE_BORDER,
191 b->height - CORNER_DIAM, CORNER_DIAM,
a2afd73 @nickg Move bubble generation into a separate file
authored
192 CORNER_DIAM, 270*64, 90*64);
8e056a1 @nickg Refactor bubble drawing code
authored
193 gdk_draw_arc(b->pixmap, b->gc, FALSE,
194 b->width - CORNER_DIAM - BUBBLE_BORDER,
a2afd73 @nickg Move bubble generation into a separate file
authored
195 BUBBLE_BORDER, CORNER_DIAM, CORNER_DIAM, 0*64, 90*64);
196
197 // Draw the top, bottom, and right sides (easy as they're straight!)
8e056a1 @nickg Refactor bubble drawing code
authored
198 gdk_draw_line(b->pixmap, b->gc,
199 b->width - BUBBLE_BORDER,
a2afd73 @nickg Move bubble generation into a separate file
authored
200 CORNER_RADIUS + BUBBLE_BORDER,
8e056a1 @nickg Refactor bubble drawing code
authored
201 b->width - BUBBLE_BORDER, b->height - CORNER_RADIUS);
202 gdk_draw_line(b->pixmap, b->gc,
42fe255 @nickg Add middle variable for cow->bubble distance
authored
203 BUBBLE_BORDER + middle + CORNER_RADIUS, BUBBLE_BORDER,
8e056a1 @nickg Refactor bubble drawing code
authored
204 b->width - CORNER_RADIUS, BUBBLE_BORDER);
205 gdk_draw_line(b->pixmap, b->gc,
42fe255 @nickg Add middle variable for cow->bubble distance
authored
206 BUBBLE_BORDER + middle + CORNER_RADIUS, b->height,
8e056a1 @nickg Refactor bubble drawing code
authored
207 b->width - CORNER_RADIUS, b->height);
0146a3f @nickg Start drawing think bubbles
authored
208
209 if (style == NORMAL)
210 gdk_draw_lines(b->pixmap, b->gc, tip_points, 5);
211 else
212 gdk_draw_line(b->pixmap, b->gc,
213 BUBBLE_BORDER + middle,
214 CORNER_RADIUS + BUBBLE_BORDER,
215 BUBBLE_BORDER + middle,
216 b->height - CORNER_RADIUS);
8e056a1 @nickg Refactor bubble drawing code
authored
217 }
218
0146a3f @nickg Start drawing think bubbles
authored
219 static void bubble_size_from_content(bubble_t *b, bubble_style_t style,
220 int c_width, int c_height)
8e056a1 @nickg Refactor bubble drawing code
authored
221 {
0146a3f @nickg Start drawing think bubbles
authored
222 int middle = style == NORMAL ? TIP_WIDTH : THINK_WIDTH;
42fe255 @nickg Add middle variable for cow->bubble distance
authored
223 b->width = 2*BUBBLE_BORDER + CORNER_DIAM + middle + c_width;
8e056a1 @nickg Refactor bubble drawing code
authored
224 b->height = BUBBLE_BORDER + CORNER_DIAM + c_height;
225 }
a2afd73 @nickg Move bubble generation into a separate file
authored
226
8e056a1 @nickg Refactor bubble drawing code
authored
227 static GdkPixbuf *bubble_tidy(bubble_t *b)
228 {
229 GdkPixbuf *pixbuf =
230 gdk_pixbuf_get_from_drawable(NULL, b->pixmap, NULL,
231 0, 0, 0, 0,
232 b->width + BUBBLE_BORDER,
233 b->height + BUBBLE_BORDER);
234 g_object_unref(b->pixmap);
235 return pixbuf;
236 }
237
0146a3f @nickg Start drawing think bubbles
authored
238 static int bubble_content_left(bubble_style_t style)
6008c9a @nickg Move all constants out of bubble generator
authored
239 {
0146a3f @nickg Start drawing think bubbles
authored
240 int middle = style == NORMAL ? TIP_WIDTH : THINK_WIDTH;
42fe255 @nickg Add middle variable for cow->bubble distance
authored
241 return BUBBLE_BORDER + middle + CORNER_RADIUS;
6008c9a @nickg Move all constants out of bubble generator
authored
242 }
243
244 static int bubble_content_top()
245 {
246 return CORNER_RADIUS;
247 }
248
9d2185a @nickg Dummy dream bubble generator
authored
249 GdkPixbuf *make_dream_bubble(const char *file, int *p_width, int *p_height)
250 {
d529976 @nickg Working dream mode
authored
251 bubble_t bubble;
252 GError *error = NULL;
253 GdkPixbuf *image = gdk_pixbuf_new_from_file(file, &error);
254
255 if (NULL == image) {
256 fprintf(stderr, "Error: failed to load %s\n", file);
257 exit(1);
258 }
259
0146a3f @nickg Start drawing think bubbles
authored
260 bubble_size_from_content(&bubble, THOUGHT, gdk_pixbuf_get_width(image),
d529976 @nickg Working dream mode
authored
261 gdk_pixbuf_get_height(image));
262 *p_width = bubble.width;
263 *p_height = bubble.height;
264
0146a3f @nickg Start drawing think bubbles
authored
265 bubble_init(&bubble, THOUGHT);
d529976 @nickg Working dream mode
authored
266
267 gdk_draw_pixbuf(bubble.pixmap, bubble.gc, image, 0, 0,
0146a3f @nickg Start drawing think bubbles
authored
268 bubble_content_left(THOUGHT), bubble_content_top(),
d529976 @nickg Working dream mode
authored
269 -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
270
271 gdk_pixbuf_unref(image);
272
273 return bubble_tidy(&bubble);
9d2185a @nickg Dummy dream bubble generator
authored
274 }
275
d28a3b8 @nickg Word wrapping in text bubbles
authored
276 GdkPixbuf *make_text_bubble(char *text, int *p_width, int *p_height,
277 int max_width, cowmode_t mode)
8e056a1 @nickg Refactor bubble drawing code
authored
278 {
279 bubble_t bubble;
280 int text_width, text_height;
281
282 // Work out the size of the bubble from the text
283 PangoContext *pango_context = gdk_pango_context_get();
284 PangoLayout *layout = pango_layout_new(pango_context);
285 PangoFontDescription *font =
286 pango_font_description_from_string(get_string_option("font"));
287 PangoAttrList *pango_attrs = NULL;
288
d28a3b8 @nickg Word wrapping in text bubbles
authored
289 // Adjust max width to account for bubble edges
290 max_width -= LEFT_BUF;
291 max_width -= TIP_WIDTH;
292 max_width -= 2 * BUBBLE_BORDER;
293 max_width -= CORNER_DIAM;
294
295 if (get_bool_option("wrap")) {
296 pango_layout_set_width(layout, max_width * PANGO_SCALE);
297 pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
298 }
299
8e056a1 @nickg Refactor bubble drawing code
authored
300 char *stripped;
4dc9971 @nickg Don't print a warning when we can't parse Pango attributes
authored
301 if (!pango_parse_markup(text, -1, 0, &pango_attrs,
302 &stripped, NULL, NULL)) {
303
304 // This isn't fatal as the the text may contain angled brackets, etc.
8e056a1 @nickg Refactor bubble drawing code
authored
305 stripped = text;
306 }
307 else {
308 pango_layout_set_attributes(layout, pango_attrs);
309 }
310
311 pango_layout_set_font_description(layout, font);
312 pango_layout_set_text(layout, stripped, -1);
313 pango_layout_get_pixel_size(layout, &text_width, &text_height);
baba3ac @nickg Add --think switch
authored
314
315 bubble_style_t style = mode == COWMODE_NORMAL ? NORMAL : THOUGHT;
8e056a1 @nickg Refactor bubble drawing code
authored
316
baba3ac @nickg Add --think switch
authored
317 bubble_size_from_content(&bubble, style, text_width, text_height);
8e056a1 @nickg Refactor bubble drawing code
authored
318 *p_width = bubble.width;
319 *p_height = bubble.height;
320
baba3ac @nickg Add --think switch
authored
321 bubble_init(&bubble, style);
8e056a1 @nickg Refactor bubble drawing code
authored
322
a2afd73 @nickg Move bubble generation into a separate file
authored
323 // Render the text
8e056a1 @nickg Refactor bubble drawing code
authored
324 gdk_draw_layout(bubble.pixmap, bubble.gc,
4dc9971 @nickg Don't print a warning when we can't parse Pango attributes
authored
325 bubble_content_left(mode), bubble_content_top(), layout);
a2afd73 @nickg Move bubble generation into a separate file
authored
326
327 // Make sure to free the Pango objects
328 g_object_unref(pango_context);
329 g_object_unref(layout);
330 pango_font_description_free(font);
331 if (NULL != pango_attrs)
332 pango_attr_list_unref(pango_attrs);
333
8e056a1 @nickg Refactor bubble drawing code
authored
334 return bubble_tidy(&bubble);
a2afd73 @nickg Move bubble generation into a separate file
authored
335 }
Something went wrong with that request. Please try again.