PageRenderTime 45ms CodeModel.GetById 36ms app.highlight 3ms RepoModel.GetById 2ms app.codeStats 0ms

/AppKit/CPTabView.j

http://github.com/cacaodev/cappuccino
Unknown | 571 lines | 462 code | 109 blank | 0 comment | 0 complexity | 58b8c2e866fdeb3e7fa66c513d284f6b MD5 | raw file
  1/*
  2 * CPTabView.j
  3 * AppKit
  4 *
  5 * Created by Derek Hammer.
  6 * Copyright 2010, Derek Hammer.
  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 "CPBox.j"
 24@import "CPSegmentedControl.j"
 25@import "CPTabViewItem.j"
 26@import "CPView.j"
 27
 28@typedef CPTabViewType
 29CPTopTabsBezelBorder     = 0;
 30//CPLeftTabsBezelBorder  = 1;
 31CPBottomTabsBezelBorder  = 2;
 32//CPRightTabsBezelBorder = 3;
 33CPNoTabsBezelBorder      = 4; //Displays no tabs and has a bezeled border.
 34CPNoTabsLineBorder       = 5; //Has no tabs and displays a line border.
 35CPNoTabsNoBorder         = 6; //Displays no tabs and no border.
 36
 37var CPTabViewDidSelectTabViewItemSelector           = 1 << 1,
 38    CPTabViewShouldSelectTabViewItemSelector        = 1 << 2,
 39    CPTabViewWillSelectTabViewItemSelector          = 1 << 3,
 40    CPTabViewDidChangeNumberOfTabViewItemsSelector  = 1 << 4;
 41
 42
 43@protocol CPTabViewDelegate <CPObject>
 44
 45@optional
 46- (BOOL)tabView:(CPTabView)tabView shouldSelectTabViewItem:(CPTabViewItem)tabViewItem;
 47- (void)tabView:(CPTabView)tabView didSelectTabViewItem:(CPTabViewItem)tabViewItem;
 48- (void)tabView:(CPTabView)tabView willSelectTabViewItem:(CPTabViewItem)tabViewItem;
 49- (void)tabViewDidChangeNumberOfTabViewItems:(CPTabView)tabView;
 50
 51@end
 52
 53
 54/*!
 55    @ingroup appkit
 56    @class CPTabView
 57
 58    A CPTabView object presents a tabbed interface where each page is one a
 59    complete view hiearchy of its own. The user can navigate between various
 60    pages by clicking on the tab headers.
 61*/
 62@implementation CPTabView : CPView
 63{
 64    CPArray                 _items;
 65
 66    CPSegmentedControl      _tabs;
 67    CPBox                   _box;
 68
 69    CPNumber                _selectedIndex;
 70
 71    CPTabViewType           _type;
 72    CPFont                  _font;
 73
 74    id <CPTabViewDelegate>  _delegate;
 75    unsigned                _delegateSelectors;
 76}
 77
 78- (id)initWithFrame:(CGRect)aFrame
 79{
 80    if (self = [super initWithFrame:aFrame])
 81    {
 82        _items = [CPArray array];
 83
 84        [self _init];
 85        [self setTabViewType:CPTopTabsBezelBorder];
 86    }
 87
 88    return self;
 89}
 90
 91- (void)_init
 92{
 93    _selectedIndex = CPNotFound;
 94
 95    _tabs = [[CPSegmentedControl alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
 96    [_tabs setHitTests:NO];
 97
 98    var height = [_tabs valueForThemeAttribute:@"min-size"].height;
 99    [_tabs setFrameSize:CGSizeMake(0, height)];
100
101    _box = [[CPBox alloc] initWithFrame:[self  bounds]];
102    [self setBackgroundColor:[CPColor colorWithCalibratedWhite:0.95 alpha:1.0]];
103
104    [self addSubview:_box];
105    [self addSubview:_tabs];
106}
107
108// Adding and Removing Tabs
109/*!
110    Adds a CPTabViewItem to the tab view.
111    @param aTabViewItem the item to add
112*/
113- (void)addTabViewItem:(CPTabViewItem)aTabViewItem
114{
115    [self insertTabViewItem:aTabViewItem atIndex:[_items count]];
116}
117
118/*!
119    Inserts a CPTabViewItem into the tab view at the specified index.
120    @param aTabViewItem the item to insert
121    @param anIndex the index for the item
122*/
123- (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPUInteger)anIndex
124{
125    [_items insertObject:aTabViewItem atIndex:anIndex];
126
127    [self _updateItems];
128    [self _repositionTabs];
129
130    [aTabViewItem _setTabView:self];
131
132    if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
133        [_delegate tabViewDidChangeNumberOfTabViewItems:self];
134}
135
136/*!
137    Removes the specified tab view item from the tab view.
138    @param aTabViewItem the item to remove
139*/
140- (void)removeTabViewItem:(CPTabViewItem)aTabViewItem
141{
142    var count = [_items count];
143    for (var i = 0; i < count; i++)
144    {
145        if ([_items objectAtIndex:i] === aTabViewItem)
146        {
147            [_items removeObjectAtIndex:i];
148            break;
149        }
150    }
151
152    [self _updateItems];
153    [self _repositionTabs];
154
155    [aTabViewItem _setTabView:nil];
156
157    if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
158        [_delegate tabViewDidChangeNumberOfTabViewItems:self];
159}
160
161// Accessing Tabs
162/*!
163    Returns the index of the specified item
164    @param aTabViewItem the item to find the index for
165    @return the index of aTabViewItem or CPNotFound
166*/
167- (int)indexOfTabViewItem:(CPTabViewItem)aTabViewItem
168{
169    return [_items indexOfObjectIdenticalTo:aTabViewItem];
170}
171
172/*!
173    Returns the index of the CPTabViewItem with the specified identifier.
174    @param anIdentifier the identifier of the item
175    @return the index of the tab view item identified by anIdentifier, or CPNotFound
176*/
177- (int)indexOfTabViewItemWithIdentifier:(CPString)anIdentifier
178{
179    for (var index = [_items count]; index >= 0; index--)
180        if ([[_items[index] identifier] isEqual:anIdentifier])
181            return index;
182
183    return CPNotFound;
184}
185
186/*!
187    Returns the number of items in the tab view.
188    @return the number of tab view items in the receiver
189*/
190- (unsigned)numberOfTabViewItems
191{
192    return [_items count];
193}
194
195/*!
196    Returns the CPTabViewItem at the specified index.
197    @return a tab view item, or nil
198*/
199- (CPTabViewItem)tabViewItemAtIndex:(CPUInteger)anIndex
200{
201    return [_items objectAtIndex:anIndex];
202}
203
204/*!
205    Returns the array of items that backs this tab view.
206    @return a copy of the array of items in the receiver
207*/
208- (CPArray)tabViewItems
209{
210    return [_items copy]; // Copy?
211}
212
213// Selecting a Tab
214/*!
215    Sets the first tab view item in the array to be displayed to the user.
216    @param aSender the object making this request
217*/
218- (void)selectFirstTabViewItem:(id)aSender
219{
220    if ([_items count] === 0)
221        return; // throw?
222
223    [self selectTabViewItemAtIndex:0];
224}
225
226/*!
227    Sets the last tab view item in the array to be displayed to the user.
228    @param aSender the object making this request
229*/
230- (void)selectLastTabViewItem:(id)aSender
231{
232    if ([_items count] === 0)
233        return; // throw?
234
235    [self selectTabViewItemAtIndex:[_items count] - 1];
236}
237
238/*!
239    Sets the next tab item in the array to be displayed.
240    @param aSender the object making this request
241*/
242- (void)selectNextTabViewItem:(id)aSender
243{
244    if (_selectedIndex === CPNotFound)
245        return;
246
247    var nextIndex = _selectedIndex + 1;
248
249    if (nextIndex === [_items count])
250        // does nothing. According to spec at (http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Reference/ApplicationKit/Classes/NSTabView_Class/Reference/Reference.html#//apple_ref/occ/instm/NSTabView/selectNextTabViewItem:)
251        return;
252
253    [self selectTabViewItemAtIndex:nextIndex];
254}
255
256/*!
257    Selects the previous item in the array for display.
258    @param aSender the object making this request
259*/
260- (void)selectPreviousTabViewItem:(id)aSender
261{
262    if (_selectedIndex === CPNotFound)
263        return;
264
265    var previousIndex = _selectedIndex - 1;
266
267    if (previousIndex < 0)
268        return; // does nothing. See above.
269
270    [self selectTabViewItemAtIndex:previousIndex];
271}
272
273/*!
274    Displays the specified item in the tab view.
275    @param aTabViewItem the item to display
276*/
277- (void)selectTabViewItem:(CPTabViewItem)aTabViewItem
278{
279    [self selectTabViewItemAtIndex:[self indexOfTabViewItem:aTabViewItem]];
280}
281
282/*!
283    Selects the item at the specified index.
284    @param anIndex the index of the item to display.
285*/
286- (BOOL)selectTabViewItemAtIndex:(CPUInteger)anIndex
287{
288    if (anIndex === _selectedIndex)
289        return;
290
291    var aTabViewItem = [self tabViewItemAtIndex:anIndex];
292
293    if ((_delegateSelectors & CPTabViewShouldSelectTabViewItemSelector) && ![_delegate tabView:self shouldSelectTabViewItem:aTabViewItem])
294        return NO;
295
296    if (_delegateSelectors & CPTabViewWillSelectTabViewItemSelector)
297        [_delegate tabView:self willSelectTabViewItem:aTabViewItem];
298
299    [_tabs selectSegmentWithTag:anIndex];
300    [self _setSelectedIndex:anIndex];
301
302    if (_delegateSelectors & CPTabViewDidSelectTabViewItemSelector)
303        [_delegate tabView:self didSelectTabViewItem:aTabViewItem];
304
305    return YES;
306}
307
308/*!
309    Returns the current item being displayed.
310    @return the tab view item currenly being displayed by the receiver
311*/
312- (CPTabViewItem)selectedTabViewItem
313{
314    if (_selectedIndex != CPNotFound)
315        return [_items objectAtIndex:_selectedIndex];
316
317    return nil;
318}
319
320// Modifying the font
321/*!
322    Returns the font for tab label text.
323    @return the font for tab label text
324*/
325- (CPFont)font
326{
327    return _font;
328}
329
330/*!
331    Sets the font for tab label text to font.
332    @param font the font the receiver should use for tab label text
333*/
334- (void)setFont:(CPFont)font
335{
336    if ([_font isEqual:font])
337        return;
338
339    _font = font;
340    [_tabs setFont:_font];
341}
342
343//
344/*!
345    Sets the tab view type.
346    @param aTabViewType the view type
347*/
348- (void)setTabViewType:(CPTabViewType)aTabViewType
349{
350    if (_type === aTabViewType)
351        return;
352
353    _type = aTabViewType;
354
355    if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
356        [_tabs removeFromSuperview];
357    else
358        [self addSubview:_tabs];
359
360    switch (_type)
361    {
362        case CPTopTabsBezelBorder:
363        case CPBottomTabsBezelBorder:
364        case CPNoTabsBezelBorder:
365            [_box setBorderType:CPBezelBorder];
366            break;
367        case CPNoTabsLineBorder:
368            [_box setBorderType:CPLineBorder];
369            break;
370        case CPNoTabsNoBorder:
371            [_box setBorderType:CPNoBorder];
372            break;
373    }
374
375    [self setNeedsLayout];
376}
377
378- (void)layoutSubviews
379{
380    // Even if CPTabView's autoresizesSubviews is NO, _tabs and _box has to be laid out.
381    // This means we can't rely on autoresize masks.
382    if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
383    {
384        [_box setFrame:[self bounds]];
385    }
386    else
387    {
388        var aFrame = [self frame],
389            segmentedHeight = CGRectGetHeight([_tabs frame]),
390            origin = _type === CPTopTabsBezelBorder ? segmentedHeight / 2 : 0;
391
392        [_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame),
393                                   CGRectGetHeight(aFrame) - segmentedHeight / 2)];
394
395        [self _updateItems];
396        [self _repositionTabs];
397    }
398}
399
400/*!
401    Returns the tab view type.
402    @return the tab view type of the receiver
403*/
404- (CPTabViewType)tabViewType
405{
406    return _type;
407}
408
409/*!
410    Returns the receiver's delegate.
411    @return the receiver's delegate
412*/
413- (id)delegate
414{
415    return _delegate;
416}
417
418/*!
419    Sets the delegate for this tab view.
420    @param aDelegate the tab view's delegate
421*/
422- (void)setDelegate:(id <CPTabViewDelegate>)aDelegate
423{
424    if (_delegate == aDelegate)
425        return;
426
427    _delegate = aDelegate;
428
429    _delegateSelectors = 0;
430
431    if ([_delegate respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)])
432        _delegateSelectors |= CPTabViewShouldSelectTabViewItemSelector;
433
434    if ([_delegate respondsToSelector:@selector(tabView:willSelectTabViewItem:)])
435        _delegateSelectors |= CPTabViewWillSelectTabViewItemSelector;
436
437    if ([_delegate respondsToSelector:@selector(tabView:didSelectTabViewItem:)])
438        _delegateSelectors |= CPTabViewDidSelectTabViewItemSelector;
439
440    if ([_delegate respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)])
441        _delegateSelectors |= CPTabViewDidChangeNumberOfTabViewItemsSelector;
442}
443
444- (void)setBackgroundColor:(CPColor)aColor
445{
446    [_box setBackgroundColor:aColor];
447}
448
449- (CPColor)backgroundColor
450{
451    return [_box backgroundColor];
452}
453
454- (void)mouseDown:(CPEvent)anEvent
455{
456    var segmentIndex = [_tabs testSegment:[_tabs convertPoint:[anEvent locationInWindow] fromView:nil]];
457
458    if (segmentIndex != CPNotFound && [self selectTabViewItemAtIndex:segmentIndex])
459        [_tabs trackSegment:anEvent];
460}
461
462- (void)_repositionTabs
463{
464    var horizontalCenterOfSelf = CGRectGetWidth([self bounds]) / 2,
465        verticalCenterOfTabs = CGRectGetHeight([_tabs bounds]) / 2;
466
467    if (_type === CPBottomTabsBezelBorder)
468        [_tabs setCenter:CGPointMake(horizontalCenterOfSelf, CGRectGetHeight([self bounds]) - verticalCenterOfTabs)];
469    else
470        [_tabs setCenter:CGPointMake(horizontalCenterOfSelf, verticalCenterOfTabs)];
471}
472
473- (void)_setSelectedIndex:(CPNumber)index
474{
475    _selectedIndex = index;
476    [self _setContentViewFromItem:[_items objectAtIndex:_selectedIndex]];
477}
478
479- (void)_setContentViewFromItem:(CPTabViewItem)anItem
480{
481    [_box setContentView:[anItem view]];
482}
483
484- (void)_updateItems
485{
486    var count = [_items count];
487    [_tabs setSegmentCount:count];
488
489    for (var i = 0; i < count; i++)
490    {
491        [_tabs setLabel:[[_items objectAtIndex:i] label] forSegment:i];
492        [_tabs setTag:i forSegment:i];
493    }
494
495    if (_selectedIndex === CPNotFound)
496        [self selectFirstTabViewItem:self];
497}
498
499@end
500
501var CPTabViewItemsKey               = "CPTabViewItemsKey",
502    CPTabViewSelectedItemKey        = "CPTabViewSelectedItemKey",
503    CPTabViewTypeKey                = "CPTabViewTypeKey",
504    CPTabViewFontKey                = "CPTabViewFontKey",
505    CPTabViewDelegateKey            = "CPTabViewDelegateKey";
506
507@implementation CPTabView (CPCoding)
508
509- (id)initWithCoder:(CPCoder)aCoder
510{
511    if (self = [super initWithCoder:aCoder])
512    {
513        [self _init];
514
515        _font = [aCoder decodeObjectForKey:CPTabViewFontKey];
516        [_tabs setFont:_font];
517
518        _items = [aCoder decodeObjectForKey:CPTabViewItemsKey];
519        [_items makeObjectsPerformSelector:@selector(_setTabView:) withObject:self];
520
521        [self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
522
523        self.selectOnAwake = [aCoder decodeObjectForKey:CPTabViewSelectedItemKey];
524        _type = [aCoder decodeIntForKey:CPTabViewTypeKey];
525    }
526
527    return self;
528}
529
530- (void)awakeFromCib
531{
532    // This cannot be run in initWithCoder because it might call selectTabViewItem:, which is
533    // not safe to call before the views of the tab views items are fully decoded.
534    [self _updateItems];
535
536    if (self.selectOnAwake)
537    {
538        [self selectTabViewItem:self.selectOnAwake];
539        delete self.selectOnAwake;
540    }
541
542    var type = _type;
543    _type = nil;
544    [self setTabViewType:type];
545
546    [self setNeedsLayout];
547}
548
549- (void)encodeWithCoder:(CPCoder)aCoder
550{
551    // Don't bother to encode the CPBox. We will recreate it on decode and its content view is already
552    // stored by the tab view item. Not encoding _box makes the resulting archive smaller and reduces
553    // the surface for decoding bugs (of which we've had many in tab view).
554    var subviews = [self subviews];
555    [_box removeFromSuperview];
556    [super encodeWithCoder:aCoder];
557    [self setSubviews:subviews];
558
559    [aCoder encodeObject:_items forKey:CPTabViewItemsKey];
560
561    var selected = [self selectedTabViewItem];
562    if (selected)
563        [aCoder encodeObject:selected forKey:CPTabViewSelectedItemKey];
564
565    [aCoder encodeInt:_type forKey:CPTabViewTypeKey];
566    [aCoder encodeObject:_font forKey:CPTabViewFontKey];
567
568    [aCoder encodeConditionalObject:_delegate forKey:CPTabViewDelegateKey];
569}
570
571@end