ec086fce8fc573150eb2b40d3fd5a246acf2fb35
[sgt/puzzles] / gtk.c
1 /*
2 * gtk.c: GTK front end for my puzzle collection.
3 *
4 * TODO:
5 *
6 * - Handle resizing, probably just by forbidding it.
7 */
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <time.h>
12 #include <stdarg.h>
13
14 #include <gtk/gtk.h>
15
16 #include "puzzles.h"
17
18 /* ----------------------------------------------------------------------
19 * Error reporting functions used elsewhere.
20 */
21
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 }
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 */
47 struct frontend {
48 GtkWidget *window;
49 GtkWidget *area;
50 GdkPixmap *pixmap;
51 GdkColor *colours;
52 int ncolours;
53 GdkColormap *colmap;
54 int w, h;
55 midend_data *me;
56 GdkGC *gc;
57 int bbox_l, bbox_r, bbox_u, bbox_d;
58 int timer_active;
59 };
60
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
130 static void destroy(GtkWidget *widget, gpointer data)
131 {
132 gtk_main_quit();
133 }
134
135 static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
136 {
137 frontend *fe = (frontend *)data;
138
139 if (!fe->pixmap)
140 return TRUE;
141
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);
145
146 return TRUE;
147 }
148
149 static gint button_event(GtkWidget *widget, GdkEventButton *event,
150 gpointer data)
151 {
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;
160
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);
172
173 return TRUE;
174 }
175
176 static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
177 gpointer data)
178 {
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;
234 int x, y;
235
236 fe = snew(frontend);
237
238 fe->me = midend_new(fe);
239 midend_new_game(fe->me, NULL);
240
241 fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
242
243 {
244 int i, ncolours;
245 float *colours;
246 gboolean *success;
247
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 }
268
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;
296 }
297
298 int main(int argc, char **argv)
299 {
300 srand(time(NULL));
301
302 gtk_init(&argc, &argv);
303 (void) new_window();
304 gtk_main();
305
306 return 0;
307 }