720a8fb7 |
1 | /* |
2 | * gtk.c: GTK front end for my puzzle collection. |
2ef96bd6 |
3 | * |
4 | * TODO: |
5 | * |
6 | * - Handle resizing, probably just by forbidding it. |
720a8fb7 |
7 | */ |
8 | |
9 | #include <stdio.h> |
10 | #include <stdlib.h> |
2ef96bd6 |
11 | #include <time.h> |
720a8fb7 |
12 | #include <stdarg.h> |
13 | |
83680571 |
14 | #include <gtk/gtk.h> |
15 | |
720a8fb7 |
16 | #include "puzzles.h" |
17 | |
83680571 |
18 | /* ---------------------------------------------------------------------- |
19 | * Error reporting functions used elsewhere. |
20 | */ |
21 | |
720a8fb7 |
22 | void fatal(char *fmt, ...) |
23 | { |
24 | va_list ap; |
25 | |
26 | fprintf(stderr, "fatal error: "); |
27 | |
28 | va_start(ap, fmt); |
29 | vfprintf(stderr, fmt, ap); |
30 | va_end(ap); |
31 | |
32 | fprintf(stderr, "\n"); |
33 | exit(1); |
34 | } |
83680571 |
35 | |
36 | /* ---------------------------------------------------------------------- |
37 | * GTK front end to puzzles. |
38 | */ |
39 | |
40 | /* |
41 | * This structure holds all the data relevant to a single window. |
42 | * In principle this would allow us to open multiple independent |
43 | * puzzle windows, although I can't currently see any real point in |
44 | * doing so. I'm just coding cleanly because there's no |
45 | * particularly good reason not to. |
46 | */ |
2ef96bd6 |
47 | struct frontend { |
83680571 |
48 | GtkWidget *window; |
7f77ea24 |
49 | GtkWidget *area; |
2ef96bd6 |
50 | GdkPixmap *pixmap; |
51 | GdkColor *colours; |
52 | int ncolours; |
53 | GdkColormap *colmap; |
54 | int w, h; |
7f77ea24 |
55 | midend_data *me; |
2ef96bd6 |
56 | GdkGC *gc; |
57 | int bbox_l, bbox_r, bbox_u, bbox_d; |
58 | int timer_active; |
83680571 |
59 | }; |
60 | |
2ef96bd6 |
61 | void frontend_default_colour(frontend *fe, float *output) |
62 | { |
63 | GdkColor col = fe->window->style->bg[GTK_STATE_NORMAL]; |
64 | output[0] = col.red / 65535.0; |
65 | output[1] = col.green / 65535.0; |
66 | output[2] = col.blue / 65535.0; |
67 | } |
68 | |
69 | void start_draw(frontend *fe) |
70 | { |
71 | fe->gc = gdk_gc_new(fe->area->window); |
72 | fe->bbox_l = fe->w; |
73 | fe->bbox_r = 0; |
74 | fe->bbox_u = fe->h; |
75 | fe->bbox_d = 0; |
76 | } |
77 | |
78 | void draw_rect(frontend *fe, int x, int y, int w, int h, int colour) |
79 | { |
80 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
81 | gdk_draw_rectangle(fe->pixmap, fe->gc, 1, x, y, w, h); |
82 | } |
83 | |
84 | void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour) |
85 | { |
86 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
87 | gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2); |
88 | } |
89 | |
90 | void draw_polygon(frontend *fe, int *coords, int npoints, |
91 | int fill, int colour) |
92 | { |
93 | GdkPoint *points = snewn(npoints, GdkPoint); |
94 | int i; |
95 | |
96 | for (i = 0; i < npoints; i++) { |
97 | points[i].x = coords[i*2]; |
98 | points[i].y = coords[i*2+1]; |
99 | } |
100 | |
101 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
102 | gdk_draw_polygon(fe->pixmap, fe->gc, fill, points, npoints); |
103 | |
104 | sfree(points); |
105 | } |
106 | |
107 | void draw_update(frontend *fe, int x, int y, int w, int h) |
108 | { |
109 | if (fe->bbox_l > x ) fe->bbox_l = x ; |
110 | if (fe->bbox_r < x+w) fe->bbox_r = x+w; |
111 | if (fe->bbox_u > y ) fe->bbox_u = y ; |
112 | if (fe->bbox_d < y+h) fe->bbox_d = y+h; |
113 | } |
114 | |
115 | void end_draw(frontend *fe) |
116 | { |
117 | gdk_gc_unref(fe->gc); |
118 | fe->gc = NULL; |
119 | |
120 | if (fe->bbox_l < fe->bbox_r && fe->bbox_u < fe->bbox_d) { |
121 | gdk_draw_pixmap(fe->area->window, |
122 | fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)], |
123 | fe->pixmap, |
124 | fe->bbox_l, fe->bbox_u, |
125 | fe->bbox_l, fe->bbox_u, |
126 | fe->bbox_r - fe->bbox_l, fe->bbox_d - fe->bbox_u); |
127 | } |
128 | } |
129 | |
83680571 |
130 | static void destroy(GtkWidget *widget, gpointer data) |
131 | { |
132 | gtk_main_quit(); |
133 | } |
134 | |
2ef96bd6 |
135 | static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) |
7f77ea24 |
136 | { |
2ef96bd6 |
137 | frontend *fe = (frontend *)data; |
7f77ea24 |
138 | |
2ef96bd6 |
139 | if (!fe->pixmap) |
140 | return TRUE; |
7f77ea24 |
141 | |
2ef96bd6 |
142 | if (event->string[0] && !event->string[1] && |
143 | !midend_process_key(fe->me, 0, 0, event->string[0])) |
144 | gtk_widget_destroy(fe->window); |
7f77ea24 |
145 | |
146 | return TRUE; |
147 | } |
148 | |
2ef96bd6 |
149 | static gint button_event(GtkWidget *widget, GdkEventButton *event, |
150 | gpointer data) |
7f77ea24 |
151 | { |
2ef96bd6 |
152 | frontend *fe = (frontend *)data; |
153 | int button; |
154 | |
155 | if (!fe->pixmap) |
156 | return TRUE; |
157 | |
158 | if (event->type != GDK_BUTTON_PRESS) |
159 | return TRUE; |
7f77ea24 |
160 | |
2ef96bd6 |
161 | if (event->button == 1) |
162 | button = LEFT_BUTTON; |
163 | else if (event->button == 2) |
164 | button = MIDDLE_BUTTON; |
165 | else if (event->button == 3) |
166 | button = RIGHT_BUTTON; |
167 | else |
168 | return FALSE; /* don't even know what button! */ |
169 | |
170 | if (!midend_process_key(fe->me, event->x, event->y, button)) |
171 | gtk_widget_destroy(fe->window); |
7f77ea24 |
172 | |
173 | return TRUE; |
174 | } |
175 | |
2ef96bd6 |
176 | static gint expose_area(GtkWidget *widget, GdkEventExpose *event, |
177 | gpointer data) |
83680571 |
178 | { |
2ef96bd6 |
179 | frontend *fe = (frontend *)data; |
180 | |
181 | if (fe->pixmap) { |
182 | gdk_draw_pixmap(widget->window, |
183 | widget->style->fg_gc[GTK_WIDGET_STATE(widget)], |
184 | fe->pixmap, |
185 | event->area.x, event->area.y, |
186 | event->area.x, event->area.y, |
187 | event->area.width, event->area.height); |
188 | } |
189 | return TRUE; |
190 | } |
191 | |
192 | static gint configure_area(GtkWidget *widget, |
193 | GdkEventConfigure *event, gpointer data) |
194 | { |
195 | frontend *fe = (frontend *)data; |
196 | GdkGC *gc; |
197 | |
198 | fe->pixmap = gdk_pixmap_new(widget->window, fe->w, fe->h, -1); |
199 | |
200 | gc = gdk_gc_new(fe->area->window); |
201 | gdk_gc_set_foreground(gc, &fe->colours[0]); |
202 | gdk_draw_rectangle(fe->pixmap, gc, 1, 0, 0, fe->w, fe->h); |
203 | gdk_gc_unref(gc); |
204 | |
205 | midend_redraw(fe->me); |
206 | |
207 | return TRUE; |
208 | } |
209 | |
210 | static gint timer_func(gpointer data) |
211 | { |
212 | frontend *fe = (frontend *)data; |
213 | |
214 | if (fe->timer_active) |
215 | midend_timer(fe->me, 0.02); /* may clear timer_active */ |
216 | |
217 | return fe->timer_active; |
218 | } |
219 | |
220 | void deactivate_timer(frontend *fe) |
221 | { |
222 | fe->timer_active = FALSE; |
223 | } |
224 | |
225 | void activate_timer(frontend *fe) |
226 | { |
227 | gtk_timeout_add(20, timer_func, fe); |
228 | fe->timer_active = TRUE; |
229 | } |
230 | |
231 | static frontend *new_window(void) |
232 | { |
233 | frontend *fe; |
7f77ea24 |
234 | int x, y; |
83680571 |
235 | |
2ef96bd6 |
236 | fe = snew(frontend); |
83680571 |
237 | |
2ef96bd6 |
238 | fe->me = midend_new(fe); |
239 | midend_new_game(fe->me, NULL); |
7f77ea24 |
240 | |
2ef96bd6 |
241 | fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
7f77ea24 |
242 | |
2ef96bd6 |
243 | { |
244 | int i, ncolours; |
245 | float *colours; |
246 | gboolean *success; |
7f77ea24 |
247 | |
2ef96bd6 |
248 | fe->colmap = gdk_colormap_get_system(); |
249 | colours = midend_colours(fe->me, &ncolours); |
250 | fe->ncolours = ncolours; |
251 | fe->colours = snewn(ncolours, GdkColor); |
252 | for (i = 0; i < ncolours; i++) { |
253 | fe->colours[i].red = colours[i*3] * 0xFFFF; |
254 | fe->colours[i].green = colours[i*3+1] * 0xFFFF; |
255 | fe->colours[i].blue = colours[i*3+2] * 0xFFFF; |
256 | } |
257 | success = snewn(ncolours, gboolean); |
258 | gdk_colormap_alloc_colors(fe->colmap, fe->colours, ncolours, |
259 | FALSE, FALSE, success); |
260 | for (i = 0; i < ncolours; i++) { |
261 | if (!success[i]) |
262 | g_error("couldn't allocate colour %d (#%02x%02x%02x)\n", |
263 | i, fe->colours[i].red >> 8, |
264 | fe->colours[i].green >> 8, |
265 | fe->colours[i].blue >> 8); |
266 | } |
267 | } |
7f77ea24 |
268 | |
2ef96bd6 |
269 | fe->area = gtk_drawing_area_new(); |
270 | midend_size(fe->me, &x, &y); |
271 | gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); |
272 | fe->w = x; |
273 | fe->h = y; |
274 | |
275 | gtk_container_add(GTK_CONTAINER(fe->window), fe->area); |
276 | |
277 | fe->pixmap = NULL; |
278 | |
279 | gtk_signal_connect(GTK_OBJECT(fe->window), "destroy", |
280 | GTK_SIGNAL_FUNC(destroy), fe); |
281 | gtk_signal_connect(GTK_OBJECT(fe->window), "key_press_event", |
282 | GTK_SIGNAL_FUNC(key_event), fe); |
283 | gtk_signal_connect(GTK_OBJECT(fe->area), "button_press_event", |
284 | GTK_SIGNAL_FUNC(button_event), fe); |
285 | gtk_signal_connect(GTK_OBJECT(fe->area), "expose_event", |
286 | GTK_SIGNAL_FUNC(expose_area), fe); |
287 | gtk_signal_connect(GTK_OBJECT(fe->area), "configure_event", |
288 | GTK_SIGNAL_FUNC(configure_area), fe); |
289 | |
290 | gtk_widget_add_events(GTK_WIDGET(fe->area), GDK_BUTTON_PRESS_MASK); |
291 | |
292 | gtk_widget_show(fe->area); |
293 | gtk_widget_show(fe->window); |
294 | |
295 | return fe; |
83680571 |
296 | } |
297 | |
298 | int main(int argc, char **argv) |
299 | { |
2ef96bd6 |
300 | srand(time(NULL)); |
301 | |
83680571 |
302 | gtk_init(&argc, &argv); |
303 | (void) new_window(); |
304 | gtk_main(); |
305 | |
306 | return 0; |
307 | } |