-
Notifications
You must be signed in to change notification settings - Fork 55
/
PGDocumentController.m
686 lines (602 loc) · 25.2 KB
/
PGDocumentController.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
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
/* Copyright © 2007-2009, The Sequential Project
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the the Sequential Project nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE SEQUENTIAL PROJECT ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE SEQUENTIAL PROJECT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#import "PGDocumentController.h"
#import <Carbon/Carbon.h>
#import <sys/resource.h>
#import <objc/Protocol.h>
#import <tgmath.h>
// Models
#import "PGDocument.h"
#import "PGResourceAdapter.h"
#import "PGContainerAdapter.h"
#import "PGResourceIdentifier.h"
#import "PGBookmark.h"
// Views
#import "PGOrientationMenuItemCell.h"
// Controllers
#import "PGAboutBoxController.h"
#import "PGPreferenceWindowController.h"
#import "PGDisplayController.h"
#import "PGWindowController.h"
#import "PGFullscreenController.h"
#import "PGInspectorPanelController.h"
#import "PGTimerPanelController.h"
#import "PGActivityPanelController.h"
#import "PGURLAlert.h"
// Other Sources
#import "PGAppKitAdditions.h"
#import "PGDelayedPerforming.h"
#import "PGFoundationAdditions.h"
#import "PGKeyboardLayout.h"
#import "PGLegacy.h"
#import "PGLocalizing.h"
#import "PGZooming.h"
NSString *const PGAntialiasWhenUpscalingKey = @"PGAntialiasWhenUpscaling";
NSString *const PGBackgroundColorKey = @"PGBackgroundColor";
NSString *const PGBackgroundPatternKey = @"PGBackgroundPattern";
NSString *const PGMouseClickActionKey = @"PGMouseClickAction";
NSString *const PGEscapeKeyMappingKey = @"PGEscapeKeyMapping";
NSString *const PGDimOtherScreensKey = @"PGDimOtherScreens";
NSString *const PGBackwardsInitialLocationKey = @"PGBackwardsInitialLocation";
NSString *const PGImageScaleConstraintKey = @"PGImageScaleConstraint";
static NSString *const PGRecentItemsKey = @"PGRecentItems2";
static NSString *const PGRecentItemsDeprecated2Key = @"PGRecentItems"; // Deprecated after 1.3.2
static NSString *const PGRecentItemsDeprecatedKey = @"PGRecentDocuments"; // Deprecated after 1.2.2.
static NSString *const PGFullscreenKey = @"PGFullscreen";
static NSString *const PGPathFinderApplicationName = @"Path Finder";
static PGDocumentController *PGSharedDocumentController = nil;
@interface PGDocumentController(Private)
- (void)_awakeAfterLocalizing;
- (void)_setFullscreen:(BOOL)flag;
- (PGDocument *)_openNew:(BOOL)flag document:(PGDocument *)document display:(BOOL)display;
@end
@implementation PGDocumentController
#pragma mark +PGDocumentController
+ (PGDocumentController *)sharedDocumentController
{
return PGSharedDocumentController ? PGSharedDocumentController : [[[self alloc] init] autorelease];
}
#pragma mark +NSObject
+ (void)initialize
{
if([PGDocumentController class] != self) return;
NSNumber *const yes = [NSNumber numberWithBool:YES], *no = [NSNumber numberWithBool:NO];
NSUserDefaults *const d = [NSUserDefaults standardUserDefaults];
[d registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
yes, PGAntialiasWhenUpscalingKey,
[NSArchiver archivedDataWithRootObject:[NSColor blackColor]], PGBackgroundColorKey,
[NSNumber numberWithUnsignedInteger:PGNoPattern], PGBackgroundPatternKey,
[NSNumber numberWithInteger:PGNextPreviousAction], PGMouseClickActionKey,
[NSNumber numberWithUnsignedInteger:1], PGMaxDepthKey,
no, PGFullscreenKey,
[NSNumber numberWithInteger:PGFullscreenMapping], PGEscapeKeyMappingKey,
no, PGDimOtherScreensKey,
[NSNumber numberWithInteger:PGEndLocation], PGBackwardsInitialLocationKey,
[NSNumber numberWithUnsignedInteger:PGScaleFreely], PGImageScaleConstraintKey,
nil]];
}
#pragma mark -PGDocumentController
- (IBAction)orderFrontStandardAboutPanel:(id)sender
{
[[PGAboutBoxController sharedAboutBoxController] showWindow:self];
}
- (IBAction)showPreferences:(id)sender
{
[[PGPreferenceWindowController sharedPrefController] showWindow:self];
}
- (IBAction)switchToFileManager:(id)sender
{
if(![[[[NSAppleScript alloc] initWithSource:self.pathFinderRunning ? @"tell application \"Path Finder\" to activate" : @"tell application \"Finder\" to activate"] autorelease] executeAndReturnError:NULL]) NSBeep();
}
#pragma mark -
- (IBAction)open:(id)sender
{
[NSApp activateIgnoringOtherApps:YES];
NSOpenPanel *const openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseDirectories:YES];
[openPanel setCanChooseFiles:YES];
[openPanel setAllowsMultipleSelection:YES];
NSURL *const URL = [[[self currentDocument] rootIdentifier] URL];
NSString *const path = [URL isFileURL] ? [URL path] : nil;
if([openPanel runModalForDirectory:[path stringByDeletingLastPathComponent] file:[path lastPathComponent] types:[PGResourceAdapter supportedFileTypes]] == NSOKButton) {
PGDocument *const oldDoc = [self currentDocument];
[self application:NSApp openFiles:[openPanel filenames]];
if([[openPanel currentEvent] modifierFlags] & NSAlternateKeyMask && [self currentDocument] != oldDoc) [oldDoc close];
}
}
- (IBAction)openURL:(id)sender
{
[NSApp activateIgnoringOtherApps:YES];
NSURL *const URL = [(PGURLAlert *)[[[PGURLAlert alloc] init] autorelease] runModal];
if(URL) [self openDocumentWithContentsOfURL:URL display:YES];
}
- (IBAction)openRecentDocument:(id)sender
{
[self openDocumentWithContentsOfIdentifier:[(NSMenuItem *)sender representedObject] display:YES];
}
- (IBAction)clearRecentDocuments:(id)sender
{
[self setRecentDocumentIdentifiers:[NSArray array]];
}
- (IBAction)closeAll:(id)sender
{
[[_fullscreenController window] close];
for(PGDocument *const doc in [self documents]) [[[doc displayController] window] performClose:self];
}
#pragma mark -
- (IBAction)toggleInspector:(id)sender
{
[_inspectorPanel toggleShown];
}
- (IBAction)toggleTimer:(id)sender
{
[_timerPanel toggleShown];
}
- (IBAction)toggleActivity:(id)sender
{
[_activityPanel toggleShown];
}
- (IBAction)selectPreviousDocument:(id)sender
{
PGDocument *const doc = [self next:NO documentBeyond:[self currentDocument]];
[[doc displayController] activateDocument:doc];
}
- (IBAction)selectNextDocument:(id)sender
{
PGDocument *const doc = [self next:YES documentBeyond:[self currentDocument]];
[[doc displayController] activateDocument:doc];
}
- (IBAction)activateDocument:(id)sender
{
PGDocument *const doc = [(NSMenuItem *)sender representedObject];
[[doc displayController] activateDocument:doc];
}
#pragma mark -
- (IBAction)showKeyboardShortcuts:(id)sender
{
[[NSHelpManager sharedHelpManager] openHelpAnchor:@"shortcuts" inBook:[[NSBundle mainBundle] objectForInfoDictionaryKey:PGCFBundleHelpBookNameKey]];
}
#pragma mark -
- (BOOL)performEscapeKeyAction
{
switch([[[NSUserDefaults standardUserDefaults] objectForKey:PGEscapeKeyMappingKey] integerValue]) {
case PGFullscreenMapping: return [self performToggleFullscreen];
case PGQuitMapping: [NSApp terminate:self]; return YES;
}
return NO;
}
- (BOOL)performZoomIn
{
return [zoomIn PG_performAction];
}
- (BOOL)performZoomOut
{
return [zoomOut PG_performAction];
}
- (BOOL)performToggleFullscreen
{
return [toggleFullscreen PG_performAction];
}
#pragma mark -
- (NSArray *)recentDocumentIdentifiers
{
return [[_recentDocumentIdentifiers retain] autorelease];
}
- (void)setRecentDocumentIdentifiers:(NSArray *)anArray
{
NSParameterAssert(anArray);
if(PGEqualObjects(anArray, _recentDocumentIdentifiers)) return;
[_recentDocumentIdentifiers PG_removeObjectObserver:self name:PGDisplayableIdentifierIconDidChangeNotification];
[_recentDocumentIdentifiers PG_removeObjectObserver:self name:PGDisplayableIdentifierDisplayNameDidChangeNotification];
[_recentDocumentIdentifiers release];
_recentDocumentIdentifiers = [[anArray subarrayWithRange:NSMakeRange(0, MIN([anArray count], [self maximumRecentDocumentCount]))] copy];
[_recentDocumentIdentifiers PG_addObjectObserver:self selector:@selector(recentDocumentIdentifierDidChange:) name:PGDisplayableIdentifierIconDidChangeNotification];
[_recentDocumentIdentifiers PG_addObjectObserver:self selector:@selector(recentDocumentIdentifierDidChange:) name:PGDisplayableIdentifierDisplayNameDidChangeNotification];
[self recentDocumentIdentifierDidChange:nil];
}
- (NSUInteger)maximumRecentDocumentCount
{
return [[[[NSDocumentController alloc] init] autorelease] maximumRecentDocumentCount]; // This is ugly but we don't want to use NSDocumentController.
}
- (PGDisplayController *)displayControllerForNewDocument
{
if(self.fullscreen) {
if(!_fullscreenController) _fullscreenController = [[PGFullscreenController alloc] init];
return _fullscreenController;
}
return [[[PGWindowController alloc] init] autorelease];
}
@synthesize fullscreen = _fullscreen;
- (void)setFullscreen:(BOOL)flag
{
if(flag == _fullscreen) return;
_fullscreen = flag;
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:flag] forKey:PGFullscreenKey];
[self _setFullscreen:flag];
}
- (BOOL)canToggleFullscreen
{
if(_fullscreen) return YES;
for(PGDocument *const doc in [self documents]) if([[[doc displayController] window] attachedSheet]) return NO;
return YES;
}
@synthesize documents = _documents;
- (NSMenu *)scaleMenu
{
return [scaleSliderItem menu];
}
- (NSSlider *)scaleSlider
{
return scaleSlider;
}
@synthesize defaultPageMenu;
@synthesize currentDocument = _currentDocument;
- (void)setCurrentDocument:(PGDocument *)document
{
_currentDocument = document;
NSMenu *const menu = [_currentDocument pageMenu];
[pageMenuItem setSubmenu:menu ? menu : [self defaultPageMenu]];
}
- (BOOL)pathFinderRunning
{
for(NSDictionary *const dict in [[NSWorkspace sharedWorkspace] launchedApplications]) if(PGEqualObjects([dict objectForKey:@"NSApplicationName"], PGPathFinderApplicationName)) return YES;
return NO;
}
#pragma mark -
- (void)addDocument:(PGDocument *)document
{
NSParameterAssert([_documents indexOfObjectIdenticalTo:document] == NSNotFound);
if(![_documents count]) [windowsMenu addItem:windowsMenuSeparator];
[_documents addObject:document];
NSMenuItem *const item = [[[NSMenuItem alloc] init] autorelease];
[item setRepresentedObject:document];
[item setAction:@selector(activateDocument:)];
[item setTarget:self];
[windowsMenu addItem:item];
[self _setFullscreen:YES];
}
- (void)removeDocument:(PGDocument *)document
{
NSParameterAssert(!document || [_documents indexOfObjectIdenticalTo:document] != NSNotFound);
if(document == [self currentDocument]) [self setCurrentDocument:nil];
if(!document) return;
[_documents removeObject:document];
NSUInteger const i = [windowsMenu indexOfItemWithRepresentedObject:document];
if(NSNotFound != i) [windowsMenu removeItemAtIndex:i];
if(![_documents count]) [windowsMenuSeparator PG_removeFromMenu];
[self _setFullscreen:[_documents count] > 0];
}
- (PGDocument *)documentForIdentifier:(PGResourceIdentifier *)ident
{
for(PGDocument *const doc in _documents) if(PGEqualObjects([doc rootIdentifier], ident)) return doc;
return nil;
}
- (PGDocument *)next:(BOOL)flag documentBeyond:(PGDocument *)document
{
NSArray *const docs = [[PGDocumentController sharedDocumentController] documents];
NSUInteger const count = [docs count];
if(count <= 1) return nil;
NSUInteger i = [docs indexOfObjectIdenticalTo:[self currentDocument]];
if(NSNotFound == i) return nil;
if(flag) {
if([docs count] == ++i) i = 0;
} else if(0 == i--) i = [docs count] - 1;
return [docs objectAtIndex:i];
}
- (NSMenuItem *)windowsMenuItemForDocument:(PGDocument *)document
{
NSInteger const i = [windowsMenu indexOfItemWithRepresentedObject:document];
return -1 == i ? nil : [windowsMenu itemAtIndex:i];
}
#pragma mark -
- (id)openDocumentWithContentsOfIdentifier:(PGResourceIdentifier *)ident display:(BOOL)flag
{
if(!ident) return nil;
PGDocument *const doc = [self documentForIdentifier:ident];
return [self _openNew:!doc document:doc ? doc : [[(PGDocument *)[PGDocument alloc] initWithIdentifier:[ident displayableIdentifier]] autorelease] display:flag];
}
- (id)openDocumentWithContentsOfURL:(NSURL *)URL display:(BOOL)flag
{
return [self openDocumentWithContentsOfIdentifier:[URL PG_resourceIdentifier] display:flag];
}
- (id)openDocumentWithBookmark:(PGBookmark *)aBookmark display:(BOOL)flag
{
PGDocument *const doc = [self documentForIdentifier:[aBookmark documentIdentifier]];
[doc openBookmark:aBookmark];
return [self _openNew:!doc document:doc ? doc : [[[PGDocument alloc] initWithBookmark:aBookmark] autorelease] display:flag];
}
- (void)noteNewRecentDocument:(PGDocument *)document
{
PGDisplayableIdentifier *const identifier = [document rootIdentifier];
if(!identifier) return;
NSMutableArray *const identifiers = [[[self recentDocumentIdentifiers] mutableCopy] autorelease];
[identifiers removeObject:identifier];
[identifiers insertObject:identifier atIndex:0];
[self setRecentDocumentIdentifiers:identifiers];
}
#pragma mark -
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
if([event eventClass] == kInternetEventClass && [event eventID] == kAEGetURL) [self openDocumentWithContentsOfURL:[NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]] display:YES];
}
#pragma mark -
- (void)recentDocumentIdentifierDidChange:(NSNotification *)aNotif
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_recentDocumentIdentifiers] forKey:PGRecentItemsKey];
}
#pragma mark -PGDocumentController(Private)
- (void)_awakeAfterLocalizing
{
for(NSMenuItem *const item in [orientationMenu itemArray]) [PGOrientationMenuIconCell addOrientationMenuIconCellToMenuItem:item];
}
- (void)_setFullscreen:(BOOL)flag
{
if(flag == _inFullscreen) return;
NSDisableScreenUpdates();
if(!flag) {
_inFullscreen = flag;
[_fullscreenController prepareToExitFullscreen];
NSMutableArray *const docs = [[[self documents] mutableCopy] autorelease];
PGDocument *const currentDoc = [_fullscreenController activeDocument];
if(currentDoc) {
[docs removeObjectIdenticalTo:currentDoc];
[docs addObject:currentDoc];
}
for(PGDocument *const doc in docs) {
[doc setDisplayController:[self displayControllerForNewDocument]];
[[doc displayController] showWindow:self];
}
[[_fullscreenController window] close];
[_fullscreenController release];
_fullscreenController = nil;
} else if([[self documents] count] && self.fullscreen) {
_inFullscreen = flag;
PGDocument *const currentDoc = [self currentDocument];
_fullscreenController = [[PGFullscreenController alloc] init];
for(PGDocument *const doc in [self documents]) {
PGDisplayController *const oldController = [doc displayController];
if(!oldController) continue;
[doc setDisplayController:_fullscreenController];
[[oldController window] close];
}
[_fullscreenController setActiveDocument:currentDoc closeIfAppropriate:NO];
[_fullscreenController showWindow:self];
}
NSEnableScreenUpdates();
}
- (PGDocument *)_openNew:(BOOL)flag document:(PGDocument *)document display:(BOOL)display
{
if(!document) return nil;
if(flag) [self addDocument:document];
if(display) [document createUI];
return document;
}
#pragma mark -NSResponder
- (BOOL)performKeyEquivalent:(NSEvent *)anEvent
{
if(!([anEvent modifierFlags] & (NSCommandKeyMask | NSShiftKeyMask | NSAlternateKeyMask))) switch([anEvent keyCode]) {
case PGKeyEscape: [self performEscapeKeyAction]; break;
case PGKeyQ: [NSApp terminate:self]; return YES;
}
return NO;
}
#pragma mark -NSObject
- (id)init
{
if((self = [super init])) {
NSUserDefaults *const defaults = [NSUserDefaults standardUserDefaults];
id recentItemsData = [defaults objectForKey:PGRecentItemsKey];
if(!recentItemsData) {
recentItemsData = [defaults objectForKey:PGRecentItemsDeprecated2Key];
[defaults removeObjectForKey:PGRecentItemsDeprecated2Key]; // Don't leave unused data around.
}
if(!recentItemsData) {
recentItemsData = [defaults objectForKey:PGRecentItemsDeprecatedKey];
[defaults removeObjectForKey:PGRecentItemsDeprecatedKey]; // Don't leave unused data around.
}
[self setRecentDocumentIdentifiers:recentItemsData ? [NSKeyedUnarchiver unarchiveObjectWithData:recentItemsData] : [NSArray array]];
_fullscreen = [[defaults objectForKey:PGFullscreenKey] boolValue];
_documents = [[NSMutableArray alloc] init];
_classesByExtension = [[NSMutableDictionary alloc] init];
_inspectorPanel = [[PGInspectorPanelController alloc] init];
_timerPanel = [[PGTimerPanelController alloc] init];
_activityPanel = [[PGActivityPanelController alloc] init];
if(!PGSharedDocumentController) {
PGSharedDocumentController = [self retain];
[[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
[self setNextResponder:[NSApp nextResponder]];
[NSApp setNextResponder:self];
}
}
return self;
}
- (void)dealloc
{
if(PGSharedDocumentController == self) [[NSAppleEventManager sharedAppleEventManager] removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL];
[self PG_removeObserver];
[defaultPageMenu release];
[windowsMenuSeparator release];
[_recentDocumentIdentifiers release];
[_documents release];
[_fullscreenController release];
[_inspectorPanel release];
[_timerPanel release];
[_activityPanel release];
[_classesByExtension release];
[super dealloc];
}
#pragma mark -NSObject(NSMenuValidation)
- (BOOL)validateMenuItem:(NSMenuItem *)anItem
{
SEL const action = [anItem action];
// Sequential:
if(@selector(switchToFileManager:) == action) [anItem setTitle:NSLocalizedString((self.pathFinderRunning ? @"Switch to Path Finder" : @"Switch to Finder"), @"Switch to Finder or Path Finder (www.cocoatech.com). Two states of the same item.")];
// Window:
if(@selector(activateDocument:) == action) [anItem setState:[anItem representedObject] == [self currentDocument]];
if([[self documents] count] <= 1) {
if(@selector(selectPreviousDocument:) == action) return NO;
if(@selector(selectNextDocument:) == action) return NO;
}
if(![[self recentDocumentIdentifiers] count]) {
if(@selector(clearRecentDocuments:) == action) return NO;
}
return [super validateMenuItem:anItem];
}
#pragma mark -NSObject(NSNibAwaking)
- (void)awakeFromNib
{
[defaultPageMenu retain];
[windowsMenuSeparator retain];
[windowsMenuSeparator PG_removeFromMenu];
[zoomIn setKeyEquivalent:@"+"];
[zoomIn setKeyEquivalentModifierMask:0];
[zoomOut setKeyEquivalent:@"-"];
[zoomOut setKeyEquivalentModifierMask:0];
[scaleSliderItem setView:[scaleSlider superview]];
[scaleSlider setMinValue:log2(PGScaleMin)];
[scaleSlider setMaxValue:log2(PGScaleMax)];
[selectPreviousDocument setKeyEquivalent:[NSString stringWithFormat:@"%C", (unichar)0x21E1]];
[selectPreviousDocument setKeyEquivalentModifierMask:NSCommandKeyMask];
[selectNextDocument setKeyEquivalent:[NSString stringWithFormat:@"%C", (unichar)0x21E3]];
[selectNextDocument setKeyEquivalentModifierMask:NSCommandKeyMask];
[self _setFullscreen:_fullscreen];
[self setCurrentDocument:nil];
[self performSelector:@selector(_awakeAfterLocalizing) withObject:nil afterDelay:0.0f inModes:[NSArray arrayWithObject:(NSString *)kCFRunLoopCommonModes]];
}
#pragma mark -<NSApplicationDelegate>
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
{
return !![self openDocumentWithContentsOfURL:[filename PG_fileURL] display:YES];
}
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
{
for(NSString *const filename in filenames) [self openDocumentWithContentsOfURL:[filename PG_fileURL] display:YES];
[sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
}
#pragma mark -<NSMenuDelegate>
- (void)menuNeedsUpdate:(NSMenu *)menu
{
[menu PG_removeAllItems];
BOOL addedAnyItems = NO;
NSArray *const identifiers = [self recentDocumentIdentifiers];
for(PGDisplayableIdentifier *const identifier in identifiers) {
if(![identifier URL]) continue; // Make sure the URLs are valid.
BOOL uniqueName = YES;
NSString *const name = [identifier displayName];
for(PGDisplayableIdentifier *const comparisonIdentifier in identifiers) {
if(comparisonIdentifier == identifier || !PGEqualObjects([comparisonIdentifier displayName], name)) continue;
uniqueName = NO;
break;
}
NSMenuItem *const item = [[[NSMenuItem alloc] initWithTitle:@"" action:@selector(openRecentDocument:) keyEquivalent:@""] autorelease];
[item setAttributedTitle:[identifier attributedStringWithAncestory:!uniqueName]];
[item setRepresentedObject:identifier];
[menu addItem:item];
addedAnyItems = YES;
}
if(addedAnyItems) [menu addItem:[NSMenuItem separatorItem]];
[menu addItem:[[[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Clear Menu", @"Clear the Open Recent menu. Should be the same as the standard text.") action:@selector(clearRecentDocuments:) keyEquivalent:@""] autorelease]];
}
@end
@interface PGApplication : NSApplication
@end
@interface PGWindow : NSWindow
@end
@interface PGView : NSView
@end
@interface PGMenu : NSMenu
@end
@interface PGMenuItem : NSMenuItem
@end
@interface PGButton : NSButton
@end
static BOOL (*PGNSWindowValidateMenuItem)(id, SEL, NSMenuItem *);
static BOOL (*PGNSMenuPerformKeyEquivalent)(id, SEL, NSEvent *);
static void (*PGNSMenuItemSetEnabled)(id, SEL, BOOL);
static BOOL (*PGNSButtonPerformKeyEquivalent)(id, SEL, NSEvent *);
@implementation PGApplication
+ (void)initialize
{
if([PGApplication class] != self) return;
PGNSWindowValidateMenuItem = [NSWindow PG_useInstance:YES implementationFromClass:[PGWindow class] forSelector:@selector(validateMenuItem:)];
PGNSMenuPerformKeyEquivalent = [NSMenu PG_useInstance:YES implementationFromClass:[PGMenu class] forSelector:@selector(performKeyEquivalent:)];
PGNSMenuItemSetEnabled = [NSMenuItem PG_useInstance:YES implementationFromClass:[PGMenuItem class] forSelector:@selector(setEnabled:)];
PGNSButtonPerformKeyEquivalent = [NSButton PG_useInstance:YES implementationFromClass:[PGButton class] forSelector:@selector(performKeyEquivalent:)];
struct rlimit const lim = {RLIM_INFINITY, RLIM_INFINITY};
(void)setrlimit(RLIMIT_NOFILE, &lim); // We use a lot of file descriptors.
[NSBundle PG_prepareToAutoLocalize];
}
- (void)sendEvent:(NSEvent *)anEvent
{
if([anEvent window] || [anEvent type] != NSKeyDown || !([[self mainMenu] performKeyEquivalent:anEvent] || [[PGDocumentController sharedDocumentController] performKeyEquivalent:anEvent])) [super sendEvent:anEvent];
}
@end
@implementation PGWindow
- (BOOL)validateMenuItem:(NSMenuItem *)anItem
{
if(@selector(PG_grow:) == [anItem action]) return [self styleMask] & NSResizableWindowMask && [[self standardWindowButton:NSWindowZoomButton] isEnabled];
return PGNSWindowValidateMenuItem(self, _cmd, anItem);
}
@end
@implementation PGMenu
- (BOOL)performKeyEquivalent:(NSEvent *)anEvent
{
if([anEvent type] != NSKeyDown) return NO;
NSInteger i;
NSInteger const count = [self numberOfItems];
for(i = 0; i < count; i++) {
NSMenuItem *const item = [self itemAtIndex:i];
NSString *const equiv = [item keyEquivalent];
if([equiv length] != 1) continue;
unsigned short const keyCode = PGKeyCodeFromUnichar([equiv characterAtIndex:0]);
if(PGKeyUnknown == keyCode || [anEvent keyCode] != keyCode) continue; // Some non-English keyboard layouts switch to English when the Command key is held, but that doesn't help our shortcuts that don't use Command, so we have to check by key code.
NSUInteger const modifiersMask = NSCommandKeyMask | NSShiftKeyMask | NSAlternateKeyMask;
if(([anEvent modifierFlags] & modifiersMask) != ([item keyEquivalentModifierMask] & modifiersMask)) continue;
return [item PG_performAction];
}
for(i = 0; i < count; i++) if([[[self itemAtIndex:i] submenu] performKeyEquivalent:anEvent]) return YES;
return [NSApp mainMenu] == self ? PGNSMenuPerformKeyEquivalent(self, _cmd, anEvent) : NO;
}
@end
@implementation PGMenuItem
- (void)setEnabled:(BOOL)flag
{
PGNSMenuItemSetEnabled(self, _cmd, flag);
[[self view] PG_setEnabled:flag recursive:YES];
}
@end
@implementation PGButton
#pragma mark -NSView
- (BOOL)performKeyEquivalent:(NSEvent *)anEvent
{
if(PGNSButtonPerformKeyEquivalent(self, _cmd, anEvent)) return YES;
if(![[NSArray arrayWithObjects:@"\r", @"\n", nil] containsObject:[self keyEquivalent]]) return NO;
if(![[anEvent charactersIgnoringModifiers] isEqual:[self keyEquivalent]]) return NO;
NSUInteger const sharedModifiers = [anEvent modifierFlags] & [self keyEquivalentModifierMask];
if([self keyEquivalentModifierMask] == sharedModifiers) {
[[self cell] performClick:self];
return YES;
}
return NO;
}
@end