Files
cappuccino/AppKit/CPTabView.j
T
Alexander Ljungberg f805b6bfa6 Fixes #1409. Improve CPTabView decoding to fix tab selection errors.
This is an improved fix to the corrupted tab view state bug partially addressed by 0f347be.

Without either fix, NSTabView is converted to a CPTabView where tab view item views are subviews of the NSTabView, while in Cappuccino the selected view item is supposed to be a subview of a CPBox.

Without this fix, CPTabView decoding called addSubview: in initWithCoder: which can cause the superview of a view to become set, only to later be overwritten when the superview ivar is decoded.

This fix removes the tab item view as a subview from the tab view before encoding it in nib2cib, and then makes sure not to call addSubview: while still in the initWithCoder stage, but rather later in awakeFromCib.
2013-02-24 23:08:44 +00:00

550 lines
14 KiB
Plaintext

/*
* CPTabView.j
* AppKit
*
* Created by Derek Hammer.
* Copyright 2010, Derek Hammer.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPBox.j"
@import "CPSegmentedControl.j"
@import "CPTabViewItem.j"
@import "CPView.j"
CPTopTabsBezelBorder = 0;
//CPLeftTabsBezelBorder = 1;
CPBottomTabsBezelBorder = 2;
//CPRightTabsBezelBorder = 3;
CPNoTabsBezelBorder = 4; //Displays no tabs and has a bezeled border.
CPNoTabsLineBorder = 5; //Has no tabs and displays a line border.
CPNoTabsNoBorder = 6; //Displays no tabs and no border.
var CPTabViewDidSelectTabViewItemSelector = 1,
CPTabViewShouldSelectTabViewItemSelector = 2,
CPTabViewWillSelectTabViewItemSelector = 4,
CPTabViewDidChangeNumberOfTabViewItemsSelector = 8;
/*!
@ingroup appkit
@class CPTabView
A CPTabView object presents a tabbed interface where each page is one a
complete view hiearchy of its own. The user can navigate between various
pages by clicking on the tab headers.
*/
@implementation CPTabView : CPView
{
CPArray _items;
CPSegmentedControl _tabs;
CPBox _box;
CPNumber _selectedIndex;
CPTabViewType _type;
CPFont _font;
id _delegate;
unsigned _delegateSelectors;
}
- (id)initWithFrame:(CGRect)aFrame
{
if (self = [super initWithFrame:aFrame])
{
_items = [CPArray array];
[self _init];
[self setTabViewType:CPTopTabsBezelBorder];
}
return self;
}
- (void)_init
{
_selectedIndex = CPNotFound;
_tabs = [[CPSegmentedControl alloc] initWithFrame:_CGRectMake(0, 0, 0, 0)];
[_tabs setHitTests:NO];
var height = [_tabs valueForThemeAttribute:@"default-height"];
[_tabs setFrameSize:_CGSizeMake(0, height)];
_box = [[CPBox alloc] initWithFrame:[self bounds]];
[self setBackgroundColor:[CPColor colorWithCalibratedWhite:0.95 alpha:1.0]];
[self addSubview:_box];
[self addSubview:_tabs];
}
// Adding and Removing Tabs
/*!
Adds a CPTabViewItem to the tab view.
@param aTabViewItem the item to add
*/
- (void)addTabViewItem:(CPTabViewItem)aTabViewItem
{
[self insertTabViewItem:aTabViewItem atIndex:[_items count]];
}
/*!
Inserts a CPTabViewItem into the tab view at the specified index.
@param aTabViewItem the item to insert
@param anIndex the index for the item
*/
- (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(unsigned)anIndex
{
[_items insertObject:aTabViewItem atIndex:anIndex];
[self _updateItems];
[self _repositionTabs];
[aTabViewItem _setTabView:self];
if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
[_delegate tabViewDidChangeNumberOfTabViewItems:self];
}
/*!
Removes the specified tab view item from the tab view.
@param aTabViewItem the item to remove
*/
- (void)removeTabViewItem:(CPTabViewItem)aTabViewItem
{
var count = [_items count];
for (var i = 0; i < count; i++)
{
if ([_items objectAtIndex:i] === aTabViewItem)
{
[_items removeObjectAtIndex:i];
break;
}
}
[self _updateItems];
[self _repositionTabs];
[aTabViewItem _setTabView:nil];
if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
[_delegate tabViewDidChangeNumberOfTabViewItems:self];
}
// Accessing Tabs
/*!
Returns the index of the specified item
@param aTabViewItem the item to find the index for
@return the index of aTabViewItem or CPNotFound
*/
- (int)indexOfTabViewItem:(CPTabViewItem)aTabViewItem
{
return [_items indexOfObjectIdenticalTo:aTabViewItem];
}
/*!
Returns the index of the CPTabViewItem with the specified identifier.
@param anIdentifier the identifier of the item
@return the index of the tab view item identified by anIdentifier, or CPNotFound
*/
- (int)indexOfTabViewItemWithIdentifier:(CPString)anIdentifier
{
for (var index = [_items count]; index >= 0; index--)
if ([[_items[index] identifier] isEqual:anIdentifier])
return index;
return CPNotFound;
}
/*!
Returns the number of items in the tab view.
@return the number of tab view items in the receiver
*/
- (unsigned)numberOfTabViewItems
{
return [_items count];
}
/*!
Returns the CPTabViewItem at the specified index.
@return a tab view item, or nil
*/
- (CPTabViewItem)tabViewItemAtIndex:(unsigned)anIndex
{
return [_items objectAtIndex:anIndex];
}
/*!
Returns the array of items that backs this tab view.
@return a copy of the array of items in the receiver
*/
- (CPArray)tabViewItems
{
return [_items copy]; // Copy?
}
// Selecting a Tab
/*!
Sets the first tab view item in the array to be displayed to the user.
@param aSender the object making this request
*/
- (void)selectFirstTabViewItem:(id)aSender
{
if ([_items count] === 0)
return; // throw?
[self selectTabViewItemAtIndex:0];
}
/*!
Sets the last tab view item in the array to be displayed to the user.
@param aSender the object making this request
*/
- (void)selectLastTabViewItem:(id)aSender
{
if ([_items count] === 0)
return; // throw?
[self selectTabViewItemAtIndex:[_items count] - 1];
}
/*!
Sets the next tab item in the array to be displayed.
@param aSender the object making this request
*/
- (void)selectNextTabViewItem:(id)aSender
{
if (_selectedIndex === CPNotFound)
return;
var nextIndex = _selectedIndex + 1;
if (nextIndex === [_items count])
// 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:)
return;
[self selectTabViewItemAtIndex:nextIndex];
}
/*!
Selects the previous item in the array for display.
@param aSender the object making this request
*/
- (void)selectPreviousTabViewItem:(id)aSender
{
if (_selectedIndex === CPNotFound)
return;
var previousIndex = _selectedIndex - 1;
if (previousIndex < 0)
return; // does nothing. See above.
[self selectTabViewItemAtIndex:previousIndex];
}
/*!
Displays the specified item in the tab view.
@param aTabViewItem the item to display
*/
- (void)selectTabViewItem:(CPTabViewItem)aTabViewItem
{
[self selectTabViewItemAtIndex:[self indexOfTabViewItem:aTabViewItem]];
}
/*!
Selects the item at the specified index.
@param anIndex the index of the item to display.
*/
- (BOOL)selectTabViewItemAtIndex:(unsigned)anIndex
{
if (anIndex === _selectedIndex)
return;
var aTabViewItem = [self tabViewItemAtIndex:anIndex];
if ((_delegateSelectors & CPTabViewShouldSelectTabViewItemSelector) && ![_delegate tabView:self shouldSelectTabViewItem:aTabViewItem])
return NO;
if (_delegateSelectors & CPTabViewWillSelectTabViewItemSelector)
[_delegate tabView:self willSelectTabViewItem:aTabViewItem];
[_tabs selectSegmentWithTag:anIndex];
[self _setSelectedIndex:anIndex];
if (_delegateSelectors & CPTabViewDidSelectTabViewItemSelector)
[_delegate tabView:self didSelectTabViewItem:aTabViewItem];
return YES;
}
/*!
Returns the current item being displayed.
@return the tab view item currenly being displayed by the receiver
*/
- (CPTabViewItem)selectedTabViewItem
{
if (_selectedIndex != CPNotFound)
return [_items objectAtIndex:_selectedIndex];
return nil;
}
// Modifying the font
/*!
Returns the font for tab label text.
@return the font for tab label text
*/
- (CPFont)font
{
return _font;
}
/*!
Sets the font for tab label text to font.
@param font the font the receiver should use for tab label text
*/
- (void)setFont:(CPFont)font
{
if ([_font isEqual:font])
return;
_font = font;
[_tabs setFont:_font];
}
//
/*!
Sets the tab view type.
@param aTabViewType the view type
*/
- (void)setTabViewType:(CPTabViewType)aTabViewType
{
if (_type === aTabViewType)
return;
_type = aTabViewType;
if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
[_tabs removeFromSuperview];
else
[self addSubview:_tabs];
switch (_type)
{
case CPTopTabsBezelBorder:
case CPBottomTabsBezelBorder:
case CPNoTabsBezelBorder:
[_box setBorderType:CPBezelBorder];
break;
case CPNoTabsLineBorder:
[_box setBorderType:CPLineBorder];
break;
case CPNoTabsNoBorder:
[_box setBorderType:CPNoBorder];
break;
}
[self setNeedsLayout];
}
- (void)layoutSubviews
{
// Even if CPTabView's autoresizesSubviews is NO, _tabs and _box has to be laid out.
// This means we can't rely on autoresize masks.
if (_type !== CPTopTabsBezelBorder && _type !== CPBottomTabsBezelBorder)
{
[_box setFrame:[self bounds]];
}
else
{
var aFrame = [self frame],
segmentedHeight = _CGRectGetHeight([_tabs frame]),
origin = _type === CPTopTabsBezelBorder ? segmentedHeight / 2 : 0;
[_box setFrame:_CGRectMake(0, origin, _CGRectGetWidth(aFrame),
_CGRectGetHeight(aFrame) - segmentedHeight / 2)];
[self _repositionTabs];
}
}
/*!
Returns the tab view type.
@return the tab view type of the receiver
*/
- (CPTabViewType)tabViewType
{
return _type;
}
/*!
Returns the receiver's delegate.
@return the receiver's delegate
*/
- (id)delegate
{
return _delegate;
}
/*!
Sets the delegate for this tab view.
@param aDelegate the tab view's delegate
*/
- (void)setDelegate:(id)aDelegate
{
if (_delegate == aDelegate)
return;
_delegate = aDelegate;
_delegateSelectors = 0;
if ([_delegate respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)])
_delegateSelectors |= CPTabViewShouldSelectTabViewItemSelector;
if ([_delegate respondsToSelector:@selector(tabView:willSelectTabViewItem:)])
_delegateSelectors |= CPTabViewWillSelectTabViewItemSelector;
if ([_delegate respondsToSelector:@selector(tabView:didSelectTabViewItem:)])
_delegateSelectors |= CPTabViewDidSelectTabViewItemSelector;
if ([_delegate respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)])
_delegateSelectors |= CPTabViewDidChangeNumberOfTabViewItemsSelector;
}
- (void)setBackgroundColor:(CPColor)aColor
{
[_box setBackgroundColor:aColor];
}
- (CPColor)backgroundColor
{
return [_box backgroundColor];
}
- (void)mouseDown:(CPEvent)anEvent
{
var segmentIndex = [_tabs testSegment:[_tabs convertPoint:[anEvent locationInWindow] fromView:nil]];
if (segmentIndex != CPNotFound && [self selectTabViewItemAtIndex:segmentIndex])
[_tabs trackSegment:anEvent];
}
- (void)_repositionTabs
{
var horizontalCenterOfSelf = _CGRectGetWidth([self bounds]) / 2,
verticalCenterOfTabs = _CGRectGetHeight([_tabs bounds]) / 2;
if (_type === CPBottomTabsBezelBorder)
[_tabs setCenter:_CGPointMake(horizontalCenterOfSelf, _CGRectGetHeight([self bounds]) - verticalCenterOfTabs)];
else
[_tabs setCenter:_CGPointMake(horizontalCenterOfSelf, verticalCenterOfTabs)];
}
- (void)_setSelectedIndex:(CPNumber)index
{
_selectedIndex = index;
[self _setContentViewFromItem:[_items objectAtIndex:_selectedIndex]];
}
- (void)_setContentViewFromItem:(CPTabViewItem)anItem
{
[_box setContentView:[anItem view]];
}
- (void)_updateItems
{
var count = [_items count];
[_tabs setSegmentCount:count];
for (var i = 0; i < count; i++)
{
[_tabs setLabel:[[_items objectAtIndex:i] label] forSegment:i];
[_tabs setTag:i forSegment:i];
}
if (_selectedIndex === CPNotFound)
[self selectFirstTabViewItem:self];
}
@end
var CPTabViewItemsKey = "CPTabViewItemsKey",
CPTabViewSelectedItemKey = "CPTabViewSelectedItemKey",
CPTabViewTypeKey = "CPTabViewTypeKey",
CPTabViewFontKey = "CPTabViewFontKey",
CPTabViewDelegateKey = "CPTabViewDelegateKey";
@implementation CPTabView (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
{
if (self = [super initWithCoder:aCoder])
{
[self _init];
_font = [aCoder decodeObjectForKey:CPTabViewFontKey];
[_tabs setFont:_font];
_items = [aCoder decodeObjectForKey:CPTabViewItemsKey];
[_items makeObjectsPerformSelector:@selector(_setTabView:) withObject:self];
[self _updateItems];
[self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
self.selectOnAwake = [aCoder decodeObjectForKey:CPTabViewSelectedItemKey];
_type = [aCoder decodeIntForKey:CPTabViewTypeKey];
}
return self;
}
- (void)awakeFromCib
{
if (self.selectOnAwake)
{
[self selectTabViewItem:self.selectOnAwake];
delete self.selectOnAwake;
}
var type = _type;
_type = nil;
[self setTabViewType:type];
[self setNeedsLayout];
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeObject:_items forKey:CPTabViewItemsKey];
var selected = [self selectedTabViewItem];
if (selected)
[aCoder encodeObject:selected forKey:CPTabViewSelectedItemKey];
[aCoder encodeInt:_type forKey:CPTabViewTypeKey];
[aCoder encodeObject:_font forKey:CPTabViewFontKey];
[aCoder encodeConditionalObject:_delegate forKey:CPTabViewDelegateKey];
}
@end