Further general development. Net is now playable, though
[sgt/puzzles] / gtk.c
CommitLineData
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 22void 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 47struct 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 61void 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
69void 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
78void 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
84void 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
90void 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
107void 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
115void 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 130static void destroy(GtkWidget *widget, gpointer data)
131{
132 gtk_main_quit();
133}
134
2ef96bd6 135static 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 149static 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 176static 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
192static 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
210static 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
220void deactivate_timer(frontend *fe)
221{
222 fe->timer_active = FALSE;
223}
224
225void activate_timer(frontend *fe)
226{
227 gtk_timeout_add(20, timer_func, fe);
228 fe->timer_active = TRUE;
229}
230
231static 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
298int 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}