-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPZWindowController.m
470 lines (389 loc) · 14.5 KB
/
PZWindowController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
//
// PZWindowController.m
// PzzApp
//
// Created by Ana Ca–izares on 27.03.12.
// Copyright 2012 Pil Pel Soft. All rights reserved.
//
#import "PZWindowController.h"
NSString* PZWindowPreferencesUpdated = @"PZWindowPreferencesUpdated";
@implementation PZButtonCell
@synthesize imageAlpha;
// FIXME!! This constructor never gets called!!
- (id) init
{
if (! (self = [super init])) {
[self release];
return nil;
}
imageAlpha = 1.0;
return self;
}
// FIXME!! This constructor never gets called!!
- (id) initImageCell: (NSImage*) anImage {
if (self = [super initImageCell:anImage])
imageAlpha = 1.0;
else
[self release];
return self;
}
// FIXME!! Since the constructors don't get called, we cannot use this!
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
[super drawWithFrame:cellFrame inView:controlView];
/*
if ([controlView isFlipped]) { // Careful: the view IS flipped
// TO-DO
[self.image drawInRect:cellFrame
fromRect:NSZeroRect
operation:NSCompositeSourceOver //Use alpha mask
fraction:imageAlpha];
} else {
[self.image drawInRect:cellFrame
fromRect:NSZeroRect
operation:NSCompositeSourceOver //Use alpha mask
fraction:imageAlpha];
}
*/
}
@end
/******************************************************************************
PZWindowController: private interface using a "class extension"
******************************************************************************/
@interface PZWindowController()
- (void) shuffle;
- (void) restart;
- (void) redrawAll;
- (void) animateSolvedPuzzle;
- (void) takeEmptyPieceOff;
- (void) refreshViewHack;
@end
/******************************************************************************
PZWindowController implementation
******************************************************************************/
@implementation PZWindowController
/// FIXME. This is WRONG, crashes if puzz initWithPrefs: returns nil.
- (id) initWithPrefs: (NSMutableDictionary*) prefs
{
currSheetMode = none;
puzzlePrefs = [[NSMutableDictionary alloc] initWithDictionary: prefs];
puzz = [[PZModel alloc] initWithPrefs:puzzlePrefs];
matrixBgColor = [prefs valueForKey:@"BackgroundColor"];
if (puzz == nil || ! (self = [super initWithWindowNibName:@"PZWindow"])) {
[self release];
[puzzlePrefs release];
[puzz release];
NSLog(@"PZWindowController initWithPrefs:");
return nil;
}
return self;
}
- (void) dealloc
{
[puzz release];
[puzzlePrefs release];
[super dealloc];
}
- (void) awakeFromNib
{
// For some reason, the prototype set using IB, although apparently the
// same as the default cell, has different properties, so we must set it here.
PZButtonCell *prototype = (PZButtonCell*)[matrix cellAtRow:0 column:0];
[matrix setPrototype:prototype];
[matrix setBackgroundColor:matrixBgColor];
}
/******************************************************************************
Undo management:
"When the first responder of an application receives an undo or redo message,
NSResponder goes up the responder chain looking for a next responder that
returns an NSUndoManager object from undoManager. Any returned undo manager is
used for the undo or redo operation.
If the undoManager message wends its way up the responder chain to the window,
the NSWindow object queries its delegates with windowWillReturnUndoManager: to
see if the delegate has an undo manager. If the delegate does not implement
this method, the window creates an NSUndoManager object for the window and all
its views.
Document-based applications often make their NSDocument objects the delegates
of their windows and have them respond to the windowWillReturnUndoManager:
message by returning the undo manager used for the document. These applications
can also make each NSWindowController object the delegate of its window - the
window controller implements windowWillReturnUndoManager: to get the undo
manager from its document and return it."
******************************************************************************/
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window
{
return [[self document] undoManager];
}
/******************************************************************************
Modifications of the model:
These four methods perform the basic operations on the model.
******************************************************************************/
- (void) changeSizeWithRows: (int) r columns: (int) c
{
if ((r == puzz.size.rows && c == puzz.size.columns) ||
r < 1 || c < 1 || r > MAX_ROWS || c > MAX_COLS)
return;
NSSize s = [[[super window] contentView] frame].size;
s.width /= c;
s.height /= r;
[matrix setCellSize:s];
[matrix renewRows:r columns:c];
[puzzlePrefs setObject:[NSNumber numberWithInt:r] forKey:@"Rows"];
[puzzlePrefs setObject:[NSNumber numberWithInt:c] forKey:@"Columns"];
[self restart];
}
- (void) shuffle
{
[puzz setPrefs:puzzlePrefs];
[puzz shuffle];
[self redrawAll];
[self takeEmptyPieceOff];
}
- (void) restart
{
[puzz setPrefs:puzzlePrefs];
[puzz reset];
[self redrawAll];
[self takeEmptyPieceOff];
[self refreshViewHack];
[matrix setIntercellSpacing: NSMakeSize(1.0, 1.0)];
[[matrix superview] setNeedsDisplay: YES];
}
- (BOOL) movePieceAtRow: (int) r column: (int) c
{
PZPos* piece = [PZPos posWithX:c Y:r];
if (! [puzz move: piece])
return FALSE;
//// "from" was modified by [puzz move] and now contains the position
//// of the shifted piece
[[[[self document]undoManager]
prepareWithInvocationTarget:self] movePieceAtRow: piece.y column: piece.x];
[[[self document]undoManager] setActionName:@"move"];
[[matrix cellAtRow:piece.y column:piece.x] setImage: [puzz imageAt:piece]];
[self takeEmptyPieceOff];
if ([puzz isSolved])
[self animateSolvedPuzzle];
return TRUE;
}
- (BOOL) changeImage: (NSImage*) newImage {
[puzzlePrefs setObject:[newImage retain] forKey:@"Image"];
[self restart];
return YES;
}
/******************************************************************************
Redrawing and animating:
When the puzzle is solved, we display an animation and then draw the original
image. We need to set ourselves as a NSAnimationDelegate for that. See below.
******************************************************************************/
- (void) redrawAll
{
for (int i=0; i<puzz.size.rows; i++)
for (int j=0; j<puzz.size.columns; j++)
[[matrix cellAtRow:i column:j] setImage: [puzz imageAt:[PZPos posWithX:j Y:i]]];
}
- (void) takeEmptyPieceOff
{
PZButtonCell* piece = [matrix cellAtRow:puzz.emptyPos.y column:puzz.emptyPos.x];
// setting the image's alpha to nil (using a special PZButtonCell) would
// be preferable to setting it to nil if we wanted to fade the piece in
// after the puzzle is completed. Since this doesn't work, we fade out
// the whole puzzle instead and leave this as is. (FIXME)
//piece.imageAlpha = 0.0;
[piece setImage:nil];
}
// Part of the NSAnimationDelegate Protocol
// We use this for the animation started by animateSolvedPuzzle
// FIXME: this doesn't get called.
- (void) animationDidEnd: (NSAnimation*) animation
{
[matrix setIntercellSpacing: NSMakeSize(0.0, 0.0)];
[matrix setAlphaValue: 1.0];
}
- (void) animateSolvedPuzzle
{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.5];
//PZButtonCell* cell = [matrix cellAtRow:puzz.emptyPos.y column:puzz.emptyPos.x];
[(NSAnimation*)[matrix animator] setDelegate:self];
[[matrix animator] setAlphaValue: 0.0];
[NSAnimationContext endGrouping];
// WARNING. Any code here will get executed before the animation even
// starts because it runs concurrently. We need to set ourselves as
// delegate to the started NSAnimation.
}
// For some misterious reason, we must resize the window in order to get the
// matrix to display it cells.
- (void) refreshViewHack
{
NSRect rect = [[super window] frame];
rect.size.width += 1.0;
[[super window] setFrame: rect display: YES];
rect.size.width -= 1.0;
[[super window] setFrame: rect display: YES];
}
/******************************************************************************
Actions available from the user interface.
Menu items and possibly some other UI items have their actions sent to the
responder chain. We implement those methods and decide whether checkmarks
must be updated, etc., using validateUserInterfaceItem: and validateMenuItem:
******************************************************************************/
- (void) doChangeSize: (id) sender
{
// The tag for the menu item calling us is n for an entry "n x n"
if ([sender tag] == puzz.size.rows) return;
AlertBlock codeAfterOKButton = ^(void) {
[self changeSizeWithRows:[sender tag] columns:[sender tag]];
};
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowChangeSizeSheet"]) {
currSheetMode = changeSizeSheet;
[self openAlertSheet: codeAfterOKButton];
} else {
codeAfterOKButton();
}
}
- (void) doChangeLevel: (id) sender
{
if (puzz.level == [sender tag]) return;
AlertBlock codeAfterOKButton = ^(void) {
[puzzlePrefs setObject:[NSNumber numberWithInt:[sender tag]] forKey:@"Level"];
[self shuffle];
};
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowChangeLevelSheet"]) {
currSheetMode = changeLevelSheet;
[self openAlertSheet: codeAfterOKButton];
} else {
codeAfterOKButton();
}
}
- (IBAction) doChangeEmptyCell: (id) sender
{
AlertBlock codeAfterOKButton = ^(void) {
[puzz reset];
[self redrawAll];
//Comienza el modo de edici—n de la pieza en blanco.
[matrix setAction:@selector(doTakePieceOff:)];
};
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowChooseEmptyBlockSheet"]) {
currSheetMode = chooseEmptyBlockSheet;
[self openAlertSheet: codeAfterOKButton];
} else {
codeAfterOKButton();
}
}
// This method does never get called.
- (void) doChangeBackgroundColor: (id) sender
{
matrixBgColor = [sender color];
[matrix setBackgroundColor:matrixBgColor];
// TODO: send notification
NSMutableDictionary *dict = [NSMutableDictionary
dictionaryWithObject:[matrix backgroundColor] forKey:@"BackgroundColor"];
[[NSNotificationCenter defaultCenter]
postNotificationName:PZWindowPreferencesUpdated
object:self
userInfo:dict];
}
- (void) doTakePieceOff: (id) sender
{
NSInteger r, c;
[matrix getRow:&r column:&c ofCell:[matrix selectedCell]];
[puzzlePrefs setObject:[NSNumber numberWithInt:c] forKey:@"EmptyX"];
[puzzlePrefs setObject:[NSNumber numberWithInt:r] forKey:@"EmptyY"];
[puzz setPrefs:puzzlePrefs];
[self takeEmptyPieceOff];
// Termina el modo de edici—n de la pieza en blanco.
[matrix setAction:@selector(doMovePiece:)];
}
- (void) doShuffle: (id) sender
{
AlertBlock codeAfterOKButton = ^(void) { [self shuffle]; };
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowShuffleSheet"]) {
currSheetMode = shuffleSheet;
[self openAlertSheet: codeAfterOKButton];
} else {
codeAfterOKButton();
}
}
- (void) doRestart: (id) sender
{
AlertBlock codeAfterOKButton = ^(void) { [self restart]; [self takeEmptyPieceOff];};
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowRestartSheet"]) {
currSheetMode = restartSheet;
[self openAlertSheet: codeAfterOKButton ];
} else
codeAfterOKButton();
}
- (IBAction) doMovePiece: (id) sender
{
NSInteger r, c;
[matrix getRow:&r column:&c ofCell:[matrix selectedCell]];
[self movePieceAtRow:r column:c];
}
- (IBAction) showOrNotAgain: (id) sender
{
NSString *changedKey = [NSString string];
NSString *newValue = [NSString string];
if ([sender state]) newValue = @"NO";
else newValue = @"YES";
if (currSheetMode == restartSheet)
changedKey = @"ShowRestartSheet";
else if (currSheetMode == shuffleSheet)
changedKey = @"ShowShuffleSheet";
else if (currSheetMode == changeLevelSheet)
changedKey = @"ShowChangeLevelSheet";
else if (currSheetMode == changeSizeSheet)
changedKey = @"ShowChangeSizeSheet";
else if (currSheetMode == chooseEmptyBlockSheet)
changedKey = @"ShowChooseEmptyBlockSheet";
else if (currSheetMode == chooseImageSheet)
changedKey = @"ShowChooseImageSheet";
[[NSUserDefaults standardUserDefaults] setObject:newValue forKey:changedKey];
}
//- (BOOL)validateUserInterfaceItem:(id < NSValidatedUserInterfaceItem >)item
- (BOOL) validateMenuItem:(NSMenuItem *)item
{
if ([item action] == @selector(doRestart:)) {
if ([puzz isSolved])
return NO;
} else if ([item action] == @selector(doChangeSize:)) {
if ([item tag] == puzz.size.rows) // FIXME: this sucks
[item setState: NSOnState];
else
[item setState: NSOffState];
} else if ([item action] == @selector(doChangeLevel:)) {
if ([item tag] == puzz.level)
[item setState: NSOnState];
else
[item setState: NSOffState];
}
return YES;
}
/******************************************************************************
Sheet management
******************************************************************************/
- (IBAction) alertSheetOK: (id) sender
{
[NSApp endSheet: alertSheet returnCode: NSOKButton];
[alertSheet orderOut:nil];
}
- (IBAction) alertSheetCancel: (id) sender
{
[NSApp endSheet:alertSheet returnCode: NSCancelButton];
[alertSheet orderOut:nil];
}
- (void) openAlertSheet: (AlertBlock) block
{
[NSApp beginSheet: alertSheet
modalForWindow: [super window]
modalDelegate: self
didEndSelector: @selector(alertSheetDidEnd: returnCode: contextInfo:)
contextInfo: [block copy]];
}
- (void) alertSheetDidEnd: (NSWindow *) sheet
returnCode: (int) returnCode contextInfo: (void *) block
{
if (returnCode == NSOKButton)
((AlertBlock)block)();
[(AlertBlock)block release];
[alertSheetCheck setState:NSOffState];
}
@end