mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
Previously, when changing the controlSize of a CPSegmentedControl, Cappuccino didn't have the theme to do that. Now, Cappuccino supports the controlSize small and mini. Aristo and Aristo2 have new attributes for that. The attribute theme "default-height" has been replaced by "min-size" and "max-size" (used everywhere else in Cappuccino). The CPSegmentedControl has now the theme attribute "nib2cib-adjustment-frame". This pull request fixed alignment issue when adding a CPSegmentedControl from xCode. In the method initWithCode, we didn't care about the divider thickness, now we do. This same method will also add leftovers pixel to have exactly the same size as in xCode. Leftovers pixels are here because FLOOR operation and size of the font. Manual test in Tests/Manual/CPSegmentedControlTest Manual test in Tests/Manual/Nib2CibAlignment
571 lines
15 KiB
Plaintext
571 lines
15 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 << 1,
|
|
CPTabViewShouldSelectTabViewItemSelector = 1 << 2,
|
|
CPTabViewWillSelectTabViewItemSelector = 1 << 3,
|
|
CPTabViewDidChangeNumberOfTabViewItemsSelector = 1 << 4;
|
|
|
|
|
|
@protocol CPTabViewDelegate <CPObject>
|
|
|
|
@optional
|
|
- (BOOL)tabView:(CPTabView)tabView shouldSelectTabViewItem:(CPTabViewItem)tabViewItem;
|
|
- (void)tabView:(CPTabView)tabView didSelectTabViewItem:(CPTabViewItem)tabViewItem;
|
|
- (void)tabView:(CPTabView)tabView willSelectTabViewItem:(CPTabViewItem)tabViewItem;
|
|
- (void)tabViewDidChangeNumberOfTabViewItems:(CPTabView)tabView;
|
|
|
|
@end
|
|
|
|
|
|
/*!
|
|
@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 <CPTabViewDelegate> _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:@"min-size"].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:(CPUInteger)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:(CPUInteger)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:(CPUInteger)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 _updateItems];
|
|
[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 <CPTabViewDelegate>)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 setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
|
|
|
|
self.selectOnAwake = [aCoder decodeObjectForKey:CPTabViewSelectedItemKey];
|
|
_type = [aCoder decodeIntForKey:CPTabViewTypeKey];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)awakeFromCib
|
|
{
|
|
// This cannot be run in initWithCoder because it might call selectTabViewItem:, which is
|
|
// not safe to call before the views of the tab views items are fully decoded.
|
|
[self _updateItems];
|
|
|
|
if (self.selectOnAwake)
|
|
{
|
|
[self selectTabViewItem:self.selectOnAwake];
|
|
delete self.selectOnAwake;
|
|
}
|
|
|
|
var type = _type;
|
|
_type = nil;
|
|
[self setTabViewType:type];
|
|
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
// Don't bother to encode the CPBox. We will recreate it on decode and its content view is already
|
|
// stored by the tab view item. Not encoding _box makes the resulting archive smaller and reduces
|
|
// the surface for decoding bugs (of which we've had many in tab view).
|
|
var subviews = [self subviews];
|
|
[_box removeFromSuperview];
|
|
[super encodeWithCoder:aCoder];
|
|
[self setSubviews:subviews];
|
|
|
|
[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
|