PageRenderTime 19ms CodeModel.GetById 15ms app.highlight 1ms RepoModel.GetById 1ms app.codeStats 0ms

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