Files
cappuccino/AppKit/CPToolbar.j
T

1150 lines
32 KiB
Plaintext

/*
* CPToolbar.j
* AppKit
*
* Portions based on NSToolbar.m (11/10/2008) in Cocotron (http://www.cocotron.org/)
* Copyright (c) 2006-2007 Christopher J. W. Lloyd
*
* Created by Francisco Tolmasky.
* Copyright 2008, 280 North, Inc.
*
* 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 <Foundation/CPObject.j>
@import "CPPopUpButton.j"
@import "CPToolbarItem.j"
/*
@global
@group CPToolbarDisplayMode
*/
CPToolbarDisplayModeDefault = 0;
/*
@global
@group CPToolbarDisplayMode
*/
CPToolbarDisplayModeIconAndLabel = 1;
/*
@global
@group CPToolbarDisplayMode
*/
CPToolbarDisplayModeIconOnly = 2;
/*
@global
@group CPToolbarDisplayMode
*/
CPToolbarDisplayModeLabelOnly = 3;
var CPToolbarsByIdentifier = nil,
CPToolbarConfigurationsByIdentifier = nil;
/*!
@ingroup appkit
@class CPToolbar
A CPToolbar is displayed at the top of a window with multiple
buttons (tools) that offer the user quick access to features.
@par Delegate Methods
@delegate -(CPArray)toolbarDefaultItemIdentifiers:(CPToolbar)toolbar;
Called to obtain the toolbar's default item identifiers. Required.
@param toolbar the toolbar to obtain identifiers for
@return an array of default item identifiers in the order on the toolbar
@delegate -(CPArray)toolbarAllowedItemIdentifiers:(CPToolbar)toolbar;
Called to obtain the toolbar's default item identifiers. Required.
@param toolbar the toolbar to obtain identifiers for
@return an array of default item identifiers in the order on the toolbar
@delegate - (CPToolbarItem)toolbar:(CPToolbar)toolbar itemForItemIdentifier:(CPString)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag;
Called to obtain a toolbar item. Required.
@param toolbar the toolbar the item belongs to
@param itemIdentifier the identifier of the toolbar item
@param flag \c YES means the item will be placed in the toolbar. \c NO means the item will be displayed for
some other purpose (non-functional)
@return the toolbar item or \c nil if no such item belongs in the toolbar
*/
@implementation CPToolbar : CPObject
{
CPString _identifier;
CPToolbarDisplayMode _displayMode;
BOOL _showsBaselineSeparator;
BOOL _allowsUserCustomization;
BOOL _isVisible;
id _delegate;
CPArray _itemIdentifiers;
CPDictionary _identifiedItems;
CPArray _defaultItems;
CPArray _allowedItems;
CPArray _selectableItems;
CPArray _items;
CPArray _itemsSortedByVisibilityPriority;
CPView _toolbarView;
CPWindow _window;
}
/* @ignore */
+ (void)initialize
{
if (self != [CPToolbar class])
return;
CPToolbarsByIdentifier = [CPDictionary dictionary];
CPToolbarConfigurationsByIdentifier = [CPDictionary dictionary];
}
/* @ignore */
+ (void)_addToolbar:(CPToolbar)toolbar forIdentifier:(CPString)identifier
{
var toolbarsSharingIdentifier = [CPToolbarsByIdentifier objectForKey:identifier];
if (!toolbarsSharingIdentifier)
{
toolbarsSharingIdentifier = []
[CPToolbarsByIdentifier setObject:toolbarsSharingIdentifier forKey:identifier];
}
[toolbarsSharingIdentifier addObject:toolbar];
}
- (id)init
{
return [self initWithIdentifier:@""];
}
/*!
Initializes the toolbar with the specified identifier.
@param anIdentifier the identifier for the toolbar
@return the initialized toolbar
*/
- (id)initWithIdentifier:(CPString)anIdentifier
{
self = [super init];
if (self)
{
_items = [];
_identifier = anIdentifier;
_isVisible = YES;
[CPToolbar _addToolbar:self forIdentifier:_identifier];
}
return self;
}
/*!
Sets the toolbar's display mode. NOT YET IMPLEMENTED.
*/
- (void)setDisplayMode:(CPToolbarDisplayMode)aDisplayMode
{
}
/*!
Returns the toolbar's identifier
*/
- (CPString)identifier
{
return _identifier;
}
/*!
Returns the toolbar's delegate
*/
- (id)delegate
{
return _delegate;
}
/*!
Returns \c YES if the toolbar is currently visible
*/
- (BOOL)isVisible
{
return _isVisible;
}
/*!
Sets whether the toolbar should be visible.
@param aFlag \c YES makes the toolbar visible
*/
- (void)setVisible:(BOOL)aFlag
{
if (_isVisible === aFlag)
return;
_isVisible = aFlag;
[_window _noteToolbarChanged];
}
- (CPWindow)_window
{
return _window;
}
- (void)_setWindow:(CPWindow)aWindow
{
_window = aWindow;
}
/*!
Sets the delegate for the toolbar.
@param aDelegate the new toolbar delegate
*/
- (void)setDelegate:(id)aDelegate
{
if (_delegate === aDelegate)
return;
_delegate = aDelegate;
[self _reloadToolbarItems];
}
/* @ignore */
- (void)_loadConfiguration
{
}
/* @ignore */
- (CPView)_toolbarView
{
if (!_toolbarView)
{
_toolbarView = [[_CPToolbarView alloc] initWithFrame:CPRectMake(0.0, 0.0, 1200.0, 59.0)];
[_toolbarView setToolbar:self];
[_toolbarView setAutoresizingMask:CPViewWidthSizable];
[_toolbarView reloadToolbarItems];
}
return _toolbarView;
}
/* @ignore */
- (void)_reloadToolbarItems
{
// As of OS X 10.5 (Leopard), toolbar items can be set in IB and a
// toolbar delegate is optional. Toolbar items can be combined from
// both IB and a delegate (see Apple's NSToolbar guide for IB, for more details).
// _defaultItems may have been loaded from Cib
_itemIdentifiers = [_defaultItems valueForKey:@"itemIdentifier"] || [];
if (_delegate)
{
var itemIdentifiersFromDelegate = [[_delegate toolbarDefaultItemIdentifiers:self] mutableCopy];
if (itemIdentifiersFromDelegate)
_itemIdentifiers = [_itemIdentifiers arrayByAddingObjectsFromArray:itemIdentifiersFromDelegate];
}
var index = 0,
count = [_itemIdentifiers count];
_items = [];
for (; index < count; ++index)
{
var identifier = _itemIdentifiers[index],
item = [CPToolbarItem _standardItemWithItemIdentifier:identifier];
// May come from a Cib.
if (!item)
item = [_identifiedItems objectForKey:identifier];
if (!item && _delegate)
item = [_delegate toolbar:self itemForItemIdentifier:identifier willBeInsertedIntoToolbar:YES];
item = [item copy];
if (item === nil)
[CPException raise:CPInvalidArgumentException
reason:@"Toolbar delegate " + _delegate + " returned nil toolbar item for identifier \"" + identifier + "\""];
item._toolbar = self;
[_items addObject:item];
}
// _items = [[self _defaultToolbarItems] mutableCopy];
// Store items sorted by priority. We want items to be removed first at the end of the array,
// items to be removed last at the front.
_itemsSortedByVisibilityPriority = [_items sortedArrayUsingFunction:_CPToolbarItemVisibilityPriorityCompare context:NULL];
[_toolbarView reloadToolbarItems];
}
/*!
Returns all the items in this toolbar.
*/
- (CPArray)items
{
return _items;
}
/*!
Returns all the visible items in this toolbar
*/
- (CPArray)visibleItems
{
return [_toolbarView visibleItems];
}
/*!
Returns the toolbar items sorted by their \c visibilityPriority(ies).
*/
- (CPArray)itemsSortedByVisibilityPriority
{
return _itemsSortedByVisibilityPriority;
}
/*!
Validates the visible toolbar items by sending a validate message to
each visible toolbar item.
*/
- (void)validateVisibleItems
{
var toolbarItems = [self visibleItems],
count = [toolbarItems count];
while (count--)
[toolbarItems[count] validate];
}
/* @ignore */
- (id)_itemForItemIdentifier:(CPString)identifier willBeInsertedIntoToolbar:(BOOL)toolbar
{
var item = [_identifiedItems objectForKey:identifier];
if (!item)
{
item = [CPToolbarItem _standardItemWithItemIdentifier:identifier];
if (_delegate && !item)
{
item = [[_delegate toolbar:self itemForItemIdentifier:identifier willBeInsertedIntoToolbar:toolbar] copy];
if (!item)
[CPException raise:CPInvalidArgumentException
reason:@"Toolbar delegate " + _delegate + " returned nil toolbar item for identifier " + identifier];
}
[_identifiedItems setObject:item forKey:identifier];
}
return item;
}
/* @ignore */
- (id)_itemsWithIdentifiers:(CPArray)identifiers
{
var items = [];
for (var i = 0; i < identifiers.length; i++)
[items addObject:[self _itemForItemIdentifier:identifiers[i] willBeInsertedIntoToolbar:NO]];
return items;
}
/* @ignore */
- (id)_defaultToolbarItems
{
if (!_defaultItems && [_delegate respondsToSelector:@selector(toolbarDefaultItemIdentifiers:)])
{
_defaultItems = [];
var identifiers = [_delegate toolbarDefaultItemIdentifiers:self],
index = 0,
count = [identifiers count];
for (; index < count; ++index)
[_defaultItems addObject:[self _itemForItemIdentifier:identifiers[index] willBeInsertedIntoToolbar:NO]];
}
return _defaultItems;
}
/*!
Notifies the toolbar that an item has been changed. This will cause the toolbar to reload its items.
@param anItem the item that has been changed
*/
- (void)toolbarItemDidChange:(CPToolbarItem)anItem
{
if ([_identifiedItems objectForKey:[anItem itemIdentifier]])
[_identifiedItems setObject:anItem forKey:[anItem itemIdentifier]];
var index = 0,
count = [_items count];
for (; index <= count; ++index)
{
var item = _items[index];
if ([item itemIdentifier] === [anItem itemIdentifier])
{
_items[index] = anItem;
_itemsSortedByVisibilityPriority = [_items sortedArrayUsingFunction:_CPToolbarItemVisibilityPriorityCompare context:NULL];
[_toolbarView reloadToolbarItems];
}
}
}
@end
var CPToolbarIdentifierKey = @"CPToolbarIdentifierKey",
CPToolbarDisplayModeKey = @"CPToolbarDisplayModeKey",
CPToolbarShowsBaselineSeparatorKey = @"CPToolbarShowsBaselineSeparatorKey",
CPToolbarAllowsUserCustomizationKey = @"CPToolbarAllowsUserCustomizationKey",
CPToolbarIsVisibleKey = @"CPToolbarIsVisibleKey",
CPToolbarDelegateKey = @"CPToolbarDelegateKey",
CPToolbarIdentifiedItemsKey = @"CPToolbarIdentifiedItemsKey",
CPToolbarDefaultItemsKey = @"CPToolbarDefaultItemsKey",
CPToolbarAllowedItemsKey = @"CPToolbarAllowedItemsKey",
CPToolbarSelectableItemsKey = @"CPToolbarSelectableItemsKey";
@implementation CPToolbar (CPCoding)
/*
Initializes the toolbar by unarchiving data from \c aCoder.
@param aCoder the coder containing the archived CPToolbar.
*/
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super init];
if (self)
{
_identifier = [aCoder decodeObjectForKey:CPToolbarIdentifierKey];
_displayMode = [aCoder decodeIntForKey:CPToolbarDisplayModeKey];
_showsBaselineSeparator = [aCoder decodeBoolForKey:CPToolbarShowsBaselineSeparatorKey];
_allowsUserCustomization = [aCoder decodeBoolForKey:CPToolbarAllowsUserCustomizationKey];
_isVisible = [aCoder decodeBoolForKey:CPToolbarIsVisibleKey];
_identifiedItems = [aCoder decodeObjectForKey:CPToolbarIdentifiedItemsKey];
_defaultItems = [aCoder decodeObjectForKey:CPToolbarDefaultItemsKey];
_allowedItems = [aCoder decodeObjectForKey:CPToolbarAllowedItemsKey];
_selectableItems = [aCoder decodeObjectForKey:CPToolbarSelectableItemsKey];
[[_identifiedItems allValues] makeObjectsPerformSelector:@selector(_setToolbar:) withObject:self];
_items = [];
[CPToolbar _addToolbar:self forIdentifier:_identifier];
// This won't come from a Cib, but can come from manual encoding.
[self setDelegate:[aCoder decodeObjectForKey:CPToolbarDelegateKey]];
// Because we don't know if a delegate will be set later (it is optional
// as of OS X 10.5), we need to call -_reloadToolbarItems here.
// In order to load any toolbar items that may have been configured in the
// Cib. Unfortunately this means that if there is a delegate
// specified, it will be read later and the resulting call to -setDelegate:
// will cause -_reloadToolbarItems] to run again :-(
// FIXME: Can we make this better?
// Do this at the end of the run loop to allow all the cib-stuff to
// finish (establishing connections, etc.).
[[CPRunLoop currentRunLoop]
performSelector:@selector(_reloadToolbarItems)
target:self
argument:nil
order:0 modes:[CPDefaultRunLoopMode]];
}
return self;
}
/*
Archives this toolbar into the provided coder.
@param aCoder the coder to which the toolbar's instance data will be written.
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeObject:_identifier forKey:CPToolbarIdentifierKey];
[aCoder encodeInt:_displayMode forKey:CPToolbarDisplayModeKey];
[aCoder encodeBool:_showsBaselineSeparator forKey:CPToolbarShowsBaselineSeparatorKey];
[aCoder encodeBool:_allowsUserCustomization forKey:CPToolbarAllowsUserCustomizationKey];
[aCoder encodeBool:_isVisible forKey:CPToolbarIsVisibleKey];
[aCoder encodeObject:_identifiedItems forKey:CPToolbarIdentifiedItemsKey];
[aCoder encodeObject:_defaultItems forKey:CPToolbarDefaultItemsKey];
[aCoder encodeObject:_allowedItems forKey:CPToolbarAllowedItemsKey];
[aCoder encodeObject:_selectableItems forKey:CPToolbarSelectableItemsKey];
[aCoder encodeConditionalObject:_delegate forKey:CPToolbarDelegateKey];
}
@end
var _CPToolbarViewBackgroundColor = nil,
_CPToolbarViewExtraItemsImage = nil,
_CPToolbarViewExtraItemsAlternateImage = nil;
var TOOLBAR_TOP_MARGIN = 5.0,
TOOLBAR_ITEM_MARGIN = 10.0,
TOOLBAR_EXTRA_ITEMS_WIDTH = 20.0;
var _CPToolbarItemInfoMake = function(anIndex, aView, aLabel, aMinWidth)
{
return { index:anIndex, view:aView, label:aLabel, minWidth:aMinWidth };
}
/* @ignore */
@implementation _CPToolbarView : CPView
{
CPToolbar _toolbar;
CPIndexSet _flexibleWidthIndexes;
CPIndexSet _visibleFlexibleWidthIndexes;
CPDictionary _itemInfos;
JSObject _viewsForToolbarItems;
CPArray _visibleItems @accessors(readonly, property=visibleItems);
CPArray _invisibleItems;
CPPopUpButton _additionalItemsButton;
CPColor _labelColor;
CPColor _labelShadowColor;
float _minWidth;
BOOL _FIXME_isHUD;
}
+ (void)initialize
{
if (self !== [_CPToolbarView class])
return;
var bundle = [CPBundle bundleForClass:self];
_CPToolbarViewExtraItemsImage = [[CPImage alloc] initWithContentsOfFile: [bundle pathForResource:"_CPToolbarView/_CPToolbarViewExtraItemsImage.png"] size: CPSizeMake(10.0, 15.0)];
_CPToolbarViewExtraItemsAlternateImage = [[CPImage alloc] initWithContentsOfFile: [bundle pathForResource:"_CPToolbarView/_CPToolbarViewExtraItemsAlternateImage.png"] size:CGSizeMake(10.0, 15.0)];
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
_minWidth = 0;
_labelColor = [CPColor blackColor];
_labelShadowColor = [CPColor colorWithWhite:1.0 alpha:0.75];
_additionalItemsButton = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 15.0) pullsDown:YES];
[_additionalItemsButton setBordered:NO];
[_additionalItemsButton setImagePosition:CPImageOnly];
[[_additionalItemsButton menu] setShowsStateColumn:NO];
[[_additionalItemsButton menu] setAutoenablesItems:NO];
[_additionalItemsButton setAlternateImage:_CPToolbarViewExtraItemsAlternateImage];
}
return self;
}
- (void)setToolbar:(CPToolbar)aToolbar
{
_toolbar = aToolbar;
}
- (CPToolbar)toolbar
{
return _toolbar;
}
- (void)FIXME_setIsHUD:(BOOL)shouldBeHUD
{
if (_FIXME_isHUD === shouldBeHUD)
return;
_FIXME_isHUD = shouldBeHUD;
var items = [_toolbar items],
count = [items count];
while (count--)
[[self viewForItem:items[count]] FIXME_setIsHUD:shouldBeHUD];
}
// This *should* be roughly O(3N) = O(N)
- (void)resizeSubviewsWithOldSize:(CGSize)aSize
{
[self tile];
}
- (_CPToolbarItemView)viewForItem:(CPToolbarItem)anItem
{
return _viewsForToolbarItems[[anItem UID]] || nil;
}
- (void)tile
{
// We begin by recalculating the visible items.
var items = [_toolbar items],
itemsWidth = CGRectGetWidth([self bounds]),
minWidth = _minWidth,
// FIXME: This should be a CPSet.
invisibleItemsSortedByPriority = [];
_visibleItems = items;
// We only have hidden items if our actual width is smaller than our
// minimum width for hiding items.
if (itemsWidth < minWidth)
{
itemsWidth -= TOOLBAR_EXTRA_ITEMS_WIDTH;
_visibleItems = [_visibleItems copy];
var itemsSortedByVisibilityPriority = [_toolbar itemsSortedByVisibilityPriority],
count = itemsSortedByVisibilityPriority.length;
// Remove items until we fit:
// The assumption here is that there are more visible items than there are
// invisible items, if not it would be faster to add items until we *no
// longer fit*.
while (minWidth > itemsWidth && count)
{
var item = itemsSortedByVisibilityPriority[--count],
view = [self viewForItem:item];
minWidth -= [view minSize].width + TOOLBAR_ITEM_MARGIN;
[_visibleItems removeObjectIdenticalTo:item];
[invisibleItemsSortedByPriority addObject:item];
[view setHidden:YES];
[view FIXME_setIsHUD:_FIXME_isHUD];
}
}
// FIXME: minHeight?
var count = [items count],
height = 0.0;
while (count--)
{
var view = [self viewForItem:items[count]],
minSize = [view minSize];
if (height < minSize.height)
height = minSize.height;
}
// Determine all the items that have flexible width.
// Also determine the height of the toolbar.
var count = _visibleItems.length,
flexibleItemIndexes = [CPIndexSet indexSet];
while (count--)
{
var item = _visibleItems[count],
view = [self viewForItem:item],
minSize = [view minSize];
if (minSize.width !== [view maxSize].width)
[flexibleItemIndexes addIndex:count];
// FIXME: Is this still necessary? (probably not since we iterate them all below).
// If the item doesn't have flexible width, then make sure it's set to the
// static width (min==max). This handles the case where the user did setView:
// with a view of a different size than minSize/maxSize
else
[view setFrameSize:CGSizeMake(minSize.width, height)];
[view setHidden:NO];
}
var remainingSpace = itemsWidth - minWidth,
proportionate = 0.0;
// Continue to distribute space proportionately while we have it,
// and there are flexible items left that want it. (Those with max
// widths may eventually not want it anymore).
while (remainingSpace && [flexibleItemIndexes count])
{
// Divy out the space.
proportionate += remainingSpace / [flexibleItemIndexes count];
// Reset the remaining space to 0
remainingSpace = 0.0;
var index = CPNotFound;
while ((index = [flexibleItemIndexes indexGreaterThanIndex:index]) !== CPNotFound)
{
var item = _visibleItems[index],
view = [self viewForItem:item],
proposedWidth = [view minSize].width + proportionate,
constrainedWidth = MIN(proposedWidth, [view maxSize].width);
if (constrainedWidth < proposedWidth)
{
[flexibleItemIndexes removeIndex:index];
remainingSpace += proposedWidth - constrainedWidth;
}
[view setFrameSize:CGSizeMake(constrainedWidth, height)];
}
}
// Now that all the visible items are the correct width, give them their final frames.
var index = 0,
count = _visibleItems.length,
x = TOOLBAR_ITEM_MARGIN;
for (; index < count; ++index)
{
var view = [self viewForItem:_visibleItems[index]],
viewWidth = CGRectGetWidth([view frame]);
[view setFrame:CGRectMake(x, 0.0, viewWidth, height)];
x += viewWidth + TOOLBAR_ITEM_MARGIN;
}
var needsAdditionalItemsButton = NO;
if ([invisibleItemsSortedByPriority count])
{
var index = 0,
count = [items count];
_invisibleItems = [];
for (; index < count; ++index)
{
var item = items[index];
if ([invisibleItemsSortedByPriority indexOfObjectIdenticalTo:item] !== CPNotFound)
{
[_invisibleItems addObject:item];
var identifier = [item itemIdentifier];
if (identifier !== CPToolbarSpaceItemIdentifier &&
identifier !== CPToolbarFlexibleSpaceItemIdentifier &&
identifier !== CPToolbarSeparatorItemIdentifier)
needsAdditionalItemsButton = YES;
}
}
}
if (needsAdditionalItemsButton)
{
[_additionalItemsButton setFrameOrigin:CGPointMake(itemsWidth + 5.0, (CGRectGetHeight([self bounds]) - CGRectGetHeight([_additionalItemsButton frame])) / 2.0)];
[self addSubview:_additionalItemsButton];
[_additionalItemsButton removeAllItems];
[_additionalItemsButton addItemWithTitle:@"Additional Items"];
[[_additionalItemsButton itemArray][0] setImage:_CPToolbarViewExtraItemsImage];
var index = 0,
count = [_invisibleItems count],
hasNonSeparatorItem = NO;
for (; index < count; ++index)
{
var item = _invisibleItems[index],
identifier = [item itemIdentifier];
if (identifier === CPToolbarSpaceItemIdentifier ||
identifier === CPToolbarFlexibleSpaceItemIdentifier)
continue;
if (identifier === CPToolbarSeparatorItemIdentifier)
{
if (hasNonSeparatorItem)
[_additionalItemsButton addItem:[CPMenuItem separatorItem]];
continue;
}
hasNonSeparatorItem = YES;
var menuItem = [[CPMenuItem alloc] initWithTitle:[item label] action:@selector(didSelectMenuItem:) keyEquivalent:nil];
[menuItem setRepresentedObject:item];
[menuItem setImage:[item image]];
[menuItem setTarget:self];
[menuItem setEnabled:[item isEnabled]];
[_additionalItemsButton addItem:menuItem];
}
}
else
[_additionalItemsButton removeFromSuperview];
}
/*
Used privately.
@ignore
*/
- (void)didSelectMenuItem:(id)aSender
{
var toolbarItem = [aSender representedObject];
[CPApp sendAction:[toolbarItem action] to:[toolbarItem target] from:toolbarItem];
}
- (void)reloadToolbarItems
{
// Get rid of all our current subviews.
var subviews = [self subviews],
count = subviews.length;
while (count--)
[subviews[count] removeFromSuperview];
// Populate with new subviews.
var items = [_toolbar items],
index = 0;
count = items.length;
_minWidth = TOOLBAR_ITEM_MARGIN;
_viewsForToolbarItems = { };
for (; index < count; ++index)
{
var item = items[index],
view = [[_CPToolbarItemView alloc] initWithToolbarItem:item toolbar:self];
_viewsForToolbarItems[[item UID]] = view;
if ([item toolTip] && [view respondsToSelector:@selector(setToolTip:)])
[view setToolTip:[item toolTip]];
[self addSubview:view];
_minWidth += [view minSize].width + TOOLBAR_ITEM_MARGIN;
}
[self tile];
}
@end
/* @ignore */
var _CPToolbarItemVisibilityPriorityCompare = function(lhs, rhs)
{
var lhsVisibilityPriority = [lhs visibilityPriority],
rhsVisibilityPriority = [rhs visibilityPriority];
if (lhsVisibilityPriority == rhsVisibilityPriority)
return CPOrderedSame;
if (lhsVisibilityPriority > rhsVisibilityPriority)
return CPOrderedAscending;
return CPOrderedDescending;
}
var TOP_MARGIN = 5.0,
LABEL_MARGIN = 2.0;
@implementation _CPToolbarItemView : CPControl
{
CGSize _minSize @accessors(readonly, property=minSize);
CGSize _maxSize @accessors(readonly, property=maxSize);
CGSize _labelSize;
CPToolbarItem _toolbarItem;
CPToolbar _toolbar;
CPImageView _imageView;
CPView _view;
CPTextField _labelField;
BOOL _FIXME_isHUD;
}
- (id)initWithToolbarItem:(CPToolbarItem)aToolbarItem toolbar:(CPToolbar)aToolbar
{
self = [super init];
if (self)
{
_toolbarItem = aToolbarItem;
_labelField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_labelField setFont:[CPFont systemFontOfSize:11.0]];
[_labelField setTextColor:[self FIXME_labelColor]];
[_labelField setTextShadowColor:[self FIXME_labelShadowColor]];
[_labelField setTextShadowOffset:CGSizeMake(0.0, 1.0)];
[_labelField setAutoresizingMask:CPViewWidthSizable | CPViewMinXMargin];
[self addSubview:_labelField];
[self updateFromItem];
_toolbar = aToolbar;
var keyPaths = [@"label", @"image", @"alternateImage", @"minSize", @"maxSize", @"target", @"action", @"enabled"],
index = 0,
count = [keyPaths count];
for (; index < count; ++index)
[_toolbarItem
addObserver:self
forKeyPath:keyPaths[index]
options:0
context:NULL];
}
return self;
}
- (void)FIXME_setIsHUD:(BOOL)shouldBeHUD
{
_FIXME_isHUD = shouldBeHUD;
[_labelField setTextColor:[self FIXME_labelColor]];
[_labelField setTextShadowColor:[self FIXME_labelShadowColor]];
}
- (void)updateFromItem
{
var identifier = [_toolbarItem itemIdentifier];
if (identifier === CPToolbarSpaceItemIdentifier ||
identifier === CPToolbarFlexibleSpaceItemIdentifier ||
identifier === CPToolbarSeparatorItemIdentifier)
{
[_view removeFromSuperview];
[_imageView removeFromSuperview];
_minSize = [_toolbarItem minSize];
_maxSize = [_toolbarItem maxSize];
if (identifier === CPToolbarSeparatorItemIdentifier)
{
_view = [[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 2.0, 32.0)];
// FIXME: Get rid of this old API!!!
sizes = {};
sizes[@"CPToolbarItemSeparator"] = [CGSizeMake(2.0, 26.0), CGSizeMake(2.0, 1.0), CGSizeMake(2.0, 26.0)];
[_view setBackgroundColor:_CPControlThreePartImagePattern(YES, sizes, @"CPToolbarItem", @"Separator")];
[self addSubview:_view];
}
return;
}
[self setTarget:[_toolbarItem target]];
[self setAction:[_toolbarItem action]];
var view = [_toolbarItem view] || nil;
if (view !== _view)
{
if (!view)
[_view removeFromSuperview];
else
{
[self addSubview:view];
[_imageView removeFromSuperview];
}
_view = view;
}
if (!_view)
{
if (!_imageView)
{
_imageView = [[CPImageView alloc] initWithFrame:[self bounds]];
[_imageView setImageScaling:CPScaleProportionally];
[self addSubview:_imageView];
}
[_imageView setImage:[_toolbarItem image]];
}
var minSize = [_toolbarItem minSize],
maxSize = [_toolbarItem maxSize];
[_labelField setStringValue:[_toolbarItem label]];
[_labelField sizeToFit]; // FIXME
[self setEnabled:[_toolbarItem isEnabled]];
_labelSize = [_labelField frame].size;
_minSize = CGSizeMake(MAX(_labelSize.width, minSize.width), _labelSize.height + minSize.height + LABEL_MARGIN + TOP_MARGIN);
_maxSize = CGSizeMake(MIN(_labelSize.width, maxSize.width), 100000000.0);
[_toolbar tile];
}
- (void)layoutSubviews
{
var identifier = [_toolbarItem itemIdentifier];
if (identifier === CPToolbarSpaceItemIdentifier ||
identifier === CPToolbarFlexibleSpaceItemIdentifier)
return;
var bounds = [self bounds],
width = _CGRectGetWidth(bounds);
if (identifier === CPToolbarSeparatorItemIdentifier)
return [_view setFrame:CGRectMake(ROUND((width - 2.0) / 2.0), 0.0, 2.0, _CGRectGetHeight(bounds))];
var view = _view || _imageView,
itemMaxSize = [_toolbarItem maxSize],
height = _CGRectGetHeight(bounds) - _labelSize.height - LABEL_MARGIN - TOP_MARGIN,
viewWidth = MIN(itemMaxSize.width, width),
viewHeight = MIN(itemMaxSize.height, height);
[view setFrame:CGRectMake( ROUND((width - viewWidth) / 2.0),
TOP_MARGIN + ROUND((height - viewHeight) / 2.0),
viewWidth,
viewHeight)];
[_labelField setFrameOrigin:CGPointMake(ROUND((width - _labelSize.width) / 2.0), TOP_MARGIN + height + LABEL_MARGIN)];
}
- (void)mouseDown:(CPEvent)anEvent
{
if ([_toolbarItem view])
return [[self nextResponder] mouseDown:anEvent];
var identifier = [_toolbarItem itemIdentifier];
if (identifier === CPToolbarSpaceItemIdentifier ||
identifier === CPToolbarFlexibleSpaceItemIdentifier ||
identifier === CPToolbarSeparatorItemIdentifier)
return [[self nextResponder] mouseDown:anEvent];
[super mouseDown:anEvent];
}
- (void)setEnabled:(BOOL)shouldBeEnabled
{
[super setEnabled:shouldBeEnabled];
if (shouldBeEnabled)
{
[_imageView setAlphaValue:1.0];
[_labelField setAlphaValue:1.0];
}
else
{
[_imageView setAlphaValue:0.5];
[_labelField setAlphaValue:0.5];
}
[_toolbar tile];
}
- (CPColor)FIXME_labelColor
{
if (_FIXME_isHUD)
return [CPColor whiteColor];
return [CPColor blackColor];
}
- (CPColor)FIXME_labelShadowColor
{
if (_FIXME_isHUD)
return [self isHighlighted] ? [CPColor colorWithWhite:1.0 alpha:0.5] : [CPColor clearColor];
return [self isHighlighted] ? [CPColor colorWithWhite:0.0 alpha:0.3] : [CPColor colorWithWhite:1.0 alpha:0.75];
}
- (void)setHighlighted:(BOOL)shouldBeHighlighted
{
[super setHighlighted:shouldBeHighlighted];
if (shouldBeHighlighted)
{
var alternateImage = [_toolbarItem alternateImage];
if (alternateImage)
[_imageView setImage:alternateImage];
[_labelField setTextShadowOffset:CGSizeMakeZero()];
}
else
{
var image = [_toolbarItem image];
if (image)
[_imageView setImage:image];
[_labelField setTextShadowOffset:CGSizeMake(0.0, 1.0)];
}
[_labelField setTextShadowColor:[self FIXME_labelShadowColor]];
}
- (void)sendAction:(SEL)anAction to:(id)aSender
{
[CPApp sendAction:anAction to:aSender from:_toolbarItem];
}
- (void)observeValueForKeyPath:(CPString)aKeyPath
ofObject:(id)anObject
change:(CPDictionary)aChange
context:(id)aContext
{
if (aKeyPath === "enabled")
[self setEnabled:[anObject isEnabled]];
else if (aKeyPath === @"target")
[self setTarget:[anObject target]];
else if (aKeyPath === @"action")
[self setAction:[anObject action]];
else
[self updateFromItem];
}
@end