2 * gtk.c: GTK front end for my puzzle collection.
11 #include <gdk/gdkkeysyms.h>
15 /* ----------------------------------------------------------------------
16 * Error reporting functions used elsewhere.
19 void fatal(char *fmt, ...)
23 fprintf(stderr, "fatal error: ");
26 vfprintf(stderr, fmt, ap);
29 fprintf(stderr, "\n");
33 /* ----------------------------------------------------------------------
34 * GTK front end to puzzles.
44 * This structure holds all the data relevant to a single window.
45 * In principle this would allow us to open multiple independent
46 * puzzle windows, although I can't currently see any real point in
47 * doing so. I'm just coding cleanly because there's no
48 * particularly good reason not to.
60 int bbox_l, bbox_r, bbox_u, bbox_d;
61 int timer_active, timer_id;
66 void frontend_default_colour(frontend *fe, float *output)
68 GdkColor col = fe->window->style->bg[GTK_STATE_NORMAL];
69 output[0] = col.red / 65535.0;
70 output[1] = col.green / 65535.0;
71 output[2] = col.blue / 65535.0;
74 void start_draw(frontend *fe)
76 fe->gc = gdk_gc_new(fe->area->window);
83 void clip(frontend *fe, int x, int y, int w, int h)
92 gdk_gc_set_clip_rectangle(fe->gc, &rect);
95 void unclip(frontend *fe)
104 gdk_gc_set_clip_rectangle(fe->gc, &rect);
107 void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
108 int align, int colour, char *text)
113 * Find or create the font.
115 for (i = 0; i < fe->nfonts; i++)
116 if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
119 if (i == fe->nfonts) {
120 if (fe->fontsize <= fe->nfonts) {
121 fe->fontsize = fe->nfonts + 10;
122 fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
127 fe->fonts[i].type = fonttype;
128 fe->fonts[i].size = fontsize;
131 * FIXME: Really I should make at least _some_ effort to
132 * pick the correct font.
134 fe->fonts[i].font = gdk_font_load("variable");
138 * Find string dimensions and process alignment.
141 int lb, rb, wid, asc, desc;
143 gdk_string_extents(fe->fonts[i].font, text,
144 &lb, &rb, &wid, &asc, &desc);
145 if (align & ALIGN_VCENTRE)
146 y += asc - (asc+desc)/2;
148 if (align & ALIGN_HCENTRE)
150 else if (align & ALIGN_HRIGHT)
156 * Set colour and actually draw text.
158 gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
159 gdk_draw_string(fe->pixmap, fe->fonts[i].font, fe->gc, x, y, text);
162 void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
164 gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
165 gdk_draw_rectangle(fe->pixmap, fe->gc, 1, x, y, w, h);
168 void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
170 gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
171 gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2);
174 void draw_polygon(frontend *fe, int *coords, int npoints,
175 int fill, int colour)
177 GdkPoint *points = snewn(npoints, GdkPoint);
180 for (i = 0; i < npoints; i++) {
181 points[i].x = coords[i*2];
182 points[i].y = coords[i*2+1];
185 gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
186 gdk_draw_polygon(fe->pixmap, fe->gc, fill, points, npoints);
191 void draw_update(frontend *fe, int x, int y, int w, int h)
193 if (fe->bbox_l > x ) fe->bbox_l = x ;
194 if (fe->bbox_r < x+w) fe->bbox_r = x+w;
195 if (fe->bbox_u > y ) fe->bbox_u = y ;
196 if (fe->bbox_d < y+h) fe->bbox_d = y+h;
199 void end_draw(frontend *fe)
201 gdk_gc_unref(fe->gc);
204 if (fe->bbox_l < fe->bbox_r && fe->bbox_u < fe->bbox_d) {
205 gdk_draw_pixmap(fe->area->window,
206 fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
208 fe->bbox_l, fe->bbox_u,
209 fe->bbox_l, fe->bbox_u,
210 fe->bbox_r - fe->bbox_l, fe->bbox_d - fe->bbox_u);
214 static void destroy(GtkWidget *widget, gpointer data)
219 static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
221 frontend *fe = (frontend *)data;
227 if (event->string[0] && !event->string[1])
228 keyval = (unsigned char)event->string[0];
229 else if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
230 event->keyval == GDK_KP_8)
232 else if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
233 event->keyval == GDK_KP_2)
234 keyval = CURSOR_DOWN;
235 else if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left ||
236 event->keyval == GDK_KP_4)
237 keyval = CURSOR_LEFT;
238 else if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right ||
239 event->keyval == GDK_KP_6)
240 keyval = CURSOR_RIGHT;
241 else if (event->keyval == GDK_KP_Home || event->keyval == GDK_KP_7)
242 keyval = CURSOR_UP_LEFT;
243 else if (event->keyval == GDK_KP_End || event->keyval == GDK_KP_1)
244 keyval = CURSOR_DOWN_LEFT;
245 else if (event->keyval == GDK_KP_Page_Up || event->keyval == GDK_KP_9)
246 keyval = CURSOR_UP_RIGHT;
247 else if (event->keyval == GDK_KP_Page_Down || event->keyval == GDK_KP_3)
248 keyval = CURSOR_DOWN_RIGHT;
253 !midend_process_key(fe->me, 0, 0, keyval))
254 gtk_widget_destroy(fe->window);
259 static gint button_event(GtkWidget *widget, GdkEventButton *event,
262 frontend *fe = (frontend *)data;
268 if (event->type != GDK_BUTTON_PRESS)
271 if (event->button == 2 || (event->state & GDK_SHIFT_MASK))
272 button = MIDDLE_BUTTON;
273 else if (event->button == 1)
274 button = LEFT_BUTTON;
275 else if (event->button == 3)
276 button = RIGHT_BUTTON;
278 return FALSE; /* don't even know what button! */
280 if (!midend_process_key(fe->me, event->x, event->y, button))
281 gtk_widget_destroy(fe->window);
286 static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
289 frontend *fe = (frontend *)data;
292 gdk_draw_pixmap(widget->window,
293 widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
295 event->area.x, event->area.y,
296 event->area.x, event->area.y,
297 event->area.width, event->area.height);
302 static gint configure_area(GtkWidget *widget,
303 GdkEventConfigure *event, gpointer data)
305 frontend *fe = (frontend *)data;
309 gdk_pixmap_unref(fe->pixmap);
311 fe->pixmap = gdk_pixmap_new(widget->window, fe->w, fe->h, -1);
313 gc = gdk_gc_new(fe->area->window);
314 gdk_gc_set_foreground(gc, &fe->colours[0]);
315 gdk_draw_rectangle(fe->pixmap, gc, 1, 0, 0, fe->w, fe->h);
318 midend_redraw(fe->me);
323 static gint timer_func(gpointer data)
325 frontend *fe = (frontend *)data;
327 if (fe->timer_active)
328 midend_timer(fe->me, 0.02); /* may clear timer_active */
330 return fe->timer_active;
333 void deactivate_timer(frontend *fe)
335 if (fe->timer_active)
336 gtk_timeout_remove(fe->timer_id);
337 fe->timer_active = FALSE;
340 void activate_timer(frontend *fe)
342 if (!fe->timer_active)
343 fe->timer_id = gtk_timeout_add(20, timer_func, fe);
344 fe->timer_active = TRUE;
347 static void menu_key_event(GtkMenuItem *menuitem, gpointer data)
349 frontend *fe = (frontend *)data;
350 int key = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem),
352 if (!midend_process_key(fe->me, 0, 0, key))
353 gtk_widget_destroy(fe->window);
356 static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
358 frontend *fe = (frontend *)data;
359 game_params *params =
360 (game_params *)gtk_object_get_data(GTK_OBJECT(menuitem), "user-data");
363 midend_set_params(fe->me, params);
364 midend_new_game(fe->me, NULL);
365 midend_size(fe->me, &x, &y);
366 gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
371 static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont,
374 GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
375 gtk_container_add(cont, menuitem);
376 gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
377 GINT_TO_POINTER(key));
378 gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
379 GTK_SIGNAL_FUNC(menu_key_event), fe);
380 gtk_widget_show(menuitem);
384 static void add_menu_separator(GtkContainer *cont)
386 GtkWidget *menuitem = gtk_menu_item_new();
387 gtk_container_add(cont, menuitem);
388 gtk_widget_show(menuitem);
391 static frontend *new_window(void)
395 GtkWidget *menubar, *menu, *menuitem;
400 fe->me = midend_new(fe);
401 midend_new_game(fe->me, NULL);
403 fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
404 gtk_window_set_title(GTK_WINDOW(fe->window), game_name);
406 gtk_window_set_resizable(GTK_WINDOW(fe->window), FALSE);
408 gtk_window_set_policy(GTK_WINDOW(fe->window), FALSE, FALSE, TRUE);
410 vbox = GTK_BOX(gtk_vbox_new(FALSE, 0));
411 gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox));
412 gtk_widget_show(GTK_WIDGET(vbox));
414 menubar = gtk_menu_bar_new();
415 gtk_box_pack_start(vbox, menubar, FALSE, FALSE, 0);
416 gtk_widget_show(menubar);
418 menuitem = gtk_menu_item_new_with_label("Game");
419 gtk_container_add(GTK_CONTAINER(menubar), menuitem);
420 gtk_widget_show(menuitem);
422 menu = gtk_menu_new();
423 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
425 add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n');
426 add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Restart", 'r');
428 if ((n = midend_num_presets(fe->me)) > 0) {
432 menuitem = gtk_menu_item_new_with_label("Type");
433 gtk_container_add(GTK_CONTAINER(menu), menuitem);
434 gtk_widget_show(menuitem);
436 submenu = gtk_menu_new();
437 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
439 for (i = 0; i < n; i++) {
443 midend_fetch_preset(fe->me, i, &name, ¶ms);
445 menuitem = gtk_menu_item_new_with_label(name);
446 gtk_container_add(GTK_CONTAINER(submenu), menuitem);
447 gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", params);
448 gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
449 GTK_SIGNAL_FUNC(menu_preset_event), fe);
450 gtk_widget_show(menuitem);
454 add_menu_separator(GTK_CONTAINER(menu));
455 add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u');
456 add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", '\x12');
457 add_menu_separator(GTK_CONTAINER(menu));
458 add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q');
465 fe->colmap = gdk_colormap_get_system();
466 colours = midend_colours(fe->me, &ncolours);
467 fe->ncolours = ncolours;
468 fe->colours = snewn(ncolours, GdkColor);
469 for (i = 0; i < ncolours; i++) {
470 fe->colours[i].red = colours[i*3] * 0xFFFF;
471 fe->colours[i].green = colours[i*3+1] * 0xFFFF;
472 fe->colours[i].blue = colours[i*3+2] * 0xFFFF;
474 success = snewn(ncolours, gboolean);
475 gdk_colormap_alloc_colors(fe->colmap, fe->colours, ncolours,
476 FALSE, FALSE, success);
477 for (i = 0; i < ncolours; i++) {
479 g_error("couldn't allocate colour %d (#%02x%02x%02x)\n",
480 i, fe->colours[i].red >> 8,
481 fe->colours[i].green >> 8,
482 fe->colours[i].blue >> 8);
486 fe->area = gtk_drawing_area_new();
487 midend_size(fe->me, &x, &y);
488 gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
492 gtk_box_pack_end(vbox, fe->area, FALSE, FALSE, 0);
496 fe->nfonts = fe->fontsize = 0;
498 fe->timer_active = FALSE;
500 gtk_signal_connect(GTK_OBJECT(fe->window), "destroy",
501 GTK_SIGNAL_FUNC(destroy), fe);
502 gtk_signal_connect(GTK_OBJECT(fe->window), "key_press_event",
503 GTK_SIGNAL_FUNC(key_event), fe);
504 gtk_signal_connect(GTK_OBJECT(fe->area), "button_press_event",
505 GTK_SIGNAL_FUNC(button_event), fe);
506 gtk_signal_connect(GTK_OBJECT(fe->area), "expose_event",
507 GTK_SIGNAL_FUNC(expose_area), fe);
508 gtk_signal_connect(GTK_OBJECT(fe->area), "configure_event",
509 GTK_SIGNAL_FUNC(configure_area), fe);
511 gtk_widget_add_events(GTK_WIDGET(fe->area), GDK_BUTTON_PRESS_MASK);
513 gtk_widget_show(fe->area);
514 gtk_widget_show(fe->window);
519 int main(int argc, char **argv)
523 gtk_init(&argc, &argv);