diff options
author | Franklin Wei <frankhwei536@gmail.com> | 2016-11-20 15:16:41 -0500 |
---|---|---|
committer | Franklin Wei <me@fwei.tk> | 2016-12-18 18:13:22 +0100 |
commit | 1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99 (patch) | |
tree | 8e7f2d6b0cbdb5d15c13457b2c3e1de69f598440 /apps/plugins/puzzles/osx.m | |
parent | 3ee79724f6fb033d50e26ef37b33d3f8cedf0c5b (diff) | |
download | rockbox-1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99.tar.gz rockbox-1a6a8b52f7aa4e2da6f4c34a0c743c760b8cfd99.zip |
Port of Simon Tatham's Puzzle Collection
Original revision: 5123b1bf68777ffa86e651f178046b26a87cf2d9
MIT Licensed. Some games still crash and others are unplayable due to
issues with controls. Still need a "real" polygon filling algorithm.
Currently builds one plugin per puzzle (about 40 in total, around 100K
each on ARM), but can easily be made to build a single monolithic
overlay (800K or so on ARM).
The following games are at least partially broken for various reasons,
and have been disabled on this commit:
Cube: failed assertion with "Icosahedron" setting
Keen: input issues
Mines: weird stuff happens on target
Palisade: input issues
Solo: input issues, occasional crash on target
Towers: input issues
Undead: input issues
Unequal: input and drawing issues (concave polys)
Untangle: input issues
Features left to do:
- In-game help system
- Figure out the weird bugs
Change-Id: I7c69b6860ab115f973c8d76799502e9bb3d52368
Diffstat (limited to 'apps/plugins/puzzles/osx.m')
-rw-r--r-- | apps/plugins/puzzles/osx.m | 1724 |
1 files changed, 1724 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/osx.m b/apps/plugins/puzzles/osx.m new file mode 100644 index 0000000000..4740124175 --- /dev/null +++ b/apps/plugins/puzzles/osx.m | |||
@@ -0,0 +1,1724 @@ | |||
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 | } | ||
430 | - (id)initWithGame:(const game *)g; | ||
431 | - (void)dealloc; | ||
432 | - (void)processButton:(int)b x:(int)x y:(int)y; | ||
433 | - (void)processKey:(int)b; | ||
434 | - (void)keyDown:(NSEvent *)ev; | ||
435 | - (void)activateTimer; | ||
436 | - (void)deactivateTimer; | ||
437 | - (void)setStatusLine:(char *)text; | ||
438 | - (void)resizeForNewGameParams; | ||
439 | - (void)updateTypeMenuTick; | ||
440 | @end | ||
441 | |||
442 | @implementation MyImageView | ||
443 | |||
444 | - (void)setWindow:(GameWindow *)win | ||
445 | { | ||
446 | ourwin = win; | ||
447 | } | ||
448 | |||
449 | - (void)mouseEvent:(NSEvent *)ev button:(int)b | ||
450 | { | ||
451 | NSPoint point = [self convertPoint:[ev locationInWindow] fromView:nil]; | ||
452 | [ourwin processButton:b x:point.x y:point.y]; | ||
453 | } | ||
454 | |||
455 | - (void)mouseDown:(NSEvent *)ev | ||
456 | { | ||
457 | unsigned mod = [ev modifierFlags]; | ||
458 | [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_BUTTON : | ||
459 | (mod & NSShiftKeyMask) ? MIDDLE_BUTTON : | ||
460 | LEFT_BUTTON)]; | ||
461 | } | ||
462 | - (void)mouseDragged:(NSEvent *)ev | ||
463 | { | ||
464 | unsigned mod = [ev modifierFlags]; | ||
465 | [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_DRAG : | ||
466 | (mod & NSShiftKeyMask) ? MIDDLE_DRAG : | ||
467 | LEFT_DRAG)]; | ||
468 | } | ||
469 | - (void)mouseUp:(NSEvent *)ev | ||
470 | { | ||
471 | unsigned mod = [ev modifierFlags]; | ||
472 | [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_RELEASE : | ||
473 | (mod & NSShiftKeyMask) ? MIDDLE_RELEASE : | ||
474 | LEFT_RELEASE)]; | ||
475 | } | ||
476 | - (void)rightMouseDown:(NSEvent *)ev | ||
477 | { | ||
478 | unsigned mod = [ev modifierFlags]; | ||
479 | [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_BUTTON : | ||
480 | RIGHT_BUTTON)]; | ||
481 | } | ||
482 | - (void)rightMouseDragged:(NSEvent *)ev | ||
483 | { | ||
484 | unsigned mod = [ev modifierFlags]; | ||
485 | [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_DRAG : | ||
486 | RIGHT_DRAG)]; | ||
487 | } | ||
488 | - (void)rightMouseUp:(NSEvent *)ev | ||
489 | { | ||
490 | unsigned mod = [ev modifierFlags]; | ||
491 | [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_RELEASE : | ||
492 | RIGHT_RELEASE)]; | ||
493 | } | ||
494 | - (void)otherMouseDown:(NSEvent *)ev | ||
495 | { | ||
496 | [self mouseEvent:ev button:MIDDLE_BUTTON]; | ||
497 | } | ||
498 | - (void)otherMouseDragged:(NSEvent *)ev | ||
499 | { | ||
500 | [self mouseEvent:ev button:MIDDLE_DRAG]; | ||
501 | } | ||
502 | - (void)otherMouseUp:(NSEvent *)ev | ||
503 | { | ||
504 | [self mouseEvent:ev button:MIDDLE_RELEASE]; | ||
505 | } | ||
506 | @end | ||
507 | |||
508 | @implementation GameWindow | ||
509 | - (void)setupContentView | ||
510 | { | ||
511 | NSRect frame; | ||
512 | int w, h; | ||
513 | |||
514 | if (status) { | ||
515 | frame = [status frame]; | ||
516 | frame.origin.y = frame.size.height; | ||
517 | } else | ||
518 | frame.origin.y = 0; | ||
519 | frame.origin.x = 0; | ||
520 | |||
521 | w = h = INT_MAX; | ||
522 | midend_size(me, &w, &h, FALSE); | ||
523 | frame.size.width = w; | ||
524 | frame.size.height = h; | ||
525 | fe.w = w; | ||
526 | fe.h = h; | ||
527 | |||
528 | fe.image = [[NSImage alloc] initWithSize:frame.size]; | ||
529 | fe.view = [[MyImageView alloc] initWithFrame:frame]; | ||
530 | [fe.view setImage:fe.image]; | ||
531 | [fe.view setWindow:self]; | ||
532 | |||
533 | midend_redraw(me); | ||
534 | |||
535 | [[self contentView] addSubview:fe.view]; | ||
536 | } | ||
537 | - (id)initWithGame:(const game *)g | ||
538 | { | ||
539 | NSRect rect = { {0,0}, {0,0} }, rect2; | ||
540 | int w, h; | ||
541 | |||
542 | ourgame = g; | ||
543 | |||
544 | fe.window = self; | ||
545 | |||
546 | me = midend_new(&fe, ourgame, &osx_drawing, &fe); | ||
547 | /* | ||
548 | * If we ever need to open a fresh window using a provided game | ||
549 | * ID, I think the right thing is to move most of this method | ||
550 | * into a new initWithGame:gameID: method, and have | ||
551 | * initWithGame: simply call that one and pass it NULL. | ||
552 | */ | ||
553 | midend_new_game(me); | ||
554 | w = h = INT_MAX; | ||
555 | midend_size(me, &w, &h, FALSE); | ||
556 | rect.size.width = w; | ||
557 | rect.size.height = h; | ||
558 | fe.w = w; | ||
559 | fe.h = h; | ||
560 | |||
561 | /* | ||
562 | * Create the status bar, which will just be an NSTextField. | ||
563 | */ | ||
564 | if (midend_wants_statusbar(me)) { | ||
565 | status = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,100,50)]; | ||
566 | [status setEditable:NO]; | ||
567 | [status setSelectable:NO]; | ||
568 | [status setBordered:YES]; | ||
569 | [status setBezeled:YES]; | ||
570 | [status setBezelStyle:NSTextFieldSquareBezel]; | ||
571 | [status setDrawsBackground:YES]; | ||
572 | [[status cell] setTitle:@DEFAULT_STATUSBAR_TEXT]; | ||
573 | [status sizeToFit]; | ||
574 | rect2 = [status frame]; | ||
575 | rect.size.height += rect2.size.height; | ||
576 | rect2.size.width = rect.size.width; | ||
577 | rect2.origin.x = rect2.origin.y = 0; | ||
578 | [status setFrame:rect2]; | ||
579 | } else | ||
580 | status = nil; | ||
581 | |||
582 | self = [super initWithContentRect:rect | ||
583 | styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask | | ||
584 | NSClosableWindowMask) | ||
585 | backing:NSBackingStoreBuffered | ||
586 | defer:YES]; | ||
587 | [self setTitle:[NSString stringWithUTF8String:ourgame->name]]; | ||
588 | |||
589 | { | ||
590 | float *colours; | ||
591 | int i, ncolours; | ||
592 | |||
593 | colours = midend_colours(me, &ncolours); | ||
594 | fe.ncolours = ncolours; | ||
595 | fe.colours = snewn(ncolours, NSColor *); | ||
596 | |||
597 | for (i = 0; i < ncolours; i++) { | ||
598 | fe.colours[i] = [[NSColor colorWithDeviceRed:colours[i*3] | ||
599 | green:colours[i*3+1] blue:colours[i*3+2] | ||
600 | alpha:1.0] retain]; | ||
601 | } | ||
602 | } | ||
603 | |||
604 | [self setupContentView]; | ||
605 | if (status) | ||
606 | [[self contentView] addSubview:status]; | ||
607 | [self setIgnoresMouseEvents:NO]; | ||
608 | |||
609 | [self center]; /* :-) */ | ||
610 | |||
611 | return self; | ||
612 | } | ||
613 | |||
614 | - (void)dealloc | ||
615 | { | ||
616 | int i; | ||
617 | for (i = 0; i < fe.ncolours; i++) { | ||
618 | [fe.colours[i] release]; | ||
619 | } | ||
620 | sfree(fe.colours); | ||
621 | midend_free(me); | ||
622 | [super dealloc]; | ||
623 | } | ||
624 | |||
625 | - (void)processButton:(int)b x:(int)x y:(int)y | ||
626 | { | ||
627 | if (!midend_process_key(me, x, fe.h - 1 - y, b)) | ||
628 | [self close]; | ||
629 | } | ||
630 | |||
631 | - (void)processKey:(int)b | ||
632 | { | ||
633 | if (!midend_process_key(me, -1, -1, b)) | ||
634 | [self close]; | ||
635 | } | ||
636 | |||
637 | - (void)keyDown:(NSEvent *)ev | ||
638 | { | ||
639 | NSString *s = [ev characters]; | ||
640 | int i, n = [s length]; | ||
641 | |||
642 | for (i = 0; i < n; i++) { | ||
643 | int c = [s characterAtIndex:i]; | ||
644 | |||
645 | /* | ||
646 | * ASCII gets passed straight to midend_process_key. | ||
647 | * Anything above that has to be translated to our own | ||
648 | * function key codes. | ||
649 | */ | ||
650 | if (c >= 0x80) { | ||
651 | int mods = FALSE; | ||
652 | switch (c) { | ||
653 | case NSUpArrowFunctionKey: | ||
654 | c = CURSOR_UP; | ||
655 | mods = TRUE; | ||
656 | break; | ||
657 | case NSDownArrowFunctionKey: | ||
658 | c = CURSOR_DOWN; | ||
659 | mods = TRUE; | ||
660 | break; | ||
661 | case NSLeftArrowFunctionKey: | ||
662 | c = CURSOR_LEFT; | ||
663 | mods = TRUE; | ||
664 | break; | ||
665 | case NSRightArrowFunctionKey: | ||
666 | c = CURSOR_RIGHT; | ||
667 | mods = TRUE; | ||
668 | break; | ||
669 | default: | ||
670 | continue; | ||
671 | } | ||
672 | |||
673 | if (mods) { | ||
674 | if ([ev modifierFlags] & NSShiftKeyMask) | ||
675 | c |= MOD_SHFT; | ||
676 | if ([ev modifierFlags] & NSControlKeyMask) | ||
677 | c |= MOD_CTRL; | ||
678 | } | ||
679 | } | ||
680 | |||
681 | if (c >= '0' && c <= '9' && ([ev modifierFlags] & NSNumericPadKeyMask)) | ||
682 | c |= MOD_NUM_KEYPAD; | ||
683 | |||
684 | [self processKey:c]; | ||
685 | } | ||
686 | } | ||
687 | |||
688 | - (void)activateTimer | ||
689 | { | ||
690 | if (timer != nil) | ||
691 | return; | ||
692 | |||
693 | timer = [NSTimer scheduledTimerWithTimeInterval:0.02 | ||
694 | target:self selector:@selector(timerTick:) | ||
695 | userInfo:nil repeats:YES]; | ||
696 | gettimeofday(&last_time, NULL); | ||
697 | } | ||
698 | |||
699 | - (void)deactivateTimer | ||
700 | { | ||
701 | if (timer == nil) | ||
702 | return; | ||
703 | |||
704 | [timer invalidate]; | ||
705 | timer = nil; | ||
706 | } | ||
707 | |||
708 | - (void)timerTick:(id)sender | ||
709 | { | ||
710 | struct timeval now; | ||
711 | float elapsed; | ||
712 | gettimeofday(&now, NULL); | ||
713 | elapsed = ((now.tv_usec - last_time.tv_usec) * 0.000001F + | ||
714 | (now.tv_sec - last_time.tv_sec)); | ||
715 | midend_timer(me, elapsed); | ||
716 | last_time = now; | ||
717 | } | ||
718 | |||
719 | - (void)showError:(char *)message | ||
720 | { | ||
721 | NSAlert *alert; | ||
722 | |||
723 | alert = [[[NSAlert alloc] init] autorelease]; | ||
724 | [alert addButtonWithTitle:@"Bah"]; | ||
725 | [alert setInformativeText:[NSString stringWithUTF8String:message]]; | ||
726 | [alert beginSheetModalForWindow:self modalDelegate:nil | ||
727 | didEndSelector:NULL contextInfo:nil]; | ||
728 | } | ||
729 | |||
730 | - (void)newGame:(id)sender | ||
731 | { | ||
732 | [self processKey:'n']; | ||
733 | } | ||
734 | - (void)restartGame:(id)sender | ||
735 | { | ||
736 | midend_restart_game(me); | ||
737 | } | ||
738 | - (void)saveGame:(id)sender | ||
739 | { | ||
740 | NSSavePanel *sp = [NSSavePanel savePanel]; | ||
741 | |||
742 | if ([sp runModal] == NSFileHandlingPanelOKButton) { | ||
743 | const char *name = [[sp filename] UTF8String]; | ||
744 | |||
745 | FILE *fp = fopen(name, "w"); | ||
746 | |||
747 | if (!fp) { | ||
748 | [self showError:"Unable to open save file"]; | ||
749 | return; | ||
750 | } | ||
751 | |||
752 | midend_serialise(me, savefile_write, fp); | ||
753 | |||
754 | fclose(fp); | ||
755 | } | ||
756 | } | ||
757 | - (void)loadSavedGame:(id)sender | ||
758 | { | ||
759 | NSOpenPanel *op = [NSOpenPanel openPanel]; | ||
760 | |||
761 | [op setAllowsMultipleSelection:NO]; | ||
762 | |||
763 | if ([op runModalForTypes:nil] == NSOKButton) { | ||
764 | /* | ||
765 | * This used to be | ||
766 | * | ||
767 | * [[[op filenames] objectAtIndex:0] cString] | ||
768 | * | ||
769 | * but the plain cString method became deprecated and Xcode 7 | ||
770 | * started complaining about it. Since OS X 10.9 we can | ||
771 | * apparently use the more modern API | ||
772 | * | ||
773 | * [[[op URLs] objectAtIndex:0] fileSystemRepresentation] | ||
774 | * | ||
775 | * but the alternative below still compiles with Xcode 7 and | ||
776 | * is a bit more backwards compatible, so I'll try it for the | ||
777 | * moment. | ||
778 | */ | ||
779 | const char *name = [[[op filenames] objectAtIndex:0] | ||
780 | cStringUsingEncoding: | ||
781 | [NSString defaultCStringEncoding]]; | ||
782 | char *err; | ||
783 | |||
784 | FILE *fp = fopen(name, "r"); | ||
785 | |||
786 | if (!fp) { | ||
787 | [self showError:"Unable to open saved game file"]; | ||
788 | return; | ||
789 | } | ||
790 | |||
791 | err = midend_deserialise(me, savefile_read, fp); | ||
792 | |||
793 | fclose(fp); | ||
794 | |||
795 | if (err) { | ||
796 | [self showError:err]; | ||
797 | return; | ||
798 | } | ||
799 | |||
800 | [self resizeForNewGameParams]; | ||
801 | [self updateTypeMenuTick]; | ||
802 | } | ||
803 | } | ||
804 | - (void)undoMove:(id)sender | ||
805 | { | ||
806 | [self processKey:'u']; | ||
807 | } | ||
808 | - (void)redoMove:(id)sender | ||
809 | { | ||
810 | [self processKey:'r'&0x1F]; | ||
811 | } | ||
812 | |||
813 | - (void)copy:(id)sender | ||
814 | { | ||
815 | char *text; | ||
816 | |||
817 | if ((text = midend_text_format(me)) != NULL) { | ||
818 | NSPasteboard *pb = [NSPasteboard generalPasteboard]; | ||
819 | NSArray *a = [NSArray arrayWithObject:NSStringPboardType]; | ||
820 | [pb declareTypes:a owner:nil]; | ||
821 | [pb setString:[NSString stringWithUTF8String:text] | ||
822 | forType:NSStringPboardType]; | ||
823 | } else | ||
824 | NSBeep(); | ||
825 | } | ||
826 | |||
827 | - (void)solveGame:(id)sender | ||
828 | { | ||
829 | char *msg; | ||
830 | |||
831 | msg = midend_solve(me); | ||
832 | |||
833 | if (msg) | ||
834 | [self showError:msg]; | ||
835 | } | ||
836 | |||
837 | - (BOOL)validateMenuItem:(NSMenuItem *)item | ||
838 | { | ||
839 | if ([item action] == @selector(copy:)) | ||
840 | return (ourgame->can_format_as_text_ever && | ||
841 | midend_can_format_as_text_now(me) ? YES : NO); | ||
842 | else if ([item action] == @selector(solveGame:)) | ||
843 | return (ourgame->can_solve ? YES : NO); | ||
844 | else | ||
845 | return [super validateMenuItem:item]; | ||
846 | } | ||
847 | |||
848 | - (void)clearTypeMenu | ||
849 | { | ||
850 | while ([typemenu numberOfItems] > 1) | ||
851 | [typemenu removeItemAtIndex:0]; | ||
852 | [[typemenu itemAtIndex:0] setState:NSOffState]; | ||
853 | } | ||
854 | |||
855 | - (void)updateTypeMenuTick | ||
856 | { | ||
857 | int i, total, n; | ||
858 | |||
859 | total = [typemenu numberOfItems]; | ||
860 | n = midend_which_preset(me); | ||
861 | if (n < 0) | ||
862 | n = total - 1; /* that's always where "Custom" lives */ | ||
863 | for (i = 0; i < total; i++) | ||
864 | [[typemenu itemAtIndex:i] setState:(i == n ? NSOnState : NSOffState)]; | ||
865 | } | ||
866 | |||
867 | - (void)becomeKeyWindow | ||
868 | { | ||
869 | int n; | ||
870 | |||
871 | [self clearTypeMenu]; | ||
872 | |||
873 | [super becomeKeyWindow]; | ||
874 | |||
875 | n = midend_num_presets(me); | ||
876 | |||
877 | if (n > 0) { | ||
878 | [typemenu insertItem:[NSMenuItem separatorItem] atIndex:0]; | ||
879 | while (n--) { | ||
880 | char *name; | ||
881 | game_params *params; | ||
882 | DataMenuItem *item; | ||
883 | |||
884 | midend_fetch_preset(me, n, &name, ¶ms); | ||
885 | |||
886 | item = [[[DataMenuItem alloc] | ||
887 | initWithTitle:[NSString stringWithUTF8String:name] | ||
888 | action:NULL keyEquivalent:@""] | ||
889 | autorelease]; | ||
890 | |||
891 | [item setEnabled:YES]; | ||
892 | [item setTarget:self]; | ||
893 | [item setAction:@selector(presetGame:)]; | ||
894 | [item setPayload:params]; | ||
895 | |||
896 | [typemenu insertItem:item atIndex:0]; | ||
897 | } | ||
898 | } | ||
899 | |||
900 | [self updateTypeMenuTick]; | ||
901 | } | ||
902 | |||
903 | - (void)resignKeyWindow | ||
904 | { | ||
905 | [self clearTypeMenu]; | ||
906 | [super resignKeyWindow]; | ||
907 | } | ||
908 | |||
909 | - (void)close | ||
910 | { | ||
911 | [self clearTypeMenu]; | ||
912 | [super close]; | ||
913 | } | ||
914 | |||
915 | - (void)resizeForNewGameParams | ||
916 | { | ||
917 | NSSize size = {0,0}; | ||
918 | int w, h; | ||
919 | |||
920 | w = h = INT_MAX; | ||
921 | midend_size(me, &w, &h, FALSE); | ||
922 | size.width = w; | ||
923 | size.height = h; | ||
924 | fe.w = w; | ||
925 | fe.h = h; | ||
926 | |||
927 | if (status) { | ||
928 | NSRect frame = [status frame]; | ||
929 | size.height += frame.size.height; | ||
930 | frame.size.width = size.width; | ||
931 | [status setFrame:frame]; | ||
932 | } | ||
933 | |||
934 | #ifndef GNUSTEP | ||
935 | NSDisableScreenUpdates(); | ||
936 | #endif | ||
937 | [self setContentSize:size]; | ||
938 | [self setupContentView]; | ||
939 | #ifndef GNUSTEP | ||
940 | NSEnableScreenUpdates(); | ||
941 | #endif | ||
942 | } | ||
943 | |||
944 | - (void)presetGame:(id)sender | ||
945 | { | ||
946 | game_params *params = [sender getPayload]; | ||
947 | |||
948 | midend_set_params(me, params); | ||
949 | midend_new_game(me); | ||
950 | |||
951 | [self resizeForNewGameParams]; | ||
952 | [self updateTypeMenuTick]; | ||
953 | } | ||
954 | |||
955 | - (void)startConfigureSheet:(int)which | ||
956 | { | ||
957 | NSButton *ok, *cancel; | ||
958 | int actw, acth, leftw, rightw, totalw, h, thish, y; | ||
959 | int k; | ||
960 | NSRect rect, tmprect; | ||
961 | const int SPACING = 16; | ||
962 | char *title; | ||
963 | config_item *i; | ||
964 | int cfg_controlsize; | ||
965 | NSTextField *tf; | ||
966 | NSButton *b; | ||
967 | NSPopUpButton *pb; | ||
968 | |||
969 | assert(sheet == NULL); | ||
970 | |||
971 | /* | ||
972 | * Every control we create here is going to have this size | ||
973 | * until we tell it to calculate a better one. | ||
974 | */ | ||
975 | tmprect = NSMakeRect(0, 0, 100, 50); | ||
976 | |||
977 | /* | ||
978 | * Set up OK and Cancel buttons. (Actually, MacOS doesn't seem | ||
979 | * to be fond of generic OK and Cancel wording, so I'm going to | ||
980 | * rename them to something nicer.) | ||
981 | */ | ||
982 | actw = acth = 0; | ||
983 | |||
984 | cancel = [[NSButton alloc] initWithFrame:tmprect]; | ||
985 | [cancel setBezelStyle:NSRoundedBezelStyle]; | ||
986 | [cancel setTitle:@"Abandon"]; | ||
987 | [cancel setTarget:self]; | ||
988 | [cancel setKeyEquivalent:@"\033"]; | ||
989 | [cancel setAction:@selector(sheetCancelButton:)]; | ||
990 | [cancel sizeToFit]; | ||
991 | rect = [cancel frame]; | ||
992 | if (actw < rect.size.width) actw = rect.size.width; | ||
993 | if (acth < rect.size.height) acth = rect.size.height; | ||
994 | |||
995 | ok = [[NSButton alloc] initWithFrame:tmprect]; | ||
996 | [ok setBezelStyle:NSRoundedBezelStyle]; | ||
997 | [ok setTitle:@"Accept"]; | ||
998 | [ok setTarget:self]; | ||
999 | [ok setKeyEquivalent:@"\r"]; | ||
1000 | [ok setAction:@selector(sheetOKButton:)]; | ||
1001 | [ok sizeToFit]; | ||
1002 | rect = [ok frame]; | ||
1003 | if (actw < rect.size.width) actw = rect.size.width; | ||
1004 | if (acth < rect.size.height) acth = rect.size.height; | ||
1005 | |||
1006 | totalw = SPACING + 2 * actw; | ||
1007 | h = 2 * SPACING + acth; | ||
1008 | |||
1009 | /* | ||
1010 | * Now fetch the midend config data and go through it creating | ||
1011 | * controls. | ||
1012 | */ | ||
1013 | cfg = midend_get_config(me, which, &title); | ||
1014 | sfree(title); /* FIXME: should we use this somehow? */ | ||
1015 | cfg_which = which; | ||
1016 | |||
1017 | cfg_ncontrols = cfg_controlsize = 0; | ||
1018 | cfg_controls = NULL; | ||
1019 | leftw = rightw = 0; | ||
1020 | for (i = cfg; i->type != C_END; i++) { | ||
1021 | if (cfg_controlsize < cfg_ncontrols + 5) { | ||
1022 | cfg_controlsize = cfg_ncontrols + 32; | ||
1023 | cfg_controls = sresize(cfg_controls, cfg_controlsize, NSView *); | ||
1024 | } | ||
1025 | |||
1026 | thish = 0; | ||
1027 | |||
1028 | switch (i->type) { | ||
1029 | case C_STRING: | ||
1030 | /* | ||
1031 | * Two NSTextFields, one being a label and the other | ||
1032 | * being an edit box. | ||
1033 | */ | ||
1034 | |||
1035 | tf = [[NSTextField alloc] initWithFrame:tmprect]; | ||
1036 | [tf setEditable:NO]; | ||
1037 | [tf setSelectable:NO]; | ||
1038 | [tf setBordered:NO]; | ||
1039 | [tf setDrawsBackground:NO]; | ||
1040 | [[tf cell] setTitle:[NSString stringWithUTF8String:i->name]]; | ||
1041 | [tf sizeToFit]; | ||
1042 | rect = [tf frame]; | ||
1043 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1044 | if (leftw < rect.size.width + 1) leftw = rect.size.width + 1; | ||
1045 | cfg_controls[cfg_ncontrols++] = tf; | ||
1046 | |||
1047 | tf = [[NSTextField alloc] initWithFrame:tmprect]; | ||
1048 | [tf setEditable:YES]; | ||
1049 | [tf setSelectable:YES]; | ||
1050 | [tf setBordered:YES]; | ||
1051 | [[tf cell] setTitle:[NSString stringWithUTF8String:i->sval]]; | ||
1052 | [tf sizeToFit]; | ||
1053 | rect = [tf frame]; | ||
1054 | /* | ||
1055 | * We impose a minimum and maximum width on editable | ||
1056 | * NSTextFields. If we allow them to size themselves to | ||
1057 | * the contents of the text within them, then they will | ||
1058 | * look very silly if that text is only one or two | ||
1059 | * characters, and equally silly if it's an absolutely | ||
1060 | * enormous Rectangles or Pattern game ID! | ||
1061 | */ | ||
1062 | if (rect.size.width < 75) rect.size.width = 75; | ||
1063 | if (rect.size.width > 400) rect.size.width = 400; | ||
1064 | |||
1065 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1066 | if (rightw < rect.size.width + 1) rightw = rect.size.width + 1; | ||
1067 | cfg_controls[cfg_ncontrols++] = tf; | ||
1068 | break; | ||
1069 | |||
1070 | case C_BOOLEAN: | ||
1071 | /* | ||
1072 | * A checkbox is an NSButton with a type of | ||
1073 | * NSSwitchButton. | ||
1074 | */ | ||
1075 | b = [[NSButton alloc] initWithFrame:tmprect]; | ||
1076 | [b setBezelStyle:NSRoundedBezelStyle]; | ||
1077 | [b setButtonType:NSSwitchButton]; | ||
1078 | [b setTitle:[NSString stringWithUTF8String:i->name]]; | ||
1079 | [b sizeToFit]; | ||
1080 | [b setState:(i->ival ? NSOnState : NSOffState)]; | ||
1081 | rect = [b frame]; | ||
1082 | if (totalw < rect.size.width + 1) totalw = rect.size.width + 1; | ||
1083 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1084 | cfg_controls[cfg_ncontrols++] = b; | ||
1085 | break; | ||
1086 | |||
1087 | case C_CHOICES: | ||
1088 | /* | ||
1089 | * A pop-up menu control is an NSPopUpButton, which | ||
1090 | * takes an embedded NSMenu. We also need an | ||
1091 | * NSTextField to act as a label. | ||
1092 | */ | ||
1093 | |||
1094 | tf = [[NSTextField alloc] initWithFrame:tmprect]; | ||
1095 | [tf setEditable:NO]; | ||
1096 | [tf setSelectable:NO]; | ||
1097 | [tf setBordered:NO]; | ||
1098 | [tf setDrawsBackground:NO]; | ||
1099 | [[tf cell] setTitle:[NSString stringWithUTF8String:i->name]]; | ||
1100 | [tf sizeToFit]; | ||
1101 | rect = [tf frame]; | ||
1102 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1103 | if (leftw < rect.size.width + 1) leftw = rect.size.width + 1; | ||
1104 | cfg_controls[cfg_ncontrols++] = tf; | ||
1105 | |||
1106 | pb = [[NSPopUpButton alloc] initWithFrame:tmprect pullsDown:NO]; | ||
1107 | [pb setBezelStyle:NSRoundedBezelStyle]; | ||
1108 | { | ||
1109 | char c, *p; | ||
1110 | |||
1111 | p = i->sval; | ||
1112 | c = *p++; | ||
1113 | while (*p) { | ||
1114 | char *q, *copy; | ||
1115 | |||
1116 | q = p; | ||
1117 | while (*p && *p != c) p++; | ||
1118 | |||
1119 | copy = snewn((p-q) + 1, char); | ||
1120 | memcpy(copy, q, p-q); | ||
1121 | copy[p-q] = '\0'; | ||
1122 | [pb addItemWithTitle:[NSString stringWithUTF8String:copy]]; | ||
1123 | sfree(copy); | ||
1124 | |||
1125 | if (*p) p++; | ||
1126 | } | ||
1127 | } | ||
1128 | [pb selectItemAtIndex:i->ival]; | ||
1129 | [pb sizeToFit]; | ||
1130 | |||
1131 | rect = [pb frame]; | ||
1132 | if (rightw < rect.size.width + 1) rightw = rect.size.width + 1; | ||
1133 | if (thish < rect.size.height + 1) thish = rect.size.height + 1; | ||
1134 | cfg_controls[cfg_ncontrols++] = pb; | ||
1135 | break; | ||
1136 | } | ||
1137 | |||
1138 | h += SPACING + thish; | ||
1139 | } | ||
1140 | |||
1141 | if (totalw < leftw + SPACING + rightw) | ||
1142 | totalw = leftw + SPACING + rightw; | ||
1143 | if (totalw > leftw + SPACING + rightw) { | ||
1144 | int excess = totalw - (leftw + SPACING + rightw); | ||
1145 | int leftexcess = leftw * excess / (leftw + rightw); | ||
1146 | int rightexcess = excess - leftexcess; | ||
1147 | leftw += leftexcess; | ||
1148 | rightw += rightexcess; | ||
1149 | } | ||
1150 | |||
1151 | /* | ||
1152 | * Now go through the list again, setting the final position | ||
1153 | * for each control. | ||
1154 | */ | ||
1155 | k = 0; | ||
1156 | y = h; | ||
1157 | for (i = cfg; i->type != C_END; i++) { | ||
1158 | y -= SPACING; | ||
1159 | thish = 0; | ||
1160 | switch (i->type) { | ||
1161 | case C_STRING: | ||
1162 | case C_CHOICES: | ||
1163 | /* | ||
1164 | * These two are treated identically, since both expect | ||
1165 | * a control on the left and another on the right. | ||
1166 | */ | ||
1167 | rect = [cfg_controls[k] frame]; | ||
1168 | if (thish < rect.size.height + 1) | ||
1169 | thish = rect.size.height + 1; | ||
1170 | rect = [cfg_controls[k+1] frame]; | ||
1171 | if (thish < rect.size.height + 1) | ||
1172 | thish = rect.size.height + 1; | ||
1173 | rect = [cfg_controls[k] frame]; | ||
1174 | rect.origin.y = y - thish/2 - rect.size.height/2; | ||
1175 | rect.origin.x = SPACING; | ||
1176 | rect.size.width = leftw; | ||
1177 | [cfg_controls[k] setFrame:rect]; | ||
1178 | rect = [cfg_controls[k+1] frame]; | ||
1179 | rect.origin.y = y - thish/2 - rect.size.height/2; | ||
1180 | rect.origin.x = 2 * SPACING + leftw; | ||
1181 | rect.size.width = rightw; | ||
1182 | [cfg_controls[k+1] setFrame:rect]; | ||
1183 | k += 2; | ||
1184 | break; | ||
1185 | |||
1186 | case C_BOOLEAN: | ||
1187 | rect = [cfg_controls[k] frame]; | ||
1188 | if (thish < rect.size.height + 1) | ||
1189 | thish = rect.size.height + 1; | ||
1190 | rect.origin.y = y - thish/2 - rect.size.height/2; | ||
1191 | rect.origin.x = SPACING; | ||
1192 | rect.size.width = totalw; | ||
1193 | [cfg_controls[k] setFrame:rect]; | ||
1194 | k++; | ||
1195 | break; | ||
1196 | } | ||
1197 | y -= thish; | ||
1198 | } | ||
1199 | |||
1200 | assert(k == cfg_ncontrols); | ||
1201 | |||
1202 | [cancel setFrame:NSMakeRect(SPACING+totalw/4-actw/2, SPACING, actw, acth)]; | ||
1203 | [ok setFrame:NSMakeRect(SPACING+3*totalw/4-actw/2, SPACING, actw, acth)]; | ||
1204 | |||
1205 | sheet = [[NSWindow alloc] | ||
1206 | initWithContentRect:NSMakeRect(0,0,totalw + 2*SPACING,h) | ||
1207 | styleMask:NSTitledWindowMask | NSClosableWindowMask | ||
1208 | backing:NSBackingStoreBuffered | ||
1209 | defer:YES]; | ||
1210 | |||
1211 | [[sheet contentView] addSubview:cancel]; | ||
1212 | [[sheet contentView] addSubview:ok]; | ||
1213 | |||
1214 | for (k = 0; k < cfg_ncontrols; k++) | ||
1215 | [[sheet contentView] addSubview:cfg_controls[k]]; | ||
1216 | |||
1217 | [app beginSheet:sheet modalForWindow:self | ||
1218 | modalDelegate:nil didEndSelector:NULL contextInfo:nil]; | ||
1219 | } | ||
1220 | |||
1221 | - (void)specificGame:(id)sender | ||
1222 | { | ||
1223 | [self startConfigureSheet:CFG_DESC]; | ||
1224 | } | ||
1225 | |||
1226 | - (void)specificRandomGame:(id)sender | ||
1227 | { | ||
1228 | [self startConfigureSheet:CFG_SEED]; | ||
1229 | } | ||
1230 | |||
1231 | - (void)customGameType:(id)sender | ||
1232 | { | ||
1233 | [self startConfigureSheet:CFG_SETTINGS]; | ||
1234 | } | ||
1235 | |||
1236 | - (void)sheetEndWithStatus:(BOOL)update | ||
1237 | { | ||
1238 | assert(sheet != NULL); | ||
1239 | [app endSheet:sheet]; | ||
1240 | [sheet orderOut:self]; | ||
1241 | sheet = NULL; | ||
1242 | if (update) { | ||
1243 | int k; | ||
1244 | config_item *i; | ||
1245 | char *error; | ||
1246 | |||
1247 | k = 0; | ||
1248 | for (i = cfg; i->type != C_END; i++) { | ||
1249 | switch (i->type) { | ||
1250 | case C_STRING: | ||
1251 | sfree(i->sval); | ||
1252 | i->sval = dupstr([[[(id)cfg_controls[k+1] cell] | ||
1253 | title] UTF8String]); | ||
1254 | k += 2; | ||
1255 | break; | ||
1256 | case C_BOOLEAN: | ||
1257 | i->ival = [(id)cfg_controls[k] state] == NSOnState; | ||
1258 | k++; | ||
1259 | break; | ||
1260 | case C_CHOICES: | ||
1261 | i->ival = [(id)cfg_controls[k+1] indexOfSelectedItem]; | ||
1262 | k += 2; | ||
1263 | break; | ||
1264 | } | ||
1265 | } | ||
1266 | |||
1267 | error = midend_set_config(me, cfg_which, cfg); | ||
1268 | if (error) { | ||
1269 | NSAlert *alert = [[[NSAlert alloc] init] autorelease]; | ||
1270 | [alert addButtonWithTitle:@"Bah"]; | ||
1271 | [alert setInformativeText:[NSString stringWithUTF8String:error]]; | ||
1272 | [alert beginSheetModalForWindow:self modalDelegate:nil | ||
1273 | didEndSelector:NULL contextInfo:nil]; | ||
1274 | } else { | ||
1275 | midend_new_game(me); | ||
1276 | [self resizeForNewGameParams]; | ||
1277 | [self updateTypeMenuTick]; | ||
1278 | } | ||
1279 | } | ||
1280 | sfree(cfg_controls); | ||
1281 | cfg_controls = NULL; | ||
1282 | } | ||
1283 | - (void)sheetOKButton:(id)sender | ||
1284 | { | ||
1285 | [self sheetEndWithStatus:YES]; | ||
1286 | } | ||
1287 | - (void)sheetCancelButton:(id)sender | ||
1288 | { | ||
1289 | [self sheetEndWithStatus:NO]; | ||
1290 | } | ||
1291 | |||
1292 | - (void)setStatusLine:(char *)text | ||
1293 | { | ||
1294 | [[status cell] setTitle:[NSString stringWithUTF8String:text]]; | ||
1295 | } | ||
1296 | |||
1297 | @end | ||
1298 | |||
1299 | /* | ||
1300 | * Drawing routines called by the midend. | ||
1301 | */ | ||
1302 | static void osx_draw_polygon(void *handle, int *coords, int npoints, | ||
1303 | int fillcolour, int outlinecolour) | ||
1304 | { | ||
1305 | frontend *fe = (frontend *)handle; | ||
1306 | NSBezierPath *path = [NSBezierPath bezierPath]; | ||
1307 | int i; | ||
1308 | |||
1309 | [[NSGraphicsContext currentContext] setShouldAntialias:YES]; | ||
1310 | |||
1311 | for (i = 0; i < npoints; i++) { | ||
1312 | NSPoint p = { coords[i*2] + 0.5, fe->h - coords[i*2+1] - 0.5 }; | ||
1313 | if (i == 0) | ||
1314 | [path moveToPoint:p]; | ||
1315 | else | ||
1316 | [path lineToPoint:p]; | ||
1317 | } | ||
1318 | |||
1319 | [path closePath]; | ||
1320 | |||
1321 | if (fillcolour >= 0) { | ||
1322 | assert(fillcolour >= 0 && fillcolour < fe->ncolours); | ||
1323 | [fe->colours[fillcolour] set]; | ||
1324 | [path fill]; | ||
1325 | } | ||
1326 | |||
1327 | assert(outlinecolour >= 0 && outlinecolour < fe->ncolours); | ||
1328 | [fe->colours[outlinecolour] set]; | ||
1329 | [path stroke]; | ||
1330 | } | ||
1331 | static void osx_draw_circle(void *handle, int cx, int cy, int radius, | ||
1332 | int fillcolour, int outlinecolour) | ||
1333 | { | ||
1334 | frontend *fe = (frontend *)handle; | ||
1335 | NSBezierPath *path = [NSBezierPath bezierPath]; | ||
1336 | |||
1337 | [[NSGraphicsContext currentContext] setShouldAntialias:YES]; | ||
1338 | |||
1339 | [path appendBezierPathWithArcWithCenter:NSMakePoint(cx+0.5, fe->h-cy-0.5) | ||
1340 | radius:radius startAngle:0.0 endAngle:360.0]; | ||
1341 | |||
1342 | [path closePath]; | ||
1343 | |||
1344 | if (fillcolour >= 0) { | ||
1345 | assert(fillcolour >= 0 && fillcolour < fe->ncolours); | ||
1346 | [fe->colours[fillcolour] set]; | ||
1347 | [path fill]; | ||
1348 | } | ||
1349 | |||
1350 | assert(outlinecolour >= 0 && outlinecolour < fe->ncolours); | ||
1351 | [fe->colours[outlinecolour] set]; | ||
1352 | [path stroke]; | ||
1353 | } | ||
1354 | static void osx_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) | ||
1355 | { | ||
1356 | frontend *fe = (frontend *)handle; | ||
1357 | NSBezierPath *path = [NSBezierPath bezierPath]; | ||
1358 | NSPoint p1 = { x1 + 0.5, fe->h - y1 - 0.5 }; | ||
1359 | NSPoint p2 = { x2 + 0.5, fe->h - y2 - 0.5 }; | ||
1360 | |||
1361 | [[NSGraphicsContext currentContext] setShouldAntialias:NO]; | ||
1362 | |||
1363 | assert(colour >= 0 && colour < fe->ncolours); | ||
1364 | [fe->colours[colour] set]; | ||
1365 | |||
1366 | [path moveToPoint:p1]; | ||
1367 | [path lineToPoint:p2]; | ||
1368 | [path stroke]; | ||
1369 | NSRectFill(NSMakeRect(x1, fe->h-y1-1, 1, 1)); | ||
1370 | NSRectFill(NSMakeRect(x2, fe->h-y2-1, 1, 1)); | ||
1371 | } | ||
1372 | |||
1373 | static void osx_draw_thick_line( | ||
1374 | void *handle, float thickness, | ||
1375 | float x1, float y1, | ||
1376 | float x2, float y2, | ||
1377 | int colour) | ||
1378 | { | ||
1379 | frontend *fe = (frontend *)handle; | ||
1380 | NSBezierPath *path = [NSBezierPath bezierPath]; | ||
1381 | |||
1382 | assert(colour >= 0 && colour < fe->ncolours); | ||
1383 | [fe->colours[colour] set]; | ||
1384 | [[NSGraphicsContext currentContext] setShouldAntialias: YES]; | ||
1385 | [path setLineWidth: thickness]; | ||
1386 | [path setLineCapStyle: NSButtLineCapStyle]; | ||
1387 | [path moveToPoint: NSMakePoint(x1, fe->h-y1)]; | ||
1388 | [path lineToPoint: NSMakePoint(x2, fe->h-y2)]; | ||
1389 | [path stroke]; | ||
1390 | } | ||
1391 | |||
1392 | static void osx_draw_rect(void *handle, int x, int y, int w, int h, int colour) | ||
1393 | { | ||
1394 | frontend *fe = (frontend *)handle; | ||
1395 | NSRect r = { {x, fe->h - y - h}, {w,h} }; | ||
1396 | |||
1397 | [[NSGraphicsContext currentContext] setShouldAntialias:NO]; | ||
1398 | |||
1399 | assert(colour >= 0 && colour < fe->ncolours); | ||
1400 | [fe->colours[colour] set]; | ||
1401 | |||
1402 | NSRectFill(r); | ||
1403 | } | ||
1404 | static void osx_draw_text(void *handle, int x, int y, int fonttype, | ||
1405 | int fontsize, int align, int colour, char *text) | ||
1406 | { | ||
1407 | frontend *fe = (frontend *)handle; | ||
1408 | NSString *string = [NSString stringWithUTF8String:text]; | ||
1409 | NSDictionary *attr; | ||
1410 | NSFont *font; | ||
1411 | NSSize size; | ||
1412 | NSPoint point; | ||
1413 | |||
1414 | [[NSGraphicsContext currentContext] setShouldAntialias:YES]; | ||
1415 | |||
1416 | assert(colour >= 0 && colour < fe->ncolours); | ||
1417 | |||
1418 | if (fonttype == FONT_FIXED) | ||
1419 | font = [NSFont userFixedPitchFontOfSize:fontsize]; | ||
1420 | else | ||
1421 | font = [NSFont userFontOfSize:fontsize]; | ||
1422 | |||
1423 | attr = [NSDictionary dictionaryWithObjectsAndKeys: | ||
1424 | fe->colours[colour], NSForegroundColorAttributeName, | ||
1425 | font, NSFontAttributeName, nil]; | ||
1426 | |||
1427 | point.x = x; | ||
1428 | point.y = fe->h - y; | ||
1429 | |||
1430 | size = [string sizeWithAttributes:attr]; | ||
1431 | if (align & ALIGN_HRIGHT) | ||
1432 | point.x -= size.width; | ||
1433 | else if (align & ALIGN_HCENTRE) | ||
1434 | point.x -= size.width / 2; | ||
1435 | if (align & ALIGN_VCENTRE) | ||
1436 | point.y -= size.height / 2; | ||
1437 | |||
1438 | [string drawAtPoint:point withAttributes:attr]; | ||
1439 | } | ||
1440 | static char *osx_text_fallback(void *handle, const char *const *strings, | ||
1441 | int nstrings) | ||
1442 | { | ||
1443 | /* | ||
1444 | * We assume OS X can cope with any UTF-8 likely to be emitted | ||
1445 | * by a puzzle. | ||
1446 | */ | ||
1447 | return dupstr(strings[0]); | ||
1448 | } | ||
1449 | struct blitter { | ||
1450 | int w, h; | ||
1451 | int x, y; | ||
1452 | NSImage *img; | ||
1453 | }; | ||
1454 | static blitter *osx_blitter_new(void *handle, int w, int h) | ||
1455 | { | ||
1456 | blitter *bl = snew(blitter); | ||
1457 | bl->x = bl->y = -1; | ||
1458 | bl->w = w; | ||
1459 | bl->h = h; | ||
1460 | bl->img = [[NSImage alloc] initWithSize:NSMakeSize(w, h)]; | ||
1461 | return bl; | ||
1462 | } | ||
1463 | static void osx_blitter_free(void *handle, blitter *bl) | ||
1464 | { | ||
1465 | [bl->img release]; | ||
1466 | sfree(bl); | ||
1467 | } | ||
1468 | static void osx_blitter_save(void *handle, blitter *bl, int x, int y) | ||
1469 | { | ||
1470 | frontend *fe = (frontend *)handle; | ||
1471 | int sx, sy, sX, sY, dx, dy, dX, dY; | ||
1472 | [fe->image unlockFocus]; | ||
1473 | [bl->img lockFocus]; | ||
1474 | |||
1475 | /* | ||
1476 | * Find the intersection of the source and destination rectangles, | ||
1477 | * so as to avoid trying to copy from outside the source image, | ||
1478 | * which GNUstep dislikes. | ||
1479 | * | ||
1480 | * Lower-case x,y coordinates are bottom left box corners; | ||
1481 | * upper-case X,Y are the top right. | ||
1482 | */ | ||
1483 | sx = x; sy = fe->h - y - bl->h; | ||
1484 | sX = sx + bl->w; sY = sy + bl->h; | ||
1485 | dx = dy = 0; | ||
1486 | dX = bl->w; dY = bl->h; | ||
1487 | if (sx < 0) { | ||
1488 | dx += -sx; | ||
1489 | sx = 0; | ||
1490 | } | ||
1491 | if (sy < 0) { | ||
1492 | dy += -sy; | ||
1493 | sy = 0; | ||
1494 | } | ||
1495 | if (sX > fe->w) { | ||
1496 | dX -= (sX - fe->w); | ||
1497 | sX = fe->w; | ||
1498 | } | ||
1499 | if (sY > fe->h) { | ||
1500 | dY -= (sY - fe->h); | ||
1501 | sY = fe->h; | ||
1502 | } | ||
1503 | |||
1504 | [fe->image drawInRect:NSMakeRect(dx, dy, dX-dx, dY-dy) | ||
1505 | fromRect:NSMakeRect(sx, sy, sX-sx, sY-sy) | ||
1506 | operation:NSCompositeCopy fraction:1.0]; | ||
1507 | [bl->img unlockFocus]; | ||
1508 | [fe->image lockFocus]; | ||
1509 | bl->x = x; | ||
1510 | bl->y = y; | ||
1511 | } | ||
1512 | static void osx_blitter_load(void *handle, blitter *bl, int x, int y) | ||
1513 | { | ||
1514 | frontend *fe = (frontend *)handle; | ||
1515 | if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) { | ||
1516 | x = bl->x; | ||
1517 | y = bl->y; | ||
1518 | } | ||
1519 | [bl->img drawInRect:NSMakeRect(x, fe->h - y - bl->h, bl->w, bl->h) | ||
1520 | fromRect:NSMakeRect(0, 0, bl->w, bl->h) | ||
1521 | operation:NSCompositeCopy fraction:1.0]; | ||
1522 | } | ||
1523 | static void osx_draw_update(void *handle, int x, int y, int w, int h) | ||
1524 | { | ||
1525 | frontend *fe = (frontend *)handle; | ||
1526 | [fe->view setNeedsDisplayInRect:NSMakeRect(x, fe->h - y - h, w, h)]; | ||
1527 | } | ||
1528 | static void osx_clip(void *handle, int x, int y, int w, int h) | ||
1529 | { | ||
1530 | frontend *fe = (frontend *)handle; | ||
1531 | NSRect r = { {x, fe->h - y - h}, {w, h} }; | ||
1532 | |||
1533 | if (!fe->clipped) | ||
1534 | [[NSGraphicsContext currentContext] saveGraphicsState]; | ||
1535 | [NSBezierPath clipRect:r]; | ||
1536 | fe->clipped = TRUE; | ||
1537 | } | ||
1538 | static void osx_unclip(void *handle) | ||
1539 | { | ||
1540 | frontend *fe = (frontend *)handle; | ||
1541 | if (fe->clipped) | ||
1542 | [[NSGraphicsContext currentContext] restoreGraphicsState]; | ||
1543 | fe->clipped = FALSE; | ||
1544 | } | ||
1545 | static void osx_start_draw(void *handle) | ||
1546 | { | ||
1547 | frontend *fe = (frontend *)handle; | ||
1548 | [fe->image lockFocus]; | ||
1549 | fe->clipped = FALSE; | ||
1550 | } | ||
1551 | static void osx_end_draw(void *handle) | ||
1552 | { | ||
1553 | frontend *fe = (frontend *)handle; | ||
1554 | [fe->image unlockFocus]; | ||
1555 | } | ||
1556 | static void osx_status_bar(void *handle, char *text) | ||
1557 | { | ||
1558 | frontend *fe = (frontend *)handle; | ||
1559 | [fe->window setStatusLine:text]; | ||
1560 | } | ||
1561 | |||
1562 | const struct drawing_api osx_drawing = { | ||
1563 | osx_draw_text, | ||
1564 | osx_draw_rect, | ||
1565 | osx_draw_line, | ||
1566 | osx_draw_polygon, | ||
1567 | osx_draw_circle, | ||
1568 | osx_draw_update, | ||
1569 | osx_clip, | ||
1570 | osx_unclip, | ||
1571 | osx_start_draw, | ||
1572 | osx_end_draw, | ||
1573 | osx_status_bar, | ||
1574 | osx_blitter_new, | ||
1575 | osx_blitter_free, | ||
1576 | osx_blitter_save, | ||
1577 | osx_blitter_load, | ||
1578 | NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ | ||
1579 | NULL, NULL, /* line_width, line_dotted */ | ||
1580 | osx_text_fallback, | ||
1581 | osx_draw_thick_line, | ||
1582 | }; | ||
1583 | |||
1584 | void deactivate_timer(frontend *fe) | ||
1585 | { | ||
1586 | [fe->window deactivateTimer]; | ||
1587 | } | ||
1588 | void activate_timer(frontend *fe) | ||
1589 | { | ||
1590 | [fe->window activateTimer]; | ||
1591 | } | ||
1592 | |||
1593 | /* ---------------------------------------------------------------------- | ||
1594 | * AppController: the object which receives the messages from all | ||
1595 | * menu selections that aren't standard OS X functions. | ||
1596 | */ | ||
1597 | @interface AppController : NSObject <NSApplicationDelegate> | ||
1598 | { | ||
1599 | } | ||
1600 | - (void)newGameWindow:(id)sender; | ||
1601 | - (void)about:(id)sender; | ||
1602 | @end | ||
1603 | |||
1604 | @implementation AppController | ||
1605 | |||
1606 | - (void)newGameWindow:(id)sender | ||
1607 | { | ||
1608 | const game *g = [sender getPayload]; | ||
1609 | id win; | ||
1610 | |||
1611 | win = [[GameWindow alloc] initWithGame:g]; | ||
1612 | [win makeKeyAndOrderFront:self]; | ||
1613 | } | ||
1614 | |||
1615 | - (void)about:(id)sender | ||
1616 | { | ||
1617 | id win; | ||
1618 | |||
1619 | win = [[AboutBox alloc] init]; | ||
1620 | [win makeKeyAndOrderFront:self]; | ||
1621 | } | ||
1622 | |||
1623 | - (NSMenu *)applicationDockMenu:(NSApplication *)sender | ||
1624 | { | ||
1625 | NSMenu *menu = newmenu("Dock Menu"); | ||
1626 | { | ||
1627 | int i; | ||
1628 | |||
1629 | for (i = 0; i < gamecount; i++) { | ||
1630 | id item = | ||
1631 | initnewitem([DataMenuItem allocWithZone:[NSMenu menuZone]], | ||
1632 | menu, gamelist[i]->name, "", self, | ||
1633 | @selector(newGameWindow:)); | ||
1634 | [item setPayload:(void *)gamelist[i]]; | ||
1635 | } | ||
1636 | } | ||
1637 | return menu; | ||
1638 | } | ||
1639 | |||
1640 | @end | ||
1641 | |||
1642 | /* ---------------------------------------------------------------------- | ||
1643 | * Main program. Constructs the menus and runs the application. | ||
1644 | */ | ||
1645 | int main(int argc, char **argv) | ||
1646 | { | ||
1647 | NSAutoreleasePool *pool; | ||
1648 | NSMenu *menu; | ||
1649 | AppController *controller; | ||
1650 | NSImage *icon; | ||
1651 | |||
1652 | pool = [[NSAutoreleasePool alloc] init]; | ||
1653 | |||
1654 | icon = [NSImage imageNamed:@"NSApplicationIcon"]; | ||
1655 | app = [NSApplication sharedApplication]; | ||
1656 | [app setApplicationIconImage:icon]; | ||
1657 | |||
1658 | controller = [[[AppController alloc] init] autorelease]; | ||
1659 | [app setDelegate:controller]; | ||
1660 | |||
1661 | [app setMainMenu: newmenu("Main Menu")]; | ||
1662 | |||
1663 | menu = newsubmenu([app mainMenu], "Apple Menu"); | ||
1664 | newitem(menu, "About Puzzles", "", NULL, @selector(about:)); | ||
1665 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1666 | [app setServicesMenu:newsubmenu(menu, "Services")]; | ||
1667 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1668 | newitem(menu, "Hide Puzzles", "h", app, @selector(hide:)); | ||
1669 | newitem(menu, "Hide Others", "o-h", app, @selector(hideOtherApplications:)); | ||
1670 | newitem(menu, "Show All", "", app, @selector(unhideAllApplications:)); | ||
1671 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1672 | newitem(menu, "Quit", "q", app, @selector(terminate:)); | ||
1673 | [app setAppleMenu: menu]; | ||
1674 | |||
1675 | menu = newsubmenu([app mainMenu], "File"); | ||
1676 | newitem(menu, "Open", "o", NULL, @selector(loadSavedGame:)); | ||
1677 | newitem(menu, "Save As", "s", NULL, @selector(saveGame:)); | ||
1678 | newitem(menu, "New Game", "n", NULL, @selector(newGame:)); | ||
1679 | newitem(menu, "Restart Game", "r", NULL, @selector(restartGame:)); | ||
1680 | newitem(menu, "Specific Game", "", NULL, @selector(specificGame:)); | ||
1681 | newitem(menu, "Specific Random Seed", "", NULL, | ||
1682 | @selector(specificRandomGame:)); | ||
1683 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1684 | { | ||
1685 | NSMenu *submenu = newsubmenu(menu, "New Window"); | ||
1686 | int i; | ||
1687 | |||
1688 | for (i = 0; i < gamecount; i++) { | ||
1689 | id item = | ||
1690 | initnewitem([DataMenuItem allocWithZone:[NSMenu menuZone]], | ||
1691 | submenu, gamelist[i]->name, "", controller, | ||
1692 | @selector(newGameWindow:)); | ||
1693 | [item setPayload:(void *)gamelist[i]]; | ||
1694 | } | ||
1695 | } | ||
1696 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1697 | newitem(menu, "Close", "w", NULL, @selector(performClose:)); | ||
1698 | |||
1699 | menu = newsubmenu([app mainMenu], "Edit"); | ||
1700 | newitem(menu, "Undo", "z", NULL, @selector(undoMove:)); | ||
1701 | newitem(menu, "Redo", "S-z", NULL, @selector(redoMove:)); | ||
1702 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1703 | newitem(menu, "Cut", "x", NULL, @selector(cut:)); | ||
1704 | newitem(menu, "Copy", "c", NULL, @selector(copy:)); | ||
1705 | newitem(menu, "Paste", "v", NULL, @selector(paste:)); | ||
1706 | [menu addItem:[NSMenuItem separatorItem]]; | ||
1707 | newitem(menu, "Solve", "S-s", NULL, @selector(solveGame:)); | ||
1708 | |||
1709 | menu = newsubmenu([app mainMenu], "Type"); | ||
1710 | typemenu = menu; | ||
1711 | newitem(menu, "Custom", "", NULL, @selector(customGameType:)); | ||
1712 | |||
1713 | menu = newsubmenu([app mainMenu], "Window"); | ||
1714 | [app setWindowsMenu: menu]; | ||
1715 | newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:)); | ||
1716 | |||
1717 | menu = newsubmenu([app mainMenu], "Help"); | ||
1718 | newitem(menu, "Puzzles Help", "?", app, @selector(showHelp:)); | ||
1719 | |||
1720 | [app run]; | ||
1721 | [pool release]; | ||
1722 | |||
1723 | return 0; | ||
1724 | } | ||