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 | |
a9668451 |
17 | #include <gdk/gdkx.h> |
18 | #include <X11/Xlib.h> |
9b4b03d3 |
19 | #include <X11/Xutil.h> |
20 | #include <X11/Xatom.h> |
a9668451 |
21 | |
720a8fb7 |
22 | #include "puzzles.h" |
23 | |
83680571 |
24 | /* ---------------------------------------------------------------------- |
25 | * Error reporting functions used elsewhere. |
26 | */ |
27 | |
720a8fb7 |
28 | void fatal(char *fmt, ...) |
29 | { |
30 | va_list ap; |
31 | |
32 | fprintf(stderr, "fatal error: "); |
33 | |
34 | va_start(ap, fmt); |
35 | vfprintf(stderr, fmt, ap); |
36 | va_end(ap); |
37 | |
38 | fprintf(stderr, "\n"); |
39 | exit(1); |
40 | } |
83680571 |
41 | |
42 | /* ---------------------------------------------------------------------- |
43 | * GTK front end to puzzles. |
44 | */ |
45 | |
4efb3868 |
46 | struct font { |
47 | GdkFont *font; |
48 | int type; |
49 | int size; |
50 | }; |
51 | |
83680571 |
52 | /* |
53 | * This structure holds all the data relevant to a single window. |
54 | * In principle this would allow us to open multiple independent |
55 | * puzzle windows, although I can't currently see any real point in |
56 | * doing so. I'm just coding cleanly because there's no |
57 | * particularly good reason not to. |
58 | */ |
2ef96bd6 |
59 | struct frontend { |
83680571 |
60 | GtkWidget *window; |
7f77ea24 |
61 | GtkWidget *area; |
fd1a1a2b |
62 | GtkWidget *statusbar; |
63 | guint statusctx; |
2ef96bd6 |
64 | GdkPixmap *pixmap; |
65 | GdkColor *colours; |
66 | int ncolours; |
67 | GdkColormap *colmap; |
68 | int w, h; |
7f77ea24 |
69 | midend_data *me; |
2ef96bd6 |
70 | GdkGC *gc; |
71 | int bbox_l, bbox_r, bbox_u, bbox_d; |
20ee89e3 |
72 | int timer_active, timer_id; |
8c1fd974 |
73 | struct timeval last_time; |
4efb3868 |
74 | struct font *fonts; |
75 | int nfonts, fontsize; |
c8230524 |
76 | config_item *cfg; |
5928817c |
77 | int cfg_which, cfgret; |
c8230524 |
78 | GtkWidget *cfgbox; |
9b4b03d3 |
79 | char *paste_data; |
80 | int paste_data_len; |
f5f4a1f1 |
81 | char *laststatus; |
83680571 |
82 | }; |
83 | |
cbb5549e |
84 | void get_random_seed(void **randseed, int *randseedsize) |
85 | { |
102d0d14 |
86 | struct timeval *tvp = snew(struct timeval); |
87 | gettimeofday(tvp, NULL); |
88 | *randseed = (void *)tvp; |
89 | *randseedsize = sizeof(struct timeval); |
cbb5549e |
90 | } |
91 | |
2ef96bd6 |
92 | void frontend_default_colour(frontend *fe, float *output) |
93 | { |
94 | GdkColor col = fe->window->style->bg[GTK_STATE_NORMAL]; |
95 | output[0] = col.red / 65535.0; |
96 | output[1] = col.green / 65535.0; |
97 | output[2] = col.blue / 65535.0; |
98 | } |
99 | |
fd1a1a2b |
100 | void status_bar(frontend *fe, char *text) |
101 | { |
48dcdd62 |
102 | char *rewritten; |
103 | |
fd1a1a2b |
104 | assert(fe->statusbar); |
105 | |
48dcdd62 |
106 | rewritten = midend_rewrite_statusbar(fe->me, text); |
f5f4a1f1 |
107 | if (!fe->laststatus || strcmp(rewritten, fe->laststatus)) { |
108 | gtk_statusbar_pop(GTK_STATUSBAR(fe->statusbar), fe->statusctx); |
109 | gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx, rewritten); |
110 | sfree(fe->laststatus); |
111 | fe->laststatus = rewritten; |
112 | } else { |
113 | sfree(rewritten); |
114 | } |
fd1a1a2b |
115 | } |
116 | |
2ef96bd6 |
117 | void start_draw(frontend *fe) |
118 | { |
119 | fe->gc = gdk_gc_new(fe->area->window); |
120 | fe->bbox_l = fe->w; |
121 | fe->bbox_r = 0; |
122 | fe->bbox_u = fe->h; |
123 | fe->bbox_d = 0; |
124 | } |
125 | |
4efb3868 |
126 | void clip(frontend *fe, int x, int y, int w, int h) |
127 | { |
128 | GdkRectangle rect; |
129 | |
130 | rect.x = x; |
131 | rect.y = y; |
132 | rect.width = w; |
133 | rect.height = h; |
134 | |
135 | gdk_gc_set_clip_rectangle(fe->gc, &rect); |
136 | } |
137 | |
138 | void unclip(frontend *fe) |
139 | { |
140 | GdkRectangle rect; |
141 | |
142 | rect.x = 0; |
143 | rect.y = 0; |
144 | rect.width = fe->w; |
145 | rect.height = fe->h; |
146 | |
147 | gdk_gc_set_clip_rectangle(fe->gc, &rect); |
148 | } |
149 | |
150 | void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize, |
151 | int align, int colour, char *text) |
152 | { |
153 | int i; |
154 | |
155 | /* |
156 | * Find or create the font. |
157 | */ |
158 | for (i = 0; i < fe->nfonts; i++) |
159 | if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize) |
160 | break; |
161 | |
162 | if (i == fe->nfonts) { |
163 | if (fe->fontsize <= fe->nfonts) { |
164 | fe->fontsize = fe->nfonts + 10; |
165 | fe->fonts = sresize(fe->fonts, fe->fontsize, struct font); |
166 | } |
167 | |
168 | fe->nfonts++; |
169 | |
170 | fe->fonts[i].type = fonttype; |
171 | fe->fonts[i].size = fontsize; |
172 | |
a9668451 |
173 | #if GTK_CHECK_VERSION(2,0,0) |
4efb3868 |
174 | /* |
a9668451 |
175 | * Use Pango to find the closest match to the requested |
176 | * font. |
4efb3868 |
177 | */ |
a9668451 |
178 | { |
179 | PangoFontDescription *fd; |
180 | |
181 | fd = pango_font_description_new(); |
182 | /* `Monospace' and `Sans' are meta-families guaranteed to exist */ |
183 | pango_font_description_set_family(fd, fonttype == FONT_FIXED ? |
184 | "Monospace" : "Sans"); |
2e837d78 |
185 | pango_font_description_set_weight(fd, PANGO_WEIGHT_BOLD); |
a9668451 |
186 | /* |
187 | * I found some online Pango documentation which |
188 | * described a function called |
189 | * pango_font_description_set_absolute_size(), which is |
190 | * _exactly_ what I want here. Unfortunately, none of |
191 | * my local Pango installations have it (presumably |
192 | * they're too old), so I'm going to have to hack round |
193 | * it by figuring out the point size myself. This |
194 | * limits me to X and probably also breaks in later |
195 | * Pango installations, so ideally I should add another |
196 | * CHECK_VERSION type ifdef and use set_absolute_size |
197 | * where available. All very annoying. |
198 | */ |
199 | #ifdef HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION |
200 | pango_font_description_set_absolute_size(fd, PANGO_SCALE*fontsize); |
201 | #else |
202 | { |
203 | Display *d = GDK_DISPLAY(); |
204 | int s = DefaultScreen(d); |
205 | double resolution = |
206 | (PANGO_SCALE * 72.27 / 25.4) * |
207 | ((double) DisplayWidthMM(d, s) / DisplayWidth (d, s)); |
208 | pango_font_description_set_size(fd, resolution * fontsize); |
209 | } |
210 | #endif |
211 | fe->fonts[i].font = gdk_font_from_description(fd); |
212 | pango_font_description_free(fd); |
213 | } |
214 | |
215 | if (!fe->fonts[i].font) |
216 | #endif |
217 | /* |
218 | * In GTK 1.2, I don't know of any plausible way to |
219 | * pick a suitable font, so I'm just going to be |
220 | * tedious. |
221 | * |
222 | * This is also fallback code called if the Pango |
223 | * approach fails to find an appropriate font. |
224 | */ |
225 | fe->fonts[i].font = gdk_font_load(fonttype == FONT_FIXED ? |
226 | "fixed" : "variable"); |
4efb3868 |
227 | } |
228 | |
229 | /* |
230 | * Find string dimensions and process alignment. |
231 | */ |
232 | { |
233 | int lb, rb, wid, asc, desc; |
234 | |
3bcf831a |
235 | /* |
236 | * Measure vertical string extents with respect to the same |
237 | * string always... |
238 | */ |
239 | gdk_string_extents(fe->fonts[i].font, |
240 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ", |
4efb3868 |
241 | &lb, &rb, &wid, &asc, &desc); |
242 | if (align & ALIGN_VCENTRE) |
243 | y += asc - (asc+desc)/2; |
244 | |
3bcf831a |
245 | /* |
246 | * ... but horizontal extents with respect to the provided |
247 | * string. This means that multiple pieces of text centred |
248 | * on the same y-coordinate don't have different baselines. |
249 | */ |
250 | gdk_string_extents(fe->fonts[i].font, text, |
251 | &lb, &rb, &wid, &asc, &desc); |
252 | |
4efb3868 |
253 | if (align & ALIGN_HCENTRE) |
254 | x -= wid / 2; |
255 | else if (align & ALIGN_HRIGHT) |
256 | x -= wid; |
257 | |
258 | } |
259 | |
260 | /* |
261 | * Set colour and actually draw text. |
262 | */ |
263 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
264 | gdk_draw_string(fe->pixmap, fe->fonts[i].font, fe->gc, x, y, text); |
265 | } |
266 | |
2ef96bd6 |
267 | void draw_rect(frontend *fe, int x, int y, int w, int h, int colour) |
268 | { |
269 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
270 | gdk_draw_rectangle(fe->pixmap, fe->gc, 1, x, y, w, h); |
271 | } |
272 | |
273 | void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour) |
274 | { |
275 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
276 | gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2); |
277 | } |
278 | |
279 | void draw_polygon(frontend *fe, int *coords, int npoints, |
280 | int fill, int colour) |
281 | { |
282 | GdkPoint *points = snewn(npoints, GdkPoint); |
283 | int i; |
284 | |
285 | for (i = 0; i < npoints; i++) { |
286 | points[i].x = coords[i*2]; |
287 | points[i].y = coords[i*2+1]; |
288 | } |
289 | |
290 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
291 | gdk_draw_polygon(fe->pixmap, fe->gc, fill, points, npoints); |
292 | |
293 | sfree(points); |
294 | } |
295 | |
296 | void draw_update(frontend *fe, int x, int y, int w, int h) |
297 | { |
298 | if (fe->bbox_l > x ) fe->bbox_l = x ; |
299 | if (fe->bbox_r < x+w) fe->bbox_r = x+w; |
300 | if (fe->bbox_u > y ) fe->bbox_u = y ; |
301 | if (fe->bbox_d < y+h) fe->bbox_d = y+h; |
302 | } |
303 | |
304 | void end_draw(frontend *fe) |
305 | { |
306 | gdk_gc_unref(fe->gc); |
307 | fe->gc = NULL; |
308 | |
309 | if (fe->bbox_l < fe->bbox_r && fe->bbox_u < fe->bbox_d) { |
310 | gdk_draw_pixmap(fe->area->window, |
311 | fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)], |
312 | fe->pixmap, |
313 | fe->bbox_l, fe->bbox_u, |
314 | fe->bbox_l, fe->bbox_u, |
315 | fe->bbox_r - fe->bbox_l, fe->bbox_d - fe->bbox_u); |
316 | } |
317 | } |
318 | |
83680571 |
319 | static void destroy(GtkWidget *widget, gpointer data) |
320 | { |
f49650a7 |
321 | frontend *fe = (frontend *)data; |
322 | deactivate_timer(fe); |
83680571 |
323 | gtk_main_quit(); |
324 | } |
325 | |
2ef96bd6 |
326 | static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) |
7f77ea24 |
327 | { |
2ef96bd6 |
328 | frontend *fe = (frontend *)data; |
1482ee76 |
329 | int keyval; |
f0ee053c |
330 | int shift = (event->state & GDK_SHIFT_MASK) ? MOD_SHFT : 0; |
331 | int ctrl = (event->state & GDK_CONTROL_MASK) ? MOD_CTRL : 0; |
7f77ea24 |
332 | |
2ef96bd6 |
333 | if (!fe->pixmap) |
334 | return TRUE; |
7f77ea24 |
335 | |
3c833d45 |
336 | if (event->keyval == GDK_Up) |
f0ee053c |
337 | keyval = shift | ctrl | CURSOR_UP; |
3c833d45 |
338 | else if (event->keyval == GDK_KP_Up || event->keyval == GDK_KP_8) |
339 | keyval = MOD_NUM_KEYPAD | '8'; |
340 | else if (event->keyval == GDK_Down) |
f0ee053c |
341 | keyval = shift | ctrl | CURSOR_DOWN; |
3c833d45 |
342 | else if (event->keyval == GDK_KP_Down || event->keyval == GDK_KP_2) |
343 | keyval = MOD_NUM_KEYPAD | '2'; |
344 | else if (event->keyval == GDK_Left) |
f0ee053c |
345 | keyval = shift | ctrl | CURSOR_LEFT; |
3c833d45 |
346 | else if (event->keyval == GDK_KP_Left || event->keyval == GDK_KP_4) |
347 | keyval = MOD_NUM_KEYPAD | '4'; |
348 | else if (event->keyval == GDK_Right) |
f0ee053c |
349 | keyval = shift | ctrl | CURSOR_RIGHT; |
3c833d45 |
350 | else if (event->keyval == GDK_KP_Right || event->keyval == GDK_KP_6) |
351 | keyval = MOD_NUM_KEYPAD | '6'; |
c71454c0 |
352 | else if (event->keyval == GDK_KP_Home || event->keyval == GDK_KP_7) |
3c833d45 |
353 | keyval = MOD_NUM_KEYPAD | '7'; |
c71454c0 |
354 | else if (event->keyval == GDK_KP_End || event->keyval == GDK_KP_1) |
3c833d45 |
355 | keyval = MOD_NUM_KEYPAD | '1'; |
c71454c0 |
356 | else if (event->keyval == GDK_KP_Page_Up || event->keyval == GDK_KP_9) |
3c833d45 |
357 | keyval = MOD_NUM_KEYPAD | '9'; |
c71454c0 |
358 | else if (event->keyval == GDK_KP_Page_Down || event->keyval == GDK_KP_3) |
3c833d45 |
359 | keyval = MOD_NUM_KEYPAD | '3'; |
360 | else if (event->keyval == GDK_KP_Insert || event->keyval == GDK_KP_0) |
361 | keyval = MOD_NUM_KEYPAD | '0'; |
362 | else if (event->keyval == GDK_KP_Begin || event->keyval == GDK_KP_5) |
363 | keyval = MOD_NUM_KEYPAD | '5'; |
364 | else if (event->string[0] && !event->string[1]) |
365 | keyval = (unsigned char)event->string[0]; |
1482ee76 |
366 | else |
367 | keyval = -1; |
368 | |
369 | if (keyval >= 0 && |
370 | !midend_process_key(fe->me, 0, 0, keyval)) |
2ef96bd6 |
371 | gtk_widget_destroy(fe->window); |
7f77ea24 |
372 | |
373 | return TRUE; |
374 | } |
375 | |
2ef96bd6 |
376 | static gint button_event(GtkWidget *widget, GdkEventButton *event, |
377 | gpointer data) |
7f77ea24 |
378 | { |
2ef96bd6 |
379 | frontend *fe = (frontend *)data; |
380 | int button; |
381 | |
382 | if (!fe->pixmap) |
383 | return TRUE; |
384 | |
74a4e547 |
385 | if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) |
2ef96bd6 |
386 | return TRUE; |
7f77ea24 |
387 | |
b0f06719 |
388 | if (event->button == 2 || (event->state & GDK_SHIFT_MASK)) |
2ef96bd6 |
389 | button = MIDDLE_BUTTON; |
b0f06719 |
390 | else if (event->button == 1) |
391 | button = LEFT_BUTTON; |
2ef96bd6 |
392 | else if (event->button == 3) |
393 | button = RIGHT_BUTTON; |
394 | else |
395 | return FALSE; /* don't even know what button! */ |
396 | |
74a4e547 |
397 | if (event->type == GDK_BUTTON_RELEASE) |
398 | button += LEFT_RELEASE - LEFT_BUTTON; |
399 | |
400 | if (!midend_process_key(fe->me, event->x, event->y, button)) |
401 | gtk_widget_destroy(fe->window); |
402 | |
403 | return TRUE; |
404 | } |
405 | |
406 | static gint motion_event(GtkWidget *widget, GdkEventMotion *event, |
407 | gpointer data) |
408 | { |
409 | frontend *fe = (frontend *)data; |
410 | int button; |
411 | |
412 | if (!fe->pixmap) |
413 | return TRUE; |
414 | |
74a4e547 |
415 | if (event->state & (GDK_BUTTON2_MASK | GDK_SHIFT_MASK)) |
416 | button = MIDDLE_DRAG; |
417 | else if (event->state & GDK_BUTTON1_MASK) |
418 | button = LEFT_DRAG; |
419 | else if (event->state & GDK_BUTTON3_MASK) |
420 | button = RIGHT_DRAG; |
421 | else |
422 | return FALSE; /* don't even know what button! */ |
423 | |
2ef96bd6 |
424 | if (!midend_process_key(fe->me, event->x, event->y, button)) |
425 | gtk_widget_destroy(fe->window); |
7f77ea24 |
426 | |
427 | return TRUE; |
428 | } |
429 | |
2ef96bd6 |
430 | static gint expose_area(GtkWidget *widget, GdkEventExpose *event, |
431 | gpointer data) |
83680571 |
432 | { |
2ef96bd6 |
433 | frontend *fe = (frontend *)data; |
434 | |
435 | if (fe->pixmap) { |
436 | gdk_draw_pixmap(widget->window, |
437 | widget->style->fg_gc[GTK_WIDGET_STATE(widget)], |
438 | fe->pixmap, |
439 | event->area.x, event->area.y, |
440 | event->area.x, event->area.y, |
441 | event->area.width, event->area.height); |
442 | } |
443 | return TRUE; |
444 | } |
445 | |
fd1a1a2b |
446 | static gint map_window(GtkWidget *widget, GdkEvent *event, |
447 | gpointer data) |
448 | { |
449 | frontend *fe = (frontend *)data; |
450 | |
451 | /* |
452 | * Apparently we need to do this because otherwise the status |
453 | * bar will fail to update immediately. Annoying, but there we |
454 | * go. |
455 | */ |
456 | gtk_widget_queue_draw(fe->window); |
457 | |
458 | return TRUE; |
459 | } |
460 | |
2ef96bd6 |
461 | static gint configure_area(GtkWidget *widget, |
462 | GdkEventConfigure *event, gpointer data) |
463 | { |
464 | frontend *fe = (frontend *)data; |
465 | GdkGC *gc; |
466 | |
eb2ad6f1 |
467 | if (fe->pixmap) |
468 | gdk_pixmap_unref(fe->pixmap); |
469 | |
2ef96bd6 |
470 | fe->pixmap = gdk_pixmap_new(widget->window, fe->w, fe->h, -1); |
471 | |
472 | gc = gdk_gc_new(fe->area->window); |
473 | gdk_gc_set_foreground(gc, &fe->colours[0]); |
474 | gdk_draw_rectangle(fe->pixmap, gc, 1, 0, 0, fe->w, fe->h); |
475 | gdk_gc_unref(gc); |
476 | |
008b4378 |
477 | midend_force_redraw(fe->me); |
2ef96bd6 |
478 | |
479 | return TRUE; |
480 | } |
481 | |
482 | static gint timer_func(gpointer data) |
483 | { |
484 | frontend *fe = (frontend *)data; |
485 | |
8c1fd974 |
486 | if (fe->timer_active) { |
487 | struct timeval now; |
488 | float elapsed; |
489 | gettimeofday(&now, NULL); |
490 | elapsed = ((now.tv_usec - fe->last_time.tv_usec) * 0.000001F + |
491 | (now.tv_sec - fe->last_time.tv_sec)); |
492 | midend_timer(fe->me, elapsed); /* may clear timer_active */ |
493 | fe->last_time = now; |
494 | } |
2ef96bd6 |
495 | |
496 | return fe->timer_active; |
497 | } |
498 | |
499 | void deactivate_timer(frontend *fe) |
500 | { |
20ee89e3 |
501 | if (fe->timer_active) |
502 | gtk_timeout_remove(fe->timer_id); |
2ef96bd6 |
503 | fe->timer_active = FALSE; |
504 | } |
505 | |
506 | void activate_timer(frontend *fe) |
507 | { |
8c1fd974 |
508 | if (!fe->timer_active) { |
20ee89e3 |
509 | fe->timer_id = gtk_timeout_add(20, timer_func, fe); |
8c1fd974 |
510 | gettimeofday(&fe->last_time, NULL); |
511 | } |
2ef96bd6 |
512 | fe->timer_active = TRUE; |
513 | } |
514 | |
c8230524 |
515 | static void window_destroy(GtkWidget *widget, gpointer data) |
516 | { |
517 | gtk_main_quit(); |
518 | } |
519 | |
520 | static void errmsg_button_clicked(GtkButton *button, gpointer data) |
521 | { |
522 | gtk_widget_destroy(GTK_WIDGET(data)); |
523 | } |
524 | |
a163b7c2 |
525 | static int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) |
526 | { |
527 | GtkObject *cancelbutton = GTK_OBJECT(data); |
528 | |
529 | /* |
530 | * `Escape' effectively clicks the cancel button |
531 | */ |
532 | if (event->keyval == GDK_Escape) { |
533 | gtk_signal_emit_by_name(GTK_OBJECT(cancelbutton), "clicked"); |
534 | return TRUE; |
535 | } |
536 | |
537 | return FALSE; |
538 | } |
539 | |
97098757 |
540 | void message_box(GtkWidget *parent, char *title, char *msg, int centre) |
c8230524 |
541 | { |
542 | GtkWidget *window, *hbox, *text, *ok; |
543 | |
544 | window = gtk_dialog_new(); |
545 | text = gtk_label_new(msg); |
546 | gtk_misc_set_alignment(GTK_MISC(text), 0.0, 0.0); |
547 | hbox = gtk_hbox_new(FALSE, 0); |
548 | gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20); |
549 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), |
550 | hbox, FALSE, FALSE, 20); |
551 | gtk_widget_show(text); |
552 | gtk_widget_show(hbox); |
97098757 |
553 | gtk_window_set_title(GTK_WINDOW(window), title); |
c8230524 |
554 | gtk_label_set_line_wrap(GTK_LABEL(text), TRUE); |
555 | ok = gtk_button_new_with_label("OK"); |
556 | gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area), |
557 | ok, FALSE, FALSE, 0); |
558 | gtk_widget_show(ok); |
559 | GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT); |
560 | gtk_window_set_default(GTK_WINDOW(window), ok); |
561 | gtk_signal_connect(GTK_OBJECT(ok), "clicked", |
562 | GTK_SIGNAL_FUNC(errmsg_button_clicked), window); |
a1ed9f0e |
563 | gtk_signal_connect(GTK_OBJECT(window), "destroy", |
564 | GTK_SIGNAL_FUNC(window_destroy), NULL); |
a163b7c2 |
565 | gtk_signal_connect(GTK_OBJECT(window), "key_press_event", |
566 | GTK_SIGNAL_FUNC(win_key_press), ok); |
c8230524 |
567 | gtk_window_set_modal(GTK_WINDOW(window), TRUE); |
568 | gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent)); |
569 | //set_transient_window_pos(parent, window); |
570 | gtk_widget_show(window); |
571 | gtk_main(); |
572 | } |
573 | |
97098757 |
574 | void error_box(GtkWidget *parent, char *msg) |
575 | { |
576 | message_box(parent, "Error", msg, FALSE); |
577 | } |
578 | |
c8230524 |
579 | static void config_ok_button_clicked(GtkButton *button, gpointer data) |
580 | { |
581 | frontend *fe = (frontend *)data; |
582 | char *err; |
583 | |
5928817c |
584 | err = midend_set_config(fe->me, fe->cfg_which, fe->cfg); |
c8230524 |
585 | |
586 | if (err) |
587 | error_box(fe->cfgbox, err); |
588 | else { |
589 | fe->cfgret = TRUE; |
590 | gtk_widget_destroy(fe->cfgbox); |
591 | } |
592 | } |
593 | |
594 | static void config_cancel_button_clicked(GtkButton *button, gpointer data) |
595 | { |
596 | frontend *fe = (frontend *)data; |
597 | |
598 | gtk_widget_destroy(fe->cfgbox); |
599 | } |
600 | |
a163b7c2 |
601 | static int editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data) |
602 | { |
603 | /* |
604 | * GtkEntry has a nasty habit of eating the Return key, which |
605 | * is unhelpful since it doesn't actually _do_ anything with it |
606 | * (it calls gtk_widget_activate, but our edit boxes never need |
607 | * activating). So I catch Return before GtkEntry sees it, and |
608 | * pass it straight on to the parent widget. Effect: hitting |
609 | * Return in an edit box will now activate the default button |
610 | * in the dialog just like it will everywhere else. |
611 | */ |
612 | if (event->keyval == GDK_Return && widget->parent != NULL) { |
613 | gint return_val; |
614 | gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event"); |
615 | gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event", |
616 | event, &return_val); |
617 | return return_val; |
618 | } |
619 | return FALSE; |
620 | } |
621 | |
c8230524 |
622 | static void editbox_changed(GtkEditable *ed, gpointer data) |
623 | { |
624 | config_item *i = (config_item *)data; |
625 | |
626 | sfree(i->sval); |
627 | i->sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed))); |
628 | } |
629 | |
630 | static void button_toggled(GtkToggleButton *tb, gpointer data) |
631 | { |
632 | config_item *i = (config_item *)data; |
633 | |
634 | i->ival = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb)); |
635 | } |
636 | |
637 | static void droplist_sel(GtkMenuItem *item, gpointer data) |
638 | { |
639 | config_item *i = (config_item *)data; |
640 | |
641 | i->ival = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item), |
642 | "user-data")); |
643 | } |
644 | |
5928817c |
645 | static int get_config(frontend *fe, int which) |
c8230524 |
646 | { |
a163b7c2 |
647 | GtkWidget *w, *table, *cancel; |
5928817c |
648 | char *title; |
c8230524 |
649 | config_item *i; |
650 | int y; |
651 | |
5928817c |
652 | fe->cfg = midend_get_config(fe->me, which, &title); |
653 | fe->cfg_which = which; |
c8230524 |
654 | fe->cfgret = FALSE; |
655 | |
656 | fe->cfgbox = gtk_dialog_new(); |
5928817c |
657 | gtk_window_set_title(GTK_WINDOW(fe->cfgbox), title); |
658 | sfree(title); |
c8230524 |
659 | |
660 | w = gtk_button_new_with_label("OK"); |
661 | gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->action_area), |
662 | w, FALSE, FALSE, 0); |
663 | gtk_widget_show(w); |
664 | GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT); |
665 | gtk_window_set_default(GTK_WINDOW(fe->cfgbox), w); |
666 | gtk_signal_connect(GTK_OBJECT(w), "clicked", |
667 | GTK_SIGNAL_FUNC(config_ok_button_clicked), fe); |
668 | |
669 | w = gtk_button_new_with_label("Cancel"); |
670 | gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->action_area), |
671 | w, FALSE, FALSE, 0); |
672 | gtk_widget_show(w); |
673 | gtk_signal_connect(GTK_OBJECT(w), "clicked", |
674 | GTK_SIGNAL_FUNC(config_cancel_button_clicked), fe); |
a163b7c2 |
675 | cancel = w; |
c8230524 |
676 | |
677 | table = gtk_table_new(1, 2, FALSE); |
678 | y = 0; |
679 | gtk_box_pack_end(GTK_BOX(GTK_DIALOG(fe->cfgbox)->vbox), |
680 | table, FALSE, FALSE, 0); |
681 | gtk_widget_show(table); |
682 | |
95709966 |
683 | for (i = fe->cfg; i->type != C_END; i++) { |
c8230524 |
684 | gtk_table_resize(GTK_TABLE(table), y+1, 2); |
685 | |
686 | switch (i->type) { |
95709966 |
687 | case C_STRING: |
c8230524 |
688 | /* |
689 | * Edit box with a label beside it. |
690 | */ |
691 | |
692 | w = gtk_label_new(i->name); |
693 | gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5); |
694 | gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1, |
695 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
696 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
697 | 3, 3); |
698 | gtk_widget_show(w); |
699 | |
700 | w = gtk_entry_new(); |
701 | gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1, |
702 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
703 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
704 | 3, 3); |
705 | gtk_entry_set_text(GTK_ENTRY(w), i->sval); |
706 | gtk_signal_connect(GTK_OBJECT(w), "changed", |
707 | GTK_SIGNAL_FUNC(editbox_changed), i); |
a163b7c2 |
708 | gtk_signal_connect(GTK_OBJECT(w), "key_press_event", |
709 | GTK_SIGNAL_FUNC(editbox_key), NULL); |
c8230524 |
710 | gtk_widget_show(w); |
711 | |
712 | break; |
713 | |
95709966 |
714 | case C_BOOLEAN: |
c8230524 |
715 | /* |
716 | * Simple checkbox. |
717 | */ |
718 | w = gtk_check_button_new_with_label(i->name); |
719 | gtk_signal_connect(GTK_OBJECT(w), "toggled", |
720 | GTK_SIGNAL_FUNC(button_toggled), i); |
721 | gtk_table_attach(GTK_TABLE(table), w, 0, 2, y, y+1, |
722 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
723 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
724 | 3, 3); |
725 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), i->ival); |
726 | gtk_widget_show(w); |
727 | break; |
728 | |
95709966 |
729 | case C_CHOICES: |
c8230524 |
730 | /* |
731 | * Drop-down list (GtkOptionMenu). |
732 | */ |
733 | |
734 | w = gtk_label_new(i->name); |
735 | gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5); |
736 | gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1, |
737 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
738 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
739 | 3, 3); |
740 | gtk_widget_show(w); |
741 | |
742 | w = gtk_option_menu_new(); |
743 | gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1, |
744 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
745 | GTK_EXPAND | GTK_SHRINK | GTK_FILL, |
746 | 3, 3); |
747 | gtk_widget_show(w); |
748 | |
749 | { |
750 | int c, val; |
751 | char *p, *q, *name; |
752 | GtkWidget *menuitem; |
753 | GtkWidget *menu = gtk_menu_new(); |
754 | |
755 | gtk_option_menu_set_menu(GTK_OPTION_MENU(w), menu); |
756 | |
757 | c = *i->sval; |
758 | p = i->sval+1; |
759 | val = 0; |
760 | |
761 | while (*p) { |
762 | q = p; |
763 | while (*q && *q != c) |
764 | q++; |
765 | |
766 | name = snewn(q-p+1, char); |
767 | strncpy(name, p, q-p); |
768 | name[q-p] = '\0'; |
769 | |
770 | if (*q) q++; /* eat delimiter */ |
771 | |
772 | menuitem = gtk_menu_item_new_with_label(name); |
773 | gtk_container_add(GTK_CONTAINER(menu), menuitem); |
774 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", |
775 | GINT_TO_POINTER(val)); |
776 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
777 | GTK_SIGNAL_FUNC(droplist_sel), i); |
778 | gtk_widget_show(menuitem); |
779 | |
780 | val++; |
781 | |
782 | p = q; |
783 | } |
784 | |
785 | gtk_option_menu_set_history(GTK_OPTION_MENU(w), i->ival); |
786 | } |
787 | |
788 | break; |
789 | } |
790 | |
791 | y++; |
792 | } |
793 | |
794 | gtk_signal_connect(GTK_OBJECT(fe->cfgbox), "destroy", |
795 | GTK_SIGNAL_FUNC(window_destroy), NULL); |
a163b7c2 |
796 | gtk_signal_connect(GTK_OBJECT(fe->cfgbox), "key_press_event", |
797 | GTK_SIGNAL_FUNC(win_key_press), cancel); |
c8230524 |
798 | gtk_window_set_modal(GTK_WINDOW(fe->cfgbox), TRUE); |
799 | gtk_window_set_transient_for(GTK_WINDOW(fe->cfgbox), |
800 | GTK_WINDOW(fe->window)); |
801 | //set_transient_window_pos(fe->window, fe->cfgbox); |
802 | gtk_widget_show(fe->cfgbox); |
803 | gtk_main(); |
804 | |
077f3cbe |
805 | free_cfg(fe->cfg); |
c8230524 |
806 | |
807 | return fe->cfgret; |
808 | } |
809 | |
eb2ad6f1 |
810 | static void menu_key_event(GtkMenuItem *menuitem, gpointer data) |
811 | { |
812 | frontend *fe = (frontend *)data; |
813 | int key = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem), |
814 | "user-data")); |
815 | if (!midend_process_key(fe->me, 0, 0, key)) |
816 | gtk_widget_destroy(fe->window); |
817 | } |
818 | |
819 | static void menu_preset_event(GtkMenuItem *menuitem, gpointer data) |
820 | { |
821 | frontend *fe = (frontend *)data; |
822 | game_params *params = |
823 | (game_params *)gtk_object_get_data(GTK_OBJECT(menuitem), "user-data"); |
824 | int x, y; |
825 | |
826 | midend_set_params(fe->me, params); |
5928817c |
827 | midend_new_game(fe->me); |
eb2ad6f1 |
828 | midend_size(fe->me, &x, &y); |
829 | gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); |
830 | fe->w = x; |
831 | fe->h = y; |
832 | } |
833 | |
9b4b03d3 |
834 | GdkAtom compound_text_atom, utf8_string_atom; |
835 | int paste_initialised = FALSE; |
836 | |
837 | void init_paste() |
838 | { |
839 | if (paste_initialised) |
840 | return; |
841 | |
842 | if (!compound_text_atom) |
843 | compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE); |
844 | if (!utf8_string_atom) |
845 | utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE); |
846 | |
847 | /* |
848 | * Ensure that all the cut buffers exist - according to the |
849 | * ICCCM, we must do this before we start using cut buffers. |
850 | */ |
851 | XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), |
852 | XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, "", 0); |
853 | XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), |
854 | XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, "", 0); |
855 | XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), |
856 | XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, "", 0); |
857 | XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), |
858 | XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, "", 0); |
859 | XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), |
860 | XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, "", 0); |
861 | XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), |
862 | XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, "", 0); |
863 | XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), |
864 | XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, "", 0); |
865 | XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), |
866 | XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, "", 0); |
867 | } |
868 | |
869 | /* Store data in a cut-buffer. */ |
870 | void store_cutbuffer(char *ptr, int len) |
871 | { |
872 | /* ICCCM says we must rotate the buffers before storing to buffer 0. */ |
873 | XRotateBuffers(GDK_DISPLAY(), 1); |
874 | XStoreBytes(GDK_DISPLAY(), ptr, len); |
875 | } |
876 | |
877 | void write_clip(frontend *fe, char *data) |
878 | { |
879 | init_paste(); |
880 | |
881 | if (fe->paste_data) |
882 | sfree(fe->paste_data); |
883 | |
884 | /* |
885 | * For this simple application we can safely assume that the |
886 | * data passed to this function is pure ASCII, which means we |
887 | * can return precisely the same stuff for types STRING, |
888 | * COMPOUND_TEXT or UTF8_STRING. |
889 | */ |
890 | |
891 | fe->paste_data = data; |
892 | fe->paste_data_len = strlen(data); |
893 | |
894 | store_cutbuffer(fe->paste_data, fe->paste_data_len); |
895 | |
896 | if (gtk_selection_owner_set(fe->area, GDK_SELECTION_PRIMARY, |
897 | CurrentTime)) { |
898 | gtk_selection_add_target(fe->area, GDK_SELECTION_PRIMARY, |
899 | GDK_SELECTION_TYPE_STRING, 1); |
900 | gtk_selection_add_target(fe->area, GDK_SELECTION_PRIMARY, |
901 | compound_text_atom, 1); |
902 | gtk_selection_add_target(fe->area, GDK_SELECTION_PRIMARY, |
903 | utf8_string_atom, 1); |
904 | } |
905 | } |
906 | |
907 | void selection_get(GtkWidget *widget, GtkSelectionData *seldata, |
908 | guint info, guint time_stamp, gpointer data) |
909 | { |
910 | frontend *fe = (frontend *)data; |
911 | gtk_selection_data_set(seldata, seldata->target, 8, |
912 | fe->paste_data, fe->paste_data_len); |
913 | } |
914 | |
915 | gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, |
916 | gpointer data) |
917 | { |
918 | frontend *fe = (frontend *)data; |
919 | |
920 | if (fe->paste_data) |
921 | sfree(fe->paste_data); |
922 | fe->paste_data = NULL; |
923 | fe->paste_data_len = 0; |
924 | return TRUE; |
925 | } |
926 | |
927 | static void menu_copy_event(GtkMenuItem *menuitem, gpointer data) |
928 | { |
929 | frontend *fe = (frontend *)data; |
930 | char *text; |
931 | |
932 | text = midend_text_format(fe->me); |
933 | |
934 | if (text) { |
935 | write_clip(fe, text); |
936 | } else { |
937 | gdk_beep(); |
938 | } |
939 | } |
940 | |
2ac6d24e |
941 | static void menu_solve_event(GtkMenuItem *menuitem, gpointer data) |
942 | { |
943 | frontend *fe = (frontend *)data; |
944 | char *msg; |
945 | |
946 | msg = midend_solve(fe->me); |
947 | |
948 | if (msg) |
949 | error_box(fe->window, msg); |
950 | } |
951 | |
7f89707c |
952 | static void menu_restart_event(GtkMenuItem *menuitem, gpointer data) |
953 | { |
954 | frontend *fe = (frontend *)data; |
955 | |
956 | midend_restart_game(fe->me); |
957 | } |
958 | |
c8230524 |
959 | static void menu_config_event(GtkMenuItem *menuitem, gpointer data) |
960 | { |
961 | frontend *fe = (frontend *)data; |
5928817c |
962 | int which = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(menuitem), |
963 | "user-data")); |
c8230524 |
964 | int x, y; |
965 | |
5928817c |
966 | if (!get_config(fe, which)) |
c8230524 |
967 | return; |
968 | |
5928817c |
969 | midend_new_game(fe->me); |
c8230524 |
970 | midend_size(fe->me, &x, &y); |
971 | gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); |
972 | fe->w = x; |
973 | fe->h = y; |
974 | } |
975 | |
97098757 |
976 | static void menu_about_event(GtkMenuItem *menuitem, gpointer data) |
977 | { |
978 | frontend *fe = (frontend *)data; |
979 | char titlebuf[256]; |
980 | char textbuf[1024]; |
981 | |
982 | sprintf(titlebuf, "About %.200s", thegame.name); |
983 | sprintf(textbuf, |
984 | "%.200s\n\n" |
985 | "from Simon Tatham's Portable Puzzle Collection\n\n" |
986 | "%.500s", thegame.name, ver); |
987 | |
988 | message_box(fe->window, titlebuf, textbuf, TRUE); |
989 | } |
990 | |
eb2ad6f1 |
991 | static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont, |
992 | char *text, int key) |
993 | { |
994 | GtkWidget *menuitem = gtk_menu_item_new_with_label(text); |
995 | gtk_container_add(cont, menuitem); |
996 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", |
997 | GINT_TO_POINTER(key)); |
998 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
999 | GTK_SIGNAL_FUNC(menu_key_event), fe); |
1000 | gtk_widget_show(menuitem); |
1001 | return menuitem; |
1002 | } |
1003 | |
1004 | static void add_menu_separator(GtkContainer *cont) |
1005 | { |
1006 | GtkWidget *menuitem = gtk_menu_item_new(); |
1007 | gtk_container_add(cont, menuitem); |
1008 | gtk_widget_show(menuitem); |
1009 | } |
1010 | |
8b7938e7 |
1011 | static frontend *new_window(char *game_id, char **error) |
2ef96bd6 |
1012 | { |
1013 | frontend *fe; |
eb2ad6f1 |
1014 | GtkBox *vbox; |
1015 | GtkWidget *menubar, *menu, *menuitem; |
1016 | int x, y, n; |
83680571 |
1017 | |
2ef96bd6 |
1018 | fe = snew(frontend); |
83680571 |
1019 | |
171fbdaa |
1020 | fe->timer_active = FALSE; |
1021 | fe->timer_id = -1; |
1022 | |
be8d5aa1 |
1023 | fe->me = midend_new(fe, &thegame); |
171fbdaa |
1024 | |
8b7938e7 |
1025 | if (game_id) { |
1185e3c5 |
1026 | *error = midend_game_id(fe->me, game_id); |
8b7938e7 |
1027 | if (*error) { |
1028 | midend_free(fe->me); |
1029 | sfree(fe); |
1030 | return NULL; |
1031 | } |
1032 | } |
5928817c |
1033 | midend_new_game(fe->me); |
7f77ea24 |
1034 | |
2ef96bd6 |
1035 | fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
be8d5aa1 |
1036 | gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name); |
ff2f787b |
1037 | #if 0 |
1038 | gtk_window_set_resizable(GTK_WINDOW(fe->window), FALSE); |
1039 | #else |
1040 | gtk_window_set_policy(GTK_WINDOW(fe->window), FALSE, FALSE, TRUE); |
1041 | #endif |
eb2ad6f1 |
1042 | vbox = GTK_BOX(gtk_vbox_new(FALSE, 0)); |
1043 | gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox)); |
1044 | gtk_widget_show(GTK_WIDGET(vbox)); |
1045 | |
1046 | menubar = gtk_menu_bar_new(); |
1047 | gtk_box_pack_start(vbox, menubar, FALSE, FALSE, 0); |
1048 | gtk_widget_show(menubar); |
1049 | |
1050 | menuitem = gtk_menu_item_new_with_label("Game"); |
1051 | gtk_container_add(GTK_CONTAINER(menubar), menuitem); |
1052 | gtk_widget_show(menuitem); |
1053 | |
1054 | menu = gtk_menu_new(); |
1055 | gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); |
1056 | |
1057 | add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n'); |
7f89707c |
1058 | |
1059 | menuitem = gtk_menu_item_new_with_label("Restart"); |
1060 | gtk_container_add(GTK_CONTAINER(menu), menuitem); |
1061 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
1062 | GTK_SIGNAL_FUNC(menu_restart_event), fe); |
1063 | gtk_widget_show(menuitem); |
eb2ad6f1 |
1064 | |
5928817c |
1065 | menuitem = gtk_menu_item_new_with_label("Specific..."); |
1066 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", |
1185e3c5 |
1067 | GINT_TO_POINTER(CFG_DESC)); |
1068 | gtk_container_add(GTK_CONTAINER(menu), menuitem); |
1069 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
1070 | GTK_SIGNAL_FUNC(menu_config_event), fe); |
1071 | gtk_widget_show(menuitem); |
1072 | |
1073 | menuitem = gtk_menu_item_new_with_label("Random Seed..."); |
1074 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", |
5928817c |
1075 | GINT_TO_POINTER(CFG_SEED)); |
1076 | gtk_container_add(GTK_CONTAINER(menu), menuitem); |
1077 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
1078 | GTK_SIGNAL_FUNC(menu_config_event), fe); |
1079 | gtk_widget_show(menuitem); |
1080 | |
be8d5aa1 |
1081 | if ((n = midend_num_presets(fe->me)) > 0 || thegame.can_configure) { |
eb2ad6f1 |
1082 | GtkWidget *submenu; |
1083 | int i; |
1084 | |
1085 | menuitem = gtk_menu_item_new_with_label("Type"); |
c8230524 |
1086 | gtk_container_add(GTK_CONTAINER(menubar), menuitem); |
eb2ad6f1 |
1087 | gtk_widget_show(menuitem); |
1088 | |
1089 | submenu = gtk_menu_new(); |
1090 | gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu); |
1091 | |
1092 | for (i = 0; i < n; i++) { |
1093 | char *name; |
1094 | game_params *params; |
1095 | |
1096 | midend_fetch_preset(fe->me, i, &name, ¶ms); |
1097 | |
1098 | menuitem = gtk_menu_item_new_with_label(name); |
1099 | gtk_container_add(GTK_CONTAINER(submenu), menuitem); |
1100 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", params); |
1101 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
1102 | GTK_SIGNAL_FUNC(menu_preset_event), fe); |
1103 | gtk_widget_show(menuitem); |
1104 | } |
c8230524 |
1105 | |
be8d5aa1 |
1106 | if (thegame.can_configure) { |
c8230524 |
1107 | menuitem = gtk_menu_item_new_with_label("Custom..."); |
5928817c |
1108 | gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", |
1109 | GPOINTER_TO_INT(CFG_SETTINGS)); |
c8230524 |
1110 | gtk_container_add(GTK_CONTAINER(submenu), menuitem); |
1111 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
1112 | GTK_SIGNAL_FUNC(menu_config_event), fe); |
1113 | gtk_widget_show(menuitem); |
1114 | } |
eb2ad6f1 |
1115 | } |
1116 | |
1117 | add_menu_separator(GTK_CONTAINER(menu)); |
1118 | add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u'); |
1119 | add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", '\x12'); |
9b4b03d3 |
1120 | if (thegame.can_format_as_text) { |
1121 | add_menu_separator(GTK_CONTAINER(menu)); |
1122 | menuitem = gtk_menu_item_new_with_label("Copy"); |
1123 | gtk_container_add(GTK_CONTAINER(menu), menuitem); |
1124 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
1125 | GTK_SIGNAL_FUNC(menu_copy_event), fe); |
1126 | gtk_widget_show(menuitem); |
1127 | } |
2ac6d24e |
1128 | if (thegame.can_solve) { |
1129 | add_menu_separator(GTK_CONTAINER(menu)); |
1130 | menuitem = gtk_menu_item_new_with_label("Solve"); |
1131 | gtk_container_add(GTK_CONTAINER(menu), menuitem); |
1132 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
1133 | GTK_SIGNAL_FUNC(menu_solve_event), fe); |
1134 | gtk_widget_show(menuitem); |
1135 | } |
eb2ad6f1 |
1136 | add_menu_separator(GTK_CONTAINER(menu)); |
1137 | add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q'); |
7f77ea24 |
1138 | |
97098757 |
1139 | menuitem = gtk_menu_item_new_with_label("Help"); |
1140 | gtk_container_add(GTK_CONTAINER(menubar), menuitem); |
1141 | gtk_widget_show(menuitem); |
1142 | |
1143 | menu = gtk_menu_new(); |
1144 | gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); |
1145 | |
1146 | menuitem = gtk_menu_item_new_with_label("About"); |
1147 | gtk_container_add(GTK_CONTAINER(menu), menuitem); |
1148 | gtk_signal_connect(GTK_OBJECT(menuitem), "activate", |
1149 | GTK_SIGNAL_FUNC(menu_about_event), fe); |
1150 | gtk_widget_show(menuitem); |
1151 | |
2ef96bd6 |
1152 | { |
1153 | int i, ncolours; |
1154 | float *colours; |
1155 | gboolean *success; |
7f77ea24 |
1156 | |
2ef96bd6 |
1157 | fe->colmap = gdk_colormap_get_system(); |
1158 | colours = midend_colours(fe->me, &ncolours); |
1159 | fe->ncolours = ncolours; |
1160 | fe->colours = snewn(ncolours, GdkColor); |
1161 | for (i = 0; i < ncolours; i++) { |
813593cc |
1162 | fe->colours[i].red = colours[i*3] * 0xFFFF; |
1163 | fe->colours[i].green = colours[i*3+1] * 0xFFFF; |
1164 | fe->colours[i].blue = colours[i*3+2] * 0xFFFF; |
2ef96bd6 |
1165 | } |
1166 | success = snewn(ncolours, gboolean); |
1167 | gdk_colormap_alloc_colors(fe->colmap, fe->colours, ncolours, |
1168 | FALSE, FALSE, success); |
1169 | for (i = 0; i < ncolours; i++) { |
1170 | if (!success[i]) |
1171 | g_error("couldn't allocate colour %d (#%02x%02x%02x)\n", |
1172 | i, fe->colours[i].red >> 8, |
1173 | fe->colours[i].green >> 8, |
1174 | fe->colours[i].blue >> 8); |
1175 | } |
1176 | } |
7f77ea24 |
1177 | |
fd1a1a2b |
1178 | if (midend_wants_statusbar(fe->me)) { |
5725d728 |
1179 | GtkWidget *viewport; |
1180 | GtkRequisition req; |
1181 | |
1182 | viewport = gtk_viewport_new(NULL, NULL); |
1183 | gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE); |
fd1a1a2b |
1184 | fe->statusbar = gtk_statusbar_new(); |
5725d728 |
1185 | gtk_container_add(GTK_CONTAINER(viewport), fe->statusbar); |
1186 | gtk_widget_show(viewport); |
1187 | gtk_box_pack_end(vbox, viewport, FALSE, FALSE, 0); |
fd1a1a2b |
1188 | gtk_widget_show(fe->statusbar); |
1189 | fe->statusctx = gtk_statusbar_get_context_id |
1190 | (GTK_STATUSBAR(fe->statusbar), "game"); |
1191 | gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx, |
5725d728 |
1192 | "test"); |
1193 | gtk_widget_size_request(fe->statusbar, &req); |
1194 | #if 0 |
1195 | /* For GTK 2.0, should we be using gtk_widget_set_size_request? */ |
1196 | #endif |
60d42abc |
1197 | gtk_widget_set_usize(viewport, -1, req.height); |
fd1a1a2b |
1198 | } else |
1199 | fe->statusbar = NULL; |
1200 | |
2ef96bd6 |
1201 | fe->area = gtk_drawing_area_new(); |
1202 | midend_size(fe->me, &x, &y); |
1203 | gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); |
1204 | fe->w = x; |
1205 | fe->h = y; |
1206 | |
eb2ad6f1 |
1207 | gtk_box_pack_end(vbox, fe->area, FALSE, FALSE, 0); |
2ef96bd6 |
1208 | |
1209 | fe->pixmap = NULL; |
4efb3868 |
1210 | fe->fonts = NULL; |
1211 | fe->nfonts = fe->fontsize = 0; |
2ef96bd6 |
1212 | |
f5f4a1f1 |
1213 | fe->laststatus = NULL; |
1214 | |
9b4b03d3 |
1215 | fe->paste_data = NULL; |
1216 | fe->paste_data_len = 0; |
1217 | |
2ef96bd6 |
1218 | gtk_signal_connect(GTK_OBJECT(fe->window), "destroy", |
1219 | GTK_SIGNAL_FUNC(destroy), fe); |
1220 | gtk_signal_connect(GTK_OBJECT(fe->window), "key_press_event", |
1221 | GTK_SIGNAL_FUNC(key_event), fe); |
1222 | gtk_signal_connect(GTK_OBJECT(fe->area), "button_press_event", |
1223 | GTK_SIGNAL_FUNC(button_event), fe); |
74a4e547 |
1224 | gtk_signal_connect(GTK_OBJECT(fe->area), "button_release_event", |
1225 | GTK_SIGNAL_FUNC(button_event), fe); |
1226 | gtk_signal_connect(GTK_OBJECT(fe->area), "motion_notify_event", |
1227 | GTK_SIGNAL_FUNC(motion_event), fe); |
9b4b03d3 |
1228 | gtk_signal_connect(GTK_OBJECT(fe->area), "selection_get", |
1229 | GTK_SIGNAL_FUNC(selection_get), fe); |
1230 | gtk_signal_connect(GTK_OBJECT(fe->area), "selection_clear_event", |
1231 | GTK_SIGNAL_FUNC(selection_clear), fe); |
2ef96bd6 |
1232 | gtk_signal_connect(GTK_OBJECT(fe->area), "expose_event", |
1233 | GTK_SIGNAL_FUNC(expose_area), fe); |
fd1a1a2b |
1234 | gtk_signal_connect(GTK_OBJECT(fe->window), "map_event", |
1235 | GTK_SIGNAL_FUNC(map_window), fe); |
2ef96bd6 |
1236 | gtk_signal_connect(GTK_OBJECT(fe->area), "configure_event", |
1237 | GTK_SIGNAL_FUNC(configure_area), fe); |
1238 | |
74a4e547 |
1239 | gtk_widget_add_events(GTK_WIDGET(fe->area), |
1240 | GDK_BUTTON_PRESS_MASK | |
1241 | GDK_BUTTON_RELEASE_MASK | |
1242 | GDK_BUTTON_MOTION_MASK); |
2ef96bd6 |
1243 | |
1244 | gtk_widget_show(fe->area); |
1245 | gtk_widget_show(fe->window); |
1246 | |
1247 | return fe; |
83680571 |
1248 | } |
1249 | |
1250 | int main(int argc, char **argv) |
1251 | { |
8b7938e7 |
1252 | char *pname = argv[0]; |
1253 | char *error; |
1254 | |
45f953cc |
1255 | if (argc > 1 && !strcmp(argv[1], "--version")) { |
1256 | printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n", |
1257 | thegame.name, ver); |
1258 | return 0; |
1259 | } |
1260 | |
d91e1fc9 |
1261 | /* |
1262 | * Special standalone mode for generating puzzle IDs on the |
1263 | * command line. Useful for generating puzzles to be printed |
1264 | * out and solved offline (for puzzles where that even makes |
1265 | * sense - Solo, for example, is a lot more pencil-and-paper |
1266 | * friendly than Net!) |
1267 | * |
1268 | * Usage: |
1269 | * |
1270 | * <puzzle-name> --generate [<n> [<params>]] |
1271 | * |
1272 | * <n>, if present, is the number of puzzle IDs to generate. |
1273 | * <params>, if present, is the same type of parameter string |
1274 | * you would pass to the puzzle when running it in GUI mode, |
1275 | * including optional extras such as the expansion factor in |
1276 | * Rectangles and the difficulty level in Solo. |
1277 | * |
1278 | * If you specify <params>, you must also specify <n> (although |
1279 | * you may specify it to be 1). Sorry; that was the |
1280 | * simplest-to-parse command-line syntax I came up with. |
1281 | */ |
1282 | if (argc > 1 && !strcmp(argv[1], "--generate")) { |
1283 | int n = 1; |
1185e3c5 |
1284 | char *params = NULL, *seed = NULL; |
d91e1fc9 |
1285 | game_params *par; |
1286 | random_state *rs; |
1287 | char *parstr; |
1288 | |
d91e1fc9 |
1289 | if (argc > 2) |
1290 | n = atoi(argv[2]); |
1291 | if (argc > 3) |
1292 | params = argv[3]; |
1293 | |
1185e3c5 |
1294 | par = thegame.default_params(); |
1295 | if (params) { |
1296 | if ( (seed = strchr(params, '#')) != NULL ) |
1297 | *seed++ = '\0'; |
1298 | thegame.decode_params(par, params); |
28fffa93 |
1299 | } |
1300 | if ((error = thegame.validate_params(par)) != NULL) { |
1301 | fprintf(stderr, "%s: %s\n", pname, error); |
1302 | return 1; |
1185e3c5 |
1303 | } |
1304 | parstr = thegame.encode_params(par, FALSE); |
1305 | |
1306 | { |
1307 | void *seeddata; |
1308 | int seedlen; |
1309 | if (seed) { |
1310 | seeddata = seed; |
1311 | seedlen = strlen(seed); |
1312 | } else { |
1313 | get_random_seed(&seeddata, &seedlen); |
1314 | } |
1315 | rs = random_init(seeddata, seedlen); |
1316 | } |
d91e1fc9 |
1317 | |
1318 | while (n-- > 0) { |
6f2d8d7c |
1319 | game_aux_info *aux = NULL; |
6aa6af4c |
1320 | char *desc = thegame.new_desc(par, rs, &aux, FALSE); |
1185e3c5 |
1321 | printf("%s:%s\n", parstr, desc); |
1322 | sfree(desc); |
6f2d8d7c |
1323 | if (aux) |
1324 | thegame.free_aux_info(aux); |
d91e1fc9 |
1325 | } |
8b7938e7 |
1326 | |
d91e1fc9 |
1327 | return 0; |
1328 | } else { |
1329 | |
1330 | gtk_init(&argc, &argv); |
1331 | |
1332 | if (!new_window(argc > 1 ? argv[1] : NULL, &error)) { |
1333 | fprintf(stderr, "%s: %s\n", pname, error); |
1334 | return 1; |
1335 | } |
1336 | |
1337 | gtk_main(); |
1338 | } |
83680571 |
1339 | |
1340 | return 0; |
1341 | } |