API  0.9.8
 All Classes Files Functions Variables Typedefs Macros Groups Pages
CPApplication.j
Go to the documentation of this file.
1 /*
2  * CPApplication.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 @typedef CPModalSession
26 
27 var CPMainCibFile = @"CPMainCibFile",
28  CPMainCibFileHumanFriendly = @"Main cib file base name",
30 
31 
33 
34 @optional
35 - (void)applicationDidBecomeActive:(CPNotification)aNotification;
36 - (void)applicationDidChangeScreenParameters:(CPNotification)aNotification;
37 - (void)applicationDidFinishLaunching:(CPNotification)aNotification;
38 - (void)applicationDidResignActive:(CPNotification)aNotification;
39 - (void)applicationWillBecomeActive:(CPNotification)aNotification;
40 - (void)applicationWillFinishLaunching:(CPNotification)aNotification;
41 - (void)applicationWillResignActive:(CPNotification)aNotification;
42 - (void)applicationWillTerminate:(CPNotification)aNotification;
43 
44 @end
45 
70 @implementation CPApplication : CPResponder
71 {
72  CPArray _eventListeners;
73  int _eventListenerInsertionIndex;
74 
75  CPEvent _currentEvent;
76  CPWindow _lastMouseMoveWindow;
77 
78  CPArray _windows;
79  CPWindow _keyWindow;
80  CPWindow _mainWindow;
81  CPWindow _previousKeyWindow;
82  CPWindow _previousMainWindow;
83 
84  CPDocumentController _documentController;
85 
86  CPModalSession _currentSession;
87 
88  //
89  id <CPApplicationDelegate> _delegate;
90  BOOL _finishedLaunching;
91  BOOL _isActive;
92 
93  CPDictionary _namedArgs;
94  CPArray _args;
95  CPString _fullArgsString;
96 
97  CPImage _applicationIconImage;
98 
99  CPPanel _aboutPanel;
100 
101  CPThemeBlend _themeBlend;
102 }
103 
109 + (CPApplication)sharedApplication
110 {
111  if (!CPApp)
112  CPApp = [[CPApplication alloc] init];
113 
114  return CPApp;
115 }
116 
122 - (id)init
123 {
124  self = [super init];
125 
126  CPApp = self;
127 
128  if (self)
129  {
130  _eventListeners = [];
131  _eventListenerInsertionIndex = 0;
132 
133  _windows = [[CPNull null]];
134  }
135 
136  return self;
137 }
138 
139 // Configuring Applications
140 
147 - (void)setDelegate:(id <CPApplicationDelegate>)aDelegate
148 {
149  if (_delegate == aDelegate)
150  return;
151 
152  var defaultCenter = [CPNotificationCenter defaultCenter],
153  delegateNotifications =
154  [
155  CPApplicationWillFinishLaunchingNotification, @selector(applicationWillFinishLaunching:),
156  CPApplicationDidFinishLaunchingNotification, @selector(applicationDidFinishLaunching:),
157  CPApplicationWillBecomeActiveNotification, @selector(applicationWillBecomeActive:),
158  CPApplicationDidBecomeActiveNotification, @selector(applicationDidBecomeActive:),
159  CPApplicationWillResignActiveNotification, @selector(applicationWillResignActive:),
160  CPApplicationDidResignActiveNotification, @selector(applicationDidResignActive:),
161  CPApplicationWillTerminateNotification, @selector(applicationWillTerminate:),
162  CPApplicationDidChangeScreenParametersNotification, @selector(applicationDidChangeScreenParameters:)
163  ],
164  count = [delegateNotifications count];
165 
166  if (_delegate)
167  {
168  var index = 0;
169 
170  for (; index < count; index += 2)
171  {
172  var notificationName = delegateNotifications[index],
173  selector = delegateNotifications[index + 1];
174 
175  if ([_delegate respondsToSelector:selector])
176  [defaultCenter removeObserver:_delegate name:notificationName object:self];
177  }
178  }
179 
180  _delegate = aDelegate;
181 
182  var index = 0;
183 
184  for (; index < count; index += 2)
185  {
186  var notificationName = delegateNotifications[index],
187  selector = delegateNotifications[index + 1];
188 
189  if ([_delegate respondsToSelector:selector])
190  [defaultCenter addObserver:_delegate selector:selector name:notificationName object:self];
191  }
192 }
193 
197 - (id)delegate
198 {
199  return _delegate;
200 }
201 
208 - (void)finishLaunching
209 {
210  // At this point we clear the window.status to eliminate Safari's "Cancelled" error message
211  // The message shouldn't be displayed, because only an XHR is cancelled, but it is a usability issue.
212  // We do it here so that applications can change it in willFinish or didFinishLaunching
213 #if PLATFORM(DOM)
214  window.status = " ";
215 #endif
216 
217  // We also want to set the default cursor on the body, so that buttons and things don't have an iBeam
219 
220  var bundle = [CPBundle mainBundle],
221  delegateClassName = [bundle objectForInfoDictionaryKey:@"CPApplicationDelegateClass"];
222 
223  if (delegateClassName)
224  {
225  var delegateClass = objj_getClass(delegateClassName);
226 
227  if (delegateClass)
228  [self setDelegate:[[delegateClass alloc] init]];
229  }
230 
231  var defaultCenter = [CPNotificationCenter defaultCenter];
232 
233  [defaultCenter
234  postNotificationName:CPApplicationWillFinishLaunchingNotification
235  object:self];
236 
237  var types = [bundle objectForInfoDictionaryKey:@"CPBundleDocumentTypes"];
238 
239  if ([types count] > 0)
240  _documentController = [CPDocumentController sharedDocumentController];
241 
242  var needsUntitled = !!_documentController,
243  URLStrings = nil;
244 
245 #if PLATFORM(DOM)
246  URLStrings = window.cpOpeningURLStrings && window.cpOpeningURLStrings();
247 #endif
248 
249  var index = 0,
250  count = [URLStrings count];
251 
252  for (; index < count; ++index)
253  needsUntitled = ![self _openURL:[CPURL URLWithString:URLStrings[index]]] && needsUntitled;
254 
255  if (needsUntitled && [_delegate respondsToSelector:@selector(applicationShouldOpenUntitledFile:)])
256  needsUntitled = [_delegate applicationShouldOpenUntitledFile:self];
257 
258  if (needsUntitled)
259  [_documentController newDocument:self];
260 
261  [_documentController _updateRecentDocumentsMenu];
262 
263  [defaultCenter
264  postNotificationName:CPApplicationDidFinishLaunchingNotification
265  object:self];
266 
267  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
268 
269  _finishedLaunching = YES;
270 }
271 
272 - (void)terminate:(id)aSender
273 {
275  postNotificationName:CPApplicationWillTerminateNotification
276  object:self];
277 
278  if (![CPPlatform isBrowser])
279  {
280  [[CPDocumentController sharedDocumentController] closeAllDocumentsWithDelegate:self
281  didCloseAllSelector:@selector(_documentController:didCloseAll:context:)
282  contextInfo:nil];
283  }
284  else
285  {
286  [[[self keyWindow] platformWindow] _propagateCurrentDOMEvent:YES];
287  }
288 }
289 
296 - (void)setApplicationIconImage:(CPImage)anImage
297 {
298  _applicationIconImage = anImage;
299 }
300 
305 - (CPImage)applicationIconImage
306 {
307  if (_applicationIconImage)
308  return _applicationIconImage;
309 
310  var imagePath = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPApplicationIcon"];
311  if (imagePath)
312  _applicationIconImage = [[CPImage alloc] initWithContentsOfFile:imagePath];
313 
314  return _applicationIconImage;
315 }
316 
320 - (void)orderFrontStandardAboutPanel:(id)sender
321 {
323 }
324 
349 - (void)orderFrontStandardAboutPanelWithOptions:(CPDictionary)options
350 {
351  if (!_aboutPanel)
352  {
353  var mainInfo = [[CPBundle mainBundle] infoDictionary],
354  applicationTitle = [options objectForKey:"ApplicationName"] || [mainInfo objectForKey:@"CPBundleName"],
355  applicationIcon = [options objectForKey:@"ApplicationIcon"] || [self applicationIconImage],
356  version = [options objectForKey:@"Version"] || [mainInfo objectForKey:@"CPBundleVersion"],
357  applicationVersion = [options objectForKey:@"ApplicationVersion"] || [mainInfo objectForKey:@"CPBundleShortVersionString"],
358  copyright = [options objectForKey:@"Copyright"] || [mainInfo objectForKey:@"CPHumanReadableCopyright"];
359 
360  var windowWidth = 275,
361  windowHeight = 223,
362  imgWidth = 100,
363  imgHeight = 100,
364  interField = 8,
365  aboutPanel = [[CPWindow alloc] initWithContentRect:CGRectMake(0, 0, windowWidth, windowHeight) styleMask:CPClosableWindowMask],
366  imageView = [[CPImageView alloc] initWithFrame:CGRectMake((windowWidth / 2) - (imgWidth / 2), interField, imgWidth, imgHeight)],
367  applicationLabel = [[CPTextField alloc] initWithFrame:CGRectMake(17, imgHeight + 16, windowWidth - 34, 24)],
368  versionLabel = [[CPTextField alloc] initWithFrame:CGRectMake(17, imgHeight + 48, windowWidth - 34, 16)],
369  copyrightLabel = [[CPTextField alloc] initWithFrame:CGRectMake(17, imgHeight + 72, windowWidth - 34, 32)],
370  contentView = [aboutPanel contentView];
371 
372  [applicationLabel setFont:[CPFont boldSystemFontOfSize:[CPFont systemFontSize] + 2]];
373  [applicationLabel setAlignment:CPCenterTextAlignment];
374  [versionLabel setFont:[CPFont systemFontOfSize:[CPFont systemFontSize] - 1]];
375  [versionLabel setAlignment:CPCenterTextAlignment];
376  [copyrightLabel setFont:[CPFont systemFontOfSize:[CPFont systemFontSize] - 1]];
377  [copyrightLabel setAlignment:CPCenterTextAlignment];
378  [copyrightLabel setLineBreakMode:CPLineBreakByWordWrapping];
379 
380  [contentView addSubview:imageView];
381  [contentView addSubview:applicationLabel];
382  [contentView addSubview:versionLabel];
383  [contentView addSubview:copyrightLabel];
384 
385  var standardPath = [[CPBundle bundleForClass:[self class]] pathForResource:@"standardApplicationIcon.png"];
386 
387  [imageView setImage:applicationIcon || [[CPImage alloc] initWithContentsOfFile:standardPath
388  size:CGSizeMake(256, 256)]];
389 
390  [applicationLabel setStringValue:applicationTitle || ""];
391 
392  if (applicationVersion && version)
393  [versionLabel setStringValue:@"Version " + applicationVersion + " (" + version + ")"];
394  else if (applicationVersion || version)
395  [versionLabel setStringValue:@"Version " + (applicationVersion || version)];
396  else
397  [versionLabel setStringValue:@""];
398 
399  [copyrightLabel setStringValue:copyright || @""];
400  [aboutPanel center];
401 
402  _aboutPanel = aboutPanel;
403  }
404 
405  [_aboutPanel orderFront:self];
406 }
407 
408 
409 - (void)_documentController:(CPDocumentController)docController didCloseAll:(BOOL)didCloseAll context:(Object)info
410 {
411  // callback method for terminate:
412  if (didCloseAll)
413  {
414  if ([_delegate respondsToSelector:@selector(applicationShouldTerminate:)])
415  [self replyToApplicationShouldTerminate:[_delegate applicationShouldTerminate:self]];
416  else
417  [self replyToApplicationShouldTerminate:YES];
418  }
419 }
420 
421 - (void)replyToApplicationShouldTerminate:(BOOL)terminate
422 {
423  if (terminate == CPTerminateNow)
424  {
425  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationWillTerminateNotification object:self];
427  }
428 }
429 
430 - (void)activateIgnoringOtherApps:(BOOL)shouldIgnoreOtherApps
431 {
432  [self _willBecomeActive];
433 
434  [CPPlatform activateIgnoringOtherApps:shouldIgnoreOtherApps];
435  _isActive = YES;
436 
437  [self _didBecomeActive];
438 }
439 
440 - (void)deactivate
441 {
442  [self _willResignActive];
443 
445  _isActive = NO;
446 
447  [self _didResignActive];
448 }
449 
450 - (void)isActive
451 {
452  return _isActive;
453 }
454 
455 - (void)hideOtherApplications:(id)aSender
456 {
458 }
459 
464 - (void)run
465 {
466  [self finishLaunching];
467 }
468 
469 // Managing the Event Loop
474 - (void)runModalForWindow:(CPWindow)aWindow
475 {
476  [self runModalSession:[self beginModalSessionForWindow:aWindow]];
477 }
478 
484 - (void)stopModalWithCode:(int)aCode
485 {
486  if (!_currentSession)
487  {
488  return;
489  // raise exception;
490  }
491 
492  _currentSession._state = aCode;
493  _currentSession = _currentSession._previous;
494 
495 // if (aCode == CPRunAbortedResponse)
496  [self _removeRunModalLoop];
497 }
498 
499 /* @ignore */
500 - (void)_removeRunModalLoop
501 {
502  var count = _eventListeners.length;
503 
504  while (count--)
505  if (_eventListeners[count]._callback === _CPRunModalLoop)
506  {
507  _eventListeners.splice(count, 1);
508  if (count <= _eventListenerInsertionIndex)
509  _eventListenerInsertionIndex--;
510 
511  return;
512  }
513 }
514 
518 - (void)stopModal
519 {
520  [self stopModalWithCode:CPRunStoppedResponse]
521 }
522 
526 - (void)abortModal
527 {
528  [self stopModalWithCode:CPRunAbortedResponse];
529 }
530 
535 - (CPModalSession)beginModalSessionForWindow:(CPWindow)aWindow
536 {
537  return _CPModalSessionMake(aWindow, 0);
538 }
539 
544 - (void)runModalSession:(CPModalSession)aModalSession
545 {
546  aModalSession._previous = _currentSession;
547  _currentSession = aModalSession;
548 
549  var theWindow = aModalSession._window;
550 
551  [theWindow center];
552  [theWindow makeKeyWindow];
553  [theWindow orderFront:self];
554 
555 // [theWindow._bridge _obscureWindowsBelowModalWindow];
556 
557  [CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:YES];
558 }
559 
564 - (CPWindow)modalWindow
565 {
566  if (!_currentSession)
567  return nil;
568 
569  return _currentSession._window;
570 }
571 
572 /* @ignore */
573 - (BOOL)_handleKeyEquivalent:(CPEvent)anEvent
574 {
575  return [[self keyWindow] performKeyEquivalent:anEvent] ||
576  [[self mainMenu] performKeyEquivalent:anEvent];
577 }
578 
583 - (void)sendEvent:(CPEvent)anEvent
584 {
585  _currentEvent = anEvent;
587 
588  var theWindow = [anEvent window];
589 
590  // Check if this is a candidate for key equivalent...
591  if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
592  // The key equivalent was handled.
593  return;
594 
595  if ([anEvent type] == CPMouseMoved)
596  {
597  if (theWindow !== _lastMouseMoveWindow)
598  [_lastMouseMoveWindow _mouseExitedResizeRect];
599 
600  _lastMouseMoveWindow = theWindow;
601  }
602 
603  /*
604  Event listeners are processed from back to front so that newer event listeners normally take
605  precedence. If during the execution of a callback a new event listener is added, it should
606  be inserted after the current callback but before any higher priority callbacks. This makes
607  repeating event listeners (those that reinsert themselves) stable relative to each other.
608  */
609  for (var i = _eventListeners.length - 1; i >= 0; i--)
610  {
611  var listener = _eventListeners[i];
612 
613  if (listener._mask & (1 << [anEvent type]))
614  {
615  _eventListeners.splice(i, 1);
616  // In case the callback wants to add more listeners.
617  _eventListenerInsertionIndex = i;
618  listener._callback(anEvent);
619 
620  if (listener._dequeue)
621  {
622  // Don't process the event normally and don't send it to any other listener.
623  _eventListenerInsertionIndex = _eventListeners.length;
624  return;
625  }
626  }
627  }
628 
629  _eventListenerInsertionIndex = _eventListeners.length;
630 
631  if (theWindow)
632  [theWindow sendEvent:anEvent];
633 }
634 
639 - (void)doCommandBySelector:(SEL)aSelector
640 {
641  if ([_delegate respondsToSelector:aSelector])
642  [_delegate performSelector:aSelector];
643  else
644  [super doCommandBySelector:aSelector];
645 }
646 
650 - (CPWindow)keyWindow
651 {
652  return _keyWindow;
653 }
654 
658 - (CPWindow)mainWindow
659 {
660  return _mainWindow;
661 }
662 
666 - (CPWindow)windowWithWindowNumber:(int)aWindowNumber
667 {
668  // Never allow _windows[0] to be returned - it's an internal CPNull placeholder.
669  if (!aWindowNumber)
670  return nil;
671 
672  return _windows[aWindowNumber];
673 }
674 
678 - (CPArray)windows
679 {
680  // Return all windows, but not the CPNull placeholder in _windows[0].
681  return [_windows subarrayWithRange:CPMakeRange(1, [_windows count] - 1)];
682 }
683 
687 - (CPArray)orderedWindows
688 {
689 #if PLATFORM(DOM)
690  return CPWindowObjectList();
691 #else
692  return [];
693 #endif
694 }
695 
696 - (void)hide:(id)aSender
697 {
698  [CPPlatform hide:self];
699 }
700 
701 // Accessing the Main Menu
705 - (CPMenu)mainMenu
706 {
707  return [self menu];
708 }
709 
714 - (void)setMainMenu:(CPMenu)aMenu
715 {
716  [self setMenu:aMenu];
717 }
718 
719 - (void)setMenu:(CPMenu)aMenu
720 {
721  if ([aMenu _menuName] === "CPMainMenu")
722  {
723  if ([self menu] === aMenu)
724  return;
725 
726  [super setMenu:aMenu];
727 
728  if ([CPPlatform supportsNativeMainMenu])
729  window.cpSetMainMenu([self menu]);
730  }
731  else
732  [aMenu _setMenuName:@"CPMainMenu"];
733 }
734 
739 - (void)orderFrontColorPanel:(id)aSender
740 {
742 }
743 
744 // Posting Actions
754 - (BOOL)tryToPerform:(SEL)anAction with:(id)anObject
755 {
756  if (!anAction)
757  return NO;
758 
759  if ([super tryToPerform:anAction with:anObject])
760  return YES;
761 
762  if ([_delegate respondsToSelector:anAction])
763  {
764  [_delegate performSelector:anAction withObject:anObject];
765 
766  return YES;
767  }
768 
769  return NO;
770 }
771 
779 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)aSender
780 {
781  var target = [self targetForAction:anAction to:aTarget from:aSender];
782 
783  if (!target)
784  return NO;
785 
786  [target performSelector:anAction withObject:aSender];
787 
788  return YES;
789 }
790 
802 - (id)targetForAction:(SEL)anAction to:(id)aTarget from:(id)aSender
803 {
804  if (!anAction)
805  return nil;
806 
807  if (aTarget)
808  return aTarget;
809 
810  return [self targetForAction:anAction];
811 }
812 
830 - (id)_targetForWindow:(CPWindow)aWindow action:(SEL)anAction
831 {
832  var responder = [aWindow firstResponder],
833  checkWindow = YES;
834 
835  while (responder)
836  {
837  if ([responder respondsToSelector:anAction])
838  return responder;
839 
840  if (responder == aWindow)
841  checkWindow = NO;
842 
843  responder = [responder nextResponder];
844  }
845 
846  if (checkWindow && [aWindow respondsToSelector:anAction])
847  return aWindow;
848 
849  var delegate = [aWindow delegate];
850 
851  if ([delegate respondsToSelector:anAction])
852  return delegate;
853 
854  var windowController = [aWindow windowController];
855 
856  if ([windowController respondsToSelector:anAction])
857  return windowController;
858 
859  var theDocument = [windowController document];
860  if (theDocument !== delegate && [theDocument respondsToSelector:anAction])
861  return theDocument;
862 
863  return nil;
864 }
865 
880 - (id)targetForAction:(SEL)anAction
881 {
882  if (!anAction)
883  return nil;
884 
885  var target = [self _targetForWindow:[self keyWindow] action:anAction];
886 
887  if (target)
888  return target;
889 
890  target = [self _targetForWindow:[self mainWindow] action:anAction];
891 
892  if (target)
893  return target;
894 
895  if ([self respondsToSelector:anAction])
896  return self;
897 
898  if ([_delegate respondsToSelector:anAction])
899  return _delegate;
900 
901  if ([_documentController respondsToSelector:anAction])
902  return _documentController;
903 
904  return nil;
905 }
906 
922 - (void)setCallback:(Function)aCallback forNextEventMatchingMask:(unsigned int)aMask untilDate:(CPDate)anExpiration inMode:(CPString)aMode dequeue:(BOOL)shouldDequeue
923 {
924  _eventListeners.splice(_eventListenerInsertionIndex++, 0, _CPEventListenerMake(aMask, aCallback, shouldDequeue));
925 }
926 
944 - (void)setTarget:(id)aTarget selector:(SEL)aSelector forNextEventMatchingMask:(unsigned int)aMask untilDate:(CPDate)anExpiration inMode:(CPString)aMode dequeue:(BOOL)shouldDequeue
945 {
946  _eventListeners.splice(_eventListenerInsertionIndex++, 0, _CPEventListenerMake(aMask, function (anEvent) { objj_msgSend(aTarget, aSelector, anEvent); }, shouldDequeue));
947 }
948 
952 - (CPEvent)currentEvent
953 {
954  return _currentEvent;
955 }
956 
957 // Managing Sheets
958 
967 - (void)beginSheet:(CPWindow)aSheet modalForWindow:(CPWindow)aWindow modalDelegate:(id)aModalDelegate didEndSelector:(SEL)didEndSelector contextInfo:(id)contextInfo
968 {
969  if ([aWindow isSheet])
970  {
971  [CPException raise:CPInternalInconsistencyException reason:@"The target window of beginSheet: cannot be a sheet"];
972  return;
973  }
974 
975  if (![aWindow attachedSheet])
976  [aSheet._windowView _enableSheet:YES inWindow:aWindow];
977 
978  [aWindow _attachSheet:aSheet modalDelegate:aModalDelegate didEndSelector:didEndSelector contextInfo:contextInfo];
979 }
980 
994 - (void)endSheet:(CPWindow)sheet returnCode:(int)returnCode
995 {
996  var count = [_windows count];
997 
998  while (--count >= 0)
999  {
1000  var aWindow = [_windows objectAtIndex:count],
1001  context = aWindow._sheetContext;
1002 
1003  if (context && context["sheet"] === sheet)
1004  {
1005  context["returnCode"] = returnCode;
1006  [aWindow _endSheet];
1007  return;
1008  }
1009  }
1010 }
1011 
1016 - (void)endSheet:(CPWindow)sheet
1017 {
1018  // FIX ME: this is wrong: by Cocoa this should be: CPRunStoppedResponse.
1019  [self endSheet:sheet returnCode:0];
1020 }
1021 
1037 - (CPArray)arguments
1038 {
1039  // FIXME This should probably not access the window object #if !PLATFORM(DOM), but the unit tests rely on it.
1040  if (window && window.location && _fullArgsString !== window.location.hash)
1041  [self _reloadArguments];
1042 
1043  return _args;
1044 }
1045 
1062 - (void)setArguments:(CPArray)args
1063 {
1064  if (!args || args.length == 0)
1065  {
1066  _args = [];
1067  // Don't use if PLATFORM(DOM) here - the unit test fakes window.location so we should play along.
1068  if (window && window.location)
1069  window.location.hash = @"#";
1070  return;
1071  }
1072 
1073  if (![args isKindOfClass:CPArray])
1074  args = [CPArray arrayWithObject:args];
1075 
1076  _args = args;
1077 
1078  var toEncode = [_args copy];
1079  for (var i = 0, count = toEncode.length; i < count; i++)
1080  toEncode[i] = encodeURIComponent(toEncode[i]);
1081 
1082  var hash = [toEncode componentsJoinedByString:@"/"];
1083 
1084  // Don't use if PLATFORM(DOM) here - the unit test fakes window.location so we should play along.
1085  if (window && window.location)
1086  window.location.hash = @"#" + hash;
1087 }
1088 
1089 - (void)_reloadArguments
1090 {
1091  // FIXME This should probably not access the window object #if !PLATFORM(DOM), but the unit tests rely on it.
1092  _fullArgsString = (window && window.location) ? window.location.hash : "";
1093 
1094  if (_fullArgsString.length)
1095  {
1096  var args = _fullArgsString.substring(1).split("/");
1097 
1098  for (var i = 0, count = args.length; i < count; i++)
1099  args[i] = decodeURIComponent(args[i]);
1100 
1101  _args = args;
1102  }
1103  else
1104  _args = [];
1105 }
1106 
1124 - (CPDictionary)namedArguments
1125 {
1126  return _namedArgs;
1127 }
1128 
1129 - (BOOL)_openURL:(CPURL)aURL
1130 {
1131  if (_delegate && [_delegate respondsToSelector:@selector(application:openFile:)])
1132  {
1133  CPLog.warn("application:openFile: is deprecated, use application:openURL: instead.");
1134  return [_delegate application:self openFile:[aURL absoluteString]];
1135  }
1136 
1137  if (_delegate && [_delegate respondsToSelector:@selector(application:openURL:)])
1138  return [_delegate application:self openURL:aURL];
1139 
1140  return !![_documentController openDocumentWithContentsOfURL:aURL display:YES error:NULL];
1141 }
1142 
1143 - (void)_willBecomeActive
1144 {
1145  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationWillBecomeActiveNotification
1146  object:self
1147  userInfo:nil];
1148 }
1149 
1150 - (void)_didBecomeActive
1151 {
1152  if (![self keyWindow] && _previousKeyWindow &&
1153  [[self windows] indexOfObjectIdenticalTo:_previousKeyWindow] !== CPNotFound)
1154  [_previousKeyWindow makeKeyWindow];
1155 
1156  if (![self mainWindow] && _previousMainWindow &&
1157  [[self windows] indexOfObjectIdenticalTo:_previousMainWindow] !== CPNotFound)
1158  [_previousMainWindow makeMainWindow];
1159 
1160  if ([self keyWindow])
1161  [[self keyWindow] orderFront:self];
1162  else if ([self mainWindow])
1163  [[self mainWindow] makeKeyAndOrderFront:self];
1164  else if ([self mainMenu])
1165  [[self mainMenu]._menuWindow makeKeyWindow]; //FIXME this may not actually work
1166 
1167  _previousKeyWindow = nil;
1168  _previousMainWindow = nil;
1169 
1170  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationDidBecomeActiveNotification
1171  object:self
1172  userInfo:nil];
1173 }
1174 
1175 - (void)_willResignActive
1176 {
1177  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationWillResignActiveNotification
1178  object:self
1179  userInfo:nil];
1180 }
1181 
1182 - (void)_didResignActive
1183 {
1184  if (self._activeMenu)
1185  [self._activeMenu cancelTracking];
1186 
1187  if ([self keyWindow])
1188  {
1189  _previousKeyWindow = [self keyWindow];
1190  [_previousKeyWindow resignKeyWindow];
1191  }
1192 
1193  if ([self mainWindow])
1194  {
1195  _previousMainWindow = [self mainWindow];
1196  [_previousMainWindow resignMainWindow];
1197  }
1198 
1199  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationDidResignActiveNotification
1200  object:self
1201  userInfo:nil];
1202 }
1203 
1204 + (CPString)defaultThemeName
1205 {
1206  return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo2");
1207 }
1208 
1209 @end
1210 
1211 var _CPModalSessionMake = function(aWindow, aStopCode)
1212 {
1213  return { _window:aWindow, _state:CPRunContinuesResponse , _previous:nil };
1214 };
1215 
1216 var _CPEventListenerMake = function(anEventMask, aCallback, shouldDequeue)
1217 {
1218  return { _mask:anEventMask, _callback:aCallback, _dequeue:shouldDequeue };
1219 };
1220 
1221 // Make this a global for use in CPPlatformWindow+DOM.j.
1222 _CPRunModalLoop = function(anEvent)
1223 {
1224  [CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:YES];
1225 
1226  var theWindow = [anEvent window],
1227  modalSession = CPApp._currentSession;
1228 
1229  /*
1230  The special case for popovers here is not clear. In Cocoa the popover window does not respond YES to worksWhenModal, yet it works when there is a modal window. Maybe it starts its own modal session, but interaction with the original modal window seems to continue working as well. Regardless of correctness, this solution beats popovers not working at all from sheets.
1231  */
1232  if (theWindow == modalSession._window ||
1233  [theWindow worksWhenModal] ||
1234  [theWindow attachedSheet] == modalSession._window || // -dw- allow modal parent of sheet to be repositioned
1235  ([theWindow isKindOfClass:_CPPopoverWindow] && [[theWindow targetView] window] === modalSession._window))
1236  {
1237  [theWindow sendEvent:anEvent];
1238  }
1239 };
1240 
1247 function CPApplicationMain(args, namedArgs)
1248 {
1249 
1250 #if PLATFORM(DOM)
1251  // hook to allow recorder, etc to manipulate things before starting AppKit
1252  if (window.parent !== window && typeof window.parent._childAppIsStarting === "function")
1253  {
1254  try
1255  {
1256  window.parent._childAppIsStarting(window);
1257  }
1258  catch(err)
1259  {
1260  // This could happen if we're in an iframe without access to the parent frame.
1261  CPLog.warn("Failed to call parent frame's _childAppIsStarting().");
1262  }
1263  }
1264 #endif
1265 
1266  var mainBundle = [CPBundle mainBundle],
1267  principalClass = [mainBundle principalClass];
1268 
1269  if (!principalClass)
1270  principalClass = [CPApplication class];
1271 
1272  [principalClass sharedApplication];
1273 
1274  if ([args containsObject:"debug"])
1275  CPLogRegister(CPLogPopup);
1276 
1277  CPApp._args = args;
1278  CPApp._namedArgs = namedArgs;
1279 
1280  [_CPAppBootstrapper performActions];
1281 }
1282 
1283 var _CPAppBootstrapperActions = nil;
1284 @implementation _CPAppBootstrapper : CPObject
1285 {
1286  id __doxygen__;
1287 }
1288 
1289 + (CPArray)actions
1290 {
1291  return [@selector(bootstrapPlatform), @selector(loadDefaultTheme), @selector(loadMainCibFile)];
1292 }
1293 
1294 + (void)performActions
1295 {
1296  if (!_CPAppBootstrapperActions)
1297  _CPAppBootstrapperActions = [self actions];
1298 
1299  while (_CPAppBootstrapperActions.length)
1300  {
1301  var action = _CPAppBootstrapperActions.shift();
1302 
1303  if (objj_msgSend(self, action))
1304  return;
1305  }
1306 
1307  [CPApp run];
1308 }
1309 
1310 + (BOOL)bootstrapPlatform
1311 {
1312  return [CPPlatform bootstrap];
1313 }
1314 
1315 + (BOOL)loadDefaultTheme
1316 {
1317  var defaultThemeName = [CPApplication defaultThemeName],
1318  themeURL = nil;
1319 
1320  if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2")
1321  themeURL = [[CPBundle bundleForClass:[CPApplication class]] pathForResource:defaultThemeName + @".blend"];
1322  else
1323  themeURL = [[CPBundle mainBundle] pathForResource:defaultThemeName + @".blend"];
1324 
1325  var blend = [[CPThemeBlend alloc] initWithContentsOfURL:themeURL];
1326  [blend loadWithDelegate:self];
1327 
1328  return YES;
1329 }
1330 
1331 + (void)blendDidFinishLoading:(CPThemeBlend)aThemeBlend
1332 {
1335 
1336  [self performActions];
1337 }
1338 
1339 + (BOOL)loadMainCibFile
1340 {
1341  var mainBundle = [CPBundle mainBundle],
1342  mainCibFile = [mainBundle objectForInfoDictionaryKey:CPMainCibFile] || [mainBundle objectForInfoDictionaryKey:CPMainCibFileHumanFriendly];
1343 
1344  if (mainCibFile)
1345  {
1346  [mainBundle loadCibFile:mainCibFile
1347  externalNameTable:@{ CPCibOwner: CPApp }
1348  loadDelegate:self];
1349 
1350  return YES;
1351  }
1352  else
1353  [self loadCiblessBrowserMainMenu];
1354 
1355  return NO;
1356 }
1357 
1358 + (void)loadCiblessBrowserMainMenu
1359 {
1360  var mainMenu = [[CPMenu alloc] initWithTitle:@"MainMenu"];
1361 
1362  // FIXME: We should implement autoenabling.
1363  [mainMenu setAutoenablesItems:NO];
1364 
1365  var newMenuItem = [[CPMenuItem alloc] initWithTitle:@"New" action:@selector(newDocument:) keyEquivalent:@"n"];
1366 
1367  [newMenuItem setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-new" forClass:_CPMenuView]];
1368  [newMenuItem setAlternateImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-new" inState:CPThemeStateHighlighted forClass:_CPMenuView]];
1369 
1370  [mainMenu addItem:newMenuItem];
1371 
1372  var openMenuItem = [[CPMenuItem alloc] initWithTitle:@"Open" action:@selector(openDocument:) keyEquivalent:@"o"];
1373 
1374  [openMenuItem setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-open" forClass:_CPMenuView]];
1375  [openMenuItem setAlternateImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-open" inState:CPThemeStateHighlighted forClass:_CPMenuView]];
1376 
1377  [mainMenu addItem:openMenuItem];
1378 
1379  var saveMenu = [[CPMenu alloc] initWithTitle:@"Save"],
1380  saveMenuItem = [[CPMenuItem alloc] initWithTitle:@"Save" action:@selector(saveDocument:) keyEquivalent:nil];
1381 
1382  [saveMenuItem setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-save" forClass:_CPMenuView]];
1383  [saveMenuItem setAlternateImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-save" inState:CPThemeStateHighlighted forClass:_CPMenuView]];
1384 
1385  [saveMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Save" action:@selector(saveDocument:) keyEquivalent:@"s"]];
1386  [saveMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Save As" action:@selector(saveDocumentAs:) keyEquivalent:nil]];
1387 
1388  [saveMenuItem setSubmenu:saveMenu];
1389 
1390  [mainMenu addItem:saveMenuItem];
1391 
1392  var editMenuItem = [[CPMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:nil],
1393  editMenu = [[CPMenu alloc] initWithTitle:@"Edit"],
1394 
1395  undoMenuItem = [[CPMenuItem alloc] initWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:CPUndoKeyEquivalent],
1396  redoMenuItem = [[CPMenuItem alloc] initWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:CPRedoKeyEquivalent];
1397 
1398  [undoMenuItem setKeyEquivalentModifierMask:CPUndoKeyEquivalentModifierMask];
1399  [redoMenuItem setKeyEquivalentModifierMask:CPRedoKeyEquivalentModifierMask];
1400 
1401  [editMenu addItem:undoMenuItem];
1402  [editMenu addItem:redoMenuItem];
1403 
1404  [editMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"]];
1405  [editMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"]];
1406  [editMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"]];
1407 
1408  [editMenuItem setSubmenu:editMenu];
1409  [editMenuItem setHidden:YES];
1410 
1411  [mainMenu addItem:editMenuItem];
1412 
1413  [mainMenu addItem:[CPMenuItem separatorItem]];
1414 
1415  [CPApp setMainMenu:mainMenu];
1416 }
1417 
1418 + (void)cibDidFinishLoading:(CPCib)aCib
1419 {
1420  [self performActions];
1421 }
1422 
1423 + (void)cibDidFailToLoad:(CPCib)aCib
1424 {
1425  throw new Error("Could not load main cib file. Did you forget to nib2cib it?");
1426 }
1427 
1428 + (void)reset
1429 {
1430  _CPAppBootstrapperActions = nil;
1431 }
1432 
1433 @end
1434 
1435 
1437 
1441 + (unsigned)modifierFlags
1442 {
1443  return CPEventModifierFlags;
1444 }
1445 
1446 @end
1447 
1449 
1453 - (CPThemeBlend)themeBlend
1454 {
1455  return _themeBlend;
1456 }
1457 
1461 - (void)setThemeBlend:(CPThemeBlend)aValue
1462 {
1463  _themeBlend = aValue;
1464 }
1465 
1466 @end