/AppKit/CPViewController.j

http://github.com/cacaodev/cappuccino · Unknown · 323 lines · 257 code · 66 blank · 0 comment · 0 complexity · 0aaf19091db29034f684b7d8d473c26d MD5 · raw file

  1. /*
  2. * CPViewController.j
  3. * AppKit
  4. *
  5. * Created by Nicholas Small and Francisco Tolmasky.
  6. * Copyright 2009, 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. @import <Foundation/CPArray.j>
  23. @import "CPCib.j"
  24. @import "CPResponder.j"
  25. @class CPDocument
  26. @global CPApp
  27. var CPViewControllerCachedCibs;
  28. /*!
  29. @ingroup appkit
  30. @class CPViewController
  31. The CPViewController class provides the fundamental view-management
  32. controller for Cappuccino applications. The basic view controller class
  33. supports the presentation of an associated view in addition to basic
  34. support for managing modal views and, in the future, animations.
  35. Subclasses such as CPNavigationController and CPTabBarController provide
  36. additional behavior for managing complex hierarchies of view controllers
  37. and views.
  38. You use each instance of CPViewController to manage a single view (and
  39. hierarchy). For a simple view controller, this entails managing the view
  40. hierarchy responsible for presenting your application content. A typical
  41. view hierarchy consists of a root view, a reference to which is available
  42. in the view property of this class, and one or more subviews presenting
  43. the actual content. In the case of navigation and tab bar controllers, the
  44. view controller manages not only the high-level view hierarchy (which
  45. provides the navigation controls) but also one or more additional view
  46. controllers that handle the presentation of the application content.
  47. Unlike UIViewController in Cocoa Touch, a CPViewController does not
  48. represent an entire screen of content. You will add your root view to an
  49. existing view or window's content view. You can manage many view
  50. controllers on screen at once. CPViewController is also the preferred way
  51. of working with Cibs.
  52. Subclasses can override -loadView to create their custom view hierarchy,
  53. or specify a cib name to be loaded automatically. It has methods that are
  54. called when a view appears or disappears. This class is also a good place
  55. for delegate & datasource methods, and other controller stuff.
  56. */
  57. @implementation CPViewController : CPResponder
  58. {
  59. CPView _view @accessors(property=view);
  60. BOOL _isLoading;
  61. BOOL _isLazy;
  62. BOOL _isViewLoaded @accessors(getter=isViewLoaded);
  63. id _representedObject @accessors(property=representedObject);
  64. CPString _title @accessors(property=title);
  65. CPString _cibName @accessors(property=cibName, readonly);
  66. CPBundle _cibBundle @accessors(property=cibBundle, readonly);
  67. CPDictionary _cibExternalNameTable @accessors(property=cibExternalNameTable, readonly);
  68. }
  69. + (void)initialize
  70. {
  71. if (self !== [CPViewController class])
  72. return;
  73. CPViewControllerCachedCibs = @{};
  74. }
  75. /*!
  76. Convenience initializer calls -initWithCibName:bundle: with nil for both parameters.
  77. */
  78. - (id)init
  79. {
  80. return [self initWithCibName:nil bundle:nil];
  81. }
  82. - (id)initWithCibName:(CPString)aCibNameOrNil bundle:(CPBundle)aCibBundleOrNil
  83. {
  84. return [self initWithCibName:aCibNameOrNil bundle:aCibBundleOrNil externalNameTable:nil];
  85. }
  86. - (id)initWithCibName:(CPString)aCibNameOrNil bundle:(CPBundle)aCibBundleOrNil owner:(id)anOwner
  87. {
  88. return [self initWithCibName:aCibNameOrNil bundle:aCibBundleOrNil externalNameTable:@{ CPCibOwner: anOwner }];
  89. }
  90. /*!
  91. The designated initializer. If you subclass CPViewController, you must
  92. call the super implementation of this method, even if you aren't using a
  93. Cib.
  94. In the specified Cib, the File's Owner proxy should have its class set to
  95. your view controller subclass, with the view outlet connected to the main
  96. view. If you pass in a nil Cib name, then you must either call -setView:
  97. before -view is invoked, or override -loadView to set up your views.
  98. @param cibNameOrNil The path to the cib to load for the root view or nil to programmatically create views.
  99. @param cibBundleOrNil The bundle that the cib is located in or nil for the main bundle.
  100. */
  101. - (id)initWithCibName:(CPString)aCibNameOrNil bundle:(CPBundle)aCibBundleOrNil externalNameTable:(CPDictionary)anExternalNameTable
  102. {
  103. self = [super init];
  104. if (self)
  105. {
  106. // Don't load the cib until someone actually requests the view. The user may just be intending to use setView:.
  107. _cibName = aCibNameOrNil;
  108. _cibBundle = aCibBundleOrNil || [CPBundle mainBundle];
  109. _cibExternalNameTable = anExternalNameTable || @{ CPCibOwner: self };
  110. _isLoading = NO;
  111. _isLazy = NO;
  112. }
  113. return self;
  114. }
  115. /*!
  116. Programmatically creates the view that the controller manages. You should
  117. never call this method directly. The view controller calls this method
  118. when the view property is requested but is nil.
  119. If you create your views manually, you must override this method and use
  120. it to create your view and assign it to the view property. The default
  121. implementation for programmatic views is to create a plain, zero width & height
  122. view. You can invoke super to utilize this view.
  123. If you use Interface Builder to create your views, and you initialize the
  124. controller using the initWithCibName:bundle: methods, then you MUST NOT override
  125. this method. The consequences risk shattering the space-time continuum.
  126. Note: The cib loading system is currently synchronous.
  127. */
  128. - (void)loadView
  129. {
  130. if (_view)
  131. return;
  132. if (_cibName)
  133. {
  134. // check if a cib is already cached for the current _cibName
  135. var cib = [CPViewControllerCachedCibs objectForKey:_cibName];
  136. if (!cib)
  137. {
  138. // if the cib isn't cached yet : fetch it and cache it
  139. cib = [[CPCib alloc] initWithCibNamed:_cibName bundle:_cibBundle];
  140. [CPViewControllerCachedCibs setObject:cib forKey:_cibName];
  141. }
  142. [cib instantiateCibWithExternalNameTable:_cibExternalNameTable];
  143. }
  144. else
  145. _view = [CPView new];
  146. }
  147. /*!
  148. Returns the view that the controller manages.
  149. If this property is nil, the controller sends loadView to itself to create
  150. the view that it manages. Subclasses should override the loadView method
  151. to create any custom views. The default value is nil.
  152. */
  153. - (CPView)view
  154. {
  155. if (!_view)
  156. {
  157. _isLoading = YES;
  158. var cibOwner = [_cibExternalNameTable objectForKey:CPCibOwner];
  159. if ([cibOwner respondsToSelector:@selector(viewControllerWillLoadCib:)])
  160. [cibOwner viewControllerWillLoadCib:self];
  161. [self loadView];
  162. if (_view === nil && [cibOwner isKindOfClass:[CPDocument class]])
  163. [self setView:[cibOwner valueForKey:@"view"]];
  164. if (!_view)
  165. {
  166. var reason = [CPString stringWithFormat:@"View for %@ could not be loaded from Cib or no view specified. Override loadView to load the view manually.", self];
  167. [CPException raise:CPInternalInconsistencyException reason:reason];
  168. }
  169. if ([cibOwner respondsToSelector:@selector(viewControllerDidLoadCib:)])
  170. [cibOwner viewControllerDidLoadCib:self];
  171. _isLoading = NO;
  172. _isLazy = NO;
  173. [self _viewDidLoad];
  174. }
  175. else if (_isLazy)
  176. {
  177. _isLazy = NO;
  178. [self _viewDidLoad];
  179. }
  180. return _view;
  181. }
  182. - (void)_viewDidLoad
  183. {
  184. [self willChangeValueForKey:"isViewLoaded"];
  185. [self viewDidLoad];
  186. _isViewLoaded = YES;
  187. [self didChangeValueForKey:"isViewLoaded"];
  188. }
  189. /*!
  190. This method is called after the view controller has loaded its associated views into memory.
  191. This method is called regardless of whether the views were stored in a nib
  192. file or created programmatically in the loadView method, but NOT when setView
  193. is invoked. This method is most commonly used to perform additional initialization
  194. steps on views that are loaded from cib files.
  195. */
  196. - (void)viewDidLoad
  197. {
  198. }
  199. /*!
  200. Manually sets the view that the controller manages.
  201. Setting to nil will cause -loadView to be called on all subsequent calls
  202. of -view.
  203. @param aView The view this controller should represent.
  204. */
  205. - (void)setView:(CPView)aView
  206. {
  207. var willChangeIsViewLoaded = (_isViewLoaded == NO && aView != nil) || (_isViewLoaded == YES && aView == nil);
  208. if (willChangeIsViewLoaded)
  209. [self willChangeValueForKey:"isViewLoaded"];
  210. _view = aView;
  211. _isViewLoaded = aView !== nil;
  212. if (willChangeIsViewLoaded)
  213. [self didChangeValueForKey:"isViewLoaded"];
  214. }
  215. - (BOOL)automaticallyNotifiesObserversOfIsViewLoaded
  216. {
  217. return NO;
  218. }
  219. @end
  220. var CPViewControllerViewKey = @"CPViewControllerViewKey",
  221. CPViewControllerTitleKey = @"CPViewControllerTitleKey",
  222. CPViewControllerCibNameKey = @"CPViewControllerCibNameKey",
  223. CPViewControllerBundleKey = @"CPViewControllerBundleKey";
  224. @implementation CPViewController (CPCoding)
  225. /*!
  226. Initializes the view controller by unarchiving data from a coder.
  227. @param aCoder the coder from which the data will be unarchived
  228. @return the initialized view controller
  229. */
  230. - (id)initWithCoder:(CPCoder)aCoder
  231. {
  232. self = [super initWithCoder:aCoder];
  233. if (self)
  234. {
  235. _view = [aCoder decodeObjectForKey:CPViewControllerViewKey];
  236. _title = [aCoder decodeObjectForKey:CPViewControllerTitleKey];
  237. _cibName = [aCoder decodeObjectForKey:CPViewControllerCibNameKey];
  238. var bundlePath = [aCoder decodeObjectForKey:CPViewControllerBundleKey];
  239. _cibBundle = bundlePath ? [CPBundle bundleWithPath:bundlePath] : [CPBundle mainBundle];
  240. _cibExternalNameTable = @{ CPCibOwner: self };
  241. _isLazy = YES;
  242. }
  243. return self;
  244. }
  245. /*!
  246. Archives the view controller to the provided coder.
  247. @param aCoder the coder to which the view controller should be archived
  248. */
  249. - (void)encodeWithCoder:(CPCoder)aCoder
  250. {
  251. [super encodeWithCoder:aCoder];
  252. [aCoder encodeObject:_view forKey:CPViewControllerViewKey];
  253. [aCoder encodeObject:_title forKey:CPViewControllerTitleKey];
  254. [aCoder encodeObject:_cibName forKey:CPViewControllerCibNameKey];
  255. [aCoder encodeObject:[_cibBundle bundlePath] forKey:CPViewControllerBundleKey];
  256. }
  257. @end