720a8fb7 |
1 | /* |
2 | * gtk.c: GTK front end for my puzzle collection. |
3 | */ |
4 | |
5 | #include <stdio.h> |
fd1a1a2b |
6 | #include <assert.h> |
720a8fb7 |
7 | #include <stdlib.h> |
2ef96bd6 |
8 | #include <time.h> |
720a8fb7 |
9 | #include <stdarg.h> |
c8230524 |
10 | #include <string.h> |
720a8fb7 |
11 | |
8c1fd974 |
12 | #include <sys/time.h> |
13 | |
83680571 |
14 | #include <gtk/gtk.h> |
1482ee76 |
15 | #include <gdk/gdkkeysyms.h> |
83680571 |
16 | |
720a8fb7 |
17 | #include "puzzles.h" |
18 | |
83680571 |
19 | /* ---------------------------------------------------------------------- |
20 | * Error reporting functions used elsewhere. |
21 | */ |
22 | |
720a8fb7 |
23 | void fatal(char *fmt, ...) |
24 | { |
25 | va_list ap; |
26 | |
27 | fprintf(stderr, "fatal error: "); |
28 | |
29 | va_start(ap, fmt); |
30 | vfprintf(stderr, fmt, ap); |
31 | va_end(ap); |
32 | |
33 | fprintf(stderr, "\n"); |
34 | exit(1); |
35 | } |
83680571 |
36 | |
37 | /* ---------------------------------------------------------------------- |
38 | * GTK front end to puzzles. |
39 | */ |
40 | |
4efb3868 |
41 | struct font { |
42 | GdkFont *font; |
43 | int type; |
44 | int size; |
45 | }; |
46 | |
83680571 |
47 | /* |
48 | * This structure holds all the data relevant to a single window. |
49 | * In principle this would allow us to open multiple independent |
50 | * puzzle windows, although I can't currently see any real point in |
51 | * doing so. I'm just coding cleanly because there's no |
52 | * particularly good reason not to. |
53 | */ |
2ef96bd6 |
54 | struct frontend { |
83680571 |
55 | GtkWidget *window; |
7f77ea24 |
56 | GtkWidget *area; |
fd1a1a2b |
57 | GtkWidget *statusbar; |
58 | guint statusctx; |
2ef96bd6 |
59 | GdkPixmap *pixmap; |
60 | GdkColor *colours; |
61 | int ncolours; |
62 | GdkColormap *colmap; |
63 | int w, h; |
7f77ea24 |
64 | midend_data *me; |
2ef96bd6 |
65 | GdkGC *gc; |
66 | int bbox_l, bbox_r, bbox_u, bbox_d; |
20ee89e3 |
67 | int timer_active, timer_id; |
8c1fd974 |
68 | struct timeval last_time; |
4efb3868 |
69 | struct font *fonts; |
70 | int nfonts, fontsize; |
c8230524 |
71 | config_item *cfg; |
5928817c |
72 | int cfg_which, cfgret; |
c8230524 |
73 | GtkWidget *cfgbox; |
83680571 |
74 | }; |
75 | |
cbb5549e |
76 | void get_random_seed(void **randseed, int *randseedsize) |
77 | { |
78 | time_t *tp = snew(time_t); |
79 | time(tp); |
80 | *randseed = (void *)tp; |
81 | *randseedsize = sizeof(time_t); |
82 | } |
83 | |
2ef96bd6 |
84 | void frontend_default_colour(frontend *fe, float *output) |
85 | { |
86 | GdkColor col = fe->window->style->bg[GTK_STATE_NORMAL]; |
87 | output[0] = col.red / 65535.0; |
88 | output[1] = col.green / 65535.0; |
89 | output[2] = col.blue / 65535.0; |
90 | } |
91 | |
fd1a1a2b |
92 | void status_bar(frontend *fe, char *text) |
93 | { |
94 | assert(fe->statusbar); |
95 | |
96 | gtk_statusbar_pop(GTK_STATUSBAR(fe->statusbar), fe->statusctx); |
97 | gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx, text); |
98 | } |
99 | |
2ef96bd6 |
100 | void start_draw(frontend *fe) |
101 | { |
102 | fe->gc = gdk_gc_new(fe->area->window); |
103 | fe->bbox_l = fe->w; |
104 | fe->bbox_r = 0; |
105 | fe->bbox_u = fe->h; |
106 | fe->bbox_d = 0; |
107 | } |
108 | |
4efb3868 |
109 | void clip(frontend *fe, int x, int y, int w, int h) |
110 | { |
111 | GdkRectangle rect; |
112 | |
113 | rect.x = x; |
114 | rect.y = y; |
115 | rect.width = w; |
116 | rect.height = h; |
117 | |
118 | gdk_gc_set_clip_rectangle(fe->gc, &rect); |
119 | } |
120 | |
121 | void unclip(frontend *fe) |
122 | { |
123 | GdkRectangle rect; |
124 | |
125 | rect.x = 0; |
126 | rect.y = 0; |
127 | rect.width = fe->w; |
128 | rect.height = fe->h; |
129 | |
130 | gdk_gc_set_clip_rectangle(fe->gc, &rect); |
131 | } |
132 | |
133 | void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize, |
134 | int align, int colour, char *text) |
135 | { |
136 | int i; |
137 | |
138 | /* |
139 | * Find or create the font. |
140 | */ |
141 | for (i = 0; i < fe->nfonts; i++) |
142 | if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize) |
143 | break; |
144 | |
145 | if (i == fe->nfonts) { |
146 | if (fe->fontsize <= fe->nfonts) { |
147 | fe->fontsize = fe->nfonts + 10; |
148 | fe->fonts = sresize(fe->fonts, fe->fontsize, struct font); |
149 | } |
150 | |
151 | fe->nfonts++; |
152 | |
153 | fe->fonts[i].type = fonttype; |
154 | fe->fonts[i].size = fontsize; |
155 | |
156 | /* |
157 | * FIXME: Really I should make at least _some_ effort to |
158 | * pick the correct font. |
159 | */ |
160 | fe->fonts[i].font = gdk_font_load("variable"); |
161 | } |
162 | |
163 | /* |
164 | * Find string dimensions and process alignment. |
165 | */ |
166 | { |
167 | int lb, rb, wid, asc, desc; |
168 | |
169 | gdk_string_extents(fe->fonts[i].font, text, |
170 | &lb, &rb, &wid, &asc, &desc); |
171 | if (align & ALIGN_VCENTRE) |
172 | y += asc - (asc+desc)/2; |
173 | |
174 | if (align & ALIGN_HCENTRE) |
175 | x -= wid / 2; |
176 | else if (align & ALIGN_HRIGHT) |
177 | x -= wid; |
178 | |
179 | } |
180 | |
181 | /* |
182 | * Set colour and actually draw text. |
183 | */ |
184 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
185 | gdk_draw_string(fe->pixmap, fe->fonts[i].font, fe->gc, x, y, text); |
186 | } |
187 | |
2ef96bd6 |
188 | void draw_rect(frontend *fe, int x, int y, int w, int h, int colour) |
189 | { |
190 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
191 | gdk_draw_rectangle(fe->pixmap, fe->gc, 1, x, y, w, h); |
192 | } |
193 | |
194 | void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour) |
195 | { |
196 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
197 | gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2); |
198 | } |
199 | |
200 | void draw_polygon(frontend *fe, int *coords, int npoints, |
201 | int fill, int colour) |
202 | { |
203 | GdkPoint *points = snewn(npoints, GdkPoint); |
204 | int i; |
205 | |
206 | for (i = 0; i < npoints; i++) { |
207 | points[i].x = coords[i*2]; |
208 | points[i].y = coords[i*2+1]; |
209 | } |
210 | |
211 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
212 | gdk_draw_polygon(fe->pixmap, fe->gc, fill, points, npoints); |
213 | |
214 | sfree(points); |
215 | } |
216 | |
217 | void draw_update(frontend *fe, int x, int y, int w, int h) |
218 | { |
219 | if (fe->bbox_l > x ) fe->bbox_l = x ; |
220 | if (fe->bbox_r < x+w) fe->bbox_r = x+w; |
221 | if (fe->bbox_u > y ) fe->bbox_u = y ; |
222 | if (fe->bbox_d < y+h) fe->bbox_d = y+h; |
223 | } |
224 | |
225 | void end_draw(frontend *fe) |
226 | { |
227 | gdk_gc_unref(fe->gc); |
228 | fe->gc = NULL; |
229 | |
230 | if (fe->bbox_l < fe->bbox_r && fe->bbox_u < fe->bbox_d) { |
231 | gdk_draw_pixmap(fe->area->window, |
232 | fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)], |
233 | fe->pixmap, |
234 | fe->bbox_l, fe->bbox_u, |
235 | fe->bbox_l, fe->bbox_u, |
236 | fe->bbox_r - fe->bbox_l, fe->bbox_d - fe->bbox_u); |
237 | } |
238 | } |
239 | |
83680571 |
240 | static void destroy(GtkWidget *widget, gpointer data) |
241 | { |
f49650a7 |
242 | frontend *fe = (frontend *)data; |
243 | deactivate_timer(fe); |
83680571 |
244 | gtk_main_quit(); |
245 | } |
246 | |
2ef96bd6 |
247 | static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) |
7f77ea24 |
248 | { |
2ef96bd6 |
249 | frontend *fe = (frontend *)data; |
1482ee76 |
250 | int keyval; |
7f77ea24 |
251 | |
2ef96bd6 |
252 | if (!fe->pixmap) |
253 | return TRUE; |
7f77ea24 |
254 | |
1482ee76 |
255 | if (event->string[0] && !event->string[1]) |
256 | keyval = (unsigned char)event->string[0]; |
c71454c0 |
257 | else if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up || |
258 | event->keyval == GDK_KP_8) |
1482ee76 |
259 | keyval = CURSOR_UP; |
c71454c0 |
260 | else if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down || |
261 | event->keyval == GDK_KP_2) |
1482ee76 |
262 | keyval = CURSOR_DOWN; |
c71454c0 |
263 | else if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left || |
264 | event->keyval == GDK_KP_4) |
1482ee76 |
265 | keyval = CURSOR_LEFT; |
c71454c0 |
266 | else if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right || |
267 | event->keyval == GDK_KP_6) |
1482ee76 |
268 | keyval = CURSOR_RIGHT; |
c71454c0 |
269 | else if (event->keyval == GDK_KP_Home || event->keyval == GDK_KP_7) |
270 | keyval = CURSOR_UP_LEFT; |
271 | else if (event->keyval == GDK_KP_End || event->keyval == GDK_KP_1) |
272 | keyval = CURSOR_DOWN_LEFT; |
273 | else if (event->keyval == GDK_KP_Page_Up || event->keyval == GDK_KP_9) |
274 | keyval = CURSOR_UP_RIGHT; |
275 | else if (event->keyval == GDK_KP_Page_Down || event->keyval == GDK_KP_3) |
276 | keyval = CURSOR_DOWN_RIGHT; |
1482ee76 |
277 | else |
278 | keyval = -1; |
279 | |
280 | if (keyval >= 0 && |
281 | !midend_process_key(fe->me, 0, 0, keyval)) |
2ef96bd6 |
282 | gtk_widget_destroy(fe->window); |
7f77ea24 |
283 | |
284 | return TRUE; |
285 | } |
286 | |
2ef96bd6 |
287 | static gint button_event(GtkWidget *widget, GdkEventButton *event, |
288 | gpointer data) |
7f77ea24 |
289 | { |
2ef96bd6 |
290 | frontend *fe = (frontend *)data; |
291 | int button; |
292 | |
293 | if (!fe->pixmap) |
294 | return TRUE; |
295 | |
74a4e547 |
296 | if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) |
2ef96bd6 |
297 | return TRUE; |
7f77ea24 |
298 | |
b0f06719 |
299 | if (event->button == 2 || (event->state & GDK_SHIFT_MASK)) |
2ef96bd6 |
300 | button = MIDDLE_BUTTON; |
b0f06719 |
301 | else if (event->button == 1) |
302 | button = LEFT_BUTTON; |
2ef96bd6 |
303 | else if (event->button == 3) |
304 | button = RIGHT_BUTTON; |
305 | else |
306 | return FALSE; /* don't even know what button! */ |
307 | |
74a4e547 |
308 | if (event->type == GDK_BUTTON_RELEASE) |
309 | button += LEFT_RELEASE - LEFT_BUTTON; |
310 | |
311 | if (!midend_process_key(fe->me, event->x, event->y, button)) |
312 | gtk_widget_destroy(fe->window); |
313 | |
314 | return TRUE; |
315 | } |
316 | |
317 | static gint motion_event(GtkWidget *widget, GdkEventMotion *event, |
318 | gpointer data) |
319 | { |
320 | frontend *fe = (frontend *)data; |
321 | int button; |
322 | |
323 | if (!fe->pixmap) |
324 | return TRUE; |
325 | |
74a4e547 |
326 | if (event->state & (GDK_BUTTON2_MASK | GDK_SHIFT_MASK)) |
327 | button = MIDDLE_DRAG; |
328 | else if (event->state & GDK_BUTTON1_MASK) |
329 | button = LEFT_DRAG; |
330 | else if (event->state & GDK_BUTTON3_MASK) |
331 | button = RIGHT_DRAG; |
332 | else |
333 | return FALSE; /* don't even know what button! */ |
334 | |
2ef96bd6 |
335 | if (!midend_process_key(fe->me, event->x, event->y, button)) |
336 | gtk_widget_destroy(fe->window); |
7f77ea24 |
337 | |
338 | return TRUE; |
339 | } |
340 | |
2ef96bd6 |
341 | static gint expose_area(GtkWidget *widget, GdkEventExpose *event, |
342 | gpointer data) |
83680571 |
343 | { |
2ef96bd6 |
344 | frontend *fe = (frontend *)data; |
345 | |
346 | if (fe->pixmap) { |
347 | gdk_draw_pixmap(widget->window, |
348 | widget->style->fg_gc[GTK_WIDGET_STATE(widget)], |
349 | fe->pixmap, |
350 | event->area.x, event->area.y, |
351 | event->area.x, event->area.y, |
352 | event->area.width, event->area.height); |
353 | } |
354 | return TRUE; |
355 | } |
356 | |
fd1a1a2b |
357 | static gint map_window(GtkWidget *widget, GdkEvent *event, |
358 | gpointer data) |
359 | { |
360 | frontend *fe = (frontend *)data; |
361 | |
362 | /* |
363 | * Apparently we need to do this because otherwise the status |
364 | * bar will fail to update immediately. Annoying, but there we |
365 | * go. |
366 | */ |
367 | gtk_widget_queue_draw(fe->window); |
368 | |
369 | return TRUE; |
370 | } |
371 | |
2ef96bd6 |
372 | static gint configure_area(GtkWidget *widget, |
373 | GdkEventConfigure *event, gpointer data) |
374 | { |
375 | frontend *fe = (frontend *)data; |
376 | GdkGC *gc; |
377 | |
eb2ad6f1 |
378 | if (fe->pixmap) |
379 | gdk_pixmap_unref(fe->pixmap); |
380 | |
2ef96bd6 |
381 | fe->pixmap = gdk_pixmap_new(widget->window, fe->w, fe->h, -1); |
382 | |
383 | gc = gdk_gc_new(fe->area->window); |
384 | gdk_gc_set_foreground(gc, &fe->colours[0]); |
385 | gdk_draw_rectangle(fe->pixmap, gc, 1, 0, 0, fe->w, fe->h); |
386 | gdk_gc_unref(gc); |
387 | |
388 | midend_redraw(fe->me); |
389 | |
390 | return TRUE; |
391 | } |
392 | |
393 | static gint timer_func(gpointer data) |
394 | { |
395 | frontend *fe = (frontend *)data; |
396 | |
8c1fd974 |
397 | if (fe->timer_active) { |
398 | struct timeval now; |
399 | float elapsed; |
400 | gettimeofday(&now, NULL); |
401 | elapsed = ((now.tv_usec - fe->last_time.tv_usec) * 0.000001F + |
402 | (now.tv_sec - fe->last_time.tv_sec)); |
403 | midend_timer(fe->me, elapsed); /* may clear timer_active */ |
404 | fe->last_time = now; |
405 | } |
2ef96bd6 |
406 | |
407 | return fe->timer_active; |
408 | } |
409 | |
410 | void deactivate_timer(frontend *fe) |
411 | { |
20ee89e3 |
412 | if (fe->timer_active) |
413 | gtk_timeout_remove(fe->timer_id); |
2ef96bd6 |
414 | fe->timer_active = FALSE; |
415 | } |
416 | |
417 | void activate_timer(frontend *fe) |
418 | { |
8c1fd974 |
419 | if (!fe->timer_active) { |
20ee89e3 |
420 | fe->timer_id = gtk_timeout_add(20, timer_func, fe); |
8c1fd974 |
421 | gettimeofday(&fe->last_time, NULL); |
422 | } |
2ef96bd6 |
423 | fe->timer_active = TRUE; |
424 | } |
425 | |
c8230524 |
426 | static void window_destroy(GtkWidget *widget, gpointer data) |
427 | { |
428 | gtk_main_quit(); |
429 | } |
430 | |
431 | static void errmsg_button_clicked(GtkButton *button, gpointer data) |
432 | { |
433 | gtk_widget_destroy(GTK_WIDGET(data)); |
434 | } |
435 | |
a163b7c2 |
436 | static int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) |
437 | { |
438 | GtkObject *cancelbutton = GTK_OBJECT(data); |
439 | |
440 | /* |
441 | * `Escape' effectively clicks the cancel button |
442 | */ |
443 | if (event->keyval == GDK_Escape) { |
444 | gtk_signal_emit_by_name(GTK_OBJECT(cancelbutton), "clicked"); |
445 | return TRUE; |
446 | } |
447 | |
448 | return FALSE; |
449 | } |
450 | |
c8230524 |
451 | void error_box(GtkWidget *parent, char *msg) |
452 | { |
453 | GtkWidget *window, *hbox, *text, *ok; |
454 | |
455 | window = gtk_dialog_new(); |
456 | text = gtk_label_new(msg); |
457 | gtk_misc_set_alignment(GTK_MISC(text), 0.0, 0.0); |
458 | hbox = gtk_hbox_new(FALSE, 0); |
459 | gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20); |
460 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), |
461 | hbox, FALSE, FALSE, 20); |
462 | gtk_widget_show(text); |
463 | gtk_widget_show(hbox); |
464 | gtk_window_set_title(GTK_WINDOW(window), "Error"); |
465 | gtk_label_set_line_wrap(GTK_LABEL(text), TRUE); |
466 | ok = gtk_button_new_with_label("OK"); |
467 | gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area), |
468 | ok, FALSE, FALSE, 0); |
469 | gtk_widget_show(ok); |
470 | GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT); |
471 | gtk_window_set_default(GTK_WINDOW(window), ok); |
472 | gtk_signal_connect(GTK_OBJECT(ok), "clicked", |
473 | GTK_SIGNAL_FUNC(errmsg_button_clicked), window); |
a1ed9f0e |
474 | gtk_signal_connect(GTK_OBJECT(window), "destroy", |
475 | GTK_SIGNAL_FUNC(window_destroy), NULL); |
a163b7c2 |
476 | gtk_signal_connect(GTK_OBJECT(window), "key_press_event", |
477 | GTK_SIGNAL_FUNC(win_key_press), ok); |
c8230524 |
478 | gtk_window_set_modal(GTK_WINDOW(window), TRUE); |
479 | gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent)); |
480 | //set_transient_window_pos(parent, window); |
481 | gtk_widget_show(window); |
482 | gtk_main(); |
483 | } |
484 | |
485 | static void config_ok_button_clicked(GtkButton *button, gpointer data) |
486 | { |
487 | frontend *fe = (frontend *)data; |
488 | char *err; |
489 | |
5928817c |
490 | err = midend_set_config(fe->me, fe->cfg_which, fe->cfg); |
c8230524 |
491 | |
492 | if (err) |
493 | error_box(fe->cfgbox, err); |
494 | else { |
495 | fe->cfgret = TRUE; |
496 | gtk_widget_destroy(fe->cfgbox); |
497 | } |
498 | } |
499 | |
500 | static void config_cancel_button_clicked(GtkButton *button, gpointer data) |
501 | { |
502 | frontend *fe = (frontend *)data; |
503 | |
504 | gtk_widget_destroy(fe->cfgbox); |
505 | } |
506 | |
a163b7c2 |
507 | static int editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data) |
508 | { |
509 | /* |
510 | * GtkEntry has a nasty habit of eating the Return key, which |
511 | * is unhelpful since it doesn't actually _do_ anything with it |
512 | * (it calls gtk_widget_activate, but our edit boxes never need |
513 | * activating). So I catch Return before GtkEntry sees it, and |
514 | * pass it straight on to the parent widget. Effect: hitting |
515 | * Return in an edit box will now activate the default button |
516 | * in the dialog just like it will everywhere else. |
517 | */ |
518 | if (event->keyval == GDK_Return && widget->parent != NULL) { |
519 | gint return_val; |
520 | gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event"); |
521 | gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event", |
522 | event, &return_val); |
523 | return return_val; |
524 | } |
525 | return FALSE; |
526 | } |
527 | |
c8230524 |
528 | static void editbox_changed(GtkEditable *ed, gpointer data) |
529 | { |
530 | config_item *i = (config_item *)data; |
531 | |
532 | sfree(i->sval); |
533 | i->sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed))); |
534 | } |
535 | |
536 | static void button_toggled(GtkToggleButton *tb, gpointer data) |
537 | { |
538 | config_item *i = (config_item *)data; |
539 | |
540 | i->ival = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb)); |
541 | } |
542 | |
543 | static void droplist_sel(GtkMenuItem *item, gpointer data) |
544 | { |
545 | config_item *i = (config_item *)data; |
546 | |
547 | i->ival = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), |
548 | "user-data")); |
549 | } |
550 | |
5928817c |
551 | static int get_config(frontend *fe, int which) |
c8230524 |
552 | { |
a163b7c2 |
553 | GtkWidget *w, *table, *cancel; |
5928817c |
554 | char *title; |
c8230524 |
555 | config_item *i; |
556 | int y; |
557 | |
5928817c |
558 | fe->cfg = midend_get_config(fe->me, which, &title); |
559 | fe->cfg_which = which; |
c8230524 |
560 | fe->cfgret = FALSE; |
561 | |
562 | fe->cfgbox = gtk_dialog_new(); |
5928817c |
563 | gtk_window_set_title(GTK_WINDOW(fe->cfgbox), title); |
564 | sfree(title); |
c8230524 |
565 | |
566 | w = gtk_button_new_with_label("OK"); |
567 | gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->action_area), |
568 | w, FALSE, FALSE, 0); |
569 | gtk_widget_show(w); |
570 | GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); |
571 | gtk_window_set_default(GTK_WINDOW(fe->cfgbox), w); |
572 | gtk_signal_connect(GTK_OBJECT(w), "clicked", |
573 | GTK_SIGNAL_FUNC(config_ok_button_clicked), fe); |
574 | |
575 | w = gtk_button_new_with_label("Cancel"); |
576 | gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->action_area), |
577 | w, FALSE, FALSE, 0); |
578 | gtk_widget_show(w); |
579 | gtk_signal_connect(GTK_OBJECT(w), "clicked", |
580 | GTK_SIGNAL_FUNC(config_cancel_button_clicked), fe); |
a163b7c2 |
581 | cancel = w; |
c8230524 |
582 | |
583 | table = gtk_table_new(1, 2, FALSE); |
584 | y = 0; |
585 | gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->vbox), |
586 | table, FALSE, FALSE, 0); |
587 | gtk_widget_show(table); |
588 | |
95709966 |
589 | for (i = fe->cfg; i->type != C_END; i++) { |
c8230524 |
590 | gtk_table_resize(GTK_TABLE(table), y+1, 2); |
591 | |
592 | switch (i->type) { |
95709966 |
593 | case C_STRING: |
c8230524 |
594 | /* |
595 | * Edit box with a label beside it. |
596 | */ |
597 | |
598 | w = gtk_label_new(i->name); |
599 | gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5); |
600 | gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1, |
601 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
602 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
603 | 3, 3); |
604 | gtk_widget_show(w); |
605 | |
606 | w = gtk_entry_new(); |
607 | gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1, |
608 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
609 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
610 | 3, 3); |
611 | gtk_entry_set_text(GTK_ENTRY(w), i->sval); |
612 | gtk_signal_connect(GTK_OBJECT(w), "changed", |
613 | GTK_SIGNAL_FUNC(editbox_changed), i); |
a163b7c2 |
614 | gtk_signal_connect(GTK_OBJECT(w), "key_press_event", |
615 | GTK_SIGNAL_FUNC(editbox_key), NULL); |
c8230524 |
616 | gtk_widget_show(w); |
617 | |
618 | break; |
619 | |
95709966 |
620 | case C_BOOLEAN: |
c8230524 |
621 | /* |
622 | * Simple checkbox. |
623 | */ |
624 | w = gtk_check_button_new_with_label(i->name); |
625 | gtk_signal_connect(GTK_OBJECT(w), "toggled", |
626 | GTK_SIGNAL_FUNC(button_toggled), i); |
627 | gtk_table_attach(GTK_TABLE(table), w, 0, 2, y, y+1, |
628 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
629 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
630 | 3, 3); |
631 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), i->ival); |
632 | gtk_widget_show(w); |
633 | break; |
634 | |
95709966 |
635 | case C_CHOICES: |
c8230524 |
636 | /* |
637 | * Drop-down list (GtkOptionMenu). |
638 | */ |
639 | |
640 | w = gtk_label_new(i->name); |
641 | gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5); |
642 | gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1, |
643 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
644 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
645 | 3, 3); |
646 | gtk_widget_show(w); |
647 | |
648 | w = gtk_option_menu_new(); |
649 | gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1, |
650 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
651 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
652 | 3, 3); |
653 | gtk_widget_show(w); |
654 | |
655 | { |
656 | int c, val; |
657 | char *p, *q, *name; |
658 | GtkWidget *menuitem; |
659 | GtkWidget *menu = gtk_menu_new(); |
660 | |
661 | gtk_option_menu_set_menu(GTK_OPTION_MENU(w), menu); |
662 | |
663 | c = *i->sval; |
664 | p = i->sval+1; |
665 | val = 0; |
666 | |
667 | while (*p) { |
668 | q = p; |
669 | while (*q && *q != c) |
670 | q++; |
671 | |
672 | name = snewn(q-p+1, char); |
673 | strncpy(name, p, q-p); |
674 | name[q-p] = '\0'; |
675 | |
676 | if (*q) q++; /* eat delimiter */ |
677 | |
678 | menuitem = gtk_menu_item_new_with_label(name); |
679 | gtk_container_add(GTK_CONTAINER(menu), menuitem); |
680 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", |
681 | GINT_TO_POINTER(val)); |
682 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
683 | GTK_SIGNAL_FUNC(droplist_sel), i); |
684 | gtk_widget_show(menuitem); |
685 | |
686 | val++; |
687 | |
688 | p = q; |
689 | } |
690 | |
691 | gtk_option_menu_set_history(GTK_OPTION_MENU(w), i->ival); |
692 | } |
693 | |
694 | break; |
695 | } |
696 | |
697 | y++; |
698 | } |
699 | |
700 | gtk_signal_connect(GTK_OBJECT(fe->cfgbox), "destroy", |
701 | GTK_SIGNAL_FUNC(window_destroy), NULL); |
a163b7c2 |
702 | gtk_signal_connect(GTK_OBJECT(fe->cfgbox), "key_press_event", |
703 | GTK_SIGNAL_FUNC(win_key_press), cancel); |
c8230524 |
704 | gtk_window_set_modal(GTK_WINDOW(fe->cfgbox), TRUE); |
705 | gtk_window_set_transient_for(GTK_WINDOW(fe->cfgbox), |
706 | GTK_WINDOW(fe->window)); |
707 | //set_transient_window_pos(fe->window, fe->cfgbox); |
708 | gtk_widget_show(fe->cfgbox); |
709 | gtk_main(); |
710 | |
077f3cbe |
711 | free_cfg(fe->cfg); |
c8230524 |
712 | |
713 | return fe->cfgret; |
714 | } |
715 | |
eb2ad6f1 |
716 | static void menu_key_event(GtkMenuItem *menuitem, gpointer data) |
717 | { |
718 | frontend *fe = (frontend *)data; |
719 | int key = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem), |
720 | "user-data")); |
721 | if (!midend_process_key(fe->me, 0, 0, key)) |
722 | gtk_widget_destroy(fe->window); |
723 | } |
724 | |
725 | static void menu_preset_event(GtkMenuItem *menuitem, gpointer data) |
726 | { |
727 | frontend *fe = (frontend *)data; |
728 | game_params *params = |
729 | (game_params *)gtk_object_get_data(GTK_OBJECT(menuitem), "user-data"); |
730 | int x, y; |
731 | |
732 | midend_set_params(fe->me, params); |
5928817c |
733 | midend_new_game(fe->me); |
eb2ad6f1 |
734 | midend_size(fe->me, &x, &y); |
735 | gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); |
736 | fe->w = x; |
737 | fe->h = y; |
738 | } |
739 | |
c8230524 |
740 | static void menu_config_event(GtkMenuItem *menuitem, gpointer data) |
741 | { |
742 | frontend *fe = (frontend *)data; |
5928817c |
743 | int which = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem), |
744 | "user-data")); |
c8230524 |
745 | int x, y; |
746 | |
5928817c |
747 | if (!get_config(fe, which)) |
c8230524 |
748 | return; |
749 | |
5928817c |
750 | midend_new_game(fe->me); |
c8230524 |
751 | midend_size(fe->me, &x, &y); |
752 | gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); |
753 | fe->w = x; |
754 | fe->h = y; |
755 | } |
756 | |
eb2ad6f1 |
757 | static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont, |
758 | char *text, int key) |
759 | { |
760 | GtkWidget *menuitem = gtk_menu_item_new_with_label(text); |
761 | gtk_container_add(cont, menuitem); |
762 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", |
763 | GINT_TO_POINTER(key)); |
764 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
765 | GTK_SIGNAL_FUNC(menu_key_event), fe); |
766 | gtk_widget_show(menuitem); |
767 | return menuitem; |
768 | } |
769 | |
770 | static void add_menu_separator(GtkContainer *cont) |
771 | { |
772 | GtkWidget *menuitem = gtk_menu_item_new(); |
773 | gtk_container_add(cont, menuitem); |
774 | gtk_widget_show(menuitem); |
775 | } |
776 | |
8b7938e7 |
777 | static frontend *new_window(char *game_id, char **error) |
2ef96bd6 |
778 | { |
779 | frontend *fe; |
eb2ad6f1 |
780 | GtkBox *vbox; |
781 | GtkWidget *menubar, *menu, *menuitem; |
782 | int x, y, n; |
83680571 |
783 | |
2ef96bd6 |
784 | fe = snew(frontend); |
83680571 |
785 | |
be8d5aa1 |
786 | fe->me = midend_new(fe, &thegame); |
8b7938e7 |
787 | if (game_id) { |
788 | *error = midend_game_id(fe->me, game_id, FALSE); |
789 | if (*error) { |
790 | midend_free(fe->me); |
791 | sfree(fe); |
792 | return NULL; |
793 | } |
794 | } |
5928817c |
795 | midend_new_game(fe->me); |
7f77ea24 |
796 | |
2ef96bd6 |
797 | fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
be8d5aa1 |
798 | gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name); |
ff2f787b |
799 | #if 0 |
800 | gtk_window_set_resizable(GTK_WINDOW(fe->window), FALSE); |
801 | #else |
802 | gtk_window_set_policy(GTK_WINDOW(fe->window), FALSE, FALSE, TRUE); |
803 | #endif |
eb2ad6f1 |
804 | vbox = GTK_BOX(gtk_vbox_new(FALSE, 0)); |
805 | gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox)); |
806 | gtk_widget_show(GTK_WIDGET(vbox)); |
807 | |
808 | menubar = gtk_menu_bar_new(); |
809 | gtk_box_pack_start(vbox, menubar, FALSE, FALSE, 0); |
810 | gtk_widget_show(menubar); |
811 | |
812 | menuitem = gtk_menu_item_new_with_label("Game"); |
813 | gtk_container_add(GTK_CONTAINER(menubar), menuitem); |
814 | gtk_widget_show(menuitem); |
815 | |
816 | menu = gtk_menu_new(); |
817 | gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); |
818 | |
819 | add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n'); |
820 | add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Restart", 'r'); |
821 | |
5928817c |
822 | menuitem = gtk_menu_item_new_with_label("Specific..."); |
823 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", |
824 | GINT_TO_POINTER(CFG_SEED)); |
825 | gtk_container_add(GTK_CONTAINER(menu), menuitem); |
826 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
827 | GTK_SIGNAL_FUNC(menu_config_event), fe); |
828 | gtk_widget_show(menuitem); |
829 | |
be8d5aa1 |
830 | if ((n = midend_num_presets(fe->me)) > 0 || thegame.can_configure) { |
eb2ad6f1 |
831 | GtkWidget *submenu; |
832 | int i; |
833 | |
834 | menuitem = gtk_menu_item_new_with_label("Type"); |
c8230524 |
835 | gtk_container_add(GTK_CONTAINER(menubar), menuitem); |
eb2ad6f1 |
836 | gtk_widget_show(menuitem); |
837 | |
838 | submenu = gtk_menu_new(); |
839 | gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu); |
840 | |
841 | for (i = 0; i < n; i++) { |
842 | char *name; |
843 | game_params *params; |
844 | |
845 | midend_fetch_preset(fe->me, i, &name, ¶ms); |
846 | |
847 | menuitem = gtk_menu_item_new_with_label(name); |
848 | gtk_container_add(GTK_CONTAINER(submenu), menuitem); |
849 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", params); |
850 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
851 | GTK_SIGNAL_FUNC(menu_preset_event), fe); |
852 | gtk_widget_show(menuitem); |
853 | } |
c8230524 |
854 | |
be8d5aa1 |
855 | if (thegame.can_configure) { |
c8230524 |
856 | menuitem = gtk_menu_item_new_with_label("Custom..."); |
5928817c |
857 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", |
858 | GPOINTER_TO_INT(CFG_SETTINGS)); |
c8230524 |
859 | gtk_container_add(GTK_CONTAINER(submenu), menuitem); |
860 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
861 | GTK_SIGNAL_FUNC(menu_config_event), fe); |
862 | gtk_widget_show(menuitem); |
863 | } |
eb2ad6f1 |
864 | } |
865 | |
866 | add_menu_separator(GTK_CONTAINER(menu)); |
867 | add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u'); |
868 | add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", '\x12'); |
869 | add_menu_separator(GTK_CONTAINER(menu)); |
870 | add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q'); |
7f77ea24 |
871 | |
2ef96bd6 |
872 | { |
873 | int i, ncolours; |
874 | float *colours; |
875 | gboolean *success; |
7f77ea24 |
876 | |
2ef96bd6 |
877 | fe->colmap = gdk_colormap_get_system(); |
878 | colours = midend_colours(fe->me, &ncolours); |
879 | fe->ncolours = ncolours; |
880 | fe->colours = snewn(ncolours, GdkColor); |
881 | for (i = 0; i < ncolours; i++) { |
882 | fe->colours[i].red = colours[i*3] * 0xFFFF; |
883 | fe->colours[i].green = colours[i*3+1] * 0xFFFF; |
884 | fe->colours[i].blue = colours[i*3+2] * 0xFFFF; |
885 | } |
886 | success = snewn(ncolours, gboolean); |
887 | gdk_colormap_alloc_colors(fe->colmap, fe->colours, ncolours, |
888 | FALSE, FALSE, success); |
889 | for (i = 0; i < ncolours; i++) { |
890 | if (!success[i]) |
891 | g_error("couldn't allocate colour %d (#%02x%02x%02x)\n", |
892 | i, fe->colours[i].red >> 8, |
893 | fe->colours[i].green >> 8, |
894 | fe->colours[i].blue >> 8); |
895 | } |
896 | } |
7f77ea24 |
897 | |
fd1a1a2b |
898 | if (midend_wants_statusbar(fe->me)) { |
5725d728 |
899 | GtkWidget *viewport; |
900 | GtkRequisition req; |
901 | |
902 | viewport = gtk_viewport_new(NULL, NULL); |
903 | gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE); |
fd1a1a2b |
904 | fe->statusbar = gtk_statusbar_new(); |
5725d728 |
905 | gtk_container_add(GTK_CONTAINER(viewport), fe->statusbar); |
906 | gtk_widget_show(viewport); |
907 | gtk_box_pack_end(vbox, viewport, FALSE, FALSE, 0); |
fd1a1a2b |
908 | gtk_widget_show(fe->statusbar); |
909 | fe->statusctx = gtk_statusbar_get_context_id |
910 | (GTK_STATUSBAR(fe->statusbar), "game"); |
911 | gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx, |
5725d728 |
912 | "test"); |
913 | gtk_widget_size_request(fe->statusbar, &req); |
914 | #if 0 |
915 | /* For GTK 2.0, should we be using gtk_widget_set_size_request? */ |
916 | #endif |
60d42abc |
917 | gtk_widget_set_usize(viewport, -1, req.height); |
fd1a1a2b |
918 | } else |
919 | fe->statusbar = NULL; |
920 | |
2ef96bd6 |
921 | fe->area = gtk_drawing_area_new(); |
922 | midend_size(fe->me, &x, &y); |
923 | gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); |
924 | fe->w = x; |
925 | fe->h = y; |
926 | |
eb2ad6f1 |
927 | gtk_box_pack_end(vbox, fe->area, FALSE, FALSE, 0); |
2ef96bd6 |
928 | |
929 | fe->pixmap = NULL; |
4efb3868 |
930 | fe->fonts = NULL; |
931 | fe->nfonts = fe->fontsize = 0; |
2ef96bd6 |
932 | |
20ee89e3 |
933 | fe->timer_active = FALSE; |
934 | |
2ef96bd6 |
935 | gtk_signal_connect(GTK_OBJECT(fe->window), "destroy", |
936 | GTK_SIGNAL_FUNC(destroy), fe); |
937 | gtk_signal_connect(GTK_OBJECT(fe->window), "key_press_event", |
938 | GTK_SIGNAL_FUNC(key_event), fe); |
939 | gtk_signal_connect(GTK_OBJECT(fe->area), "button_press_event", |
940 | GTK_SIGNAL_FUNC(button_event), fe); |
74a4e547 |
941 | gtk_signal_connect(GTK_OBJECT(fe->area), "button_release_event", |
942 | GTK_SIGNAL_FUNC(button_event), fe); |
943 | gtk_signal_connect(GTK_OBJECT(fe->area), "motion_notify_event", |
944 | GTK_SIGNAL_FUNC(motion_event), fe); |
2ef96bd6 |
945 | gtk_signal_connect(GTK_OBJECT(fe->area), "expose_event", |
946 | GTK_SIGNAL_FUNC(expose_area), fe); |
fd1a1a2b |
947 | gtk_signal_connect(GTK_OBJECT(fe->window), "map_event", |
948 | GTK_SIGNAL_FUNC(map_window), fe); |
2ef96bd6 |
949 | gtk_signal_connect(GTK_OBJECT(fe->area), "configure_event", |
950 | GTK_SIGNAL_FUNC(configure_area), fe); |
951 | |
74a4e547 |
952 | gtk_widget_add_events(GTK_WIDGET(fe->area), |
953 | GDK_BUTTON_PRESS_MASK | |
954 | GDK_BUTTON_RELEASE_MASK | |
955 | GDK_BUTTON_MOTION_MASK); |
2ef96bd6 |
956 | |
957 | gtk_widget_show(fe->area); |
958 | gtk_widget_show(fe->window); |
959 | |
960 | return fe; |
83680571 |
961 | } |
962 | |
963 | int main(int argc, char **argv) |
964 | { |
8b7938e7 |
965 | char *pname = argv[0]; |
966 | char *error; |
967 | |
83680571 |
968 | gtk_init(&argc, &argv); |
8b7938e7 |
969 | |
970 | if (!new_window(argc > 1 ? argv[1] : NULL, &error)) { |
971 | fprintf(stderr, "%s: %s\n", pname, error); |
972 | return 1; |
973 | } |
974 | |
83680571 |
975 | gtk_main(); |
976 | |
977 | return 0; |
978 | } |