API  0.9.7
 All Classes Files Functions Variables 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 var CPMainCibFile = @"CPMainCibFile",
26  CPMainCibFileHumanFriendly = @"Main cib file base name",
28 
54 @implementation CPApplication : CPResponder
55 {
56  CPArray _eventListeners;
57  int _eventListenerInsertionIndex;
58 
59  CPEvent _currentEvent;
60  CPWindow _lastMouseMoveWindow;
61 
62  CPArray _windows;
63  CPWindow _keyWindow;
64  CPWindow _mainWindow;
65  CPWindow _previousKeyWindow;
66  CPWindow _previousMainWindow;
67 
68  CPDocumentController _documentController;
69 
70  CPModalSession _currentSession;
71 
72  //
73  id _delegate;
74  BOOL _finishedLaunching;
75  BOOL _isActive;
76 
77  CPDictionary _namedArgs;
78  CPArray _args;
79  CPString _fullArgsString;
80 
81  CPImage _applicationIconImage;
82 
83  CPPanel _aboutPanel;
84 
85  CPThemeBlend _themeBlend;
86 }
87 
93 + (CPApplication)sharedApplication
94 {
95  if (!CPApp)
96  CPApp = [[CPApplication alloc] init];
97 
98  return CPApp;
99 }
100 
106 - (id)init
107 {
108  self = [super init];
109 
110  CPApp = self;
111 
112  if (self)
113  {
114  _eventListeners = [];
115  _eventListenerInsertionIndex = 0;
116 
117  _windows = [[CPNull null]];
118  }
119 
120  return self;
121 }
122 
123 // Configuring Applications
124 
131 - (void)setDelegate:(id)aDelegate
132 {
133  if (_delegate == aDelegate)
134  return;
135 
136  var defaultCenter = [CPNotificationCenter defaultCenter],
137  delegateNotifications =
138  [
139  CPApplicationWillFinishLaunchingNotification, @selector(applicationWillFinishLaunching:),
140  CPApplicationDidFinishLaunchingNotification, @selector(applicationDidFinishLaunching:),
141  CPApplicationWillBecomeActiveNotification, @selector(applicationWillBecomeActive:),
142  CPApplicationDidBecomeActiveNotification, @selector(applicationDidBecomeActive:),
143  CPApplicationWillResignActiveNotification, @selector(applicationWillResignActive:),
144  CPApplicationDidResignActiveNotification, @selector(applicationDidResignActive:),
145  CPApplicationWillTerminateNotification, @selector(applicationWillTerminate:),
146  CPApplicationDidChangeScreenParametersNotification, @selector(applicationDidChangeScreenParameters:)
147  ],
148  count = [delegateNotifications count];
149 
150  if (_delegate)
151  {
152  var index = 0;
153 
154  for (; index < count; index += 2)
155  {
156  var notificationName = delegateNotifications[index],
157  selector = delegateNotifications[index + 1];
158 
159  if ([_delegate respondsToSelector:selector])
160  [defaultCenter removeObserver:_delegate name:notificationName object:self];
161  }
162  }
163 
164  _delegate = aDelegate;
165 
166  var index = 0;
167 
168  for (; index < count; index += 2)
169  {
170  var notificationName = delegateNotifications[index],
171  selector = delegateNotifications[index + 1];
172 
173  if ([_delegate respondsToSelector:selector])
174  [defaultCenter addObserver:_delegate selector:selector name:notificationName object:self];
175  }
176 }
177 
181 - (id)delegate
182 {
183  return _delegate;
184 }
185 
192 - (void)finishLaunching
193 {
194  // At this point we clear the window.status to eliminate Safari's "Cancelled" error message
195  // The message shouldn't be displayed, because only an XHR is cancelled, but it is a usability issue.
196  // We do it here so that applications can change it in willFinish or didFinishLaunching
197 #if PLATFORM(DOM)
198  window.status = " ";
199 #endif
200 
201  // We also want to set the default cursor on the body, so that buttons and things don't have an iBeam
203 
204  var bundle = [CPBundle mainBundle],
205  delegateClassName = [bundle objectForInfoDictionaryKey:@"CPApplicationDelegateClass"];
206 
207  if (delegateClassName)
208  {
209  var delegateClass = objj_getClass(delegateClassName);
210 
211  if (delegateClass)
212  [self setDelegate:[[delegateClass alloc] init]];
213  }
214 
215  var defaultCenter = [CPNotificationCenter defaultCenter];
216 
217  [defaultCenter
218  postNotificationName:CPApplicationWillFinishLaunchingNotification
219  object:self];
220 
221  var types = [bundle objectForInfoDictionaryKey:@"CPBundleDocumentTypes"];
222 
223  if ([types count] > 0)
224  _documentController = [CPDocumentController sharedDocumentController];
225 
226  var needsUntitled = !!_documentController,
227  URLStrings = nil;
228 
229 #if PLATFORM(DOM)
230  URLStrings = window.cpOpeningURLStrings && window.cpOpeningURLStrings();
231 #endif
232 
233  var index = 0,
234  count = [URLStrings count];
235 
236  for (; index < count; ++index)
237  needsUntitled = ![self _openURL:[CPURL URLWithString:URLStrings[index]]] && needsUntitled;
238 
239  if (needsUntitled && [_delegate respondsToSelector:@selector(applicationShouldOpenUntitledFile:)])
240  needsUntitled = [_delegate applicationShouldOpenUntitledFile:self];
241 
242  if (needsUntitled)
243  [_documentController newDocument:self];
244 
245  [_documentController _updateRecentDocumentsMenu];
246 
247  [defaultCenter
248  postNotificationName:CPApplicationDidFinishLaunchingNotification
249  object:self];
250 
251  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
252 
253  _finishedLaunching = YES;
254 }
255 
256 - (void)terminate:(id)aSender
257 {
259  postNotificationName:CPApplicationWillTerminateNotification
260  object:self];
261 
262  if (![CPPlatform isBrowser])
263  {
264  [[CPDocumentController sharedDocumentController] closeAllDocumentsWithDelegate:self
265  didCloseAllSelector:@selector(_documentController:didCloseAll:context:)
266  contextInfo:nil];
267  }
268  else
269  {
270  [[[self keyWindow] platformWindow] _propagateCurrentDOMEvent:YES];
271  }
272 }
273 
280 - (void)setApplicationIconImage:(CPImage)anImage
281 {
282  _applicationIconImage = anImage;
283 }
284 
289 - (CPImage)applicationIconImage
290 {
291  if (_applicationIconImage)
292  return _applicationIconImage;
293 
294  var imagePath = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPApplicationIcon"];
295  if (imagePath)
296  _applicationIconImage = [[CPImage alloc] initWithContentsOfFile:imagePath];
297 
298  return _applicationIconImage;
299 }
300 
304 - (void)orderFrontStandardAboutPanel:(id)sender
305 {
307 }
308 
333 - (void)orderFrontStandardAboutPanelWithOptions:(CPDictionary)options
334 {
335  if (!_aboutPanel)
336  {
337  var mainInfo = [[CPBundle mainBundle] infoDictionary],
338  applicationTitle = [options objectForKey:"ApplicationName"] || [mainInfo objectForKey:@"CPBundleName"],
339  applicationIcon = [options objectForKey:@"ApplicationIcon"] || [self applicationIconImage],
340  version = [options objectForKey:@"Version"] || [mainInfo objectForKey:@"CPBundleVersion"],
341  applicationVersion = [options objectForKey:@"ApplicationVersion"] || [mainInfo objectForKey:@"CPBundleShortVersionString"],
342  copyright = [options objectForKey:@"Copyright"] || [mainInfo objectForKey:@"CPHumanReadableCopyright"];
343 
344  var windowWidth = 275,
345  windowHeight = 223,
346  imgWidth = 100,
347  imgHeight = 100,
348  interField = 8,
349  aboutPanel = [[CPWindow alloc] initWithContentRect:CGRectMake(0, 0, windowWidth, windowHeight) styleMask:CPClosableWindowMask],
350  imageView = [[CPImageView alloc] initWithFrame:CGRectMake((windowWidth / 2) - (imgWidth / 2), interField, imgWidth, imgHeight)],
351  applicationLabel = [[CPTextField alloc] initWithFrame:CGRectMake(17, imgHeight + 16, windowWidth - 34, 24)],
352  versionLabel = [[CPTextField alloc] initWithFrame:CGRectMake(17, imgHeight + 48, windowWidth - 34, 16)],
353  copyrightLabel = [[CPTextField alloc] initWithFrame:CGRectMake(17, imgHeight + 72, windowWidth - 34, 32)],
354  contentView = [aboutPanel contentView];
355 
356  [applicationLabel setFont:[CPFont boldSystemFontOfSize:[CPFont systemFontSize] + 2]];
357  [applicationLabel setAlignment:CPCenterTextAlignment];
358  [versionLabel setFont:[CPFont systemFontOfSize:[CPFont systemFontSize] - 1]];
359  [versionLabel setAlignment:CPCenterTextAlignment];
360  [copyrightLabel setFont:[CPFont systemFontOfSize:[CPFont systemFontSize] - 1]];
361  [copyrightLabel setAlignment:CPCenterTextAlignment];
362  [copyrightLabel setLineBreakMode:CPLineBreakByWordWrapping];
363 
364  [contentView addSubview:imageView];
365  [contentView addSubview:applicationLabel];
366  [contentView addSubview:versionLabel];
367  [contentView addSubview:copyrightLabel];
368 
369  var standardPath = [[CPBundle bundleForClass:[self class]] pathForResource:@"standardApplicationIcon.png"];
370 
371  [imageView setImage:applicationIcon || [[CPImage alloc] initWithContentsOfFile:standardPath
372  size:CGSizeMake(256, 256)]];
373 
374  [applicationLabel setStringValue:applicationTitle || ""];
375 
376  if (applicationVersion && version)
377  [versionLabel setStringValue:@"Version " + applicationVersion + " (" + version + ")"];
378  else if (applicationVersion || version)
379  [versionLabel setStringValue:@"Version " + (applicationVersion || version)];
380  else
381  [versionLabel setStringValue:@""];
382 
383  [copyrightLabel setStringValue:copyright || @""];
384  [aboutPanel center];
385 
386  _aboutPanel = aboutPanel;
387  }
388 
389  [_aboutPanel orderFront:self];
390 }
391 
392 
393 - (void)_documentController:(CPDocumentController)docController didCloseAll:(BOOL)didCloseAll context:(Object)info
394 {
395  // callback method for terminate:
396  if (didCloseAll)
397  {
398  if ([_delegate respondsToSelector:@selector(applicationShouldTerminate:)])
399  [self replyToApplicationShouldTerminate:[_delegate applicationShouldTerminate:self]];
400  else
401  [self replyToApplicationShouldTerminate:YES];
402  }
403 }
404 
405 - (void)replyToApplicationShouldTerminate:(BOOL)terminate
406 {
407  if (terminate == CPTerminateNow)
408  {
409  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationWillTerminateNotification object:self];
411  }
412 }
413 
414 - (void)activateIgnoringOtherApps:(BOOL)shouldIgnoreOtherApps
415 {
416  [self _willBecomeActive];
417 
418  [CPPlatform activateIgnoringOtherApps:shouldIgnoreOtherApps];
419  _isActive = YES;
420 
421  [self _willResignActive];
422 }
423 
424 - (void)deactivate
425 {
426  [self _willResignActive];
427 
429  _isActive = NO;
430 
431  [self _didResignActive];
432 }
433 
434 - (void)isActive
435 {
436  return _isActive;
437 }
438 
439 - (void)hideOtherApplications:(id)aSender
440 {
442 }
443 
448 - (void)run
449 {
450  [self finishLaunching];
451 }
452 
453 // Managing the Event Loop
458 - (void)runModalForWindow:(CPWindow)aWindow
459 {
460  [self runModalSession:[self beginModalSessionForWindow:aWindow]];
461 }
462 
468 - (void)stopModalWithCode:(int)aCode
469 {
470  if (!_currentSession)
471  {
472  return;
473  // raise exception;
474  }
475 
476  _currentSession._state = aCode;
477  _currentSession = _currentSession._previous;
478 
479 // if (aCode == CPRunAbortedResponse)
480  [self _removeRunModalLoop];
481 }
482 
483 /* @ignore */
484 - (void)_removeRunModalLoop
485 {
486  var count = _eventListeners.length;
487 
488  while (count--)
489  if (_eventListeners[count]._callback === _CPRunModalLoop)
490  {
491  _eventListeners.splice(count, 1);
492  if (count <= _eventListenerInsertionIndex)
493  _eventListenerInsertionIndex--;
494 
495  return;
496  }
497 }
498 
502 - (void)stopModal
503 {
504  [self stopModalWithCode:CPRunStoppedResponse]
505 }
506 
510 - (void)abortModal
511 {
512  [self stopModalWithCode:CPRunAbortedResponse];
513 }
514 
519 - (CPModalSession)beginModalSessionForWindow:(CPWindow)aWindow
520 {
521  return _CPModalSessionMake(aWindow, 0);
522 }
523 
528 - (void)runModalSession:(CPModalSession)aModalSession
529 {
530  aModalSession._previous = _currentSession;
531  _currentSession = aModalSession;
532 
533  var theWindow = aModalSession._window;
534 
535  [theWindow center];
536  [theWindow makeKeyWindow];
537  [theWindow orderFront:self];
538 
539 // [theWindow._bridge _obscureWindowsBelowModalWindow];
540 
541  [CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:YES];
542 }
543 
548 - (CPWindow)modalWindow
549 {
550  if (!_currentSession)
551  return nil;
552 
553  return _currentSession._window;
554 }
555 
556 /* @ignore */
557 - (BOOL)_handleKeyEquivalent:(CPEvent)anEvent
558 {
559  return [[self keyWindow] performKeyEquivalent:anEvent] ||
560  [[self mainMenu] performKeyEquivalent:anEvent];
561 }
562 
567 - (void)sendEvent:(CPEvent)anEvent
568 {
569  _currentEvent = anEvent;
570  CPEventModifierFlags = [anEvent modifierFlags];
571 
572  var theWindow = [anEvent window];
573 
574  // Check if this is a candidate for key equivalent...
575  if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
576  // The key equivalent was handled.
577  return;
578 
579  if ([anEvent type] == CPMouseMoved)
580  {
581  if (theWindow !== _lastMouseMoveWindow)
582  [_lastMouseMoveWindow _mouseExitedResizeRect];
583 
584  _lastMouseMoveWindow = theWindow;
585  }
586 
587  /*
588  Event listeners are processed from back to front so that newer event listeners normally take
589  precedence. If during the execution of a callback a new event listener is added, it should
590  be inserted after the current callback but before any higher priority callbacks. This makes
591  repeating event listeners (those that reinsert themselves) stable relative to each other.
592  */
593  for (var i = _eventListeners.length - 1; i >= 0; i--)
594  {
595  var listener = _eventListeners[i];
596 
597  if (listener._mask & (1 << [anEvent type]))
598  {
599  _eventListeners.splice(i, 1);
600  // In case the callback wants to add more listeners.
601  _eventListenerInsertionIndex = i;
602  listener._callback(anEvent);
603 
604  if (listener._dequeue)
605  {
606  // Don't process the event normally and don't send it to any other listener.
607  _eventListenerInsertionIndex = _eventListeners.length;
608  return;
609  }
610  }
611  }
612 
613  _eventListenerInsertionIndex = _eventListeners.length;
614 
615  if (theWindow)
616  [theWindow sendEvent:anEvent];
617 }
618 
623 - (void)doCommandBySelector:(SEL)aSelector
624 {
625  if ([_delegate respondsToSelector:aSelector])
626  [_delegate performSelector:aSelector];
627  else
628  [super doCommandBySelector:aSelector];
629 }
630 
634 - (CPWindow)keyWindow
635 {
636  return _keyWindow;
637 }
638 
642 - (CPWindow)mainWindow
643 {
644  return _mainWindow;
645 }
646 
650 - (CPWindow)windowWithWindowNumber:(int)aWindowNumber
651 {
652  // Never allow _windows[0] to be returned - it's an internal CPNull placeholder.
653  if (!aWindowNumber)
654  return nil;
655 
656  return _windows[aWindowNumber];
657 }
658 
662 - (CPArray)windows
663 {
664  // Return all windows, but not the CPNull placeholder in _windows[0].
665  return [_windows subarrayWithRange:CPMakeRange(1, [_windows count] - 1)];
666 }
667 
671 - (CPArray)orderedWindows
672 {
673 #if PLATFORM(DOM)
674  return CPWindowObjectList();
675 #else
676  return [];
677 #endif
678 }
679 
680 - (void)hide:(id)aSender
681 {
682  [CPPlatform hide:self];
683 }
684 
685 // Accessing the Main Menu
689 - (CPMenu)mainMenu
690 {
691  return [self menu];
692 }
693 
698 - (void)setMainMenu:(CPMenu)aMenu
699 {
700  [self setMenu:aMenu];
701 }
702 
703 - (void)setMenu:(CPMenu)aMenu
704 {
705  if ([aMenu _menuName] === "CPMainMenu")
706  {
707  if ([self menu] === aMenu)
708  return;
709 
710  [super setMenu:aMenu];
711 
712  if ([CPPlatform supportsNativeMainMenu])
713  window.cpSetMainMenu([self menu]);
714  }
715  else
716  [aMenu _setMenuName:@"CPMainMenu"];
717 }
718 
723 - (void)orderFrontColorPanel:(id)aSender
724 {
725  [[CPColorPanel sharedColorPanel] orderFront:self];
726 }
727 
728 // Posting Actions
738 - (BOOL)tryToPerform:(SEL)anAction with:(id)anObject
739 {
740  if (!anAction)
741  return NO;
742 
743  if ([super tryToPerform:anAction with:anObject])
744  return YES;
745 
746  if ([_delegate respondsToSelector:anAction])
747  {
748  [_delegate performSelector:anAction withObject:anObject];
749 
750  return YES;
751  }
752 
753  return NO;
754 }
755 
763 - (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)aSender
764 {
765  var target = [self targetForAction:anAction to:aTarget from:aSender];
766 
767  if (!target)
768  return NO;
769 
770  [target performSelector:anAction withObject:aSender];
771 
772  return YES;
773 }
774 
786 - (id)targetForAction:(SEL)anAction to:(id)aTarget from:(id)aSender
787 {
788  if (!anAction)
789  return nil;
790 
791  if (aTarget)
792  return aTarget;
793 
794  return [self targetForAction:anAction];
795 }
796 
814 - (id)_targetForWindow:(CPWindow)aWindow action:(SEL)anAction
815 {
816  var responder = [aWindow firstResponder],
817  checkWindow = YES;
818 
819  while (responder)
820  {
821  if ([responder respondsToSelector:anAction])
822  return responder;
823 
824  if (responder == aWindow)
825  checkWindow = NO;
826 
827  responder = [responder nextResponder];
828  }
829 
830  if (checkWindow && [aWindow respondsToSelector:anAction])
831  return aWindow;
832 
833  var delegate = [aWindow delegate];
834 
835  if ([delegate respondsToSelector:anAction])
836  return delegate;
837 
838  var windowController = [aWindow windowController];
839 
840  if ([windowController respondsToSelector:anAction])
841  return windowController;
842 
843  var theDocument = [windowController document];
844  if (theDocument !== delegate && [theDocument respondsToSelector:anAction])
845  return theDocument;
846 
847  return nil;
848 }
849 
864 - (id)targetForAction:(SEL)anAction
865 {
866  if (!anAction)
867  return nil;
868 
869  var target = [self _targetForWindow:[self keyWindow] action:anAction];
870 
871  if (target)
872  return target;
873 
874  target = [self _targetForWindow:[self mainWindow] action:anAction];
875 
876  if (target)
877  return target;
878 
879  if ([self respondsToSelector:anAction])
880  return self;
881 
882  if ([_delegate respondsToSelector:anAction])
883  return _delegate;
884 
885  if ([_documentController respondsToSelector:anAction])
886  return _documentController;
887 
888  return nil;
889 }
890 
906 - (void)setCallback:(Function)aCallback forNextEventMatchingMask:(unsigned int)aMask untilDate:(CPDate)anExpiration inMode:(CPString)aMode dequeue:(BOOL)shouldDequeue
907 {
908  _eventListeners.splice(_eventListenerInsertionIndex++, 0, _CPEventListenerMake(aMask, aCallback, shouldDequeue));
909 }
910 
928 - (void)setTarget:(id)aTarget selector:(SEL)aSelector forNextEventMatchingMask:(unsigned int)aMask untilDate:(CPDate)anExpiration inMode:(CPString)aMode dequeue:(BOOL)shouldDequeue
929 {
930  _eventListeners.splice(_eventListenerInsertionIndex++, 0, _CPEventListenerMake(aMask, function (anEvent) { objj_msgSend(aTarget, aSelector, anEvent); }, shouldDequeue));
931 }
932 
936 - (CPEvent)currentEvent
937 {
938  return _currentEvent;
939 }
940 
941 // Managing Sheets
942 
951 - (void)beginSheet:(CPWindow)aSheet modalForWindow:(CPWindow)aWindow modalDelegate:(id)aModalDelegate didEndSelector:(SEL)didEndSelector contextInfo:(id)contextInfo
952 {
953  if ([aWindow isSheet])
954  {
955  [CPException raise:CPInternalInconsistencyException reason:@"The target window of beginSheet: cannot be a sheet"];
956  return;
957  }
958 
959  [aSheet._windowView _enableSheet:YES inWindow:aWindow];
960  [aWindow _attachSheet:aSheet modalDelegate:aModalDelegate didEndSelector:didEndSelector contextInfo:contextInfo];
961 }
962 
976 - (void)endSheet:(CPWindow)sheet returnCode:(int)returnCode
977 {
978  var count = [_windows count];
979 
980  while (--count >= 0)
981  {
982  var aWindow = [_windows objectAtIndex:count],
983  context = aWindow._sheetContext;
984 
985  if (context && context["sheet"] === sheet)
986  {
987  context["returnCode"] = returnCode;
988  [aWindow _endSheet];
989  return;
990  }
991  }
992 }
993 
998 - (void)endSheet:(CPWindow)sheet
999 {
1000  // FIX ME: this is wrong: by Cocoa this should be: CPRunStoppedResponse.
1001  [self endSheet:sheet returnCode:0];
1002 }
1003 
1019 - (CPArray)arguments
1020 {
1021  // FIXME This should probably not access the window object #if !PLATFORM(DOM), but the unit tests rely on it.
1022  if (window && window.location && _fullArgsString !== window.location.hash)
1023  [self _reloadArguments];
1024 
1025  return _args;
1026 }
1027 
1044 - (void)setArguments:(CPArray)args
1045 {
1046  if (!args || args.length == 0)
1047  {
1048  _args = [];
1049  // Don't use if PLATFORM(DOM) here - the unit test fakes window.location so we should play along.
1050  if (window && window.location)
1051  window.location.hash = @"#";
1052  return;
1053  }
1054 
1055  if (![args isKindOfClass:CPArray])
1056  args = [CPArray arrayWithObject:args];
1057 
1058  _args = args;
1059 
1060  var toEncode = [_args copy];
1061  for (var i = 0, count = toEncode.length; i < count; i++)
1062  toEncode[i] = encodeURIComponent(toEncode[i]);
1063 
1064  var hash = [toEncode componentsJoinedByString:@"/"];
1065 
1066  // Don't use if PLATFORM(DOM) here - the unit test fakes window.location so we should play along.
1067  if (window && window.location)
1068  window.location.hash = @"#" + hash;
1069 }
1070 
1071 - (void)_reloadArguments
1072 {
1073  // FIXME This should probably not access the window object #if !PLATFORM(DOM), but the unit tests rely on it.
1074  _fullArgsString = (window && window.location) ? window.location.hash : "";
1075 
1076  if (_fullArgsString.length)
1077  {
1078  var args = _fullArgsString.substring(1).split("/");
1079 
1080  for (var i = 0, count = args.length; i < count; i++)
1081  args[i] = decodeURIComponent(args[i]);
1082 
1083  _args = args;
1084  }
1085  else
1086  _args = [];
1087 }
1088 
1106 - (CPDictionary)namedArguments
1107 {
1108  return _namedArgs;
1109 }
1110 
1111 - (BOOL)_openURL:(CPURL)aURL
1112 {
1113  if (_delegate && [_delegate respondsToSelector:@selector(application:openFile:)])
1114  {
1115  CPLog.warn("application:openFile: is deprecated, use application:openURL: instead.");
1116  return [_delegate application:self openFile:[aURL absoluteString]];
1117  }
1118 
1119  if (_delegate && [_delegate respondsToSelector:@selector(application:openURL:)])
1120  return [_delegate application:self openURL:aURL];
1121 
1122  return !![_documentController openDocumentWithContentsOfURL:aURL display:YES error:NULL];
1123 }
1124 
1125 - (void)_willBecomeActive
1126 {
1127  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationWillBecomeActiveNotification
1128  object:self
1129  userInfo:nil];
1130 }
1131 
1132 - (void)_didBecomeActive
1133 {
1134  if (![self keyWindow] && _previousKeyWindow &&
1135  [[self windows] indexOfObjectIdenticalTo:_previousKeyWindow] !== CPNotFound)
1136  [_previousKeyWindow makeKeyWindow];
1137 
1138  if (![self mainWindow] && _previousMainWindow &&
1139  [[self windows] indexOfObjectIdenticalTo:_previousMainWindow] !== CPNotFound)
1140  [_previousMainWindow makeMainWindow];
1141 
1142  if ([self keyWindow])
1143  [[self keyWindow] orderFront:self];
1144  else if ([self mainWindow])
1145  [[self mainWindow] makeKeyAndOrderFront:self];
1146  else
1147  [[self mainMenu]._menuWindow makeKeyWindow]; //FIXME this may not actually work
1148 
1149  _previousKeyWindow = nil;
1150  _previousMainWindow = nil;
1151 
1152  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationDidBecomeActiveNotification
1153  object:self
1154  userInfo:nil];
1155 }
1156 
1157 - (void)_willResignActive
1158 {
1159  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationWillResignActiveNotification
1160  object:self
1161  userInfo:nil];
1162 }
1163 
1164 - (void)_didResignActive
1165 {
1166  if (self._activeMenu)
1167  [self._activeMenu cancelTracking];
1168 
1169  if ([self keyWindow])
1170  {
1171  _previousKeyWindow = [self keyWindow];
1172  [_previousKeyWindow resignKeyWindow];
1173  }
1174 
1175  if ([self mainWindow])
1176  {
1177  _previousMainWindow = [self mainWindow];
1178  [_previousMainWindow resignMainWindow];
1179  }
1180 
1181  [[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationDidResignActiveNotification
1182  object:self
1183  userInfo:nil];
1184 }
1185 
1186 + (CPString)defaultThemeName
1187 {
1188  return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo2");
1189 }
1190 
1191 @end
1192 
1193 var _CPModalSessionMake = function(aWindow, aStopCode)
1194 {
1195  return { _window:aWindow, _state:CPRunContinuesResponse , _previous:nil };
1196 };
1197 
1198 var _CPEventListenerMake = function(anEventMask, aCallback, shouldDequeue)
1199 {
1200  return { _mask:anEventMask, _callback:aCallback, _dequeue:shouldDequeue };
1201 };
1202 
1203 // Make this a global for use in CPPlatformWindow+DOM.j.
1204 _CPRunModalLoop = function(anEvent)
1205 {
1206  [CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:YES];
1207 
1208  var theWindow = [anEvent window],
1209  modalSession = CPApp._currentSession;
1210 
1211  /*
1212  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.
1213  */
1214  if (theWindow == modalSession._window ||
1215  [theWindow worksWhenModal] ||
1216  [theWindow attachedSheet] == modalSession._window || // -dw- allow modal parent of sheet to be repositioned
1217  ([theWindow isKindOfClass:_CPPopoverWindow] && [[theWindow targetView] window] === modalSession._window))
1218  {
1219  [theWindow sendEvent:anEvent];
1220  }
1221 };
1222 
1230 function CPApplicationMain(args, namedArgs)
1231 {
1232 
1233 #if PLATFORM(DOM)
1234  // hook to allow recorder, etc to manipulate things before starting AppKit
1235  if (window.parent !== window && typeof window.parent._childAppIsStarting === "function")
1236  {
1237  try
1238  {
1239  window.parent._childAppIsStarting(window);
1240  }
1241  catch(err)
1242  {
1243  // This could happen if we're in an iframe without access to the parent frame.
1244  CPLog.warn("Failed to call parent frame's _childAppIsStarting().");
1245  }
1246  }
1247 #endif
1248 
1249  var mainBundle = [CPBundle mainBundle],
1250  principalClass = [mainBundle principalClass];
1251 
1252  if (!principalClass)
1253  principalClass = [CPApplication class];
1254 
1255  [principalClass sharedApplication];
1256 
1257  if ([args containsObject:"debug"])
1258  CPLogRegister(CPLogPopup);
1259 
1260  CPApp._args = args;
1261  CPApp._namedArgs = namedArgs;
1262 
1263  [_CPAppBootstrapper performActions];
1264 }
1265 
1266 var _CPAppBootstrapperActions = nil;
1267 @implementation _CPAppBootstrapper : CPObject
1268 {
1269  id __doxygen__;
1270 }
1271 
1272 + (CPArray)actions
1273 {
1274  return [@selector(bootstrapPlatform), @selector(loadDefaultTheme), @selector(loadMainCibFile)];
1275 }
1276 
1277 + (void)performActions
1278 {
1279  if (!_CPAppBootstrapperActions)
1280  _CPAppBootstrapperActions = [self actions];
1281 
1282  while (_CPAppBootstrapperActions.length)
1283  {
1284  var action = _CPAppBootstrapperActions.shift();
1285 
1286  if (objj_msgSend(self, action))
1287  return;
1288  }
1289 
1290  [CPApp run];
1291 }
1292 
1293 + (BOOL)bootstrapPlatform
1294 {
1295  return [CPPlatform bootstrap];
1296 }
1297 
1298 + (BOOL)loadDefaultTheme
1299 {
1300  var defaultThemeName = [CPApplication defaultThemeName],
1301  themeURL = nil;
1302 
1303  if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2")
1304  themeURL = [[CPBundle bundleForClass:[CPApplication class]] pathForResource:defaultThemeName + @".blend"];
1305  else
1306  themeURL = [[CPBundle mainBundle] pathForResource:defaultThemeName + @".blend"];
1307 
1308  var blend = [[CPThemeBlend alloc] initWithContentsOfURL:themeURL];
1309  [blend loadWithDelegate:self];
1310 
1311  return YES;
1312 }
1313 
1314 + (void)blendDidFinishLoading:(CPThemeBlend)aThemeBlend
1315 {
1317  [CPTheme setDefaultTheme:[CPTheme themeNamed:[CPApplication defaultThemeName]]];
1318 
1319  [self performActions];
1320 }
1321 
1322 + (BOOL)loadMainCibFile
1323 {
1324  var mainBundle = [CPBundle mainBundle],
1325  mainCibFile = [mainBundle objectForInfoDictionaryKey:CPMainCibFile] || [mainBundle objectForInfoDictionaryKey:CPMainCibFileHumanFriendly];
1326 
1327  if (mainCibFile)
1328  {
1329  [mainBundle loadCibFile:mainCibFile
1330  externalNameTable:@{ CPCibOwner: CPApp }
1331  loadDelegate:self];
1332 
1333  return YES;
1334  }
1335  else
1336  [self loadCiblessBrowserMainMenu];
1337 
1338  return NO;
1339 }
1340 
1341 + (void)loadCiblessBrowserMainMenu
1342 {
1343  var mainMenu = [[CPMenu alloc] initWithTitle:@"MainMenu"];
1344 
1345  // FIXME: We should implement autoenabling.
1346  [mainMenu setAutoenablesItems:NO];
1347 
1348  var newMenuItem = [[CPMenuItem alloc] initWithTitle:@"New" action:@selector(newDocument:) keyEquivalent:@"n"];
1349 
1350  [newMenuItem setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-new" forClass:_CPMenuView]];
1351  [newMenuItem setAlternateImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-new" inState:CPThemeStateHighlighted forClass:_CPMenuView]];
1352 
1353  [mainMenu addItem:newMenuItem];
1354 
1355  var openMenuItem = [[CPMenuItem alloc] initWithTitle:@"Open" action:@selector(openDocument:) keyEquivalent:@"o"];
1356 
1357  [openMenuItem setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-open" forClass:_CPMenuView]];
1358  [openMenuItem setAlternateImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-open" inState:CPThemeStateHighlighted forClass:_CPMenuView]];
1359 
1360  [mainMenu addItem:openMenuItem];
1361 
1362  var saveMenu = [[CPMenu alloc] initWithTitle:@"Save"],
1363  saveMenuItem = [[CPMenuItem alloc] initWithTitle:@"Save" action:@selector(saveDocument:) keyEquivalent:nil];
1364 
1365  [saveMenuItem setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-save" forClass:_CPMenuView]];
1366  [saveMenuItem setAlternateImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-save" inState:CPThemeStateHighlighted forClass:_CPMenuView]];
1367 
1368  [saveMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Save" action:@selector(saveDocument:) keyEquivalent:@"s"]];
1369  [saveMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Save As" action:@selector(saveDocumentAs:) keyEquivalent:nil]];
1370 
1371  [saveMenuItem setSubmenu:saveMenu];
1372 
1373  [mainMenu addItem:saveMenuItem];
1374 
1375  var editMenuItem = [[CPMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:nil],
1376  editMenu = [[CPMenu alloc] initWithTitle:@"Edit"],
1377 
1378  undoMenuItem = [[CPMenuItem alloc] initWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:CPUndoKeyEquivalent],
1379  redoMenuItem = [[CPMenuItem alloc] initWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:CPRedoKeyEquivalent];
1380 
1381  [undoMenuItem setKeyEquivalentModifierMask:CPUndoKeyEquivalentModifierMask];
1382  [redoMenuItem setKeyEquivalentModifierMask:CPRedoKeyEquivalentModifierMask];
1383 
1384  [editMenu addItem:undoMenuItem];
1385  [editMenu addItem:redoMenuItem];
1386 
1387  [editMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"]];
1388  [editMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"]];
1389  [editMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"]];
1390 
1391  [editMenuItem setSubmenu:editMenu];
1392  [editMenuItem setHidden:YES];
1393 
1394  [mainMenu addItem:editMenuItem];
1395 
1396  [mainMenu addItem:[CPMenuItem separatorItem]];
1397 
1398  [CPApp setMainMenu:mainMenu];
1399 }
1400 
1401 + (void)cibDidFinishLoading:(CPCib)aCib
1402 {
1403  [self performActions];
1404 }
1405 
1406 + (void)cibDidFailToLoad:(CPCib)aCib
1407 {
1408  throw new Error("Could not load main cib file. Did you forget to nib2cib it?");
1409 }
1410 
1411 + (void)reset
1412 {
1413  _CPAppBootstrapperActions = nil;
1414 }
1415 
1416 @end
1417 
1418 
1420 
1424 + (unsigned)modifierFlags
1425 {
1426  return CPEventModifierFlags;
1427 }
1428 
1429 @end
1430 
1432 
1436 - (CPThemeBlend)themeBlend
1437 {
1438  return _themeBlend;
1439 }
1440 
1444 - (void)setThemeBlend:(CPThemeBlend)aValue
1445 {
1446  _themeBlend = aValue;
1447 }
1448 
1449 @end