diff options
Diffstat (limited to 'apps/plugins/puzzles/src/osx.m')
-rw-r--r-- | apps/plugins/puzzles/src/osx.m | 1775 |
1 files changed, 1775 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/osx.m b/apps/plugins/puzzles/src/osx.m new file mode 100644 index 0000000000..9d74da1574 --- /dev/null +++ b/apps/plugins/puzzles/src/osx.m | |||
@@ -0,0 +1,1775 @@ | |||
1 | /* | ||
2 | * Mac OS X / Cocoa front end to puzzles. | ||
3 | * | ||
4 | * Still to do: | ||
5 | * | ||
6 | * - I'd like to be able to call up context help for a specific | ||
7 | * game at a time. | ||
8 | * | ||
9 | * Mac interface issues that possibly could be done better: | ||
10 | * | ||
11 | * - is there a better approach to frontend_default_colour? | ||
12 | * | ||
13 | * - do we need any more options in the Window menu? | ||
14 | * | ||
15 | * - can / should we be doing anything with the titles of the | ||
16 | * configuration boxes? | ||
17 | * | ||
18 | * - not sure what I should be doing about default window | ||
19 | * placement. Centring new windows is a bit feeble, but what's | ||
20 | * better? Is there a standard way to tell the OS "here's the | ||
21 | * _size_ of window I want, now use your best judgment about the | ||
22 | * initial position"? | ||
23 | * + there's a standard _policy_ on window placement, given in | ||
24 | * the HI guidelines. Have to implement it ourselves though, | ||
25 | * bah. | ||
26 | * | ||
27 | * - a brief frob of the Mac numeric keypad suggests that it | ||
28 | * generates numbers no matter what you do. I wonder if I should | ||
29 | * try to figure out a way of detecting keypad codes so I can | ||
30 | * implement UP_LEFT and friends. Alternatively, perhaps I | ||
31 | * should simply assign the number keys to UP_LEFT et al? | ||
32 | * They're not in use for anything else right now. | ||
33 | * | ||
34 | * - see if we can do anything to one-button-ise the multi-button | ||
35 | * dependent puzzle UIs: | ||
36 | * - Pattern is a _little_ unwieldy but not too bad (since | ||
37 | * generally you never need the middle button unless you've | ||
38 | * made a mistake, so it's just click versus command-click). | ||
39 | * - Net is utterly vile; having normal click be one rotate and | ||
40 | * command-click be the other introduces a horrid asymmetry, | ||
41 | * and yet requiring a shift key for _each_ click would be | ||
42 | * even worse because rotation feels as if it ought to be the | ||
43 | * default action. I fear this is why the Flash Net had the | ||
44 | * UI it did... | ||
45 | * + I've tried out an alternative dragging interface for | ||
46 | * Net; it might work nicely for stylus-based platforms | ||
47 | * where you have better hand/eye feedback for the thing | ||
48 | * you're clicking on, but it's rather unwieldy on the | ||
49 | * Mac. I fear even shift-clicking is better than that. | ||
50 | * | ||
51 | * - Should we _return_ to a game configuration sheet once an | ||
52 | * error is reported by midend_set_config, to allow the user to | ||
53 | * correct the one faulty input and keep the other five OK ones? | ||
54 | * The Apple `one sheet at a time' restriction would require me | ||
55 | * to do this by closing the config sheet, opening the alert | ||
56 | * sheet, and then reopening the config sheet when the alert is | ||
57 | * closed; and the human interface types, who presumably | ||
58 | * invented the one-sheet-at-a-time rule for good reasons, might | ||
59 | * look with disfavour on me trying to get round them to fake a | ||
60 | * nested sheet. On the other hand I think there are good | ||
61 | * practical reasons for wanting it that way. Uncertain. | ||
62 | * | ||
63 | * - User feedback dislikes nothing happening when you start the | ||
64 | * app; they suggest a finder-like window containing an icon for | ||
65 | * each puzzle type, enabling you to start one easily. Needs | ||
66 | * thought. | ||
67 | * | ||
68 | * Grotty implementation details that could probably be improved: | ||
69 | * | ||
70 | * - I am _utterly_ unconvinced that NSImageView was the right way | ||
71 | * to go about having a window with a reliable backing store! It | ||
72 | * just doesn't feel right; NSImageView is a _control_. Is there | ||
73 | * a simpler way? | ||
74 | * | ||
75 | * - Resizing is currently very bad; rather than bother to work | ||
76 | * out how to resize the NSImageView, I just splatter and | ||
77 | * recreate it. | ||
78 | */ | ||
79 | |||
80 | #define COMBINED /* we put all the puzzles in one binary in this port */ | ||
81 | |||
82 | #include <ctype.h> | ||
83 | #include <time.h> | ||
84 | #include <sys/time.h> | ||
85 | #import <Cocoa/Cocoa.h> | ||
86 | #include "puzzles.h" | ||
87 | |||
88 | /* ---------------------------------------------------------------------- | ||
89 | * Global variables. | ||
90 | */ | ||
91 | |||
92 | /* | ||
93 | * The `Type' menu. We frob this dynamically to allow the user to | ||
94 | * choose a preset set of settings from the current game. | ||
95 | */ | ||
96 | NSMenu *typemenu; | ||
97 | |||
98 | /* | ||
99 | * Forward reference. | ||
100 | */ | ||
101 | extern const struct drawing_api osx_drawing; | ||
102 | |||
103 | /* | ||
104 | * The NSApplication shared instance, which I'll want to refer to from | ||
105 | * a few places here and there. | ||
106 | */ | ||
107 | NSApplication *app; | ||
108 | |||
109 | /* ---------------------------------------------------------------------- | ||
110 | * Miscellaneous support routines that aren't part of any object or | ||
111 | * clearly defined subsystem. | ||
112 | */ | ||
113 | |||
114 | void fatal(char *fmt, ...) | ||
115 | { | ||
116 | va_list ap; | ||
117 | char errorbuf[2048]; | ||
118 | NSAlert *alert; | ||
119 | |||
120 | va_start(ap, fmt); | ||
121 | vsnprintf(errorbuf, lenof(errorbuf), fmt, ap); | ||
122 | va_end(ap); | ||
123 | |||
124 | alert = [NSAlert alloc]; | ||
125 | /* | ||
126 | * We may have come here because we ran out of memory, in which | ||
127 | * case it's entirely likely that that alloc will fail, so we | ||
128 | * should have a fallback of some sort. | ||
129 | */ | ||
130 | if (!alert) { | ||
131 | fprintf(stderr, "fatal error (and NSAlert failed): %s\n", errorbuf); | ||
132 | } else { | ||
133 | alert = [[alert init] autorelease]; | ||
134 | [alert addButtonWithTitle:@"Oh dear"]; | ||
135 | [alert setInformativeText:[NSString stringWithUTF8String:errorbuf]]; | ||
136 | [alert runModal]; | ||
137 | } | ||
138 | exit(1); | ||
139 | } | ||
140 | |||
141 | void frontend_default_colour(frontend *fe, float *output) | ||
142 | { | ||
143 | /* FIXME: Is there a system default we can tap into for this? */ | ||
144 | output[0] = output[1] = output[2] = 0.8F; | ||
145 | } | ||
146 | |||
147 | void get_random_seed(void **randseed, int *randseedsize) | ||
148 | { | ||
149 | time_t *tp = snew(time_t); | ||
150 | time(tp); | ||
151 | *randseed = (void *)tp; | ||
152 | *randseedsize = sizeof(time_t); | ||
153 | } | ||
154 | |||
155 | static void savefile_write(void *wctx, void *buf, int len) | ||
156 | { | ||
157 | FILE *fp = (FILE *)wctx; | ||
158 | fwrite(buf, 1, len, fp); | ||
159 | } | ||
160 | |||
161 | static int savefile_read(void *wctx, void *buf, int len) | ||
162 | { | ||
163 | FILE *fp = (FILE *)wctx; | ||
164 | int ret; | ||
165 | |||
166 | ret = fread(buf, 1, len, fp); | ||
167 | return (ret == len); | ||
168 | } | ||
169 | |||
170 | /* | ||
171 | * Since this front end does not support printing (yet), we need | ||
172 | * this stub to satisfy the reference in midend_print_puzzle(). | ||
173 | */ | ||
174 | void document_add_puzzle(document *doc, const game *game, game_params *par, | ||
175 | game_state *st, game_state *st2) | ||
176 | { | ||
177 | } | ||
178 | |||
179 | /* | ||
180 | * setAppleMenu isn't listed in the NSApplication header, but an | ||
181 | * NSApp responds to it, so we're adding it here to silence | ||
182 | * warnings. (This was removed from the headers in 10.4, so we | ||
183 | * only need to include it for 10.4+.) | ||
184 | */ | ||
185 | #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1040 | ||
186 | @interface NSApplication(NSAppleMenu) | ||
187 | - (void)setAppleMenu:(NSMenu *)menu; | ||
188 | @end | ||
189 | #endif | ||
190 | |||
191 | /* ---------------------------------------------------------------------- | ||
192 | * Tiny extension to NSMenuItem which carries a payload of a `void | ||
193 | * *', allowing several menu items to invoke the same message but | ||
194 | * pass different data through it. | ||
195 | */ | ||
196 | @interface DataMenuItem : NSMenuItem | ||
197 | { | ||
198 | void *payload; | ||
199 | } | ||
200 | - (void)setPayload:(void *)d; | ||
201 | - (void *)getPayload; | ||
202 | @end | ||
203 | @implementation DataMenuItem | ||
204 | - (void)setPayload:(void *)d | ||
205 | { | ||
206 | payload = d; | ||
207 | } | ||
208 | - (void *)getPayload | ||
209 | { | ||
210 | return payload; | ||
211 | } | ||
212 | @end | ||
213 | |||
214 | /* ---------------------------------------------------------------------- | ||
215 | * Utility routines for constructing OS X menus. | ||
216 | */ | ||
217 | |||
218 | NSMenu *newmenu(const char *title) | ||
219 | { | ||
220 | return [[[NSMenu allocWithZone:[NSMenu menuZone]] | ||
221 | initWithTitle:[NSString stringWithUTF8String:title]] | ||
222 | autorelease]; | ||
223 | } | ||
224 | |||
225 | NSMenu *newsubmenu(NSMenu *parent, const char *title) | ||
226 | { | ||
227 | NSMenuItem *item; | ||
228 | NSMenu *child; | ||
229 | |||
230 | item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] | ||
231 | initWithTitle:[NSString stringWithUTF8String:title] | ||
232 | action:NULL | ||
233 | keyEquivalent:@""] | ||
234 | autorelease]; | ||
235 | child = newmenu(title); | ||
236 | [item setEnabled:YES]; | ||
237 | [item setSubmenu:child]; | ||
238 | [parent addItem:item]; | ||
239 | return child; | ||
240 | } | ||
241 | |||
242 | id initnewitem(NSMenuItem *item, NSMenu *parent, const char *title, | ||
243 | const char *key, id target, SEL action) | ||
244 | { | ||
245 | unsigned mask = NSCommandKeyMask; | ||
246 | |||
247 | if (key[strcspn(key, "-")]) { | ||
248 | while (*key && *key != '-') { | ||
249 | int c = tolower((unsigned char)*key); | ||
250 | if (c == 's') { | ||
251 | mask |= NSShiftKeyMask; | ||
252 | } else if (c == 'o' || c == 'a') { | ||
253 | mask |= NSAlternateKeyMask; | ||
254 | } | ||
255 | key++; | ||
256 | } | ||
257 | if (*key) | ||
258 | key++; | ||
259 | } | ||
260 | |||
261 | item = [[item initWithTitle:[NSString stringWithUTF8String:title] | ||
262 | action:NULL | ||
263 | keyEquivalent:[NSString stringWithUTF8String:key]] | ||
264 | autorelease]; | ||
265 | |||
266 | if (*key) | ||
267 | [item setKeyEquivalentModifierMask: mask]; | ||
268 | |||
269 | [item setEnabled:YES]; | ||
270 | [item setTarget:target]; | ||
271 | [item setAction:action]; | ||
272 | |||
273 | [parent addItem:item]; | ||
274 | |||
275 | return item; | ||
276 | } | ||
277 | |||
278 | NSMenuItem *newitem(NSMenu *parent, char *title, char *key, | ||
279 | id target, SEL action) | ||
280 | { | ||
281 | return initnewitem([NSMenuItem allocWithZone:[NSMenu menuZone]], | ||
282 | parent, title, key, target, action); | ||
283 | } | ||
284 | |||
285 | /* ---------------------------------------------------------------------- | ||
286 | * About box. | ||
287 | */ | ||
288 | |||
289 | @class AboutBox; | ||
290 | |||
291 | @interface AboutBox : NSWindow | ||
292 | { | ||
293 | } | ||
294 | - (id)init; | ||
295 | @end | ||
296 | |||
297 | @implementation AboutBox | ||
298 | - (id)init | ||
299 | { | ||
300 | NSRect totalrect; | ||
301 | NSView *views[16]; | ||
302 | int nviews = 0; | ||
303 | NSImageView *iv; | ||
304 | NSTextField *tf; | ||
305 | NSFont *font1 = [NSFont systemFontOfSize:0]; | ||
306 | NSFont *font2 = [NSFont boldSystemFontOfSize:[font1 pointSize] * 1.1]; | ||
307 | const int border = 24; | ||
308 | int i; | ||
309 | double y; | ||
310 | |||
311 | /* | ||
312 | * Construct the controls that go in the About box. | ||
313 | */ | ||
314 | |||
315 | iv = [[NSImageView alloc] initWithFrame:NSMakeRect(0,0,64,64)]; | ||
316 | [iv setImage:[NSImage imageNamed:@"NSApplicationIcon"]]; | ||
317 | views[nviews++] = iv; | ||
318 | |||
319 | tf = [[NSTextField alloc] | ||
320 | initWithFrame:NSMakeRect(0,0,400,1)]; | ||
321 | [tf setEditable:NO]; | ||
322 | [tf setSelectable:NO]; | ||
323 | [tf setBordered:NO]; | ||
324 | [tf setDrawsBackground:NO]; | ||
325 | [tf setFont:font2]; | ||
326 | [tf setStringValue:@"Simon Tatham's Portable Puzzle Collection"]; | ||
327 | [tf sizeToFit]; | ||
328 | views[nviews++] = tf; | ||
329 | |||
330 | tf = [[NSTextField alloc] | ||
331 | initWithFrame:NSMakeRect(0,0,400,1)]; | ||
332 | [tf setEditable:NO]; | ||
333 | [tf setSelectable:NO]; | ||
334 | [tf setBordered:NO]; | ||
335 | [tf setDrawsBackground:NO]; | ||
336 | [tf setFont:font1]; | ||
337 | [tf setStringValue:[NSString stringWithUTF8String:ver]]; | ||
338 | [tf sizeToFit]; | ||
339 | views[nviews++] = tf; | ||
340 | |||
341 | /* | ||
342 | * Lay the controls out. | ||
343 | */ | ||
344 | totalrect = NSMakeRect(0,0,0,0); | ||
345 | for (i = 0; i < nviews; i++) { | ||
346 | NSRect r = [views[i] frame]; | ||
347 | if (totalrect.size.width < r.size.width) | ||
348 | totalrect.size.width = r.size.width; | ||
349 | totalrect.size.height += border + r.size.height; | ||
350 | } | ||
351 | totalrect.size.width += 2 * border; | ||
352 | totalrect.size.height += border; | ||
353 | y = totalrect.size.height; | ||
354 | for (i = 0; i < nviews; i++) { | ||
355 | NSRect r = [views[i] frame]; | ||
356 | r.origin.x = (totalrect.size.width - r.size.width) / 2; | ||
357 | y -= border + r.size.height; | ||
358 | r.origin.y = y; | ||
359 | [views[i] setFrame:r]; | ||
360 | } | ||
361 | |||
362 | self = [super initWithContentRect:totalrect | ||
363 | styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask | | ||
364 | NSClosableWindowMask) | ||
365 | backing:NSBackingStoreBuffered | ||
366 | defer:YES]; | ||
367 | |||
368 | for (i = 0; i < nviews; i++) | ||
369 | [[self contentView] addSubview:views[i]]; | ||
370 | |||
371 | [self center]; /* :-) */ | ||
372 | |||
373 | return self; | ||
374 | } | ||
375 | @end | ||
376 | |||
377 | /* ---------------------------------------------------------------------- | ||
378 | * The front end presented to midend.c. | ||
379 | * | ||
380 | * This is mostly a subclass of NSWindow. The actual `frontend' | ||
381 | * structure passed to the midend contains a variety of pointers, | ||
382 | * including that window object but also including the image we | ||
383 | * draw on, an ImageView to display it in the window, and so on. | ||
384 | */ | ||
385 | |||
386 | @class GameWindow; | ||
387 | @class MyImageView; | ||
388 | |||
389 | struct frontend { | ||
390 | GameWindow *window; | ||
391 | NSImage *image; | ||
392 | MyImageView *view; | ||
393 | NSColor **colours; | ||
394 | int ncolours; | ||
395 | int clipped; | ||
396 | int w, h; | ||
397 | }; | ||
398 | |||
399 | @interface MyImageView : NSImageView | ||
400 | { | ||
401 | GameWindow *ourwin; | ||
402 | } | ||
403 | - (void)setWindow:(GameWindow *)win; | ||
404 | - (void)mouseEvent:(NSEvent *)ev button:(int)b; | ||
405 | - (void)mouseDown:(NSEvent *)ev; | ||
406 | - (void)mouseDragged:(NSEvent *)ev; | ||
407 | - (void)mouseUp:(NSEvent *)ev; | ||
408 | - (void)rightMouseDown:(NSEvent *)ev; | ||
409 | - (void)rightMouseDragged:(NSEvent *)ev; | ||
410 | - (void)rightMouseUp:(NSEvent *)ev; | ||
411 | - (void)otherMouseDown:(NSEvent *)ev; | ||
412 | - (void)otherMouseDragged:(NSEvent *)ev; | ||
413 | - (void)otherMouseUp:(NSEvent *)ev; | ||
414 | @end | ||
415 | |||
416 | @interface GameWindow : NSWindow | ||
417 | { | ||
418 | const game *ourgame; | ||
419 | midend *me; | ||
420 | struct frontend fe; | ||
421 | struct timeval last_time; | ||
422 | NSTimer *timer; | ||
423 | NSWindow *sheet; | ||
424 | config_item *cfg; | ||
425 | int cfg_which; | ||
426 | NSView **cfg_controls; | ||
427 | int cfg_ncontrols; | ||
428 | NSTextField *status; | ||
429 | struct preset_menu *preset_menu; | ||
430 | NSMenuItem **preset_menu_items; | ||
431 | int n_preset_menu_items; | ||
432 | } | ||
433 | - (id)initWithGame:(const game *)g; | ||
434 | - (void)dealloc; | ||
435 | - (void)processButton:(int)b x:(int)x y:(int)y; | ||
436 | - (void)processKey:(int)b; | ||
437 | - (void)keyDown:(NSEvent *)ev; | ||
438 | - (void)activateTimer; | ||
439 | - (void)deactivateTimer; | ||
440 | - (void)setStatusLine:(char *)text; | ||
441 | - (void)resizeForNewGameParams; | ||
442 | - (void)updateTypeMenuTick; | ||
443 | @end | ||
444 | |||
445 | @implementation MyImageView | ||
446 | |||
447 | - (void)setWindow:(GameWindow *)win | ||
448 | { | ||
449 | ourwin = win; | ||
450 | } | ||
451 | |||
452 | - (void)mouseEvent:(NSEvent *)ev button:(int)b | ||
453 | { | ||
454 | NSPoint point = [self convertPoint:[ev locationInWindow] fromView:nil]; | ||
455 | [ourwin processButton:b x:point.x y:point.y]; | ||
456 | } | ||
457 | |||
458 | - (void)mouseDown:(NSEvent *)ev | ||
459 | { | ||
460 | unsigned mod = [ev modifierFlags]; | ||
461 | [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_BUTTON : | ||
462 | (mod & NSShiftKeyMask) ? MIDDLE_BUTTON : | ||
463 | LEFT_BUTTON)]; | ||
464 | } | ||
465 | - (void)mouseDragged:(NSEvent *)ev | ||
466 | { | ||
467 | unsigned mod = [ev modifierFlags]; | ||
468 | [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_DRAG : | ||
469 | (mod & NSShiftKeyMask) ? MIDDLE_DRAG : | ||
470 | LEFT_DRAG)]; | ||
471 | } | ||
472 | - (void)mouseUp:(NSEvent *)ev | ||
473 | { | ||
474 | unsigned mod = [ev modifierFlags]; | ||
475 | [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_RELEASE : | ||
476 | (mod & NSShiftKeyMask) ? MIDDLE_RELEASE : | ||
477 | LEFT_RELEASE)]; | ||
478 | } | ||
479 | - (void)rightMouseDown:(NSEvent *)ev | ||
480 | { | ||
481 | unsigned mod = [ev modifierFlags]; | ||
482 | [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_BUTTON : | ||
483 | RIGHT_BUTTON)]; | ||
484 | } | ||
485 | - (void)rightMouseDragged:(NSEvent *)ev | ||
486 | { | ||
487 | unsigned mod = [ev modifierFlags]; | ||
488 | [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_DRAG : | ||
489 | RIGHT_DRAG)]; | ||
490 | } | ||
491 | - (void)rightMouseUp:(NSEvent *)ev | ||
492 | { | ||
493 | unsigned mod = [ev modifierFlags]; | ||
494 | [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_RELEASE : | ||
495 | RIGHT_RELEASE)]; | ||
496 | } | ||
497 | - (void)otherMouseDown:(NSEvent *)ev | ||
498 | { | ||
499 | [self mouseEvent:ev button:MIDDLE_BUTTON]; | ||
500 | } | ||
501 | - (void)otherMouseDragged:(NSEvent *)ev | ||
502 | { | ||
503 | [self mouseEvent:ev button:MIDDLE_DRAG]; | ||
504 | } | ||
505 | - (void)otherMouseUp:(NSEvent *)ev | ||
506 | { | ||
507 | [self mouseEvent:ev button:MIDDLE_RELEASE]; | ||
508 | } | ||
509 | @end | ||
510 | |||
511 | @implementation GameWindow | ||
512 | - (void)setupContentView | ||
513 | { | ||
514 | NSRect frame; | ||
515 | int w, h; | ||
516 | |||
517 | if (status) { | ||
518 | frame = [status frame]; | ||
519 | frame.origin.y = frame.size.height; | ||
520 | } else | ||
521 | frame.origin.y = 0; | ||
522 | frame.origin.x = 0; | ||
523 | |||
524 | w = h = INT_MAX; | ||
525 | midend_size(me, &w, &h, FALSE); | ||
526 | frame.size.width = w; | ||
527 | frame.size.height = h; | ||
528 | fe.w = w; | ||
529 | fe.h = h; | ||
530 | |||
531 | fe.image = [[NSImage alloc] initWithSize:frame.size]; | ||
532 | fe.view = [[MyImageView alloc] initWithFrame:frame]; | ||
533 | [fe.view setImage:fe.image]; | ||
534 | [fe.view setWindow:self]; | ||
535 | |||
536 | midend_redraw(me); | ||
537 | |||
538 | [[self contentView] addSubview:fe.view]; | ||
539 | } | ||
540 | - (id)initWithGame:(const game *)g | ||
541 | { | ||
542 | NSRect rect = { {0,0}, {0,0} }, rect2; | ||
543 | int w, h; | ||
544 | |||
545 | ourgame = g; | ||
546 | preset_menu = NULL; | ||
547 | preset_menu_items = NULL; | ||
548 | |||
549 | fe.window = self; | ||
550 | |||
551 | me = midend_new(&fe, ourgame, &osx_drawing, &fe); | ||
552 | /* | ||
553 | * If we ever need to open a fresh window using a provided game | ||
554 | * ID, I think the right thing is to move most of this method | ||
555 | * into a new initWithGame:gameID: method, and have | ||
556 | * initWithGame: simply call that one and pass it NULL. | ||
557 | */ | ||
558 | midend_new_game(me); | ||
559 | w = h = INT_MAX; | ||
560 | midend_size(me, &w, &h, FALSE); | ||
561 | rect.size.width = w; | ||
562 | rect.size.height = h; | ||
563 | fe.w = w; | ||
564 | fe.h = h; | ||
565 | |||
566 | /* | ||
567 | * Create the status bar, which will just be an NSTextField. | ||
568 | */ | ||
569 | if (midend_wants_statusbar(me)) { | ||
570 | status = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,100,50)]; | ||
571 | [status setEditable:NO]; | ||
572 | [status setSelectable:NO]; | ||
573 | [status setBordered:YES]; | ||
574 | [status setBezeled:YES]; | ||
575 | [status setBezelStyle:NSTextFieldSquareBezel]; | ||
576 | [status setDrawsBackground:YES]; | ||
577 | [[status cell] setTitle:@DEFAULT_STATUSBAR_TEXT]; | ||
578 | [status sizeToFit]; | ||
579 | rect2 = [status frame]; | ||
580 | rect.size.height += rect2.size.height; | ||
581 | rect2.size.width = rect.size.width; | ||
582 | rect2.origin.x = rect2.origin.y = 0; | ||
583 | [status setFrame:rect2]; | ||
584 | } else | ||
585 | status = nil; | ||
586 | |||
587 | self = [super initWithContentRect:rect | ||
588 | styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask | | ||
589 | NSClosableWindowMask) | ||
590 | backing:NSBackingStoreBuffered | ||
591 | defer:YES]; | ||
592 | [self setTitle:[NSString stringWithUTF8String:ourgame->name]]; | ||
593 | |||
594 | { | ||
595 | float *colours; | ||
596 | int i, ncolours; | ||
597 | |||
598 | colours = midend_colours(me, &ncolours); | ||
599 | fe.ncolours = ncolours; | ||
600 | fe.colours = snewn(ncolours, NSColor *); | ||
601 | |||
602 | for (i = 0; i < ncolours; i++) { | ||
603 | fe.colours[i] = [[NSColor colorWithDeviceRed:colours[i*3] | ||
604 | green:colours[i*3+1] blue:colours[i*3+2] | ||
605 | alpha:1.0] retain]; | ||
606 | } | ||
607 | } | ||
608 | |||
609 | [self setupContentView]; | ||
610 | if (status) | ||
611 | [[self contentView] addSubview:status]; | ||
612 | [self setIgnoresMouseEvents:NO]; | ||
613 | |||
614 | [self center]; /* :-) */ | ||
615 | |||
616 | return self; | ||
617 | } | ||
618 | |||
619 | - (void)dealloc | ||
620 | { | ||
621 | int i; | ||
622 | for (i = 0; i < fe.ncolours; i++) { | ||
623 | [fe.colours[i] release]; | ||
624 | } | ||
625 | sfree(fe.colours); | ||
626 | sfree(preset_menu_items); | ||
627 | midend_free(me); | ||
628 | [super dealloc]; | ||
629 | } | ||
630 | |||
631 | - (void)processButton:(int)b x:(int)x y:(int)y | ||
632 | { | ||
633 | if (!midend_process_key(me, x, fe.h - 1 - y, b)) | ||
634 | [self close]; | ||
635 | } | ||
636 | |||
637 | - (void)processKey:(int)b | ||
638 | { | ||
639 | if (!midend_process_key(me, -1, -1, b)) | ||
640 | [self close]; | ||
641 | } | ||
642 | |||
643 | - (void)keyDown:(NSEvent *)ev | ||
644 | { | ||
645 | NSString *s = [ev characters]; | ||
646 | int i, n = [s length]; | ||
647 | |||
648 | for (i = 0; i < n; i++) { | ||
649 | int c = [s characterAtIndex:i]; | ||
650 | |||
651 | /* | ||
652 | * ASCII gets passed straight to midend_process_key. | ||
653 | * Anything above that has to be translated to our own | ||
654 | * function key codes. | ||
655 | */ | ||
656 | if (c >= 0x80) { | ||
657 | int mods = FALSE; | ||
658 | switch (c) { | ||
659 | case NSUpArrowFunctionKey: | ||
660 | c = CURSOR_UP; | ||
661 | mods = TRUE; | ||
662 | break; | ||
663 | case NSDownArrowFunctionKey: | ||
664 | c = CURSOR_DOWN; | ||
665 | mods = TRUE; | ||
666 | break; | ||
667 | case NSLeftArrowFunctionKey: | ||
668 | c = CURSOR_LEFT; | ||
669 | mods = TRUE; | ||
670 | break; | ||
671 | case NSRightArrowFunctionKey: | ||
672 | c = CURSOR_RIGHT; | ||
673 | mods = TRUE; | ||
674 | break; | ||
675 | default: | ||
676 | continue; | ||
677 | } | ||
678 | |||
679 | if (mods) { | ||
680 | if ([ev modifierFlags] & NSShiftKeyMask) | ||
681 | c |= MOD_SHFT; | ||
682 | if ([ev modifierFlags] & NSControlKeyMask) | ||
683 | c |= MOD_CTRL; | ||
684 | } | ||
685 | } | ||
686 | |||
687 | if (c >= '0' && c <= '9' && ([ev modifierFlags] & NSNumericPadKeyMask)) | ||
688 | c |= MOD_NUM_KEYPAD; | ||
689 | |||
690 | [self processKey:c]; | ||
691 | } | ||
692 | } | ||
693 | |||
694 | - (void)activateTimer | ||
695 | { | ||
696 | if (timer != nil) | ||
697 | return; | ||
698 | |||
699 | timer = [NSTimer scheduledTimerWithTimeInterval:0.02 | ||
700 | target:self selector:@selector(timerTick:) | ||
701 | userInfo:nil repeats:YES]; | ||
702 | gettimeofday(&last_time, NULL); | ||
703 | } | ||
704 | |||
705 | - (void)deactivateTimer | ||
706 | { | ||
707 | if (timer == nil) | ||
708 | return; | ||
709 | |||
710 | [timer invalidate]; | ||
711 | timer = nil; | ||
712 | } | ||
713 | |||
714 | - (void)timerTick:(id)sender | ||
715 | { | ||
716 | struct timeval now; | ||
717 | float elapsed; | ||
718 | gettimeofday(&now, NULL); | ||
719 | elapsed = ((now.tv_usec - last_time.tv_usec) * 0.000001F + | ||
720 | (now.tv_sec - last_time.tv_sec)); | ||
721 | midend_timer(me, elapsed); | ||
722 | last_time = now; | ||
723 | } | ||
724 | |||
725 | - (void)showError:(char *)message | ||
726 | { | ||
727 | NSAlert *alert; | ||
728 | |||
729 | alert = [[[NSAlert alloc] init] autorelease]; | ||
730 | [alert addButtonWithTitle:@"Bah"]; | ||
731 | [alert setInformativeText:[NSString stringWithUTF8String:message]]; | ||
732 | [alert beginSheetModalForWindow:self modalDelegate:nil | ||
733 | didEndSelector:NULL contextInfo:nil]; | ||
734 | } | ||
735 | |||
736 | - (void)newGame:(id)sender | ||
737 | { | ||
738 | [self processKey:'n']; | ||
739 | } | ||
740 | - (void)restartGame:(id)sender | ||
741 | { | ||
742 | midend_restart_game(me); | ||
743 | } | ||
744 | - (void)saveGame:(id)sender | ||
745 | { | ||
746 | NSSavePanel *sp = [NSSavePanel savePanel]; | ||
747 | |||
748 | if ([sp runModal] == NSFileHandlingPanelOKButton) { | ||
749 | const char *name = [[sp filename] UTF8String]; | ||
750 | |||
751 | FILE *fp = fopen(name, "w"); | ||
752 | |||
753 | if (!fp) { | ||
754 | [self showError:"Unable to open save file"]; | ||
755 | return; | ||
756 | } | ||
757 | |||
758 | midend_serialise(me, savefile_write, fp); | ||
759 | |||
760 | fclose(fp); | ||
761 | } | ||
762 | } | ||
763 | - (void)loadSavedGame:(id)sender | ||
764 | { | ||
765 | NSOpenPanel *op = [NSOpenPanel openPanel]; | ||
766 | |||
767 | [op setAllowsMultipleSelection:NO]; | ||
768 | |||
769 | if ([op runModalForTypes:nil] == NSOKButton) { | ||
770 | /* | ||
771 | * This used to be | ||
772 | * | ||
773 | * [[[op filenames] objectAtIndex:0] cString] | ||
774 | * | ||
775 | * but the plain cString method became deprecated and Xcode 7 | ||
776 | * started complaining about it. Since OS X 10.9 we can | ||
777 | * apparently use the more modern API | ||
778 | * | ||
779 | * [[[op URLs] objectAtIndex:0] fileSystemRepresentation] | ||
780 | * | ||
781 | * but the alternative below still compiles with Xcode 7 and | ||
782 | * is a bit more backwards compatible, so I'll try it for the | ||
783 | * moment. | ||
784 | */ | ||
785 | const char *name = [[[op filenames] objectAtIndex:0] | ||
786 | cStringUsingEncoding: | ||
787 | [NSString defaultCStringEncoding]]; | ||
788 | char *err; | ||
789 | |||
790 | FILE *fp = fopen(name, "r"); | ||
791 | |||
792 | if (!fp) { | ||
793 | [self showError:"Unable to open saved game file"]; | ||
794 | return; | ||
795 | } | ||
796 | |||
797 | err = midend_deserialise(me, savefile_read, fp); | ||
798 | |||
799 | fclose(fp); | ||
800 | |||
801 | if (err) { | ||
802 | [self showError:err]; | ||
803 | return; | ||
804 | } | ||
805 | |||
806 | [self resizeForNewGameParams]; | ||
807 | [self updateTypeMenuTick]; | ||
808 | } | ||
809 | } | ||
810 | - (void)undoMove:(id)sender | ||
811 | { | ||
812 | [self processKey:'u']; | ||
813 | } | ||
814 | - (void)redoMove:(id)sender | ||
815 | { | ||
816 | [self processKey:'r'&0x1F]; | ||
817 | } | ||
818 | |||
819 | - (void)copy:(id)sender | ||
820 | { | ||
821 | char *text; | ||
822 | |||
823 | if ((text = midend_text_format(me)) != NULL) { | ||
824 | NSPasteboard *pb = [NSPasteboard generalPasteboard]; | ||
825 | NSArray *a = [NSArray arrayWithObject:NSStringPboardType]; | ||
826 | [pb declareTypes:a owner:nil]; | ||
827 | [pb setString:[NSString stringWithUTF8String:text] | ||
828 | forType:NSStringPboardType]; | ||
829 | } else | ||
830 | NSBeep(); | ||
831 | } | ||
832 | |||
833 | - (void)solveGame:(id)sender | ||
834 | { | ||
835 | char *msg; | ||
836 | |||
837 | msg = midend_solve(me); | ||
838 | |||
839 | if (msg) | ||
840 | [self showError:msg]; | ||
841 | } | ||
842 | |||
843 | - (BOOL)validateMenuItem:(NSMenuItem *)item | ||
844 | { | ||
845 | if ([item action] == @selector(copy:)) | ||
846 | return (ourgame->can_format_as_text_ever && | ||
847 | midend_can_format_as_text_now(me) ? YES : NO); | ||
848 | else if ([item action] == @selector(solveGame:)) | ||
849 | return (ourgame->can_solve ? YES : NO); | ||
850 | else | ||
851 | return [super validateMenuItem:item]; | ||
852 | } | ||
853 | |||
854 | - (void)clearTypeMenu | ||
855 | { | ||
856 | int i; | ||
857 | |||
858 | while ([typemenu numberOfItems] > 1) | ||
859 | [typemenu removeItemAtIndex:0]; | ||
860 | [[typemenu itemAtIndex:0] setState:NSOffState]; | ||
861 | |||
862 | for (i = 0; i < n_preset_menu_items; i++) | ||
863 | preset_menu_items[i] = NULL; | ||
864 | } | ||
865 | |||
866 | - (void)updateTypeMenuTick | ||
867 | { | ||
868 | int i, n; | ||
869 | |||
870 | n = midend_which_preset(me); | ||
871 | |||
872 | for (i = 0; i < n_preset_menu_items; i++) | ||
873 | if (preset_menu_items[i]) | ||
874 | [preset_menu_items[i] setState:(i == n ? NSOnState : NSOffState)]; | ||
875 | |||
876 | /* | ||
877 | * The Custom menu item is always right at the bottom of the | ||
878 | * Type menu. | ||
879 | */ | ||
880 | [[typemenu itemAtIndex:[typemenu numberOfItems]-1] | ||
881 | setState:(n < 0 ? NSOnState : NSOffState)]; | ||
882 | } | ||
883 | |||
884 | - (void)populateTypeMenu:(NSMenu *)nsmenu from:(struct preset_menu *)menu | ||
885 | { | ||
886 | int i; | ||
887 | |||
888 | /* | ||
889 | * We process the entries in reverse order so that (in the | ||
890 | * top-level Type menu at least) we don't disturb the 'Custom' | ||
891 | * item which remains fixed even when we change back and forth | ||
892 | * between puzzle type windows. | ||
893 | */ | ||
894 | for (i = menu->n_entries; i-- > 0 ;) { | ||
895 | struct preset_menu_entry *entry = &menu->entries[i]; | ||
896 | NSMenuItem *item; | ||
897 | |||
898 | if (entry->params) { | ||
899 | DataMenuItem *ditem; | ||
900 | ditem = [[[DataMenuItem alloc] | ||
901 | initWithTitle:[NSString stringWithUTF8String: | ||
902 | entry->title] | ||
903 | action:NULL keyEquivalent:@""] | ||
904 | autorelease]; | ||
905 | |||
906 | [ditem setTarget:self]; | ||
907 | [ditem setAction:@selector(presetGame:)]; | ||
908 | [ditem setPayload:entry->params]; | ||
909 | |||
910 | preset_menu_items[entry->id] = ditem; | ||
911 | |||
912 | item = ditem; | ||
913 | } else { | ||
914 | NSMenu *nssubmenu; | ||
915 | |||
916 | item = [[[NSMenuItem alloc] | ||
917 | initWithTitle:[NSString stringWithUTF8String: | ||
918 | entry->title] | ||
919 | action:NULL keyEquivalent:@""] | ||
920 | autorelease]; | ||
921 | nssubmenu = newmenu(entry->title); | ||
922 | [item setSubmenu:nssubmenu]; | ||
923 | |||
924 | [self populateTypeMenu:nssubmenu from:entry->submenu]; | ||
925 | } | ||
926 | |||
927 | [item setEnabled:YES]; | ||
928 | [nsmenu insertItem:item atIndex:0]; | ||
929 | } | ||
930 | } | ||
931 | |||
932 | - (void)becomeKeyWindow | ||
933 | { | ||
934 | [self clearTypeMenu]; | ||
935 | |||
936 | [super becomeKeyWindow]; | ||
937 | |||
938 | if (!preset_menu) { | ||
939 | int i; | ||
940 | preset_menu = midend_get_presets(me, &n_preset_menu_items); | ||
941 | preset_menu_items = snewn(n_preset_menu_items, NSMenuItem *); | ||
942 | for (i = 0; i < n_preset_menu_items; i++) | ||
943 | preset_menu_items[i] = NULL; | ||
944 | } | ||
945 | |||
946 | if (preset_menu->n_entries > 0) { | ||
947 | [typemenu insertItem:[NSMenuItem separatorItem] atIndex:0]; | ||
948 | [self populateTypeMenu:typemenu from:preset_menu]; | ||
949 | } | ||
950 | |||
951 | [self updateTypeMenuTick]; | ||
952 | } | ||
953 | |||
954 | - (void)resignKeyWindow | ||
955 | { | ||
956 | [self clearTypeMenu]; | ||
957 | [super resignKeyWindow]; | ||
958 | } | ||
959 | |||
960 | - (void)close | ||
961 | { | ||
962 | [self clearTypeMenu]; | ||
963 | [super close]; | ||
964 | } | ||
965 | |||
966 | - (void)resizeForNewGameParams | ||
967 | { | ||
968 | NSSize size = {0,0}; | ||
969 | int w, h; | ||
970 | |||
971 | w = h = INT_MAX; | ||
972 | midend_size(me, &w, &h, FALSE); | ||
973 | size.width = w; | ||
974 | size.height = h; | ||
975 | fe.w = w; | ||
976 | fe.h = h; | ||
977 | |||
978 | if (status) { | ||
979 | NSRect frame = [status frame]; | ||
980 | size.height += frame.size.height; | ||
981 | frame.size.width = size.width; | ||
982 | [status setFrame:frame]; | ||
983 | } | ||
984 | |||
985 | #ifndef GNUSTEP | ||
986 | NSDisableScreenUpdates(); | ||
987 | #endif | ||
988 | [self setContentSize:size]; | ||
989 | [self setupContentView]; | ||
990 | #ifndef GNUSTEP | ||
991 | NSEnableScreenUpdates(); | ||
992 | #endif | ||
993 | } | ||
994 | |||
995 | - (void)presetGame:(id)sender | ||
996 | { | ||
997 | game_params *params = [sender getPayload]; | ||
998 | |||
999 | midend_set_params(me, params); | ||
1000 | midend_new_game(me); | ||
1001 | |||
1002 | [self resizeForNewGameParams]; | ||
1003 | [self updateTypeMenuTick]; | ||
1004 | } | ||
1005 | |||
1006 | - (void)startConfigureSheet:(int)which | ||
1007 | { | ||
1008 | NSButton *ok, *cancel; | ||
1009 | int actw, acth, leftw, rightw, totalw, h, thish, y; | ||
1010 | int k; | ||
1011 | NSRect rect, tmprect; | ||
1012 | const int SPACING = 16; | ||
1013 | char *title; | ||
1014 | config_item *i; | ||
1015 | int cfg_controlsize; | ||
1016 | NSTextField *tf; | ||
1017 | NSButton *b; | ||
1018 | NSPopUpButton *pb; | ||
1019 | |||
1020 | assert(sheet == NULL); | ||
1021 | |||
1022 | /* | ||
1023 | * Every control we create here is going to have this size | ||
1024 | * until we tell it to calculate a better one. | ||
1025 | */ | ||
1026 | tmprect = NSMakeRect(0, 0, 100, 50); | ||
1027 | |||
1028 | /* | ||
1029 | * Set up OK and Cancel buttons. (Actually, MacOS doesn't seem | ||
1030 | * to be fond of generic OK and Cancel wording, so I'm going to | ||
1031 | * rename them to something nicer.) | ||
1032 | */ | ||
1033 | actw = acth = 0; | ||
1034 | |||
1035 | cancel = [[NSButton alloc] initWithFrame:tmprect]; | ||
1036 | [cancel setBezelStyle:NSRoundedBezelStyle]; | ||
1037 | [cancel setTitle:@"Abandon"]; | ||
1038 | [cancel setTarget:self]; | ||
1039 | [cancel setKeyEquivalent:@"\033"]; | ||
1040 | [cancel setAction:@selector(sheetCancelButton:)]; | ||
1041 | [cancel sizeToFit]; | ||
1042 | rect = [cancel frame]; | ||
1043 | if (actw < rect.size.width) actw = rect.size.width; | ||
1044 | if (acth < rect.size.height) acth = rect.size.height; | ||
1045 | |||
1046 | ok = [[NSButton alloc] initWithFrame:tmprect]; | ||
1047 | [ok setBezelStyle:NSRoundedBezelStyle]; | ||
1048 | [ok setTitle:@"Accept"]; | ||
1049 | [ok setTarget:self]; | ||
1050 | [ok setKeyEquivalent:@"\r"]; | ||
1051 | [ok setAction:@selector(sheetOKButton:)]; | ||
1052 | [ok sizeToFit]; | ||
1053 | rect = [ok frame]; | ||
1054 | if (actw < rect.size.width) actw = rect.size.width; | ||
1055 | if (acth < rect.size.height) acth = rect.size.height; | ||
1056 | |||
1057 | totalw = SPACING + 2 * actw; | ||
1058 | h = 2 * SPACING + acth; | ||
1059 | |||
1060 | /* | ||
1061 | * Now fetch the midend config data and go through it creating | ||
1062 | * controls. | ||
1063 | */ | ||
1064 | cfg = midend_get_config(me, which, &title); | ||
1065 | sfree(title); /* FIXME: should we use this somehow? */ | ||
1066 | cfg_which = which; | ||
1067 | |||
1068 | cfg_ncontrols = cfg_controlsize = 0; | ||
1069 | cfg_controls = NULL; | ||
1070 | leftw = rightw = 0; | ||
1071 | for (i = cfg; i->type != C_END; i++) { | ||
1072 | if (cfg_controlsize < cfg_ncontrols + 5) { | ||
1073 | cfg_controlsize = cfg_ncontrols + 32; | ||
1074 | cfg_controls = sresize(cfg_controls, cfg_controlsize, NSView *); | ||
1075 | } | ||
1076 | |||
1077 | thish = 0; | ||
1078 | |||
1079 | switch (i->type) { | ||
1080 | case C_STRING: | ||
1081 | /* | ||
1082 | * Two NSTextFields, one being a label and the other | ||
1083 | * being an edit box. | ||
1084 | */ | ||
1085 | |||
1086 | tf = [[NSTextField alloc] initWithFrame:tmprect]; | ||
1087 | [tf setEditable:NO]; | ||
1088 | [tf setSelectable:NO]; | ||
1089 | [tf setBordered:NO]; | ||
1090 | [tf setDrawsBackground:NO]; | ||
1091 | [[tf cell] setTitle:[NSString stringWithUTF8String:i->name]]; | ||
1092 | [tf sizeToFit]; | ||
1093 | rect = [tf frame]; | ||
1094 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1095 | if (leftw < rect.size.width + 1) leftw = rect.size.width + 1; | ||
1096 | cfg_controls[cfg_ncontrols++] = tf; | ||
1097 | |||
1098 | tf = [[NSTextField alloc] initWithFrame:tmprect]; | ||
1099 | [tf setEditable:YES]; | ||
1100 | [tf setSelectable:YES]; | ||
1101 | [tf setBordered:YES]; | ||
1102 | [[tf cell] setTitle:[NSString stringWithUTF8String:i->sval]]; | ||
1103 | [tf sizeToFit]; | ||
1104 | rect = [tf frame]; | ||
1105 | /* | ||
1106 | * We impose a minimum and maximum width on editable | ||
1107 | * NSTextFields. If we allow them to size themselves to | ||
1108 | * the contents of the text within them, then they will | ||
1109 | * look very silly if that text is only one or two | ||
1110 | * characters, and equally silly if it's an absolutely | ||
1111 | * enormous Rectangles or Pattern game ID! | ||
1112 | */ | ||
1113 | if (rect.size.width < 75) rect.size.width = 75; | ||
1114 | if (rect.size.width > 400) rect.size.width = 400; | ||
1115 | |||
1116 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1117 | if (rightw < rect.size.width + 1) rightw = rect.size.width + 1; | ||
1118 | cfg_controls[cfg_ncontrols++] = tf; | ||
1119 | break; | ||
1120 | |||
1121 | case C_BOOLEAN: | ||
1122 | /* | ||
1123 | * A checkbox is an NSButton with a type of | ||
1124 | * NSSwitchButton. | ||
1125 | */ | ||
1126 | b = [[NSButton alloc] initWithFrame:tmprect]; | ||
1127 | [b setBezelStyle:NSRoundedBezelStyle]; | ||
1128 | [b setButtonType:NSSwitchButton]; | ||
1129 | [b setTitle:[NSString stringWithUTF8String:i->name]]; | ||
1130 | [b sizeToFit]; | ||
1131 | [b setState:(i->ival ? NSOnState : NSOffState)]; | ||
1132 | rect = [b frame]; | ||
1133 | if (totalw < rect.size.width + 1) totalw = rect.size.width + 1; | ||
1134 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1135 | cfg_controls[cfg_ncontrols++] = b; | ||
1136 | break; | ||
1137 | |||
1138 | case C_CHOICES: | ||
1139 | /* | ||
1140 | * A pop-up menu control is an NSPopUpButton, which | ||
1141 | * takes an embedded NSMenu. We also need an | ||
1142 | * NSTextField to act as a label. | ||
1143 | */ | ||
1144 | |||
1145 | tf = [[NSTextField alloc] initWithFrame:tmprect]; | ||
1146 | [tf setEditable:NO]; | ||
1147 | [tf setSelectable:NO]; | ||
1148 | [tf setBordered:NO]; | ||
1149 | [tf setDrawsBackground:NO]; | ||
1150 | [[tf cell] setTitle:[NSString stringWithUTF8String:i->name]]; | ||
1151 | [tf sizeToFit]; | ||
1152 | rect = [tf frame]; | ||
1153 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1154 | if (leftw < rect.size.width + 1) leftw = rect.size.width + 1; | ||
1155 | cfg_controls[cfg_ncontrols++] = tf; | ||
1156 | |||
1157 | pb = [[NSPopUpButton alloc] initWithFrame:tmprect pullsDown:NO]; | ||
1158 | [pb setBezelStyle:NSRoundedBezelStyle]; | ||
1159 | { | ||
1160 | char c, *p; | ||
1161 | |||
1162 | p = i->sval; | ||
1163 | c = *p++; | ||
1164 | while (*p) { | ||
1165 | char *q, *copy; | ||
1166 | |||
1167 | q = p; | ||
1168 | while (*p && *p != c) p++; | ||
1169 | |||
1170 | copy = snewn((p-q) + 1, char); | ||
1171 | memcpy(copy, q, p-q); | ||
1172 | copy[p-q] = '\0'; | ||
1173 | [pb addItemWithTitle:[NSString stringWithUTF8String:copy]]; | ||
1174 | sfree(copy); | ||
1175 | |||
1176 | if (*p) p++; | ||
1177 | } | ||
1178 | } | ||
1179 | [pb selectItemAtIndex:i->ival]; | ||
1180 | [pb sizeToFit]; | ||
1181 | |||
1182 | rect = [pb frame]; | ||
1183 | if (rightw < rect.size.width + 1) rightw = rect.size.width + 1; | ||
1184 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1185 | cfg_controls[cfg_ncontrols++] = pb; | ||
1186 | break; | ||
1187 | } | ||
1188 | |||
1189 | h += SPACING + thish; | ||
1190 | } | ||
1191 | |||
1192 | if (totalw < leftw + SPACING + rightw) | ||
1193 | totalw = leftw + SPACING + rightw; | ||
1194 | if (totalw > leftw + SPACING + rightw) { | ||
1195 | int excess = totalw - (leftw + SPACING + rightw); | ||
1196 | int leftexcess = leftw * excess / (leftw + rightw); | ||
1197 | int rightexcess = excess - leftexcess; | ||
1198 | leftw += leftexcess; | ||
1199 | rightw += rightexcess; | ||
1200 | } | ||
1201 | |||
1202 | /* | ||
1203 | * Now go through the list again, setting the final position | ||
1204 | * for each control. | ||
1205 | */ | ||
1206 | k = 0; | ||
1207 | y = h; | ||
1208 | for (i = cfg; i->type != C_END; i++) { | ||
1209 | y -= SPACING; | ||
1210 | thish = 0; | ||
1211 | switch (i->type) { | ||
1212 | case C_STRING: | ||
1213 | case C_CHOICES: | ||
1214 | /* | ||
1215 | * These two are treated identically, since both expect | ||
1216 | * a control on the left and another on the right. | ||
1217 | */ | ||
1218 | rect = [cfg_controls[k] frame]; | ||
1219 | if (thish < rect.size.height + 1) | ||
1220 | thish = rect.size.height + 1; | ||
1221 | rect = [cfg_controls[k+1] frame]; | ||
1222 | if (thish < rect.size.height + 1) | ||
1223 | thish = rect.size.height + 1; | ||
1224 | rect = [cfg_controls[k] frame]; | ||
1225 | rect.origin.y = y - thish/2 - rect.size.height/2; | ||
1226 | rect.origin.x = SPACING; | ||
1227 | rect.size.width = leftw; | ||
1228 | [cfg_controls[k] setFrame:rect]; | ||
1229 | rect = [cfg_controls[k+1] frame]; | ||
1230 | rect.origin.y = y - thish/2 - rect.size.height/2; | ||
1231 | rect.origin.x = 2 * SPACING + leftw; | ||
1232 | rect.size.width = rightw; | ||
1233 | [cfg_controls[k+1] setFrame:rect]; | ||
1234 | k += 2; | ||
1235 | break; | ||
1236 | |||
1237 | case C_BOOLEAN: | ||
1238 | rect = [cfg_controls[k] frame]; | ||
1239 | if (thish < rect.size.height + 1) | ||
1240 | thish = rect.size.height + 1; | ||
1241 | rect.origin.y = y - thish/2 - rect.size.height/2; | ||
1242 | rect.origin.x = SPACING; | ||
1243 | rect.size.width = totalw; | ||
1244 | [cfg_controls[k] setFrame:rect]; | ||
1245 | k++; | ||
1246 | break; | ||
1247 | } | ||
1248 | y -= thish; | ||
1249 | } | ||
1250 | |||
1251 | assert(k == cfg_ncontrols); | ||
1252 | |||
1253 | [cancel setFrame:NSMakeRect(SPACING+totalw/4-actw/2, SPACING, actw, acth)]; | ||
1254 | [ok setFrame:NSMakeRect(SPACING+3*totalw/4-actw/2, SPACING, actw, acth)]; | ||
1255 | |||
1256 | sheet = [[NSWindow alloc] | ||
1257 | initWithContentRect:NSMakeRect(0,0,totalw + 2*SPACING,h) | ||
1258 | styleMask:NSTitledWindowMask | NSClosableWindowMask | ||
1259 | backing:NSBackingStoreBuffered | ||
1260 | defer:YES]; | ||
1261 | |||
1262 | [[sheet contentView] addSubview:cancel]; | ||
1263 | [[sheet contentView] addSubview:ok]; | ||
1264 | |||
1265 | for (k = 0; k < cfg_ncontrols; k++) | ||
1266 | [[sheet contentView] addSubview:cfg_controls[k]]; | ||
1267 | |||
1268 | [app beginSheet:sheet modalForWindow:self | ||
1269 | modalDelegate:nil didEndSelector:NULL contextInfo:nil]; | ||
1270 | } | ||
1271 | |||
1272 | - (void)specificGame:(id)sender | ||
1273 | { | ||
1274 | [self startConfigureSheet:CFG_DESC]; | ||
1275 | } | ||
1276 | |||
1277 | - (void)specificRandomGame:(id)sender | ||
1278 | { | ||
1279 | [self startConfigureSheet:CFG_SEED]; | ||
1280 | } | ||
1281 | |||
1282 | - (void)customGameType:(id)sender | ||
1283 | { | ||
1284 | [self startConfigureSheet:CFG_SETTINGS]; | ||
1285 | } | ||
1286 | |||
1287 | - (void)sheetEndWithStatus:(BOOL)update | ||
1288 | { | ||
1289 | assert(sheet != NULL); | ||
1290 | [app endSheet:sheet]; | ||
1291 | [sheet orderOut:self]; | ||
1292 | sheet = NULL; | ||
1293 | if (update) { | ||
1294 | int k; | ||
1295 | config_item *i; | ||
1296 | char *error; | ||
1297 | |||
1298 | k = 0; | ||
1299 | for (i = cfg; i->type != C_END; i++) { | ||
1300 | switch (i->type) { | ||
1301 | case C_STRING: | ||
1302 | sfree(i->sval); | ||
1303 | i->sval = dupstr([[[(id)cfg_controls[k+1] cell] | ||
1304 | title] UTF8String]); | ||
1305 | k += 2; | ||
1306 | break; | ||
1307 | case C_BOOLEAN: | ||
1308 | i->ival = [(id)cfg_controls[k] state] == NSOnState; | ||
1309 | k++; | ||
1310 | break; | ||
1311 | case C_CHOICES: | ||
1312 | i->ival = [(id)cfg_controls[k+1] indexOfSelectedItem]; | ||
1313 | k += 2; | ||
1314 | break; | ||
1315 | } | ||
1316 | } | ||
1317 | |||
1318 | error = midend_set_config(me, cfg_which, cfg); | ||
1319 | if (error) { | ||
1320 | NSAlert *alert = [[[NSAlert alloc] init] autorelease]; | ||
1321 | [alert addButtonWithTitle:@"Bah"]; | ||
1322 | [alert setInformativeText:[NSString stringWithUTF8String:error]]; | ||
1323 | [alert beginSheetModalForWindow:self modalDelegate:nil | ||
1324 | didEndSelector:NULL contextInfo:nil]; | ||
1325 | } else { | ||
1326 | midend_new_game(me); | ||
1327 | [self resizeForNewGameParams]; | ||
1328 | [self updateTypeMenuTick]; | ||
1329 | } | ||
1330 | } | ||
1331 | sfree(cfg_controls); | ||
1332 | cfg_controls = NULL; | ||
1333 | } | ||
1334 | - (void)sheetOKButton:(id)sender | ||
1335 | { | ||
1336 | [self sheetEndWithStatus:YES]; | ||
1337 | } | ||
1338 | - (void)sheetCancelButton:(id)sender | ||
1339 | { | ||
1340 | [self sheetEndWithStatus:NO]; | ||
1341 | } | ||
1342 | |||
1343 | - (void)setStatusLine:(char *)text | ||
1344 | { | ||
1345 | [[status cell] setTitle:[NSString stringWithUTF8String:text]]; | ||
1346 | } | ||
1347 | |||
1348 | @end | ||
1349 | |||
1350 | /* | ||
1351 | * Drawing routines called by the midend. | ||
1352 | */ | ||
1353 | static void osx_draw_polygon(void *handle, int *coords, int npoints, | ||
1354 | int fillcolour, int outlinecolour) | ||
1355 | { | ||
1356 | frontend *fe = (frontend *)handle; | ||
1357 | NSBezierPath *path = [NSBezierPath bezierPath]; | ||
1358 | int i; | ||
1359 | |||
1360 | [[NSGraphicsContext currentContext] setShouldAntialias:YES]; | ||
1361 | |||
1362 | for (i = 0; i < npoints; i++) { | ||
1363 | NSPoint p = { coords[i*2] + 0.5, fe->h - coords[i*2+1] - 0.5 }; | ||
1364 | if (i == 0) | ||
1365 | [path moveToPoint:p]; | ||
1366 | else | ||
1367 | [path lineToPoint:p]; | ||
1368 | } | ||
1369 | |||
1370 | [path closePath]; | ||
1371 | |||
1372 | if (fillcolour >= 0) { | ||
1373 | assert(fillcolour >= 0 && fillcolour < fe->ncolours); | ||
1374 | [fe->colours[fillcolour] set]; | ||
1375 | [path fill]; | ||
1376 | } | ||
1377 | |||
1378 | assert(outlinecolour >= 0 && outlinecolour < fe->ncolours); | ||
1379 | [fe->colours[outlinecolour] set]; | ||
1380 | [path stroke]; | ||
1381 | } | ||
1382 | static void osx_draw_circle(void *handle, int cx, int cy, int radius, | ||
1383 | int fillcolour, int outlinecolour) | ||
1384 | { | ||
1385 | frontend *fe = (frontend *)handle; | ||
1386 | NSBezierPath *path = [NSBezierPath bezierPath]; | ||
1387 | |||
1388 | [[NSGraphicsContext currentContext] setShouldAntialias:YES]; | ||
1389 | |||
1390 | [path appendBezierPathWithArcWithCenter:NSMakePoint(cx+0.5, fe->h-cy-0.5) | ||
1391 | radius:radius startAngle:0.0 endAngle:360.0]; | ||
1392 | |||
1393 | [path closePath]; | ||
1394 | |||
1395 | if (fillcolour >= 0) { | ||
1396 | assert(fillcolour >= 0 && fillcolour < fe->ncolours); | ||
1397 | [fe->colours[fillcolour] set]; | ||
1398 | [path fill]; | ||
1399 | } | ||
1400 | |||
1401 | assert(outlinecolour >= 0 && outlinecolour < fe->ncolours); | ||
1402 | [fe->colours[outlinecolour] set]; | ||
1403 | [path stroke]; | ||
1404 | } | ||
1405 | static void osx_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) | ||
1406 | { | ||
1407 | frontend *fe = (frontend *)handle; | ||
1408 | NSBezierPath *path = [NSBezierPath bezierPath]; | ||
1409 | NSPoint p1 = { x1 + 0.5, fe->h - y1 - 0.5 }; | ||
1410 | NSPoint p2 = { x2 + 0.5, fe->h - y2 - 0.5 }; | ||
1411 | |||
1412 | [[NSGraphicsContext currentContext] setShouldAntialias:NO]; | ||
1413 | |||
1414 | assert(colour >= 0 && colour < fe->ncolours); | ||
1415 | [fe->colours[colour] set]; | ||
1416 | |||
1417 | [path moveToPoint:p1]; | ||
1418 | [path lineToPoint:p2]; | ||
1419 | [path stroke]; | ||
1420 | NSRectFill(NSMakeRect(x1, fe->h-y1-1, 1, 1)); | ||
1421 | NSRectFill(NSMakeRect(x2, fe->h-y2-1, 1, 1)); | ||
1422 | } | ||
1423 | |||
1424 | static void osx_draw_thick_line( | ||
1425 | void *handle, float thickness, | ||
1426 | float x1, float y1, | ||
1427 | float x2, float y2, | ||
1428 | int colour) | ||
1429 | { | ||
1430 | frontend *fe = (frontend *)handle; | ||
1431 | NSBezierPath *path = [NSBezierPath bezierPath]; | ||
1432 | |||
1433 | assert(colour >= 0 && colour < fe->ncolours); | ||
1434 | [fe->colours[colour] set]; | ||
1435 | [[NSGraphicsContext currentContext] setShouldAntialias: YES]; | ||
1436 | [path setLineWidth: thickness]; | ||
1437 | [path setLineCapStyle: NSButtLineCapStyle]; | ||
1438 | [path moveToPoint: NSMakePoint(x1, fe->h-y1)]; | ||
1439 | [path lineToPoint: NSMakePoint(x2, fe->h-y2)]; | ||
1440 | [path stroke]; | ||
1441 | } | ||
1442 | |||
1443 | static void osx_draw_rect(void *handle, int x, int y, int w, int h, int colour) | ||
1444 | { | ||
1445 | frontend *fe = (frontend *)handle; | ||
1446 | NSRect r = { {x, fe->h - y - h}, {w,h} }; | ||
1447 | |||
1448 | [[NSGraphicsContext currentContext] setShouldAntialias:NO]; | ||
1449 | |||
1450 | assert(colour >= 0 && colour < fe->ncolours); | ||
1451 | [fe->colours[colour] set]; | ||
1452 | |||
1453 | NSRectFill(r); | ||
1454 | } | ||
1455 | static void osx_draw_text(void *handle, int x, int y, int fonttype, | ||
1456 | int fontsize, int align, int colour, char *text) | ||
1457 | { | ||
1458 | frontend *fe = (frontend *)handle; | ||
1459 | NSString *string = [NSString stringWithUTF8String:text]; | ||
1460 | NSDictionary *attr; | ||
1461 | NSFont *font; | ||
1462 | NSSize size; | ||
1463 | NSPoint point; | ||
1464 | |||
1465 | [[NSGraphicsContext currentContext] setShouldAntialias:YES]; | ||
1466 | |||
1467 | assert(colour >= 0 && colour < fe->ncolours); | ||
1468 | |||
1469 | if (fonttype == FONT_FIXED) | ||
1470 | font = [NSFont userFixedPitchFontOfSize:fontsize]; | ||
1471 | else | ||
1472 | font = [NSFont userFontOfSize:fontsize]; | ||
1473 | |||
1474 | attr = [NSDictionary dictionaryWithObjectsAndKeys: | ||
1475 | fe->colours[colour], NSForegroundColorAttributeName, | ||
1476 | font, NSFontAttributeName, nil]; | ||
1477 | |||
1478 | point.x = x; | ||
1479 | point.y = fe->h - y; | ||
1480 | |||
1481 | size = [string sizeWithAttributes:attr]; | ||
1482 | if (align & ALIGN_HRIGHT) | ||
1483 | point.x -= size.width; | ||
1484 | else if (align & ALIGN_HCENTRE) | ||
1485 | point.x -= size.width / 2; | ||
1486 | if (align & ALIGN_VCENTRE) | ||
1487 | point.y -= size.height / 2; | ||
1488 | |||
1489 | [string drawAtPoint:point withAttributes:attr]; | ||
1490 | } | ||
1491 | static char *osx_text_fallback(void *handle, const char *const *strings, | ||
1492 | int nstrings) | ||
1493 | { | ||
1494 | /* | ||
1495 | * We assume OS X can cope with any UTF-8 likely to be emitted | ||
1496 | * by a puzzle. | ||
1497 | */ | ||
1498 | return dupstr(strings[0]); | ||
1499 | } | ||
1500 | struct blitter { | ||
1501 | int w, h; | ||
1502 | int x, y; | ||
1503 | NSImage *img; | ||
1504 | }; | ||
1505 | static blitter *osx_blitter_new(void *handle, int w, int h) | ||
1506 | { | ||
1507 | blitter *bl = snew(blitter); | ||
1508 | bl->x = bl->y = -1; | ||
1509 | bl->w = w; | ||
1510 | bl->h = h; | ||
1511 | bl->img = [[NSImage alloc] initWithSize:NSMakeSize(w, h)]; | ||
1512 | return bl; | ||
1513 | } | ||
1514 | static void osx_blitter_free(void *handle, blitter *bl) | ||
1515 | { | ||
1516 | [bl->img release]; | ||
1517 | sfree(bl); | ||
1518 | } | ||
1519 | static void osx_blitter_save(void *handle, blitter *bl, int x, int y) | ||
1520 | { | ||
1521 | frontend *fe = (frontend *)handle; | ||
1522 | int sx, sy, sX, sY, dx, dy, dX, dY; | ||
1523 | [fe->image unlockFocus]; | ||
1524 | [bl->img lockFocus]; | ||
1525 | |||
1526 | /* | ||
1527 | * Find the intersection of the source and destination rectangles, | ||
1528 | * so as to avoid trying to copy from outside the source image, | ||
1529 | * which GNUstep dislikes. | ||
1530 | * | ||
1531 | * Lower-case x,y coordinates are bottom left box corners; | ||
1532 | * upper-case X,Y are the top right. | ||
1533 | */ | ||
1534 | sx = x; sy = fe->h - y - bl->h; | ||
1535 | sX = sx + bl->w; sY = sy + bl->h; | ||
1536 | dx = dy = 0; | ||
1537 | dX = bl->w; dY = bl->h; | ||
1538 | if (sx < 0) { | ||
1539 | dx += -sx; | ||
1540 | sx = 0; | ||
1541 | } | ||
1542 | if (sy < 0) { | ||
1543 | dy += -sy; | ||
1544 | sy = 0; | ||
1545 | } | ||
1546 | if (sX > fe->w) { | ||
1547 | dX -= (sX - fe->w); | ||
1548 | sX = fe->w; | ||
1549 | } | ||
1550 | if (sY > fe->h) { | ||
1551 | dY -= (sY - fe->h); | ||
1552 | sY = fe->h; | ||
1553 | } | ||
1554 | |||
1555 | [fe->image drawInRect:NSMakeRect(dx, dy, dX-dx, dY-dy) | ||
1556 | fromRect:NSMakeRect(sx, sy, sX-sx, sY-sy) | ||
1557 | operation:NSCompositeCopy fraction:1.0]; | ||
1558 | [bl->img unlockFocus]; | ||
1559 | [fe->image lockFocus]; | ||
1560 | bl->x = x; | ||
1561 | bl->y = y; | ||
1562 | } | ||
1563 | static void osx_blitter_load(void *handle, blitter *bl, int x, int y) | ||
1564 | { | ||
1565 | frontend *fe = (frontend *)handle; | ||
1566 | if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) { | ||
1567 | x = bl->x; | ||
1568 | y = bl->y; | ||
1569 | } | ||
1570 | [bl->img drawInRect:NSMakeRect(x, fe->h - y - bl->h, bl->w, bl->h) | ||
1571 | fromRect:NSMakeRect(0, 0, bl->w, bl->h) | ||
1572 | operation:NSCompositeCopy fraction:1.0]; | ||
1573 | } | ||
1574 | static void osx_draw_update(void *handle, int x, int y, int w, int h) | ||
1575 | { | ||
1576 | frontend *fe = (frontend *)handle; | ||
1577 | [fe->view setNeedsDisplayInRect:NSMakeRect(x, fe->h - y - h, w, h)]; | ||
1578 | } | ||
1579 | static void osx_clip(void *handle, int x, int y, int w, int h) | ||
1580 | { | ||
1581 | frontend *fe = (frontend *)handle; | ||
1582 | NSRect r = { {x, fe->h - y - h}, {w, h} }; | ||
1583 | |||
1584 | if (!fe->clipped) | ||
1585 | [[NSGraphicsContext currentContext] saveGraphicsState]; | ||
1586 | [NSBezierPath clipRect:r]; | ||
1587 | fe->clipped = TRUE; | ||
1588 | } | ||
1589 | static void osx_unclip(void *handle) | ||
1590 | { | ||
1591 | frontend *fe = (frontend *)handle; | ||
1592 | if (fe->clipped) | ||
1593 | [[NSGraphicsContext currentContext] restoreGraphicsState]; | ||
1594 | fe->clipped = FALSE; | ||
1595 | } | ||
1596 | static void osx_start_draw(void *handle) | ||
1597 | { | ||
1598 | frontend *fe = (frontend *)handle; | ||
1599 | [fe->image lockFocus]; | ||
1600 | fe->clipped = FALSE; | ||
1601 | } | ||
1602 | static void osx_end_draw(void *handle) | ||
1603 | { | ||
1604 | frontend *fe = (frontend *)handle; | ||
1605 | [fe->image unlockFocus]; | ||
1606 | } | ||
1607 | static void osx_status_bar(void *handle, char *text) | ||
1608 | { | ||
1609 | frontend *fe = (frontend *)handle; | ||
1610 | [fe->window setStatusLine:text]; | ||
1611 | } | ||
1612 | |||
1613 | const struct drawing_api osx_drawing = { | ||
1614 | osx_draw_text, | ||
1615 | osx_draw_rect, | ||
1616 | osx_draw_line, | ||
1617 | osx_draw_polygon, | ||
1618 | osx_draw_circle, | ||
1619 | osx_draw_update, | ||
1620 | osx_clip, | ||
1621 | osx_unclip, | ||
1622 | osx_start_draw, | ||
1623 | osx_end_draw, | ||
1624 | osx_status_bar, | ||
1625 | osx_blitter_new, | ||
1626 | osx_blitter_free, | ||
1627 | osx_blitter_save, | ||
1628 | osx_blitter_load, | ||
1629 | NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ | ||
1630 | NULL, NULL, /* line_width, line_dotted */ | ||
1631 | osx_text_fallback, | ||
1632 | osx_draw_thick_line, | ||
1633 | }; | ||
1634 | |||
1635 | void deactivate_timer(frontend *fe) | ||
1636 | { | ||
1637 | [fe->window deactivateTimer]; | ||
1638 | } | ||
1639 | void activate_timer(frontend *fe) | ||
1640 | { | ||
1641 | [fe->window activateTimer]; | ||
1642 | } | ||
1643 | |||
1644 | /* ---------------------------------------------------------------------- | ||
1645 | * AppController: the object which receives the messages from all | ||
1646 | * menu selections that aren't standard OS X functions. | ||
1647 | */ | ||
1648 | @interface AppController : NSObject <NSApplicationDelegate> | ||
1649 | { | ||
1650 | } | ||
1651 | - (void)newGameWindow:(id)sender; | ||
1652 | - (void)about:(id)sender; | ||
1653 | @end | ||
1654 | |||
1655 | @implementation AppController | ||
1656 | |||
1657 | - (void)newGameWindow:(id)sender | ||
1658 | { | ||
1659 | const game *g = [sender getPayload]; | ||
1660 | id win; | ||
1661 | |||
1662 | win = [[GameWindow alloc] initWithGame:g]; | ||
1663 | [win makeKeyAndOrderFront:self]; | ||
1664 | } | ||
1665 | |||
1666 | - (void)about:(id)sender | ||
1667 | { | ||
1668 | id win; | ||
1669 | |||
1670 | win = [[AboutBox alloc] init]; | ||
1671 | [win makeKeyAndOrderFront:self]; | ||
1672 | } | ||
1673 | |||
1674 | - (NSMenu *)applicationDockMenu:(NSApplication *)sender | ||
1675 | { | ||
1676 | NSMenu *menu = newmenu("Dock Menu"); | ||
1677 | { | ||
1678 | int i; | ||
1679 | |||
1680 | for (i = 0; i < gamecount; i++) { | ||
1681 | id item = | ||
1682 | initnewitem([DataMenuItem allocWithZone:[NSMenu menuZone]], | ||
1683 | menu, gamelist[i]->name, "", self, | ||
1684 | @selector(newGameWindow:)); | ||
1685 | [item setPayload:(void *)gamelist[i]]; | ||
1686 | } | ||
1687 | } | ||
1688 | return menu; | ||
1689 | } | ||
1690 | |||
1691 | @end | ||
1692 | |||
1693 | /* ---------------------------------------------------------------------- | ||
1694 | * Main program. Constructs the menus and runs the application. | ||
1695 | */ | ||
1696 | int main(int argc, char **argv) | ||
1697 | { | ||
1698 | NSAutoreleasePool *pool; | ||
1699 | NSMenu *menu; | ||
1700 | AppController *controller; | ||
1701 | NSImage *icon; | ||
1702 | |||
1703 | pool = [[NSAutoreleasePool alloc] init]; | ||
1704 | |||
1705 | icon = [NSImage imageNamed:@"NSApplicationIcon"]; | ||
1706 | app = [NSApplication sharedApplication]; | ||
1707 | [app setApplicationIconImage:icon]; | ||
1708 | |||
1709 | controller = [[[AppController alloc] init] autorelease]; | ||
1710 | [app setDelegate:controller]; | ||
1711 | |||
1712 | [app setMainMenu: newmenu("Main Menu")]; | ||
1713 | |||
1714 | menu = newsubmenu([app mainMenu], "Apple Menu"); | ||
1715 | newitem(menu, "About Puzzles", "", NULL, @selector(about:)); | ||
1716 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1717 | [app setServicesMenu:newsubmenu(menu, "Services")]; | ||
1718 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1719 | newitem(menu, "Hide Puzzles", "h", app, @selector(hide:)); | ||
1720 | newitem(menu, "Hide Others", "o-h", app, @selector(hideOtherApplications:)); | ||
1721 | newitem(menu, "Show All", "", app, @selector(unhideAllApplications:)); | ||
1722 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1723 | newitem(menu, "Quit", "q", app, @selector(terminate:)); | ||
1724 | [app setAppleMenu: menu]; | ||
1725 | |||
1726 | menu = newsubmenu([app mainMenu], "File"); | ||
1727 | newitem(menu, "Open", "o", NULL, @selector(loadSavedGame:)); | ||
1728 | newitem(menu, "Save As", "s", NULL, @selector(saveGame:)); | ||
1729 | newitem(menu, "New Game", "n", NULL, @selector(newGame:)); | ||
1730 | newitem(menu, "Restart Game", "r", NULL, @selector(restartGame:)); | ||
1731 | newitem(menu, "Specific Game", "", NULL, @selector(specificGame:)); | ||
1732 | newitem(menu, "Specific Random Seed", "", NULL, | ||
1733 | @selector(specificRandomGame:)); | ||
1734 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1735 | { | ||
1736 | NSMenu *submenu = newsubmenu(menu, "New Window"); | ||
1737 | int i; | ||
1738 | |||
1739 | for (i = 0; i < gamecount; i++) { | ||
1740 | id item = | ||
1741 | initnewitem([DataMenuItem allocWithZone:[NSMenu menuZone]], | ||
1742 | submenu, gamelist[i]->name, "", controller, | ||
1743 | @selector(newGameWindow:)); | ||
1744 | [item setPayload:(void *)gamelist[i]]; | ||
1745 | } | ||
1746 | } | ||
1747 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1748 | newitem(menu, "Close", "w", NULL, @selector(performClose:)); | ||
1749 | |||
1750 | menu = newsubmenu([app mainMenu], "Edit"); | ||
1751 | newitem(menu, "Undo", "z", NULL, @selector(undoMove:)); | ||
1752 | newitem(menu, "Redo", "S-z", NULL, @selector(redoMove:)); | ||
1753 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1754 | newitem(menu, "Cut", "x", NULL, @selector(cut:)); | ||
1755 | newitem(menu, "Copy", "c", NULL, @selector(copy:)); | ||
1756 | newitem(menu, "Paste", "v", NULL, @selector(paste:)); | ||
1757 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1758 | newitem(menu, "Solve", "S-s", NULL, @selector(solveGame:)); | ||
1759 | |||
1760 | menu = newsubmenu([app mainMenu], "Type"); | ||
1761 | typemenu = menu; | ||
1762 | newitem(menu, "Custom", "", NULL, @selector(customGameType:)); | ||
1763 | |||
1764 | menu = newsubmenu([app mainMenu], "Window"); | ||
1765 | [app setWindowsMenu: menu]; | ||
1766 | newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:)); | ||
1767 | |||
1768 | menu = newsubmenu([app mainMenu], "Help"); | ||
1769 | newitem(menu, "Puzzles Help", "?", app, @selector(showHelp:)); | ||
1770 | |||
1771 | [app run]; | ||
1772 | [pool release]; | ||
1773 | |||
1774 | return 0; | ||
1775 | } | ||