mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-09 20:27:13 +00:00
Compare commits
143
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
427f575926 | ||
|
|
541d46c67f | ||
|
|
ca86db0253 | ||
|
|
9f7611ce2b | ||
|
|
313e13c25f | ||
|
|
8bfb262a01 | ||
|
|
85284708ec | ||
|
|
d9a4e5eb6e | ||
|
|
1d0ff78e7b | ||
|
|
1ab9ba1543 | ||
|
|
326dc0ce29 | ||
|
|
d1ef3e5209 | ||
|
|
42706157b0 | ||
|
|
04f5c60059 | ||
|
|
7d72cf067a | ||
|
|
442623865b | ||
|
|
0c8db935a6 | ||
|
|
7f2da3adde | ||
|
|
e4d312d5e8 | ||
|
|
56a10a6d77 | ||
|
|
67b51bb5ad | ||
|
|
c8b958ae66 | ||
|
|
ac44e41337 | ||
|
|
0c08da4474 | ||
|
|
7cc2b08dd5 | ||
|
|
d989cfe171 | ||
|
|
ef609c6e9c | ||
|
|
65f7b4e940 | ||
|
|
c199f35feb | ||
|
|
ff7cb4bb6e | ||
|
|
996bff916e | ||
|
|
c31a5ec380 | ||
|
|
7655a832ef | ||
|
|
5f9e33a40d | ||
|
|
fdc656d489 | ||
|
|
c9496605bc | ||
|
|
6ce2eb1d19 | ||
|
|
d5a9611f6b | ||
|
|
eda6e5864d | ||
|
|
0712aac491 | ||
|
|
33d6c49de2 | ||
|
|
e5225fa317 | ||
|
|
19f811fb68 | ||
|
|
2ca437c214 | ||
|
|
ffed2c5a29 | ||
|
|
5283466f74 | ||
|
|
7aa75c56eb | ||
|
|
8342c836bc | ||
|
|
e767b193cd | ||
|
|
b9493d5fae | ||
|
|
1f632291d8 | ||
|
|
133c5753cc | ||
|
|
b2daee1c68 | ||
|
|
e9037ddc78 | ||
|
|
c8e76381a4 | ||
|
|
caf011563d | ||
|
|
df30933520 | ||
|
|
ae384104db | ||
|
|
1425a836ac | ||
|
|
a1a9361ae3 | ||
|
|
c371414260 | ||
|
|
21c68ed0ba | ||
|
|
1015ce3f87 | ||
|
|
1756c4e3c9 | ||
|
|
61948fc97c | ||
|
|
2ad41b2d02 | ||
|
|
41f5a89604 | ||
|
|
8ca9ae1e75 | ||
|
|
fa7a58b8fa | ||
|
|
791d2cf64d | ||
|
|
6d46a04866 | ||
|
|
adabd23726 | ||
|
|
7401993633 | ||
|
|
8a6011063a | ||
|
|
d01dcb2675 | ||
|
|
d8a30bc942 | ||
|
|
587d04f6d2 | ||
|
|
480d8cb9d0 | ||
|
|
84fa7bd21f | ||
|
|
1c5aa7b634 | ||
|
|
21b587e382 | ||
|
|
21ee91362f | ||
|
|
5e8b34eb96 | ||
|
|
8a8a9ad460 | ||
|
|
e0af640836 | ||
|
|
c27930f1eb | ||
|
|
4c43a9aba3 | ||
|
|
731a5b9e87 | ||
|
|
622a959d31 | ||
|
|
3a3986c458 | ||
|
|
5029403b89 | ||
|
|
84006c99ae | ||
|
|
55d07cc1ca | ||
|
|
c84f993374 | ||
|
|
6a97de40e1 | ||
|
|
b208cf5580 | ||
|
|
c3165fbb83 | ||
|
|
27e97d8f37 | ||
|
|
49c9b989b8 | ||
|
|
a3f0096e9d | ||
|
|
37740896b1 | ||
|
|
97e9796ceb | ||
|
|
ce0254c22c | ||
|
|
ee8f11c117 | ||
|
|
0680bb7a97 | ||
|
|
a5c3003065 | ||
|
|
dc50103988 | ||
|
|
95db3ab648 | ||
|
|
b8e9a9236e | ||
|
|
662942a68b | ||
|
|
525d4e0dcd | ||
|
|
0958d4bb33 | ||
|
|
11d185a86c | ||
|
|
a88f98f32f | ||
|
|
414d89fe0f | ||
|
|
331aa2633a | ||
|
|
11c7f2bc12 | ||
|
|
a2756ad879 | ||
|
|
2c82677a72 | ||
|
|
48559c47da | ||
|
|
ea38af7c77 | ||
|
|
befac199de | ||
|
|
4baad7c398 | ||
|
|
3d1ef0564d | ||
|
|
7dae79d6e4 | ||
|
|
c9246efb5c | ||
|
|
80bd2a4418 | ||
|
|
33d2d60a6a | ||
|
|
903d512084 | ||
|
|
ffc2cf1d6f | ||
|
|
3435d0ac20 | ||
|
|
ae26c00f42 | ||
|
|
8e3f988e89 | ||
|
|
9f7cea8605 | ||
|
|
151b7d503d | ||
|
|
1e04a7f818 | ||
|
|
ea7e183e92 | ||
|
|
a43faac2e6 | ||
|
|
8395f6fb04 | ||
|
|
7f49f0d168 | ||
|
|
868067c41b | ||
|
|
6c3c197411 | ||
|
|
25fd3a95af |
-12
@@ -1,12 +0,0 @@
|
||||
.DS_Store
|
||||
Frameworks
|
||||
Build
|
||||
Demos
|
||||
./Aristo
|
||||
WebSite
|
||||
.push-package
|
||||
*.xcodeproj/*.pbxuser
|
||||
*.xcodeproj/*.perspectivev3
|
||||
xcuserdata/
|
||||
!*.xcodeproj/project.pbxproj
|
||||
*.xCodeSupport/
|
||||
@@ -1,8 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.8
|
||||
before_install:
|
||||
- jdk_switcher use openjdk6
|
||||
install: ./bootstrap.sh --quiet --noprompt --directory ./narwhal
|
||||
script: jake test
|
||||
env: PATH=/home/travis/builds/cappuccino/cappuccino/narwhal/bin:$PATH CAPP_BUILD=/home/travis/builds/cappuccino/cappuccino/Build NARWHAL_ENGINE=rhino
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* AppKit.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "_CPToolTip.j"
|
||||
@import "CALayer.j"
|
||||
@import "CGGeometry.j"
|
||||
@import "CPAccordionView.j"
|
||||
@import "CPAlert.j"
|
||||
@import "CPAnimation.j"
|
||||
@import "CPApplication.j"
|
||||
@import "CPArrayController.j"
|
||||
@import "CPBezierPath.j"
|
||||
@import "CPBox.j"
|
||||
@import "CPBrowser.j"
|
||||
@import "CPButton.j"
|
||||
@import "CPButtonBar.j"
|
||||
@import "CPCheckBox.j"
|
||||
@import "CPCib.j"
|
||||
@import "CPCibBindingConnector.j"
|
||||
@import "CPCibConnector.j"
|
||||
@import "CPCibControlConnector.j"
|
||||
@import "CPCibHelpConnector.j"
|
||||
@import "CPCibLoading.j"
|
||||
@import "CPCibOutletConnector.j"
|
||||
@import "CPCibRuntimeAttributesConnector.j"
|
||||
@import "CPClipView.j"
|
||||
@import "CPCollectionView.j"
|
||||
@import "CPCollectionViewItem.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPColorPanel.j"
|
||||
@import "CPColorSpace.j"
|
||||
@import "CPColorWell.j"
|
||||
@import "CPComboBox.j"
|
||||
@import "CPCompatibility.j"
|
||||
@import "CPControl.j"
|
||||
@import "CPController.j"
|
||||
@import "CPCookie.j"
|
||||
@import "CPCursor.j"
|
||||
@import "CPDocument.j"
|
||||
@import "CPDocumentController.j"
|
||||
@import "CPEvent.j"
|
||||
@import "CPFlashMovie.j"
|
||||
@import "CPFlashView.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPFontManager.j"
|
||||
@import "CPGradient.j"
|
||||
@import "CPGraphics.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPKeyBinding.j"
|
||||
@import "CPLevelIndicator.j"
|
||||
@import "CPMenu.j"
|
||||
@import "CPMenuItem.j"
|
||||
@import "CPOpenPanel.j"
|
||||
@import "CPOutlineView.j"
|
||||
@import "CPPanel.j"
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPPopover.j"
|
||||
@import "CPPopUpButton.j"
|
||||
@import "CPPredicateEditor.j"
|
||||
@import "CPPredicateEditorRowTemplate.j"
|
||||
@import "CPProgressIndicator.j"
|
||||
@import "CPRadio.j"
|
||||
@import "CPResponder.j"
|
||||
@import "CPRuleEditor.j"
|
||||
@import "CPScroller.j"
|
||||
@import "CPScrollView.j"
|
||||
@import "CPSearchField.j"
|
||||
@import "CPSecureTextField.j"
|
||||
@import "CPSegmentedControl.j"
|
||||
@import "CPShadow.j"
|
||||
@import "CPSlider.j"
|
||||
@import "CPSound.j"
|
||||
@import "CPSplitView.j"
|
||||
@import "CPStepper.j"
|
||||
@import "CPTableColumn.j"
|
||||
@import "CPTableView.j"
|
||||
@import "CPTabView.j"
|
||||
@import "CPText.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPTokenField.j"
|
||||
@import "CPToolbar.j"
|
||||
@import "CPToolbarItem.j"
|
||||
@import "CPTreeNode.j"
|
||||
@import "CPUserDefaultsController.j"
|
||||
@import "CPView.j"
|
||||
@import "CPViewAnimation.j"
|
||||
@import "CPViewController.j"
|
||||
@import "CPWebView.j"
|
||||
@import "CPWindow.j"
|
||||
@import "CPWindowController.j"
|
||||
@import "CPWorkspace.j"
|
||||
@@ -1,416 +0,0 @@
|
||||
/*
|
||||
* CPAccordionView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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/CPArray.j>
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPKeyValueObserving.j>
|
||||
@import <Foundation/CPIndexSet.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CPView.j"
|
||||
@import "CPButton.j"
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPAccordionViewItem
|
||||
|
||||
<p>A CPAccordionViewItem represents a single section of a CPAccordionView.</p>
|
||||
*/
|
||||
@implementation CPAccordionViewItem : CPObject
|
||||
{
|
||||
CPString _identifier @accessors(property=identifier);
|
||||
CPView _view @accessors(property=view);
|
||||
CPString _label @accessors(property=label);
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithIdentifier:@""];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a new CPAccordionViewItem with the specified identifier. This identifier may
|
||||
then be used in subsequent methods to provide abstract introspection of the CPAccordionView.
|
||||
*/
|
||||
- (id)initWithIdentifier:(CPString)anIdentifier
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
[self setIdentifier:anIdentifier];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPAccordionView
|
||||
|
||||
<p>CPAccordionView provides a container for CPAccordionViewItem objects and manages layout state
|
||||
for all sub-layout items.</p>
|
||||
|
||||
<strong>Example</strong><br />
|
||||
<pre>
|
||||
var myAccordionView = [[CPAccordionView alloc] initWithFrame:CGRectMakeZero()];
|
||||
var firstItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"firstSection"]];
|
||||
[firstItem setView:[[CPView alloc] initWithFrame:CGRectMakeZero()]];
|
||||
var secondItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"secondSection"]];
|
||||
[secondItem setView:[[CPView alloc] initWithFrame:CGRectMakeZero()]];
|
||||
[myAccordionView addItem:firstItem];
|
||||
[myAccordionView addItem:secondItem];
|
||||
[myAccordionView setAutoresizingMask: CPViewWidthSizable | CPViewHeightSizable];
|
||||
</pre>
|
||||
*/
|
||||
@implementation CPAccordionView : CPView
|
||||
{
|
||||
CPInteger _dirtyItemIndex;
|
||||
CPView _itemHeaderPrototype;
|
||||
|
||||
CPMutableArray _items;
|
||||
CPMutableArray _itemViews;
|
||||
CPIndexSet _expandedItemIndexes;
|
||||
}
|
||||
/*!
|
||||
Initializes the receiver for usage with the specified bounding rectangle
|
||||
@return the initialized view
|
||||
*/
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_items = [];
|
||||
_itemViews = [];
|
||||
_expandedItemIndexes = [CPIndexSet indexSet];
|
||||
|
||||
[self setItemHeaderPrototype:[[CPButton alloc] initWithFrame:_CGRectMake(0.0, 0.0, 100.0, 24.0)]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setItemHeaderPrototype:(CPView)aView
|
||||
{
|
||||
_itemHeaderPrototype = aView;
|
||||
}
|
||||
|
||||
- (CPView)itemHeaderPrototype
|
||||
{
|
||||
return _itemHeaderPrototype;
|
||||
}
|
||||
|
||||
- (CPArray)items
|
||||
{
|
||||
return _items;
|
||||
}
|
||||
/*!
|
||||
Append a CPAccordionViewItem to the receiver. Note that the CPAccordionViewItem must contain
|
||||
a valid CPView derived component or a TypeError will be generated when the contents of the
|
||||
ViewItem are disclosed.
|
||||
*/
|
||||
- (void)addItem:(CPAccordionViewItem)anItem
|
||||
{
|
||||
[self insertItem:anItem atIndex:_items.length];
|
||||
}
|
||||
|
||||
- (void)insertItem:(CPAccordionViewItem)anItem atIndex:(CPInteger)anIndex
|
||||
{
|
||||
// FIXME: SHIFT ITEMS RIGHT
|
||||
[_expandedItemIndexes addIndex:anIndex];
|
||||
|
||||
var itemView = [[_CPAccordionItemView alloc] initWithAccordionView:self];
|
||||
|
||||
[itemView setIndex:anIndex];
|
||||
[itemView setLabel:[anItem label]];
|
||||
[itemView setContentView:[anItem view]];
|
||||
|
||||
[self addSubview:itemView];
|
||||
|
||||
[_items insertObject:anItem atIndex:anIndex];
|
||||
[_itemViews insertObject:itemView atIndex:anIndex];
|
||||
|
||||
[self _invalidateItemsStartingAtIndex:anIndex];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)removeItem:(CPAccordionViewItem)anItem
|
||||
{
|
||||
[self removeItemAtIndex:[_items indexOfObjectIdenticalTo:anItem]];
|
||||
}
|
||||
|
||||
- (void)removeItemAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
// SHIFT ITEMS LEFT
|
||||
[_expandedItemIndexes removeIndex:anIndex];
|
||||
|
||||
[_itemViews[anIndex] removeFromSuperview];
|
||||
|
||||
[_items removeObjectAtIndex:anIndex];
|
||||
[_itemViews removeObjectAtIndex:anIndex];
|
||||
|
||||
[self _invalidateItemsStartingAtIndex:anIndex];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)removeAllItems
|
||||
{
|
||||
var count = _items.length;
|
||||
|
||||
while (count--)
|
||||
[self removeItemAtIndex:count];
|
||||
}
|
||||
|
||||
- (void)expandItemAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
if (![_itemViews[anIndex] isCollapsed])
|
||||
return;
|
||||
|
||||
[_expandedItemIndexes addIndex:anIndex];
|
||||
[_itemViews[anIndex] setCollapsed:NO];
|
||||
|
||||
[self _invalidateItemsStartingAtIndex:anIndex];
|
||||
}
|
||||
|
||||
- (void)collapseItemAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
if ([_itemViews[anIndex] isCollapsed])
|
||||
return;
|
||||
|
||||
[_expandedItemIndexes removeIndex:anIndex];
|
||||
[_itemViews[anIndex] setCollapsed:YES];
|
||||
|
||||
[self _invalidateItemsStartingAtIndex:anIndex];
|
||||
}
|
||||
|
||||
- (void)toggleItemAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
var itemView = _itemViews[anIndex];
|
||||
|
||||
if ([itemView isCollapsed])
|
||||
[self expandItemAtIndex:anIndex];
|
||||
|
||||
else
|
||||
[self collapseItemAtIndex:anIndex];
|
||||
}
|
||||
|
||||
- (CPIndexSet)expandedItemIndexes
|
||||
{
|
||||
return _expandedItemIndexes;
|
||||
}
|
||||
|
||||
- (CPIndexSet)collapsedItemIndexes
|
||||
{
|
||||
var indexSet = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, _items.length)];
|
||||
|
||||
[indexSet removeIndexes:_expandedIndexes];
|
||||
|
||||
return indexSet;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)isEnabled forItemAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
var itemView = _itemViews[anIndex];
|
||||
if (!itemView)
|
||||
return;
|
||||
|
||||
if (!isEnabled)
|
||||
[self collapseItemAtIndex:anIndex];
|
||||
else
|
||||
[self expandItemAtIndex:anIndex];
|
||||
|
||||
[itemView setEnabled:isEnabled];
|
||||
}
|
||||
|
||||
- (void)_invalidateItemsStartingAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
if (_dirtyItemIndex === CPNotFound)
|
||||
_dirtyItemIndex = anIndex;
|
||||
|
||||
_dirtyItemIndex = MIN(_dirtyItemIndex, anIndex);
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
var width = _CGRectGetWidth([self frame]);
|
||||
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
if (width !== _CGRectGetWidth([self frame]))
|
||||
[self _invalidateItemsStartingAtIndex:0];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (_items.length <= 0)
|
||||
return [self setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), 0.0)];
|
||||
|
||||
if (_dirtyItemIndex === CPNotFound)
|
||||
return;
|
||||
|
||||
_dirtyItemIndex = MIN(_dirtyItemIndex, _items.length - 1);
|
||||
|
||||
var index = _dirtyItemIndex,
|
||||
count = _itemViews.length,
|
||||
width = _CGRectGetWidth([self bounds]),
|
||||
y = index > 0 ? CGRectGetMaxY([_itemViews[index - 1] frame]) : 0.0;
|
||||
|
||||
// Do this now (instead of after looping), so that if we are made dirty again in the middle we don't blow this value away.
|
||||
_dirtyItemIndex = CPNotFound;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var itemView = _itemViews[index];
|
||||
|
||||
[itemView setFrameY:y width:width];
|
||||
|
||||
y = CGRectGetMaxY([itemView frame]);
|
||||
}
|
||||
|
||||
[self setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), y)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPAccordionItemView : CPView
|
||||
{
|
||||
CPAccordionView _accordionView;
|
||||
|
||||
BOOL _isCollapsed @accessors(getter=isCollapsed, setter=setCollapsed:);
|
||||
CPInteger _index @accessors(property=index);
|
||||
CPView _headerView;
|
||||
CPView _contentView;
|
||||
}
|
||||
|
||||
- (id)initWithAccordionView:(CPAccordionView)anAccordionView
|
||||
{
|
||||
self = [super initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_accordionView = anAccordionView;
|
||||
_isCollapsed = NO;
|
||||
|
||||
var bounds = [self bounds];
|
||||
|
||||
_headerView = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:[_accordionView itemHeaderPrototype]]];
|
||||
|
||||
if ([_headerView respondsToSelector:@selector(setTarget:)] && [_headerView respondsToSelector:@selector(setAction:)])
|
||||
{
|
||||
[_headerView setTarget:self];
|
||||
[_headerView setAction:@selector(toggle:)];
|
||||
}
|
||||
|
||||
[self addSubview:_headerView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)toggle:(id)aSender
|
||||
{
|
||||
[_accordionView toggleItemAtIndex:[self index]];
|
||||
}
|
||||
|
||||
- (void)setLabel:(CPString)aLabel
|
||||
{
|
||||
if ([_headerView respondsToSelector:@selector(setTitle:)])
|
||||
[_headerView setTitle:aLabel];
|
||||
|
||||
else if ([_headerView respondsToSelector:@selector(setLabel:)])
|
||||
[_headerView setLabel:aLabel];
|
||||
|
||||
else if ([_headerView respondsToSelector:@selector(setStringValue:)])
|
||||
[_headerView setStringValue:aLabel];
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)isEnabled
|
||||
{
|
||||
if ([_headerView respondsToSelector:@selector(setEnabled:)])
|
||||
[_headerView setEnabled:isEnabled];
|
||||
}
|
||||
|
||||
- (void)setContentView:(CPView)aView
|
||||
{
|
||||
if (_contentView === aView)
|
||||
return;
|
||||
|
||||
[_contentView removeObserver:self forKeyPath:@"frame"];
|
||||
|
||||
[_contentView removeFromSuperview];
|
||||
|
||||
_contentView = aView;
|
||||
|
||||
[_contentView addObserver:self forKeyPath:@"frame" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:NULL];
|
||||
|
||||
[self addSubview:_contentView];
|
||||
|
||||
[_accordionView _invalidateItemsStartingAtIndex:[self index]];
|
||||
}
|
||||
|
||||
- (void)setFrameY:(float)aY width:(float)aWidth
|
||||
{
|
||||
var headerHeight = _CGRectGetHeight([_headerView frame]);
|
||||
|
||||
// Size to fit or something?
|
||||
[_headerView setFrameSize:_CGSizeMake(aWidth, headerHeight)];
|
||||
[_contentView setFrameOrigin:_CGPointMake(0.0, headerHeight)];
|
||||
|
||||
if ([self isCollapsed])
|
||||
[self setFrame:_CGRectMake(0.0, aY, aWidth, headerHeight)];
|
||||
|
||||
else
|
||||
{
|
||||
var contentHeight = _CGRectGetHeight([_contentView frame]);
|
||||
|
||||
[_contentView setFrameSize:_CGSizeMake(aWidth, contentHeight)];
|
||||
[self setFrame:_CGRectMake(0.0, aY, aWidth, contentHeight + headerHeight)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resizeSubviewsWithOldSize:(CGSize)aSize
|
||||
{
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath
|
||||
ofObject:(id)anObject
|
||||
change:(CPDictionary)aChange
|
||||
context:(id)aContext
|
||||
{
|
||||
if (aKeyPath === "frame" && !CGRectEqualToRect([aChange objectForKey:CPKeyValueChangeOldKey], [aChange objectForKey:CPKeyValueChangeNewKey]))
|
||||
[_accordionView _invalidateItemsStartingAtIndex:[self index]];
|
||||
/*
|
||||
else if (aKeyPath === "itemHeaderPrototype")
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,730 +0,0 @@
|
||||
/*
|
||||
* CPAlert.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Jake MacMullin.
|
||||
* Copyright 2008, Jake MacMullin.
|
||||
*
|
||||
* 11/10/2008 Ross Boucher
|
||||
* - Make it conform to style guidelines, general cleanup and enhancements
|
||||
* 11/10/2010 Antoine Mercadal
|
||||
* - Enhancements, better compliance with Cocoa API
|
||||
*
|
||||
* 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 <Foundation/CPString.j>
|
||||
|
||||
@import "CPApplication.j"
|
||||
@import "CPButton.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPPanel.j"
|
||||
@import "CPTextField.j"
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPAlertStyle
|
||||
*/
|
||||
CPWarningAlertStyle = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPAlertStyle
|
||||
*/
|
||||
CPInformationalAlertStyle = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPAlertStyle
|
||||
*/
|
||||
CPCriticalAlertStyle = 2;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
|
||||
CPAlert is an alert panel that can be displayed modally to present the
|
||||
user with a message and one or more options.
|
||||
|
||||
It can be used to display an information message \c CPInformationalAlertStyle,
|
||||
a warning message \c CPWarningAlertStyle (the default), or a critical
|
||||
alert \c CPCriticalAlertStyle. In each case the user can be presented with one
|
||||
or more options by adding buttons using the \c -addButtonWithTitle: method.
|
||||
|
||||
The panel is displayed modally by calling \c -runModal and once the user has
|
||||
dismissed the panel, a message will be sent to the panel's delegate (if set), informing
|
||||
it which button was clicked (see delegate methods).
|
||||
|
||||
@delegate -(void)alertDidEnd:(CPAlert)theAlert returnCode:(int)returnCode;
|
||||
Called when the user dismisses the alert by clicking one of the buttons.
|
||||
@param theAlert the alert panel that the user dismissed
|
||||
@param returnCode the index of the button that the user clicked (starting from 0,
|
||||
representing the first button added to the alert which appears on the
|
||||
right, 1 representing the next button to the left and so on)
|
||||
*/
|
||||
@implementation CPAlert : CPObject
|
||||
{
|
||||
BOOL _showHelp @accessors(property=showsHelp);
|
||||
BOOL _showSuppressionButton @accessors(property=showsSuppressionButton);
|
||||
|
||||
CPAlertStyle _alertStyle @accessors(property=alertStyle);
|
||||
CPString _title @accessors(property=title);
|
||||
CPView _accessoryView @accessors(property=accessoryView);
|
||||
CPImage _icon @accessors(property=icon);
|
||||
|
||||
CPArray _buttons @accessors(property=buttons,readonly);
|
||||
CPCheckBox _suppressionButton @accessors(property=suppressionButton,readonly);
|
||||
|
||||
id _delegate @accessors(property=delegate);
|
||||
id _modalDelegate;
|
||||
SEL _didEndSelector;
|
||||
|
||||
_CPAlertThemeView _themeView @accessors(property=themeView, readonly);
|
||||
CPWindow _window @accessors(property=window,readonly);
|
||||
int _defaultWindowStyle;
|
||||
|
||||
CPImageView _alertImageView;
|
||||
CPTextField _informativeLabel;
|
||||
CPTextField _messageLabel;
|
||||
CPButton _alertHelpButton;
|
||||
|
||||
BOOL _needsLayout;
|
||||
}
|
||||
|
||||
#pragma mark Creating Alerts
|
||||
|
||||
/*!
|
||||
Returns a CPAlert object with the provided info
|
||||
|
||||
@param aMessage the main body text of the alert
|
||||
@param defaultButton the title of the default button
|
||||
@param alternateButton if not nil, the title of a second button
|
||||
@param otherButton if not nil, the title of the third button
|
||||
@param informativeText if not nil the informative text of the alert
|
||||
@return fully initialized CPAlert
|
||||
*/
|
||||
+ (CPAlert)alertWithMessageText:(CPString)aMessage defaultButton:(CPString)defaultButtonTitle alternateButton:(CPString)alternateButtonTitle otherButton:(CPString)otherButtonTitle informativeTextWithFormat:(CPString)informativeText
|
||||
{
|
||||
var newAlert = [[self alloc] init];
|
||||
|
||||
[newAlert setMessageText:aMessage];
|
||||
[newAlert addButtonWithTitle:defaultButtonTitle];
|
||||
|
||||
if (alternateButtonTitle)
|
||||
[newAlert addButtonWithTitle:alternateButtonTitle];
|
||||
|
||||
if (otherButtonTitle)
|
||||
[newAlert addButtonWithTitle:otherButtonTitle];
|
||||
|
||||
if (informativeText)
|
||||
[newAlert setInformativeText:informativeText];
|
||||
|
||||
return newAlert;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return an CPAlert with type error
|
||||
|
||||
@param anErrorMessage the message of the alert
|
||||
@return fully initialized CPAlert
|
||||
*/
|
||||
+ (CPAlert)alertWithError:(CPString)anErrorMessage
|
||||
{
|
||||
var newAlert = [[self alloc] init];
|
||||
|
||||
[newAlert setMessageText:anErrorMessage];
|
||||
[newAlert setAlertStyle:CPCriticalAlertStyle];
|
||||
|
||||
return newAlert;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a \c CPAlert panel with the default alert style \c CPWarningAlertStyle.
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_buttons = [];
|
||||
_alertStyle = CPWarningAlertStyle;
|
||||
_showHelp = NO;
|
||||
_needsLayout = YES;
|
||||
_defaultWindowStyle = CPTitledWindowMask;
|
||||
_themeView = [_CPAlertThemeView new];
|
||||
|
||||
_messageLabel = [CPTextField labelWithTitle:@"Alert"];
|
||||
_alertImageView = [[CPImageView alloc] init];
|
||||
_informativeLabel = [[CPTextField alloc] init];
|
||||
_suppressionButton = [CPCheckBox checkBoxWithTitle:@"Do not show this message again"];
|
||||
|
||||
_alertHelpButton = [[CPButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 16.0, 16.0)];
|
||||
[_alertHelpButton setTarget:self];
|
||||
[_alertHelpButton setAction:@selector(_showHelp:)];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Accessors
|
||||
|
||||
- (CPTheme)theme
|
||||
{
|
||||
return [_themeView theme];
|
||||
}
|
||||
|
||||
/*!
|
||||
set the theme to use
|
||||
|
||||
@param the theme to use
|
||||
*/
|
||||
- (void)setTheme:(CPTheme)aTheme
|
||||
{
|
||||
if (aTheme === [self theme])
|
||||
return;
|
||||
|
||||
if (aTheme === [CPTheme defaultHudTheme])
|
||||
_defaultWindowStyle = CPTitledWindowMask | CPHUDBackgroundWindowMask;
|
||||
else
|
||||
_defaultWindowStyle = CPTitledWindowMask;
|
||||
|
||||
_window = nil; // will be regenerated at next layout
|
||||
_needsLayout = YES;
|
||||
[_themeView setTheme:aTheme];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
|
||||
{
|
||||
[_themeView setValue:aValue forThemeAttribute:aName];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName inState:(CPThemeState)aState
|
||||
{
|
||||
[_themeView setValue:aValue forThemeAttribute:aName inState:aState];
|
||||
}
|
||||
|
||||
|
||||
/*! @deprecated
|
||||
*/
|
||||
- (void)setWindowStyle:(int)aStyle
|
||||
{
|
||||
CPLog.warn("DEPRECATED: setWindowStyle: is deprecated. use setTheme: instead");
|
||||
[self setTheme:(aStyle === CPHUDBackgroundWindowMask) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]];
|
||||
}
|
||||
|
||||
/*! @deprecated
|
||||
*/
|
||||
- (int)windowStyle
|
||||
{
|
||||
CPLog.warn("DEPRECATED: windowStyle: is deprecated. use theme instead");
|
||||
return _defaultWindowStyle;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
set the text of the alert's message
|
||||
|
||||
@param aText CPString containing the text
|
||||
*/
|
||||
- (void)setMessageText:(CPString)aText
|
||||
{
|
||||
[_messageLabel setStringValue:aText];
|
||||
_needsLayout = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
return the content of the message text
|
||||
@return CPString containing the message text
|
||||
*/
|
||||
- (CPString)messageText
|
||||
{
|
||||
return [_messageLabel stringValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
set the text of the alert's informative text
|
||||
|
||||
@param aText CPString containing the informative text
|
||||
*/
|
||||
- (void)setInformativeText:(CPString)aText
|
||||
{
|
||||
[_informativeLabel setStringValue:aText];
|
||||
_needsLayout = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
return the content of the message text
|
||||
|
||||
@return CPString containing the message text
|
||||
*/
|
||||
- (CPString)informativeText
|
||||
{
|
||||
return [_informativeLabel stringValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the title of the alert window.
|
||||
This API is not present in Cocoa.
|
||||
@param aTitle CPString containing the window title
|
||||
*/
|
||||
- (void)setTitle:(CPString)aTitle
|
||||
{
|
||||
_title = aTitle;
|
||||
[_window setTitle:aTitle];
|
||||
}
|
||||
|
||||
/*!
|
||||
set the accessory view
|
||||
|
||||
@param aView the accessory view
|
||||
*/
|
||||
- (void)setAccessoryView:(CPView)aView
|
||||
{
|
||||
_accessoryView = aView;
|
||||
_needsLayout = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
set if alert shows the suppression button
|
||||
|
||||
@param shouldShowSuppressionButton YES or NO
|
||||
*/
|
||||
- (void)setShowsSuppressionButton:(BOOL)shouldShowSuppressionButton
|
||||
{
|
||||
_showSuppressionButton = shouldShowSuppressionButton;
|
||||
_needsLayout = YES;
|
||||
}
|
||||
|
||||
#pragma mark Accessing Buttons
|
||||
|
||||
/*!
|
||||
Adds a button with a given title to the receiver.
|
||||
Buttons will be added starting from the right hand side of the \c CPAlert panel.
|
||||
The first button will have the index 0, the second button 1 and so on.
|
||||
|
||||
The first button will automatically be given a key equivalent of Return,
|
||||
and any button titled "Cancel" will be given a key equivalent of Escape.
|
||||
|
||||
You really shouldn't need more than 3 buttons.
|
||||
|
||||
@param title the title of the button
|
||||
*/
|
||||
- (void)addButtonWithTitle:(CPString)aTitle
|
||||
{
|
||||
var bounds = [[_window contentView] bounds],
|
||||
count = [_buttons count],
|
||||
|
||||
button = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[button setTitle:aTitle];
|
||||
[button setTag:count];
|
||||
[button setTarget:self];
|
||||
[button setAction:@selector(_takeReturnCodeFrom:)];
|
||||
|
||||
[[_window contentView] addSubview:button];
|
||||
|
||||
if (count == 0)
|
||||
[button setKeyEquivalent:CPCarriageReturnCharacter];
|
||||
else if ([aTitle lowercaseString] === @"cancel")
|
||||
[button setKeyEquivalent:CPEscapeFunctionKey];
|
||||
|
||||
[_buttons insertObject:button atIndex:0];
|
||||
}
|
||||
|
||||
#pragma mark Layout
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_layoutMessageView
|
||||
{
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
sizeWithFontCorrection = 6.0,
|
||||
messageLabelWidth,
|
||||
messageLabelTextSize;
|
||||
|
||||
[_messageLabel setTextColor:[_themeView currentValueForThemeAttribute:@"message-text-color"]];
|
||||
[_messageLabel setFont:[_themeView currentValueForThemeAttribute:@"message-text-font"]];
|
||||
[_messageLabel setTextShadowColor:[_themeView currentValueForThemeAttribute:@"message-text-shadow-color"]];
|
||||
[_messageLabel setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"message-text-shadow-offset"]];
|
||||
[_messageLabel setAlignment:[_themeView currentValueForThemeAttribute:@"message-text-alignment"]];
|
||||
[_messageLabel setLineBreakMode:CPLineBreakByWordWrapping];
|
||||
|
||||
messageLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right;
|
||||
messageLabelTextSize = [[_messageLabel stringValue] sizeWithFont:[_messageLabel font] inWidth:messageLabelWidth];
|
||||
|
||||
[_messageLabel setFrame:CGRectMake(inset.left, inset.top, messageLabelTextSize.width, messageLabelTextSize.height + sizeWithFontCorrection)];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_layoutInformativeView
|
||||
{
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
sizeWithFontCorrection = 6.0,
|
||||
informativeLabelWidth,
|
||||
informativeLabelOriginY,
|
||||
informativeLabelTextSize;
|
||||
|
||||
[_informativeLabel setTextColor:[_themeView currentValueForThemeAttribute:@"informative-text-color"]];
|
||||
[_informativeLabel setFont:[_themeView currentValueForThemeAttribute:@"informative-text-font"]];
|
||||
[_informativeLabel setTextShadowColor:[_themeView currentValueForThemeAttribute:@"informative-text-shadow-color"]];
|
||||
[_informativeLabel setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"informative-text-shadow-offset"]];
|
||||
[_informativeLabel setAlignment:[_themeView currentValueForThemeAttribute:@"informative-text-alignment"]];
|
||||
[_informativeLabel setLineBreakMode:CPLineBreakByWordWrapping];
|
||||
|
||||
informativeLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right;
|
||||
informativeLabelOriginY = [_messageLabel frameOrigin].y + [_messageLabel frameSize].height + defaultElementsMargin;
|
||||
informativeLabelTextSize = [[_informativeLabel stringValue] sizeWithFont:[_informativeLabel font] inWidth:informativeLabelWidth];
|
||||
|
||||
[_informativeLabel setFrame:CGRectMake(inset.left, informativeLabelOriginY, informativeLabelTextSize.width, informativeLabelTextSize.height + sizeWithFontCorrection)];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_layoutAccessoryView
|
||||
{
|
||||
if (!_accessoryView)
|
||||
return;
|
||||
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
accessoryViewWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right,
|
||||
accessoryViewOriginY = CGRectGetMaxY([_informativeLabel frame]) + defaultElementsMargin;
|
||||
|
||||
[_accessoryView setFrameOrigin:CGPointMake(inset.left, accessoryViewOriginY)];
|
||||
[[_window contentView] addSubview:_accessoryView];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_layoutSuppressionButton
|
||||
{
|
||||
if (!_showSuppressionButton)
|
||||
return;
|
||||
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
suppressionViewXOffset = [_themeView currentValueForThemeAttribute:@"suppression-button-x-offset"],
|
||||
suppressionViewYOffset = [_themeView currentValueForThemeAttribute:@"suppression-button-y-offset"],
|
||||
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
suppressionButtonViewOriginY = CGRectGetMaxY([(_accessoryView || _informativeLabel) frame]) + defaultElementsMargin + suppressionViewYOffset;
|
||||
|
||||
[_suppressionButton setTextColor:[_themeView currentValueForThemeAttribute:@"suppression-button-text-color"]];
|
||||
[_suppressionButton setFont:[_themeView currentValueForThemeAttribute:@"suppression-button-text-font"]];
|
||||
[_suppressionButton setTextShadowColor:[_themeView currentValueForThemeAttribute:@"suppression-button-text-shadow-color"]];
|
||||
[_suppressionButton setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"suppression-button-text-shadow-offset"]];
|
||||
[_suppressionButton sizeToFit];
|
||||
|
||||
[_suppressionButton setFrameOrigin:CGPointMake(inset.left + suppressionViewXOffset, suppressionButtonViewOriginY)];
|
||||
[[_window contentView] addSubview:_suppressionButton];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (CGSize)_layoutButtonsFromView:(CPView)lastView
|
||||
{
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
minimumSize = [_themeView currentValueForThemeAttribute:@"size"],
|
||||
buttonOffset = [_themeView currentValueForThemeAttribute:@"button-offset"],
|
||||
helpLeftOffset = [_themeView currentValueForThemeAttribute:@"help-image-left-offset"],
|
||||
aRepresentativeButton = [_buttons objectAtIndex:0],
|
||||
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
panelSize = [[_window contentView] frame].size,
|
||||
buttonsOriginY,
|
||||
offsetX;
|
||||
|
||||
[aRepresentativeButton setTheme:[self theme]];
|
||||
[aRepresentativeButton sizeToFit];
|
||||
|
||||
panelSize.height = CGRectGetMaxY([lastView frame]) + defaultElementsMargin + [aRepresentativeButton frameSize].height;
|
||||
if (panelSize.height < minimumSize.height)
|
||||
panelSize.height = minimumSize.height;
|
||||
|
||||
buttonsOriginY = panelSize.height - [aRepresentativeButton frameSize].height + buttonOffset;
|
||||
offsetX = panelSize.width - inset.right;
|
||||
|
||||
for (var i = [_buttons count] - 1; i >= 0 ; i--)
|
||||
{
|
||||
var button = _buttons[i];
|
||||
[button setTheme:[self theme]];
|
||||
[button sizeToFit];
|
||||
|
||||
var buttonFrame = [button frame],
|
||||
width = MAX(80.0, CGRectGetWidth(buttonFrame)),
|
||||
height = CGRectGetHeight(buttonFrame);
|
||||
|
||||
offsetX -= width;
|
||||
[button setFrame:CGRectMake(offsetX, buttonsOriginY, width, height)];
|
||||
offsetX -= 10;
|
||||
}
|
||||
|
||||
if (_showHelp)
|
||||
{
|
||||
var helpImage = [_themeView currentValueForThemeAttribute:@"help-image"],
|
||||
helpImagePressed = [_themeView currentValueForThemeAttribute:@"help-image-pressed"],
|
||||
helpImageSize = helpImage ? [helpImage size] : CGSizeMakeZero(),
|
||||
helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY, helpImageSize.width, helpImageSize.height);
|
||||
|
||||
[_alertHelpButton setImage:helpImage];
|
||||
[_alertHelpButton setAlternateImage:helpImagePressed];
|
||||
[_alertHelpButton setBordered:NO];
|
||||
[_alertHelpButton setFrame:helpFrame];
|
||||
}
|
||||
|
||||
panelSize.height += [aRepresentativeButton frameSize].height + inset.bottom + buttonOffset;
|
||||
return panelSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)layout
|
||||
{
|
||||
if (!_needsLayout)
|
||||
return;
|
||||
|
||||
if (!_window)
|
||||
[self _createWindowWithStyle:nil];
|
||||
|
||||
var iconOffset = [_themeView currentValueForThemeAttribute:@"image-offset"],
|
||||
theImage = _icon,
|
||||
finalSize;
|
||||
|
||||
if (!theImage)
|
||||
switch (_alertStyle)
|
||||
{
|
||||
case CPWarningAlertStyle:
|
||||
theImage = [_themeView currentValueForThemeAttribute:@"warning-image"];
|
||||
break;
|
||||
case CPInformationalAlertStyle:
|
||||
theImage = [_themeView currentValueForThemeAttribute:@"information-image"];
|
||||
break;
|
||||
case CPCriticalAlertStyle:
|
||||
theImage = [_themeView currentValueForThemeAttribute:@"error-image"];
|
||||
break;
|
||||
}
|
||||
|
||||
[_alertImageView setImage:theImage];
|
||||
|
||||
var imageSize = theImage ? [theImage size] : CGSizeMakeZero();
|
||||
[_alertImageView setFrame:CGRectMake(iconOffset.x, iconOffset.y, imageSize.width, imageSize.height)];
|
||||
|
||||
[self _layoutMessageView];
|
||||
[self _layoutInformativeView];
|
||||
[self _layoutAccessoryView];
|
||||
[self _layoutSuppressionButton];
|
||||
|
||||
var lastView = _informativeLabel;
|
||||
if (_showSuppressionButton)
|
||||
lastView = _suppressionButton;
|
||||
else if (_accessoryView)
|
||||
lastView = _accessoryView
|
||||
|
||||
finalSize = [self _layoutButtonsFromView:lastView];
|
||||
if ([_window styleMask] & CPDocModalWindowMask)
|
||||
finalSize.height -= 26; // adjust the absence of title bar
|
||||
|
||||
[_window setFrameSize:finalSize];
|
||||
[_window center];
|
||||
|
||||
_needsLayout = NO;
|
||||
}
|
||||
|
||||
#pragma mark Displaying Alerts
|
||||
|
||||
/*!
|
||||
Displays the \c CPAlert panel as a modal dialog. The user will not be
|
||||
able to interact with any other controls until s/he has dismissed the alert
|
||||
by clicking on one of the buttons.
|
||||
*/
|
||||
- (void)runModal
|
||||
{
|
||||
if (!([_window styleMask] & _defaultWindowStyle))
|
||||
{
|
||||
_needsLayout = YES;
|
||||
[self _createWindowWithStyle:_defaultWindowStyle];
|
||||
}
|
||||
|
||||
[self layout];
|
||||
[CPApp runModalForWindow:_window];
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs the receiver modally as an alert sheet attached to a specified window.
|
||||
|
||||
@param window The parent window for the sheet.
|
||||
@param modalDelegate The delegate for the modal-dialog session.
|
||||
@param alertDidEndSelector Message the alert sends to modalDelegate after the sheet is dismissed.
|
||||
@param contextInfo Contextual data passed to modalDelegate in didEndSelector message.
|
||||
*/
|
||||
- (void)beginSheetModalForWindow:(CPWindow)aWindow modalDelegate:(id)modalDelegate didEndSelector:(SEL)alertDidEndSelector contextInfo:(id)contextInfo
|
||||
{
|
||||
if (!([_window styleMask] & CPDocModalWindowMask))
|
||||
{
|
||||
_needsLayout = YES;
|
||||
[self _createWindowWithStyle:CPDocModalWindowMask];
|
||||
}
|
||||
|
||||
[self layout];
|
||||
|
||||
_modalDelegate = modalDelegate;
|
||||
_didEndSelector = alertDidEndSelector;
|
||||
|
||||
[CPApp beginSheet:_window modalForWindow:aWindow modalDelegate:self didEndSelector:@selector(_alertDidEnd:returnCode:contextInfo:) contextInfo:contextInfo];
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs the receiver modally as an alert sheet attached to a specified window.
|
||||
|
||||
@param window The parent window for the sheet.
|
||||
*/
|
||||
- (void)beginSheetModalForWindow:(CPWindow)aWindow
|
||||
{
|
||||
[self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_createWindowWithStyle:(int)forceStyle
|
||||
{
|
||||
var frame = CGRectMakeZero();
|
||||
frame.size = [_themeView currentValueForThemeAttribute:@"size"];
|
||||
|
||||
_window = [[CPWindow alloc] initWithContentRect:frame styleMask:forceStyle || _defaultWindowStyle];
|
||||
[_window setLevel:CPStatusWindowLevel];
|
||||
|
||||
if (_title)
|
||||
[_window setTitle:_title];
|
||||
|
||||
var contentView = [_window contentView],
|
||||
count = [_buttons count];
|
||||
|
||||
if (count)
|
||||
while (count--)
|
||||
[contentView addSubview:_buttons[count]];
|
||||
else
|
||||
[self addButtonWithTitle:@"OK"];
|
||||
|
||||
[contentView addSubview:_messageLabel];
|
||||
[contentView addSubview:_alertImageView];
|
||||
[contentView addSubview:_informativeLabel];
|
||||
|
||||
if (_showHelp)
|
||||
[contentView addSubview:_alertHelpButton];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (@action)_showHelp:(id)aSender
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(alertShowHelp:)])
|
||||
[_delegate alertShowHelp:self];
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (@action)_takeReturnCodeFrom:(id)aSender
|
||||
{
|
||||
if ([_window isSheet])
|
||||
{
|
||||
[_window orderOut:nil];
|
||||
[CPApp endSheet:_window returnCode:[aSender tag]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[CPApp abortModal];
|
||||
[_window close];
|
||||
|
||||
[self _alertDidEnd:_window returnCode:[aSender tag] contextInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_alertDidEnd:(CPWindow)aWindow returnCode:(int)returnCode contextInfo:(id)contextInfo
|
||||
{
|
||||
if (_didEndSelector)
|
||||
objj_msgSend(_modalDelegate, _didEndSelector, self, returnCode, contextInfo);
|
||||
|
||||
_modalDelegate = nil;
|
||||
_didEndSelector = nil;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(alertDidEnd:returnCode:)])
|
||||
[_delegate alertDidEnd:self returnCode:returnCode];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPAlertThemeView : CPView
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"alert";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[CGSizeMake(400.0, 110.0), CGInsetMake(15, 15, 15, 50), 6, 10,
|
||||
CPJustifiedTextAlignment, [CPColor blackColor], [CPFont boldSystemFontOfSize:13.0], [CPNull null], CGSizeMakeZero(),
|
||||
CPJustifiedTextAlignment, [CPColor blackColor], [CPFont systemFontOfSize:12.0], [CPNull null], CGSizeMakeZero(),
|
||||
CGPointMake(15, 12),
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
0.0,
|
||||
0.0,
|
||||
3.0,
|
||||
[CPColor blackColor],
|
||||
[CPFont systemFontOfSize:12.0],
|
||||
[CPNull null],
|
||||
0.0
|
||||
]
|
||||
forKeys:[@"size", @"content-inset", @"informative-offset", @"button-offset",
|
||||
@"message-text-alignment", @"message-text-color", @"message-text-font", @"message-text-shadow-color", @"message-text-shadow-offset",
|
||||
@"informative-text-alignment", @"informative-text-color", @"informative-text-font", @"informative-text-shadow-color", @"informative-text-shadow-offset",
|
||||
@"image-offset",
|
||||
@"information-image",
|
||||
@"warning-image",
|
||||
@"error-image",
|
||||
@"help-image",
|
||||
@"help-image-left-offset",
|
||||
@"help-image-pressed",
|
||||
@"suppression-button-y-offset",
|
||||
@"suppression-button-x-offset",
|
||||
@"default-elements-margin",
|
||||
@"suppression-button-text-color",
|
||||
@"suppression-button-text-font",
|
||||
@"suppression-button-text-shadow-color",
|
||||
@"suppression-button-text-shadow-offset"
|
||||
]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,428 +0,0 @@
|
||||
/*
|
||||
* CPAnimation.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 <Foundation/CPTimer.j>
|
||||
|
||||
@import "CAMediaTimingFunction.j"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationEaseInOut = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationEaseIn = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationEaseOut = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationLinear = 3;
|
||||
|
||||
ACTUAL_FRAME_RATE = 0;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPAnimation
|
||||
|
||||
Manages an animation. Contains timing and progress information.
|
||||
|
||||
@par Delegate Methods
|
||||
|
||||
@delegate -(BOOL)animationShouldStart:(CPAnimation)animation;
|
||||
Called at the beginning of \c -startAnimation.
|
||||
@param animation the animation that will start
|
||||
@return \c YES allows the animation to start.
|
||||
\c NO stops the animation.
|
||||
|
||||
@delegate -(void)animationDidEnd:(CPAnimation)animation;
|
||||
Called when an animation has completed.
|
||||
@param animation the animation that completed
|
||||
|
||||
@delegate -(void)animationDidStop:(CPAnimation)animation;
|
||||
Called when the animation was stopped (before completing).
|
||||
@param animation the animation that was stopped
|
||||
|
||||
@delegate - (float)animation:(CPAnimation)animation valueForProgress:(float)progress;
|
||||
The value from this method will be returned when CPAnimation's
|
||||
\c currentValue method is called.
|
||||
@param animation the animation to obtain the curve value for
|
||||
@param progress the current animation progress
|
||||
@return the curve value
|
||||
*/
|
||||
@implementation CPAnimation : CPObject
|
||||
{
|
||||
CPTimeInterval _lastTime;
|
||||
CPTimeInterval _duration;
|
||||
|
||||
CPAnimationCurve _animationCurve;
|
||||
CAMediaTimingFunction _timingFunction;
|
||||
|
||||
float _frameRate;
|
||||
float _progress;
|
||||
|
||||
id _delegate;
|
||||
CPTimer _timer;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the animation with a duration and animation curve.
|
||||
@param aDuration the length of the animation
|
||||
@param anAnimationCurve defines the animation's pace
|
||||
@throws CPInvalidArgumentException if an invalid animation curve is specified
|
||||
*/
|
||||
- (id)initWithDuration:(float)aDuration animationCurve:(CPAnimationCurve)anAnimationCurve
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_progress = 0.0;
|
||||
_duration = MAX(0.0, aDuration);
|
||||
_frameRate = 60.0;
|
||||
|
||||
[self setAnimationCurve:anAnimationCurve];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation's pace.
|
||||
@param anAnimationCurve the animation's pace
|
||||
@throws CPInvalidArgumentException if an invalid animation curve is specified
|
||||
*/
|
||||
- (void)setAnimationCurve:(CPAnimationCurve)anAnimationCurve
|
||||
{
|
||||
var timingFunctionName;
|
||||
switch (anAnimationCurve)
|
||||
{
|
||||
case CPAnimationEaseInOut: timingFunctionName = kCAMediaTimingFunctionEaseInEaseOut;
|
||||
break;
|
||||
|
||||
case CPAnimationEaseIn: timingFunctionName = kCAMediaTimingFunctionEaseIn;
|
||||
break;
|
||||
|
||||
case CPAnimationEaseOut: timingFunctionName = kCAMediaTimingFunctionEaseOut;
|
||||
break;
|
||||
|
||||
case CPAnimationLinear: timingFunctionName = kCAMediaTimingFunctionLinear;
|
||||
break;
|
||||
|
||||
default: [CPException raise:CPInvalidArgumentException
|
||||
reason:"Invalid value provided for animation curve"];
|
||||
break;
|
||||
}
|
||||
|
||||
_animationCurve = anAnimationCurve;
|
||||
_timingFunction = [CAMediaTimingFunction functionWithName:timingFunctionName];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's pace
|
||||
*/
|
||||
- (CPAnimationCurve)animationCurve
|
||||
{
|
||||
return _animationCurve;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation's length.
|
||||
@param aDuration the new animation length
|
||||
@throws CPInvalidArgumentException if \c aDuration is negative
|
||||
*/
|
||||
- (void)setDuration:(CPTimeInterval)aDuration
|
||||
{
|
||||
if (aDuration < 0)
|
||||
[CPException raise:CPInvalidArgumentException reason:"aDuration can't be negative"];
|
||||
|
||||
_duration = aDuration;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the length of the animation.
|
||||
*/
|
||||
- (CPTimeInterval)duration
|
||||
{
|
||||
return _duration;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation frame rate. This is not a guaranteed frame rate. 0 means to go as fast as possible.
|
||||
@param frameRate the new desired frame rate
|
||||
@throws CPInvalidArgumentException if \c frameRate is negative
|
||||
*/
|
||||
- (void)setFrameRate:(float)frameRate
|
||||
{
|
||||
if (frameRate < 0)
|
||||
[CPException raise:CPInvalidArgumentException reason:"frameRate can't be negative"];
|
||||
|
||||
_frameRate = frameRate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the desired frame rate.
|
||||
*/
|
||||
- (float)frameRate
|
||||
{
|
||||
return _frameRate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's delegate
|
||||
*/
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation's delegate.
|
||||
@param aDelegate the new delegate
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Starts the animation. The method calls \c -animationShouldStart:
|
||||
on the delegate (if it implements it) to see if the animation
|
||||
should begin.
|
||||
*/
|
||||
- (void)startAnimation
|
||||
{
|
||||
// If we're already animating, or our delegate stops us, animate.
|
||||
if (_timer || _delegate && [_delegate respondsToSelector:@selector(animationShouldStart:)] && ![_delegate animationShouldStart:self])
|
||||
return;
|
||||
|
||||
if (_progress === 1.0)
|
||||
_progress = 0.0;
|
||||
|
||||
ACTUAL_FRAME_RATE = 0;
|
||||
_lastTime = new Date();
|
||||
|
||||
_timer = [CPTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(animationTimerDidFire:) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (void)animationTimerDidFire:(CPTimer)aTimer
|
||||
{
|
||||
var currentTime = new Date(),
|
||||
progress = MIN(1.0, [self currentProgress] + (currentTime - _lastTime) / (_duration * 1000.0));
|
||||
|
||||
_lastTime = currentTime;
|
||||
|
||||
++ACTUAL_FRAME_RATE;
|
||||
|
||||
[self setCurrentProgress:progress];
|
||||
|
||||
if (progress === 1.0)
|
||||
{
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animationDidEnd:)])
|
||||
[_delegate animationDidEnd:self];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Stops the animation before it has completed.
|
||||
*/
|
||||
- (void)stopAnimation
|
||||
{
|
||||
if (!_timer)
|
||||
return;
|
||||
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animationDidStop:)])
|
||||
[_delegate animationDidStop:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the animation
|
||||
is running.
|
||||
*/
|
||||
- (BOOL)isAnimating
|
||||
{
|
||||
return _timer;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation's progress.
|
||||
@param aProgress the animation's progress
|
||||
*/
|
||||
- (void)setCurrentProgress:(float)aProgress
|
||||
{
|
||||
_progress = aProgress;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's progress
|
||||
*/
|
||||
- (float)currentProgress
|
||||
{
|
||||
return _progress;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's timing progress.
|
||||
*/
|
||||
- (float)currentValue
|
||||
{
|
||||
var t = [self currentProgress];
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animation:valueForProgress:)])
|
||||
return [_delegate animation:self valueForProgress:t];
|
||||
|
||||
if (_animationCurve == CPAnimationLinear)
|
||||
return t;
|
||||
|
||||
var c1 = [],
|
||||
c2 = [];
|
||||
|
||||
[_timingFunction getControlPointAtIndex:1 values:c1];
|
||||
[_timingFunction getControlPointAtIndex:2 values:c2];
|
||||
|
||||
return CubicBezierAtTime(t, c1[0], c1[1], c2[0], c2[1], _duration);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// currently used function to determine time
|
||||
// 1:1 conversion to js from webkit source files
|
||||
// UnitBezier.h, WebCore_animation_AnimationBase.cpp
|
||||
var CubicBezierAtTime = function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration)
|
||||
{
|
||||
var ax = 0,
|
||||
bx = 0,
|
||||
cx = 0,
|
||||
ay = 0,
|
||||
by = 0,
|
||||
cy = 0;
|
||||
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
|
||||
function sampleCurveX(t)
|
||||
{
|
||||
return ((ax * t + bx) * t + cx) * t;
|
||||
}
|
||||
|
||||
function sampleCurveY(t)
|
||||
{
|
||||
return ((ay * t + by) * t + cy) * t;
|
||||
}
|
||||
|
||||
function sampleCurveDerivativeX(t)
|
||||
{
|
||||
return (3.0 * ax * t + 2.0 * bx) * t + cx;
|
||||
}
|
||||
|
||||
// The epsilon value to pass given that the animation is going to run over |duration| seconds. The longer the animation, the more precision is needed in the timing function result to avoid ugly discontinuities.
|
||||
function solveEpsilon(duration)
|
||||
{
|
||||
return 1.0 / (200.0 * duration);
|
||||
}
|
||||
|
||||
function solve(x, epsilon)
|
||||
{
|
||||
return sampleCurveY(solveCurveX(x, epsilon));
|
||||
}
|
||||
|
||||
// Given an x value, find a parametric value it came from.
|
||||
function solveCurveX(x, epsilon)
|
||||
{
|
||||
var t0,
|
||||
t1,
|
||||
t2 = x,
|
||||
x2,
|
||||
d2,
|
||||
i = 0;
|
||||
|
||||
// First try a few iterations of Newton's method -- normally very fast.
|
||||
for (; i < 8; i++)
|
||||
{
|
||||
x2 = sampleCurveX(t2) - x;
|
||||
|
||||
if (ABS(x2) < epsilon)
|
||||
return t2;
|
||||
|
||||
d2 = sampleCurveDerivativeX(t2);
|
||||
|
||||
if (ABS(d2) < 1e-6)
|
||||
break;
|
||||
|
||||
t2 = t2 - x2 / d2;
|
||||
}
|
||||
|
||||
// Fall back to the bisection method for reliability.
|
||||
t0 = 0.0;
|
||||
t1 = 1.0;
|
||||
t2 = x;
|
||||
|
||||
if (t2 < t0)
|
||||
return t0;
|
||||
|
||||
if (t2 > t1)
|
||||
return t1;
|
||||
|
||||
while (t0 < t1)
|
||||
{
|
||||
x2 = sampleCurveX(t2);
|
||||
|
||||
if (ABS(x2 - x) < epsilon)
|
||||
return t2;
|
||||
|
||||
if (x > x2)
|
||||
t0 = t2;
|
||||
|
||||
else
|
||||
t1 = t2;
|
||||
|
||||
t2 = (t1 - t0) * 0.5 + t0;
|
||||
}
|
||||
|
||||
return t2; // Failure.
|
||||
};
|
||||
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
|
||||
cx = 3.0 * p1x;
|
||||
bx = 3.0 * (p2x - p1x) - cx;
|
||||
ax = 1.0 - cx - bx;
|
||||
cy = 3.0 * p1y;
|
||||
by = 3.0 * (p2y - p1y) - cy;
|
||||
ay = 1.0 - cy - by;
|
||||
|
||||
// Convert from input time to parametric value in curve, then from that to output time.
|
||||
return solve(t, solveEpsilon(duration));
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
* CPBezierPath.j
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2009, 280 North, Inc.
|
||||
*
|
||||
* Adapted from Kevin Wojniak, portions Copyright 2009 Kevin Wojniak.
|
||||
*
|
||||
* 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
|
||||
* Copyright 2009 280 North, Inc.
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CPGraphicsContext.j"
|
||||
|
||||
|
||||
// Class defaults
|
||||
|
||||
var DefaultLineWidth = 1.0;
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPBezierPath
|
||||
|
||||
A CPBezierPath allows you to create paths for drawing to the screen using a simpler API than CoreGraphics.
|
||||
Paths can form any shape, including regular polygons like squares and triangles; circles, arcs; or complex
|
||||
line segments.
|
||||
|
||||
A path can be stroked and filled using the relevant method. The currently active fill and stroke color will
|
||||
be used, which can be set by calling setFill: and setStroke: on any CPColor object (or set: for both).
|
||||
*/
|
||||
|
||||
@implementation CPBezierPath : CPObject
|
||||
{
|
||||
CGPath _path;
|
||||
float _lineWidth;
|
||||
}
|
||||
|
||||
/*!
|
||||
Create a new CPBezierPath object.
|
||||
*/
|
||||
+ (CPBezierPath)bezierPath
|
||||
{
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
/*!
|
||||
Create a new CPBezierPath object initialized with an oval path drawn within a rectangular path.
|
||||
*/
|
||||
+ (CPBezierPath)bezierPathWithOvalInRect:(CGRect)rect
|
||||
{
|
||||
var path = [self bezierPath];
|
||||
|
||||
[path appendBezierPathWithOvalInRect:rect];
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/*!
|
||||
Create a new CPBezierPath object initialized with a rectangular path.
|
||||
*/
|
||||
+ (CPBezierPath)bezierPathWithRect:(CGRect)rect
|
||||
{
|
||||
var path = [self bezierPath];
|
||||
|
||||
[path appendBezierPathWithRect:rect];
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/*!
|
||||
Get default line width.
|
||||
*/
|
||||
+ (float)defaultLineWidth
|
||||
{
|
||||
return DefaultLineWidth;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set default line width.
|
||||
*/
|
||||
+ (void)setDefaultLineWidth:(float)width
|
||||
{
|
||||
DefaultLineWidth = width;
|
||||
}
|
||||
|
||||
/*!
|
||||
Fill rectangular path with current fill color.
|
||||
*/
|
||||
+ (void)fillRect:(CGRect)aRect
|
||||
{
|
||||
[[self bezierPathWithRect:aRect] fill];
|
||||
}
|
||||
|
||||
/*!
|
||||
Using the current stroke color and default drawing attributes, strokes a counterclockwise path beginning at the rectangle's origin.
|
||||
*/
|
||||
+ (void)strokeRect:(CGRect)aRect
|
||||
{
|
||||
[[self bezierPathWithRect:aRect] stroke];
|
||||
}
|
||||
|
||||
/*!
|
||||
Using the current stroke color and default drawing attributes, strokes a line between two points.
|
||||
*/
|
||||
+ (void)strokeLineFromPoint:(CGPoint)point1 toPoint:(CGPoint)point2
|
||||
{
|
||||
var path = [self bezierPath];
|
||||
|
||||
[path moveToPoint:point1];
|
||||
[path lineToPoint:point2];
|
||||
|
||||
[path stroke];
|
||||
}
|
||||
|
||||
/*!
|
||||
Create a new CPBezierPath object using the default line width.
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_path = CGPathCreateMutable();
|
||||
_lineWidth = [[self class] defaultLineWidth];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Moves the current point to another location.
|
||||
*/
|
||||
- (void)moveToPoint:(CGPoint)point
|
||||
{
|
||||
CGPathMoveToPoint(_path, nil, point.x, point.y);
|
||||
}
|
||||
|
||||
/*!
|
||||
Append a straight line to the path.
|
||||
*/
|
||||
- (void)lineToPoint:(CGPoint)point
|
||||
{
|
||||
CGPathAddLineToPoint(_path, nil, point.x, point.y);
|
||||
}
|
||||
|
||||
/*!
|
||||
Add a cubic Bezier curve to the path.
|
||||
*/
|
||||
- (void)curveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2
|
||||
{
|
||||
CGPathAddCurveToPoint(_path, nil, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y);
|
||||
}
|
||||
|
||||
- (CGRect)bounds
|
||||
{
|
||||
// TODO: this should return this. The controlPointBounds is not a tight fit.
|
||||
// return CGPathGetPathBoundingBox(_path);
|
||||
|
||||
return [self controlPointBounds];
|
||||
}
|
||||
|
||||
- (CGRect)controlPointBounds
|
||||
{
|
||||
return CGPathGetBoundingBox(_path);
|
||||
}
|
||||
|
||||
/*!
|
||||
Create a line segment between the first and last points in the subpath, closing it.
|
||||
*/
|
||||
- (void)closePath
|
||||
{
|
||||
CGPathCloseSubpath(_path);
|
||||
}
|
||||
|
||||
/*!
|
||||
Draw a line along the path with the current stroke color and default drawing attributes.
|
||||
*/
|
||||
- (void)stroke
|
||||
{
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextBeginPath(ctx);
|
||||
CGContextAddPath(ctx, _path);
|
||||
CGContextSetLineWidth(ctx, [self lineWidth]);
|
||||
CGContextStrokePath(ctx);
|
||||
}
|
||||
|
||||
/*!
|
||||
Fill the path with the current fill color.
|
||||
*/
|
||||
- (void)fill
|
||||
{
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextBeginPath(ctx);
|
||||
CGContextAddPath(ctx, _path);
|
||||
CGContextSetLineWidth(ctx, [self lineWidth]);
|
||||
CGContextClosePath(ctx);
|
||||
CGContextFillPath(ctx);
|
||||
}
|
||||
|
||||
/*!
|
||||
Get the line width.
|
||||
*/
|
||||
- (float)lineWidth
|
||||
{
|
||||
return _lineWidth;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the line width.
|
||||
*/
|
||||
- (void)setLineWidth:(float)lineWidth
|
||||
{
|
||||
_lineWidth = lineWidth;
|
||||
}
|
||||
|
||||
/*!
|
||||
Get the total number of elements.
|
||||
*/
|
||||
- (unsigned)elementCount
|
||||
{
|
||||
return _path.count;
|
||||
}
|
||||
|
||||
/*!
|
||||
Check if receiver is empty, returns appropriate Boolean value.
|
||||
*/
|
||||
- (BOOL)isEmpty
|
||||
{
|
||||
return CGPathIsEmpty(_path);
|
||||
}
|
||||
|
||||
/*!
|
||||
Get the current point.
|
||||
*/
|
||||
- (CGPoint)currentPoint
|
||||
{
|
||||
return CGPathGetCurrentPoint(_path);
|
||||
}
|
||||
|
||||
/*!
|
||||
Append a series of line segments.
|
||||
*/
|
||||
- (void)appendBezierPathWithPoints:(CPArray)points count:(unsigned)count
|
||||
{
|
||||
CGPathAddLines(_path, nil, points, count);
|
||||
}
|
||||
|
||||
/*!
|
||||
Append a rectangular path.
|
||||
*/
|
||||
- (void)appendBezierPathWithRect:(CGRect)rect
|
||||
{
|
||||
CGPathAddRect(_path, nil, rect);
|
||||
}
|
||||
|
||||
/*!
|
||||
Append an oval path; oval is drawn within the rectangular path.
|
||||
*/
|
||||
- (void)appendBezierPathWithOvalInRect:(CGRect)rect
|
||||
{
|
||||
CGPathAddPath(_path, nil, CGPathWithEllipseInRect(rect));
|
||||
}
|
||||
|
||||
/*!
|
||||
Append a rounded rectangular path.
|
||||
*/
|
||||
- (void)appendBezierPathWithRoundedRect:(CGRect)rect xRadius:(float)xRadius yRadius:(float)yRadius
|
||||
{
|
||||
CGPathAddPath(_path, nil, CGPathWithRoundedRectangleInRect(rect, xRadius, yRadius, YES, YES, YES, YES));
|
||||
}
|
||||
|
||||
- (void)appendBezierPathWithArcFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint radius:(float)radius
|
||||
{
|
||||
CGPathAddArcToPoint(_path, null, fromPoint.x, fromPoint.y, toPoint.x, toPoint.y, radius);
|
||||
}
|
||||
|
||||
/*!
|
||||
Append the contents of a CPBezierPath object.
|
||||
*/
|
||||
- (void)appendBezierPath:(CPBezierPath)other
|
||||
{
|
||||
CGPathAddPath(_path, nil, other._path);
|
||||
}
|
||||
|
||||
/*!
|
||||
Remove all path elements; clears path.
|
||||
*/
|
||||
- (void)removeAllPoints
|
||||
{
|
||||
_path = CGPathCreateMutable();
|
||||
}
|
||||
|
||||
- (void)addClip
|
||||
{
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextAddPath(ctx, _path);
|
||||
CGContextClip(ctx);
|
||||
}
|
||||
|
||||
- (void)setClip
|
||||
{
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextBeginPath(ctx);
|
||||
CGContextAddPath(ctx, _path);
|
||||
CGContextClip(ctx);
|
||||
}
|
||||
|
||||
@end
|
||||
-577
@@ -1,577 +0,0 @@
|
||||
/*
|
||||
* CPBox.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2009, 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 "CPView.j"
|
||||
|
||||
// CPBoxType
|
||||
CPBoxPrimary = 0;
|
||||
CPBoxSecondary = 1;
|
||||
CPBoxSeparator = 2;
|
||||
CPBoxOldStyle = 3;
|
||||
CPBoxCustom = 4;
|
||||
|
||||
// CPBorderType
|
||||
CPNoBorder = 0;
|
||||
CPLineBorder = 1;
|
||||
CPBezelBorder = 2;
|
||||
CPGrooveBorder = 3;
|
||||
|
||||
// CPTitlePosition
|
||||
CPNoTitle = 0;
|
||||
CPAboveTop = 1;
|
||||
CPAtTop = 2;
|
||||
CPBelowTop = 3;
|
||||
CPAboveBottom = 4;
|
||||
CPAtBottom = 5;
|
||||
CPBelowBottom = 6;
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPBox
|
||||
|
||||
A CPBox is a simple view which can display a border.
|
||||
*/
|
||||
@implementation CPBox : CPView
|
||||
{
|
||||
CPBoxType _boxType;
|
||||
CPBorderType _borderType;
|
||||
|
||||
CPColor _borderColor;
|
||||
CPColor _fillColor;
|
||||
|
||||
float _cornerRadius;
|
||||
float _borderWidth;
|
||||
|
||||
CPSize _contentMargin;
|
||||
CPView _contentView;
|
||||
|
||||
CPString _title @accessors(getter=title);
|
||||
int _titlePosition @accessors(getter=titlePosition);
|
||||
CPTextField _titleView;
|
||||
}
|
||||
|
||||
+ (id)boxEnclosingView:(CPView)aView
|
||||
{
|
||||
var box = [[self alloc] initWithFrame:CGRectMakeZero()],
|
||||
enclosingView = [aView superview];
|
||||
|
||||
[box setAutoresizingMask:[aView autoresizingMask]];
|
||||
[box setFrameFromContentFrame:[aView frame]];
|
||||
|
||||
[enclosingView replaceSubview:aView with:box];
|
||||
|
||||
[box setContentView:aView];
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)frameRect
|
||||
{
|
||||
self = [super initWithFrame:frameRect];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_borderType = CPBezelBorder;
|
||||
_fillColor = [CPColor clearColor];
|
||||
_borderColor = [CPColor blackColor];
|
||||
|
||||
_borderWidth = 1.0;
|
||||
_contentMargin = CGSizeMake(0.0, 0.0);
|
||||
|
||||
_titlePosition = CPNoTitle;
|
||||
_titleView = [CPTextField labelWithTitle:@""];
|
||||
|
||||
_contentView = [[CPView alloc] initWithFrame:[self bounds]];
|
||||
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
[self setAutoresizesSubviews:YES];
|
||||
[self addSubview:_contentView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Configuring Boxes
|
||||
|
||||
/*!
|
||||
Returns the receiver's border rectangle.
|
||||
|
||||
@return the border rectangle of the box
|
||||
*/
|
||||
- (CPRect)borderRect
|
||||
{
|
||||
return [self bounds];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's border type. Possible values are:
|
||||
|
||||
<pre>
|
||||
CPNoBorder
|
||||
CPLineBorder
|
||||
CPBezelBorder
|
||||
CPGrooveBorder
|
||||
</pre>
|
||||
|
||||
@return the border type of the box
|
||||
*/
|
||||
- (CPBorderType)borderType
|
||||
{
|
||||
return _borderType;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Sets the receiver's border type. Valid values are:
|
||||
|
||||
<pre>
|
||||
CPNoBorder
|
||||
CPLineBorder
|
||||
CPBezelBorder
|
||||
CPGrooveBorder
|
||||
</pre>
|
||||
|
||||
@param borderType the border type to use
|
||||
*/
|
||||
- (void)setBorderType:(CPBorderType)aBorderType
|
||||
{
|
||||
if (_borderType === aBorderType)
|
||||
return;
|
||||
|
||||
_borderType = aBorderType;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's box type. Possible values are:
|
||||
|
||||
<pre>
|
||||
CPBoxPrimary
|
||||
CPBoxSecondary
|
||||
CPBoxSeparator
|
||||
CPBoxOldStyle
|
||||
CPBoxCustom
|
||||
</pre>
|
||||
|
||||
(In the current implementation, all values act the same except CPBoxSeparator.)
|
||||
|
||||
@return the box type of the box.
|
||||
*/
|
||||
- (CPBoxType)boxType
|
||||
{
|
||||
return _boxType;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's box type. Valid values are:
|
||||
|
||||
<pre>
|
||||
CPBoxPrimary
|
||||
CPBoxSecondary
|
||||
CPBoxSeparator
|
||||
CPBoxOldStyle
|
||||
CPBoxCustom
|
||||
</pre>
|
||||
|
||||
(In the current implementation, all values act the same except CPBoxSeparator.)
|
||||
|
||||
@param aBoxType the box type of the box.
|
||||
*/
|
||||
- (void)setBoxType:(CPBoxType)aBoxType
|
||||
{
|
||||
if (_boxType === aBoxType)
|
||||
return;
|
||||
|
||||
_boxType = aBoxType;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (CPColor)borderColor
|
||||
{
|
||||
return _borderColor;
|
||||
}
|
||||
|
||||
- (void)setBorderColor:(CPColor)color
|
||||
{
|
||||
if ([color isEqual:_borderColor])
|
||||
return;
|
||||
|
||||
_borderColor = color;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (float)borderWidth
|
||||
{
|
||||
return _borderWidth;
|
||||
}
|
||||
|
||||
- (void)setBorderWidth:(float)width
|
||||
{
|
||||
if (width === _borderWidth)
|
||||
return;
|
||||
|
||||
_borderWidth = width;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (float)cornerRadius
|
||||
{
|
||||
return _cornerRadius;
|
||||
}
|
||||
|
||||
- (void)setCornerRadius:(float)radius
|
||||
{
|
||||
if (radius === _cornerRadius)
|
||||
return;
|
||||
|
||||
_cornerRadius = radius;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (CPColor)fillColor
|
||||
{
|
||||
return _fillColor;
|
||||
}
|
||||
|
||||
- (void)setFillColor:(CPColor)color
|
||||
{
|
||||
if ([color isEqual:_fillColor])
|
||||
return;
|
||||
|
||||
_fillColor = color;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (CPView)contentView
|
||||
{
|
||||
return _contentView;
|
||||
}
|
||||
|
||||
- (void)setContentView:(CPView)aView
|
||||
{
|
||||
if (aView === _contentView)
|
||||
return;
|
||||
|
||||
[aView setFrame:CGRectInset([self bounds], _contentMargin.width + _borderWidth, _contentMargin.height + _borderWidth)];
|
||||
[aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
[self replaceSubview:_contentView with:aView];
|
||||
|
||||
_contentView = aView;
|
||||
}
|
||||
|
||||
- (CPSize)contentViewMargins
|
||||
{
|
||||
return _contentMargin;
|
||||
}
|
||||
|
||||
- (void)setContentViewMargins:(CPSize)size
|
||||
{
|
||||
if (size.width < 0 || size.height < 0)
|
||||
[CPException raise:CPGenericException reason:@"Margins must be positive"];
|
||||
|
||||
_contentMargin = CGSizeMakeCopy(size);
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setFrameFromContentFrame:(CPRect)aRect
|
||||
{
|
||||
var offset = [self _titleHeightOffset];
|
||||
|
||||
[self setFrame:CGRectInset(aRect, -(_contentMargin.width + _borderWidth), -(_contentMargin.height + offset[0] + _borderWidth))];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setTitle:(CPString)aTitle
|
||||
{
|
||||
if (aTitle == _title)
|
||||
return;
|
||||
|
||||
_title = aTitle;
|
||||
|
||||
[self _manageTitlePositioning];
|
||||
}
|
||||
|
||||
- (void)setTitlePosition:(int)aTitlePotisition
|
||||
{
|
||||
if (aTitlePotisition == _titlePosition)
|
||||
return;
|
||||
|
||||
_titlePosition = aTitlePotisition;
|
||||
|
||||
[self _manageTitlePositioning];
|
||||
}
|
||||
|
||||
- (CPFont)titleFont
|
||||
{
|
||||
return [_titleView font];
|
||||
}
|
||||
|
||||
- (void)setTitleFont:(CPFont)aFont
|
||||
{
|
||||
[_titleView setFont:aFont];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the text field used to display the receiver's title.
|
||||
|
||||
This is the Cappuccino equivalent to the `titleCell` method.
|
||||
*/
|
||||
- (CPTextField)titleView
|
||||
{
|
||||
return _titleView;
|
||||
}
|
||||
|
||||
- (void)_manageTitlePositioning
|
||||
{
|
||||
if (_titlePosition == CPNoTitle)
|
||||
{
|
||||
[_titleView removeFromSuperview];
|
||||
[self setNeedsDisplay:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
[_titleView setStringValue:_title];
|
||||
[_titleView sizeToFit];
|
||||
[self addSubview:_titleView];
|
||||
|
||||
switch (_titlePosition)
|
||||
{
|
||||
case CPAtTop:
|
||||
case CPAboveTop:
|
||||
case CPBelowTop:
|
||||
[_titleView setFrameOrigin:CPPointMake(5.0, 0.0)];
|
||||
[_titleView setAutoresizingMask:CPViewNotSizable];
|
||||
break;
|
||||
|
||||
case CPAboveBottom:
|
||||
case CPAtBottom:
|
||||
case CPBelowBottom:
|
||||
var h = [_titleView frameSize].height;
|
||||
[_titleView setFrameOrigin:CPPointMake(5.0, [self frameSize].height - h)];
|
||||
[_titleView setAutoresizingMask:CPViewMinYMargin];
|
||||
break;
|
||||
}
|
||||
|
||||
[self sizeToFit];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)sizeToFit
|
||||
{
|
||||
var contentFrame = [_contentView frame],
|
||||
offset = [self _titleHeightOffset];
|
||||
|
||||
if (!contentFrame)
|
||||
return;
|
||||
|
||||
[_contentView setAutoresizingMask:CPViewNotSizable];
|
||||
[self setFrameSize:CGSizeMake(contentFrame.size.width + _contentMargin.width * 2,
|
||||
contentFrame.size.height + _contentMargin.height * 2 + offset[0])];
|
||||
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
[_contentView setFrameOrigin:CGPointMake(_contentMargin.width, _contentMargin.height + offset[1])];
|
||||
}
|
||||
|
||||
- (float)_titleHeightOffset
|
||||
{
|
||||
if (_titlePosition == CPNoTitle)
|
||||
return [0.0, 0.0];
|
||||
|
||||
switch (_titlePosition)
|
||||
{
|
||||
case CPAtTop:
|
||||
return [[_titleView frameSize].height, [_titleView frameSize].height];
|
||||
|
||||
case CPAtBottom:
|
||||
return [[_titleView frameSize].height, 0.0];
|
||||
|
||||
default:
|
||||
return [0.0, 0.0];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawRect:(CPRect)rect
|
||||
{
|
||||
if (_borderType === CPNoBorder)
|
||||
return;
|
||||
|
||||
var bounds = CGRectMakeCopy([self bounds]);
|
||||
|
||||
switch (_boxType)
|
||||
{
|
||||
case CPBoxSeparator:
|
||||
// NSBox does not include a horizontal flag for the separator type. We have to determine
|
||||
// the type of separator to draw by the width and height of the frame.
|
||||
if (CGRectGetWidth(bounds) === 5.0)
|
||||
return [self _drawVerticalSeperatorInRect:bounds];
|
||||
else if (CGRectGetHeight(bounds) === 5.0)
|
||||
return [self _drawHorizontalSeperatorInRect:bounds];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (_titlePosition == CPAtTop)
|
||||
{
|
||||
bounds.origin.y += [_titleView frameSize].height;
|
||||
bounds.size.height -= [_titleView frameSize].height;
|
||||
}
|
||||
if (_titlePosition == CPAtBottom)
|
||||
{
|
||||
bounds.size.height -= [_titleView frameSize].height;
|
||||
}
|
||||
|
||||
switch (_borderType)
|
||||
{
|
||||
case CPBezelBorder:
|
||||
[self _drawBezelBorderInRect:bounds];
|
||||
break;
|
||||
|
||||
default:
|
||||
case CPLineBorder:
|
||||
[self _drawLineBorderInRect:bounds];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_drawHorizontalSeperatorInRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextSetStrokeColor(context, [self borderColor]);
|
||||
CGContextSetLineWidth(context, 1.0);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(aRect), CGRectGetMinY(aRect) + 0.5);
|
||||
CGContextAddLineToPoint(context, CGRectGetWidth(aRect), CGRectGetMinY(aRect) + 0.5);
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void)_drawVerticalSeperatorInRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextSetStrokeColor(context, [self borderColor]);
|
||||
CGContextSetLineWidth(context, 1.0);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(aRect) + 0.5, CGRectGetMinY(aRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(aRect) + 0.5, CGRectGetHeight(aRect));
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void)_drawBezelBorderInRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
sides = [CPMinYEdge, CPMaxXEdge, CPMaxYEdge, CPMinXEdge],
|
||||
sideGray = 190.0 / 255.0,
|
||||
grays = [142.0 / 255.0, sideGray, sideGray, sideGray],
|
||||
borderWidth = _borderWidth;
|
||||
|
||||
while (borderWidth--)
|
||||
aRect = CPDrawTiledRects(aRect, aRect, sides, grays);
|
||||
|
||||
CGContextSetFillColor(context, [self fillColor]);
|
||||
CGContextFillRect(context, aRect);
|
||||
}
|
||||
|
||||
- (void)_drawLineBorderInRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
aRect = CGRectInset(aRect, _borderWidth / 2.0, _borderWidth / 2.0);
|
||||
|
||||
CGContextSetFillColor(context, [self fillColor]);
|
||||
CGContextSetStrokeColor(context, [self borderColor]);
|
||||
|
||||
CGContextSetLineWidth(context, _borderWidth);
|
||||
CGContextFillRoundedRectangleInRect(context, aRect, _cornerRadius, YES, YES, YES, YES);
|
||||
CGContextStrokeRoundedRectangleInRect(context, aRect, _cornerRadius, YES, YES, YES, YES);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPBoxTypeKey = @"CPBoxTypeKey",
|
||||
CPBoxBorderTypeKey = @"CPBoxBorderTypeKey",
|
||||
CPBoxBorderColorKey = @"CPBoxBorderColorKey",
|
||||
CPBoxFillColorKey = @"CPBoxFillColorKey",
|
||||
CPBoxCornerRadiusKey = @"CPBoxCornerRadiusKey",
|
||||
CPBoxBorderWidthKey = @"CPBoxBorderWidthKey",
|
||||
CPBoxContentMarginKey = @"CPBoxContentMarginKey",
|
||||
CPBoxTitle = @"CPBoxTitle",
|
||||
CPBoxTitlePosition = @"CPBoxTitlePosition",
|
||||
CPBoxTitleView = @"CPBoxTitleView";
|
||||
|
||||
@implementation CPBox (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_boxType = [aCoder decodeIntForKey:CPBoxTypeKey];
|
||||
_borderType = [aCoder decodeIntForKey:CPBoxBorderTypeKey];
|
||||
|
||||
_borderColor = [aCoder decodeObjectForKey:CPBoxBorderColorKey];
|
||||
_fillColor = [aCoder decodeObjectForKey:CPBoxFillColorKey];
|
||||
|
||||
_cornerRadius = [aCoder decodeFloatForKey:CPBoxCornerRadiusKey];
|
||||
_borderWidth = [aCoder decodeFloatForKey:CPBoxBorderWidthKey];
|
||||
|
||||
_contentMargin = [aCoder decodeSizeForKey:CPBoxContentMarginKey];
|
||||
|
||||
_title = [aCoder decodeObjectForKey:CPBoxTitle];
|
||||
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePosition];
|
||||
_titleView = [aCoder decodeObjectForKey:CPBoxTitleView] || [CPTextField labelWithTitle:_title];
|
||||
|
||||
_contentView = [self subviews][0];
|
||||
|
||||
[self setAutoresizesSubviews:YES];
|
||||
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
[self _manageTitlePositioning];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeInt:_boxType forKey:CPBoxTypeKey];
|
||||
[aCoder encodeInt:_borderType forKey:CPBoxBorderTypeKey];
|
||||
|
||||
[aCoder encodeObject:_borderColor forKey:CPBoxBorderColorKey];
|
||||
[aCoder encodeObject:_fillColor forKey:CPBoxFillColorKey];
|
||||
|
||||
[aCoder encodeFloat:_cornerRadius forKey:CPBoxCornerRadiusKey];
|
||||
[aCoder encodeFloat:_borderWidth forKey:CPBoxBorderWidthKey];
|
||||
|
||||
[aCoder encodeObject:_title forKey:CPBoxTitle];
|
||||
[aCoder encodeInt:_titlePosition forKey:CPBoxTitlePosition];
|
||||
[aCoder encodeObject:_titleView forKey:CPBoxTitleView];
|
||||
|
||||
[aCoder encodeSize:_contentMargin forKey:CPBoxContentMarginKey];
|
||||
}
|
||||
|
||||
@end
|
||||
-1015
File diff suppressed because it is too large
Load Diff
@@ -1,993 +0,0 @@
|
||||
/*
|
||||
* CPButton.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "_CPImageAndTextView.j"
|
||||
@import "CGGeometry.j"
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPStringDrawing.j"
|
||||
|
||||
|
||||
/* @group CPBezelStyle */
|
||||
|
||||
// IB style
|
||||
CPRoundedBezelStyle = 1; // Push
|
||||
CPRegularSquareBezelStyle = 2; // Bevel
|
||||
CPThickSquareBezelStyle = 3;
|
||||
CPThickerSquareBezelStyle = 4;
|
||||
CPDisclosureBezelStyle = 5; // Disclosure triangle
|
||||
CPShadowlessSquareBezelStyle = 6; // Square
|
||||
CPCircularBezelStyle = 7; // Round
|
||||
CPTexturedSquareBezelStyle = 8; // Textured
|
||||
CPHelpButtonBezelStyle = 9; // Help
|
||||
CPSmallSquareBezelStyle = 10; // Gradient
|
||||
CPTexturedRoundedBezelStyle = 11; // Round Textured
|
||||
CPRoundRectBezelStyle = 12; // Round Rect
|
||||
CPRecessedBezelStyle = 13; // Recessed
|
||||
CPRoundedDisclosureBezelStyle = 14; // Disclosure
|
||||
CPHUDBezelStyle = -1;
|
||||
|
||||
|
||||
/* @group CPButtonType */
|
||||
CPMomentaryLightButton = 0;
|
||||
CPPushOnPushOffButton = 1;
|
||||
CPToggleButton = 2;
|
||||
CPSwitchButton = 3; // Deprecated, use CPCheckBox instead.
|
||||
CPRadioButton = 4; // Deprecated, use CPRadio instead.
|
||||
CPMomentaryChangeButton = 5;
|
||||
CPOnOffButton = 6;
|
||||
CPMomentaryPushInButton = 7;
|
||||
CPMomentaryPushButton = 0;
|
||||
CPMomentaryLight = 7;
|
||||
|
||||
CPNoButtonMask = 0;
|
||||
CPContentsButtonMask = 1;
|
||||
CPPushInButtonMask = 2;
|
||||
CPGrayButtonMask = 4;
|
||||
CPBackgroundButtonMask = 8;
|
||||
|
||||
CPNoCellMask = CPNoButtonMask;
|
||||
CPContentsCellMask = CPContentsButtonMask;
|
||||
CPPushInCellMask = CPPushInButtonMask;
|
||||
CPChangeGrayCellMask = CPGrayButtonMask;
|
||||
CPChangeBackgroundCellMask = CPBackgroundButtonMask;
|
||||
|
||||
CPButtonStateMixed = CPThemeState("mixed");
|
||||
CPButtonStateBezelStyleRounded = CPThemeState("rounded");
|
||||
|
||||
// add all future correspondance between bezel styles and theme state here.
|
||||
var CPButtonBezelStyleStateMap = [CPDictionary dictionaryWithObjects:[CPButtonStateBezelStyleRounded, nil]
|
||||
forKeys:[CPRoundedBezelStyle, CPRoundRectBezelStyle]];
|
||||
|
||||
|
||||
CPButtonDefaultHeight = 24.0;
|
||||
CPButtonImageOffset = 3.0;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPButton
|
||||
|
||||
CPButton is a subclass of CPControl that
|
||||
intercepts mouse-down events and sends an action message to a
|
||||
target object when it's clicked or pressed.
|
||||
*/
|
||||
@implementation CPButton : CPControl
|
||||
{
|
||||
BOOL _allowsMixedState;
|
||||
|
||||
CPString _title;
|
||||
CPString _alternateTitle;
|
||||
|
||||
CPInteger _showsStateBy;
|
||||
CPInteger _highlightsBy;
|
||||
BOOL _imageDimsWhenDisabled;
|
||||
|
||||
// NS-style Display Properties
|
||||
CPBezelStyle _bezelStyle;
|
||||
CPControlSize _controlSize;
|
||||
|
||||
CPString _keyEquivalent;
|
||||
unsigned _keyEquivalentModifierMask;
|
||||
|
||||
CPTimer _continuousDelayTimer;
|
||||
CPTimer _continuousTimer;
|
||||
float _periodicDelay;
|
||||
float _periodicInterval;
|
||||
|
||||
BOOL _isTracking;
|
||||
}
|
||||
|
||||
+ (id)buttonWithTitle:(CPString)aTitle
|
||||
{
|
||||
return [self buttonWithTitle:aTitle theme:[CPTheme defaultTheme]];
|
||||
}
|
||||
|
||||
+ (id)buttonWithTitle:(CPString)aTitle theme:(CPTheme)aTheme
|
||||
{
|
||||
var button = [[self alloc] init];
|
||||
|
||||
[button setTheme:aTheme];
|
||||
[button setTitle:aTitle];
|
||||
[button sizeToFit];
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"button";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[[CPNull null], 0.0, _CGInsetMakeZero(), _CGInsetMakeZero(), [CPNull null]]
|
||||
forKeys:[@"image", @"image-offset", @"bezel-inset", @"content-inset", @"bezel-color"]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes and returns a newly allocated CPButton object with a specified frame rectangle.
|
||||
@param aFrame The frame rectangle for the created button object.
|
||||
@return An initialized CPView object or nil if the object couldn't be created.
|
||||
*/
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
// Should we instead override the defaults?
|
||||
[self setValue:CPCenterTextAlignment forThemeAttribute:@"alignment"];
|
||||
[self setValue:CPCenterVerticalTextAlignment forThemeAttribute:@"vertical-alignment"];
|
||||
[self setValue:CPImageLeft forThemeAttribute:@"image-position"];
|
||||
[self setValue:CPImageScaleNone forThemeAttribute:@"image-scaling"];
|
||||
|
||||
[self setBezelStyle:CPRoundRectBezelStyle];
|
||||
[self setBordered:YES];
|
||||
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
_controlSize = CPRegularControlSize;
|
||||
|
||||
_keyEquivalent = @"";
|
||||
_keyEquivalentModifierMask = 0;
|
||||
|
||||
// Continuous button defaults.
|
||||
_periodicInterval = 0.05;
|
||||
_periodicDelay = 0.5;
|
||||
|
||||
[self setButtonType:CPMomentaryPushInButton];
|
||||
}
|
||||
|
||||
// Setting the state
|
||||
/*!
|
||||
Returns a Boolean value indicating whether the button allows a mixed state.
|
||||
@return \c YES if the button has a 'mixed' state in addition to on and off.
|
||||
*/
|
||||
- (BOOL)allowsMixedState
|
||||
{
|
||||
return _allowsMixedState;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the button can have a 'mixed' state.
|
||||
@param aFlag specifies whether a 'mixed' state is allowed or not
|
||||
*/
|
||||
- (void)setAllowsMixedState:(BOOL)aFlag
|
||||
{
|
||||
aFlag = !!aFlag;
|
||||
|
||||
if (_allowsMixedState === aFlag)
|
||||
return;
|
||||
|
||||
_allowsMixedState = aFlag;
|
||||
|
||||
if (!_allowsMixedState && [self state] === CPMixedState)
|
||||
[self setState:CPOnState];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the value of the button using an Objective-J object.
|
||||
@param anObjectValue The value of the button interpreted as an Objective-J object.
|
||||
*/
|
||||
- (void)setObjectValue:(id)anObjectValue
|
||||
{
|
||||
if (!anObjectValue || anObjectValue === @"" || ([anObjectValue intValue] === 0))
|
||||
anObjectValue = CPOffState;
|
||||
else if (![anObjectValue isKindOfClass:[CPNumber class]])
|
||||
anObjectValue = CPOnState;
|
||||
else if (anObjectValue >= CPOnState)
|
||||
anObjectValue = CPOnState
|
||||
else if (anObjectValue < CPOffState)
|
||||
if ([self allowsMixedState])
|
||||
anObjectValue = CPMixedState;
|
||||
else
|
||||
anObjectValue = CPOnState;
|
||||
|
||||
[super setObjectValue:anObjectValue];
|
||||
|
||||
switch ([self objectValue])
|
||||
{
|
||||
case CPMixedState:
|
||||
[self unsetThemeState:CPThemeStateSelected];
|
||||
[self setThemeState:CPButtonStateMixed];
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self setThemeState:CPThemeStateHighlighted];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateHighlighted];
|
||||
break;
|
||||
|
||||
case CPOnState:
|
||||
[self unsetThemeState:CPButtonStateMixed];
|
||||
[self setThemeState:CPThemeStateSelected];
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self setThemeState:CPThemeStateHighlighted];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateHighlighted];
|
||||
break;
|
||||
|
||||
case CPOffState:
|
||||
[self unsetThemeState:CPThemeStateSelected | CPButtonStateMixed | CPThemeStateHighlighted];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the button's next state.
|
||||
@return The button's state. A button can have two or three states.
|
||||
If it has two, this value is either \c CPOffState (the normal or unpressed state)
|
||||
or \c CPOnState (the alternate or pressed state).
|
||||
If it has three, this value can be \c CPOnState (the feature is in effect everywhere), \c CPOffState (the feature is in effect nowhere), or \c CPMixedState (the feature is in effect somewhere).
|
||||
*/
|
||||
- (CPInteger)nextState
|
||||
{
|
||||
if ([self allowsMixedState])
|
||||
{
|
||||
var value = [self state];
|
||||
|
||||
return value - ((value === -1) ? -2 : 1);
|
||||
}
|
||||
|
||||
return 1 - [self state];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's next state to \c aState.
|
||||
@param aState Possible states are any of the CPButton globals:
|
||||
\c CPOffState, \c CPOnState, \c CPMixedState
|
||||
*/
|
||||
- (void)setNextState
|
||||
{
|
||||
if ([self infoForBinding:CPValueBinding])
|
||||
[self setAllowsMixedState:NO];
|
||||
|
||||
[self setState:[self nextState]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's state to \c aState.
|
||||
@param aState Possible states are any of the CPButton globals:
|
||||
\c CPOffState, \c CPOnState, \c CPMixedState
|
||||
*/
|
||||
- (void)setState:(CPInteger)aState
|
||||
{
|
||||
[self setIntValue:aState];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the button's current state
|
||||
*/
|
||||
- (CPInteger)state
|
||||
{
|
||||
return [self intValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the title displayed by the button when in its normal state.
|
||||
@param aTitle The string to set as the button's title. This title is always shown on buttons
|
||||
that don’t use their alternate contents when highlighting or displaying their alternate state.
|
||||
*/
|
||||
- (void)setTitle:(CPString)aTitle
|
||||
{
|
||||
if (_title === aTitle)
|
||||
return;
|
||||
|
||||
_title = aTitle;
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the title displayed on the button when it’s in its normal state.
|
||||
@return The title displayed on the receiver when it’s in its normal state
|
||||
or the empty string if the button doesn’t display a title.
|
||||
*/
|
||||
- (CPString)title
|
||||
{
|
||||
return _title;
|
||||
}
|
||||
|
||||
- (void)setAlternateTitle:(CPString)aTitle
|
||||
{
|
||||
if (_alternateTitle === aTitle)
|
||||
return;
|
||||
|
||||
_alternateTitle = aTitle;
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (CPString)alternateTitle
|
||||
{
|
||||
return _alternateTitle;
|
||||
}
|
||||
|
||||
- (void)setImage:(CPImage)anImage
|
||||
{
|
||||
[self setValue:anImage forThemeAttribute:@"image"];
|
||||
}
|
||||
|
||||
- (CPImage)image
|
||||
{
|
||||
return [self valueForThemeAttribute:@"image" inState:CPThemeStateNormal];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's image which is used in its alternate state.
|
||||
@param anImage the image to be used while the button is in an alternate state
|
||||
*/
|
||||
- (void)setAlternateImage:(CPImage)anImage
|
||||
{
|
||||
[self setValue:anImage forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the image used when the button is in an alternate state.
|
||||
*/
|
||||
- (CPImage)alternateImage
|
||||
{
|
||||
return [self valueForThemeAttribute:@"image" inState:CPThemeStateHighlighted];
|
||||
}
|
||||
|
||||
- (void)setImageOffset:(float)theImageOffset
|
||||
{
|
||||
[self setValue:theImageOffset forThemeAttribute:@"image-offset"];
|
||||
}
|
||||
|
||||
- (float)imageOffset
|
||||
{
|
||||
return [self valueForThemeAttribute:@"image-offset"];
|
||||
}
|
||||
|
||||
- (void)setShowsStateBy:(CPInteger)aMask
|
||||
{
|
||||
// CPPushInCellMask cannot be set for showsStateBy.
|
||||
aMask &= ~CPPushInCellMask;
|
||||
|
||||
if (_showsStateBy === aMask)
|
||||
return;
|
||||
|
||||
_showsStateBy = aMask;
|
||||
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask) && [self state] != CPOffState)
|
||||
[self setThemeState:CPThemeStateHighlighted];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateHighlighted];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (CPInteger)showsStateBy
|
||||
{
|
||||
return _showsStateBy;
|
||||
}
|
||||
|
||||
- (void)setHighlightsBy:(CPInteger)aMask
|
||||
{
|
||||
if (_highlightsBy === aMask)
|
||||
return;
|
||||
|
||||
_highlightsBy = aMask;
|
||||
|
||||
if ([self hasThemeState:CPThemeStateHighlighted])
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPInteger)highlightsBy
|
||||
{
|
||||
return _highlightsBy;
|
||||
}
|
||||
|
||||
- (void)setButtonType:(CPButtonType)aButtonType
|
||||
{
|
||||
switch (aButtonType)
|
||||
{
|
||||
case CPMomentaryLightButton: [self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPNoCellMask];
|
||||
break;
|
||||
|
||||
case CPMomentaryPushInButton: [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPNoCellMask];
|
||||
break;
|
||||
|
||||
case CPMomentaryChangeButton: [self setHighlightsBy:CPContentsCellMask];
|
||||
[self setShowsStateBy:CPNoCellMask];
|
||||
break;
|
||||
|
||||
case CPPushOnPushOffButton: [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPChangeBackgroundCellMask | CPChangeGrayCellMask];
|
||||
break;
|
||||
|
||||
case CPOnOffButton: [self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
break;
|
||||
|
||||
case CPToggleButton: [self setHighlightsBy:CPPushInCellMask | CPContentsCellMask];
|
||||
[self setShowsStateBy:CPContentsCellMask];
|
||||
break;
|
||||
|
||||
case CPSwitchButton: [CPException raise:CPInvalidArgumentException
|
||||
reason:"The CPSwitchButton type is not supported in Cappuccino, use the CPCheckBox class instead."];
|
||||
|
||||
case CPRadioButton: [CPException raise:CPInvalidArgumentException
|
||||
reason:"The CPRadioButton type is not supported in Cappuccino, use the CPRadio class instead."];
|
||||
|
||||
default: [CPException raise:CPInvalidArgumentException
|
||||
reason:"Unknown button type."];
|
||||
}
|
||||
|
||||
[self setImageDimsWhenDisabled:YES];
|
||||
}
|
||||
|
||||
- (void)setImageDimsWhenDisabled:(BOOL)imageShouldDimWhenDisabled
|
||||
{
|
||||
imageShouldDimWhenDisabled = !!imageShouldDimWhenDisabled;
|
||||
|
||||
if (_imageDimsWhenDisabled === imageShouldDimWhenDisabled)
|
||||
return;
|
||||
|
||||
_imageDimsWhenDisabled = imageShouldDimWhenDisabled;
|
||||
|
||||
if ([self hasThemeState:CPThemeStateDisabled])
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)imageDimsWhenDisabled
|
||||
{
|
||||
return _imageDimsWhenDisabled;
|
||||
}
|
||||
|
||||
- (void)setPeriodicDelay:(float)aDelay interval:(float)anInterval
|
||||
{
|
||||
_periodicDelay = aDelay;
|
||||
_periodicInterval = anInterval;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if ([self isContinuous])
|
||||
{
|
||||
_continuousDelayTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicDelay callback: function()
|
||||
{
|
||||
if (!_continuousTimer)
|
||||
_continuousTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicInterval target:self selector:@selector(onContinousEvent:) userInfo:anEvent repeats:YES];
|
||||
}
|
||||
|
||||
repeats:NO];
|
||||
}
|
||||
|
||||
[super mouseDown:anEvent];
|
||||
}
|
||||
|
||||
- (void)onContinousEvent:(CPTimer)aTimer
|
||||
{
|
||||
if (_target && _action && [_target respondsToSelector:_action])
|
||||
[_target performSelector:_action withObject:self];
|
||||
}
|
||||
|
||||
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
||||
{
|
||||
_isTracking = YES;
|
||||
|
||||
var startedTracking = [super startTrackingAt:aPoint];
|
||||
if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
|
||||
{
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self highlight:[self state] == CPOffState];
|
||||
else
|
||||
[self highlight:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self highlight:[self state] != CPOffState];
|
||||
else
|
||||
[self highlight:NO];
|
||||
}
|
||||
return startedTracking;
|
||||
}
|
||||
|
||||
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
|
||||
{
|
||||
_isTracking = NO;
|
||||
|
||||
if (mouseIsUp && CGRectContainsPoint([self bounds], aPoint))
|
||||
[self setNextState];
|
||||
else
|
||||
{
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
[self highlight:[self state] != CPOffState];
|
||||
else
|
||||
[self highlight:NO];
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
[self invalidateTimers];
|
||||
}
|
||||
|
||||
- (void)invalidateTimers
|
||||
{
|
||||
if (_continuousTimer)
|
||||
{
|
||||
[_continuousTimer invalidate];
|
||||
_continuousTimer = nil;
|
||||
}
|
||||
|
||||
if (_continuousDelayTimer)
|
||||
{
|
||||
[_continuousDelayTimer invalidate];
|
||||
_continuousDelayTimer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGRect)contentRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
|
||||
|
||||
return _CGRectInsetByInset(bounds, contentInset);
|
||||
}
|
||||
|
||||
- (CGRect)bezelRectForBounds:(CGRect)bounds
|
||||
{
|
||||
// Is this necessary? The theme itself can just change its inset to a zero inset when !CPThemeStateBordered.
|
||||
if (![self isBordered])
|
||||
return bounds;
|
||||
|
||||
var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
|
||||
|
||||
return _CGRectInsetByInset(bounds, bezelInset);
|
||||
}
|
||||
|
||||
- (CGSize)_minimumFrameSize
|
||||
{
|
||||
var size = CGSizeMakeZero(),
|
||||
contentView = [self ephemeralSubviewNamed:@"content-view"];
|
||||
|
||||
if (contentView)
|
||||
{
|
||||
[contentView sizeToFit];
|
||||
size = [contentView frameSize];
|
||||
}
|
||||
else
|
||||
size = [([self title] || " ") sizeWithFont:[self currentValueForThemeAttribute:@"font"]];
|
||||
|
||||
var contentInset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
minSize = [self currentValueForThemeAttribute:@"min-size"],
|
||||
maxSize = [self currentValueForThemeAttribute:@"max-size"];
|
||||
|
||||
size.width = MAX(size.width + contentInset.left + contentInset.right, minSize.width);
|
||||
size.height = MAX(size.height + contentInset.top + contentInset.bottom, minSize.height);
|
||||
|
||||
if (maxSize.width >= 0.0)
|
||||
size.width = MIN(size.width, maxSize.width);
|
||||
|
||||
if (maxSize.height >= 0.0)
|
||||
size.height = MIN(size.height, maxSize.height);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/*!
|
||||
Adjust the size of the button to fit the title and surrounding button image.
|
||||
*/
|
||||
- (void)sizeToFit
|
||||
{
|
||||
[self layoutSubviews];
|
||||
|
||||
[self setFrameSize:[self _minimumFrameSize]];
|
||||
|
||||
if ([self ephemeralSubviewNamed:@"content-view"])
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if (aName === "bezel-view")
|
||||
return [self bezelRectForBounds:[self bounds]];
|
||||
|
||||
else if (aName === "content-view")
|
||||
return [self contentRectForBounds:[self bounds]];
|
||||
|
||||
return [super rectForEphemeralSubviewNamed:aName];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if (aName === "bezel-view")
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
return view;
|
||||
}
|
||||
else
|
||||
return [[_CPImageAndTextView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:@"content-view"];
|
||||
|
||||
[bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
|
||||
|
||||
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"bezel-view"];
|
||||
|
||||
if (contentView)
|
||||
{
|
||||
var title = nil,
|
||||
image = nil;
|
||||
|
||||
if (_isTracking)
|
||||
{
|
||||
if (_highlightsBy & CPContentsCellMask)
|
||||
{
|
||||
if (_showsStateBy & CPContentsCellMask)
|
||||
{
|
||||
title = ([self state] == CPOffState && _alternateTitle) ? _alternateTitle : _title;
|
||||
image = ([self state] == CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
|
||||
}
|
||||
else
|
||||
{
|
||||
title = [self alternateTitle];
|
||||
image = [self alternateImage];
|
||||
}
|
||||
}
|
||||
else if (_showsStateBy & CPContentsCellMask)
|
||||
{
|
||||
title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
|
||||
image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
|
||||
}
|
||||
else
|
||||
{
|
||||
title = _title;
|
||||
image = [self image];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_showsStateBy & CPContentsCellMask)
|
||||
{
|
||||
title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
|
||||
image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
|
||||
}
|
||||
else
|
||||
{
|
||||
title = _title;
|
||||
image = [self image];
|
||||
}
|
||||
}
|
||||
|
||||
[contentView setText:title];
|
||||
[contentView setImage:image];
|
||||
[contentView setImageOffset:[self currentValueForThemeAttribute:@"image-offset"]];
|
||||
|
||||
[contentView setFont:[self currentValueForThemeAttribute:@"font"]];
|
||||
[contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
|
||||
[contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
|
||||
[contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
|
||||
[contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
|
||||
[contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
|
||||
[contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
|
||||
[contentView setImagePosition:[self currentValueForThemeAttribute:@"image-position"]];
|
||||
[contentView setImageScaling:[self currentValueForThemeAttribute:@"image-scaling"]];
|
||||
[contentView setDimsImage:[self hasThemeState:CPThemeStateDisabled] && _imageDimsWhenDisabled];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBordered:(BOOL)shouldBeBordered
|
||||
{
|
||||
if (shouldBeBordered)
|
||||
[self setThemeState:CPThemeStateBordered];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateBordered];
|
||||
}
|
||||
|
||||
- (BOOL)isBordered
|
||||
{
|
||||
return [self hasThemeState:CPThemeStateBordered];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the keyboard shortcut for this button. For special keys see
|
||||
CPEvent.j CP...FunctionKey and CPText.j CP...Character.
|
||||
|
||||
@param aString the keyboard shortcut as a string
|
||||
*/
|
||||
- (void)setKeyEquivalent:(CPString)aString
|
||||
{
|
||||
_keyEquivalent = aString || @"";
|
||||
|
||||
// Check if the key equivalent is the enter key
|
||||
// Treat \r and \n as the same key equivalent. See issue #710.
|
||||
if (aString === CPNewlineCharacter || aString === CPCarriageReturnCharacter)
|
||||
[self setThemeState:CPThemeStateDefault];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateDefault];
|
||||
}
|
||||
|
||||
- (void)viewWillMoveToWindow:(CPWindow)aWindow
|
||||
{
|
||||
var selfWindow = [self window];
|
||||
|
||||
if (selfWindow === aWindow || aWindow === nil)
|
||||
return;
|
||||
|
||||
if ([selfWindow defaultButton] === self)
|
||||
[selfWindow setDefaultButton:nil];
|
||||
|
||||
if ([self keyEquivalent] === CPNewlineCharacter || [self keyEquivalent] === CPCarriageReturnCharacter)
|
||||
[aWindow setDefaultButton:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the keyboard shortcut for this button.
|
||||
*/
|
||||
- (CPString)keyEquivalent
|
||||
{
|
||||
return _keyEquivalent;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the mask used with this button's key equivalent.
|
||||
*/
|
||||
- (void)setKeyEquivalentModifierMask:(unsigned)aMask
|
||||
{
|
||||
_keyEquivalentModifierMask = aMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the mask to be used with this button's key equivalent.
|
||||
*/
|
||||
- (unsigned)keyEquivalentModifierMask
|
||||
{
|
||||
return _keyEquivalentModifierMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Checks the button's key equivalent against that in the event, and if they
|
||||
match simulates a button click.
|
||||
*/
|
||||
- (BOOL)performKeyEquivalent:(CPEvent)anEvent
|
||||
{
|
||||
// Don't handle the key equivalent for the default window because the window will handle it for us
|
||||
if ([[self window] defaultButton] === self)
|
||||
return NO;
|
||||
|
||||
if (![anEvent _triggersKeyEquivalent:[self keyEquivalent] withModifierMask:[self keyEquivalentModifierMask]])
|
||||
return NO;
|
||||
|
||||
[self performClick:nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Perform a click on the receiver.
|
||||
|
||||
@param sender - The sender object
|
||||
*/
|
||||
- (void)performClick:(id)sender
|
||||
{
|
||||
// This is slightly different from [super performClick:] in that the highlight behaviour is dependent on
|
||||
// highlightsBy and showsStateBy.
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
[self setState:[self nextState]];
|
||||
|
||||
var shouldHighlight = NO;
|
||||
|
||||
if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
|
||||
{
|
||||
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
|
||||
shouldHighlight = [self state] == CPOffState;
|
||||
else
|
||||
shouldHighlight = YES;
|
||||
}
|
||||
|
||||
[self highlight:shouldHighlight];
|
||||
|
||||
try
|
||||
{
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (shouldHighlight)
|
||||
[CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(unhighlightButtonTimerDidFinish:) userInfo:nil repeats:NO];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPButton (NS)
|
||||
|
||||
- (void)setBezelStyle:(unsigned)aBezelStyle
|
||||
{
|
||||
if (aBezelStyle === _bezelStyle)
|
||||
return;
|
||||
|
||||
var currentState = [CPButtonBezelStyleStateMap objectForKey:_bezelStyle],
|
||||
newState = [CPButtonBezelStyleStateMap objectForKey:aBezelStyle];
|
||||
|
||||
if (currentState)
|
||||
[self unsetThemeState:currentState];
|
||||
|
||||
if (newState)
|
||||
[self setThemeState:newState];
|
||||
|
||||
_bezelStyle = aBezelStyle;
|
||||
}
|
||||
|
||||
- (unsigned)bezelStyle
|
||||
{
|
||||
return _bezelStyle;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPButtonImageKey = @"CPButtonImageKey",
|
||||
CPButtonAlternateImageKey = @"CPButtonAlternateImageKey",
|
||||
CPButtonTitleKey = @"CPButtonTitleKey",
|
||||
CPButtonAlternateTitleKey = @"CPButtonAlternateTitleKey",
|
||||
CPButtonIsBorderedKey = @"CPButtonIsBorderedKey",
|
||||
CPButtonAllowsMixedStateKey = @"CPButtonAllowsMixedStateKey",
|
||||
CPButtonImageDimsWhenDisabledKey = @"CPButtonImageDimsWhenDisabledKey",
|
||||
CPButtonImagePositionKey = @"CPButtonImagePositionKey",
|
||||
CPButtonKeyEquivalentKey = @"CPButtonKeyEquivalentKey",
|
||||
CPButtonKeyEquivalentMaskKey = @"CPButtonKeyEquivalentMaskKey",
|
||||
CPButtonPeriodicDelayKey = @"CPButtonPeriodicDelayKey",
|
||||
CPButtonPeriodicIntervalKey = @"CPButtonPeriodicIntervalKey",
|
||||
CPButtonHighlightsByKey = @"CPButtonHighlightsByKey",
|
||||
CPButtonShowsStateByKey = @"CPButtonShowsStateByKey";
|
||||
|
||||
@implementation CPButton (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the button by unarchiving data from \c aCoder.
|
||||
@param aCoder the coder containing the archived CPButton.
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self _init];
|
||||
|
||||
_title = [aCoder decodeObjectForKey:CPButtonTitleKey];
|
||||
_alternateTitle = [aCoder decodeObjectForKey:CPButtonAlternateTitleKey];
|
||||
_allowsMixedState = [aCoder decodeBoolForKey:CPButtonAllowsMixedStateKey];
|
||||
|
||||
if ([aCoder containsValueForKey:CPButtonHighlightsByKey])
|
||||
{
|
||||
// If one exists, assume both do.
|
||||
_highlightsBy = [aCoder decodeIntForKey:CPButtonHighlightsByKey];
|
||||
_showsStateBy = [aCoder decodeIntForKey:CPButtonShowsStateByKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Backwards compatibility: if this CPButton was encoded before coding of
|
||||
// highlightsBy and showsStateBy were added, we should just use the
|
||||
// default values from _init rather than overwriting with 0, 0.
|
||||
}
|
||||
|
||||
[self setImageDimsWhenDisabled:[aCoder decodeObjectForKey:CPButtonImageDimsWhenDisabledKey]];
|
||||
|
||||
if ([aCoder containsValueForKey:CPButtonImagePositionKey])
|
||||
[self setImagePosition:[aCoder decodeIntForKey:CPButtonImagePositionKey]];
|
||||
|
||||
if ([aCoder containsValueForKey:CPButtonKeyEquivalentKey])
|
||||
[self setKeyEquivalent:CFData.decodeBase64ToUtf16String([aCoder decodeObjectForKey:CPButtonKeyEquivalentKey])];
|
||||
|
||||
if ([aCoder containsValueForKey:CPButtonPeriodicDelayKey])
|
||||
_periodicDelay = [aCoder decodeObjectForKey:CPButtonPeriodicDelayKey];
|
||||
|
||||
if ([aCoder containsValueForKey:CPButtonPeriodicIntervalKey])
|
||||
_periodicInterval = [aCoder decodeObjectForKey:CPButtonPeriodicIntervalKey];
|
||||
|
||||
_keyEquivalentModifierMask = [aCoder decodeIntForKey:CPButtonKeyEquivalentMaskKey];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Archives this button into the provided coder.
|
||||
@param aCoder the coder to which the button's instance data will be written.
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
[self invalidateTimers];
|
||||
|
||||
[aCoder encodeObject:_title forKey:CPButtonTitleKey];
|
||||
[aCoder encodeObject:_alternateTitle forKey:CPButtonAlternateTitleKey];
|
||||
|
||||
[aCoder encodeBool:_allowsMixedState forKey:CPButtonAllowsMixedStateKey];
|
||||
|
||||
[aCoder encodeInt:_highlightsBy forKey:CPButtonHighlightsByKey];
|
||||
[aCoder encodeInt:_showsStateBy forKey:CPButtonShowsStateByKey];
|
||||
|
||||
[aCoder encodeBool:[self imageDimsWhenDisabled] forKey:CPButtonImageDimsWhenDisabledKey];
|
||||
[aCoder encodeInt:[self imagePosition] forKey:CPButtonImagePositionKey];
|
||||
|
||||
if (_keyEquivalent)
|
||||
[aCoder encodeObject:CFData.encodeBase64Utf16String(_keyEquivalent) forKey:CPButtonKeyEquivalentKey];
|
||||
|
||||
[aCoder encodeInt:_keyEquivalentModifierMask forKey:CPButtonKeyEquivalentMaskKey];
|
||||
|
||||
[aCoder encodeObject:_periodicDelay forKey:CPButtonPeriodicDelayKey];
|
||||
[aCoder encodeObject:_periodicInterval forKey:CPButtonPeriodicIntervalKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@import "CPCheckBox.j"
|
||||
@import "CPRadio.j"
|
||||
@@ -1,277 +0,0 @@
|
||||
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
@implementation CPButtonBar : CPView
|
||||
{
|
||||
BOOL _hasResizeControl;
|
||||
BOOL _resizeControlIsLeftAligned;
|
||||
CPArray _buttons;
|
||||
}
|
||||
|
||||
+ (id)plusButton
|
||||
{
|
||||
var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 35, 25)],
|
||||
image = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPButtonBar class]] pathForResource:@"plus_button.png"] size:CGSizeMake(11, 12)];
|
||||
|
||||
[button setBordered:NO];
|
||||
[button setImage:image];
|
||||
[button setImagePosition:CPImageOnly];
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
+ (id)minusButton
|
||||
{
|
||||
var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 35, 25)],
|
||||
image = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPButtonBar class]] pathForResource:@"minus_button.png"] size:CGSizeMake(11, 4)];
|
||||
|
||||
[button setBordered:NO];
|
||||
[button setImage:image];
|
||||
[button setImagePosition:CPImageOnly];
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
+ (id)actionPopupButton
|
||||
{
|
||||
var button = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0, 0, 35, 25)],
|
||||
image = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPButtonBar class]] pathForResource:@"action_button.png"] size:CGSizeMake(22, 14)];
|
||||
|
||||
[button addItemWithTitle:nil];
|
||||
[[button lastItem] setImage:image];
|
||||
[button setImagePosition:CPImageOnly];
|
||||
[button setValue:CGInsetMake(0, 0, 0, 0) forThemeAttribute:"content-inset"];
|
||||
|
||||
[button setPullsDown:YES];
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"button-bar";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[CGInsetMake(0.0, 0.0, 0.0, 0.0), CGSizeMakeZero(), [CPNull null], [CPNull null], [CPNull null], [CPNull null]]
|
||||
forKeys:[@"resize-control-inset", @"resize-control-size", @"resize-control-color", @"bezel-color", @"button-bezel-color", @"button-text-color"]];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_buttons = [];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)awakeFromCib
|
||||
{
|
||||
var view = [self superview],
|
||||
subview = self;
|
||||
|
||||
while (view)
|
||||
{
|
||||
if ([view isKindOfClass:[CPSplitView class]])
|
||||
{
|
||||
var viewIndex = [[view subviews] indexOfObject:subview];
|
||||
[view setButtonBar:self forDividerAtIndex:viewIndex];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
subview = view;
|
||||
view = [view superview];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setButtons:(CPArray)buttons
|
||||
{
|
||||
_buttons = [CPArray arrayWithArray:buttons];
|
||||
|
||||
for (var i = 0, count = [_buttons count]; i < count; i++)
|
||||
[_buttons[i] setBordered:YES];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (CPArray)buttons
|
||||
{
|
||||
return [CPArray arrayWithArray:_buttons];
|
||||
}
|
||||
|
||||
- (void)setHasResizeControl:(BOOL)shouldHaveResizeControl
|
||||
{
|
||||
if (_hasResizeControl === shouldHaveResizeControl)
|
||||
return;
|
||||
|
||||
_hasResizeControl = !!shouldHaveResizeControl;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (BOOL)hasResizeControl
|
||||
{
|
||||
return _hasResizeControl;
|
||||
}
|
||||
|
||||
- (void)setResizeControlIsLeftAligned:(BOOL)shouldBeLeftAligned
|
||||
{
|
||||
if (_resizeControlIsLeftAligned === shouldBeLeftAligned)
|
||||
return;
|
||||
|
||||
_resizeControlIsLeftAligned = !!shouldBeLeftAligned;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (BOOL)resizeControlIsLeftAligned
|
||||
{
|
||||
return _resizeControlIsLeftAligned;
|
||||
}
|
||||
|
||||
- (CGRect)resizeControlFrame
|
||||
{
|
||||
var inset = [self currentValueForThemeAttribute:@"resize-control-inset"],
|
||||
size = [self currentValueForThemeAttribute:@"resize-control-size"],
|
||||
currentSize = [self bounds],
|
||||
leftOrigin = _resizeControlIsLeftAligned ? 0 : currentSize.size.width - size.width - inset.right - inset.left;
|
||||
|
||||
return CGRectMake(leftOrigin, 0, size.width + inset.left + inset.right, size.height + inset.top + inset.bottom);
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if (aName === "resize-control-view")
|
||||
{
|
||||
var inset = [self currentValueForThemeAttribute:@"resize-control-inset"],
|
||||
size = [self currentValueForThemeAttribute:@"resize-control-size"],
|
||||
currentSize = [self bounds];
|
||||
|
||||
if (_resizeControlIsLeftAligned)
|
||||
return CGRectMake(inset.left, inset.top, size.width, size.height);
|
||||
else
|
||||
return CGRectMake(currentSize.size.width - size.width - inset.right, inset.top, size.width, size.height);
|
||||
}
|
||||
|
||||
return [super rectForEphemeralSubviewNamed:aName];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if (aName === "resize-control-view")
|
||||
return [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
return [super createEphemeralSubviewNamed:aName];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
|
||||
|
||||
var normalColor = [self valueForThemeAttribute:@"button-bezel-color" inState:CPThemeStateNormal],
|
||||
highlightedColor = [self valueForThemeAttribute:@"button-bezel-color" inState:CPThemeStateHighlighted],
|
||||
disabledColor = [self valueForThemeAttribute:@"button-bezel-color" inState:CPThemeStateDisabled],
|
||||
textColor = [self valueForThemeAttribute:@"button-text-color" inState:CPThemeStateNormal];
|
||||
|
||||
var buttonsNotHidden = [CPArray arrayWithArray:_buttons],
|
||||
count = [buttonsNotHidden count];
|
||||
|
||||
while (count--)
|
||||
if ([buttonsNotHidden[count] isHidden])
|
||||
[buttonsNotHidden removeObject:buttonsNotHidden[count]];
|
||||
|
||||
var currentButtonOffset = _resizeControlIsLeftAligned ? CGRectGetMaxX([self bounds]) + 1 : -1,
|
||||
bounds = [self bounds],
|
||||
height = CGRectGetHeight(bounds) - 1,
|
||||
frameWidth = CGRectGetWidth(bounds),
|
||||
resizeRect = _hasResizeControl ? [self rectForEphemeralSubviewNamed:"resize-control-view"] : CGRectMakeZero(),
|
||||
resizeWidth = CGRectGetWidth(resizeRect),
|
||||
availableWidth = frameWidth - resizeWidth - 1;
|
||||
|
||||
for (var i = 0, count = [buttonsNotHidden count]; i < count; i++)
|
||||
{
|
||||
var button = buttonsNotHidden[i],
|
||||
width = CGRectGetWidth([button frame]);
|
||||
|
||||
if (availableWidth > width)
|
||||
availableWidth -= width;
|
||||
else
|
||||
break;
|
||||
|
||||
if (_resizeControlIsLeftAligned)
|
||||
{
|
||||
[button setFrame:CGRectMake(currentButtonOffset - width, 1, width, height)];
|
||||
currentButtonOffset -= width - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
[button setFrame:CGRectMake(currentButtonOffset, 1, width, height)];
|
||||
currentButtonOffset += width - 1;
|
||||
}
|
||||
|
||||
[button setValue:normalColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal | CPThemeStateBordered];
|
||||
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inState:CPThemeStateHighlighted | CPThemeStateBordered];
|
||||
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled | CPThemeStateBordered];
|
||||
[button setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateBordered];
|
||||
|
||||
// FIXME shouldn't need this
|
||||
[button setValue:normalColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal | CPThemeStateBordered | CPPopUpButtonStatePullsDown];
|
||||
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inState:CPThemeStateHighlighted | CPThemeStateBordered | CPPopUpButtonStatePullsDown];
|
||||
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled | CPThemeStateBordered | CPPopUpButtonStatePullsDown];
|
||||
|
||||
[self addSubview:button];
|
||||
}
|
||||
|
||||
if (_hasResizeControl)
|
||||
{
|
||||
var resizeControlView = [self layoutEphemeralSubviewNamed:@"resize-control-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
[resizeControlView setAutoresizingMask: _resizeControlIsLeftAligned ? CPViewMaxXMargin : CPViewMinXMargin];
|
||||
[resizeControlView setBackgroundColor:[self currentValueForThemeAttribute:@"resize-control-color"]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey",
|
||||
CPButtonBarResizeControlIsLeftAlignedKey = @"CPButtonBarResizeControlIsLeftAlignedKey",
|
||||
CPButtonBarButtonsKey = @"CPButtonBarButtonsKey";
|
||||
|
||||
@implementation CPButtonBar (CPCoding)
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeBool:_hasResizeControl forKey:CPButtonBarHasResizeControlKey];
|
||||
[aCoder encodeBool:_resizeControlIsLeftAligned forKey:CPButtonBarResizeControlIsLeftAlignedKey];
|
||||
[aCoder encodeObject:_buttons forKey:CPButtonBarButtonsKey];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_buttons = [aCoder decodeObjectForKey:CPButtonBarButtonsKey] || [];
|
||||
_hasResizeControl = [aCoder decodeBoolForKey:CPButtonBarHasResizeControlKey];
|
||||
_resizeControlIsLeftAligned = [aCoder decodeBoolForKey:CPButtonBarResizeControlIsLeftAlignedKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* CPCheckBox.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPButton.j"
|
||||
|
||||
|
||||
CPCheckBoxImageOffset = 4.0;
|
||||
|
||||
@implementation CPCheckBox : CPButton
|
||||
{
|
||||
}
|
||||
|
||||
+ (id)checkBoxWithTitle:(CPString)aTitle theme:(CPTheme)aTheme
|
||||
{
|
||||
return [self buttonWithTitle:aTitle theme:aTheme];
|
||||
}
|
||||
|
||||
+ (id)checkBoxWithTitle:(CPString)aTitle
|
||||
{
|
||||
return [self buttonWithTitle:aTitle];
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"check-box";
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (theBinding === CPValueBinding)
|
||||
return [_CPCheckBoxValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setHighlightsBy:CPContentsCellMask];
|
||||
[self setShowsStateBy:CPContentsCellMask];
|
||||
|
||||
// Defaults?
|
||||
[self setImagePosition:CPImageLeft];
|
||||
[self setAlignment:CPLeftTextAlignment];
|
||||
|
||||
[self setBordered:NO];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)takeStateFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
|
||||
{
|
||||
var count = objects.length,
|
||||
value = [objects[0] valueForKeyPath:aKeyPath] ? CPOnState : CPOffState;
|
||||
|
||||
[self setAllowsMixedState:NO];
|
||||
[self setState:value];
|
||||
|
||||
while (count-- > 1)
|
||||
{
|
||||
if (value !== ([objects[count] valueForKeyPath:aKeyPath] ? CPOnState : CPOffState))
|
||||
{
|
||||
[self setAllowsMixedState:YES];
|
||||
[self setState:CPMixedState];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
|
||||
{
|
||||
[self takeStateFromKeyPath:aKeyPath ofObjects:objects];
|
||||
}
|
||||
|
||||
- (CPImage)image
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image"];
|
||||
}
|
||||
|
||||
- (CPImage)alternateImage
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image"];
|
||||
}
|
||||
|
||||
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
||||
{
|
||||
var startedTracking = [super startTrackingAt:aPoint];
|
||||
[self highlight:YES];
|
||||
return startedTracking;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPCheckBoxValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options
|
||||
{
|
||||
[super _updatePlaceholdersWithOptions:options];
|
||||
|
||||
[self _setPlaceholder:CPMixedState forMarker:CPMultipleValuesMarker isDefault:YES];
|
||||
[self _setPlaceholder:CPOffState forMarker:CPNoSelectionMarker isDefault:YES];
|
||||
[self _setPlaceholder:CPOffState forMarker:CPNotApplicableMarker isDefault:YES];
|
||||
[self _setPlaceholder:CPOffState forMarker:CPNullMarker isDefault:YES];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source setAllowsMixedState:(aValue === CPMixedState)];
|
||||
[_source setState:aValue];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source setState:aValue];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,248 +0,0 @@
|
||||
/*
|
||||
* CPClipView.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CPView.j"
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPClipView
|
||||
|
||||
CPClipView allows you to define a clip rect and display only that portion of its containing view.
|
||||
It is used to hold the document view in a CPScrollView.
|
||||
*/
|
||||
@implementation CPClipView : CPView
|
||||
{
|
||||
CPView _documentView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the document view to be \c aView.
|
||||
@param aView the new document view. It's frame origin will be changed to \c (0,0) after calling this method.
|
||||
*/
|
||||
- (void)setDocumentView:(CPView)aView
|
||||
{
|
||||
if (_documentView == aView)
|
||||
return;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_documentView)
|
||||
{
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPViewFrameDidChangeNotification
|
||||
object:_documentView];
|
||||
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPViewBoundsDidChangeNotification
|
||||
object:_documentView];
|
||||
|
||||
[_documentView removeFromSuperview];
|
||||
}
|
||||
|
||||
_documentView = aView;
|
||||
|
||||
if (_documentView)
|
||||
{
|
||||
[self addSubview:_documentView];
|
||||
|
||||
[_documentView setPostsFrameChangedNotifications:YES];
|
||||
[_documentView setPostsBoundsChangedNotifications:YES];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(viewFrameChanged:)
|
||||
name:CPViewFrameDidChangeNotification
|
||||
object:_documentView];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(viewBoundsChanged:)
|
||||
name:CPViewBoundsDidChangeNotification
|
||||
object:_documentView];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document view.
|
||||
*/
|
||||
- (id)documentView
|
||||
{
|
||||
return _documentView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new point that may be adjusted from \c aPoint
|
||||
to make sure it lies within the document view.
|
||||
@param aPoint
|
||||
@return the adjusted point
|
||||
*/
|
||||
- (CGPoint)constrainScrollPoint:(CGPoint)aPoint
|
||||
{
|
||||
if (!_documentView)
|
||||
return _CGPointMakeZero();
|
||||
|
||||
var documentFrame = [_documentView frame];
|
||||
|
||||
aPoint.x = MAX(0.0, MIN(aPoint.x, MAX(_CGRectGetWidth(documentFrame) - _CGRectGetWidth(_bounds), 0.0)));
|
||||
aPoint.y = MAX(0.0, MIN(aPoint.y, MAX(_CGRectGetHeight(documentFrame) - _CGRectGetHeight(_bounds), 0.0)));
|
||||
|
||||
return aPoint;
|
||||
}
|
||||
|
||||
- (void)setBoundsOrigin:(CGPoint)aPoint
|
||||
{
|
||||
if (_CGPointEqualToPoint(_bounds.origin, aPoint))
|
||||
return;
|
||||
|
||||
[super setBoundsOrigin:aPoint];
|
||||
|
||||
var superview = [self superview],
|
||||
|
||||
// This is hack to avoid having to import CPScrollView.
|
||||
// FIXME: Should CPScrollView be finding out about this on its own somehow?
|
||||
scrollViewClass = objj_getClass("CPScrollView");
|
||||
|
||||
if ([superview isKindOfClass:scrollViewClass])
|
||||
[superview reflectScrolledClipView:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Scrolls the clip view to the specified point. The method
|
||||
sets its bounds origin to \c aPoint.
|
||||
*/
|
||||
- (void)scrollToPoint:(CGPoint)aPoint
|
||||
{
|
||||
[self setBoundsOrigin:[self constrainScrollPoint:aPoint]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Handles a CPViewBoundsDidChangeNotification.
|
||||
@param aNotification the notification event
|
||||
*/
|
||||
- (void)viewBoundsChanged:(CPNotification)aNotification
|
||||
{
|
||||
[self _constrainScrollPoint];
|
||||
}
|
||||
|
||||
/*!
|
||||
Handles a CPViewFrameDidChangeNotification.
|
||||
@param aNotification the notification event
|
||||
*/
|
||||
- (void)viewFrameChanged:(CPNotification)aNotification
|
||||
{
|
||||
[self _constrainScrollPoint];
|
||||
}
|
||||
|
||||
- (void)resizeSubviewsWithOldSize:(CGSize)aSize
|
||||
{
|
||||
[super resizeSubviewsWithOldSize:aSize];
|
||||
[self _constrainScrollPoint];
|
||||
}
|
||||
|
||||
- (void)_constrainScrollPoint
|
||||
{
|
||||
var oldScrollPoint = [self bounds].origin;
|
||||
|
||||
// Call scrollToPoint: because the current scroll point may no longer make
|
||||
// sense given the new frame of the document view.
|
||||
[self scrollToPoint:oldScrollPoint];
|
||||
|
||||
// scrollToPoint: takes care of reflectScrollClipView: for us, so bail if
|
||||
// the scroll points are not equal (meaning scrollToPoint: didn't early bail).
|
||||
if (!CGPointEqualToPoint(oldScrollPoint, [self bounds].origin))
|
||||
return;
|
||||
|
||||
// ... and we're in a scroll view of course.
|
||||
var superview = [self superview],
|
||||
|
||||
// This is hack to avoid having to import CPScrollView.
|
||||
// FIXME: Should CPScrollView be finding out about this on its own somehow?
|
||||
scrollViewClass = objj_getClass("CPScrollView");
|
||||
|
||||
if ([superview isKindOfClass:scrollViewClass])
|
||||
[superview reflectScrolledClipView:self];
|
||||
}
|
||||
|
||||
- (BOOL)autoscroll:(CPEvent)anEvent
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
eventLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
superview = [self superview],
|
||||
deltaX = 0,
|
||||
deltaY = 0;
|
||||
|
||||
if (CGRectContainsPoint(bounds, eventLocation))
|
||||
return NO;
|
||||
|
||||
if (![superview isKindOfClass:[CPScrollView class]] || [superview hasVerticalScroller])
|
||||
{
|
||||
if (eventLocation.y < CGRectGetMinY(bounds))
|
||||
deltaY = CGRectGetMinY(bounds) - eventLocation.y;
|
||||
else if (eventLocation.y > CGRectGetMaxY(bounds))
|
||||
deltaY = CGRectGetMaxY(bounds) - eventLocation.y;
|
||||
if (deltaY < -bounds.size.height)
|
||||
deltaY = -bounds.size.height;
|
||||
if (deltaY > bounds.size.height)
|
||||
deltaY = bounds.size.height;
|
||||
}
|
||||
|
||||
if (![superview isKindOfClass:[CPScrollView class]] || [superview hasHorizontalScroller])
|
||||
{
|
||||
if (eventLocation.x < CGRectGetMinX(bounds))
|
||||
deltaX = CGRectGetMinX(bounds) - eventLocation.x;
|
||||
else if (eventLocation.x > CGRectGetMaxX(bounds))
|
||||
deltaX = CGRectGetMaxX(bounds) - eventLocation.x;
|
||||
if (deltaX < -bounds.size.width)
|
||||
deltaX = -bounds.size.width;
|
||||
if (deltaX > bounds.size.width)
|
||||
deltaX = bounds.size.width;
|
||||
}
|
||||
|
||||
return [self scrollToPoint:CGPointMake(bounds.origin.x - deltaX, bounds.origin.y - deltaY)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPClipViewDocumentViewKey = @"CPScrollViewDocumentView";
|
||||
|
||||
@implementation CPClipView (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
[self setDocumentView:[aCoder decodeObjectForKey:CPClipViewDocumentViewKey]];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_documentView forKey:CPClipViewDocumentViewKey];
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* CPCollectionViewItem.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPViewController.j"
|
||||
|
||||
|
||||
/*!
|
||||
Represents an object inside a CPCollectionView.
|
||||
*/
|
||||
@implementation CPCollectionViewItem : CPViewController
|
||||
{
|
||||
BOOL _isSelected;
|
||||
}
|
||||
|
||||
// Setting the Represented Object
|
||||
/*!
|
||||
Sets the object to be represented by this item.
|
||||
@param anObject the object to be represented
|
||||
*/
|
||||
- (void)setRepresentedObject:(id)anObject
|
||||
{
|
||||
[super setRepresentedObject:anObject];
|
||||
|
||||
var view = [self view];
|
||||
|
||||
if ([view respondsToSelector:@selector(setRepresentedObject:)])
|
||||
[view setRepresentedObject:[self representedObject]];
|
||||
}
|
||||
|
||||
// Modifying the Selection
|
||||
/*!
|
||||
Sets whether this view item should be selected.
|
||||
@param shouldBeSelected \c YES makes the item selected. \c NO deselects it.
|
||||
*/
|
||||
- (void)setSelected:(BOOL)shouldBeSelected
|
||||
{
|
||||
shouldBeSelected = !!shouldBeSelected;
|
||||
|
||||
if (_isSelected === shouldBeSelected)
|
||||
return;
|
||||
|
||||
_isSelected = shouldBeSelected;
|
||||
|
||||
var view = [self view];
|
||||
|
||||
if ([view respondsToSelector:@selector(setSelected:)])
|
||||
[view setSelected:[self isSelected]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the item is currently selected. \c NO if the item is not selected.
|
||||
*/
|
||||
- (BOOL)isSelected
|
||||
{
|
||||
return _isSelected;
|
||||
}
|
||||
|
||||
// Parent Collection View
|
||||
/*!
|
||||
Returns the collection view of which this item is a part.
|
||||
*/
|
||||
- (CPCollectionView)collectionView
|
||||
{
|
||||
return [_view superview];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,875 +0,0 @@
|
||||
/*
|
||||
* CPColor.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CGColor.j"
|
||||
|
||||
@import "CPCompatibility.j"
|
||||
@import "CPImage.j"
|
||||
|
||||
/// @cond IGNORE
|
||||
|
||||
var _redComponent = 0,
|
||||
_greenComponent = 1,
|
||||
_blueComponent = 2,
|
||||
_alphaCompnent = 3;
|
||||
|
||||
var _hueComponent = 0,
|
||||
_saturationComponent = 1,
|
||||
_brightnessComponent = 2;
|
||||
|
||||
var cachedBlackColor,
|
||||
cachedRedColor,
|
||||
cachedGreenColor,
|
||||
cachedBlueColor,
|
||||
cachedYellowColor,
|
||||
cachedGrayColor,
|
||||
cachedLightGrayColor,
|
||||
cachedDarkGrayColor,
|
||||
cachedWhiteColor,
|
||||
cachedBrownColor,
|
||||
cachedCyanColor,
|
||||
cachedMagentaColor,
|
||||
cachedOrangeColor,
|
||||
cachedPurpleColor,
|
||||
cachedShadowColor,
|
||||
cachedClearColor;
|
||||
|
||||
/// @endcond
|
||||
|
||||
/*!
|
||||
Orientation to use with \c CPColorPattern for vertical patterns.
|
||||
*/
|
||||
CPColorPatternIsVertical = YES;
|
||||
|
||||
/*!
|
||||
Orientation to use with \c CPColorPattern for horizontal patterns.
|
||||
*/
|
||||
CPColorPatternIsHorizontal = NO;
|
||||
|
||||
/*!
|
||||
To create a simple color with a pattern image:
|
||||
|
||||
<code>CPColorWithImages(name, width, height{, bundle})</code>
|
||||
|
||||
To create a color with a three part pattern image:
|
||||
|
||||
<code>CPColorWithImages(slices{, orientation})</code>
|
||||
|
||||
where slices is an array of three [name, width, height{, bundle}] arrays,
|
||||
and orientation is \c CPColorPatternIsVertical or \ref CPColorPatternIsHorizontal.
|
||||
If orientatation is not passed, it defaults to \ref CPColorPatternIsHorizontal.
|
||||
|
||||
To create a color with a nine part pattern image:
|
||||
|
||||
<code>CPColorWithImages(slices);</code>
|
||||
|
||||
where slices is an array of nine [name, width, height{, bundle}] arrays.
|
||||
*/
|
||||
function CPColorWithImages()
|
||||
{
|
||||
if (arguments.length < 3)
|
||||
{
|
||||
var slices = arguments[0],
|
||||
imageSlices = [];
|
||||
|
||||
for (var i = 0; i < slices.length; ++i)
|
||||
{
|
||||
var slice = slices[i];
|
||||
|
||||
imageSlices.push(slice ? CPImageInBundle(slice[0], CGSizeMake(slice[1], slice[2]), slice[3]) : nil);
|
||||
}
|
||||
|
||||
if (imageSlices.length === 3)
|
||||
return [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:imageSlices isVertical:arguments[1] || CPColorPatternIsHorizontal]];
|
||||
else
|
||||
return [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:imageSlices]];
|
||||
}
|
||||
else if (arguments.length === 3 || arguments.length === 4)
|
||||
{
|
||||
return [CPColor colorWithPatternImage:CPImageInBundle(arguments[0], CGSizeMake(arguments[1], arguments[2]), arguments[3])];
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
|
||||
\c CPColor can be used to represent color
|
||||
in an RGB or HSB model with an optional transparency value.</p>
|
||||
|
||||
<p>It also provides some class helper methods that
|
||||
returns instances of commonly used colors.</p>
|
||||
*/
|
||||
@implementation CPColor : CPObject
|
||||
{
|
||||
CPArray _components;
|
||||
|
||||
CPImage _patternImage;
|
||||
CPString _cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a color in the RGB colorspace, with an alpha value.
|
||||
Each component should be between the range of 0.0 to 1.0. For
|
||||
the alpha component, a value of 1.0 is opaque, and 0.0 means
|
||||
completely transparent.
|
||||
|
||||
@param red the red component of the color
|
||||
@param green the green component of the color
|
||||
@param blue the blue component of the color
|
||||
@param alpha the alpha component
|
||||
|
||||
@return a color initialized to the values specified
|
||||
*/
|
||||
+ (CPColor)colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[MAX(0.0, MIN(1.0, red)), MAX(0.0, MIN(1.0, green)), MAX(0.0, MIN(1.0, blue)), MAX(0.0, MIN(1.0, alpha))]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@deprecated in favor of colorWithRed:green:blue:alpha:
|
||||
|
||||
Creates a color in the RGB colorspace, with an alpha value.
|
||||
Each component should be between the range of 0.0 to 1.0. For
|
||||
the alpha component, a value of 1.0 is opaque, and 0.0 means
|
||||
completely transparent.
|
||||
|
||||
@param red the red component of the color
|
||||
@param green the green component of the color
|
||||
@param blue the blue component of the color
|
||||
@param alpha the alpha component
|
||||
|
||||
@return a color initialized to the values specified
|
||||
*/
|
||||
+ (CPColor)colorWithCalibratedRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
|
||||
{
|
||||
return [self colorWithRed:red green:green blue:blue alpha:alpha];
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Creates a new color object with \c white for the RGB components.
|
||||
For the alpha component, a value of 1.0 is opaque, and 0.0 means completely transparent.
|
||||
|
||||
@param white a float between 0.0 and 1.0
|
||||
@param alpha the alpha component between 0.0 and 1.0
|
||||
|
||||
@return a color initialized to the values specified
|
||||
*/
|
||||
+ (CPColor)colorWithWhite:(float)white alpha:(float)alpha
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[white, white, white, alpha]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@deprecated in favor of colorWithWhite:alpha:
|
||||
|
||||
Creates a new color object with \c white for the RGB components.
|
||||
For the alpha component, a value of 1.0 is opaque, and 0.0 means completely transparent.
|
||||
|
||||
@param white a float between 0.0 and 1.0
|
||||
@param alpha the alpha component between 0.0 and 1.0
|
||||
|
||||
@return a color initialized to the values specified
|
||||
*/
|
||||
+ (CPColor)colorWithCalibratedWhite:(float)white alpha:(float)alpha
|
||||
{
|
||||
return [self colorWithWhite:white alpha:alpha];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new color in HSB space.
|
||||
|
||||
@param hue the hue value
|
||||
@param saturation the saturation value
|
||||
@param brightness the brightness value
|
||||
|
||||
@return the initialized color
|
||||
*/
|
||||
+ (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness
|
||||
{
|
||||
return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0];
|
||||
}
|
||||
|
||||
+ (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
|
||||
{
|
||||
if (saturation === 0.0)
|
||||
return [CPColor colorWithCalibratedWhite:brightness / 100.0 alpha:alpha];
|
||||
|
||||
var f = hue % 60,
|
||||
p = (brightness * (100 - saturation)) / 10000,
|
||||
q = (brightness * (6000 - saturation * f)) / 600000,
|
||||
t = (brightness * (6000 - saturation * (60 -f))) / 600000,
|
||||
b = brightness / 100.0;
|
||||
|
||||
switch (FLOOR(hue / 60))
|
||||
{
|
||||
case 0: return [CPColor colorWithCalibratedRed:b green:t blue:p alpha:alpha];
|
||||
case 1: return [CPColor colorWithCalibratedRed:q green:b blue:p alpha:alpha];
|
||||
case 2: return [CPColor colorWithCalibratedRed:p green:b blue:t alpha:alpha];
|
||||
case 3: return [CPColor colorWithCalibratedRed:p green:q blue:b alpha:alpha];
|
||||
case 4: return [CPColor colorWithCalibratedRed:t green:p blue:b alpha:alpha];
|
||||
case 5: return [CPColor colorWithCalibratedRed:b green:p blue:q alpha:alpha];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates an RGB color from a hexadecimal string. For example,
|
||||
the a string of "FFFFFF" would return a white CPColor.
|
||||
"FF0000" would return a pure red, "00FF00" would return a
|
||||
pure blue, and "0000FF" would return a pure green.
|
||||
|
||||
@param hex a 6 character long string of hex
|
||||
|
||||
@return an initialized RGB color
|
||||
*/
|
||||
+ (CPColor)colorWithHexString:(string)hex
|
||||
{
|
||||
var rgba = hexToRGB(hex);
|
||||
return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a color in the sRGB colorspace with the given components and alpha values.
|
||||
Values below 0.0 are treated as 0.0 and values above 1.0 are treated as 1.0.
|
||||
*/
|
||||
+ (CPColor)colorWithSRGBRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
|
||||
{
|
||||
// TODO If Cappuccino is ported to a colorspace aware platform, this color should be in
|
||||
// sRGBColorSpace.
|
||||
return [self colorWithRed:red green:green blue:blue alpha:alpha];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a black color object. (RGBA=[0.0, 0.0, 0.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)blackColor
|
||||
{
|
||||
if (!cachedBlackColor)
|
||||
cachedBlackColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0]];
|
||||
|
||||
return cachedBlackColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a blue color object. (RGBA=[0.0, 0.0, 1.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)blueColor
|
||||
{
|
||||
if (!cachedBlueColor)
|
||||
cachedBlueColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 1.0, 1.0]];
|
||||
|
||||
return cachedBlueColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a dark gray color object. (RGBA=[0.33 ,0.33, 0.33, 1.0])
|
||||
*/
|
||||
+ (CPColor)darkGrayColor
|
||||
{
|
||||
if (!cachedDarkGrayColor)
|
||||
cachedDarkGrayColor = [CPColor colorWithCalibratedWhite:1.0 / 3.0 alpha:1.0];
|
||||
|
||||
return cachedDarkGrayColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a gray color object. (RGBA=[0.5, 0.5, 0.5, 1.0])
|
||||
*/
|
||||
+ (CPColor)grayColor
|
||||
{
|
||||
if (!cachedGrayColor)
|
||||
cachedGrayColor = [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0];
|
||||
|
||||
return cachedGrayColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a green color object. (RGBA=[0.0, 1.0, 0.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)greenColor
|
||||
{
|
||||
if (!cachedGreenColor)
|
||||
cachedGreenColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 0.0, 1.0]];
|
||||
|
||||
return cachedGreenColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a light gray color object (RGBA=[0.66, 0.66, 0.66, 1.0])
|
||||
*/
|
||||
+ (CPColor)lightGrayColor
|
||||
{
|
||||
if (!cachedLightGrayColor)
|
||||
cachedLightGrayColor = [CPColor colorWithCalibratedWhite:2.0 / 3.0 alpha:1.0];
|
||||
|
||||
return cachedLightGrayColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a red color object (RGBA=[1.0, 0.0, 0.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)redColor
|
||||
{
|
||||
if (!cachedRedColor)
|
||||
cachedRedColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 0.0, 1.0]];
|
||||
|
||||
return cachedRedColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a white color object (RGBA=[1.0, 1.0, 1.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)whiteColor
|
||||
{
|
||||
if (!cachedWhiteColor)
|
||||
cachedWhiteColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 1.0, 1.0]];
|
||||
|
||||
return cachedWhiteColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a yellow color object (RGBA=[1.0, 1.0, 0.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)yellowColor
|
||||
{
|
||||
if (!cachedYellowColor)
|
||||
cachedYellowColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 0.0, 1.0]];
|
||||
|
||||
return cachedYellowColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a brown color object (RGBA=[0.6, 0.4, 0.2, 1.0])
|
||||
*/
|
||||
+ (CPColor)brownColor
|
||||
{
|
||||
if (!cachedBrownColor)
|
||||
cachedBrownColor = [[CPColor alloc] _initWithRGBA:[0.6, 0.4, 0.2, 1.0]];
|
||||
|
||||
return cachedBrownColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a cyan color object (RGBA=[0.0, 1.0, 1.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)cyanColor
|
||||
{
|
||||
if (!cachedCyanColor)
|
||||
cachedCyanColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 1.0, 1.0]];
|
||||
|
||||
return cachedCyanColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a magenta color object (RGBA=[1.0, 0.0, 1.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)magentaColor
|
||||
{
|
||||
if (!cachedMagentaColor)
|
||||
cachedMagentaColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 1.0, 1.0]];
|
||||
|
||||
return cachedMagentaColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a orange color object (RGBA=[1.0, 0.5, 0.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)orangeColor
|
||||
{
|
||||
if (!cachedOrangeColor)
|
||||
cachedOrangeColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.5, 0.0, 1.0]];
|
||||
|
||||
return cachedOrangeColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a purple color object (RGBA=[0.5, 0.0, 0.5, 1.0])
|
||||
*/
|
||||
+ (CPColor)purpleColor
|
||||
{
|
||||
if (!cachedPurpleColor)
|
||||
cachedPurpleColor = [[CPColor alloc] _initWithRGBA:[0.5, 0.0, 0.5, 1.0]];
|
||||
|
||||
return cachedPurpleColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a shadow looking color (RGBA=[0.0, 0.0, 0.0, 0.33])
|
||||
*/
|
||||
|
||||
+ (CPColor)shadowColor
|
||||
{
|
||||
if (!cachedShadowColor)
|
||||
cachedShadowColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0 / 3.0]];
|
||||
|
||||
return cachedShadowColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a clear color (RGBA=[0.0, 0.0, 0.0, 0.0])
|
||||
*/
|
||||
|
||||
+ (CPColor)clearColor
|
||||
{
|
||||
if (!cachedClearColor)
|
||||
cachedClearColor = [self colorWithCalibratedWhite:0.0 alpha:0.0];
|
||||
|
||||
return cachedClearColor;
|
||||
}
|
||||
|
||||
+ (CPColor)alternateSelectedControlColor
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]];
|
||||
}
|
||||
|
||||
+ (CPColor)secondarySelectedControlColor
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a color using a tile pattern with \c anImage
|
||||
@param the image to tile
|
||||
@return a tiled image color object
|
||||
*/
|
||||
+ (CPColor)colorWithPatternImage:(CPImage)anImage
|
||||
{
|
||||
return [[CPColor alloc] _initWithPatternImage:anImage];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a CPColor from a valid CSS RGB string. Example, "rgb(32,64,129)".
|
||||
|
||||
@param aString a CSS color string
|
||||
@return a color initialized to the value in the css string
|
||||
*/
|
||||
+ (CPColor)colorWithCSSString:(CPString)aString
|
||||
{
|
||||
return [[CPColor alloc] _initWithCSSString: aString];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithCSSString:(CPString)aString
|
||||
{
|
||||
if (aString.indexOf("rgb") == CPNotFound)
|
||||
return nil;
|
||||
|
||||
self = [super init];
|
||||
|
||||
var startingIndex = aString.indexOf("("),
|
||||
parts = aString.substring(startingIndex + 1).split(',');
|
||||
|
||||
_components = [
|
||||
parseInt(parts[0], 10) / 255.0,
|
||||
parseInt(parts[1], 10) / 255.0,
|
||||
parseInt(parts[2], 10) / 255.0,
|
||||
parts[3] ? parseFloat(parts[3], 10) : 1.0
|
||||
];
|
||||
|
||||
// We can't reuse aString as _cssString because the browser might not support the `rgba` syntax, and aString might
|
||||
// use it (issue #1413.)
|
||||
[self _initCSSStringFromComponents];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithRGBA:(CPArray)components
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_components = components;
|
||||
|
||||
[self _initCSSStringFromComponents];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_initCSSStringFromComponents
|
||||
{
|
||||
var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
|
||||
|
||||
_cssString = (hasAlpha ? "rgba(" : "rgb(") +
|
||||
parseInt(_components[0] * 255.0) + ", " +
|
||||
parseInt(_components[1] * 255.0) + ", " +
|
||||
parseInt(_components[2] * 255.0) +
|
||||
(hasAlpha ? (", " + _components[3]) : "") + ")";
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithPatternImage:(CPImage)anImage
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_patternImage = anImage;
|
||||
_cssString = "url(\"" + [_patternImage filename] + "\")";
|
||||
_components = [0.0, 0.0, 0.0, 1.0];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the image being used as the pattern for the tile in this color.
|
||||
*/
|
||||
- (CPImage)patternImage
|
||||
{
|
||||
return _patternImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the alpha component of this color.
|
||||
*/
|
||||
- (float)alphaComponent
|
||||
{
|
||||
return _components[3];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the blue component of this color.
|
||||
*/
|
||||
- (float)blueComponent
|
||||
{
|
||||
return _components[2];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the green component of this color.
|
||||
*/
|
||||
- (float)greenComponent
|
||||
{
|
||||
return _components[1];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the red component of this color.
|
||||
*/
|
||||
- (float)redComponent
|
||||
{
|
||||
return _components[0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the RGBA components of this color in an array.
|
||||
The index values are ordered as:
|
||||
<pre>
|
||||
<b>Index</b> <b>Component</b>
|
||||
0 Red
|
||||
1 Green
|
||||
2 Blue
|
||||
3 Alpha
|
||||
</pre>
|
||||
*/
|
||||
- (CPArray)components
|
||||
{
|
||||
return _components;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new color with the same RGB as the receiver but a new alpha component.
|
||||
|
||||
@param anAlphaComponent the alpha component for the new color
|
||||
|
||||
@return a new color object
|
||||
*/
|
||||
- (CPColor)colorWithAlphaComponent:(float)anAlphaComponent
|
||||
{
|
||||
var components = _components.slice();
|
||||
|
||||
components[components.length - 1] = anAlphaComponent;
|
||||
|
||||
return [[[self class] alloc] _initWithRGBA:components];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver. This method is a placeholder that does nothing but may be implemented in the future.
|
||||
*/
|
||||
- (CPColor)colorUsingColorSpaceName:(id)aColorSpaceName
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array with the HSB values for this color.
|
||||
The index values are ordered as:
|
||||
<pre>
|
||||
<b>Index</b> <b>Component</b>
|
||||
0 Hue
|
||||
1 Saturation
|
||||
2 Brightness
|
||||
</pre>
|
||||
*/
|
||||
- (CPArray)hsbComponents
|
||||
{
|
||||
var red = ROUND(_components[_redComponent] * 255.0),
|
||||
green = ROUND(_components[_greenComponent] * 255.0),
|
||||
blue = ROUND(_components[_blueComponent] * 255.0);
|
||||
|
||||
var max = MAX(red, green, blue),
|
||||
min = MIN(red, green, blue),
|
||||
delta = max - min;
|
||||
|
||||
var brightness = max / 255.0,
|
||||
saturation = (max != 0) ? delta / max : 0;
|
||||
|
||||
var hue;
|
||||
|
||||
if (saturation == 0)
|
||||
{
|
||||
hue = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var rr = (max - red) / delta,
|
||||
gr = (max - green) / delta,
|
||||
br = (max - blue) / delta;
|
||||
|
||||
if (red == max)
|
||||
hue = br - gr;
|
||||
else if (green == max)
|
||||
hue = 2 + rr - br;
|
||||
else
|
||||
hue = 4 + gr - rr;
|
||||
|
||||
hue /= 6;
|
||||
if (hue < 0)
|
||||
hue++;
|
||||
}
|
||||
|
||||
return [
|
||||
ROUND(hue * 360.0),
|
||||
ROUND(saturation * 100.0),
|
||||
ROUND(brightness * 100.0)
|
||||
];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CSS representation of this color. The color will
|
||||
be in one of the following forms:
|
||||
<pre>
|
||||
rgb(22,44,88)
|
||||
rgba(22,44,88,0.5) // if there is an alpha
|
||||
url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
|
||||
</pre>
|
||||
*/
|
||||
- (CPString)cssString
|
||||
{
|
||||
return _cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a 6 character long hex string of this color.
|
||||
*/
|
||||
- (CPString)hexString
|
||||
{
|
||||
return rgbToHex([self redComponent], [self greenComponent], [self blueComponent]);
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(CPColor)aColor
|
||||
{
|
||||
if (!aColor)
|
||||
return NO;
|
||||
|
||||
if (aColor === self)
|
||||
return YES;
|
||||
|
||||
if (![aColor isKindOfClass:CPColor])
|
||||
return NO;
|
||||
|
||||
if (_patternImage || [aColor patternImage])
|
||||
return [_patternImage isEqual:[aColor patternImage]];
|
||||
|
||||
// We don't require the components to be equal beyond 8 bits since otherwise
|
||||
// simple rounding errors will make two colours which are exactly the same on
|
||||
// screen compare unequal.
|
||||
return ROUND([self redComponent] * 255.0) == ROUND([aColor redComponent] * 255.0) &&
|
||||
ROUND([self greenComponent] * 255.0) == ROUND([aColor greenComponent] * 255.0) &&
|
||||
ROUND([self blueComponent] * 255.0) == ROUND([aColor blueComponent] * 255.0) &&
|
||||
[self alphaComponent] == [aColor alphaComponent];
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
var description = [super description],
|
||||
patternImage = [self patternImage];
|
||||
|
||||
if (!patternImage)
|
||||
return description + " " + [self cssString];
|
||||
|
||||
description += " {\n";
|
||||
|
||||
if ([patternImage isThreePartImage] || [patternImage isNinePartImage])
|
||||
{
|
||||
var slices = [patternImage imageSlices];
|
||||
|
||||
if ([patternImage isThreePartImage])
|
||||
description += " orientation: " + ([patternImage isVertical] ? "vertical" : "horizontal") + ",\n";
|
||||
|
||||
description += " patternImage (" + slices.length + " part): [\n";
|
||||
|
||||
for (var i = 0; i < slices.length; ++i)
|
||||
{
|
||||
var imgDescription = [slices[i] description];
|
||||
|
||||
description += imgDescription.replace(/^/mg, " ") + ",\n";
|
||||
}
|
||||
|
||||
description = description.substr(0, description.length - 2) + "\n ]\n}";
|
||||
}
|
||||
else
|
||||
description += [patternImage description].replace(/^/mg, " ") + "\n}";
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPColor (CoreGraphicsExtensions)
|
||||
|
||||
/*!
|
||||
Set's the receiver to be the fill and stroke color in the current graphics context
|
||||
*/
|
||||
- (void)set
|
||||
{
|
||||
[self setFill];
|
||||
[self setStroke];
|
||||
}
|
||||
|
||||
/*!
|
||||
Set's the receiver to be the fill color in the current graphics context
|
||||
*/
|
||||
- (void)setFill
|
||||
{
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSetFillColor(ctx, self);
|
||||
}
|
||||
|
||||
/*!
|
||||
Set's the receiver to be the stroke color in the current graphics context
|
||||
*/
|
||||
- (void)setStroke
|
||||
{
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSetStrokeColor(ctx, self);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPColor (Debugging)
|
||||
|
||||
+ (CPColor)randomColor
|
||||
{
|
||||
return [CPColor colorWithRed:RAND() green:RAND() blue:RAND() alpha:1.0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/// @cond IGNORE
|
||||
var CPColorComponentsKey = @"CPColorComponentsKey",
|
||||
CPColorPatternImageKey = @"CPColorPatternImageKey";
|
||||
/// @endcond
|
||||
|
||||
@implementation CPColor (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes this color from the data archived in a coder.
|
||||
@param aCoder the coder from which the color will be loaded
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if ([aCoder containsValueForKey:CPColorPatternImageKey])
|
||||
return [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]];
|
||||
|
||||
return [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Archives this color into a coder.
|
||||
@param aCoder the coder into which the color will be archived.
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (_patternImage)
|
||||
[aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey];
|
||||
else
|
||||
[aCoder encodeObject:_components forKey:CPColorComponentsKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/// @cond IGNORE
|
||||
var hexCharacters = "0123456789ABCDEF";
|
||||
|
||||
/*
|
||||
Used for the CPColor +colorWithHexString: implementation.
|
||||
Returns an array of rgb components.
|
||||
*/
|
||||
var hexToRGB = function(hex)
|
||||
{
|
||||
if (hex.length == 3)
|
||||
hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
|
||||
|
||||
if (hex.length != 6)
|
||||
return null;
|
||||
|
||||
hex = hex.toUpperCase();
|
||||
|
||||
for (var i = 0; i < hex.length; i++)
|
||||
if (hexCharacters.indexOf(hex.charAt(i)) == -1)
|
||||
return null;
|
||||
|
||||
var red = (hexCharacters.indexOf(hex.charAt(0)) * 16 + hexCharacters.indexOf(hex.charAt(1))) / 255.0,
|
||||
green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0,
|
||||
blue = (hexCharacters.indexOf(hex.charAt(4)) * 16 + hexCharacters.indexOf(hex.charAt(5))) / 255.0;
|
||||
|
||||
return [red, green, blue, 1.0];
|
||||
};
|
||||
|
||||
var rgbToHex = function(r,g,b)
|
||||
{
|
||||
return byteToHex(r) + byteToHex(g) + byteToHex(b);
|
||||
};
|
||||
|
||||
var byteToHex = function(n)
|
||||
{
|
||||
if (!n || isNaN(n))
|
||||
return "00";
|
||||
|
||||
n = FLOOR(MIN(255, MAX(0, 256 * n)));
|
||||
|
||||
return hexCharacters.charAt((n - n % 16) / 16) +
|
||||
hexCharacters.charAt(n % 16);
|
||||
};
|
||||
|
||||
/// @endcond
|
||||
@@ -1,668 +0,0 @@
|
||||
/*
|
||||
* CPColorPanel.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* 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 "CPApplication.j"
|
||||
@import "CPButton.j"
|
||||
@import "CPCookie.j"
|
||||
@import "CPPanel.j"
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
CPColorPanelColorDidChangeNotification = @"CPColorPanelColorDidChangeNotification";
|
||||
|
||||
var PREVIEW_HEIGHT = 20.0,
|
||||
TOOLBAR_HEIGHT = 32.0,
|
||||
SWATCH_HEIGHT = 14.0,
|
||||
ICON_WIDTH = 32.0,
|
||||
ICON_PADDING = 12.0;
|
||||
|
||||
var SharedColorPanel = nil,
|
||||
ColorPickerClasses = [];
|
||||
|
||||
/*
|
||||
A color wheel
|
||||
@global
|
||||
@group CPColorPanelMode
|
||||
*/
|
||||
CPWheelColorPickerMode = 1;
|
||||
/*
|
||||
Slider based picker
|
||||
@global
|
||||
@group CPColorPanelMode
|
||||
*/
|
||||
CPSliderColorPickerMode = 2;
|
||||
|
||||
CPColorPickerViewWidth = 265;
|
||||
CPColorPickerViewHeight = 370;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPColorPanel
|
||||
|
||||
CPColorPanel provides a reusable panel that can be used
|
||||
displayed on screen to prompt the user for a color selection. To
|
||||
obtain the panel, call the \c +sharedColorPanel method.
|
||||
*/
|
||||
@implementation CPColorPanel : CPPanel
|
||||
{
|
||||
_CPColorPanelToolbar _toolbar;
|
||||
_CPColorPanelSwatches _swatchView;
|
||||
_CPColorPanelPreview _previewView;
|
||||
|
||||
CPSlider _opacitySlider;
|
||||
|
||||
CPArray _colorPickers;
|
||||
CPView _currentView;
|
||||
id _activePicker;
|
||||
|
||||
CPColor _color;
|
||||
|
||||
id _target;
|
||||
SEL _action;
|
||||
|
||||
int _mode;
|
||||
}
|
||||
|
||||
/*!
|
||||
A list of color pickers is collected here, and any color panel created will contain
|
||||
any picker in this list up to this point. In other words, call before creating a color panel.
|
||||
*/
|
||||
+ (void)provideColorPickerClass:(Class)aColorPickerSubclass
|
||||
{
|
||||
ColorPickerClasses.push(aColorPickerSubclass);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns (and if necessary, creates) the shared color panel.
|
||||
*/
|
||||
+ (CPColorPanel)sharedColorPanel
|
||||
{
|
||||
if (!SharedColorPanel)
|
||||
SharedColorPanel = [[CPColorPanel alloc] init];
|
||||
|
||||
return SharedColorPanel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the mode for the shared color panel.
|
||||
@param mode the mode to which the color panel will be set
|
||||
*/
|
||||
+ (void)setPickerMode:(CPColorPanelMode)mode
|
||||
{
|
||||
var panel = [CPColorPanel sharedColorPanel];
|
||||
[panel setMode:mode];
|
||||
}
|
||||
|
||||
/*
|
||||
To obtain the color panel, use \c +sharedColorPanel.
|
||||
@ignore
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super initWithContentRect:CGRectMake(500.0, 50.0, 219.0, 370.0)
|
||||
styleMask:(CPTitledWindowMask | CPClosableWindowMask | CPResizableWindowMask)];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
|
||||
|
||||
[self setTitle:@"Color Panel"];
|
||||
[self setLevel:CPFloatingWindowLevel];
|
||||
|
||||
[self setFloatingPanel:YES];
|
||||
[self setBecomesKeyOnlyIfNeeded:YES];
|
||||
|
||||
[self setMinSize:CGSizeMake(219.0, 363.0)];
|
||||
[self setMaxSize:CGSizeMake(323.0, 537.0)];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the color of the panel, and updates the picker. Also posts a \c CPColorPanelDidChangeNotification.
|
||||
*/
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
_color = aColor;
|
||||
[_previewView setBackgroundColor:_color];
|
||||
|
||||
[CPApp sendAction:@selector(changeColor:) to:nil from:self];
|
||||
|
||||
if (_target && _action)
|
||||
[CPApp sendAction:_action to:_target from:self];
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPColorPanelColorDidChangeNotification
|
||||
object:self];
|
||||
|
||||
[_activePicker setColor:_color];
|
||||
[_opacitySlider setFloatValue:[_color alphaComponent]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the selected color of the panel and optionally updates the picker.
|
||||
@param bool whether or not to update the picker
|
||||
@ignore
|
||||
*/
|
||||
- (void)setColor:(CPColor)aColor updatePicker:(BOOL)bool
|
||||
{
|
||||
[self setColor:aColor];
|
||||
|
||||
if (bool)
|
||||
[_activePicker setColor:_color];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the panel's currently selected color.
|
||||
*/
|
||||
- (CPColor)color
|
||||
{
|
||||
return _color;
|
||||
}
|
||||
|
||||
- (float)opacity
|
||||
{
|
||||
return [_opacitySlider floatValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the target for the color panel. Messages are sent
|
||||
to the target when colors are selected in the panel.
|
||||
*/
|
||||
- (void)setTarget:(id)aTarget
|
||||
{
|
||||
_target = aTarget;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current target. The target receives messages
|
||||
when colors are selected in the panel.
|
||||
*/
|
||||
- (id)target
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the action that gets sent to the target.
|
||||
This action is sent whenever a color is selected in the panel.
|
||||
@param anAction the action that will be sent
|
||||
*/
|
||||
- (void)setAction:(selector)anAction
|
||||
{
|
||||
_action = anAction;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current target action.
|
||||
*/
|
||||
- (selector)action
|
||||
{
|
||||
return _action;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the mode (look) of the color panel.
|
||||
@param mode the mode in which to display the color panel
|
||||
*/
|
||||
- (void)setMode:(CPColorPanelMode)mode
|
||||
{
|
||||
_mode = mode;
|
||||
}
|
||||
|
||||
- (void)_setPicker:(id)sender
|
||||
{
|
||||
var picker = _colorPickers[[sender tag]],
|
||||
view = [picker provideNewView:NO];
|
||||
|
||||
if (!view)
|
||||
view = [picker provideNewView:YES];
|
||||
|
||||
if (view == _currentView)
|
||||
return;
|
||||
|
||||
if (_currentView)
|
||||
[view setFrame:[_currentView frame]];
|
||||
else
|
||||
{
|
||||
var height = (TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5 + SWATCH_HEIGHT + 32),
|
||||
bounds = [[self contentView] bounds];
|
||||
|
||||
[view setFrameSize:CPSizeMake(bounds.size.width - 10, bounds.size.height - height)];
|
||||
[view setFrameOrigin:CPPointMake(5, height)];
|
||||
}
|
||||
|
||||
[_currentView removeFromSuperview];
|
||||
[[self contentView] addSubview:view];
|
||||
|
||||
_currentView = view;
|
||||
_activePicker = picker;
|
||||
|
||||
[picker setColor:[self color]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the color panel's current display mode.
|
||||
*/
|
||||
- (CPColorPanelMode)mode
|
||||
{
|
||||
return _mode;
|
||||
}
|
||||
|
||||
- (void)orderFront:(id)aSender
|
||||
{
|
||||
[self _loadContentsIfNecessary];
|
||||
[super orderFront:aSender];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_loadContentsIfNecessary
|
||||
{
|
||||
if (_toolbar)
|
||||
return;
|
||||
|
||||
if (!_color)
|
||||
_color = [CPColor whiteColor];
|
||||
|
||||
_colorPickers = [];
|
||||
|
||||
var count = [ColorPickerClasses count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var currentPickerClass = ColorPickerClasses[i],
|
||||
currentPicker = [[currentPickerClass alloc] initWithPickerMask:0 colorPanel:self];
|
||||
|
||||
_colorPickers.push(currentPicker);
|
||||
}
|
||||
|
||||
var contentView = [self contentView],
|
||||
bounds = [contentView bounds];
|
||||
|
||||
_toolbar = [[CPView alloc] initWithFrame:CGRectMake(0, 6, CGRectGetWidth(bounds), TOOLBAR_HEIGHT)];
|
||||
[_toolbar setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
var totalToolbarWidth = count * ICON_WIDTH + (count - 1) * ICON_PADDING,
|
||||
leftOffset = (CGRectGetWidth(bounds) - totalToolbarWidth) / 2.0,
|
||||
buttonForLater = nil;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var image = [_colorPickers[i] provideNewButtonImage],
|
||||
highlightImage = [_colorPickers[i] provideNewAlternateButtonImage],
|
||||
button = [[CPButton alloc] initWithFrame:CGRectMake(leftOffset + i * (ICON_WIDTH + ICON_PADDING), 0, ICON_WIDTH, ICON_WIDTH)];
|
||||
|
||||
[button setTag:i];
|
||||
[button setTarget:self];
|
||||
[button setAction:@selector(_setPicker:)];
|
||||
[button setBordered:NO];
|
||||
[button setAutoresizingMask:CPViewMinXMargin | CPViewMaxXMargin];
|
||||
|
||||
[button setImage:image];
|
||||
[button setAlternateImage:highlightImage];
|
||||
|
||||
[_toolbar addSubview:button];
|
||||
|
||||
if (!buttonForLater)
|
||||
buttonForLater = button;
|
||||
}
|
||||
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
var previewBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10, CGRectGetWidth(bounds) - 86, PREVIEW_HEIGHT)];
|
||||
|
||||
_previewView = [[_CPColorPanelPreview alloc] initWithFrame:CGRectInset([previewBox bounds], 2.0, 2.0)];
|
||||
|
||||
[_previewView setColorPanel:self];
|
||||
[_previewView setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[previewBox setBackgroundColor:[CPColor colorWithWhite:0.8 alpha:1.0]];
|
||||
[previewBox setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[previewBox addSubview:_previewView];
|
||||
|
||||
var _previewLabel = [[CPTextField alloc] initWithFrame:CGRectMake(10, TOOLBAR_HEIGHT + 10, 60, 15)];
|
||||
[_previewLabel setStringValue:"Preview:"];
|
||||
[_previewLabel setTextColor:[CPColor blackColor]];
|
||||
[_previewLabel setAlignment:CPRightTextAlignment];
|
||||
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
var swatchBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5, CGRectGetWidth(bounds) - 86, SWATCH_HEIGHT + 2.0)];
|
||||
|
||||
[swatchBox setBackgroundColor:[CPColor colorWithWhite:0.8 alpha:1.0]];
|
||||
[swatchBox setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
_swatchView = [[_CPColorPanelSwatches alloc] initWithFrame:CGRectInset([swatchBox bounds], 1.0, 1.0)];
|
||||
|
||||
[_swatchView setColorPanel:self];
|
||||
[_swatchView setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[swatchBox addSubview:_swatchView];
|
||||
|
||||
var _swatchLabel = [[CPTextField alloc] initWithFrame:CGRectMake(10, TOOLBAR_HEIGHT + 8 + PREVIEW_HEIGHT + 6, 60, 15)];
|
||||
[_swatchLabel setStringValue:"Swatches:"];
|
||||
[_swatchLabel setTextColor:[CPColor blackColor]];
|
||||
[_swatchLabel setAlignment:CPRightTextAlignment];
|
||||
|
||||
|
||||
var opacityLabel = [[CPTextField alloc] initWithFrame:CGRectMake(10, TOOLBAR_HEIGHT + PREVIEW_HEIGHT + 35, 60, 20)];
|
||||
[opacityLabel setStringValue:"Opacity:"];
|
||||
[opacityLabel setTextColor:[CPColor blackColor]];
|
||||
[opacityLabel setAlignment:CPRightTextAlignment];
|
||||
|
||||
_opacitySlider = [[CPSlider alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + PREVIEW_HEIGHT + 34, CGRectGetWidth(bounds) - 86, 20.0)];
|
||||
|
||||
[_opacitySlider setMinValue:0.0];
|
||||
[_opacitySlider setMaxValue:1.0];
|
||||
[_opacitySlider setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[_opacitySlider setTarget:self];
|
||||
[_opacitySlider setAction:@selector(setOpacity:)];
|
||||
|
||||
[contentView addSubview:_toolbar];
|
||||
[contentView addSubview:previewBox];
|
||||
[contentView addSubview:_previewLabel];
|
||||
[contentView addSubview:swatchBox];
|
||||
[contentView addSubview:_swatchLabel];
|
||||
[contentView addSubview:opacityLabel];
|
||||
[contentView addSubview:_opacitySlider];
|
||||
|
||||
_target = nil;
|
||||
_action = nil;
|
||||
_activePicker = nil;
|
||||
|
||||
[_previewView setBackgroundColor:_color];
|
||||
|
||||
if (buttonForLater)
|
||||
[self _setPicker:buttonForLater];
|
||||
}
|
||||
|
||||
- (void)setOpacity:(id)sender
|
||||
{
|
||||
var components = [[self color] components],
|
||||
alpha = [sender floatValue];
|
||||
|
||||
[self setColor:[_color colorWithAlphaComponent:alpha] updatePicker:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
CPColorDragType = "CPColorDragType";
|
||||
|
||||
var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
|
||||
/* @ignore */
|
||||
@implementation _CPColorPanelSwatches : CPView
|
||||
{
|
||||
CPArray _swatches;
|
||||
CPColor _dragColor;
|
||||
CPColorPanel _colorPanel;
|
||||
CPCookie _swatchCookie;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
[self setBackgroundColor:[CPColor grayColor]];
|
||||
|
||||
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
|
||||
|
||||
var whiteColor = [CPColor whiteColor];
|
||||
|
||||
_swatchCookie = [[CPCookie alloc] initWithName:CPColorPanelSwatchesCookie];
|
||||
var colorList = [self startingColorList];
|
||||
|
||||
_swatches = [];
|
||||
|
||||
for (var i = 0; i < 50; i++)
|
||||
{
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
var view = [[CPView alloc] initWithFrame:CPRectMake(13 * i + 1, 1, 12, 12)],
|
||||
fillView = [[CPView alloc] initWithFrame:CGRectInset([view bounds], 1.0, 1.0)];
|
||||
|
||||
[view setBackgroundColor:whiteColor];
|
||||
[fillView setBackgroundColor:(i < colorList.length) ? colorList[i] : whiteColor];
|
||||
|
||||
[view addSubview:fillView];
|
||||
|
||||
[self addSubview:view];
|
||||
|
||||
_swatches.push(view);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CPArray)startingColorList
|
||||
{
|
||||
var cookieValue = [_swatchCookie value];
|
||||
|
||||
if (!cookieValue)
|
||||
{
|
||||
return [
|
||||
[CPColor blackColor],
|
||||
[CPColor darkGrayColor],
|
||||
[CPColor grayColor],
|
||||
[CPColor lightGrayColor],
|
||||
[CPColor whiteColor],
|
||||
[CPColor redColor],
|
||||
[CPColor greenColor],
|
||||
[CPColor blueColor],
|
||||
[CPColor yellowColor]
|
||||
];
|
||||
}
|
||||
|
||||
var cookieValue = eval(cookieValue),
|
||||
result = [];
|
||||
|
||||
for (var i = 0; i < cookieValue.length; i++)
|
||||
result.push([CPColor colorWithHexString:cookieValue[i]]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CPArray)saveColorList
|
||||
{
|
||||
var result = [];
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
for (var i = 0; i < _swatches.length; i++)
|
||||
result.push([[[_swatches[i] subviews][0] backgroundColor] hexString]);
|
||||
|
||||
var future = new Date();
|
||||
future.setYear(2019);
|
||||
|
||||
[_swatchCookie setValue:JSON.stringify(result) expires:future domain:nil];
|
||||
}
|
||||
|
||||
- (void)setColorPanel:(CPColorPanel)panel
|
||||
{
|
||||
_colorPanel = panel;
|
||||
}
|
||||
|
||||
- (CPColorPanel)colorPanel
|
||||
{
|
||||
return _colorPanel;
|
||||
}
|
||||
|
||||
- (CPColor)colorAtIndex:(int)index
|
||||
{
|
||||
return [[_swatches[index] subviews][0] backgroundColor];
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)aColor atIndex:(int)index
|
||||
{
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
[[_swatches[index] subviews][0] setBackgroundColor:aColor];
|
||||
[self saveColorList];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
bounds = [self bounds];
|
||||
|
||||
if (!CGRectContainsPoint(bounds, point) || point.x > [self bounds].size.width - 1 || point.x < 1)
|
||||
return NO;
|
||||
|
||||
[_colorPanel setColor:[self colorAtIndex:FLOOR(point.x / 13)] updatePicker:YES];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if (point.x > [self bounds].size.width - 1 || point.x < 1)
|
||||
return NO;
|
||||
|
||||
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
|
||||
|
||||
var swatch = _swatches[FLOOR(point.x / 13)];
|
||||
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
_dragColor = [[swatch subviews][0] backgroundColor];
|
||||
|
||||
var bounds = CGRectMakeCopy([swatch bounds]);
|
||||
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
var dragView = [[CPView alloc] initWithFrame:bounds],
|
||||
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
|
||||
|
||||
[dragView setBackgroundColor:[CPColor blackColor]];
|
||||
[dragFillView setBackgroundColor:_dragColor];
|
||||
|
||||
[dragView addSubview:dragFillView];
|
||||
|
||||
[self dragView:dragView
|
||||
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
|
||||
offset:CGPointMake(0.0, 0.0)
|
||||
event:anEvent
|
||||
pasteboard:nil
|
||||
source:self
|
||||
slideBack:YES];
|
||||
}
|
||||
|
||||
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
|
||||
{
|
||||
if (aType == CPColorDragType)
|
||||
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_dragColor] forType:aType];
|
||||
}
|
||||
|
||||
- (void)performDragOperation:(id <CPDraggingInfo>)aSender
|
||||
{
|
||||
var location = [self convertPoint:[aSender draggingLocation] fromView:nil],
|
||||
pasteboard = [aSender draggingPasteboard],
|
||||
swatch = nil;
|
||||
|
||||
if (![pasteboard availableTypeFromArray:[CPColorDragType]] || location.x > [self bounds].size.width - 1 || location.x < 1)
|
||||
return NO;
|
||||
|
||||
[self setColor:[CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]] atIndex:FLOOR(location.x / 13)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* @ignore */
|
||||
@implementation _CPColorPanelPreview : CPView
|
||||
{
|
||||
CPColorPanel _colorPanel;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setColorPanel:(CPColorPanel)aPanel
|
||||
{
|
||||
_colorPanel = aPanel;
|
||||
}
|
||||
|
||||
- (CPColorPanel)colorPanel
|
||||
{
|
||||
return _colorPanel;
|
||||
}
|
||||
|
||||
- (void)performDragOperation:(id <CPDraggingInfo>)aSender
|
||||
{
|
||||
var pasteboard = [aSender draggingPasteboard];
|
||||
|
||||
if (![pasteboard availableTypeFromArray:[CPColorDragType]])
|
||||
return NO;
|
||||
|
||||
var color = [CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]];
|
||||
[_colorPanel setColor:color updatePicker:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPColorDragType] owner:self];
|
||||
|
||||
var bounds = CPRectMake(0, 0, 15, 15);
|
||||
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
var dragView = [[CPView alloc] initWithFrame:bounds],
|
||||
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
|
||||
|
||||
[dragView setBackgroundColor:[CPColor blackColor]];
|
||||
[dragFillView setBackgroundColor:[self backgroundColor]];
|
||||
|
||||
[dragView addSubview:dragFillView];
|
||||
|
||||
[self dragView:dragView
|
||||
at:CPPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
|
||||
offset:CPPointMake(0.0, 0.0)
|
||||
event:anEvent
|
||||
pasteboard:nil
|
||||
source:self
|
||||
slideBack:YES];
|
||||
}
|
||||
|
||||
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
|
||||
{
|
||||
if (aType == CPColorDragType)
|
||||
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:aType];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@import "CPColorPicker.j"
|
||||
@import "CPSliderColorPicker.j"
|
||||
|
||||
[CPColorPanel provideColorPickerClass:CPColorWheelColorPicker];
|
||||
[CPColorPanel provideColorPickerClass:CPSliderColorPicker];
|
||||
@@ -1,380 +0,0 @@
|
||||
/*
|
||||
* CPColorPicker.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* 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 "CPView.j"
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPColorPicker
|
||||
|
||||
CPColorPicker is an abstract superclass for all color picker subclasses. If you want a particular color picker, use CPColorPanel's \c +setPickerMode: method. The simplest way to implement your own color picker is to create a subclass of CPColorPicker.
|
||||
*/
|
||||
@implementation CPColorPicker : CPObject
|
||||
{
|
||||
CPColorPanel _panel;
|
||||
int _mask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the color picker.
|
||||
@param aMask a unique unsigned int identifying your color picker
|
||||
@param aPanel the color panel that owns this picker
|
||||
*/
|
||||
- (id)initWithPickerMask:(int)aMask colorPanel:(CPColorPanel)aPanel
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_panel = aPanel;
|
||||
_mask = aMask;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the color panel that owns this picker
|
||||
*/
|
||||
- (CPColorPanel)colorPanel
|
||||
{
|
||||
return _panel;
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME Not implemented.
|
||||
@return \c nil
|
||||
@ignore
|
||||
*/
|
||||
- (CPImage)provideNewButtonImage
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the color picker's mode.
|
||||
@param mode the color panel mode
|
||||
*/
|
||||
- (void)setMode:(CPColorPanelMode)mode
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the picker's color.
|
||||
@param aColor the new color for the picker
|
||||
*/
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
The wheel mode color picker.
|
||||
@ignore
|
||||
*/
|
||||
@implementation CPColorWheelColorPicker : CPColorPicker
|
||||
{
|
||||
CPView _pickerView;
|
||||
CPView _brightnessSlider;
|
||||
__CPColorWheel _hueSaturationView;
|
||||
|
||||
CPColor _cachedColor;
|
||||
}
|
||||
|
||||
- (id)initWithPickerMask:(int)mask colorPanel:(CPColorPanel)owningColorPanel
|
||||
{
|
||||
return [super initWithPickerMask:mask colorPanel: owningColorPanel];
|
||||
}
|
||||
|
||||
- (id)initView
|
||||
{
|
||||
var aFrame = _CGRectMake(0, 0, CPColorPickerViewWidth, CPColorPickerViewHeight);
|
||||
|
||||
_pickerView = [[CPView alloc] initWithFrame:aFrame];
|
||||
[_pickerView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
_brightnessSlider = [[CPSlider alloc] initWithFrame:_CGRectMake(0, (aFrame.size.height - 34), aFrame.size.width, 15)];
|
||||
|
||||
[_brightnessSlider setValue:15.0 forThemeAttribute:@"track-width"];
|
||||
[_brightnessSlider setValue:[CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPColorPicker class]] pathForResource:@"brightness_bar.png"]]] forThemeAttribute:@"track-color"];
|
||||
|
||||
[_brightnessSlider setMinValue:0.0];
|
||||
[_brightnessSlider setMaxValue:100.0];
|
||||
[_brightnessSlider setFloatValue:100.0];
|
||||
|
||||
[_brightnessSlider setTarget:self];
|
||||
[_brightnessSlider setAction:@selector(brightnessSliderDidChange:)];
|
||||
[_brightnessSlider setAutoresizingMask:CPViewWidthSizable | CPViewMinYMargin];
|
||||
|
||||
_hueSaturationView = [[__CPColorWheel alloc] initWithFrame:_CGRectMake(0, 0, aFrame.size.width, aFrame.size.height - 38)];
|
||||
[_hueSaturationView setDelegate:self];
|
||||
[_hueSaturationView setAutoresizingMask:(CPViewWidthSizable | CPViewHeightSizable)];
|
||||
|
||||
[_pickerView addSubview:_hueSaturationView];
|
||||
[_pickerView addSubview:_brightnessSlider];
|
||||
}
|
||||
|
||||
- (void)brightnessSliderDidChange:(id)sender
|
||||
{
|
||||
[self updateColor];
|
||||
}
|
||||
|
||||
- (void)colorWheelDidChange:(id)sender
|
||||
{
|
||||
[self updateColor];
|
||||
}
|
||||
|
||||
- (void)updateColor
|
||||
{
|
||||
var hue = [_hueSaturationView angle],
|
||||
saturation = [_hueSaturationView distance],
|
||||
brightness = [_brightnessSlider floatValue];
|
||||
|
||||
[_hueSaturationView setWheelBrightness:brightness / 100.0];
|
||||
[_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hue saturation:saturation brightness:100]];
|
||||
|
||||
var colorPanel = [self colorPanel],
|
||||
opacity = [colorPanel opacity];
|
||||
|
||||
_cachedColor = [CPColor colorWithHue:hue saturation:saturation brightness:brightness alpha:opacity];
|
||||
|
||||
[[self colorPanel] setColor:_cachedColor];
|
||||
}
|
||||
|
||||
- (BOOL)supportsMode:(int)mode
|
||||
{
|
||||
return (mode == CPWheelColorPickerMode) ? YES : NO;
|
||||
}
|
||||
|
||||
- (int)currentMode
|
||||
{
|
||||
return CPWheelColorPickerMode;
|
||||
}
|
||||
|
||||
- (CPView)provideNewView:(BOOL)initialRequest
|
||||
{
|
||||
if (initialRequest)
|
||||
[self initView];
|
||||
|
||||
return _pickerView;
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)newColor
|
||||
{
|
||||
if ([newColor isEqual:_cachedColor])
|
||||
return;
|
||||
|
||||
var hsb = [newColor hsbComponents];
|
||||
|
||||
[_hueSaturationView setPositionToColor:newColor];
|
||||
[_brightnessSlider setFloatValue:hsb[2]];
|
||||
[_hueSaturationView setWheelBrightness:hsb[2] / 100.0];
|
||||
|
||||
[_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hsb[0] saturation:hsb[1] brightness:100]];
|
||||
}
|
||||
|
||||
- (CPImage)provideNewButtonImage
|
||||
{
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:CPColorPicker] pathForResource:"wheel_button.png"] size:CGSizeMake(32, 32)];
|
||||
}
|
||||
|
||||
- (CPImage)provideNewAlternateButtonImage
|
||||
{
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:CPColorPicker] pathForResource:"wheel_button_h.png"] size:CGSizeMake(32, 32)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* @ignore */
|
||||
@implementation __CPColorWheel : CPView
|
||||
{
|
||||
DOMElement _wheelImage;
|
||||
DOMElement _blackWheelImage;
|
||||
|
||||
CPView _crosshair;
|
||||
|
||||
id _delegate;
|
||||
|
||||
float _angle;
|
||||
float _distance;
|
||||
|
||||
float _radius;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var path = [[CPBundle bundleForClass:CPColorPicker] pathForResource:@"wheel.png"];
|
||||
|
||||
_wheelImage = new Image();
|
||||
_wheelImage.src = path;
|
||||
_wheelImage.style.position = "absolute";
|
||||
|
||||
path = [[CPBundle bundleForClass:CPColorPicker] pathForResource:@"wheel_black.png"];
|
||||
|
||||
_blackWheelImage = new Image();
|
||||
_blackWheelImage.src = path;
|
||||
_blackWheelImage.style.opacity = "0";
|
||||
_blackWheelImage.style.filter = "alpha(opacity=0)"
|
||||
_blackWheelImage.style.position = "absolute";
|
||||
|
||||
_DOMElement.appendChild(_wheelImage);
|
||||
_DOMElement.appendChild(_blackWheelImage);
|
||||
#endif
|
||||
|
||||
[self setWheelSize:aFrame.size];
|
||||
|
||||
_crosshair = [[CPView alloc] initWithFrame:CGRectMake(_radius - 2, _radius - 2, 4, 4)];
|
||||
[_crosshair setBackgroundColor:[CPColor blackColor]];
|
||||
|
||||
var view = [[CPView alloc] initWithFrame:CGRectInset([_crosshair bounds], 1.0, 1.0)];
|
||||
[view setBackgroundColor:[CPColor whiteColor]];
|
||||
|
||||
[_crosshair addSubview:view];
|
||||
|
||||
[self addSubview:_crosshair];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setWheelBrightness:(float)brightness
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_blackWheelImage.style.opacity = 1.0 - brightness;
|
||||
_blackWheelImage.style.filter = "alpha(opacity=" + (1.0 - brightness) * 100 + ")"
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CPSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
[self setWheelSize:aSize];
|
||||
}
|
||||
|
||||
- (void)setWheelSize:(CPSize)aSize
|
||||
{
|
||||
var min = MIN(aSize.width, aSize.height);
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_blackWheelImage.style.width = min;
|
||||
_blackWheelImage.style.height = min;
|
||||
_blackWheelImage.width = min;
|
||||
_blackWheelImage.height = min;
|
||||
_blackWheelImage.style.top = (aSize.height - min) / 2.0 + "px";
|
||||
_blackWheelImage.style.left = (aSize.width - min) / 2.0 + "px";
|
||||
|
||||
_wheelImage.style.width = min;
|
||||
_wheelImage.style.height = min;
|
||||
_wheelImage.width = min;
|
||||
_wheelImage.height = min;
|
||||
_wheelImage.style.top = (aSize.height - min) / 2.0 + "px";
|
||||
_wheelImage.style.left = (aSize.width - min) / 2.0 + "px";
|
||||
#endif
|
||||
|
||||
_radius = min / 2.0;
|
||||
|
||||
[self setAngle:[self degreesToRadians:_angle] distance:(_distance / 100.0) * _radius];
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
}
|
||||
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
- (float)angle
|
||||
{
|
||||
return _angle;
|
||||
}
|
||||
|
||||
- (float)distance
|
||||
{
|
||||
return _distance;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[self reposition:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
[self reposition:anEvent];
|
||||
}
|
||||
|
||||
- (void)reposition:(CPEvent)anEvent
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
midX = CGRectGetMidX(bounds),
|
||||
midY = CGRectGetMidY(bounds),
|
||||
distance = MIN(SQRT((location.x - midX) * (location.x - midX) + (location.y - midY) * (location.y - midY)), _radius),
|
||||
angle = ATAN2(location.y - midY, location.x - midX);
|
||||
|
||||
[self setAngle:angle distance:distance];
|
||||
|
||||
[_delegate colorWheelDidChange:self];
|
||||
}
|
||||
|
||||
- (void)setAngle:(int)angle distance:(float)distance
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
midX = CGRectGetMidX(bounds),
|
||||
midY = CGRectGetMidY(bounds);
|
||||
|
||||
_angle = [self radiansToDegrees:angle];
|
||||
_distance = (distance / _radius) * 100.0;
|
||||
|
||||
[_crosshair setFrameOrigin:CPPointMake(COS(angle) * distance + midX - 2.0, SIN(angle) * distance + midY - 2.0)];
|
||||
}
|
||||
|
||||
- (void)setPositionToColor:(CPColor)aColor
|
||||
{
|
||||
var hsb = [aColor hsbComponents],
|
||||
bounds = [self bounds],
|
||||
angle = [self degreesToRadians:hsb[0]],
|
||||
distance = (hsb[1] / 100.0) * _radius;
|
||||
|
||||
[self setAngle:angle distance:distance];
|
||||
}
|
||||
|
||||
- (int)radiansToDegrees:(float)radians
|
||||
{
|
||||
return ((-radians / PI) * 180 + 360) % 360;
|
||||
}
|
||||
|
||||
- (float)degreesToRadians:(float)degrees
|
||||
{
|
||||
return -(((degrees - 360) / 180) * PI);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* CPColorSpace.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexander Ljungberg.
|
||||
* Copyright 2012, SlevenBits Ltd.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
var sRGBColorSpace = nil;
|
||||
|
||||
/*!
|
||||
Represent color spaces.
|
||||
*/
|
||||
@implementation CPColorSpace : CPObject
|
||||
{
|
||||
CGColorSpace _cgColorSpace;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return an object representing the standard sRGB color space.
|
||||
*/
|
||||
+ (CPColorSpace)sRGBColorSpace
|
||||
{
|
||||
if (!sRGBColorSpace)
|
||||
sRGBColorSpace = [[self alloc] initWithCGColorSpace:CGColorSpaceCreateDeviceRGB()];
|
||||
return sRGBColorSpace;
|
||||
}
|
||||
|
||||
- (id)initWithCGColorSpace:(CGColorSpace)cgColorSpace
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_cgColorSpace = cgColorSpace;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return a Core Graphics color space representing the receiver.
|
||||
*/
|
||||
- (CGColorSpace)CGColorSpace
|
||||
{
|
||||
return _cgColorSpace;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
/*
|
||||
* CPColorWell.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* 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/CPString.j>
|
||||
|
||||
@import "CPView.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPColorPanel.j"
|
||||
|
||||
|
||||
var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiveNotification";
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPColorWell
|
||||
|
||||
CPColorWell is a CPControl for selecting and displaying a single color value. An example of a CPColorWell object (or simply color well) is found in CPColorPanel, which uses a color well to display the current color selection.</p>
|
||||
|
||||
<p>An application can have one or more active CPColorWells. You can activate multiple CPColorWells by invoking the \c -activate: method with \c NO as its argument. When a mouse-down event occurs on an CPColorWell's border, it becomes the only active color well. When a color well becomes active, it brings up the color panel also.
|
||||
*/
|
||||
@implementation CPColorWell : CPControl
|
||||
{
|
||||
BOOL _active;
|
||||
BOOL _bordered;
|
||||
|
||||
CPColor _color;
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (theBinding == CPValueBinding)
|
||||
return [CPColorWellValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"colorwell";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[_CGInsetMakeZero(), [CPNull null], _CGInsetMake(3.0, 3.0, 3.0, 3.0), _CGInsetMakeZero(), [CPNull null]]
|
||||
forKeys:[@"bezel-inset", @"bezel-color", @"content-inset", @"content-border-inset", @"content-border-color"]];
|
||||
}
|
||||
|
||||
- (void)_reverseSetBinding
|
||||
{
|
||||
var binderClass = [[self class] _binderClassForBinding:CPValueBinding],
|
||||
theBinding = [binderClass getBinding:CPValueBinding forObject:self];
|
||||
|
||||
[theBinding reverseSetValueFor:@"color"];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_active = NO;
|
||||
_color = [CPColor whiteColor];
|
||||
[self setBordered:YES];
|
||||
|
||||
[self _registerForNotifications];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_registerForNotifications
|
||||
{
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(colorWellDidBecomeExclusive:)
|
||||
name:_CPColorWellDidBecomeExclusiveNotification
|
||||
object:nil];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(colorPanelWillClose:)
|
||||
name:CPWindowWillCloseNotification
|
||||
object:[CPColorPanel sharedColorPanel]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the color well is bordered.
|
||||
*/
|
||||
- (void)setBordered:(BOOL)shouldBeBordered
|
||||
{
|
||||
if (shouldBeBordered)
|
||||
[self setThemeState:CPThemeStateBordered];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateBordered];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the color well is bordered
|
||||
*/
|
||||
- (BOOL)isBordered
|
||||
{
|
||||
return [self hasThemeState:CPThemeStateBordered];
|
||||
}
|
||||
|
||||
// Managing Color From Color Wells
|
||||
|
||||
/*!
|
||||
Returns the color well's current color.
|
||||
*/
|
||||
- (CPColor)color
|
||||
{
|
||||
return _color;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the color well's current color.
|
||||
*/
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
if (_color == aColor)
|
||||
return;
|
||||
|
||||
_color = aColor;
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
/*!
|
||||
Changes the color of the well to that of \c aSender.
|
||||
@param aSender the object from which to retrieve the color
|
||||
*/
|
||||
- (void)takeColorFrom:(id)aSender
|
||||
{
|
||||
[self setColor:[aSender color]];
|
||||
}
|
||||
|
||||
// Activating and Deactivating Color Wells
|
||||
/*!
|
||||
Activates the color well, displays the color panel, and makes the panel's current color the same as its own.
|
||||
If exclusive is \c YES, deactivates any other CPColorWells. \c NO, keeps them active.
|
||||
@param shouldBeExclusive whether other color wells should be deactivated.
|
||||
*/
|
||||
- (void)activate:(BOOL)shouldBeExclusive
|
||||
{
|
||||
if (shouldBeExclusive)
|
||||
// FIXME: make this queue!
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:_CPColorWellDidBecomeExclusiveNotification
|
||||
object:self];
|
||||
|
||||
|
||||
if ([self isActive])
|
||||
return;
|
||||
|
||||
_active = YES;
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(colorPanelDidChangeColor:)
|
||||
name:CPColorPanelColorDidChangeNotification
|
||||
object:[CPColorPanel sharedColorPanel]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Deactivates the color well.
|
||||
*/
|
||||
- (void)deactivate
|
||||
{
|
||||
if (![self isActive])
|
||||
return;
|
||||
|
||||
_active = NO;
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
removeObserver:self
|
||||
name:CPColorPanelColorDidChangeNotification
|
||||
object:[CPColorPanel sharedColorPanel]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the color well is active.
|
||||
*/
|
||||
- (BOOL)isActive
|
||||
{
|
||||
return _active;
|
||||
}
|
||||
|
||||
- (void)colorPanelDidChangeColor:(CPNotification)aNotification
|
||||
{
|
||||
[self takeColorFrom:[aNotification object]];
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
- (void)colorWellDidBecomeExclusive:(CPNotification)aNotification
|
||||
{
|
||||
if (self != [aNotification object])
|
||||
[self deactivate];
|
||||
}
|
||||
|
||||
- (void)colorPanelWillClose:(CPNotification)aNotification
|
||||
{
|
||||
[self deactivate];
|
||||
}
|
||||
|
||||
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
|
||||
{
|
||||
[self highlight:NO];
|
||||
|
||||
if (!mouseIsUp || !CGRectContainsPoint([self bounds], aPoint) || ![self isEnabled])
|
||||
return;
|
||||
|
||||
[self activate:YES];
|
||||
|
||||
var colorPanel = [CPColorPanel sharedColorPanel];
|
||||
|
||||
[colorPanel setColor:_color];
|
||||
[colorPanel orderFront:self];
|
||||
}
|
||||
|
||||
- (CGRect)contentRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
|
||||
|
||||
return _CGRectInsetByInset(bounds, contentInset);
|
||||
}
|
||||
|
||||
- (CGRect)bezelRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
|
||||
|
||||
return _CGRectInsetByInset(bounds, bezelInset);
|
||||
}
|
||||
|
||||
- (CGRect)contentBorderRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var contentBorderInset = [self currentValueForThemeAttribute:@"content-border-inset"];
|
||||
|
||||
return _CGRectInsetByInset(bounds, contentBorderInset);
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
switch (aName)
|
||||
{
|
||||
case "bezel-view":
|
||||
return [self bezelRectForBounds:[self bounds]];
|
||||
case "content-view":
|
||||
return [self contentRectForBounds:[self bounds]];
|
||||
case "content-border-view":
|
||||
return [self contentBorderRectForBounds:[self bounds]];
|
||||
}
|
||||
|
||||
return [super rectForEphemeralSubviewNamed:aName];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:@"content-view"];
|
||||
|
||||
[bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
|
||||
|
||||
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"bezel-view"];
|
||||
|
||||
|
||||
[contentView setBackgroundColor:_color];
|
||||
|
||||
var contentBorderView = [self layoutEphemeralSubviewNamed:@"content-border-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"content-view"];
|
||||
|
||||
[contentBorderView setBackgroundColor:[self currentValueForThemeAttribute:@"content-border-color"]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPColorWellValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options
|
||||
{
|
||||
var placeholderColor = [CPColor blueColor];
|
||||
|
||||
[self _setPlaceholder:placeholderColor forMarker:CPMultipleValuesMarker isDefault:YES];
|
||||
[self _setPlaceholder:placeholderColor forMarker:CPNoSelectionMarker isDefault:YES];
|
||||
[self _setPlaceholder:placeholderColor forMarker:CPNotApplicableMarker isDefault:YES];
|
||||
[self _setPlaceholder:placeholderColor forMarker:CPNullMarker isDefault:YES];
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)theBinding
|
||||
{
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
newValue = [destination valueForKeyPath:keyPath],
|
||||
isPlaceholder = CPIsControllerMarker(newValue);
|
||||
|
||||
if (isPlaceholder)
|
||||
{
|
||||
if (newValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
|
||||
{
|
||||
[CPException raise:CPGenericException
|
||||
reason:@"can't transform non applicable key on: " + _source + " value: " + newValue];
|
||||
}
|
||||
|
||||
newValue = [self _placeholderForMarker:newValue];
|
||||
}
|
||||
else
|
||||
{
|
||||
newValue = [self transformValue:newValue withOptions:options];
|
||||
}
|
||||
|
||||
[_source setColor:newValue];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPColorWellColorKey = "CPColorWellColorKey",
|
||||
CPColorWellBorderedKey = "CPColorWellBorderedKey";
|
||||
|
||||
@implementation CPColorWell (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the color well by unarchiving data from \c aCoder.
|
||||
@param aCoder the coder containing the archived CPColorWell.
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_active = NO;
|
||||
_color = [aCoder decodeObjectForKey:CPColorWellColorKey];
|
||||
[self setBordered:[aCoder decodeBoolForKey:CPColorWellBorderedKey]];
|
||||
|
||||
[self _registerForNotifications];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Archives this button into the provided coder.
|
||||
@param aCoder the coder to which the color well's instance data will be written.
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_color forKey:CPColorWellColorKey];
|
||||
[aCoder encodeObject:[self isBordered] forKey:CPColorWellBorderedKey];
|
||||
}
|
||||
|
||||
@end
|
||||
-1192
File diff suppressed because it is too large
Load Diff
@@ -1,360 +0,0 @@
|
||||
/*
|
||||
* CPCompatibility.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CPEvent.j"
|
||||
@import "CPPlatform.j"
|
||||
|
||||
|
||||
// Browser Engines
|
||||
CPUnknownBrowserEngine = 0;
|
||||
CPGeckoBrowserEngine = 1;
|
||||
CPInternetExplorerBrowserEngine = 2;
|
||||
CPKHTMLBrowserEngine = 3;
|
||||
CPOperaBrowserEngine = 4;
|
||||
CPWebKitBrowserEngine = 5;
|
||||
|
||||
// Operating Systems
|
||||
CPMacOperatingSystem = 0;
|
||||
CPWindowsOperatingSystem = 1;
|
||||
CPOtherOperatingSystem = 2;
|
||||
|
||||
// Features
|
||||
CPCSSRGBAFeature = 1 << 5;
|
||||
|
||||
CPHTMLCanvasFeature = 1 << 6;
|
||||
CPHTMLContentEditableFeature = 1 << 7;
|
||||
CPHTMLDragAndDropFeature = 1 << 8;
|
||||
|
||||
CPJavaScriptInnerTextFeature = 1 << 9;
|
||||
CPJavaScriptTextContentFeature = 1 << 10;
|
||||
CPJavaScriptClipboardEventsFeature = 1 << 11;
|
||||
CPJavaScriptClipboardAccessFeature = 1 << 12;
|
||||
CPJavaScriptCanvasDrawFeature = 1 << 13;
|
||||
CPJavaScriptCanvasTransformFeature = 1 << 14;
|
||||
|
||||
CPVMLFeature = 1 << 15;
|
||||
|
||||
CPJavaScriptRemedialKeySupport = 1 << 16;
|
||||
CPJavaScriptShadowFeature = 1 << 20;
|
||||
|
||||
CPJavaScriptNegativeMouseWheelValues = 1 << 22;
|
||||
CPJavaScriptMouseWheelValues_8_15 = 1 << 23;
|
||||
|
||||
CPOpacityRequiresFilterFeature = 1 << 24;
|
||||
|
||||
//Internet explorer does not allow dynamically changing the type of an input element
|
||||
CPInputTypeCanBeChangedFeature = 1 << 25;
|
||||
CPHTML5DragAndDropSourceYOffBy1 = 1 << 26;
|
||||
|
||||
CPSOPDisabledFromFileURLs = 1 << 27;
|
||||
|
||||
// element.style.font can be set for an element not in the DOM.
|
||||
CPInputSetFontOutsideOfDOM = 1 << 28;
|
||||
|
||||
// Input elements have 1 px of extra padding on the left regardless of padding setting.
|
||||
CPInput1PxLeftPadding = 1 << 29;
|
||||
CPInputOnInputEventFeature = 1 << 30;
|
||||
|
||||
// When an absolutely positioned div (CPView) with an absolutely positioned canvas in it (CPView with drawRect:) moves things on top of the canvas
|
||||
// (subviews) don't redraw correctly. E.g. if you have a bunch of text fields in a CPBox in a sheet which animates in, some of the text fields might
|
||||
// not be visible because the CPBox has a canvas at the bottom and the box moved form offscreen to onscreen.
|
||||
// This bug is probably very related: https://bugs.webkit.org/show_bug.cgi?id=67203
|
||||
CPCanvasParentDrawErrorsOnMovementBug = 1 << 0;
|
||||
|
||||
var USER_AGENT = "",
|
||||
PLATFORM_ENGINE = CPUnknownBrowserEngine,
|
||||
PLATFORM_FEATURES = 0,
|
||||
PLATFORM_BUGS = 0,
|
||||
PLATFORM_STYLE_JS_PROPERTIES = {};
|
||||
|
||||
// default these features to true
|
||||
|
||||
PLATFORM_FEATURES |= CPInputTypeCanBeChangedFeature;
|
||||
PLATFORM_FEATURES |= CPInputSetFontOutsideOfDOM;
|
||||
|
||||
if (typeof window !== "undefined" && typeof window.navigator !== "undefined")
|
||||
USER_AGENT = window.navigator.userAgent;
|
||||
|
||||
// Opera
|
||||
if (typeof window !== "undefined" && window.opera)
|
||||
{
|
||||
PLATFORM_ENGINE = CPOperaBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES |= CPJavaScriptCanvasDrawFeature;
|
||||
}
|
||||
|
||||
// Internet Explorer
|
||||
else if (typeof window !== "undefined" && window.attachEvent) // Must follow Opera check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPInternetExplorerBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with IE (no known independent tests)
|
||||
PLATFORM_FEATURES |= CPVMLFeature;
|
||||
PLATFORM_FEATURES |= CPJavaScriptRemedialKeySupport;
|
||||
PLATFORM_FEATURES |= CPJavaScriptShadowFeature;
|
||||
|
||||
PLATFORM_FEATURES |= CPOpacityRequiresFilterFeature;
|
||||
|
||||
PLATFORM_FEATURES &= ~CPInputTypeCanBeChangedFeature;
|
||||
|
||||
// Tested in Internet Explore 8 and 9.
|
||||
PLATFORM_FEATURES &= ~CPInputSetFontOutsideOfDOM;
|
||||
}
|
||||
|
||||
// WebKit
|
||||
else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
{
|
||||
PLATFORM_ENGINE = CPWebKitBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with WebKit (no known independent tests)
|
||||
PLATFORM_FEATURES |= CPCSSRGBAFeature;
|
||||
PLATFORM_FEATURES |= CPHTMLContentEditableFeature;
|
||||
|
||||
if (USER_AGENT.indexOf("Chrome") === -1)
|
||||
PLATFORM_FEATURES |= CPHTMLDragAndDropFeature;
|
||||
|
||||
PLATFORM_FEATURES |= CPJavaScriptClipboardEventsFeature;
|
||||
PLATFORM_FEATURES |= CPJavaScriptClipboardAccessFeature;
|
||||
PLATFORM_FEATURES |= CPJavaScriptShadowFeature;
|
||||
|
||||
var versionStart = USER_AGENT.indexOf("AppleWebKit/") + "AppleWebKit/".length,
|
||||
versionEnd = USER_AGENT.indexOf(" ", versionStart),
|
||||
versionString = USER_AGENT.substring(versionStart, versionEnd),
|
||||
versionDivision = versionString.indexOf('.'),
|
||||
majorVersion = parseInt(versionString.substring(0, versionDivision)),
|
||||
minorVersion = parseInt(versionString.substr(versionDivision + 1));
|
||||
|
||||
if ((USER_AGENT.indexOf("Safari") !== CPNotFound && (majorVersion > 525 || (majorVersion === 525 && minorVersion > 14))) || USER_AGENT.indexOf("Chrome") !== CPNotFound)
|
||||
PLATFORM_FEATURES |= CPJavaScriptRemedialKeySupport;
|
||||
|
||||
// FIXME this is a terrible hack to get around this bug:
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=21548
|
||||
if (![CPPlatform isBrowser])
|
||||
PLATFORM_FEATURES |= CPJavaScriptRemedialKeySupport;
|
||||
|
||||
if (majorVersion < 532 || (majorVersion === 532 && minorVersion < 6))
|
||||
PLATFORM_FEATURES |= CPHTML5DragAndDropSourceYOffBy1;
|
||||
|
||||
// This is supposedly fixed in webkit r123603. Seems to work in Chrome 21 but not Safari 6.0.
|
||||
if (majorVersion < 537)
|
||||
PLATFORM_FEATURES |= CPInput1PxLeftPadding;
|
||||
|
||||
if (USER_AGENT.indexOf("Chrome") === CPNotFound)
|
||||
PLATFORM_FEATURES |= CPSOPDisabledFromFileURLs;
|
||||
|
||||
// Assume this bug was introduced around Safari 5.1/Chrome 16. This could probably be tighter.
|
||||
if (majorVersion > 533)
|
||||
PLATFORM_BUGS |= CPCanvasParentDrawErrorsOnMovementBug;
|
||||
}
|
||||
|
||||
// KHTML
|
||||
else if (USER_AGENT.indexOf("KHTML") != -1) // Must follow WebKit check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPKHTMLBrowserEngine;
|
||||
}
|
||||
|
||||
// Gecko
|
||||
else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPGeckoBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES |= CPJavaScriptCanvasDrawFeature;
|
||||
|
||||
var index = USER_AGENT.indexOf("Firefox"),
|
||||
version = (index === -1) ? 2.0 : parseFloat(USER_AGENT.substring(index + "Firefox".length + 1));
|
||||
|
||||
if (version >= 3.0)
|
||||
PLATFORM_FEATURES |= CPCSSRGBAFeature;
|
||||
|
||||
if (version < 3.0)
|
||||
PLATFORM_FEATURES |= CPJavaScriptMouseWheelValues_8_15;
|
||||
|
||||
// Some day this might be fixed and should be version prefixed. No known fixed version yet.
|
||||
PLATFORM_FEATURES |= CPInput1PxLeftPadding;
|
||||
}
|
||||
|
||||
// Feature Specific Checks
|
||||
if (typeof document != "undefined")
|
||||
{
|
||||
var canvasElement = document.createElement("canvas");
|
||||
// Detect Canvas Support
|
||||
if (canvasElement && canvasElement.getContext)
|
||||
{
|
||||
PLATFORM_FEATURES |= CPHTMLCanvasFeature;
|
||||
|
||||
// Detect Canvas setTransform/transform support
|
||||
var context = document.createElement("canvas").getContext("2d");
|
||||
|
||||
if (context && context.setTransform && context.transform)
|
||||
PLATFORM_FEATURES |= CPJavaScriptCanvasTransformFeature;
|
||||
}
|
||||
|
||||
var DOMElement = document.createElement("div");
|
||||
|
||||
// Detect whether we have innerText or textContent (or neither)
|
||||
if (DOMElement.innerText != undefined)
|
||||
PLATFORM_FEATURES |= CPJavaScriptInnerTextFeature;
|
||||
else if (DOMElement.textContent != undefined)
|
||||
PLATFORM_FEATURES |= CPJavaScriptTextContentFeature;
|
||||
|
||||
var DOMInputElement = document.createElement("input");
|
||||
if ("oninput" in DOMInputElement)
|
||||
PLATFORM_FEATURES |= CPInputOnInputEventFeature;
|
||||
else if (typeof DOMInputElement.setAttribute === "function")
|
||||
{
|
||||
DOMInputElement.setAttribute("oninput", "return;");
|
||||
if (typeof DOMInputElement.oninput === "function")
|
||||
PLATFORM_FEATURES |= CPInputOnInputEventFeature;
|
||||
}
|
||||
}
|
||||
|
||||
function CPFeatureIsCompatible(aFeature)
|
||||
{
|
||||
return PLATFORM_FEATURES & aFeature;
|
||||
}
|
||||
|
||||
function CPPlatformHasBug(aBug)
|
||||
{
|
||||
return PLATFORM_BUGS & aBug;
|
||||
}
|
||||
|
||||
function CPBrowserIsEngine(anEngine)
|
||||
{
|
||||
return PLATFORM_ENGINE === anEngine;
|
||||
}
|
||||
|
||||
function CPBrowserIsOperatingSystem(anOperatingSystem)
|
||||
{
|
||||
return OPERATING_SYSTEM === anOperatingSystem;
|
||||
}
|
||||
|
||||
OPERATING_SYSTEM = CPOtherOperatingSystem;
|
||||
|
||||
if (USER_AGENT.indexOf("Mac") !== -1)
|
||||
{
|
||||
OPERATING_SYSTEM = CPMacOperatingSystem;
|
||||
|
||||
CPPlatformActionKeyMask = CPCommandKeyMask;
|
||||
|
||||
CPUndoKeyEquivalent = @"z";
|
||||
CPRedoKeyEquivalent = @"Z";
|
||||
|
||||
CPUndoKeyEquivalentModifierMask = CPCommandKeyMask;
|
||||
CPRedoKeyEquivalentModifierMask = CPCommandKeyMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (USER_AGENT.indexOf("Windows") !== -1)
|
||||
OPERATING_SYSTEM = CPWindowsOperatingSystem;
|
||||
|
||||
CPPlatformActionKeyMask = CPControlKeyMask;
|
||||
|
||||
CPUndoKeyEquivalent = @"z";
|
||||
CPRedoKeyEquivalent = @"y";
|
||||
|
||||
CPUndoKeyEquivalentModifierMask = CPControlKeyMask;
|
||||
CPRedoKeyEquivalentModifierMask = CPControlKeyMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the properly prefixed JS property for the given name. E.g. in a webkit browser,
|
||||
CPBrowserStyleProperty('transition') -> WebkitTransition
|
||||
|
||||
While technically not a style property, style related event handler names are also supported.
|
||||
CPBrowserStyleProperty('transitionend') -> 'webkitTransitionEnd'
|
||||
|
||||
CSS is only available in platform(dom), so don't rely too heavily on it.
|
||||
*/
|
||||
function CPBrowserStyleProperty(aProperty)
|
||||
{
|
||||
var lowerProperty = aProperty.toLowerCase();
|
||||
|
||||
if (PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] === undefined)
|
||||
{
|
||||
var r = nil;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
var testElement = document.createElement('div');
|
||||
|
||||
switch (lowerProperty)
|
||||
{
|
||||
case 'transitionend':
|
||||
var candidates = {
|
||||
'WebkitTransition' : 'webkitTransitionEnd',
|
||||
'MozTransition' : 'transitionend',
|
||||
'OTransition' : 'oTransitionEnd',
|
||||
'msTransition' : 'MSTransitionEnd',
|
||||
'transition' : 'transitionend'
|
||||
};
|
||||
|
||||
r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transition']] || nil;
|
||||
break;
|
||||
default:
|
||||
var prefixes = ["Webkit", "Moz", "O", "ms"],
|
||||
capProperty = [aProperty capitalizedString];
|
||||
|
||||
for (var i = 0; i < prefixes.length; i++)
|
||||
{
|
||||
if (prefixes[i] + capProperty in testElement.style)
|
||||
{
|
||||
r = prefixes[i] + capProperty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!r && lowerProperty in testElement.style)
|
||||
r = lowerProperty;
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] = r;
|
||||
}
|
||||
|
||||
return PLATFORM_STYLE_JS_PROPERTIES[lowerProperty];
|
||||
}
|
||||
|
||||
function CPBrowserCSSProperty(aProperty)
|
||||
{
|
||||
var browserProperty = CPBrowserStyleProperty(aProperty);
|
||||
|
||||
if (!browserProperty)
|
||||
return nil;
|
||||
|
||||
var prefixes = {
|
||||
'Webkit': '-webkit-',
|
||||
'Moz': '-moz-',
|
||||
'O': '-o-',
|
||||
'ms': '-ms-'
|
||||
};
|
||||
|
||||
for (var prefix in prefixes)
|
||||
{
|
||||
if (browserProperty.substring(0, prefix.length) == prefix)
|
||||
{
|
||||
return prefixes[prefix] + browserProperty.substring(prefix.length).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
return browserProperty.toLowerCase();
|
||||
}
|
||||
-1004
File diff suppressed because it is too large
Load Diff
@@ -1,88 +0,0 @@
|
||||
//
|
||||
// CPController.j
|
||||
// AppKit
|
||||
//
|
||||
// Created by Ross Boucher 1/15/09
|
||||
// Copyright 280 North
|
||||
//
|
||||
// Adapted from GNUStep
|
||||
// Copyright (C) 2007 Free Software Foundation, Inc
|
||||
// Released under the LGPL.
|
||||
//
|
||||
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
var CPControllerDeclaredKeysKey = @"CPControllerDeclaredKeysKey";
|
||||
|
||||
@implementation CPController : CPObject
|
||||
{
|
||||
CPArray _editors;
|
||||
CPArray _declaredKeys;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_editors = [];
|
||||
_declaredKeys = [];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if ([_declaredKeys count] > 0)
|
||||
[aCoder encodeObject:_declaredKeys forKey:CPControllerDeclaredKeysKey];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aDecoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_editors = [];
|
||||
_declaredKeys = [aDecoder decodeObjectForKey:CPControllerDeclaredKeysKey] || [];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isEditing
|
||||
{
|
||||
return [_editors count] > 0;
|
||||
}
|
||||
|
||||
- (BOOL)commitEditing
|
||||
{
|
||||
var index = 0,
|
||||
count = _editors.length;
|
||||
|
||||
for (; index < count; ++index)
|
||||
if (![[_editors objectAtIndex:i] commitEditing])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)discardEditing
|
||||
{
|
||||
[_editors makeObjectsPerformSelector:@selector(discardEditing)];
|
||||
}
|
||||
|
||||
- (void)objectDidBeginEditing:(id)anEditor
|
||||
{
|
||||
[_editors addObject:anEditor];
|
||||
}
|
||||
|
||||
- (void)objectDidEndEditing:(id)anEditor
|
||||
{
|
||||
[_editors removeObject:anEditor];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* CPCookie.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 <Foundation/CPString.j>
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPCookie
|
||||
CPCookie is the Cappuccino interface to a web browser cookie. You can set the name
|
||||
*/
|
||||
@implementation CPCookie : CPObject
|
||||
{
|
||||
CPString _cookieName;
|
||||
CPString _cookieValue;
|
||||
|
||||
CPString _expires;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a cookie with a given name \c aName.
|
||||
@param the name for the cookie
|
||||
*/
|
||||
- (id)initWithName:(CPString)aName
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_cookieName = aName;
|
||||
_cookieValue = [self _readCookieValue];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the cookie's data value
|
||||
*/
|
||||
- (CPString)value
|
||||
{
|
||||
return _cookieValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the cookie's name
|
||||
*/
|
||||
- (CPString)name
|
||||
{
|
||||
return _cookieName;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the cookie's expiration date
|
||||
*/
|
||||
- (CPString)expires
|
||||
{
|
||||
return _expires;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a value, expiration date, and domain for the cookie
|
||||
@param value the cookie's value
|
||||
@param date the cookie's expiration date
|
||||
@param domain the cookie's domain
|
||||
*/
|
||||
- (void)setValue:(CPString)value expires:(CPDate)date domain:(CPString)domain
|
||||
{
|
||||
if (date)
|
||||
var expires = "; expires=" + date.toGMTString();
|
||||
else
|
||||
var expires = "";
|
||||
|
||||
if (domain)
|
||||
domain = "; domain=" + domain;
|
||||
else
|
||||
domain = "";
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
document.cookie = _cookieName+"="+value+expires+"; path=/"+domain;
|
||||
#else
|
||||
_cookieValue = value;
|
||||
_expires = expires;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (CPString)_readCookieValue
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var nameEQ = _cookieName + "=",
|
||||
ca = document.cookie.split(';');
|
||||
|
||||
for (var i = 0; i < ca.length; i++)
|
||||
{
|
||||
var c = ca[i];
|
||||
|
||||
while (c.charAt(0) == ' ')
|
||||
c = c.substring(1, c.length);
|
||||
|
||||
if (c.indexOf(nameEQ) == 0)
|
||||
return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//http://www.quirksmode.org/js/cookies.html
|
||||
@@ -1,323 +0,0 @@
|
||||
/*
|
||||
Cursor support by browser:
|
||||
OS X 10.6/Chrome 8 : All
|
||||
OS X 10.6/Safari 5 : All
|
||||
OS X 10.6/Firefox 3 : All except disappearingItemCursor (no url() support)
|
||||
OS X 10.6/Firefox 3.5 : All except disappearingItemCursor (no url() support)
|
||||
OS X 10.6/Firefox 3.6 : All except disappearingItemCursor, contextualMenuCursor, dragLinkCursor, dragCopyCursor, operationNotAllowedCursor (no url() support)
|
||||
OS X 10.6/Firefox 4.0b10 : All
|
||||
OS X/Opera 9 : All except disappearingItemCursor, closedHandCursor, openHandCursor, contextualMenuCursor, dragLinkCursor, dragCopyCursor, operationNotAllowedCursor, resizeUpDownCursor, resizeLeftRightCursor (no url() support)
|
||||
OS X/Opera 10 : All except disappearingItemCursor, closedHandCursor, contextualMenuCursor, dragLinkCursor, dragCopyCursor, operationNotAllowedCursor (no url() support)
|
||||
OS X/Opera 11 : All except disappearingItemCursor, closedHandCursor, contextualMenuCursor, dragLinkCursor, dragCopyCursor, operationNotAllowedCursor (no url() support)
|
||||
Win XP/Chrome 8 : All
|
||||
Win XP/Safari 5 : All
|
||||
Win XP/Firefox 3 : All
|
||||
Win XP/Firefox 3.5 : All
|
||||
Win XP/Firefox 3.6 : All
|
||||
Win XP/Firefox 4.0b10 : All
|
||||
Win XP/Opera 10 : All except disappearingItemCursor, closedHandCursor, openHandCursor, contextualMenuCursor, dragLinkCursor, dragCopyCursor, operationNotAllowedCursor, resizeUpDownCursor, resizeLeftRightCursor (no url() support)
|
||||
Win XP/Opera 11 : All except disappearingItemCursor, closedHandCursor, openHandCursor, contextualMenuCursor, dragLinkCursor, dragCopyCursor, operationNotAllowedCursor, resizeUpDownCursor, resizeLeftRightCursor (no url() support)
|
||||
Win XP/IE 7 : All
|
||||
Win XP/IE 8 : All
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
var currentCursor = nil,
|
||||
cursorStack = [],
|
||||
cursors = {},
|
||||
ieCursorMap = {};
|
||||
|
||||
@implementation CPCursor : CPObject
|
||||
{
|
||||
CPString _cssString @accessors(readonly);
|
||||
CPString _hotSpot @accessors(readonly, getter=hotSpot);
|
||||
CPImage _image @accessors(readonly, getter=image);
|
||||
BOOL _isSetOnMouseEntered @accessors(readwrite, getter=isSetOnMouseEntered, setter=setOnMouseEntered:);
|
||||
BOOL _isSetOnMouseExited @accessors(readwrite, getter=isSetOnMouseExited, setter=setOnMouseExited:);
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== CPCursor)
|
||||
return;
|
||||
|
||||
// IE < 9 does not support some CSS cursors, we map them to supported ones
|
||||
ieCursorMap = {
|
||||
"ew-resize": "e-resize",
|
||||
"ns-resize": "n-resize",
|
||||
"nesw-resize": "ne-resize",
|
||||
"nwse-resize": "nw-resize"
|
||||
};
|
||||
}
|
||||
|
||||
- (id)initWithCSSString:(CPString)aString
|
||||
{
|
||||
if (self = [super init])
|
||||
_cssString = aString;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Init a cursor with the given image and hotspot.
|
||||
hotspot is supported in CSS3 (but not IE).
|
||||
*/
|
||||
- (id)initWithImage:(CPImage)image hotSpot:(CGPoint)hotSpot
|
||||
{
|
||||
_hotSpot = hotSpot;
|
||||
_image = image;
|
||||
return [self initWithCSSString:"url(" + [_image filename] + ")" + hotSpot.x + " " + hotSpot.y + ", auto"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Init a cursor with the given image and hotspot. This is provided
|
||||
for compliance with Cocoa. Note that foregroundColor and backgroundColor are ignored
|
||||
(as they are in Cocoa). See http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSCursor_Class/Reference/Reference.html
|
||||
*/
|
||||
- (id)initWithImage:(CPImage)image foregroundColorHint:(CPColor)foregroundColor backgroundColorHint:(CPColor)backgroundColor hotSpot:(CGPoint)aHotSpot
|
||||
{
|
||||
return [self initWithImage:image hotSpot:hotSpot];
|
||||
}
|
||||
|
||||
+ (void)hide
|
||||
{
|
||||
[self _setCursorCSS:@"none"]; // Not supported in IE < 9
|
||||
}
|
||||
|
||||
+ (void)unhide
|
||||
{
|
||||
[self _setCursorCSS:[currentCursor _cssString]];
|
||||
}
|
||||
|
||||
+ (void)setHiddenUntilMouseMoves:(BOOL)flag
|
||||
{
|
||||
if (flag)
|
||||
[CPCursor hide];
|
||||
else
|
||||
[CPCursor unhide];
|
||||
}
|
||||
|
||||
- (void)pop
|
||||
{
|
||||
[CPCursor pop];
|
||||
}
|
||||
|
||||
+ (void)pop
|
||||
{
|
||||
if (cursorStack.length > 1)
|
||||
{
|
||||
cursorStack.pop();
|
||||
currentCursor = cursorStack[cursorStack.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)push
|
||||
{
|
||||
currentCursor = cursorStack.push(self);
|
||||
}
|
||||
|
||||
- (void)set
|
||||
{
|
||||
currentCursor = self;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
[[self class] _setCursorCSS:_cssString];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(CPEvent)event
|
||||
{
|
||||
}
|
||||
|
||||
- (void)mouseExited:(CPEvent)event
|
||||
{
|
||||
}
|
||||
|
||||
+ (CPCursor)currentCursor
|
||||
{
|
||||
return currentCursor;
|
||||
}
|
||||
|
||||
+ (void)_setCursorCSS:(CPString)aString
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var platformWindows = [[CPPlatformWindow visiblePlatformWindows] allObjects];
|
||||
|
||||
for (var i = 0, count = [platformWindows count]; i < count; i++)
|
||||
platformWindows[i]._DOMBodyElement.style.cursor = aString;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Internal method that is used to return the system cursors. Caches the system cursors for performance.
|
||||
+ (CPCursor)_systemCursorWithName:(CPString)cursorName cssString:(CPString)aString hasImage:(BOOL)doesHaveImage
|
||||
{
|
||||
var cursor = cursors[cursorName];
|
||||
|
||||
if (typeof cursor === "undefined")
|
||||
{
|
||||
var cssString;
|
||||
|
||||
if (doesHaveImage)
|
||||
cssString = [CPString stringWithFormat:@"url(%@/CPCursor/%@.cur), %@", [[CPBundle bundleForClass:self] resourcePath], cursorName, aString];
|
||||
else
|
||||
{
|
||||
// IE <= 8 does not support some cursors, map them to supported cursors
|
||||
var ieLessThan9 = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPFeatureIsCompatible(CPHTMLCanvasFeature);
|
||||
|
||||
if (ieLessThan9)
|
||||
cssString = ieCursorMap[aString] || aString;
|
||||
else
|
||||
cssString = aString;
|
||||
}
|
||||
|
||||
cursor = [[CPCursor alloc] initWithCSSString:cssString];
|
||||
cursors[cursorName] = cursor;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
+ (CPCursor)arrowCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"default" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)crosshairCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"crosshair" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)IBeamCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"text" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)pointingHandCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"pointer" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNorthwestCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nw-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNorthwestSoutheastCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nwse-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNortheastCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ne-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNortheastSouthwestCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nesw-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeSouthwestCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"sw-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeSoutheastCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"se-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeDownCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"s-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeUpCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"n-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeLeftCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"w-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeRightCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"e-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeLeftRightCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"col-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeEastWestCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ew-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeUpDownCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"row-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNorthSouthCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ns-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)operationNotAllowedCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"not-allowed" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)dragCopyCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"copy" hasImage:YES];
|
||||
}
|
||||
|
||||
+ (CPCursor)dragLinkCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"alias" hasImage:YES];
|
||||
}
|
||||
|
||||
+ (CPCursor)contextualMenuCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"context-menu" hasImage:YES];
|
||||
}
|
||||
|
||||
+ (CPCursor)openHandCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"move" hasImage:YES];
|
||||
}
|
||||
|
||||
+ (CPCursor)closedHandCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"-moz-grabbing" hasImage:YES];
|
||||
}
|
||||
|
||||
+ (CPCursor)disappearingItemCursor
|
||||
{
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"auto" hasImage:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPCursor(CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
if (self = [super init])
|
||||
_cssString = [coder decodeObjectForKey:@"CPCursorNameKey"];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[coder encodeObject:_cssString forKey:@"CPCursorNameKey"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,930 +0,0 @@
|
||||
/*
|
||||
* CPDocument.j
|
||||
* AppKit
|
||||
*
|
||||
* 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/CPString.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@import "CPApplication.j"
|
||||
@import "CPResponder.j"
|
||||
@import "CPSavePanel.j"
|
||||
@import "CPViewController.j"
|
||||
@import "CPWindowController.j"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPSaveOperationType
|
||||
*/
|
||||
CPSaveOperation = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPSaveOperationType
|
||||
*/
|
||||
CPSaveAsOperation = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPSaveOperationType
|
||||
*/
|
||||
CPSaveToOperation = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPSaveOperationType
|
||||
*/
|
||||
CPAutosaveOperation = 3;
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeDone = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeUndone = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeCleared = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeReadOtherContents = 3;
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeAutosaved = 4;
|
||||
|
||||
CPDocumentWillSaveNotification = @"CPDocumentWillSaveNotification";
|
||||
CPDocumentDidSaveNotification = @"CPDocumentDidSaveNotification";
|
||||
CPDocumentDidFailToSaveNotification = @"CPDocumentDidFailToSaveNotification";
|
||||
|
||||
var CPDocumentUntitledCount = 0;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPDocument
|
||||
|
||||
CPDocument is used to represent a document/file in a Cappuccino application.
|
||||
In a document-based application, generally multiple documents are open simultaneously
|
||||
(multiple text documents, slide presentations, spreadsheets, etc.), and multiple
|
||||
CPDocuments should be used to represent this.
|
||||
*/
|
||||
@implementation CPDocument : CPResponder
|
||||
{
|
||||
CPWindow _window; // For outlet purposes.
|
||||
CPView _view; // For outlet purposes
|
||||
CPDictionary _viewControllersForWindowControllers;
|
||||
|
||||
CPURL _fileURL;
|
||||
CPString _fileType;
|
||||
CPArray _windowControllers;
|
||||
unsigned _untitledDocumentIndex;
|
||||
|
||||
BOOL _hasUndoManager;
|
||||
CPUndoManager _undoManager;
|
||||
|
||||
int _changeCount;
|
||||
|
||||
CPURLConnection _readConnection;
|
||||
CPURLRequest _writeRequest;
|
||||
|
||||
CPAlert _canCloseAlert;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes an empty document.
|
||||
@return the initialized document
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_windowControllers = [];
|
||||
_viewControllersForWindowControllers = [CPDictionary dictionary];
|
||||
|
||||
_hasUndoManager = YES;
|
||||
_changeCount = 0;
|
||||
|
||||
[self setNextResponder:CPApp];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the document with a specific data type.
|
||||
@param aType the type of document to initialize
|
||||
@param anError not used
|
||||
@return the initialized document
|
||||
*/
|
||||
- (id)initWithType:(CPString)aType error:({CPError})anError
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
[self setFileType:aType];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a document of a specific type located at a URL. Notifies
|
||||
the provided delegate after initialization.
|
||||
@param anAbsoluteURL the url of the document content
|
||||
@param aType the type of document located at the URL
|
||||
@param aDelegate the delegate to notify
|
||||
@param aDidReadSelector the selector used to notify the delegate
|
||||
@param aContextInfo context information passed to the delegate
|
||||
after initialization
|
||||
@return the initialized document
|
||||
*/
|
||||
- (id)initWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setFileURL:anAbsoluteURL];
|
||||
[self setFileType:aType];
|
||||
|
||||
[self readFromURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the document from a URL.
|
||||
@param anAbsoluteURL the document location
|
||||
@param absoluteContentsURL the location of the document's contents
|
||||
@param aType the type of the contents
|
||||
@param aDelegate this object will receive a callback after the document's contents are loaded
|
||||
@param aDidReadSelector the message selector that will be sent to \c aDelegate
|
||||
@param aContextInfo passed as the argument to the message sent to the \c aDelegate
|
||||
@return the initialized document
|
||||
*/
|
||||
- (id)initForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setFileURL:anAbsoluteURL];
|
||||
[self setFileType:aType];
|
||||
|
||||
[self readFromURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's data in a specified type. The default implementation just
|
||||
throws an exception.
|
||||
@param aType the format of the data
|
||||
@param anError not used
|
||||
@throws CPUnsupportedMethodException if this method hasn't been overridden by the subclass
|
||||
@return the document data
|
||||
*/
|
||||
- (CPData)dataOfType:(CPString)aType error:({CPError})anError
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:"dataOfType:error: must be overridden by the document subclass."];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the content of the document by reading the provided
|
||||
data. The default implementation just throws an exception.
|
||||
@param aData the document's data
|
||||
@param aType the document type
|
||||
@param anError not used
|
||||
@throws CPUnsupportedMethodException if this method hasn't been
|
||||
overridden by the subclass
|
||||
*/
|
||||
- (void)readFromData:(CPData)aData ofType:(CPString)aType error:(CPError)anError
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:"readFromData:ofType: must be overridden by the document subclass."];
|
||||
}
|
||||
|
||||
- (void)viewControllerWillLoadCib:(CPViewController)aViewController
|
||||
{
|
||||
}
|
||||
|
||||
- (void)viewControllerDidLoadCib:(CPViewController)aViewController
|
||||
{
|
||||
}
|
||||
|
||||
- (CPWindowController)firstEligibleExistingWindowController
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Creating and managing window controllers
|
||||
/*!
|
||||
Creates the window controller for this document.
|
||||
*/
|
||||
- (void)makeWindowControllers
|
||||
{
|
||||
[self makeViewAndWindowControllers];
|
||||
}
|
||||
|
||||
- (void)makeViewAndWindowControllers
|
||||
{
|
||||
var viewCibName = [self viewCibName],
|
||||
viewController = nil,
|
||||
windowController = nil;
|
||||
|
||||
// Create our view controller if we have a cib for it.
|
||||
if ([viewCibName length])
|
||||
viewController = [[CPViewController alloc] initWithCibName:viewCibName bundle:nil owner:self];
|
||||
|
||||
// If we have a view controller, check if we have a free window for it.
|
||||
if (viewController)
|
||||
windowController = [self firstEligibleExistingWindowController];
|
||||
|
||||
// If not, create one.
|
||||
if (!windowController)
|
||||
{
|
||||
var windowCibName = [self windowCibName];
|
||||
|
||||
// From a cib if we have one.
|
||||
if ([windowCibName length])
|
||||
windowController = [[CPWindowController alloc] initWithWindowCibName:windowCibName owner:self];
|
||||
|
||||
// If not you get a standard window capable of displaying multiple documents and view
|
||||
else if (viewController)
|
||||
{
|
||||
var view = [viewController view],
|
||||
viewFrame = [view frame];
|
||||
|
||||
viewFrame.origin = CGPointMake(50, 50);
|
||||
|
||||
var theWindow = [[CPWindow alloc] initWithContentRect:viewFrame styleMask:CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask | CPResizableWindowMask];
|
||||
|
||||
windowController = [[CPWindowController alloc] initWithWindow:theWindow];
|
||||
}
|
||||
}
|
||||
|
||||
if (windowController && viewController)
|
||||
[windowController setSupportsMultipleDocuments:YES];
|
||||
|
||||
if (windowController)
|
||||
[self addWindowController:windowController];
|
||||
|
||||
if (viewController)
|
||||
[self addViewController:viewController forWindowController:windowController];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document's window controllers
|
||||
*/
|
||||
- (CPArray)windowControllers
|
||||
{
|
||||
return _windowControllers;
|
||||
}
|
||||
|
||||
/*!
|
||||
Add a controller to the document's list of controllers. This should
|
||||
be called after making a new window controller.
|
||||
@param aWindowController the controller to add
|
||||
*/
|
||||
- (void)addWindowController:(CPWindowController)aWindowController
|
||||
{
|
||||
[_windowControllers addObject:aWindowController];
|
||||
|
||||
if ([aWindowController document] !== self)
|
||||
[aWindowController setDocument:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Remove a controller to the document's list of controllers. This should
|
||||
be called after closing the controller's window.
|
||||
@param aWindowController the controller to remove
|
||||
*/
|
||||
- (void)removeWindowController:(CPWindowController)aWindowController
|
||||
{
|
||||
if (aWindowController)
|
||||
[_windowControllers removeObject:aWindowController];
|
||||
|
||||
if ([aWindowController document] === self)
|
||||
[aWindowController setDocument:nil];
|
||||
}
|
||||
|
||||
- (CPView)view
|
||||
{
|
||||
return _view;
|
||||
}
|
||||
|
||||
- (CPArray)viewControllers
|
||||
{
|
||||
return [_viewControllersForWindowControllers allValues];
|
||||
}
|
||||
|
||||
- (void)addViewController:(CPViewController)aViewController forWindowController:(CPWindowController)aWindowController
|
||||
{
|
||||
// FIXME: exception if we don't own the window controller?
|
||||
[_viewControllersForWindowControllers setObject:aViewController forKey:[aWindowController UID]];
|
||||
|
||||
if ([aWindowController document] === self)
|
||||
[aWindowController setViewController:aViewController];
|
||||
}
|
||||
|
||||
- (void)removeViewController:(CPViewController)aViewController
|
||||
{
|
||||
[_viewControllersForWindowControllers removeObject:aViewController];
|
||||
}
|
||||
|
||||
- (CPViewController)viewControllerForWindowController:(CPWindowController)aWindowController
|
||||
{
|
||||
return [_viewControllersForWindowControllers objectForKey:[aWindowController UID]];
|
||||
}
|
||||
|
||||
// Managing Document Windows
|
||||
/*!
|
||||
Shows all the document's windows.
|
||||
*/
|
||||
- (void)showWindows
|
||||
{
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(setDocument:) withObject:self];
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(showWindow:) withObject:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the name of the document as displayed in the title bar.
|
||||
*/
|
||||
- (CPString)displayName
|
||||
{
|
||||
if (_fileURL)
|
||||
return [_fileURL lastPathComponent];
|
||||
|
||||
if (!_untitledDocumentIndex)
|
||||
_untitledDocumentIndex = ++CPDocumentUntitledCount;
|
||||
|
||||
if (_untitledDocumentIndex == 1)
|
||||
return @"Untitled";
|
||||
|
||||
return @"Untitled " + _untitledDocumentIndex;
|
||||
}
|
||||
|
||||
- (CPString)viewCibName
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document's Cib name
|
||||
*/
|
||||
- (CPString)windowCibName
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Called after \c aWindowController loads the document's Nib file.
|
||||
@param aWindowController the controller that loaded the Nib file
|
||||
*/
|
||||
- (void)windowControllerDidLoadCib:(CPWindowController)aWindowController
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Called before \c aWindowController will load the document's Nib file.
|
||||
@param aWindowController the controller that will load the Nib file
|
||||
*/
|
||||
- (void)windowControllerWillLoadCib:(CPWindowController)aWindowController
|
||||
{
|
||||
}
|
||||
|
||||
// Reading from and Writing to URLs
|
||||
/*!
|
||||
Set the document's data from a URL. Notifies the provided delegate afterwards.
|
||||
@param anAbsoluteURL the URL to the document's content
|
||||
@param aType the document type
|
||||
@param aDelegate delegate to notify after reading the data
|
||||
@param aDidReadSelector message that will be sent to the delegate
|
||||
@param aContextInfo context information that gets sent to the delegate
|
||||
*/
|
||||
- (void)readFromURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
[_readConnection cancel];
|
||||
|
||||
// FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
|
||||
_readConnection = [CPURLConnection connectionWithRequest:[CPURLRequest requestWithURL:anAbsoluteURL] delegate:self];
|
||||
|
||||
_readConnection.session = _CPReadSessionMake(aType, aDelegate, aDidReadSelector, aContextInfo);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the path to the document's file.
|
||||
*/
|
||||
- (CPURL)fileURL
|
||||
{
|
||||
return _fileURL;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the path to the document's file.
|
||||
@param aFileURL the path to the document's file
|
||||
*/
|
||||
- (void)setFileURL:(CPURL)aFileURL
|
||||
{
|
||||
if (_fileURL === aFileURL)
|
||||
return;
|
||||
|
||||
_fileURL = aFileURL;
|
||||
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(synchronizeWindowTitleWithDocumentName)];
|
||||
}
|
||||
|
||||
/*!
|
||||
Saves the document to the specified URL. Notifies the provided delegate
|
||||
with the provided selector and context info afterwards.
|
||||
@param anAbsoluteURL the url to write the document data to
|
||||
@param aTypeName the document type
|
||||
@param aSaveOperation the type of save operation
|
||||
@param aDelegate the delegate to notify after saving
|
||||
@param aDidSaveSelector the selector to send the delegate
|
||||
@param aContextInfo context info that gets passed to the delegate
|
||||
*/
|
||||
- (void)saveToURL:(CPURL)anAbsoluteURL ofType:(CPString)aTypeName forSaveOperation:(CPSaveOperationType)aSaveOperation delegate:(id)aDelegate didSaveSelector:(SEL)aDidSaveSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
var data = [self dataOfType:[self fileType] error:nil],
|
||||
oldChangeCount = _changeCount;
|
||||
|
||||
_writeRequest = [CPURLRequest requestWithURL:anAbsoluteURL];
|
||||
|
||||
// FIXME: THIS IS WRONG! We need a way to decide
|
||||
if ([CPPlatform isBrowser])
|
||||
[_writeRequest setHTTPMethod:@"POST"];
|
||||
else
|
||||
[_writeRequest setHTTPMethod:@"PUT"];
|
||||
|
||||
[_writeRequest setHTTPBody:[data rawString]];
|
||||
|
||||
[_writeRequest setValue:@"close" forHTTPHeaderField:@"Connection"];
|
||||
|
||||
if (aSaveOperation === CPSaveOperation)
|
||||
[_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
|
||||
|
||||
if (aSaveOperation !== CPSaveToOperation)
|
||||
[self updateChangeCount:CPChangeCleared];
|
||||
|
||||
// FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
|
||||
var connection = [CPURLConnection connectionWithRequest:_writeRequest delegate:self];
|
||||
|
||||
connection.session = _CPSaveSessionMake(anAbsoluteURL, aSaveOperation, oldChangeCount, aDelegate, aDidSaveSelector, aContextInfo, connection);
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate method for CPURLConnection
|
||||
@ignore
|
||||
*/
|
||||
- (void)connection:(CPURLConnection)aConnection didReceiveResponse:(CPURLResponse)aResponse
|
||||
{
|
||||
// If we got this far and it wasn't an HTTP request, then everything is fine.
|
||||
if (![aResponse isKindOfClass:[CPHTTPURLResponse class]])
|
||||
return;
|
||||
|
||||
var statusCode = [aResponse statusCode];
|
||||
|
||||
// Nothing to do if everything is hunky dory.
|
||||
if (statusCode === 200)
|
||||
return;
|
||||
|
||||
var session = aConnection.session;
|
||||
|
||||
if (aConnection == _readConnection)
|
||||
{
|
||||
[aConnection cancel];
|
||||
|
||||
alert("There was an error retrieving the document.");
|
||||
|
||||
objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 409: Conflict, in Cappuccino, overwrite protection for documents.
|
||||
if (statusCode == 409)
|
||||
{
|
||||
[aConnection cancel];
|
||||
|
||||
if (confirm("There already exists a file with that name, would you like to overwrite it?"))
|
||||
{
|
||||
[_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
|
||||
|
||||
[aConnection start];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (session.saveOperation != CPSaveToOperation)
|
||||
{
|
||||
_changeCount += session.changeCount;
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
|
||||
}
|
||||
|
||||
_writeRequest = nil;
|
||||
|
||||
objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
|
||||
[self _sendDocumentSavedNotification:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate method for CPURLConnection
|
||||
@ignore
|
||||
*/
|
||||
- (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)aData
|
||||
{
|
||||
var session = aConnection.session;
|
||||
|
||||
// READ
|
||||
if (aConnection == _readConnection)
|
||||
{
|
||||
[self readFromData:[CPData dataWithRawString:aData] ofType:session.fileType error:nil];
|
||||
|
||||
objj_msgSend(session.delegate, session.didReadSelector, self, YES, session.contextInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (session.saveOperation != CPSaveToOperation)
|
||||
[self setFileURL:session.absoluteURL];
|
||||
|
||||
_writeRequest = nil;
|
||||
|
||||
objj_msgSend(session.delegate, session.didSaveSelector, self, YES, session.contextInfo);
|
||||
[self _sendDocumentSavedNotification:YES];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate method for CPURLConnection
|
||||
@ignore
|
||||
*/
|
||||
- (void)connection:(CPURLConnection)aConnection didFailWithError:(CPError)anError
|
||||
{
|
||||
var session = aConnection.session;
|
||||
|
||||
if (_readConnection == aConnection)
|
||||
objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
|
||||
|
||||
else
|
||||
{
|
||||
if (session.saveOperation != CPSaveToOperation)
|
||||
{
|
||||
_changeCount += session.changeCount;
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
|
||||
}
|
||||
|
||||
_writeRequest = nil;
|
||||
|
||||
alert("There was an error saving the document.");
|
||||
|
||||
objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
|
||||
[self _sendDocumentSavedNotification:NO];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate method for CPURLConnection
|
||||
@ignore
|
||||
*/
|
||||
- (void)connectionDidFinishLoading:(CPURLConnection)aConnection
|
||||
{
|
||||
if (_readConnection == aConnection)
|
||||
_readConnection = nil;
|
||||
}
|
||||
|
||||
// Managing Document Status
|
||||
/*!
|
||||
Returns \c YES if there are any unsaved changes.
|
||||
*/
|
||||
- (BOOL)isDocumentEdited
|
||||
{
|
||||
return _changeCount != 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Updates the number of unsaved changes to the document.
|
||||
@param aChangeType a new document change to apply
|
||||
*/
|
||||
- (void)updateChangeCount:(CPDocumentChangeType)aChangeType
|
||||
{
|
||||
if (aChangeType == CPChangeDone)
|
||||
++_changeCount;
|
||||
else if (aChangeType == CPChangeUndone)
|
||||
--_changeCount;
|
||||
else if (aChangeType == CPChangeCleared)
|
||||
_changeCount = 0;
|
||||
/*else if (aChangeType == CPCHangeReadOtherContents)
|
||||
|
||||
else if (aChangeType == CPChangeAutosaved)*/
|
||||
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
|
||||
}
|
||||
|
||||
// Managing File Types
|
||||
/*!
|
||||
Sets the document's file type
|
||||
@param aType the document's type
|
||||
*/
|
||||
- (void)setFileType:(CPString)aType
|
||||
{
|
||||
_fileType = aType;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document's file type
|
||||
*/
|
||||
- (CPString)fileType
|
||||
{
|
||||
return _fileType;
|
||||
}
|
||||
|
||||
// Working with Undo Manager
|
||||
/*!
|
||||
Returns \c YES if the document has a
|
||||
CPUndoManager.
|
||||
*/
|
||||
- (BOOL)hasUndoManager
|
||||
{
|
||||
return _hasUndoManager;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the document should have a CPUndoManager.
|
||||
@param aFlag \c YES makes the document have an undo manager
|
||||
*/
|
||||
- (void)setHasUndoManager:(BOOL)aFlag
|
||||
{
|
||||
if (_hasUndoManager == aFlag)
|
||||
return;
|
||||
|
||||
_hasUndoManager = aFlag;
|
||||
|
||||
if (!_hasUndoManager)
|
||||
[self setUndoManager:nil];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_undoManagerWillCloseGroup:(CPNotification)aNotification
|
||||
{
|
||||
var undoManager = [aNotification object];
|
||||
|
||||
if ([undoManager isUndoing] || [undoManager isRedoing])
|
||||
return;
|
||||
|
||||
[self updateChangeCount:CPChangeDone];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_undoManagerDidUndoChange:(CPNotification)aNotification
|
||||
{
|
||||
[self updateChangeCount:CPChangeUndone];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_undoManagerDidRedoChange:(CPNotification)aNotification
|
||||
{
|
||||
[self updateChangeCount:CPChangeDone];
|
||||
}
|
||||
|
||||
/*
|
||||
Sets the document's undo manager. This method will add the
|
||||
undo manager as an observer to the notification center.
|
||||
@param anUndoManager the new undo manager for the document
|
||||
*/
|
||||
- (void)setUndoManager:(CPUndoManager)anUndoManager
|
||||
{
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_undoManager)
|
||||
{
|
||||
[defaultCenter removeObserver:self
|
||||
name:CPUndoManagerDidUndoChangeNotification
|
||||
object:_undoManager];
|
||||
|
||||
[defaultCenter removeObserver:self
|
||||
name:CPUndoManagerDidRedoChangeNotification
|
||||
object:_undoManager];
|
||||
|
||||
[defaultCenter removeObserver:self
|
||||
name:CPUndoManagerWillCloseUndoGroupNotification
|
||||
object:_undoManager];
|
||||
}
|
||||
|
||||
_undoManager = anUndoManager;
|
||||
|
||||
if (_undoManager)
|
||||
{
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(_undoManagerDidUndoChange:)
|
||||
name:CPUndoManagerDidUndoChangeNotification
|
||||
object:_undoManager];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(_undoManagerDidRedoChange:)
|
||||
name:CPUndoManagerDidRedoChangeNotification
|
||||
object:_undoManager];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(_undoManagerWillCloseGroup:)
|
||||
name:CPUndoManagerWillCloseUndoGroupNotification
|
||||
object:_undoManager];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document's undo manager. If the document
|
||||
should have one, but the manager is \c nil, it
|
||||
will be created and then returned.
|
||||
@return the document's undo manager
|
||||
*/
|
||||
- (CPUndoManager)undoManager
|
||||
{
|
||||
if (_hasUndoManager && !_undoManager)
|
||||
[self setUndoManager:[[CPUndoManager alloc] init]];
|
||||
|
||||
return _undoManager;
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate of a CPWindow
|
||||
@ignore
|
||||
*/
|
||||
- (CPUndoManager)windowWillReturnUndoManager:(CPWindow)aWindow
|
||||
{
|
||||
return [self undoManager];
|
||||
}
|
||||
|
||||
// Handling User Actions
|
||||
/*!
|
||||
Saves the document. If the document does not
|
||||
have a file path to save to (\c fileURL)
|
||||
then \c -saveDocumentAs: will be called.
|
||||
@param aSender the object requesting the save
|
||||
*/
|
||||
- (void)saveDocument:(id)aSender
|
||||
{
|
||||
[self saveDocumentWithDelegate:nil didSaveSelector:nil contextInfo:nil];
|
||||
}
|
||||
|
||||
- (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
|
||||
{
|
||||
if (_fileURL)
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPDocumentWillSaveNotification
|
||||
object:self];
|
||||
|
||||
[self saveToURL:_fileURL ofType:[self fileType] forSaveOperation:CPSaveOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
|
||||
}
|
||||
else
|
||||
[self _saveDocumentAsWithDelegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
|
||||
}
|
||||
|
||||
/*!
|
||||
Saves the document to a user specified path.
|
||||
@param aSender the object requesting the operation
|
||||
*/
|
||||
- (void)saveDocumentAs:(id)aSender
|
||||
{
|
||||
[self _saveDocumentAsWithDelegate:nil didSaveSelector:nil contextInfo:nil];
|
||||
}
|
||||
|
||||
- (void)_saveDocumentAsWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
|
||||
{
|
||||
var savePanel = [CPSavePanel savePanel],
|
||||
response = [savePanel runModal];
|
||||
|
||||
if (!response)
|
||||
return;
|
||||
|
||||
var saveURL = [savePanel URL];
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPDocumentWillSaveNotification
|
||||
object:self];
|
||||
|
||||
[self saveToURL:saveURL ofType:[self fileType] forSaveOperation:CPSaveAsOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (void)_sendDocumentSavedNotification:(BOOL)didSave
|
||||
{
|
||||
if (didSave)
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPDocumentDidSaveNotification
|
||||
object:self];
|
||||
else
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPDocumentDidFailToSaveNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPDocument (ClosingDocuments)
|
||||
|
||||
- (void)close
|
||||
{
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(removeDocumentAndCloseIfNecessary:) withObject:self];
|
||||
[[CPDocumentController sharedDocumentController] removeDocument:self];
|
||||
}
|
||||
|
||||
- (void)shouldCloseWindowController:(CPWindowController)controller delegate:(id)delegate shouldCloseSelector:(SEL)selector contextInfo:(Object)info
|
||||
{
|
||||
if ([controller shouldCloseDocument] || ([_windowControllers count] < 2 && [_windowControllers indexOfObject:controller] !== CPNotFound))
|
||||
[self canCloseDocumentWithDelegate:self shouldCloseSelector:@selector(_document:shouldClose:context:) contextInfo:{delegate:delegate, selector:selector, context:info}];
|
||||
|
||||
else if ([delegate respondsToSelector:selector])
|
||||
objj_msgSend(delegate, selector, self, YES, info);
|
||||
}
|
||||
|
||||
- (void)_document:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context
|
||||
{
|
||||
if (aDocument === self && shouldClose)
|
||||
[self close];
|
||||
|
||||
objj_msgSend(context.delegate, context.selector, aDocument, shouldClose, context.context);
|
||||
}
|
||||
|
||||
- (void)canCloseDocumentWithDelegate:(id)aDelegate shouldCloseSelector:(SEL)aSelector contextInfo:(Object)context
|
||||
{
|
||||
if (![self isDocumentEdited])
|
||||
return [aDelegate respondsToSelector:aSelector] && objj_msgSend(aDelegate, aSelector, self, YES, context);
|
||||
|
||||
_canCloseAlert = [[CPAlert alloc] init];
|
||||
|
||||
[_canCloseAlert setDelegate:self];
|
||||
[_canCloseAlert setAlertStyle:CPWarningAlertStyle];
|
||||
[_canCloseAlert setTitle:@"Unsaved Document"];
|
||||
[_canCloseAlert setMessageText:@"Do you want to save the changes you've made to the document \"" + ([self displayName] || [self fileName]) + "\"?"];
|
||||
|
||||
[_canCloseAlert addButtonWithTitle:@"Save"];
|
||||
[_canCloseAlert addButtonWithTitle:@"Cancel"];
|
||||
[_canCloseAlert addButtonWithTitle:@"Don't Save"];
|
||||
|
||||
_canCloseAlert._context = {delegate:aDelegate, selector:aSelector, context:context};
|
||||
|
||||
[_canCloseAlert runModal];
|
||||
}
|
||||
|
||||
- (void)alertDidEnd:(CPAlert)alert returnCode:(int)returnCode
|
||||
{
|
||||
if (alert !== _canCloseAlert)
|
||||
return;
|
||||
|
||||
var delegate = alert._context.delegate,
|
||||
selector = alert._context.selector,
|
||||
context = alert._context.context;
|
||||
|
||||
if (returnCode === 0)
|
||||
[self saveDocumentWithDelegate:delegate didSaveSelector:selector contextInfo:context];
|
||||
else
|
||||
objj_msgSend(delegate, selector, self, returnCode === 2, context);
|
||||
|
||||
_canCloseAlert = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPReadSessionMake = function(aType, aDelegate, aDidReadSelector, aContextInfo)
|
||||
{
|
||||
return { fileType:aType, delegate:aDelegate, didReadSelector:aDidReadSelector, contextInfo:aContextInfo };
|
||||
};
|
||||
|
||||
var _CPSaveSessionMake = function(anAbsoluteURL, aSaveOperation, aChangeCount, aDelegate, aDidSaveSelector, aContextInfo, aConnection)
|
||||
{
|
||||
return { absoluteURL:anAbsoluteURL, saveOperation:aSaveOperation, changeCount:aChangeCount, delegate:aDelegate, didSaveSelector:aDidSaveSelector, contextInfo:aContextInfo, connection:aConnection };
|
||||
};
|
||||
@@ -1,458 +0,0 @@
|
||||
/*
|
||||
* CPDocumentController.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 <Foundation/CPBundle.j>
|
||||
|
||||
@import "CPDocument.j"
|
||||
@import "CPOpenPanel.j"
|
||||
|
||||
|
||||
var CPSharedDocumentController = nil;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPDocumentController
|
||||
This class is responsible for managing an application's open documents.
|
||||
*/
|
||||
@implementation CPDocumentController : CPObject
|
||||
{
|
||||
CPArray _documents;
|
||||
CPArray _documentTypes;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the singleton instance of the application's document controller. If it has not
|
||||
been created yet, it will be created then returned.
|
||||
@return a CPDocumentController
|
||||
*/
|
||||
+ (id)sharedDocumentController
|
||||
{
|
||||
if (!CPSharedDocumentController)
|
||||
[[self alloc] init];
|
||||
|
||||
return CPSharedDocumentController;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_documents = [[CPArray alloc] init];
|
||||
|
||||
if (!CPSharedDocumentController)
|
||||
CPSharedDocumentController = self;
|
||||
|
||||
_documentTypes = [[[CPBundle mainBundle] infoDictionary] objectForKey:@"CPBundleDocumentTypes"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Creating and Opening Documents
|
||||
|
||||
/*!
|
||||
Returns the document matching the specified URL. This
|
||||
method searches documents already open. It does not
|
||||
open the document at the URL if it is not already open.
|
||||
@param aURL the url of the document
|
||||
@return the document, or \c nil if such a document is not open
|
||||
*/
|
||||
- (CPDocument)documentForURL:(CPURL)aURL
|
||||
{
|
||||
var index = 0,
|
||||
count = [_documents count];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var theDocument = _documents[index];
|
||||
|
||||
if ([[theDocument fileURL] isEqual:aURL])
|
||||
return theDocument;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new document of the specified type.
|
||||
@param aType the type of the new document
|
||||
@param shouldDisplay whether to display the document on screen
|
||||
*/
|
||||
- (void)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay
|
||||
{
|
||||
var theDocument = [self makeUntitledDocumentOfType:aType error:nil];
|
||||
|
||||
if (theDocument)
|
||||
[self addDocument:theDocument];
|
||||
|
||||
if (shouldDisplay)
|
||||
{
|
||||
[theDocument makeWindowControllers];
|
||||
[theDocument showWindows];
|
||||
}
|
||||
|
||||
return theDocument;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a document of the specified type.
|
||||
@param aType the document type
|
||||
@param anError not used
|
||||
@return the created document
|
||||
*/
|
||||
- (CPDocument)makeUntitledDocumentOfType:(CPString)aType error:({CPError})anError
|
||||
{
|
||||
return [[[self documentClassForType:aType] alloc] initWithType:aType error:anError];
|
||||
}
|
||||
|
||||
/*!
|
||||
Opens the document at the specified URL.
|
||||
@param anAbsoluteURL the path to the document's file
|
||||
@param shouldDisplay whether to display the document on screen
|
||||
@param anError not used
|
||||
@return the opened document
|
||||
*/
|
||||
- (CPDocument)openDocumentWithContentsOfURL:(CPURL)anAbsoluteURL display:(BOOL)shouldDisplay error:(CPError)anError
|
||||
{
|
||||
var result = [self documentForURL:anAbsoluteURL];
|
||||
|
||||
if (!result)
|
||||
{
|
||||
var type = [self typeForContentsOfURL:anAbsoluteURL error:anError];
|
||||
|
||||
result = [self makeDocumentWithContentsOfURL:anAbsoluteURL ofType:type delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:[CPDictionary dictionaryWithObject:shouldDisplay forKey:@"shouldDisplay"]];
|
||||
|
||||
[self addDocument:result];
|
||||
|
||||
if (result)
|
||||
[self noteNewRecentDocument:result];
|
||||
}
|
||||
else if (shouldDisplay)
|
||||
[result showWindows];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Loads a document for a specified URL with it's content
|
||||
retrieved from another URL.
|
||||
@param anAbsoluteURL the document URL
|
||||
@param absoluteContentsURL the location of the document's contents
|
||||
@param anError not used
|
||||
@return the loaded document or \c nil if there was an error
|
||||
*/
|
||||
- (CPDocument)reopenDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL error:(CPError)anError
|
||||
{
|
||||
return [self makeDocumentForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a document from the contents at the specified URL.
|
||||
Notifies the provided delegate with the provided selector afterwards.
|
||||
@param anAbsoluteURL the location of the document data
|
||||
@param aType the document type
|
||||
@param aDelegate the delegate to notify
|
||||
@param aSelector the selector to notify with
|
||||
@param aContextInfo the context information passed to the delegate
|
||||
*/
|
||||
- (CPDocument)makeDocumentWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
return [[[self documentClassForType:aType] alloc] initWithContentsOfURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a document from the contents of a URL, and sets
|
||||
the document's URL location as another URL.
|
||||
@param anAbsoluteURL the document's location
|
||||
@param absoluteContentsURL the location of the document's contents
|
||||
@param aType the document's data type
|
||||
@param aDelegate receives a callback after the load has completed
|
||||
@param aSelector the selector to invoke for the callback
|
||||
@param aContextInfo an object passed as an argument for the callback
|
||||
@return a new document or \c nil if there was an error
|
||||
*/
|
||||
- (CPDocument)makeDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
return [[[self documentClassForType:aType] alloc] initForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo];
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented delegate method
|
||||
@ignore
|
||||
*/
|
||||
- (void)document:(CPDocument)aDocument didRead:(BOOL)didRead contextInfo:(id)aContextInfo
|
||||
{
|
||||
if (!didRead)
|
||||
return;
|
||||
|
||||
[aDocument makeWindowControllers];
|
||||
|
||||
if ([aContextInfo objectForKey:@"shouldDisplay"])
|
||||
[aDocument showWindows];
|
||||
}
|
||||
|
||||
/*!
|
||||
Opens a new document in the application.
|
||||
@param aSender the requesting object
|
||||
*/
|
||||
- (CFAction)newDocument:(id)aSender
|
||||
{
|
||||
[self openUntitledDocumentOfType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] display:YES];
|
||||
}
|
||||
|
||||
- (void)openDocument:(id)aSender
|
||||
{
|
||||
var openPanel = [CPOpenPanel openPanel];
|
||||
|
||||
[openPanel runModal];
|
||||
|
||||
var URLs = [openPanel URLs],
|
||||
index = 0,
|
||||
count = [URLs count];
|
||||
|
||||
for (; index < count; ++index)
|
||||
[self openDocumentWithContentsOfURL:[CPURL URLWithString:URLs[index]] display:YES error:nil];
|
||||
}
|
||||
|
||||
// Managing Documents
|
||||
|
||||
/*!
|
||||
Returns the array of all documents being managed. This is
|
||||
the same as all open documents in the application.
|
||||
*/
|
||||
- (CPArray)documents
|
||||
{
|
||||
return _documents;
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds \c aDocument under the control of the receiver.
|
||||
@param aDocument the document to add
|
||||
*/
|
||||
- (void)addDocument:(CPDocument)aDocument
|
||||
{
|
||||
[_documents addObject:aDocument];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes \c aDocument from the control of the receiver.
|
||||
@param aDocument the document to remove
|
||||
*/
|
||||
- (void)removeDocument:(CPDocument)aDocument
|
||||
{
|
||||
[_documents removeObjectIdenticalTo:aDocument];
|
||||
}
|
||||
|
||||
- (CPString)defaultType
|
||||
{
|
||||
return [_documentTypes[0] objectForKey:@"CPBundleTypeName"];
|
||||
}
|
||||
|
||||
- (CPString)typeForContentsOfURL:(CPURL)anAbsoluteURL error:(CPError)outError
|
||||
{
|
||||
var index = 0,
|
||||
count = _documentTypes.length,
|
||||
|
||||
extension = [[anAbsoluteURL pathExtension] lowercaseString],
|
||||
starType = nil;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var documentType = _documentTypes[index],
|
||||
extensions = [documentType objectForKey:@"CFBundleTypeExtensions"],
|
||||
extensionIndex = 0,
|
||||
extensionCount = extensions.length;
|
||||
|
||||
for (; extensionIndex < extensionCount; ++extensionIndex)
|
||||
{
|
||||
var thisExtension = [extensions[extensionIndex] lowercaseString];
|
||||
if (thisExtension === extension)
|
||||
return [documentType objectForKey:@"CPBundleTypeName"];
|
||||
|
||||
if (thisExtension === "****")
|
||||
starType = [documentType objectForKey:@"CPBundleTypeName"];
|
||||
}
|
||||
}
|
||||
|
||||
return starType || [self defaultType];
|
||||
}
|
||||
|
||||
// Managing Document Types
|
||||
|
||||
/* @ignore */
|
||||
- (CPDictionary)_infoForType:(CPString)aType
|
||||
{
|
||||
var i = 0,
|
||||
count = [_documentTypes count];
|
||||
|
||||
for (;i < count; ++i)
|
||||
{
|
||||
var documentType = _documentTypes[i];
|
||||
|
||||
if ([documentType objectForKey:@"CPBundleTypeName"] == aType)
|
||||
return documentType;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPDocument subclass associated with \c aType.
|
||||
@param aType the type of document
|
||||
@return a Cappuccino Class object, or \c nil if no match was found
|
||||
*/
|
||||
- (Class)documentClassForType:(CPString)aType
|
||||
{
|
||||
var className = [[self _infoForType:aType] objectForKey:@"CPDocumentClass"];
|
||||
|
||||
return className ? CPClassFromString(className) : nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPDocumentController (Closing)
|
||||
|
||||
- (void)closeAllDocumentsWithDelegate:(id)aDelegate didCloseAllSelector:(SEL)didCloseSelector contextInfo:(Object)info
|
||||
{
|
||||
var context = {
|
||||
delegate: aDelegate,
|
||||
selector: didCloseSelector,
|
||||
context: info
|
||||
};
|
||||
|
||||
[self _closeDocumentsStartingWith:nil shouldClose:YES context:context];
|
||||
}
|
||||
|
||||
// Recursive callback method. Start it by passing in a document of nil.
|
||||
- (void)_closeDocumentsStartingWith:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context
|
||||
{
|
||||
if (shouldClose)
|
||||
{
|
||||
[aDocument close];
|
||||
|
||||
if ([[self documents] count] > 0)
|
||||
{
|
||||
[[[self documents] lastObject] canCloseDocumentWithDelegate:self
|
||||
shouldCloseSelector:@selector(_closeDocumentsStartingWith:shouldClose:context:)
|
||||
contextInfo:context];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ([context.delegate respondsToSelector:context.selector])
|
||||
objj_msgSend(context.delegate, context.selector, self, [[self documents] count] === 0, context.context);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPDocumentController (Recents)
|
||||
|
||||
- (CPArray)recentDocumentURLs
|
||||
{
|
||||
// FIXME move this to CP land
|
||||
if (typeof window["cpRecentDocumentURLs"] === 'function')
|
||||
return window.cpRecentDocumentURLs();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
- (void)clearRecentDocuments:(id)sender
|
||||
{
|
||||
if (typeof window["cpClearRecentDocuments"] === 'function')
|
||||
window.cpClearRecentDocuments();
|
||||
|
||||
[self _updateRecentDocumentsMenu];
|
||||
}
|
||||
|
||||
- (void)noteNewRecentDocument:(CPDocument)aDocument
|
||||
{
|
||||
[self noteNewRecentDocumentURL:[aDocument fileURL]];
|
||||
}
|
||||
|
||||
- (void)noteNewRecentDocumentURL:(CPURL)aURL
|
||||
{
|
||||
var urlAsString = [aURL isKindOfClass:CPString] ? aURL : [aURL absoluteString];
|
||||
if (typeof window["cpNoteNewRecentDocumentPath"] === 'function')
|
||||
window.cpNoteNewRecentDocumentPath(urlAsString);
|
||||
|
||||
[self _updateRecentDocumentsMenu];
|
||||
}
|
||||
|
||||
- (void)_removeAllRecentDocumentsFromMenu:(CPMenu)aMenu
|
||||
{
|
||||
var items = [aMenu itemArray],
|
||||
count = [items count];
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var item = items[count];
|
||||
|
||||
if ([item action] === @selector(_openRecentDocument:))
|
||||
[aMenu removeItemAtIndex:count];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_updateRecentDocumentsMenu
|
||||
{
|
||||
var menu = [[CPApp mainMenu] _menuWithName:@"_CPRecentDocumentsMenu"],
|
||||
recentDocuments = [self recentDocumentURLs],
|
||||
menuItems = [menu itemArray],
|
||||
documentCount = [recentDocuments count],
|
||||
menuItemCount = [menuItems count];
|
||||
|
||||
[self _removeAllRecentDocumentsFromMenu:menu];
|
||||
|
||||
if (menuItemCount)
|
||||
{
|
||||
if (!documentCount)
|
||||
{
|
||||
if ([menuItems[0] isSeparatorItem])
|
||||
[menu removeItemAtIndex:0];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (![menuItems[0] isSeparatorItem])
|
||||
[menu insertItem:[CPMenuItem separatorItem] atIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
while (documentCount--)
|
||||
{
|
||||
var path = recentDocuments[documentCount],
|
||||
item = [[CPMenuItem alloc] initWithTitle:[path lastPathComponent] action:@selector(_openRecentDocument:) keyEquivalent:nil];
|
||||
|
||||
[item setTag:path];
|
||||
[menu insertItem:item atIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_openRecentDocument:(id)sender
|
||||
{
|
||||
[self openDocumentWithContentsOfURL:[sender tag] display:YES error:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,515 +0,0 @@
|
||||
/*
|
||||
* CPDragServer.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CPApplication.j"
|
||||
@import "CPEvent.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPView.j"
|
||||
@import "CPWindow.j"
|
||||
|
||||
|
||||
CPDragOperationNone = 0;
|
||||
CPDragOperationCopy = 1 << 1;
|
||||
CPDragOperationLink = 1 << 1;
|
||||
CPDragOperationGeneric = 1 << 2;
|
||||
CPDragOperationPrivate = 1 << 3;
|
||||
CPDragOperationMove = 1 << 4;
|
||||
CPDragOperationDelete = 1 << 5;
|
||||
CPDragOperationEvery = -1;
|
||||
|
||||
#define DRAGGING_WINDOW(anObject) ([anObject isKindOfClass:[CPWindow class]] ? anObject : [anObject window])
|
||||
|
||||
var CPDragServerPreviousEvent = nil,
|
||||
CPDragServerPeriodicUpdateInterval = 0.05;
|
||||
|
||||
var CPSharedDragServer = nil;
|
||||
|
||||
var CPDragServerSource = nil,
|
||||
CPDragServerDraggingInfo = nil;
|
||||
|
||||
/*
|
||||
CPDraggingInfo is a container of information about a specific dragging session.
|
||||
@ignore
|
||||
*/
|
||||
@implementation CPDraggingInfo : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
- (CPPasteboard)draggingPasteboard
|
||||
{
|
||||
if ([CPPlatform supportsDragAndDrop])
|
||||
return [_CPDOMDataTransferPasteboard DOMDataTransferPasteboard];
|
||||
|
||||
return [[CPDragServer sharedDragServer] draggingPasteboard];
|
||||
}
|
||||
|
||||
- (id)draggingSource
|
||||
{
|
||||
return [[CPDragServer sharedDragServer] draggingSource];
|
||||
}
|
||||
|
||||
/*
|
||||
- (unsigned)draggingSourceOperationMask
|
||||
*/
|
||||
|
||||
- (CPPoint)draggingLocation
|
||||
{
|
||||
return [[CPDragServer sharedDragServer] draggingLocation];
|
||||
}
|
||||
|
||||
- (CPWindow)draggingDestinationWindow
|
||||
{
|
||||
return DRAGGING_WINDOW([[CPDragServer sharedDragServer] draggingDestination]);
|
||||
}
|
||||
|
||||
- (CPImage)draggedImage
|
||||
{
|
||||
return [[self draggedView] image];
|
||||
}
|
||||
|
||||
- (CGPoint)draggedImageLocation
|
||||
{
|
||||
return [self draggedViewLocation];
|
||||
}
|
||||
|
||||
- (CPView)draggedView
|
||||
{
|
||||
return [[CPDragServer sharedDragServer] draggedView];
|
||||
}
|
||||
|
||||
- (CGPoint)draggedViewLocation
|
||||
{
|
||||
var dragServer = [CPDragServer sharedDragServer];
|
||||
|
||||
return [DRAGGING_WINDOW([dragServer draggingDestination]) convertPlatformWindowToBase:[[dragServer draggedView] frame].origin];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
CPDraggingSource_draggedImage_endedAt_operation_ = 1 << 1,
|
||||
CPDraggingSource_draggedView_movedTo_ = 1 << 2,
|
||||
CPDraggingSource_draggedView_endedAt_operation_ = 1 << 3;
|
||||
|
||||
@implementation CPDragServer : CPObject
|
||||
{
|
||||
BOOL _isDragging @accessors(readonly, getter=isDragging);
|
||||
|
||||
CPWindow _draggedWindow @accessors(readonly, getter=draggedWindow);
|
||||
CPView _draggedView @accessors(readonly, getter=draggedView);
|
||||
CPImageView _imageView;
|
||||
|
||||
BOOL _isDraggingImage;
|
||||
|
||||
CGSize _draggingOffset @accessors(readonly, getter=draggingOffset);
|
||||
|
||||
CPPasteboard _draggingPasteboard @accessors(readonly, getter=draggingPasteboard);
|
||||
|
||||
id _draggingSource @accessors(readonly, getter=draggingSource);
|
||||
unsigned _implementedDraggingSourceMethods;
|
||||
|
||||
CGPoint _draggingLocation;
|
||||
id _draggingDestination;
|
||||
BOOL _draggingDestinationWantsPeriodicUpdates;
|
||||
|
||||
CGPoint _startDragLocation;
|
||||
BOOL _shouldSlideBack;
|
||||
unsigned _dragOperation;
|
||||
|
||||
CPTimer _draggingUpdateTimer;
|
||||
}
|
||||
|
||||
/*
|
||||
Private Objective-J/Cappuccino method
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPDragServer class])
|
||||
return;
|
||||
|
||||
CPDragServerDraggingInfo = [[CPDraggingInfo alloc] init];
|
||||
}
|
||||
|
||||
+ (CPDragServer)sharedDragServer
|
||||
{
|
||||
if (!CPSharedDragServer)
|
||||
CPSharedDragServer = [[CPDragServer alloc] init];
|
||||
|
||||
return CPSharedDragServer;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_draggedWindow = [[CPWindow alloc] initWithContentRect:_CGRectMakeZero() styleMask:CPBorderlessWindowMask];
|
||||
|
||||
[_draggedWindow setLevel:CPDraggingWindowLevel];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)draggingDestination
|
||||
{
|
||||
return _draggingDestination;
|
||||
}
|
||||
|
||||
- (CGPoint)draggingLocation
|
||||
{
|
||||
return _draggingLocation
|
||||
}
|
||||
|
||||
- (void)draggingStartedInPlatformWindow:(CPPlatformWindow)aPlatformWindow globalLocation:(CGPoint)aLocation
|
||||
{
|
||||
if (_isDraggingImage)
|
||||
{
|
||||
if ([_draggingSource respondsToSelector:@selector(draggedImage:beganAt:)])
|
||||
[_draggingSource draggedImage:[_draggedView image] beganAt:aLocation];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([_draggingSource respondsToSelector:@selector(draggedView:beganAt:)])
|
||||
[_draggingSource draggedView:_draggedView beganAt:aLocation];
|
||||
}
|
||||
|
||||
if (![CPPlatform supportsDragAndDrop])
|
||||
[_draggedWindow orderFront:self];
|
||||
}
|
||||
|
||||
- (void)draggingSourceUpdatedWithGlobalLocation:(CGPoint)aGlobalLocation
|
||||
{
|
||||
if (![CPPlatform supportsDragAndDrop])
|
||||
[_draggedWindow setFrameOrigin:_CGPointMake(aGlobalLocation.x - _draggingOffset.width, aGlobalLocation.y - _draggingOffset.height)];
|
||||
|
||||
if (_implementedDraggingSourceMethods & CPDraggingSource_draggedImage_movedTo_)
|
||||
[_draggingSource draggedImage:[_draggedView image] movedTo:aGlobalLocation];
|
||||
|
||||
else if (_implementedDraggingSourceMethods & CPDraggingSource_draggedView_movedTo_)
|
||||
[_draggingSource draggedView:_draggedView movedTo:aGlobalLocation];
|
||||
}
|
||||
|
||||
- (CPDragOperation)draggingUpdatedInPlatformWindow:(CPPlatformWindow)aPlatformWindow location:(CGPoint)aLocation
|
||||
{
|
||||
[_draggingUpdateTimer invalidate];
|
||||
_draggingUpdateTimer = nil;
|
||||
|
||||
var dragOperation = CPDragOperationCopy,
|
||||
// We have to convert base to bridge since the drag event comes from the source window, not the drag window.
|
||||
draggingDestination = [aPlatformWindow _dragHitTest:aLocation pasteboard:[CPDragServerDraggingInfo draggingPasteboard]];
|
||||
|
||||
if (draggingDestination)
|
||||
_draggingLocation = [DRAGGING_WINDOW(draggingDestination) convertPlatformWindowToBase:aLocation];
|
||||
|
||||
if (draggingDestination !== _draggingDestination)
|
||||
{
|
||||
if ([_draggingDestination respondsToSelector:@selector(draggingExited:)])
|
||||
[_draggingDestination draggingExited:CPDragServerDraggingInfo];
|
||||
|
||||
_draggingDestination = draggingDestination;
|
||||
|
||||
if ([_draggingDestination respondsToSelector:@selector(wantsPeriodicDraggingUpdates)])
|
||||
_draggingDestinationWantsPeriodicUpdates = [_draggingDestination wantsPeriodicDraggingUpdates];
|
||||
else
|
||||
_draggingDestinationWantsPeriodicUpdates = YES;
|
||||
|
||||
if ([_draggingDestination respondsToSelector:@selector(draggingEntered:)])
|
||||
dragOperation = [_draggingDestination draggingEntered:CPDragServerDraggingInfo];
|
||||
}
|
||||
else if ([_draggingDestination respondsToSelector:@selector(draggingUpdated:)])
|
||||
dragOperation = [_draggingDestination draggingUpdated:CPDragServerDraggingInfo];
|
||||
|
||||
if (!_draggingDestination)
|
||||
dragOperation = CPDragOperationNone;
|
||||
else
|
||||
{
|
||||
if (_draggingDestinationWantsPeriodicUpdates)
|
||||
_draggingUpdateTimer = [CPTimer scheduledTimerWithTimeInterval:CPDragServerPeriodicUpdateInterval
|
||||
target:self
|
||||
selector:@selector(_sendPeriodicDraggingUpdate:)
|
||||
userInfo:[CPDictionary dictionaryWithJSObject:{platformWindow:aPlatformWindow, location:aLocation}]
|
||||
repeats:NO];
|
||||
|
||||
var scrollView = [_draggingDestination isKindOfClass:[CPView class]] ? [_draggingDestination enclosingScrollView] : nil;
|
||||
if (scrollView)
|
||||
{
|
||||
var contentView = [scrollView contentView],
|
||||
bounds = [contentView bounds],
|
||||
insetBounds = CGRectInset(bounds, 30, 30),
|
||||
eventLocation = [contentView convertPoint:_draggingLocation fromView:nil],
|
||||
deltaX = 0,
|
||||
deltaY = 0;
|
||||
|
||||
if (!CGRectContainsPoint(insetBounds, eventLocation))
|
||||
{
|
||||
if ([scrollView hasVerticalScroller])
|
||||
{
|
||||
if (eventLocation.y < _CGRectGetMinY(insetBounds))
|
||||
deltaY = _CGRectGetMinY(insetBounds) - eventLocation.y;
|
||||
else if (eventLocation.y > _CGRectGetMaxY(insetBounds))
|
||||
deltaY = _CGRectGetMaxY(insetBounds) - eventLocation.y;
|
||||
if (deltaY < -insetBounds.size.height)
|
||||
deltaY = -insetBounds.size.height;
|
||||
if (deltaY > insetBounds.size.height)
|
||||
deltaY = insetBounds.size.height;
|
||||
}
|
||||
|
||||
if ([scrollView hasHorizontalScroller])
|
||||
{
|
||||
if (eventLocation.x < _CGRectGetMinX(insetBounds))
|
||||
deltaX = _CGRectGetMinX(insetBounds) - eventLocation.x;
|
||||
else if (eventLocation.x > _CGRectGetMaxX(insetBounds))
|
||||
deltaX = _CGRectGetMaxX(insetBounds) - eventLocation.x;
|
||||
if (deltaX < -insetBounds.size.width)
|
||||
deltaX = -insetBounds.size.width;
|
||||
if (deltaX > insetBounds.size.width)
|
||||
deltaX = insetBounds.size.width;
|
||||
}
|
||||
|
||||
var scrollPoint = _CGPointMake(bounds.origin.x - deltaX, bounds.origin.y - deltaY);
|
||||
|
||||
[contentView scrollToPoint:scrollPoint];
|
||||
[[scrollView _headerView] scrollPoint:scrollPoint];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dragOperation;
|
||||
}
|
||||
|
||||
- (void)_sendPeriodicDraggingUpdate:(CPTimer)aTimer
|
||||
{
|
||||
var userInfo = [aTimer userInfo];
|
||||
_dragOperation = [self draggingUpdatedInPlatformWindow:[userInfo objectForKey:@"platformWindow"]
|
||||
location:[userInfo objectForKey:@"location"]];
|
||||
}
|
||||
|
||||
- (void)draggingEndedInPlatformWindow:(CPPlatformWindow)aPlatformWindow globalLocation:(CGPoint)aLocation operation:(CPDragOperation)anOperation
|
||||
{
|
||||
[_draggingUpdateTimer invalidate];
|
||||
_draggingUpdateTimer = nil;
|
||||
|
||||
[_draggedView removeFromSuperview];
|
||||
|
||||
if (![CPPlatform supportsDragAndDrop])
|
||||
[_draggedWindow orderOut:self];
|
||||
|
||||
if (_implementedDraggingSourceMethods & CPDraggingSource_draggedImage_endedAt_operation_)
|
||||
[_draggingSource draggedImage:[_draggedView image] endedAt:aLocation operation:anOperation];
|
||||
else if (_implementedDraggingSourceMethods & CPDraggingSource_draggedView_endedAt_operation_)
|
||||
[_draggingSource draggedView:_draggedView endedAt:aLocation operation:anOperation];
|
||||
|
||||
_isDragging = NO;
|
||||
}
|
||||
|
||||
- (void)performDragOperationInPlatformWindow:(CPPlatformWindow)aPlatformWindow
|
||||
{
|
||||
if (_draggingDestination &&
|
||||
(![_draggingDestination respondsToSelector:@selector(prepareForDragOperation:)] || [_draggingDestination prepareForDragOperation:CPDragServerDraggingInfo]) &&
|
||||
(![_draggingDestination respondsToSelector:@selector(performDragOperation:)] || [_draggingDestination performDragOperation:CPDragServerDraggingInfo]) &&
|
||||
[_draggingDestination respondsToSelector:@selector(concludeDragOperation:)])
|
||||
[_draggingDestination concludeDragOperation:CPDragServerDraggingInfo];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initiates a drag session.
|
||||
@param aView the view being dragged
|
||||
@param aWindow the window where the drag source is
|
||||
@param viewLocation
|
||||
@param mouseOffset
|
||||
@param anEvent
|
||||
@param aPasteboard the pasteboard that contains the drag data
|
||||
@param aSourceObject the object where the drag started
|
||||
@param slideBack if \c YES, \c aView slides back to
|
||||
its origin on a failed drop
|
||||
*/
|
||||
- (void)dragView:(CPView)aView fromWindow:(CPWindow)aWindow at:(CGPoint)viewLocation offset:(CGSize)mouseOffset event:(CPEvent)mouseDownEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
|
||||
{
|
||||
_isDragging = YES;
|
||||
|
||||
_draggedView = aView;
|
||||
_draggingPasteboard = aPasteboard || [CPPasteboard pasteboardWithName:CPDragPboard];
|
||||
_draggingSource = aSourceObject;
|
||||
_draggingDestination = nil;
|
||||
_shouldSlideBack = slideBack;
|
||||
|
||||
// The offset is based on the distance from where we want the view to be initially from where the mouse is initially
|
||||
// Hence the use of mouseDownEvent's location and view's location in global coordinates.
|
||||
var mouseDownWindow = [mouseDownEvent window],
|
||||
mouseDownEventLocation = [mouseDownEvent locationInWindow];
|
||||
|
||||
if (mouseDownEventLocation)
|
||||
{
|
||||
if (mouseDownWindow)
|
||||
mouseDownEventLocation = [mouseDownWindow convertBaseToGlobal:mouseDownEventLocation];
|
||||
|
||||
_draggingOffset = _CGSizeMake(mouseDownEventLocation.x - viewLocation.x, mouseDownEventLocation.y - viewLocation.y);
|
||||
}
|
||||
else
|
||||
_draggingOffset = _CGSizeMakeZero();
|
||||
|
||||
if ([CPPlatform isBrowser])
|
||||
[_draggedWindow setPlatformWindow:[aWindow platformWindow]];
|
||||
|
||||
[aView setFrameOrigin:_CGPointMakeZero()];
|
||||
|
||||
var mouseLocation = [CPEvent mouseLocation];
|
||||
|
||||
// Place it where the mouse pointer is.
|
||||
_startDragLocation = _CGPointMake(mouseLocation.x - _draggingOffset.width, mouseLocation.y - _draggingOffset.height);
|
||||
[_draggedWindow setFrameOrigin:_startDragLocation];
|
||||
[_draggedWindow setFrameSize:[aView frame].size];
|
||||
|
||||
[[_draggedWindow contentView] addSubview:aView];
|
||||
|
||||
_implementedDraggingSourceMethods = 0;
|
||||
|
||||
if (_draggedView === _imageView)
|
||||
{
|
||||
if ([_draggingSource respondsToSelector:@selector(draggedImage:movedTo:)])
|
||||
_implementedDraggingSourceMethods |= CPDraggingSource_draggedImage_movedTo_;
|
||||
|
||||
if ([_draggingSource respondsToSelector:@selector(draggedImage:endedAt:operation:)])
|
||||
_implementedDraggingSourceMethods |= CPDraggingSource_draggedImage_endedAt_operation_;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([_draggingSource respondsToSelector:@selector(draggedView:movedTo:)])
|
||||
_implementedDraggingSourceMethods |= CPDraggingSource_draggedView_movedTo_;
|
||||
|
||||
if ([_draggingSource respondsToSelector:@selector(draggedView:endedAt:operation:)])
|
||||
_implementedDraggingSourceMethods |= CPDraggingSource_draggedView_endedAt_operation_;
|
||||
}
|
||||
|
||||
if (![CPPlatform supportsDragAndDrop])
|
||||
{
|
||||
[self draggingStartedInPlatformWindow:[aWindow platformWindow] globalLocation:mouseLocation];
|
||||
[self trackDragging:mouseDownEvent];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Initiates a drag session.
|
||||
@param anImage the image to be dragged
|
||||
@param aWindow the source window of the drag session
|
||||
@param imageLocation
|
||||
@param mouseOffset
|
||||
@param anEvent
|
||||
@param aPasteboard the pasteboard where the drag data is located
|
||||
@param aSourceObject the object where the drag started
|
||||
@param slideBack if \c YES, \c aView slides back to
|
||||
its origin on a failed drop
|
||||
*/
|
||||
- (void)dragImage:(CPImage)anImage fromWindow:(CPWindow)aWindow at:(CGPoint)imageLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
|
||||
{
|
||||
_isDraggingImage = YES;
|
||||
|
||||
var imageSize = [anImage size];
|
||||
|
||||
if (!_imageView)
|
||||
_imageView = [[CPImageView alloc] initWithFrame:_CGRectMake(0.0, 0.0, imageSize.width, imageSize.height)];
|
||||
|
||||
[_imageView setImage:anImage];
|
||||
|
||||
[self dragView:_imageView fromWindow:aWindow at:imageLocation offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack];
|
||||
}
|
||||
|
||||
- (void)trackDragging:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type],
|
||||
platformWindow = [_draggedWindow platformWindow],
|
||||
platformWindowLocation = [[anEvent window] convertBaseToPlatformWindow:[anEvent locationInWindow]];
|
||||
|
||||
if (type === CPLeftMouseUp)
|
||||
{
|
||||
// Make sure we do not finalize (cancel) the drag if the last drag update was disallowed
|
||||
if (_dragOperation !== CPDragOperationNone)
|
||||
[self performDragOperationInPlatformWindow:platformWindow];
|
||||
|
||||
[self draggingEndedInPlatformWindow:platformWindow globalLocation:platformWindowLocation operation:_dragOperation];
|
||||
|
||||
// Stop tracking events.
|
||||
return;
|
||||
}
|
||||
else if (type === CPKeyDown)
|
||||
{
|
||||
var characters = [anEvent characters];
|
||||
if (characters === CPEscapeFunctionKey)
|
||||
{
|
||||
_dragOperation = CPDragOperationNone;
|
||||
[self draggingEndedInPlatformWindow:platformWindow globalLocation:CGPointMakeZero() operation:_dragOperation];
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self draggingSourceUpdatedWithGlobalLocation:platformWindowLocation];
|
||||
_dragOperation = [self draggingUpdatedInPlatformWindow:platformWindow location:platformWindowLocation];
|
||||
}
|
||||
|
||||
// If we're not a mouse up, then we're going to want to grab the next event.
|
||||
[CPApp setTarget:self selector:@selector(trackDragging:)
|
||||
forNextEventMatchingMask:CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPKeyDownMask
|
||||
untilDate:nil inMode:0 dequeue:NO];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPWindow (CPDraggingAdditions)
|
||||
|
||||
/* @ignore */
|
||||
- (id)_dragHitTest:(CGPoint)aPoint pasteboard:(CPPasteboard)aPasteboard
|
||||
{
|
||||
// If none of our views or ourselves has registered for drag events...
|
||||
if (!_inclusiveRegisteredDraggedTypes)
|
||||
return nil;
|
||||
|
||||
// We don't need to do this because the only place this gets called
|
||||
// -_dragHitTest: in CPPlatformWindow does this already. Perhaps to
|
||||
// be safe?
|
||||
// if (![self containsPoint:aPoint])
|
||||
// return nil;
|
||||
|
||||
var adjustedPoint = [self convertPlatformWindowToBase:aPoint],
|
||||
hitView = [_windowView hitTest:adjustedPoint];
|
||||
|
||||
while (hitView && ![aPasteboard availableTypeFromArray:[hitView registeredDraggedTypes]])
|
||||
hitView = [hitView superview];
|
||||
|
||||
if (hitView)
|
||||
return hitView;
|
||||
|
||||
if ([aPasteboard availableTypeFromArray:[self registeredDraggedTypes]])
|
||||
return self;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,693 +0,0 @@
|
||||
/*
|
||||
* CPEvent.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CPText.j"
|
||||
|
||||
|
||||
CPLeftMouseDown = 1;
|
||||
CPLeftMouseUp = 2;
|
||||
CPRightMouseDown = 3;
|
||||
CPRightMouseUp = 4;
|
||||
CPMouseMoved = 5;
|
||||
CPLeftMouseDragged = 6;
|
||||
CPRightMouseDragged = 7;
|
||||
CPMouseEntered = 8;
|
||||
CPMouseExited = 9;
|
||||
CPKeyDown = 10;
|
||||
CPKeyUp = 11;
|
||||
CPFlagsChanged = 12;
|
||||
CPAppKitDefined = 13;
|
||||
CPSystemDefined = 14;
|
||||
CPApplicationDefined = 15;
|
||||
CPPeriodic = 16;
|
||||
CPCursorUpdate = 17;
|
||||
CPScrollWheel = 22;
|
||||
CPOtherMouseDown = 25;
|
||||
CPOtherMouseUp = 26;
|
||||
CPOtherMouseDragged = 27;
|
||||
|
||||
// iPhone Event Types
|
||||
CPTouchStart = 28;
|
||||
CPTouchMove = 29;
|
||||
CPTouchEnd = 30;
|
||||
CPTouchCancel = 31;
|
||||
|
||||
CPAlphaShiftKeyMask = 1 << 16;
|
||||
CPShiftKeyMask = 1 << 17;
|
||||
CPControlKeyMask = 1 << 18;
|
||||
CPAlternateKeyMask = 1 << 19;
|
||||
CPCommandKeyMask = 1 << 20;
|
||||
CPNumericPadKeyMask = 1 << 21;
|
||||
CPHelpKeyMask = 1 << 22;
|
||||
CPFunctionKeyMask = 1 << 23;
|
||||
CPDeviceIndependentModifierFlagsMask = 0xffff0000;
|
||||
|
||||
CPLeftMouseDownMask = 1 << CPLeftMouseDown;
|
||||
CPLeftMouseUpMask = 1 << CPLeftMouseUp;
|
||||
CPRightMouseDownMask = 1 << CPRightMouseDown;
|
||||
CPRightMouseUpMask = 1 << CPRightMouseUp;
|
||||
CPOtherMouseDownMask = 1 << CPOtherMouseDown;
|
||||
CPOtherMouseUpMask = 1 << CPOtherMouseUp;
|
||||
CPMouseMovedMask = 1 << CPMouseMoved;
|
||||
CPLeftMouseDraggedMask = 1 << CPLeftMouseDragged;
|
||||
CPRightMouseDraggedMask = 1 << CPRightMouseDragged;
|
||||
CPOtherMouseDragged = 1 << CPOtherMouseDragged;
|
||||
CPMouseEnteredMask = 1 << CPMouseEntered;
|
||||
CPMouseExitedMask = 1 << CPMouseExited;
|
||||
CPCursorUpdateMask = 1 << CPCursorUpdate;
|
||||
CPKeyDownMask = 1 << CPKeyDown;
|
||||
CPKeyUpMask = 1 << CPKeyUp;
|
||||
CPFlagsChangedMask = 1 << CPFlagsChanged;
|
||||
CPAppKitDefinedMask = 1 << CPAppKitDefined;
|
||||
CPSystemDefinedMask = 1 << CPSystemDefined;
|
||||
CPApplicationDefinedMask = 1 << CPApplicationDefined;
|
||||
CPPeriodicMask = 1 << CPPeriodic;
|
||||
CPScrollWheelMask = 1 << CPScrollWheel;
|
||||
CPAnyEventMask = 0xffffffff;
|
||||
|
||||
CPUpArrowFunctionKey = "\uF700";
|
||||
CPDownArrowFunctionKey = "\uF701";
|
||||
CPLeftArrowFunctionKey = "\uF702";
|
||||
CPRightArrowFunctionKey = "\uF703";
|
||||
CPF1FunctionKey = "\uF704";
|
||||
CPF2FunctionKey = "\uF705";
|
||||
CPF3FunctionKey = "\uF706";
|
||||
CPF4FunctionKey = "\uF707";
|
||||
CPF5FunctionKey = "\uF708";
|
||||
CPF6FunctionKey = "\uF709";
|
||||
CPF7FunctionKey = "\uF70A";
|
||||
CPF8FunctionKey = "\uF70B";
|
||||
CPF9FunctionKey = "\uF70C";
|
||||
CPF10FunctionKey = "\uF70D";
|
||||
CPF11FunctionKey = "\uF70E";
|
||||
CPF12FunctionKey = "\uF70F";
|
||||
CPF13FunctionKey = "\uF710";
|
||||
CPF14FunctionKey = "\uF711";
|
||||
CPF15FunctionKey = "\uF712";
|
||||
CPF16FunctionKey = "\uF713";
|
||||
CPF17FunctionKey = "\uF714";
|
||||
CPF18FunctionKey = "\uF715";
|
||||
CPF19FunctionKey = "\uF716";
|
||||
CPF20FunctionKey = "\uF717";
|
||||
CPF21FunctionKey = "\uF718";
|
||||
CPF22FunctionKey = "\uF719";
|
||||
CPF23FunctionKey = "\uF71A";
|
||||
CPF24FunctionKey = "\uF71B";
|
||||
CPF25FunctionKey = "\uF71C";
|
||||
CPF26FunctionKey = "\uF71D";
|
||||
CPF27FunctionKey = "\uF71E";
|
||||
CPF28FunctionKey = "\uF71F";
|
||||
CPF29FunctionKey = "\uF720";
|
||||
CPF30FunctionKey = "\uF721";
|
||||
CPF31FunctionKey = "\uF722";
|
||||
CPF32FunctionKey = "\uF723";
|
||||
CPF33FunctionKey = "\uF724";
|
||||
CPF34FunctionKey = "\uF725";
|
||||
CPF35FunctionKey = "\uF726";
|
||||
CPInsertFunctionKey = "\uF727";
|
||||
CPDeleteFunctionKey = "\uF728";
|
||||
CPHomeFunctionKey = "\uF729";
|
||||
CPBeginFunctionKey = "\uF72A";
|
||||
CPEndFunctionKey = "\uF72B";
|
||||
CPPageUpFunctionKey = "\uF72C";
|
||||
CPPageDownFunctionKey = "\uF72D";
|
||||
CPPrintScreenFunctionKey = "\uF72E";
|
||||
CPScrollLockFunctionKey = "\uF72F";
|
||||
CPPauseFunctionKey = "\uF730";
|
||||
CPSysReqFunctionKey = "\uF731";
|
||||
CPBreakFunctionKey = "\uF732";
|
||||
CPResetFunctionKey = "\uF733";
|
||||
CPStopFunctionKey = "\uF734";
|
||||
CPMenuFunctionKey = "\uF735";
|
||||
CPUserFunctionKey = "\uF736";
|
||||
CPSystemFunctionKey = "\uF737";
|
||||
CPPrintFunctionKey = "\uF738";
|
||||
CPClearLineFunctionKey = "\uF739";
|
||||
CPClearDisplayFunctionKey = "\uF73A";
|
||||
CPInsertLineFunctionKey = "\uF73B";
|
||||
CPDeleteLineFunctionKey = "\uF73C";
|
||||
CPInsertCharFunctionKey = "\uF73D";
|
||||
CPDeleteCharFunctionKey = "\uF73E";
|
||||
CPPrevFunctionKey = "\uF73F";
|
||||
CPNextFunctionKey = "\uF740";
|
||||
CPSelectFunctionKey = "\uF741";
|
||||
CPExecuteFunctionKey = "\uF742";
|
||||
CPUndoFunctionKey = "\uF743";
|
||||
CPRedoFunctionKey = "\uF744";
|
||||
CPFindFunctionKey = "\uF745";
|
||||
CPHelpFunctionKey = "\uF746";
|
||||
CPModeSwitchFunctionKey = "\uF747";
|
||||
CPEscapeFunctionKey = "\u001B";
|
||||
CPSpaceFunctionKey = "\u0020";
|
||||
|
||||
|
||||
CPDOMEventDoubleClick = "dblclick";
|
||||
CPDOMEventMouseDown = "mousedown";
|
||||
CPDOMEventMouseUp = "mouseup";
|
||||
CPDOMEventMouseMoved = "mousemove";
|
||||
CPDOMEventMouseDragged = "mousedrag";
|
||||
CPDOMEventKeyUp = "keyup";
|
||||
CPDOMEventKeyDown = "keydown";
|
||||
CPDOMEventKeyPress = "keypress";
|
||||
CPDOMEventCopy = "copy";
|
||||
CPDOMEventPaste = "paste";
|
||||
CPDOMEventScrollWheel = "mousewheel";
|
||||
CPDOMEventTouchStart = "touchstart";
|
||||
CPDOMEventTouchMove = "touchmove";
|
||||
CPDOMEventTouchEnd = "touchend";
|
||||
CPDOMEventTouchCancel = "touchcancel";
|
||||
|
||||
var _CPEventPeriodicEventPeriod = 0,
|
||||
_CPEventPeriodicEventTimer = nil,
|
||||
_CPEventUpperCaseRegex = new RegExp("[A-Z]"),
|
||||
_CPEventStartupMilliseconds = new Date().getTime();
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPEvent
|
||||
CPEvent encapsulates the details of a Cappuccino keyboard or mouse event.
|
||||
*/
|
||||
@implementation CPEvent : CPObject
|
||||
{
|
||||
CPEventType _type;
|
||||
CPPoint _location;
|
||||
unsigned _modifierFlags;
|
||||
CPTimeInterval _timestamp;
|
||||
CPGraphicsContext _context;
|
||||
int _eventNumber;
|
||||
unsigned _clickCount;
|
||||
float _pressure;
|
||||
CPWindow _window;
|
||||
Number _windowNumber;
|
||||
CPString _characters;
|
||||
CPString _charactersIgnoringModifiers
|
||||
BOOL _isARepeat;
|
||||
unsigned _keyCode;
|
||||
DOMEvent _DOMEvent;
|
||||
int _data1;
|
||||
int _data2;
|
||||
|
||||
float _deltaX;
|
||||
float _deltaY;
|
||||
float _deltaZ;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current time in fractional seconds since startup.
|
||||
*/
|
||||
+ (CPTimeInterval)currentTimestamp
|
||||
{
|
||||
return (new Date().getTime() - _CPEventStartupMilliseconds) / 1000;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new keyboard event.
|
||||
|
||||
@param anEventType the event type. Must be one of CPKeyDown, CPKeyUp or CPFlagsChanged
|
||||
@param aPoint the location of the cursor in the window specified by \c aWindowNumber
|
||||
@param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals
|
||||
@param aTimestamp the time the event occurred
|
||||
@param aWindowNumber the number of the CPWindow where the event occurred
|
||||
@param aGraphicsContext the graphics context where the event occurred
|
||||
@param characters the characters associated with the event
|
||||
@param unmodCharacters the string of keys pressed without the presence of any modifiers other than Shift
|
||||
@param repeatKey \c YES if this is caused by the system repeat as opposed to the user pressing the key again
|
||||
@param code a number associated with the keyboard key of this event
|
||||
@throws CPInternalInconsistencyException if \c anEventType is not a CPKeyDown,
|
||||
CPKeyUp or CPFlagsChanged
|
||||
@return the keyboard event
|
||||
*/
|
||||
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code
|
||||
{
|
||||
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
|
||||
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
|
||||
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new mouse event.
|
||||
|
||||
@param anEventType the event type
|
||||
@param aPoint the location of the cursor in the window specified by \c aWindowNumber
|
||||
@param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals
|
||||
@param aTimestamp the time the event occurred
|
||||
@param aWindowNumber the number of the CPWindow where the event occurred
|
||||
@param aGraphicsContext the graphics context where the event occurred
|
||||
@param anEventNumber a number for this event
|
||||
@param aClickCount the number of clicks that caused this event
|
||||
@param aPressure the amount of pressure applied to the input device (ranges from 0.0 to 1.0)
|
||||
@throws CPInternalInconsistencyException if an invalid event type is provided
|
||||
@return the new mouse event
|
||||
*/
|
||||
+ (id)mouseEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
eventNumber:(int)anEventNumber clickCount:(int)aClickCount pressure:(float)aPressure
|
||||
{
|
||||
return [[self alloc] _initMouseEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
|
||||
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext eventNumber:anEventNumber clickCount:aClickCount pressure:aPressure];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new custom event.
|
||||
|
||||
@param anEventType the event type. Must be one of CPAppKitDefined, CPSystemDefined, CPApplicationDefined or CPPeriodic
|
||||
@param aLocation the location of the cursor in the window specified by \c aWindowNumber
|
||||
@param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals
|
||||
@param aTimestamp the time the event occurred
|
||||
@param aWindowNumber the number of the CPWindow where the event occurred
|
||||
@param aGraphicsContext the graphics context where the event occurred
|
||||
@param aSubtype a numeric identifier to differentiate this event from other custom events
|
||||
@param aData1 more data that describes the event
|
||||
@param aData2 even more data that describes the event
|
||||
@throws CPInternalInconsistencyException if an invalid event type is provided
|
||||
@return the new custom event
|
||||
*/
|
||||
+ (CPEvent)otherEventWithType:(CPEventType)anEventType location:(CGPoint)aLocation modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
subtype:(short)aSubtype data1:(int)aData1 data2:(int)aData2
|
||||
{
|
||||
return [[self alloc] _initOtherEventWithType:anEventType location:aLocation modifierFlags:modifierFlags
|
||||
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext subtype:aSubtype data1:aData1 data2:aData2];
|
||||
}
|
||||
|
||||
- (id)_initWithType:(CPEventType)anEventType
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_type = anEventType;
|
||||
|
||||
// Make sure these are 0 rather than nil.
|
||||
_deltaX = 0;
|
||||
_deltaY = 0;
|
||||
_deltaZ = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initMouseEventWithType:(CPEventType)anEventType location:(CPPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
eventNumber:(int)anEventNumber clickCount:(int)aClickCount pressure:(float)aPressure
|
||||
{
|
||||
if (self = [self _initWithType:anEventType])
|
||||
{
|
||||
_location = CPPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
_eventNumber = anEventNumber;
|
||||
_clickCount = aClickCount;
|
||||
_pressure = aPressure;
|
||||
_window = [CPApp windowWithWindowNumber:aWindowNumber];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initKeyEventWithType:(CPEventType)anEventType location:(CPPoint)aPoint modifierFlags:(unsigned int)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code
|
||||
{
|
||||
if (self = [self _initWithType:anEventType])
|
||||
{
|
||||
_location = CPPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
_characters = characters;
|
||||
_charactersIgnoringModifiers = unmodCharacters;
|
||||
_isARepeat = isARepeat;
|
||||
_keyCode = code;
|
||||
_windowNumber = aWindowNumber;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initOtherEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
subtype:(short)aSubtype data1:(int)aData1 data2:(int)aData2
|
||||
{
|
||||
if (self = [self _initWithType:anEventType])
|
||||
{
|
||||
_location = CPPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
_subtype = aSubtype;
|
||||
_data1 = aData1;
|
||||
_data2 = aData2;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the location of the mouse (for mouse events).
|
||||
If the receiver is not a mouse event, it returns \c nil.
|
||||
If \c window returns \c nil, then the mouse coordinates will be based on the screen coordinates.
|
||||
Otherwise, the coordinates are relative to the window's coordinates.
|
||||
|
||||
@return the location of the mouse, or \c nil for non-mouse events.
|
||||
*/
|
||||
- (CGPoint)locationInWindow
|
||||
{
|
||||
return _CGPointMakeCopy(_location);
|
||||
}
|
||||
|
||||
- (CGPoint)globalLocation
|
||||
{
|
||||
var theWindow = [self window],
|
||||
location = [self locationInWindow];
|
||||
|
||||
if (theWindow)
|
||||
return [theWindow convertBaseToGlobal:location];
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns event information as a bit mask.
|
||||
*/
|
||||
- (unsigned)modifierFlags
|
||||
{
|
||||
return _modifierFlags;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the time the event occurred in seconds since startup.
|
||||
*/
|
||||
- (CPTimeInterval)timestamp
|
||||
{
|
||||
return _timestamp;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the type of event.
|
||||
*/
|
||||
- (CPEventType)type
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the event's associated window.
|
||||
*/
|
||||
- (CPWindow)window
|
||||
{
|
||||
if (!_window)
|
||||
_window = [CPApp windowWithWindowNumber:_windowNumber];
|
||||
|
||||
return _window;
|
||||
}
|
||||
|
||||
/*!
|
||||
The number of the window associated with the event.
|
||||
*/
|
||||
- (int)windowNumber
|
||||
{
|
||||
return _windowNumber;
|
||||
}
|
||||
|
||||
// Mouse Event Information
|
||||
/*!
|
||||
Returns the button number for the mouse that generated the event.
|
||||
*/
|
||||
- (int)buttonNumber
|
||||
{
|
||||
if (_type === CPRightMouseDown || _type === CPRightMouseUp || _type === CPRightMouseDragged)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of clicks that caused this event (mouse only).
|
||||
*/
|
||||
- (int)clickCount
|
||||
{
|
||||
return _clickCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the characters associated with this event (keyboard only).
|
||||
|
||||
@throws CPInternalInconsistencyException if this method is called on a non-key event
|
||||
*/
|
||||
- (CPString)characters
|
||||
{
|
||||
return _characters;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the character ignoring any modifiers (except shift).
|
||||
|
||||
@throws CPInternalInconsistencyException if this method is called on a non-key event
|
||||
*/
|
||||
- (CPString)charactersIgnoringModifiers
|
||||
{
|
||||
return _charactersIgnoringModifiers;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the keyboard event was caused by the key being held down.
|
||||
|
||||
@throws CPInternalInconsistencyException if this method is called on a non-key event
|
||||
*/
|
||||
- (BOOL)isARepeat
|
||||
{
|
||||
return _isARepeat;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the key's key code.
|
||||
|
||||
@throws CPInternalInconsistencyException if this method is called on a non-key event
|
||||
*/
|
||||
- (unsigned short)keyCode
|
||||
{
|
||||
return _keyCode;
|
||||
}
|
||||
|
||||
+ (CGPoint)mouseLocation
|
||||
{
|
||||
// FIXME: this is incorrect, we shouldn't depend on the current event.
|
||||
var event = [CPApp currentEvent],
|
||||
eventWindow = [event window];
|
||||
|
||||
if (eventWindow)
|
||||
return [eventWindow convertBaseToGlobal:[event locationInWindow]];
|
||||
|
||||
return [event locationInWindow];
|
||||
}
|
||||
|
||||
- (float)pressure
|
||||
{
|
||||
return _pressure;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (DOMEvent)_DOMEvent
|
||||
{
|
||||
return _DOMEvent;
|
||||
}
|
||||
|
||||
- (int)data1
|
||||
{
|
||||
return _data1;
|
||||
}
|
||||
|
||||
- (int)data2
|
||||
{
|
||||
return _data2;
|
||||
}
|
||||
|
||||
// Getting Scroll Wheel Event Information
|
||||
/*!
|
||||
Returns the change in the x-axis for a mouse event.
|
||||
*/
|
||||
- (float)deltaX
|
||||
{
|
||||
return _deltaX;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the change in the y-axis for a mouse event.
|
||||
*/
|
||||
- (float)deltaY
|
||||
{
|
||||
return _deltaY;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the change in the x-axis for a mouse event.
|
||||
*/
|
||||
- (float)deltaZ
|
||||
{
|
||||
return _deltaZ;
|
||||
}
|
||||
|
||||
- (BOOL)_triggersKeyEquivalent:(CPString)aKeyEquivalent withModifierMask:aKeyEquivalentModifierMask
|
||||
{
|
||||
if (!aKeyEquivalent)
|
||||
return NO;
|
||||
|
||||
if (_CPEventUpperCaseRegex.test(aKeyEquivalent))
|
||||
aKeyEquivalentModifierMask |= CPShiftKeyMask;
|
||||
|
||||
// Windows and Linux don't have command keys, so just switch it to ctrl.
|
||||
if (!CPBrowserIsOperatingSystem(CPMacOperatingSystem) && (aKeyEquivalentModifierMask & CPCommandKeyMask))
|
||||
{
|
||||
aKeyEquivalentModifierMask |= CPControlKeyMask;
|
||||
aKeyEquivalentModifierMask &= ~CPCommandKeyMask;
|
||||
}
|
||||
|
||||
if ((_modifierFlags & (CPShiftKeyMask | CPAlternateKeyMask | CPCommandKeyMask | CPControlKeyMask)) !== aKeyEquivalentModifierMask)
|
||||
return NO;
|
||||
|
||||
// Treat \r and \n as the same key equivalent. See issue #710.
|
||||
if (_characters === CPNewlineCharacter || _characters === CPCarriageReturnCharacter)
|
||||
return CPNewlineCharacter === aKeyEquivalent || CPCarriageReturnCharacter === aKeyEquivalent;
|
||||
|
||||
return [_characters caseInsensitiveCompare:aKeyEquivalent] === CPOrderedSame;
|
||||
}
|
||||
|
||||
- (BOOL)_couldBeKeyEquivalent
|
||||
{
|
||||
if (_type !== CPKeyDown)
|
||||
return NO;
|
||||
|
||||
var characterCount = _characters.length;
|
||||
|
||||
if (!characterCount)
|
||||
return NO;
|
||||
|
||||
if (_modifierFlags & (CPCommandKeyMask | CPControlKeyMask))
|
||||
return YES;
|
||||
|
||||
// Cocoa allows almost any key as a key equivalent unless the first responder is a
|
||||
// text field (presumably a subclass of NSText.)
|
||||
var firstResponderIsText = [[_window firstResponder] isKindOfClass:[CPTextField class]];
|
||||
|
||||
// Some keys are accepted as key equivalents even if the first responder is a text
|
||||
// field.
|
||||
for (var i = 0; i < characterCount; i++)
|
||||
{
|
||||
var c = _characters.charAt(i);
|
||||
|
||||
if ((c >= CPUpArrowFunctionKey && c <= CPModeSwitchFunctionKey) ||
|
||||
c === CPEnterCharacter ||
|
||||
c === CPNewlineCharacter ||
|
||||
c === CPCarriageReturnCharacter ||
|
||||
c === CPEscapeFunctionKey)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return !firstResponderIsText;
|
||||
}
|
||||
|
||||
/*!
|
||||
Generates periodic events every \c aPeriod seconds.
|
||||
|
||||
@param aDelay the number of seconds before the first event
|
||||
@param aPeriod the length of time in seconds between successive events
|
||||
*/
|
||||
+ (void)startPeriodicEventsAfterDelay:(CPTimeInterval)aDelay withPeriod:(CPTimeInterval)aPeriod
|
||||
{
|
||||
_CPEventPeriodicEventPeriod = aPeriod;
|
||||
|
||||
// FIXME: OH TIMERS!!!
|
||||
_CPEventPeriodicEventTimer = window.setTimeout(function() { _CPEventPeriodicEventTimer = window.setInterval(_CPEventFirePeriodEvent, aPeriod * 1000.0); }, aDelay * 1000.0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Stops the periodic events from being generated.
|
||||
*/
|
||||
+ (void)stopPeriodicEvents
|
||||
{
|
||||
if (_CPEventPeriodicEventTimer === nil)
|
||||
return;
|
||||
|
||||
window.clearTimeout(_CPEventPeriodicEventTimer);
|
||||
|
||||
_CPEventPeriodicEventTimer = nil;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
switch (_type)
|
||||
{
|
||||
case CPKeyDown:
|
||||
case CPKeyUp:
|
||||
case CPFlagsChanged:
|
||||
return [CPString stringWithFormat:@"CPEvent: type=%d loc=%@ time=%.1f flags=0x%X win=%@ winNum=%d ctxt=%@ chars=\"%@\" unmodchars=\"%@\" repeat=%d keyCode=%d", _type, CPStringFromPoint(_location), _timestamp, _modifierFlags, _window, _windowNumber, _context, _characters, _charactersIgnoringModifiers, _isARepeat, _keyCode];
|
||||
case CPLeftMouseDown:
|
||||
case CPLeftMouseUp:
|
||||
case CPRightMouseDown:
|
||||
case CPRightMouseUp:
|
||||
case CPMouseMoved:
|
||||
case CPLeftMouseDragged:
|
||||
case CPRightMouseDragged:
|
||||
case CPMouseEntered:
|
||||
case CPMouseExited:
|
||||
return [CPString stringWithFormat:@"CPEvent: type=%d loc=%@ time=%.1f flags=0x%X win=%@ winNum=%d ctxt=%@ evNum=%d click=%d buttonNumber=%d pressure=%f", _type, CPStringFromPoint(_location), _timestamp, _modifierFlags, _window, _windowNumber, _context, _eventNumber, _clickCount, [self buttonNumber], _pressure];
|
||||
default:
|
||||
return [CPString stringWithFormat:@"CPEvent: type=%d loc=%@ time=%.1f flags=0x%X win=%@ winNum=%d ctxt=%@ subtype=%d data1=%d data2=%d", _type, CPStringFromPoint(_location), _timestamp, _modifierFlags, _window, _windowNumber, _context, _subtype, _data1, _data2];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
function _CPEventFirePeriodEvent()
|
||||
{
|
||||
[CPApp sendEvent:[CPEvent otherEventWithType:CPPeriodic location:_CGPointMakeZero() modifierFlags:0 timestamp:[CPEvent currentTimestamp] windowNumber:0 context:nil subtype:0 data1:0 data2:0]];
|
||||
}
|
||||
|
||||
var CPEventClass = [CPEvent class];
|
||||
|
||||
function _CPEventFromNativeMouseEvent(aNativeEvent, anEventType, aPoint, modifierFlags, aTimestamp, aWindowNumber, aGraphicsContext, anEventNumber, aClickCount, aPressure)
|
||||
{
|
||||
aNativeEvent.isa = CPEventClass;
|
||||
|
||||
aNativeEvent._type = anEventType;
|
||||
aNativeEvent._location = aPoint;
|
||||
aNativeEvent._modifierFlags = modifierFlags;
|
||||
aNativeEvent._timestamp = aTimestamp;
|
||||
aNativeEvent._windowNumber = aWindowNumber;
|
||||
aNativeEvent._window = nil;
|
||||
aNativeEvent._context = aGraphicsContext;
|
||||
aNativeEvent._eventNumber = anEventNumber;
|
||||
aNativeEvent._clickCount = aClickCount;
|
||||
aNativeEvent._pressure = aPressure;
|
||||
|
||||
return aNativeEvent;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* CPFlashMovie.j
|
||||
* AppKit
|
||||
*
|
||||
* 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>
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPFlashMovie
|
||||
|
||||
CPFlashMovie is used to represent a Flash movie in the Cappuccino framework.
|
||||
*/
|
||||
@implementation CPFlashMovie : CPObject
|
||||
{
|
||||
CPString _filename;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new Flash movie with the swf at \c aFileName.
|
||||
@param aFilename the swf to load
|
||||
@return the initialized CPFlashMovie
|
||||
*/
|
||||
+ (id)flashMovieWithFile:(CPString)aFilename
|
||||
{
|
||||
return [[self alloc] initWithFile:aFilename];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the Flash movie.
|
||||
@param aFilename the swf to load
|
||||
@return the initialized CPFlashMovie
|
||||
*/
|
||||
- (id)initWithFile:(CPString)aFilename
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_filename = aFilename;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)filename
|
||||
{
|
||||
return _filename;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPFlashMovieFilenameKey = "CPFlashMovieFilenameKey";
|
||||
|
||||
@implementation CPFlashMovie (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
_filename = [aCoder decodeObjectForKey:CPFlashMovieFilenameKey];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_filename forKey:CPFlashMovieFilenameKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* CPFlashView.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CPFlashMovie.j"
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
var IEFlashCLSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
*/
|
||||
@implementation CPFlashView : CPView
|
||||
{
|
||||
CPFlashMovie _flashMovie;
|
||||
|
||||
CPDictionary _params;
|
||||
CPDictionary _paramElements;
|
||||
#if PLATFORM(DOM)
|
||||
DOMElement _DOMParamElement;
|
||||
DOMElement _DOMObjectElement;
|
||||
DOMElement _DOMInnerObjectElement;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
_DOMObjectElement = document.createElement(@"object");
|
||||
_DOMObjectElement.width = @"100%";
|
||||
_DOMObjectElement.height = @"100%";
|
||||
_DOMObjectElement.style.top = @"0px";
|
||||
_DOMObjectElement.style.left = @"0px";
|
||||
_DOMObjectElement.type = @"application/x-shockwave-flash";
|
||||
_DOMObjectElement.setAttribute(@"classid", IEFlashCLSID);
|
||||
|
||||
_DOMParamElement = document.createElement(@"param");
|
||||
_DOMParamElement.name = @"movie";
|
||||
|
||||
_DOMInnerObjectElement = document.createElement(@"object");
|
||||
_DOMInnerObjectElement.width = @"100%";
|
||||
_DOMInnerObjectElement.height = @"100%";
|
||||
|
||||
_DOMObjectElement.appendChild(_DOMParamElement);
|
||||
_DOMObjectElement.appendChild(_DOMInnerObjectElement);
|
||||
|
||||
_DOMElement.appendChild(_DOMObjectElement);
|
||||
}
|
||||
else
|
||||
[self _rebuildIEObjects];
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFlashMovie:(CPFlashMovie)aFlashMovie
|
||||
{
|
||||
if (_flashMovie == aFlashMovie)
|
||||
return;
|
||||
|
||||
_flashMovie = aFlashMovie;
|
||||
#if PLATFORM(DOM)
|
||||
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
_DOMParamElement.value = [aFlashMovie filename];
|
||||
_DOMInnerObjectElement.data = [aFlashMovie filename];
|
||||
}
|
||||
else
|
||||
[self _rebuildIEObjects];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (CPFlashMovie)flashMovie
|
||||
{
|
||||
return _flashMovie;
|
||||
}
|
||||
|
||||
- (void)setFlashVars:(CPDictionary)aDictionary
|
||||
{
|
||||
var varString = @"",
|
||||
enumerator = [aDictionary keyEnumerator],
|
||||
key;
|
||||
|
||||
while ((key = [enumerator nextObject]) !== nil)
|
||||
varString = [varString stringByAppendingFormat:@"&%@=%@", key, [aDictionary objectForKey:key]];
|
||||
|
||||
if (!_params)
|
||||
_params = [CPDictionary dictionary];
|
||||
|
||||
[_params setObject:varString forKey:@"flashvars"];
|
||||
[self setParameters:_params];
|
||||
}
|
||||
|
||||
- (CPDictionary)flashVars
|
||||
{
|
||||
return [_params objectForKey:@"flashvars"];
|
||||
}
|
||||
|
||||
- (void)setParameters:(CPDictionary)aDictionary
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
if (_paramElements && !CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
var elements = [_paramElements allValues],
|
||||
count = [elements count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
_DOMObjectElement.removeChild([elements objectAtIndex:i]);
|
||||
}
|
||||
#endif
|
||||
if (!_params)
|
||||
_params = aDictionary;
|
||||
else
|
||||
[_params addEntriesFromDictionary:aDictionary];
|
||||
#if PLATFORM(DOM)
|
||||
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
_paramElements = [CPDictionary dictionary];
|
||||
|
||||
var enumerator = [_params keyEnumerator],
|
||||
key;
|
||||
|
||||
while (_DOMObjectElement && (key = [enumerator nextObject]) !== nil)
|
||||
{
|
||||
var param = document.createElement(@"param");
|
||||
param.name = key;
|
||||
param.value = [_params objectForKey:key];
|
||||
|
||||
_DOMObjectElement.appendChild(param);
|
||||
|
||||
[_paramElements setObject:param forKey:key];
|
||||
}
|
||||
}
|
||||
else
|
||||
[self _rebuildIEObjects];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (CPDictionary)parameters
|
||||
{
|
||||
return _params;
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
- (void)_rebuildIEObjects
|
||||
{
|
||||
_DOMElement.innerHTML = @"";
|
||||
if (![_flashMovie filename])
|
||||
return;
|
||||
|
||||
var paramString = [CPString stringWithFormat:@"<param name='movie' value='%@' />", [_flashMovie filename]],
|
||||
paramEnumerator = [_params keyEnumerator],
|
||||
key;
|
||||
|
||||
while ((key = [paramEnumerator nextObject]) !== nil)
|
||||
paramString = [paramString stringByAppendingFormat:@"<param name='%@' value='%@' />", key, [_params objectForKey:key]];
|
||||
|
||||
_DOMObjectElement = document.createElement(@"object");
|
||||
_DOMElement.appendChild(_DOMObjectElement);
|
||||
|
||||
_DOMObjectElement.outerHTML = [CPString stringWithFormat:@"<object classid=%@ width=%@ height=%@>%@</object>", IEFlashCLSID, CGRectGetWidth([self bounds]), CGRectGetHeight([self bounds]), paramString];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
-514
@@ -1,514 +0,0 @@
|
||||
/*
|
||||
* CPFont.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 <Foundation/CPBundle.j>
|
||||
|
||||
@import "CPView.j"
|
||||
|
||||
CPFontDefaultSystemFontFace = @"Arial, sans-serif";
|
||||
CPFontDefaultSystemFontSize = 12;
|
||||
|
||||
/*!
|
||||
To create a font of a size that will dynamically reflect the current
|
||||
system font size, use this for the size argument.
|
||||
*/
|
||||
CPFontCurrentSystemSize = -1;
|
||||
|
||||
// For internal use only by this class and subclasses
|
||||
_CPFontSystemFacePlaceholder = "_CPFontSystemFacePlaceholder";
|
||||
|
||||
var _CPFontCache = {},
|
||||
_CPSystemFontCache = {},
|
||||
_CPFontSystemFontFace = CPFontDefaultSystemFontFace,
|
||||
_CPFontSystemFontSize = 12,
|
||||
_CPFontFallbackFaces = CPFontDefaultSystemFontFace.split(", "),
|
||||
_CPFontStripRegExp = new RegExp("(^\\s*[\"']?|[\"']?\\s*$)", "g");
|
||||
|
||||
|
||||
#define _CPRealFontSize(aSize) (aSize <= 0 ? _CPFontSystemFontSize : aSize)
|
||||
#define _CPFontNormalizedNames(aName) _CPFontNormalizedNameArray(aName).join(", ")
|
||||
#define _CPCachedFont(aName, aSize, isBold, isItalic) _CPFontCache[_CPFontCreateCSSString(_CPFontNormalizedNames(aName), aSize, isBold, isItalic)]
|
||||
#define _CPUserFont(aName, aSize, isBold, isItalic) _CPCachedFont(aName, aSize, isBold, isItalic) || [[CPFont alloc] _initWithName:aName size:aSize bold:isBold italic:isItalic system:NO]
|
||||
|
||||
#define _CPSystemFontCacheKey(aSize, isBold) (String(aSize) + (isBold ? "b" : ""))
|
||||
#define _CPCachedSystemFont(aSize, isBold) _CPSystemFontCache[_CPSystemFontCacheKey(aSize, isBold)]
|
||||
#define _CPSystemFont(aSize, isBold) (_CPCachedSystemFont(aSize, isBold) || [[CPFont alloc] _initWithName:_CPFontSystemFacePlaceholder size:aSize bold:isBold italic:NO system:YES])
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPFont
|
||||
|
||||
The CPFont class allows control of the fonts used for displaying text anywhere on the screen.
|
||||
The primary method for getting a particular font is through one of the class methods that take
|
||||
a name and/or size as arguments, and return the appropriate CPFont.
|
||||
|
||||
### System fonts
|
||||
When you create a font using \c -systemFontOfSize: or \c -boldSystemFontOfSize:, a proxy font is
|
||||
created that always refers to the current system font face and size. By default the system
|
||||
font face/size is Arial 12px, with a fallback to sans-serif 12px. You may configure this at
|
||||
runtime in two ways:
|
||||
|
||||
- By sending [CPFont setSystemFontFace:<face>] and/or [CPFont setSystemFontSize:<size>].
|
||||
Note that if you change the system font face or size during runtime, the next time
|
||||
any view using a system font is redrawn, it will show the new font.
|
||||
- By configuring Info.plist for your application or for AppKit. You can set the font face
|
||||
by adding a CPSystemFontFace string item to the Info.plist, and you can set the font size
|
||||
by adding a CPSystemFontSize integer item to the Info.plist.
|
||||
|
||||
Note that in either case, you can specify a comma-delimited list of fonts as the font face.
|
||||
Do not quote enclose font faces that contain spaces, that is done automatically when a CSS
|
||||
representation of a font is requested.
|
||||
|
||||
The browser will use the first available font in the list you supply. CPFont always ensures
|
||||
that Arial and sans-serif are in the CSS representation of a font as a fallback, so there is
|
||||
no need to add them to the end of your font list.
|
||||
|
||||
### nib2cib conversion
|
||||
Fonts are converted by nib2cib according to the following algorithm:
|
||||
|
||||
- If the font family is Lucida Grande, then a system font will be created.
|
||||
- If the font is Lucida Grande 13 (plain), the "default" font will be used at runtime.
|
||||
The default font is taken from the "font" theme attribute if one exists,
|
||||
otherwise from the current system font face and size.
|
||||
- Lucida Grande of any size other than 13 will retain its size.
|
||||
- Fonts using any family other than Lucida Grande will be used as is, including the size.
|
||||
|
||||
### Using custom web fonts
|
||||
The configurability of CPFont makes it easy to use a custom web font as the system font.
|
||||
For example, if you want to use the google font Asap as the system font, you would do the
|
||||
following:
|
||||
|
||||
- Add a <link> to the <head> of index-debug.html and index.html:
|
||||
@code
|
||||
<link href='http://fonts.googleapis.com/css?family=Asap:400,700' rel='stylesheet' type='text/css'>
|
||||
@endcode
|
||||
- Specify Asap as the system font in Info.plist by adding the following item:
|
||||
@code
|
||||
<key>CPSystemFontFace</key>
|
||||
<string>Asap</string>
|
||||
@endcode
|
||||
*/
|
||||
@implementation CPFont : CPObject
|
||||
{
|
||||
CPString _name;
|
||||
float _size;
|
||||
float _ascender;
|
||||
float _descender;
|
||||
float _lineHeight;
|
||||
BOOL _isBold @accessors(readonly, getter=isBold);
|
||||
BOOL _isItalic @accessors(readonly, getter=isItalic);
|
||||
BOOL _isSystem @accessors(readonly, getter=isSystem);
|
||||
|
||||
CPString _cssString;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPFont class])
|
||||
return;
|
||||
|
||||
var systemFontFace = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPSystemFontFace"];
|
||||
|
||||
if (!systemFontFace)
|
||||
systemFontFace = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontFace"];
|
||||
|
||||
if (systemFontFace)
|
||||
_CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace);
|
||||
|
||||
var systemFontSize = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPSystemFontSize"];
|
||||
|
||||
if (!systemFontSize)
|
||||
systemFontSize = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontSize"];
|
||||
|
||||
if (systemFontSize)
|
||||
_CPFontSystemFontSize = systemFontSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the default system font face, which may consist of several comma-separated family names.
|
||||
*/
|
||||
+ (CPString)systemFontFace
|
||||
{
|
||||
return _CPFontSystemFontFace;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the default system font face, which may consist of several comma-separated family names.
|
||||
*/
|
||||
+ (CPString)setSystemFontFace:(CPString)aFace
|
||||
{
|
||||
var normalizedFaces = _CPFontNormalizedNames(aFace);
|
||||
|
||||
if (normalizedFaces === _CPFontSystemFontFace)
|
||||
return;
|
||||
|
||||
[self _invalidateSystemFontCache]
|
||||
_CPFontSystemFontFace = aFace;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the default system font size.
|
||||
*/
|
||||
+ (float)systemFontSize
|
||||
{
|
||||
return _CPFontSystemFontSize;
|
||||
}
|
||||
|
||||
+ (float)systemFontSizeForControlSize:(CPControlSize)aSize
|
||||
{
|
||||
// TODO These sizes should be themable or made less arbitrary in some other way.
|
||||
switch (aSize)
|
||||
{
|
||||
case CPSmallControlSize:
|
||||
return _CPFontSystemFontSize - 1;
|
||||
case CPMiniControlSize:
|
||||
return _CPFontSystemFontSize - 2;
|
||||
case CPRegularControlSize:
|
||||
default:
|
||||
return _CPFontSystemFontSize;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the default system font size.
|
||||
*/
|
||||
+ (float)setSystemFontSize:(float)size
|
||||
{
|
||||
if (size > 0 && size !== _CPFontSystemFontSize)
|
||||
{
|
||||
[self _invalidateSystemFontCache];
|
||||
_CPFontSystemFontSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)_invalidateSystemFontCache
|
||||
{
|
||||
var systemSize = String(_CPFontSystemFontSize),
|
||||
currentSize = String(CPFontCurrentSystemSize);
|
||||
|
||||
for (key in _CPSystemFontCache)
|
||||
{
|
||||
if (_CPSystemFontCache.hasOwnProperty(key) &&
|
||||
(key.indexOf(systemSize) === 0 || key.indexOf(currentSize) === 0))
|
||||
{
|
||||
delete _CPSystemFontCache[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a font with the specified name and size.
|
||||
@param aName the name of the font
|
||||
@param aSize the size of the font (in px). 0 or negative will create a font
|
||||
in the current system font size.
|
||||
@return the requested font
|
||||
*/
|
||||
+ (CPFont)fontWithName:(CPString)aName size:(float)aSize
|
||||
{
|
||||
return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, NO, NO);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a font with the specified name, size and style.
|
||||
@param aName the name of the font
|
||||
@param aSize the size of the font (in px). 0 or negative will create a font
|
||||
in the current system font size.
|
||||
@param italic whether the font should be italicized
|
||||
@return the requested font
|
||||
*/
|
||||
+ (CPFont)fontWithName:(CPString)aName size:(float)aSize italic:(BOOL)italic
|
||||
{
|
||||
return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, NO, italic);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a bold font with the specified name and size.
|
||||
@param aName the name of the font
|
||||
@param aSize the size of the font (in px). 0 or negative will create a font
|
||||
in the current system font size.
|
||||
@return the requested bold font
|
||||
*/
|
||||
+ (CPFont)boldFontWithName:(CPString)aName size:(float)aSize
|
||||
{
|
||||
return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, YES, NO);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a bold font with the specified name, size and style.
|
||||
@param aName the name of the font
|
||||
@param aSize the size of the font (in px). 0 or negative will create a font
|
||||
in the current system font size.
|
||||
@param italic whether the font should be italicized
|
||||
@return the requested font
|
||||
*/
|
||||
+ (CPFont)boldFontWithName:(CPString)aName size:(float)aSize italic:(BOOL)italic
|
||||
{
|
||||
return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, YES, italic);
|
||||
}
|
||||
|
||||
/*!
|
||||
Internal font getter like fontWithName:size:italic: with a bold selector.
|
||||
*/
|
||||
+ (CPFont)_fontWithName:(CPString)aName size:(float)aSize bold:(BOOL)bold italic:(BOOL)italic
|
||||
{
|
||||
return _CPUserFont(aName, aSize <= 0 ? _CPFontSystemFontSize : aSize, bold, italic);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the system font scaled to the specified size
|
||||
@param aSize the size of the font (in px). 0 creates a static font
|
||||
in the current system font size. Negative creates a font
|
||||
that dynamically tracks the current system font size.
|
||||
@return the requested system font
|
||||
*/
|
||||
+ (CPFont)systemFontOfSize:(CPSize)aSize
|
||||
{
|
||||
return _CPSystemFont(aSize === 0 ? _CPFontSystemFontSize : aSize, NO);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the bold system font scaled to the specified size
|
||||
@param aSize the size of the font (in px). 0 creates a static font
|
||||
in the current system font size. Negative creates a font
|
||||
that dynamically tracks the current system font size.
|
||||
@return the requested bold system font
|
||||
*/
|
||||
+ (CPFont)boldSystemFontOfSize:(CPSize)aSize
|
||||
{
|
||||
return _CPSystemFont(aSize === 0 ? _CPFontSystemFontSize : aSize, YES);
|
||||
}
|
||||
|
||||
- (id)_initWithName:(CPString)aName size:(float)aSize bold:(BOOL)isBold italic:(BOOL)isItalic system:(BOOL)isSystem
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_size = aSize;
|
||||
_ascender = 0;
|
||||
_descender = 0;
|
||||
_lineHeight = 0;
|
||||
_isBold = isBold;
|
||||
_isItalic = isItalic;
|
||||
_isSystem = isSystem;
|
||||
|
||||
if (isSystem)
|
||||
{
|
||||
_name = aName;
|
||||
_cssString = _CPFontCreateCSSString(_CPFontSystemFontFace, _size, _isBold, _isItalic);
|
||||
_CPSystemFontCache[_CPSystemFontCacheKey(_size, _isBold)] = self;
|
||||
}
|
||||
else
|
||||
{
|
||||
_name = _CPFontNormalizedNames(aName);
|
||||
_cssString = _CPFontCreateCSSString(_name, _size, _isBold, _isItalic);
|
||||
_CPFontCache[_cssString] = self;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the distance of the longest ascender's top y-coordinate from the baseline (in CSS px)
|
||||
*/
|
||||
- (float)ascender
|
||||
{
|
||||
var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
|
||||
|
||||
if (!font._ascender)
|
||||
[font _getMetrics];
|
||||
|
||||
return font._ascender;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the bottom y coordinate (in CSS px), offset from the baseline, of the receiver's longest descender.
|
||||
Thus, if the longest descender extends 2 px below the baseline, descender will return –2.
|
||||
*/
|
||||
- (float)descender
|
||||
{
|
||||
var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
|
||||
|
||||
if (!font._descender)
|
||||
[font _getMetrics];
|
||||
|
||||
return font._descender;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the default line height.
|
||||
|
||||
NOTE: This was moved from NSFont to NSLayoutManager in Cocoa, but since there is no CPLayoutManager, it has been kept here.
|
||||
*/
|
||||
- (float)defaultLineHeightForFont
|
||||
{
|
||||
var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
|
||||
|
||||
if (!font._lineHeight)
|
||||
[font _getMetrics];
|
||||
|
||||
return font._lineHeight;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the font size (in CSS px)
|
||||
*/
|
||||
- (float)size
|
||||
{
|
||||
return _CPRealFontSize(_size);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the font as a CSS string
|
||||
*/
|
||||
- (CPString)cssString
|
||||
{
|
||||
var font = _isSystem ? _CPSystemFont(_size, _isBold) : self;
|
||||
|
||||
return font._cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the font's family name
|
||||
*/
|
||||
- (CPString)familyName
|
||||
{
|
||||
if (_isSystem)
|
||||
return _CPFontSystemFontFace;
|
||||
|
||||
return _name;
|
||||
}
|
||||
|
||||
- (BOOL)isSystemSize
|
||||
{
|
||||
return _size <= 0;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)anObject
|
||||
{
|
||||
return [anObject isKindOfClass:[CPFont class]] && [anObject cssString] === _cssString;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return [CPString stringWithFormat:@"%@ %@", [super description], [self cssString]];
|
||||
}
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
return [[CPFont alloc] _initWithName:_name size:_size bold:_isBold italic:_isItalic system:_isSystem];
|
||||
}
|
||||
|
||||
- (void)_getMetrics
|
||||
{
|
||||
var metrics = [CPString metricsOfFont:self];
|
||||
|
||||
_ascender = [metrics objectForKey:@"ascender"];
|
||||
_descender = [metrics objectForKey:@"descender"];
|
||||
_lineHeight = [metrics objectForKey:@"lineHeight"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPFontNameKey = @"CPFontNameKey",
|
||||
CPFontSizeKey = @"CPFontSizeKey",
|
||||
CPFontIsBoldKey = @"CPFontIsBoldKey",
|
||||
CPFontIsItalicKey = @"CPFontIsItalicKey",
|
||||
CPFontIsSystemKey = @"CPFontIsSystemKey";
|
||||
|
||||
@implementation CPFont (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the font from a coder.
|
||||
@param aCoder the coder from which to read the font data
|
||||
@return the initialized font
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
var fontName = [aCoder decodeObjectForKey:CPFontNameKey],
|
||||
size = [aCoder decodeFloatForKey:CPFontSizeKey],
|
||||
isBold = [aCoder decodeBoolForKey:CPFontIsBoldKey],
|
||||
isItalic = [aCoder decodeBoolForKey:CPFontIsItalicKey],
|
||||
isSystem = [aCoder decodeBoolForKey:CPFontIsSystemKey];
|
||||
|
||||
return [self _initWithName:fontName size:size bold:isBold italic:isItalic system:isSystem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes the font information out to a coder.
|
||||
@param aCoder the coder to which the data will be written
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_name forKey:CPFontNameKey];
|
||||
[aCoder encodeFloat:_size forKey:CPFontSizeKey];
|
||||
[aCoder encodeBool:_isBold forKey:CPFontIsBoldKey];
|
||||
[aCoder encodeBool:_isItalic forKey:CPFontIsItalicKey];
|
||||
[aCoder encodeBool:_isSystem forKey:CPFontIsSystemKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// aName must be normalized
|
||||
var _CPFontCreateCSSString = function(aName, aSize, isBold, isItalic)
|
||||
{
|
||||
var properties = (isItalic ? "italic " : "") + (isBold ? "bold " : "") + _CPRealFontSize(aSize) + "px ";
|
||||
|
||||
return properties + _CPFontConcatNameWithFallback(aName);
|
||||
};
|
||||
|
||||
var _CPFontConcatNameWithFallback = function(aName)
|
||||
{
|
||||
var names = _CPFontNormalizedNameArray(aName),
|
||||
fallbackFaces = _CPFontFallbackFaces.slice(0);
|
||||
|
||||
// Remove the fallback names used in the names passed in
|
||||
for (var i = 0; i < names.length; ++i)
|
||||
{
|
||||
for (var j = 0; j < fallbackFaces.length; ++j)
|
||||
{
|
||||
if (names[i].toLowerCase() === fallbackFaces[j].toLowerCase())
|
||||
{
|
||||
fallbackFaces.splice(j, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (names[i].indexOf(" ") > 0)
|
||||
names[i] = '"' + names[i] + '"';
|
||||
}
|
||||
|
||||
return names.concat(fallbackFaces).join(", ");
|
||||
};
|
||||
|
||||
var _CPFontNormalizedNameArray = function(aName)
|
||||
{
|
||||
var names = aName.split(",");
|
||||
|
||||
for (var i = 0; i < names.length; ++i)
|
||||
names[i] = names[i].replace(_CPFontStripRegExp, "");
|
||||
|
||||
return names;
|
||||
};
|
||||
@@ -1,304 +0,0 @@
|
||||
/*
|
||||
* CPFontManager.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Tom Robinson.
|
||||
* 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 "CPFont.j"
|
||||
|
||||
CPItalicFontMask = 1 << 0;
|
||||
CPBoldFontMask = 1 << 1;
|
||||
CPUnboldFontMask = 1 << 2;
|
||||
CPNonStandardCharacterSetFontMask = 1 << 3;
|
||||
CPNarrowFontMask = 1 << 4;
|
||||
CPExpandedFontMask = 1 << 5;
|
||||
CPCondensedFontMask = 1 << 6;
|
||||
CPSmallCapsFontMask = 1 << 7;
|
||||
CPPosterFontMask = 1 << 8;
|
||||
CPCompressedFontMask = 1 << 9;
|
||||
CPFixedPitchFontMask = 1 << 10;
|
||||
CPUnitalicFontMask = 1 << 24;
|
||||
|
||||
|
||||
var CPSharedFontManager = nil,
|
||||
CPFontManagerFactory = Nil;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
*/
|
||||
@implementation CPFontManager : CPObject
|
||||
{
|
||||
CPArray _availableFonts;
|
||||
|
||||
id _target @accessors(property=target);
|
||||
SEL _action @accessors(property=action);
|
||||
|
||||
id _delegate @accessors(property=delegate);
|
||||
|
||||
CPFont _selectedFont;
|
||||
BOOL _multiple @accessors;
|
||||
|
||||
CPDictionary _activeChange;
|
||||
}
|
||||
|
||||
// Getting the Shared Font Manager
|
||||
/*!
|
||||
Returns the application's font manager. If the font
|
||||
manager does not exist yet, it will be created.
|
||||
*/
|
||||
+ (CPFontManager)sharedFontManager
|
||||
{
|
||||
if (!CPSharedFontManager)
|
||||
CPSharedFontManager = [[CPFontManagerFactory alloc] init];
|
||||
|
||||
return CPSharedFontManager;
|
||||
}
|
||||
|
||||
// Changing the Default Font Conversion Classes
|
||||
/*!
|
||||
Sets the class that will be used to create the application's
|
||||
font manager.
|
||||
*/
|
||||
+ (void)setFontManagerFactory:(Class)aClass
|
||||
{
|
||||
CPFontManagerFactory = aClass;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_action = @selector(changeFont:);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of the available fonts
|
||||
*/
|
||||
- (CPArray)availableFonts
|
||||
{
|
||||
if (!_availableFonts)
|
||||
{
|
||||
_availableFonts = [];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_CPFontDetectSpan = document.createElement("span");
|
||||
_CPFontDetectSpan.fontSize = "24px";
|
||||
_CPFontDetectSpan.appendChild(document.createTextNode("mmmmmmmmmml"));
|
||||
var div = document.createElement("div");
|
||||
div.style.position = "absolute";
|
||||
div.style.top = "-1000px";
|
||||
div.appendChild(_CPFontDetectSpan);
|
||||
document.getElementsByTagName("body")[0].appendChild(div);
|
||||
|
||||
_CPFontDetectReferenceFonts = _CPFontDetectPickTwoDifferentFonts(["monospace", "serif", "sans-serif", "cursive"]);
|
||||
|
||||
for (var i = 0; i < _CPFontDetectAllFonts.length; i++)
|
||||
{
|
||||
var available = _CPFontDetectFontAvailable(_CPFontDetectAllFonts[i]);
|
||||
if (available)
|
||||
_availableFonts.push(_CPFontDetectAllFonts[i]);
|
||||
}
|
||||
#else
|
||||
// If there's no font detection, just assume all fonts are available.
|
||||
_availableFonts = _CPFontDetectAllFonts;
|
||||
#endif
|
||||
}
|
||||
return _availableFonts;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the available fonts matching the provided name.
|
||||
@param aFontName the name of the font
|
||||
*/
|
||||
- (CPArray)fontWithNameIsAvailable:(CPString)aFontName
|
||||
{
|
||||
return _CPFontDetectFontAvailable(aFontName);
|
||||
}
|
||||
|
||||
- (void)setSelectedFont:(CPFont)aFont isMultiple:(BOOL)aFlag
|
||||
{
|
||||
_selectedFont = aFont;
|
||||
_isMultiple = aFlag;
|
||||
|
||||
// TODO Notify CPFontPanel when it exists.
|
||||
}
|
||||
|
||||
- (CPFont)selectedFont
|
||||
{
|
||||
return _selectedFont;
|
||||
}
|
||||
|
||||
- (BOOL)isMultiple
|
||||
{
|
||||
return _isMultiple;
|
||||
}
|
||||
|
||||
- (int)weightOfFont:(CPFont)aFont
|
||||
{
|
||||
// TODO Weight 5 is a normal of book weight and 9 and above is bold, but it would be nice to be more
|
||||
// precise than that.
|
||||
return [aFont isBold] ? 9 : 5;
|
||||
}
|
||||
|
||||
- (CPFontTraitMask)traitsOfFont:(CPFont)aFont
|
||||
{
|
||||
return ([aFont isBold] ? CPBoldFontMask : 0) | ([aFont isItalic] ? CPItalicFontMask : 0);
|
||||
}
|
||||
|
||||
- (CPFont)convertFont:(CPFont)aFont
|
||||
{
|
||||
if (!_activeChange)
|
||||
return aFont;
|
||||
|
||||
var addTraits = [_activeChange valueForKey:@"addTraits"];
|
||||
|
||||
if (addTraits)
|
||||
aFont = [self convertFont:aFont toHaveTrait:addTraits];
|
||||
|
||||
return aFont;
|
||||
}
|
||||
|
||||
- (CPFont)convertFont:(CPFont)aFont toHaveTrait:(CPFontTraitMask)addTraits
|
||||
{
|
||||
if (!aFont)
|
||||
return nil;
|
||||
|
||||
var shouldBeBold = ([aFont isBold] || (addTraits & CPBoldFontMask)) && !(addTraits & CPUnboldFontMask),
|
||||
shouldBeItalic = ([aFont isItalic] || (addTraits & CPItalicFontMask)) && !(addTraits & CPUnitalicFontMask),
|
||||
shouldBeSize = [aFont size];
|
||||
|
||||
// XXX On the current platform there will always be a bold/italic version of each font, but still leave
|
||||
// || aFont in here for future platforms.
|
||||
aFont = [CPFont _fontWithName:[aFont familyName] size:shouldBeSize bold:shouldBeBold italic:shouldBeItalic] || aFont;
|
||||
|
||||
return aFont;
|
||||
}
|
||||
|
||||
- (CPFont)convertFont:(CPFont)aFont toFace:(CPString)aTypeface
|
||||
{
|
||||
if (!aFont)
|
||||
return nil;
|
||||
|
||||
var shouldBeBold = [aFont isBold],
|
||||
shouldBeItalic = [aFont isItalic],
|
||||
shouldBeSize = [aFont size];
|
||||
|
||||
aFont = [CPFont _fontWithName:aTypeface size:shouldBeSize bold:shouldBeBold italic:shouldBeItalic] || aFont;
|
||||
|
||||
return aFont;
|
||||
}
|
||||
|
||||
- (@action)addFontTrait:(id)sender
|
||||
{
|
||||
_activeChange = [CPDictionary dictionaryWithObject:[sender tag] forKey:@"addTraits"];
|
||||
|
||||
[self sendAction];
|
||||
}
|
||||
|
||||
- (BOOL)sendAction
|
||||
{
|
||||
return [CPApp sendAction:_action to:_target from:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPFontDetectSpan,
|
||||
_CPFontDetectReferenceFonts,
|
||||
_CPFontDetectAllFonts = [
|
||||
/* "04b_21", "A Charming Font", "Abadi MT Condensed", "Abadi MT Condensed Extra Bold", "Abadi MT Condensed Light", "Academy Engraved LET", "Agency FB", "Alba", "Alba Matter", "Alba Super", "Algerian",*/
|
||||
"American Typewriter",
|
||||
/* "Andale Mono", "Andale Mono IPA", "Andy", */
|
||||
"Apple Chancery", "Arial", "Arial Black", "Arial Narrow", "Arial Rounded MT Bold", "Arial Unicode MS",
|
||||
/* "Avant Garde", "Avantgarde", "Baby Kruffy", "Base 02", "Baskerville", "Baskerville Old Face", "Bauhaus 93", "Beesknees ITC", "Bell MT", "Berlin Sans FB", "Berlin Sans FB Demi", "Bernard MT Condensed", "Bickley Script",*/
|
||||
"Big Caslon", "Bitstream Vera Sans", "Bitstream Vera Sans Mono", "Bitstream Vera Serif",
|
||||
/* "Blackadder ITC", "Blackletter686 BT", "Bodoni MT", "Bodoni MT Black", "Bodoni MT Condensed", "Bodoni MT Poster Compressed", "Book Antiqua", "Bookman", "Bookman Old Style", "Bradley Hand ITC", "Braggadocio", "Britannic Bold", "Broadway", "Broadway BT",*/
|
||||
"Brush Script MT",
|
||||
/* "BudHand", "CAMPBELL", "Calibri", "Californian FB", "Calisto MT", "Calligraph421 BT",*/
|
||||
"Cambria",
|
||||
/* "Candara", "Capitals",*/
|
||||
"Caslon", "Castellar", "Cataneo BT", "Centaur", "Century Gothic", "Century Schoolbook", "Century Schoolbook L",
|
||||
/* "Champignon", "Charcoal", "Charter", "Charter BT", "Chicago", "Chick", "Chiller", "ClearlyU", "Colonna MT",*/
|
||||
"Comic Sans", "Comic Sans MS", "Consolas", "Constantia", "Cooper Black", "Copperplate", "Copperplate Gothic Bold", "Copperplate Gothic Light", "Corbel", "Courier", "Courier New",
|
||||
/* "Croobie", "Curlz MT", "Desdemona", "Didot", "DomBold BT", "Edwardian Script ITC", "Engravers MT", "Eras Bold ITC", "Eras Demi ITC", "Eras Light ITC", "Eras Medium ITC", "Eurostile", "FIRSTHOME", "Fat", "Felix Titling", "Fine Hand", "Fixed", "Footlight MT Light", "Forte", "Franklin Gothic Book", "Franklin Gothic Demi", "Franklin Gothic Demi Cond", "Franklin Gothic Heavy", "Franklin Gothic Medium", "Franklin Gothic Medium Cond", "Freestyle Script", "French Script MT", "Freshbot", "Frosty",*/
|
||||
"Futura",
|
||||
/* "GENUINE", "Gadget", "Garamond",*/
|
||||
"Geneva", "Georgia", "Georgia Ref", "Geeza Pro", "Gigi", "Gill Sans", "Gill Sans MT", "Gill Sans MT Condensed", "Gill Sans MT Ext Condensed Bold", "Gill Sans Ultra Bold", "Gill Sans Ultra Bold Condensed",
|
||||
/* "GlooGun", "Gloucester MT Extra Condensed", "Goudy Old Style", "Goudy Stout", "Haettenschweiler", "Harlow Solid Italic", "Harrington",*/
|
||||
"Helvetica", "Helvetica Narrow", "Helvetica Neue", "Herculanum", "High Tower Text", "Highlight LET", "Hoefler Text", "Impact", "Imprint MT Shadow",
|
||||
/* "Informal Roman", "Jenkins v2.0", "John Handy LET", "Jokerman", "Jokerman LET", "Jokewood", "Juice ITC", "Kabel Ult BT", "Kartika", "Kino MT", "Kristen ITC", "Kunstler Script", "La Bamba LET", */
|
||||
"Lucida", "Lucida Bright", "Lucida Calligraphy", "Lucida Console", "Lucida Fax", "Lucida Grande", "Lucida Handwriting", "Lucida Sans", "Lucida Sans Typewriter", "Lucida Sans Unicode",
|
||||
/* "Luxi Mono", "Luxi Sans", "Luxi Serif", "MARKETPRO", "MS Reference Sans Serif", "MS Reference Serif", "Magneto", "Maiandra GD", */
|
||||
"Marker Felt",
|
||||
/* "Matisse ITC", "Matura MT Script Capitals", "Mead Bold", "Mekanik LET", "Mercurius Script MT Bold", */
|
||||
"Microsoft Sans Serif", "Milano LET", "Minion Web", "MisterEarl BT", "Mistral", "Monaco", "Monotype Corsiva", "Monotype.com", "New Century Schoolbook", "New York", "News Gothic MT",
|
||||
/* "Niagara Engraved", "Niagara Solid", "Nimbus Mono L", "Nimbus Roman No9 L", "OCR A Extended", "OCRB", "Odessa LET", "Old English Text MT", "OldDreadfulNo7 BT", "One Stroke Script LET", "Onyx", "Optima", "Orange LET", "Palace Script MT", "Palatino", "Palatino Linotype", */
|
||||
"Papyrus",
|
||||
/* "ParkAvenue BT", "Pepita MT", "Perpetua", "Perpetua Titling MT", "Placard Condensed", "Playbill", "Poornut", "Pristina", "Pump Demi Bold LET", "Pussycat", "Quixley LET", "Rage Italic", "Rage Italic LET", "Ravie", "Rockwell", "Rockwell Condensed", "Rockwell Extra Bold", "Ruach LET", "Runic MT Condensed", "Sand", "Script MT Bold", "Scruff LET", "Segoe UI", "Showcard Gothic", "Skia", "Smudger LET", "Snap ITC", "Square721 BT", "Staccato222 BT", "Stencil", "Sylfaen", */
|
||||
"Tahoma", "Techno", "Tempus Sans ITC", "Terminal", "Textile", "Times", "Times New Roman", "Tiranti Solid LET", "Trebuchet MS",
|
||||
/* "Tw Cen MT", "Tw Cen MT Condensed", "Tw Cen MT Condensed Extra Bold", "URW Antiqua T", "URW Bookman L", "URW Chancery L", "URW Gothic L", "URW Palladio L", "Univers", "University Roman LET", "Utopia", */
|
||||
"Verdana", "Verdana Ref", /* "Victorian LET", "Viner Hand ITC", "Vivaldi", "Vladimir Script", "Vrinda", "Weltron Urban", "Westwood LET", "Wide Latin", "Zapf Chancery", */
|
||||
"Zapfino"];
|
||||
|
||||
// Compare against the reference fonts. Return true if it produces a different size than at least one of them.
|
||||
var _CPFontDetectFontAvailable = function(font)
|
||||
{
|
||||
for (var i = 0; i < _CPFontDetectReferenceFonts.length; i++)
|
||||
if (_CPFontDetectCompareFonts(_CPFontDetectReferenceFonts[i], font))
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
var _CPFontDetectCache = {};
|
||||
|
||||
// Compares two given fonts. Returns true if they produce different sizes (i.e. fontA didn't fallback to fontB)
|
||||
var _CPFontDetectCompareFonts = function(fontA, fontB)
|
||||
{
|
||||
var a;
|
||||
if (_CPFontDetectCache[fontA])
|
||||
a = _CPFontDetectCache[fontA];
|
||||
|
||||
else
|
||||
{
|
||||
_CPFontDetectSpan.style.fontFamily = '"' + fontA + '"';
|
||||
_CPFontDetectCache[fontA] = a = { w: _CPFontDetectSpan.offsetWidth, h: _CPFontDetectSpan.offsetHeight };
|
||||
}
|
||||
|
||||
_CPFontDetectSpan.style.fontFamily = '"' + fontB + '", "' + fontA + '"';
|
||||
var bWidth = _CPFontDetectSpan.offsetWidth,
|
||||
bHeight = _CPFontDetectSpan.offsetHeight;
|
||||
|
||||
return (a.w != bWidth || a.h != bHeight);
|
||||
};
|
||||
|
||||
// Test the candidate fonts pairwise until we find two that are different. Otherwise return the first.
|
||||
var _CPFontDetectPickTwoDifferentFonts = function(candidates)
|
||||
{
|
||||
for (var i = 0; i < candidates.length; i++)
|
||||
for (var j = 0; j < i; j++)
|
||||
if (_CPFontDetectCompareFonts(candidates[i], candidates[j]))
|
||||
return [candidates[i], candidates[j]];
|
||||
return [candidates[0]];
|
||||
};
|
||||
|
||||
[CPFontManager setFontManagerFactory:[CPFontManager class]];
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* CPGradient.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexander Ljungberg.
|
||||
* Copyright 2012, SlevenBits Ltd.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
CPGradientDrawsBeforeStartingLocation = kCGGradientDrawsBeforeStartLocation;
|
||||
CPGradientDrawsAfterEndingLocation = kCGGradientDrawsAfterEndLocation;
|
||||
|
||||
/*!
|
||||
A class for drawing linear and radial gradients with a convenient API.
|
||||
*/
|
||||
@implementation CPGradient : CPObject
|
||||
{
|
||||
CGGradient _gradient;
|
||||
}
|
||||
|
||||
- (id)initWithColors:(CPArray)someColors
|
||||
{
|
||||
var count = [someColors count];
|
||||
|
||||
if (count < 2)
|
||||
[CPException raise:CPInvalidArgumentException reason:@"at least 2 colors required"];
|
||||
|
||||
var distance = 1.0 / (count - 1),
|
||||
locations = [CPMutableArray array],
|
||||
location = 0.0;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
[locations addObject:location];
|
||||
location += distance;
|
||||
}
|
||||
|
||||
return [self initWithColors:someColors atLocations:locations colorSpace:nil];
|
||||
}
|
||||
|
||||
- (id)initWithColors:(CPArray)someColors atLocations:(CPArray)someLocations colorSpace:(CPColorSpace)aColorSpace
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
var cgColors = [],
|
||||
count = [someColors count],
|
||||
colorSpace = [aColorSpace CGColorSpace] || CGColorSpaceCreateDeviceRGB;
|
||||
for (var i = 0; i < count; i++)
|
||||
cgColors.push(CGColorCreate(colorSpace, [someColors[i] components]));
|
||||
_gradient = CGGradientCreateWithColors(colorSpace, cgColors, someLocations);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawInRect:(CGRect)rect angle:(float)angle
|
||||
{
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextSaveGState(ctx);
|
||||
CGContextClipToRect(ctx, rect);
|
||||
CGContextAddRect(ctx, rect);
|
||||
|
||||
var startPoint,
|
||||
endPoint;
|
||||
|
||||
// Modulo of negative values doesn't work as expected in JS.
|
||||
angle = ((angle % 360.0) + 360.0) % 360.0;
|
||||
|
||||
if (angle < 90.0)
|
||||
startPoint = _CGPointMake(_CGRectGetMinX(rect), _CGRectGetMinY(rect));
|
||||
else if (angle < 180.0)
|
||||
startPoint = _CGPointMake(_CGRectGetMaxX(rect), _CGRectGetMinY(rect));
|
||||
else if (angle < 270.0)
|
||||
startPoint = _CGPointMake(_CGRectGetMaxX(rect), _CGRectGetMaxY(rect));
|
||||
else
|
||||
startPoint = _CGPointMake(_CGRectGetMinX(rect), _CGRectGetMaxY(rect));
|
||||
|
||||
// A line segment comes out of the starting point at the given angle, with the first colour
|
||||
// at the starting point and the last at the end. To do what drawInRect: is supposed to do
|
||||
// we want the opposite corner of the starting corner to just reach the final colour stop.
|
||||
// So when the angle isn't a right angle, the segment has to extend beyond the edge of the
|
||||
// rectangle just far enough. This is hard to describe without a picture but if we place
|
||||
// another line through the opposite corner at -90 degrees, it'll help form some triangles
|
||||
// from which we can derive this formula:
|
||||
//
|
||||
// length = cos(angle) * (rectWidth + rectHeight * tan(angle))
|
||||
//
|
||||
// This simplifies down to (in the first quadrant) rectWidth * cos(a) + rectHeight * sin(a).
|
||||
|
||||
var radians = PI * angle / 180.0,
|
||||
length = ABS(_CGRectGetWidth(rect) * COS(radians)) + ABS(_CGRectGetHeight(rect) * SIN(radians));
|
||||
|
||||
endPoint = _CGPointMake(startPoint.x + length * COS(radians),
|
||||
startPoint.y + length * SIN(radians));
|
||||
|
||||
[self drawFromPoint:startPoint toPoint:endPoint options:CPGradientDrawsBeforeStartingLocation | CPGradientDrawsAfterEndingLocation];
|
||||
CGContextRestoreGState(ctx);
|
||||
}
|
||||
|
||||
- (void)drawFromPoint:(NSPoint)startingPoint toPoint:(NSPoint)endingPoint options:(NSGradientDrawingOptions)options
|
||||
{
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
// TODO kCGGradientDrawsBeforeStartLocation and kCGGradientDrawsAfterEndLocation are not actually supported
|
||||
// by CGContextDrawLinearGradient yet.
|
||||
CGContextDrawLinearGradient(ctx, _gradient, startingPoint, endingPoint, options);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* CPGraphics.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2010, 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 "CPColor.j"
|
||||
@import "CPGraphicsContext.j"
|
||||
|
||||
CPCalibratedWhiteColorSpace = @"CalibratedWhiteColorSpace";
|
||||
CPCalibratedBlackColorSpace = @"CalibratedBlackColorSpace";
|
||||
CPCalibratedRGBColorSpace = @"CalibratedRGBColorSpace";
|
||||
CPDeviceWhiteColorSpace = @"DeviceWhiteColorSpace";
|
||||
CPDeviceBlackColorSpace = @"DeviceBlackColorSpace";
|
||||
CPDeviceRGBColorSpace = @"DeviceRGBColorSpace";
|
||||
CPDeviceCMYKColorSpace = @"DeviceCMYKColorSpace";
|
||||
CPNamedColorSpace = @"NamedColorSpace";
|
||||
CPPatternColorSpace = @"PatternColorSpace";
|
||||
CPCustomColorSpace = @"CustomColorSpace";
|
||||
|
||||
function CPDrawTiledRects(
|
||||
/* CGRect */ boundsRect,
|
||||
/* CGRect */ clipRect,
|
||||
/* CPRectEdge[] */ sides,
|
||||
/* float[] */ grays)
|
||||
{
|
||||
if (sides.length != grays.length)
|
||||
[CPException raise:CPInvalidArgumentException reason:@"sides (length: " + sides.length + ") and grays (length: " + grays.length + ") must have the same length."];
|
||||
|
||||
var colors = [];
|
||||
|
||||
for (var i = 0; i < grays.length; ++i)
|
||||
colors.push([CPColor colorWithCalibratedWhite:grays[i] alpha:1.0]);
|
||||
|
||||
return CPDrawColorTiledRects(boundsRect, clipRect, sides, colors);
|
||||
}
|
||||
|
||||
function CPDrawColorTiledRects(
|
||||
/* CGRect */ boundsRect,
|
||||
/* CGRect */ clipRect,
|
||||
/* CPRectEdge[] */ sides,
|
||||
/* CPColor[] */ colors)
|
||||
{
|
||||
if (sides.length != colors.length)
|
||||
[CPException raise:CPInvalidArgumentException reason:@"sides (length: " + sides.length + ") and colors (length: " + colors.length + ") must have the same length."];
|
||||
|
||||
var resultRect = _CGRectMakeCopy(boundsRect),
|
||||
slice = _CGRectMakeZero(),
|
||||
remainder = _CGRectMakeZero(),
|
||||
context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextSaveGState(context);
|
||||
CGContextSetLineWidth(context, 1.0);
|
||||
|
||||
for (var sideIndex = 0; sideIndex < sides.length; ++sideIndex)
|
||||
{
|
||||
var side = sides[sideIndex];
|
||||
|
||||
CGRectDivide(resultRect, slice, remainder, 1.0, side);
|
||||
resultRect = remainder;
|
||||
slice = CGRectIntersection(slice, clipRect);
|
||||
|
||||
// Cocoa docs say that only slices that are within the clipRect are actually drawn
|
||||
if (_CGRectIsEmpty(slice))
|
||||
continue;
|
||||
|
||||
var minX,
|
||||
maxX,
|
||||
minY,
|
||||
maxY;
|
||||
|
||||
if (side == CPMinXEdge || side == CPMaxXEdge)
|
||||
{
|
||||
// Make sure we have at least 1 pixel to draw a line
|
||||
if (_CGRectGetWidth(slice) < 1.0)
|
||||
continue;
|
||||
|
||||
minX = _CGRectGetMinX(slice) + 0.5;
|
||||
maxX = minX;
|
||||
minY = _CGRectGetMinY(slice);
|
||||
maxY = _CGRectGetMaxY(slice);
|
||||
}
|
||||
else // CPMinYEdge || CPMaxYEdge
|
||||
{
|
||||
// Make sure we have at least 1 pixel to draw a line
|
||||
if (_CGRectGetHeight(slice) < 1.0)
|
||||
continue;
|
||||
|
||||
minX = _CGRectGetMinX(slice);
|
||||
maxX = _CGRectGetMaxX(slice);
|
||||
minY = _CGRectGetMinY(slice) + 0.5;
|
||||
maxY = minY;
|
||||
}
|
||||
|
||||
CGContextBeginPath(context);
|
||||
CGContextMoveToPoint(context, minX, minY);
|
||||
CGContextAddLineToPoint(context, maxX, maxY);
|
||||
CGContextSetStrokeColor(context, colors[sideIndex]);
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
return resultRect;
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* CPGraphicsContext.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CGContext.j"
|
||||
|
||||
|
||||
var CPGraphicsContextCurrent = nil,
|
||||
CPGraphicsContextThreadStack = nil;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
Provides an interface for drawing to the screen.
|
||||
*/
|
||||
@implementation CPGraphicsContext : CPObject
|
||||
{
|
||||
CGContext _graphicsPort;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current graphics context.
|
||||
*/
|
||||
+ (CPGraphicsContext)currentContext
|
||||
{
|
||||
return CPGraphicsContextCurrent;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current graphics context
|
||||
*/
|
||||
+ (void)setCurrentContext:(CPGraphicsContext)aGraphicsContext
|
||||
{
|
||||
CPGraphicsContextCurrent = aGraphicsContext;
|
||||
}
|
||||
|
||||
+ (void)saveGraphicsState
|
||||
{
|
||||
if (!CPGraphicsContextCurrent)
|
||||
return;
|
||||
|
||||
if (!CPGraphicsContextThreadStack)
|
||||
CPGraphicsContextThreadStack = [CPMutableArray array];
|
||||
|
||||
[CPGraphicsContextThreadStack addObject:CPGraphicsContextCurrent];
|
||||
[CPGraphicsContextCurrent saveGraphicsState];
|
||||
}
|
||||
|
||||
+ (void)restoreGraphicsState
|
||||
{
|
||||
var lastContext = [CPGraphicsContextThreadStack lastObject];
|
||||
if (lastContext)
|
||||
{
|
||||
[lastContext restoreGraphicsState];
|
||||
[CPGraphicsContextThreadStack removeLastObject];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a graphics context with a provided port.
|
||||
@param aContext the context to initialize with
|
||||
@param aFlag whether the context should be flipped
|
||||
@return the initialized graphics context
|
||||
*/
|
||||
+ (CPGraphicsContext)graphicsContextWithGraphicsPort:(CGContext)aContext flipped:(BOOL)aFlag
|
||||
{
|
||||
return [[self alloc] initWithGraphicsPort:aContext];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the context with a graphics port.
|
||||
@param aGraphicsPort the graphics port to initialize with
|
||||
@return the initialized context
|
||||
*/
|
||||
- (id)initWithGraphicsPort:(CPContext)aGraphicsPort
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_graphicsPort = aGraphicsPort;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the graphics context's port.
|
||||
*/
|
||||
- (CGContext)graphicsPort
|
||||
{
|
||||
return _graphicsPort;
|
||||
}
|
||||
|
||||
/*!
|
||||
(Not implemented. Always returns Cappuccino's default, `YES`.)
|
||||
|
||||
Return `isFlipped` of the receiver's focused view, unless no view is focused in which case the receiver's
|
||||
`flipped` flag, as set by `graphicsContextWithGraphicsPort:flipped:`, is returned.
|
||||
|
||||
Note: Cappuccino is flipped by default.
|
||||
*/
|
||||
- (BOOL)isFlipped
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)saveGraphicsState
|
||||
{
|
||||
CGContextSaveGState(_graphicsPort);
|
||||
}
|
||||
|
||||
- (void)restoreGraphicsState
|
||||
{
|
||||
CGContextRestoreGState(_graphicsPort);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,588 +0,0 @@
|
||||
/*
|
||||
* CPImage.j
|
||||
* AppKit
|
||||
*
|
||||
* 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/CPBundle.j>
|
||||
@import <Foundation/CPGeometry.j>
|
||||
@import <Foundation/CPNotificationCenter.j>
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPRunLoop.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
|
||||
CPImageLoadStatusInitialized = 0;
|
||||
CPImageLoadStatusLoading = 1;
|
||||
CPImageLoadStatusCompleted = 2;
|
||||
CPImageLoadStatusCancelled = 3;
|
||||
CPImageLoadStatusInvalidData = 4;
|
||||
CPImageLoadStatusUnexpectedEOF = 5;
|
||||
CPImageLoadStatusReadError = 6;
|
||||
|
||||
CPImageDidLoadNotification = @"CPImageDidLoadNotification";
|
||||
|
||||
// Image Names
|
||||
CPImageNameColorPanel = @"CPImageNameColorPanel";
|
||||
CPImageNameColorPanelHighlighted = @"CPImageNameColorPanelHighlighted";
|
||||
|
||||
var imagesForNames = { },
|
||||
AppKitImageForNames = { },
|
||||
ImageDescriptionFormat = "%s {\n filename: \"%s\",\n size: { width:%f, height:%f }\n}";
|
||||
|
||||
AppKitImageForNames[CPImageNameColorPanel] = CGSizeMake(26.0, 29.0);
|
||||
AppKitImageForNames[CPImageNameColorPanelHighlighted] = CGSizeMake(26.0, 29.0);
|
||||
|
||||
function CPImageInBundle(aFilename, aSize, aBundle)
|
||||
{
|
||||
if (!aBundle)
|
||||
aBundle = [CPBundle mainBundle];
|
||||
|
||||
if (aSize)
|
||||
return [[CPImage alloc] initWithContentsOfFile:[aBundle pathForResource:aFilename] size:aSize];
|
||||
|
||||
return [[CPImage alloc] initWithContentsOfFile:[aBundle pathForResource:aFilename]];
|
||||
}
|
||||
|
||||
function CPAppKitImage(aFilename, aSize)
|
||||
{
|
||||
return CPImageInBundle(aFilename, aSize, [CPBundle bundleForClass:[CPView class]]);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPImage
|
||||
|
||||
CPImage is used to represent images in the Cappuccino framework. It supports loading
|
||||
all image types supported by the browser.
|
||||
|
||||
@par Delegate Methods
|
||||
|
||||
@delegate -(void)imageDidLoad:(CPImage)image;
|
||||
Called when the specified image has finished loading.
|
||||
@param image the image that loaded
|
||||
|
||||
@delegate -(void)imageDidError:(CPImage)image;
|
||||
Called when the specified image had an error loading.
|
||||
@param image the image with the loading error
|
||||
|
||||
@delegate -(void)imageDidAbort:(CPImage)image;
|
||||
Called when the image loading was aborted.
|
||||
@param image the image that was aborted
|
||||
*/
|
||||
@implementation CPImage : CPObject
|
||||
{
|
||||
CGSize _size;
|
||||
CPString _filename;
|
||||
CPString _name;
|
||||
|
||||
id _delegate;
|
||||
unsigned _loadStatus;
|
||||
|
||||
Image _image;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initByReferencingFile:@"" size:CGSizeMake(-1, -1)];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the image, by associating it with a filename. The image
|
||||
denoted in \c aFilename is not actually loaded. It will
|
||||
be loaded once needed.
|
||||
@param aFilename the file containing the image
|
||||
@param aSize the image's size
|
||||
@return the initialized image
|
||||
*/
|
||||
- (id)initByReferencingFile:(CPString)aFilename size:(CGSize)aSize
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_size = CPSizeCreateCopy(aSize);
|
||||
_filename = aFilename;
|
||||
_loadStatus = CPImageLoadStatusInitialized;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the image. Loads the specified image into memory.
|
||||
@param aFilename the image to load
|
||||
@param aSize the size of the image
|
||||
@return the initialized image.
|
||||
*/
|
||||
- (id)initWithContentsOfFile:(CPString)aFilename size:(CGSize)aSize
|
||||
{
|
||||
self = [self initByReferencingFile:aFilename size:aSize];
|
||||
|
||||
if (self)
|
||||
[self load];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the receiver with the contents of the specified
|
||||
image file. The method loads the data into memory.
|
||||
@param aFilename the file name of the image
|
||||
@return the initialized image
|
||||
*/
|
||||
- (id)initWithContentsOfFile:(CPString)aFilename
|
||||
{
|
||||
self = [self initByReferencingFile:aFilename size:CGSizeMake(-1, -1)];
|
||||
|
||||
if (self)
|
||||
[self load];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the receiver with the specified data. The method loads the data into memory.
|
||||
@param someData the CPData object representing the image
|
||||
@return the initialized image
|
||||
*/
|
||||
- (id)initWithData:(CPData)someData
|
||||
{
|
||||
var base64 = [someData base64],
|
||||
type = [base64 hasPrefix:@"/9j/4AAQSkZJRgABAQEASABIAAD/"] ? @"jpg" : @"png",
|
||||
dataURL = "data:image/" + type + ";base64," + base64;
|
||||
|
||||
return [self initWithContentsOfFile:dataURL];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the path of the file associated with this image.
|
||||
*/
|
||||
- (CPString)filename
|
||||
{
|
||||
return _filename;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the data associated with this image.
|
||||
@discussion Returns nil if the reciever was not initialized with -initWithData: and the browser does not support the canvas feature;
|
||||
*/
|
||||
- (CPData)data
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var dataURL;
|
||||
|
||||
if ([_filename hasPrefix:@"data:image"])
|
||||
dataURL = _filename;
|
||||
else if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
|
||||
{
|
||||
var canvas = document.createElement("canvas"),
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
canvas.width = _image.width,
|
||||
canvas.height = _image.height;
|
||||
|
||||
ctx.drawImage(_image, 0, 0);
|
||||
|
||||
dataURL = canvas.toDataURL("image/png");
|
||||
}
|
||||
else
|
||||
return nil;
|
||||
|
||||
var base64 = dataURL.replace(/^data:image\/png;base64,/, "");
|
||||
return [CPData dataWithBase64:base64];
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the size of the image.
|
||||
@param the size of the image
|
||||
*/
|
||||
- (void)setSize:(CGSize)aSize
|
||||
{
|
||||
_size = CGSizeMakeCopy(aSize);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the size of the image
|
||||
*/
|
||||
- (CGSize)size
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
+ (id)imageNamed:(CPString)aName
|
||||
{
|
||||
var image = imagesForNames[aName];
|
||||
|
||||
if (image)
|
||||
return image;
|
||||
|
||||
var imageOrSize = AppKitImageForNames[aName];
|
||||
|
||||
if (!imageOrSize)
|
||||
return nil;
|
||||
|
||||
if (!imageOrSize.isa)
|
||||
{
|
||||
imageOrSize = CPAppKitImage("CPImage/" + aName + ".png", imageOrSize);
|
||||
|
||||
[imageOrSize setName:aName];
|
||||
|
||||
AppKitImageForNames[aName] = imageOrSize;
|
||||
}
|
||||
|
||||
return imageOrSize;
|
||||
}
|
||||
|
||||
- (BOOL)setName:(CPString)aName
|
||||
{
|
||||
if (_name === aName)
|
||||
return YES;
|
||||
|
||||
if (imagesForNames[aName])
|
||||
return NO;
|
||||
|
||||
_name = aName;
|
||||
|
||||
imagesForNames[aName] = self;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CPString)name
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's delegate.
|
||||
@param the delegate
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's delegate
|
||||
*/
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the load status, which will be CPImageLoadStatusCompleted if the image data has already been loaded.
|
||||
*/
|
||||
- (unsigned)loadStatus
|
||||
{
|
||||
return _loadStatus;
|
||||
}
|
||||
|
||||
/*!
|
||||
Loads the image data from the file into memory. You
|
||||
should not call this method directly. Instead use
|
||||
one of the initializers.
|
||||
*/
|
||||
|
||||
- (void)load
|
||||
{
|
||||
if (_loadStatus == CPImageLoadStatusLoading || _loadStatus == CPImageLoadStatusCompleted)
|
||||
return;
|
||||
|
||||
_loadStatus = CPImageLoadStatusLoading;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_image = new Image();
|
||||
|
||||
var isSynchronous = YES;
|
||||
|
||||
// FIXME: We need a better/performance way of doing this.
|
||||
_image.onload = function ()
|
||||
{
|
||||
if (isSynchronous)
|
||||
window.setTimeout(function() { [self _imageDidLoad]; }, 0);
|
||||
else
|
||||
{
|
||||
[self _imageDidLoad];
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
[self _derefFromImage];
|
||||
};
|
||||
|
||||
_image.onerror = function ()
|
||||
{
|
||||
if (isSynchronous)
|
||||
window.setTimeout(function() { [self _imageDidError]; }, 0);
|
||||
else
|
||||
{
|
||||
[self _imageDidError];
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
[self _derefFromImage];
|
||||
};
|
||||
|
||||
_image.onabort = function ()
|
||||
{
|
||||
if (isSynchronous)
|
||||
window.setTimeout(function() { [self _imageDidAbort]; }, 0);
|
||||
else
|
||||
{
|
||||
[self _imageDidAbort];
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
[self _derefFromImage];
|
||||
};
|
||||
|
||||
_image.src = _filename;
|
||||
|
||||
// onload and friends may fire after this point but BEFORE the end of the run loop,
|
||||
// crazy, I know. So don't set isSynchronous here, rather wait a bit longer.
|
||||
window.setTimeout(function() { isSynchronous = NO; }, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)isThreePartImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isNinePartImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
var filename = [self filename],
|
||||
size = [self size];
|
||||
|
||||
if (filename.indexOf("data:") === 0)
|
||||
{
|
||||
var index = filename.indexOf(",");
|
||||
|
||||
if (index > 0)
|
||||
filename = [CPString stringWithFormat:@"%s,%s...%s", filename.substr(0, index), filename.substr(index + 1, 10), filename.substr(filename.length - 10)];
|
||||
else
|
||||
filename = "data:<unknown type>";
|
||||
}
|
||||
|
||||
return [CPString stringWithFormat:ImageDescriptionFormat, [super description], filename, size.width, size.height];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_derefFromImage
|
||||
{
|
||||
_image.onload = null;
|
||||
_image.onerror = null;
|
||||
_image.onabort = null;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_imageDidLoad
|
||||
{
|
||||
_loadStatus = CPImageLoadStatusCompleted;
|
||||
|
||||
// FIXME: IE is wrong on image sizes????
|
||||
if (!_size || (_size.width == -1 && _size.height == -1))
|
||||
_size = CGSizeMake(_image.width, _image.height);
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPImageDidLoadNotification
|
||||
object:self];
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(imageDidLoad:)])
|
||||
[_delegate imageDidLoad:self];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_imageDidError
|
||||
{
|
||||
_loadStatus = CPImageLoadStatusReadError;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(imageDidError:)])
|
||||
[_delegate imageDidError:self];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_imageDidAbort
|
||||
{
|
||||
_loadStatus = CPImageLoadStatusCancelled;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(imageDidAbort:)])
|
||||
[_delegate imageDidAbort:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPImage (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the image with data from a coder.
|
||||
@param aCoder the coder from which to read the image data
|
||||
@return the initialized image
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
return [self initWithContentsOfFile:[aCoder decodeObjectForKey:@"CPFilename"] size:[aCoder decodeSizeForKey:@"CPSize"]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes the image data from memory into the coder.
|
||||
@param aCoder the coder to which the data will be written
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_filename forKey:@"CPFilename"];
|
||||
[aCoder encodeSize:_size forKey:@"CPSize"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPThreePartImage : CPObject
|
||||
{
|
||||
CPArray _imageSlices;
|
||||
BOOL _isVertical;
|
||||
}
|
||||
|
||||
- (id)initWithImageSlices:(CPArray)imageSlices isVertical:(BOOL)isVertical
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_imageSlices = imageSlices;
|
||||
_isVertical = isVertical;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)filename
|
||||
{
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (CPArray)imageSlices
|
||||
{
|
||||
return _imageSlices;
|
||||
}
|
||||
|
||||
- (BOOL)isVertical
|
||||
{
|
||||
return _isVertical;
|
||||
}
|
||||
|
||||
- (BOOL)isThreePartImage
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isNinePartImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPThreePartImageImageSlicesKey = @"CPThreePartImageImageSlicesKey",
|
||||
CPThreePartImageIsVerticalKey = @"CPThreePartImageIsVerticalKey";
|
||||
|
||||
@implementation CPThreePartImage (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_imageSlices = [aCoder decodeObjectForKey:CPThreePartImageImageSlicesKey];
|
||||
_isVertical = [aCoder decodeBoolForKey:CPThreePartImageIsVerticalKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_imageSlices forKey:CPThreePartImageImageSlicesKey];
|
||||
[aCoder encodeBool:_isVertical forKey:CPThreePartImageIsVerticalKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPNinePartImage : CPObject
|
||||
{
|
||||
CPArray _imageSlices;
|
||||
}
|
||||
|
||||
- (id)initWithImageSlices:(CPArray)imageSlices
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_imageSlices = imageSlices;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)filename
|
||||
{
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (CPArray)imageSlices
|
||||
{
|
||||
return _imageSlices;
|
||||
}
|
||||
|
||||
- (BOOL)isThreePartImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isNinePartImage
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
|
||||
|
||||
@implementation CPNinePartImage (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_imageSlices = [aCoder decodeObjectForKey:CPNinePartImageImageSlicesKey];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_imageSlices forKey:CPNinePartImageImageSlicesKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,567 +0,0 @@
|
||||
/*
|
||||
* CPImageView.j
|
||||
* AppKit
|
||||
*
|
||||
* 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/CPNotificationCenter.j>
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPShadowView.j"
|
||||
|
||||
CPImageAlignCenter = 0;
|
||||
CPImageAlignTop = 1;
|
||||
CPImageAlignTopLeft = 2;
|
||||
CPImageAlignTopRight = 3;
|
||||
CPImageAlignLeft = 4;
|
||||
CPImageAlignBottom = 5;
|
||||
CPImageAlignBottomLeft = 6;
|
||||
CPImageAlignBottomRight = 7;
|
||||
CPImageAlignRight = 8;
|
||||
|
||||
var CPImageViewEmptyPlaceholderImage = nil;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPImageView
|
||||
|
||||
This class is a control that displays an image.
|
||||
*/
|
||||
@implementation CPImageView : CPControl
|
||||
{
|
||||
DOMElement _DOMImageElement;
|
||||
|
||||
BOOL _hasShadow;
|
||||
CPView _shadowView;
|
||||
|
||||
BOOL _isEditable;
|
||||
|
||||
CGRect _imageRect;
|
||||
CPImageAlignment _imageAlignment;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPImageView class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:[CPView class]];
|
||||
|
||||
CPImageViewEmptyPlaceholderImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"empty.png"]];
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (theBinding === CPValueBinding || theBinding === CPValueURLBinding || theBinding === CPValuePathBinding || theBinding === CPDataBinding)
|
||||
return [CPImageViewValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement = document.createElement("img");
|
||||
_DOMImageElement.style.position = "absolute";
|
||||
_DOMImageElement.style.left = "0px";
|
||||
_DOMImageElement.style.top = "0px";
|
||||
|
||||
if ([CPPlatform supportsDragAndDrop])
|
||||
{
|
||||
_DOMImageElement.setAttribute("draggable", "true");
|
||||
_DOMImageElement.style["-khtml-user-drag"] = "element";
|
||||
}
|
||||
|
||||
CPDOMDisplayServerAppendChild(_DOMElement, _DOMImageElement);
|
||||
|
||||
_DOMImageElement.style.visibility = "hidden";
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the view's image.
|
||||
*/
|
||||
- (CPImage)image
|
||||
{
|
||||
return [self objectValue];
|
||||
}
|
||||
|
||||
- (void)setImage:(CPImage)anImage
|
||||
{
|
||||
[self setObjectValue:anImage];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)setObjectValue:(CPImage)anImage
|
||||
{
|
||||
var oldImage = [self objectValue];
|
||||
|
||||
if (oldImage === anImage)
|
||||
return;
|
||||
|
||||
[super setObjectValue:anImage];
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (oldImage)
|
||||
[defaultCenter removeObserver:self name:CPImageDidLoadNotification object:oldImage];
|
||||
|
||||
var newImage = [self objectValue];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement.src = newImage ? [newImage filename] : [CPImageViewEmptyPlaceholderImage filename];
|
||||
#endif
|
||||
|
||||
var size = [newImage size];
|
||||
|
||||
if (size && size.width === -1 && size.height === -1)
|
||||
{
|
||||
[defaultCenter addObserver:self selector:@selector(imageDidLoad:) name:CPImageDidLoadNotification object:newImage];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement.width = 0;
|
||||
_DOMImageElement.height = 0;
|
||||
#endif
|
||||
|
||||
[_shadowView setHidden:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self hideOrDisplayContents];
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)imageDidLoad:(CPNotification)aNotification
|
||||
{
|
||||
[self hideOrDisplayContents];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the image view draws with
|
||||
a drop shadow. The default is \c NO.
|
||||
*/
|
||||
- (BOOL)hasShadow
|
||||
{
|
||||
return _hasShadow;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the image view should draw with a drop shadow.
|
||||
@param shouldHaveShadow whether the image view should have a shadow
|
||||
*/
|
||||
- (void)setHasShadow:(BOOL)shouldHaveShadow
|
||||
{
|
||||
if (_hasShadow == shouldHaveShadow)
|
||||
return;
|
||||
|
||||
_hasShadow = shouldHaveShadow;
|
||||
|
||||
if (_hasShadow)
|
||||
{
|
||||
_shadowView = [[CPShadowView alloc] initWithFrame:[self bounds]];
|
||||
|
||||
[self addSubview:_shadowView];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_shadowView removeFromSuperview];
|
||||
|
||||
_shadowView = nil;
|
||||
}
|
||||
|
||||
[self hideOrDisplayContents];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the type of image alignment that should be used to
|
||||
render the image.
|
||||
@param anImageAlignment the type of scaling to use
|
||||
*/
|
||||
- (void)setImageAlignment:(CPImageAlignment)anImageAlignment
|
||||
{
|
||||
if (_imageAlignment == anImageAlignment)
|
||||
return;
|
||||
|
||||
_imageAlignment = anImageAlignment;
|
||||
|
||||
if (![self image])
|
||||
return;
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (unsigned)imageAlignment
|
||||
{
|
||||
return _imageAlignment;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the type of image scaling that should be used to
|
||||
render the image.
|
||||
@param anImageScaling the type of scaling to use
|
||||
*/
|
||||
- (void)setImageScaling:(CPImageScaling)anImageScaling
|
||||
{
|
||||
[super setImageScaling:anImageScaling];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if ([self currentValueForThemeAttribute:@"image-scaling"] === CPImageScaleAxesIndependently)
|
||||
{
|
||||
CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, 0.0, 0.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (unsigned)imageScaling
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image-scaling"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Toggles the display of the image view.
|
||||
*/
|
||||
- (void)hideOrDisplayContents
|
||||
{
|
||||
if (![self image])
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement.style.visibility = "hidden";
|
||||
#endif
|
||||
[_shadowView setHidden:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement.style.visibility = "visible";
|
||||
#endif
|
||||
[_shadowView setHidden:NO];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the view's image rectangle
|
||||
*/
|
||||
- (CGRect)imageRect
|
||||
{
|
||||
return _imageRect;
|
||||
}
|
||||
|
||||
/*!
|
||||
Add a description
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (![self image])
|
||||
return;
|
||||
|
||||
var bounds = [self bounds],
|
||||
image = [self image],
|
||||
imageScaling = [self currentValueForThemeAttribute:@"image-scaling"],
|
||||
x = 0.0,
|
||||
y = 0.0,
|
||||
insetWidth = (_hasShadow ? [_shadowView horizontalInset] : 0.0),
|
||||
insetHeight = (_hasShadow ? [_shadowView verticalInset] : 0.0),
|
||||
boundsWidth = _CGRectGetWidth(bounds),
|
||||
boundsHeight = _CGRectGetHeight(bounds),
|
||||
width = boundsWidth - insetWidth,
|
||||
height = boundsHeight - insetHeight;
|
||||
|
||||
if (imageScaling === CPImageScaleAxesIndependently)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement.width = ROUND(width);
|
||||
_DOMImageElement.height = ROUND(height);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
var size = [image size];
|
||||
|
||||
if (size.width == -1 && size.height == -1)
|
||||
return;
|
||||
|
||||
switch (imageScaling)
|
||||
{
|
||||
case CPImageScaleProportionallyDown:
|
||||
if (width >= size.width && height >= size.height)
|
||||
{
|
||||
width = size.width;
|
||||
height = size.height;
|
||||
break;
|
||||
}
|
||||
|
||||
// intentionally fall through to the next case
|
||||
|
||||
case CPImageScaleProportionallyUpOrDown:
|
||||
var imageRatio = size.width / size.height,
|
||||
viewRatio = width / height;
|
||||
|
||||
if (viewRatio > imageRatio)
|
||||
width = height * imageRatio;
|
||||
else
|
||||
height = width / imageRatio;
|
||||
break;
|
||||
|
||||
case CPImageScaleAxesIndependently:
|
||||
case CPImageScaleNone:
|
||||
width = size.width;
|
||||
height = size.height;
|
||||
break;
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement.width = ROUND(width);
|
||||
_DOMImageElement.height = ROUND(height);
|
||||
#endif
|
||||
|
||||
var x,
|
||||
y;
|
||||
|
||||
switch (_imageAlignment)
|
||||
{
|
||||
case CPImageAlignLeft:
|
||||
case CPImageAlignTopLeft:
|
||||
case CPImageAlignBottomLeft:
|
||||
x = 0.0;
|
||||
break;
|
||||
|
||||
case CPImageAlignRight:
|
||||
case CPImageAlignTopRight:
|
||||
case CPImageAlignBottomRight:
|
||||
x = boundsWidth - width;
|
||||
break;
|
||||
|
||||
default:
|
||||
x = (boundsWidth - width) / 2.0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (_imageAlignment)
|
||||
{
|
||||
case CPImageAlignTop:
|
||||
case CPImageAlignTopLeft:
|
||||
case CPImageAlignTopRight:
|
||||
y = 0.0;
|
||||
break;
|
||||
|
||||
case CPImageAlignBottom:
|
||||
case CPImageAlignBottomLeft:
|
||||
case CPImageAlignBottomRight:
|
||||
y = boundsHeight - height;
|
||||
break;
|
||||
|
||||
default:
|
||||
y = (boundsHeight - height) / 2.0;
|
||||
break;
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, x, y);
|
||||
#endif
|
||||
}
|
||||
|
||||
_imageRect = _CGRectMake(x, y, width, height);
|
||||
|
||||
if (_hasShadow)
|
||||
[_shadowView setFrame:_CGRectMake(x - [_shadowView leftInset], y - [_shadowView topInset], width + insetWidth, height + insetHeight)];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
// Should we do something with this event?
|
||||
[[self nextResponder] mouseDown:anEvent];
|
||||
}
|
||||
|
||||
- (void)setEditable:(BOOL)shouldBeEditable
|
||||
{
|
||||
if (_isEditable === shouldBeEditable)
|
||||
return;
|
||||
|
||||
_isEditable = shouldBeEditable;
|
||||
|
||||
if (_isEditable)
|
||||
[self registerForDraggedTypes:[CPImagesPboardType]];
|
||||
|
||||
else
|
||||
{
|
||||
var draggedTypes = [self registeredDraggedTypes];
|
||||
|
||||
[self unregisterDraggedTypes];
|
||||
|
||||
[draggedTypes removeObjectIdenticalTo:CPImagesPboardType];
|
||||
|
||||
[self registerForDraggedTypes:draggedTypes];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return _isEditable;
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation:(CPDraggingInfo)aSender
|
||||
{
|
||||
var images = [CPKeyedUnarchiver unarchiveObjectWithData:[[aSender draggingPasteboard] dataForType:CPImagesPboardType]];
|
||||
|
||||
if ([images count])
|
||||
{
|
||||
[self setImage:images[0]];
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPImageViewValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options
|
||||
{
|
||||
[self _setPlaceholder:nil forMarker:CPMultipleValuesMarker isDefault:YES];
|
||||
[self _setPlaceholder:nil forMarker:CPNoSelectionMarker isDefault:YES];
|
||||
[self _setPlaceholder:nil forMarker:CPNotApplicableMarker isDefault:YES];
|
||||
[self _setPlaceholder:nil forMarker:CPNullMarker isDefault:YES];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source setImage:nil];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
var image;
|
||||
|
||||
if (aBinding === CPDataBinding)
|
||||
image = [[CPImage alloc] initWithData:aValue];
|
||||
else if (aBinding === CPValueURLBinding || aBinding === CPValuePathBinding)
|
||||
image = [[CPImage alloc] initWithContentsOfFile:aValue];
|
||||
else if (aBinding === CPValueBinding)
|
||||
image = aValue;
|
||||
|
||||
[_source setImage:image];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPImageViewImageKey = @"CPImageViewImageKey",
|
||||
CPImageViewImageScalingKey = @"CPImageViewImageScalingKey",
|
||||
CPImageViewImageAlignmentKey = @"CPImageViewImageAlignmentKey",
|
||||
CPImageViewHasShadowKey = @"CPImageViewHasShadowKey",
|
||||
CPImageViewIsEditableKey = @"CPImageViewIsEditableKey";
|
||||
|
||||
@implementation CPImageView (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the image view with the provided coder.
|
||||
@param aCoder the coder from which data will be read.
|
||||
@return the initialized image view
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement = document.createElement("img");
|
||||
_DOMImageElement.style.position = "absolute";
|
||||
_DOMImageElement.style.left = "0px";
|
||||
_DOMImageElement.style.top = "0px";
|
||||
_DOMImageElement.style.visibility = "hidden";
|
||||
if ([CPPlatform supportsDragAndDrop])
|
||||
{
|
||||
_DOMImageElement.setAttribute("draggable", "true");
|
||||
_DOMImageElement.style["-khtml-user-drag"] = "element";
|
||||
}
|
||||
|
||||
if (typeof(appkit_tag_dom_elements) !== "undefined" && !!appkit_tag_dom_elements)
|
||||
_DOMImageElement.setAttribute("data-cappuccino-view", [self className]);
|
||||
#endif
|
||||
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMElement.appendChild(_DOMImageElement);
|
||||
#endif
|
||||
|
||||
[self setHasShadow:[aCoder decodeBoolForKey:CPImageViewHasShadowKey]];
|
||||
[self setImageAlignment:[aCoder decodeIntForKey:CPImageViewImageAlignmentKey]];
|
||||
|
||||
if ([aCoder decodeBoolForKey:CPImageViewIsEditableKey])
|
||||
[self setEditable:YES];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes the image view out to the coder.
|
||||
@param aCoder the coder to which the image
|
||||
view will be written
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
// We do this in order to avoid encoding the _shadowView, which
|
||||
// should just automatically be created programmatically as needed.
|
||||
if (_shadowView)
|
||||
{
|
||||
var actualSubviews = _subviews;
|
||||
|
||||
_subviews = [_subviews copy];
|
||||
[_subviews removeObjectIdenticalTo:_shadowView];
|
||||
}
|
||||
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
if (_shadowView)
|
||||
_subviews = actualSubviews;
|
||||
|
||||
[aCoder encodeBool:_hasShadow forKey:CPImageViewHasShadowKey];
|
||||
[aCoder encodeInt:_imageAlignment forKey:CPImageViewImageAlignmentKey];
|
||||
|
||||
if (_isEditable)
|
||||
[aCoder encodeBool:_isEditable forKey:CPImageViewIsEditableKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,264 +0,0 @@
|
||||
/*
|
||||
* CPKeyBinding.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Nicholas Small.
|
||||
* Copyright 2010, 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 "CPEvent.j"
|
||||
|
||||
|
||||
CPStandardKeyBindings = {
|
||||
@"@.": @"cancelOperation:",
|
||||
|
||||
@"@a": @"selectAll:",
|
||||
@"^a": @"moveToBeginningOfParagraph:",
|
||||
@"^$a": @"moveToBeginningOfParagraphAndModifySelection:",
|
||||
@"^b": @"moveBackward:",
|
||||
@"^$b": @"moveBackwardAndModifySelection:",
|
||||
@"^~b": @"moveWordBackward:",
|
||||
@"^~$b": @"moveWordBackwardAndModifySelection:",
|
||||
@"^d": @"deleteForward:",
|
||||
@"^e": @"moveToEndOfParagraph:",
|
||||
@"^$e": @"moveToEndOfParagraphAndModifySelection:",
|
||||
@"^f": @"moveForward:",
|
||||
@"^$f": @"moveForwardAndModifySelection:",
|
||||
@"^~f": @"moveWordForward:",
|
||||
@"^~$f": @"moveWordForwardAndModifySelection:",
|
||||
@"^h": @"deleteBackward:",
|
||||
@"^k": @"deleteToEndOfParagraph:",
|
||||
@"^l": @"centerSelectionInVisibleArea:",
|
||||
@"^n": @"moveDown:",
|
||||
@"^$n": @"moveDownAndModifySelection:",
|
||||
@"^o": [@"insertNewlineIgnoringFieldEditor:", @"moveBackward:"],
|
||||
@"^p": @"moveUp:",
|
||||
@"^$p": @"moveUpAndModifySelection:",
|
||||
@"^t": @"transpose:",
|
||||
@"^v": @"pageDown:",
|
||||
@"^$v": @"pageDownAndModifySelection:",
|
||||
@"^y": @"yank:"
|
||||
};
|
||||
|
||||
CPStandardKeyBindings[CPNewlineCharacter] = @"insertNewline:";
|
||||
CPStandardKeyBindings[CPCarriageReturnCharacter] = @"insertNewline:";
|
||||
CPStandardKeyBindings[CPEnterCharacter] = @"insertNewline:";
|
||||
CPStandardKeyBindings[@"~" + CPNewlineCharacter] = @"insertNewlineIgnoringFieldEditor:";
|
||||
CPStandardKeyBindings[@"~" + CPCarriageReturnCharacter] = @"insertNewlineIgnoringFieldEditor:";
|
||||
CPStandardKeyBindings[@"~" + CPEnterCharacter] = @"insertNewlineIgnoringFieldEditor:";
|
||||
CPStandardKeyBindings[@"^" + CPNewlineCharacter] = @"insertLineBreak:";
|
||||
CPStandardKeyBindings[@"^" + CPCarriageReturnCharacter] = @"insertLineBreak:";
|
||||
CPStandardKeyBindings[@"^" + CPEnterCharacter] = @"insertLineBreak:";
|
||||
|
||||
CPStandardKeyBindings[CPBackspaceCharacter] = @"deleteBackward:";
|
||||
CPStandardKeyBindings[@"~" + CPBackspaceCharacter] = @"deleteWordBackward:";
|
||||
CPStandardKeyBindings[CPDeleteCharacter] = @"deleteBackward:";
|
||||
CPStandardKeyBindings[@"@" + CPDeleteCharacter] = @"deleteToBeginningOfLine:";
|
||||
CPStandardKeyBindings[@"~" + CPDeleteCharacter] = @"deleteWordBackward:";
|
||||
CPStandardKeyBindings[@"^" + CPDeleteCharacter] = @"deleteBackwardByDecomposingPreviousCharacter:";
|
||||
CPStandardKeyBindings[@"^~" + CPDeleteCharacter] = @"deleteWordBackward:";
|
||||
|
||||
CPStandardKeyBindings[CPDeleteFunctionKey] = @"deleteForward:";
|
||||
CPStandardKeyBindings[@"~" + CPDeleteFunctionKey] = @"deleteWordForward:";
|
||||
|
||||
CPStandardKeyBindings[CPTabCharacter] = @"insertTab:";
|
||||
CPStandardKeyBindings[@"~" + CPTabCharacter] = @"insertTabIgnoringFieldEditor:";
|
||||
CPStandardKeyBindings[@"^" + CPTabCharacter] = @"selectNextKeyView:";
|
||||
CPStandardKeyBindings[CPBackTabCharacter] = @"insertBacktab:";
|
||||
CPStandardKeyBindings[@"^" + CPBackTabCharacter] = @"selectPreviousKeyView:";
|
||||
|
||||
CPStandardKeyBindings[CPEscapeFunctionKey] = @"cancelOperation:";
|
||||
CPStandardKeyBindings[@"~" + CPEscapeFunctionKey] = @"complete:";
|
||||
CPStandardKeyBindings[CPF5FunctionKey] = @"complete:";
|
||||
|
||||
CPStandardKeyBindings[CPLeftArrowFunctionKey] = @"moveLeft:";
|
||||
CPStandardKeyBindings[@"~" + CPLeftArrowFunctionKey] = @"moveWordLeft:";
|
||||
CPStandardKeyBindings[@"^" + CPLeftArrowFunctionKey] = @"moveToLeftEndOfLine:";
|
||||
CPStandardKeyBindings[@"@" + CPLeftArrowFunctionKey] = @"moveToLeftEndOfLine:";
|
||||
CPStandardKeyBindings[@"$" + CPLeftArrowFunctionKey] = @"moveLeftAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$~" + CPLeftArrowFunctionKey] = @"moveWordLeftAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$^" + CPLeftArrowFunctionKey] = @"moveToLeftEndOfLineAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$@" + CPLeftArrowFunctionKey] = @"moveToLeftEndOfLineAndModifySelection:";
|
||||
CPStandardKeyBindings[@"@^" + CPLeftArrowFunctionKey] = @"makeBaseWritingDirectionRightToLeft:";
|
||||
CPStandardKeyBindings[@"@^~" + CPLeftArrowFunctionKey] = @"makeTextWritingDirectionRightToLeft:";
|
||||
|
||||
CPStandardKeyBindings[CPRightArrowFunctionKey] = @"moveRight:";
|
||||
CPStandardKeyBindings[@"~" + CPRightArrowFunctionKey] = @"moveWordRight:";
|
||||
CPStandardKeyBindings[@"^" + CPRightArrowFunctionKey] = @"moveToRightEndOfLine:";
|
||||
CPStandardKeyBindings[@"@" + CPRightArrowFunctionKey] = @"moveToRightEndOfLine:";
|
||||
CPStandardKeyBindings[@"$" + CPRightArrowFunctionKey] = @"moveRightAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$~" + CPRightArrowFunctionKey] = @"moveWordRightAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$^" + CPRightArrowFunctionKey] = @"moveToRightEndOfLineAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$@" + CPRightArrowFunctionKey] = @"moveToRightEndOfLineAndModifySelection:";
|
||||
CPStandardKeyBindings[@"@^" + CPRightArrowFunctionKey] = @"makeBaseWritingDirectionLeftToRight:";
|
||||
CPStandardKeyBindings[@"@^~" + CPRightArrowFunctionKey] = @"makeTextWritingDirectionLeftToRight:";
|
||||
|
||||
CPStandardKeyBindings[CPUpArrowFunctionKey] = @"moveUp:";
|
||||
CPStandardKeyBindings[@"~" + CPUpArrowFunctionKey] = [@"moveBackward:", @"moveToBeginningOfParagraph:"];
|
||||
CPStandardKeyBindings[@"^" + CPUpArrowFunctionKey] = @"scrollPageUp:";
|
||||
CPStandardKeyBindings[@"@" + CPUpArrowFunctionKey] = @"moveToBeginningOfDocument:";
|
||||
CPStandardKeyBindings[@"$" + CPUpArrowFunctionKey] = @"moveUpAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$~" + CPUpArrowFunctionKey] = @"moveParagraphBackwardAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$@" + CPUpArrowFunctionKey] = @"moveToBeginningOfDocumentAndModifySelection:";
|
||||
|
||||
CPStandardKeyBindings[CPDownArrowFunctionKey] = @"moveDown:";
|
||||
CPStandardKeyBindings[@"~" + CPDownArrowFunctionKey] = [@"moveForward:", @"moveToEndOfParagraph:"];
|
||||
CPStandardKeyBindings[@"^" + CPDownArrowFunctionKey] = @"scrollPageDown:";
|
||||
CPStandardKeyBindings[@"@" + CPDownArrowFunctionKey] = @"moveToEndOfDocument:";
|
||||
CPStandardKeyBindings[@"$" + CPDownArrowFunctionKey] = @"moveDownAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$~" + CPDownArrowFunctionKey] = @"moveParagraphForwardAndModifySelection:";
|
||||
CPStandardKeyBindings[@"$@" + CPDownArrowFunctionKey] = @"moveToEndOfDocumentAndModifySelection:";
|
||||
CPStandardKeyBindings[@"@^" + CPDownArrowFunctionKey] = @"makeBaseWritingDirectionNatural:";
|
||||
CPStandardKeyBindings[@"@^~" + CPDownArrowFunctionKey] = @"makeTextWritingDirectionNatural:";
|
||||
|
||||
CPStandardKeyBindings[CPHomeFunctionKey] = @"scrollToBeginningOfDocument:";
|
||||
CPStandardKeyBindings[@"$" + CPHomeFunctionKey] = @"moveToBeginningOfDocumentAndModifySelection:";
|
||||
CPStandardKeyBindings[CPEndFunctionKey] = @"scrollToEndOfDocument:";
|
||||
CPStandardKeyBindings[@"$" + CPEndFunctionKey] = @"moveToEndOfDocumentAndModifySelection:";
|
||||
|
||||
CPStandardKeyBindings[CPPageUpFunctionKey] = @"scrollPageUp:";
|
||||
CPStandardKeyBindings[@"~" + CPPageUpFunctionKey] = @"pageUp:";
|
||||
CPStandardKeyBindings[@"$" + CPPageUpFunctionKey] = @"pageUpAndModifySelection:";
|
||||
CPStandardKeyBindings[CPPageDownFunctionKey] = @"scrollPageDown:";
|
||||
CPStandardKeyBindings[@"~" + CPPageDownFunctionKey] = @"pageDown:";
|
||||
CPStandardKeyBindings[@"$" + CPPageDownFunctionKey] = @"pageDownAndModifySelection:";
|
||||
|
||||
var CPKeyBindingCache = {};
|
||||
|
||||
@implementation CPKeyBinding : CPObject
|
||||
{
|
||||
CPString _key;
|
||||
unsigned _modifierFlags;
|
||||
|
||||
CPArray _selectors;
|
||||
|
||||
CPString _cacheName;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPKeyBinding class])
|
||||
return;
|
||||
|
||||
[self createKeyBindingsFromJSObject:CPStandardKeyBindings];
|
||||
}
|
||||
|
||||
+ (void)createKeyBindingsFromJSObject:(JSObject)anObject
|
||||
{
|
||||
var binding;
|
||||
for (binding in anObject)
|
||||
[self cacheKeyBinding:[[CPKeyBinding alloc] initWithPhysicalKeyString:binding selectors:anObject[binding]]];
|
||||
}
|
||||
|
||||
+ (void)cacheKeyBinding:(CPKeyBinding)aBinding
|
||||
{
|
||||
if (!aBinding)
|
||||
return;
|
||||
|
||||
CPKeyBindingCache[[aBinding _cacheName]] = aBinding;
|
||||
}
|
||||
|
||||
+ (CPKeyBinding)keyBindingForKey:(CPString)aKey modifierFlags:(unsigned)aFlag
|
||||
{
|
||||
var tempBinding = [[self alloc] initWithKey:aKey modifierFlags:aFlag selectors:nil];
|
||||
return CPKeyBindingCache[[tempBinding _cacheName]];
|
||||
}
|
||||
|
||||
+ (CPArray)selectorsForKey:(CPString)aKey modifierFlags:(unsigned)aFlag
|
||||
{
|
||||
return [[self keyBindingForKey:aKey modifierFlags:aFlag] selectors];
|
||||
}
|
||||
|
||||
- (id)initWithPhysicalKeyString:(CPString)binding selectors:(CPArray)selectors
|
||||
{
|
||||
var components = binding.split(@""),
|
||||
modifierFlags = ([components containsObject:@"$"] ? CPShiftKeyMask : 0) |
|
||||
([components containsObject:@"^"] ? CPControlKeyMask : 0) |
|
||||
([components containsObject:@"~"] ? CPAlternateKeyMask : 0) |
|
||||
([components containsObject:@"@"] ? CPCommandKeyMask : 0);
|
||||
|
||||
if (![selectors isKindOfClass:CPArray])
|
||||
selectors = [selectors];
|
||||
|
||||
return [self initWithKey:[components lastObject] modifierFlags:modifierFlags selectors:selectors];
|
||||
}
|
||||
|
||||
- (id)initWithKey:(CPString)aKey modifierFlags:(unsigned)aFlag selectors:(CPArray)selectors
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_key = aKey;
|
||||
_modifierFlags = aFlag;
|
||||
|
||||
_selectors = selectors;
|
||||
|
||||
// We normalize our key binding string in order to properly cache it.
|
||||
// We want to ensure the modifiers are always in the same order.
|
||||
var cacheName = [];
|
||||
|
||||
if (_modifierFlags & CPCommandKeyMask)
|
||||
cacheName.push(@"@");
|
||||
if (_modifierFlags & CPControlKeyMask)
|
||||
cacheName.push(@"^");
|
||||
if (_modifierFlags & CPAlternateKeyMask)
|
||||
cacheName.push(@"~");
|
||||
if (_modifierFlags & CPShiftKeyMask)
|
||||
cacheName.push(@"$");
|
||||
|
||||
cacheName.push(_key);
|
||||
|
||||
_cacheName = cacheName.join(@"");
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)key
|
||||
{
|
||||
return _key;
|
||||
}
|
||||
|
||||
- (unsigned)modifierFlags
|
||||
{
|
||||
return _modifierFlags;
|
||||
}
|
||||
|
||||
- (CPArray)selectors
|
||||
{
|
||||
return _selectors;
|
||||
}
|
||||
|
||||
- (CPString)_cacheName
|
||||
{
|
||||
return _cacheName;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(CPKeyBinding)rhs
|
||||
{
|
||||
return _key === [rhs key] && _modifierFlags === [rhs modifierFlags];
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return [CPString stringWithFormat:@"<KeyBinding string: '%@' modifierFlags: 0x%lx selectors: %@>", _key, _modifierFlags, _selectors];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,624 +0,0 @@
|
||||
/*
|
||||
* CPKeyValueBinding.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher 1/13/09
|
||||
* Copyright 280 North, Inc.
|
||||
*
|
||||
* Adapted from GNUStep
|
||||
* Released under the LGPL.
|
||||
*
|
||||
* 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 <Foundation/CPArray.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPValueTransformer.j>
|
||||
|
||||
|
||||
var exposedBindingsMap = [CPDictionary new],
|
||||
bindingsMap = [CPDictionary new];
|
||||
|
||||
var CPBindingOperationAnd = 0,
|
||||
CPBindingOperationOr = 1;
|
||||
|
||||
@implementation CPBinder : CPObject
|
||||
{
|
||||
CPDictionary _info;
|
||||
id _source;
|
||||
|
||||
JSObject _suppressedNotifications;
|
||||
JSObject _placeholderForMarker;
|
||||
}
|
||||
|
||||
+ (void)exposeBinding:(CPString)aBinding forClass:(Class)aClass
|
||||
{
|
||||
var bindings = [exposedBindingsMap objectForKey:[aClass UID]];
|
||||
|
||||
if (!bindings)
|
||||
{
|
||||
bindings = [];
|
||||
[exposedBindingsMap setObject:bindings forKey:[aClass UID]];
|
||||
}
|
||||
|
||||
bindings.push(aBinding);
|
||||
}
|
||||
|
||||
+ (CPArray)exposedBindingsForClass:(Class)aClass
|
||||
{
|
||||
return [[exposedBindingsMap objectForKey:[aClass UID]] copy];
|
||||
}
|
||||
|
||||
+ (CPBinder)getBinding:(CPString)aBinding forObject:(id)anObject
|
||||
{
|
||||
return [[bindingsMap objectForKey:[anObject UID]] objectForKey:aBinding];
|
||||
}
|
||||
|
||||
+ (CPDictionary)infoForBinding:(CPString)aBinding forObject:(id)anObject
|
||||
{
|
||||
var theBinding = [self getBinding:aBinding forObject:anObject];
|
||||
|
||||
if (theBinding)
|
||||
return theBinding._info;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (CPDictionary)allBindingsForObject:(id)anObject
|
||||
{
|
||||
return [bindingsMap objectForKey:[anObject UID]];
|
||||
}
|
||||
|
||||
+ (void)unbind:(CPString)aBinding forObject:(id)anObject
|
||||
{
|
||||
var bindings = [bindingsMap objectForKey:[anObject UID]];
|
||||
|
||||
if (!bindings)
|
||||
return;
|
||||
|
||||
var theBinding = [bindings objectForKey:aBinding];
|
||||
|
||||
if (!theBinding)
|
||||
return;
|
||||
|
||||
var infoDictionary = theBinding._info,
|
||||
observedObject = [infoDictionary objectForKey:CPObservedObjectKey],
|
||||
keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey];
|
||||
|
||||
[observedObject removeObserver:theBinding forKeyPath:keyPath];
|
||||
[bindings removeObjectForKey:aBinding];
|
||||
}
|
||||
|
||||
+ (void)unbindAllForObject:(id)anObject
|
||||
{
|
||||
var bindings = [bindingsMap objectForKey:[anObject UID]];
|
||||
if (!bindings)
|
||||
return;
|
||||
|
||||
var allKeys = [bindings allKeys],
|
||||
count = allKeys.length;
|
||||
|
||||
while (count--)
|
||||
[anObject unbind:[bindings objectForKey:allKeys[count]]];
|
||||
|
||||
[bindingsMap removeObjectForKey:[anObject UID]];
|
||||
}
|
||||
|
||||
- (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_source = aSource;
|
||||
_info = [CPDictionary dictionaryWithObjects:[aDestination, aKeyPath] forKeys:[CPObservedObjectKey, CPObservedKeyPathKey]];
|
||||
_suppressedNotifications = {};
|
||||
_placeholderForMarker = {};
|
||||
|
||||
if (options)
|
||||
[_info setObject:options forKey:CPOptionsKey];
|
||||
|
||||
[self _updatePlaceholdersWithOptions:options forBinding:aName];
|
||||
|
||||
[aDestination addObserver:self forKeyPath:aKeyPath options:CPKeyValueObservingOptionNew context:aBinding];
|
||||
|
||||
var bindings = [bindingsMap objectForKey:[_source UID]];
|
||||
if (!bindings)
|
||||
{
|
||||
bindings = [CPDictionary new];
|
||||
[bindingsMap setObject:bindings forKey:[_source UID]];
|
||||
}
|
||||
|
||||
[bindings setObject:self forKey:aName];
|
||||
[self setValueFor:aBinding];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)theBinding
|
||||
{
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
newValue = [destination valueForKeyPath:keyPath],
|
||||
isPlaceholder = CPIsControllerMarker(newValue);
|
||||
|
||||
if (isPlaceholder)
|
||||
{
|
||||
if (newValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
|
||||
{
|
||||
[CPException raise:CPGenericException
|
||||
reason:@"Cannot transform non-applicable key on: " + _source + " key path: " + keyPath + " value: " + newValue];
|
||||
}
|
||||
|
||||
var value = [self _placeholderForMarker:newValue];
|
||||
[self setPlaceholderValue:value withMarker:newValue forBinding:theBinding];
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = [self transformValue:newValue withOptions:options];
|
||||
[self setValue:value forBinding:theBinding];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source setValue:aValue forKey:aBinding];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source setValue:aValue forKey:aBinding];
|
||||
}
|
||||
|
||||
- (void)reverseSetValueFor:(CPString)aBinding
|
||||
{
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
newValue = [self valueForBinding:aBinding];
|
||||
|
||||
newValue = [self reverseTransformValue:newValue withOptions:options];
|
||||
|
||||
[self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
|
||||
[destination setValue:newValue forKeyPath:keyPath];
|
||||
[self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
|
||||
}
|
||||
|
||||
- (id)valueForBinding:(CPString)aBinding
|
||||
{
|
||||
return [_source valueForKeyPath:aBinding];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
|
||||
{
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
var objectSuppressions = _suppressedNotifications[[anObject UID]];
|
||||
if (objectSuppressions && objectSuppressions[aKeyPath])
|
||||
return;
|
||||
|
||||
[self setValueFor:context];
|
||||
}
|
||||
|
||||
- (id)transformValue:(id)aValue withOptions:(CPDictionary)options
|
||||
{
|
||||
var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
|
||||
valueTransformer;
|
||||
|
||||
if (valueTransformerName)
|
||||
{
|
||||
valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
|
||||
|
||||
if (!valueTransformer)
|
||||
{
|
||||
var valueTransformerClass = CPClassFromString(valueTransformerName);
|
||||
if (valueTransformerClass)
|
||||
{
|
||||
valueTransformer = [[valueTransformerClass alloc] init];
|
||||
[valueTransformerClass setValueTransformer:valueTransformer forName:valueTransformerName];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
|
||||
|
||||
if (valueTransformer)
|
||||
aValue = [valueTransformer transformedValue:aValue];
|
||||
|
||||
if (aValue === undefined || aValue === nil || aValue === [CPNull null])
|
||||
aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
|
||||
|
||||
return aValue;
|
||||
}
|
||||
|
||||
- (id)reverseTransformValue:(id)aValue withOptions:(CPDictionary)options
|
||||
{
|
||||
var valueTransformerName = [options objectForKey:CPValueTransformerNameBindingOption],
|
||||
valueTransformer;
|
||||
|
||||
if (valueTransformerName)
|
||||
valueTransformer = [CPValueTransformer valueTransformerForName:valueTransformerName];
|
||||
else
|
||||
valueTransformer = [options objectForKey:CPValueTransformerBindingOption];
|
||||
|
||||
if (valueTransformer && [[valueTransformer class] allowsReverseTransformation])
|
||||
aValue = [valueTransformer reverseTransformedValue:aValue];
|
||||
|
||||
return aValue;
|
||||
}
|
||||
|
||||
- (BOOL)continuouslyUpdatesValue
|
||||
{
|
||||
var options = [_info objectForKey:CPOptionsKey];
|
||||
return [[options objectForKey:CPContinuouslyUpdatesValueBindingOption] boolValue];
|
||||
}
|
||||
|
||||
- (BOOL)handlesContentAsCompoundValue
|
||||
{
|
||||
var options = [_info objectForKey:CPOptionsKey];
|
||||
return [[options objectForKey:CPHandlesContentAsCompoundValueBindingOption] boolValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Use this to avoid reacting to the notifications coming out of a reverseTransformedValue:.
|
||||
*/
|
||||
- (void)suppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
|
||||
{
|
||||
if (!anObject)
|
||||
return;
|
||||
|
||||
var uid = [anObject UID],
|
||||
objectSuppressions = _suppressedNotifications[uid];
|
||||
if (!objectSuppressions)
|
||||
_suppressedNotifications[uid] = objectSuppressions = {};
|
||||
|
||||
objectSuppressions[aKeyPath] = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Use this to cancel suppressSpecificNotificationFromObject:keyPath:.
|
||||
*/
|
||||
- (void)unsuppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
|
||||
{
|
||||
if (!anObject)
|
||||
return;
|
||||
|
||||
var uid = [anObject UID],
|
||||
objectSuppressions = _suppressedNotifications[uid];
|
||||
if (!objectSuppressions)
|
||||
return;
|
||||
|
||||
delete objectSuppressions[aKeyPath];
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options
|
||||
{
|
||||
var count = [CPBinderPlaceholderMarkers count];
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var marker = CPBinderPlaceholderMarkers[count],
|
||||
optionName = CPBinderPlaceholderOptions[count],
|
||||
isExplicit = [options containsKey:optionName],
|
||||
placeholder = isExplicit ? [options objectForKey:optionName] : nil;
|
||||
[self _setPlaceholder:placeholder forMarker:marker isDefault:!isExplicit];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
|
||||
{
|
||||
[self _updatePlaceholdersWithOptions:options];
|
||||
}
|
||||
|
||||
- (void)_placeholderForMarker:aMarker
|
||||
{
|
||||
var placeholder = _placeholderForMarker[aMarker];
|
||||
if (placeholder)
|
||||
return placeholder['value'];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)_setPlaceholder:(id)aPlaceholder forMarker:(id)aMarker isDefault:(BOOL)isDefault
|
||||
{
|
||||
if (isDefault)
|
||||
{
|
||||
var existingPlaceholder = _placeholderForMarker[aMarker];
|
||||
|
||||
// Don't overwrite an explicitly set placeholder with a default.
|
||||
if (existingPlaceholder && !existingPlaceholder['isDefault'])
|
||||
return;
|
||||
}
|
||||
|
||||
_placeholderForMarker[aMarker] = { 'isDefault': isDefault, 'value': aPlaceholder };
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPObject (KeyValueBindingCreation)
|
||||
|
||||
+ (void)exposeBinding:(CPString)aBinding
|
||||
{
|
||||
[CPBinder exposeBinding:aBinding forClass:[self class]];
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
return [CPBinder class];
|
||||
}
|
||||
|
||||
- (CPArray)exposedBindings
|
||||
{
|
||||
var exposedBindings = [],
|
||||
theClass = [self class];
|
||||
|
||||
while (theClass)
|
||||
{
|
||||
var temp = [CPBinder exposedBindingsForClass:theClass];
|
||||
|
||||
if (temp)
|
||||
[exposedBindings addObjectsFromArray:temp];
|
||||
|
||||
theClass = [theClass superclass];
|
||||
}
|
||||
|
||||
return exposedBindings;
|
||||
}
|
||||
|
||||
- (Class)valueClassForBinding:(CPString)binding
|
||||
{
|
||||
return [CPString class];
|
||||
}
|
||||
|
||||
- (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
|
||||
{
|
||||
if (!anObject || !aKeyPath)
|
||||
return CPLog.error("Invalid object or path on " + self + " for " + aBinding);
|
||||
|
||||
//if (![[self exposedBindings] containsObject:aBinding])
|
||||
// CPLog.warn("No binding exposed on " + self + " for " + aBinding);
|
||||
|
||||
var binderClass = [[self class] _binderClassForBinding:aBinding];
|
||||
|
||||
[self unbind:aBinding];
|
||||
[[binderClass alloc] initWithBinding:[self _replacementKeyPathForBinding:aBinding] name:aBinding to:anObject keyPath:aKeyPath options:options from:self];
|
||||
}
|
||||
|
||||
- (CPDictionary)infoForBinding:(CPString)aBinding
|
||||
{
|
||||
return [CPBinder infoForBinding:aBinding forObject:self];
|
||||
}
|
||||
|
||||
- (void)unbind:(CPString)aBinding
|
||||
{
|
||||
var binderClass = [[self class] _binderClassForBinding:aBinding];
|
||||
[binderClass unbind:aBinding forObject:self];
|
||||
}
|
||||
|
||||
- (id)_replacementKeyPathForBinding:(CPString)binding
|
||||
{
|
||||
return binding;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Provides stub implementations that simply call super for the "objectValue" binding
|
||||
This class should not be necessary but assures backwards compliance with our old way of doing bindings
|
||||
Every class with a value binding should implement a subclass to handle it's specific value binding logic
|
||||
*/
|
||||
@implementation _CPValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)theBinding
|
||||
{
|
||||
[super setValueFor:@"objectValue"];
|
||||
}
|
||||
|
||||
- (void)reverseSetValueFor:(CPString)theBinding
|
||||
{
|
||||
[super reverseSetValueFor:@"objectValue"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPKeyValueOrBinding : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)aBinding
|
||||
{
|
||||
var bindings = [bindingsMap valueForKey:[_source UID]];
|
||||
|
||||
if (!bindings)
|
||||
return;
|
||||
|
||||
[_source setValue:resolveMultipleValues(aBinding, bindings, CPBindingOperationOr) forKey:aBinding];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
|
||||
{
|
||||
[self setValueFor:context];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPKeyValueAndBinding : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)aBinding
|
||||
{
|
||||
var bindings = [bindingsMap objectForKey:[_source UID]];
|
||||
|
||||
if (!bindings)
|
||||
return;
|
||||
|
||||
[_source setValue:resolveMultipleValues(aBinding, bindings, CPBindingOperationAnd) forKey:aBinding];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObejct change:(CPDictionary)changes context:(id)context
|
||||
{
|
||||
[self setValueFor:context];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var resolveMultipleValues = function(/*CPString*/key, /*CPDictionary*/bindings, /*GSBindingOperationKind*/operation)
|
||||
{
|
||||
var bindingName = key,
|
||||
theBinding,
|
||||
count = 1;
|
||||
|
||||
while (theBinding = [bindings objectForKey:bindingName])
|
||||
{
|
||||
var infoDictionary = theBinding._info,
|
||||
object = [infoDictionary objectForKey:CPObservedObjectKey],
|
||||
keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey],
|
||||
options = [infoDictionary objectForKey:CPOptionsKey];
|
||||
|
||||
var value = [theBinding transformValue:[object valueForKeyPath:keyPath] withOptions:options];
|
||||
|
||||
if (value == operation)
|
||||
return operation;
|
||||
|
||||
bindingName = [CPString stringWithFormat:@"%@%i", key, ++count];
|
||||
}
|
||||
|
||||
return !operation;
|
||||
};
|
||||
|
||||
var invokeAction = function(/*CPString*/targetKey, /*CPString*/argumentKey, /*CPDictionary*/bindings)
|
||||
{
|
||||
var theBinding = [bindings objectForKey:targetKey],
|
||||
infoDictionary = theBinding._info,
|
||||
|
||||
object = [infoDictionary objectForKey:CPObservedObjectKey],
|
||||
keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey],
|
||||
options = [infoDictionary objectForKey:CPOptionsKey],
|
||||
|
||||
target = [object valueForKeyPath:keyPath],
|
||||
selector = [options objectForKey:CPSelectorNameBindingOption];
|
||||
|
||||
if (!target || !selector)
|
||||
return;
|
||||
|
||||
var invocation = [CPInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
|
||||
[invocation setSelector:selector];
|
||||
|
||||
var bindingName = argumentKey,
|
||||
count = 1;
|
||||
|
||||
while (theBinding = [bindings objectForKey:bindingName])
|
||||
{
|
||||
infoDictionary = theBinding._info;
|
||||
|
||||
keyPath = [infoDictionary objectForKey:CPObserverKeyPathKey];
|
||||
object = [[infoDictionary objectForKey:CPObservedObjectKey] valueForKeyPath:keyPath];
|
||||
|
||||
if (object)
|
||||
[invocation setArgument:object atIndex:++count];
|
||||
|
||||
bindingName = [CPString stringWithFormat:@"%@%i", argumentKey, count];
|
||||
}
|
||||
|
||||
[invocation invoke];
|
||||
};
|
||||
|
||||
// Keys in options dictionary
|
||||
|
||||
// Keys in dictionary returned by infoForBinding
|
||||
CPObservedObjectKey = @"CPObservedObjectKey";
|
||||
CPObservedKeyPathKey = @"CPObservedKeyPathKey";
|
||||
CPOptionsKey = @"CPOptionsKey";
|
||||
|
||||
// special markers
|
||||
CPMultipleValuesMarker = @"CPMultipleValuesMarker";
|
||||
CPNoSelectionMarker = @"CPNoSelectionMarker";
|
||||
CPNotApplicableMarker = @"CPNotApplicableMarker";
|
||||
CPNullMarker = @"CPNullMarker";
|
||||
|
||||
// Binding name constants
|
||||
CPAlignmentBinding = @"alignment";
|
||||
CPContentArrayBinding = @"contentArray";
|
||||
CPContentBinding = @"content";
|
||||
CPContentObjectBinding = @"contentObject";
|
||||
CPContentObjectsBinding = @"contentObjects";
|
||||
CPContentValuesBinding = @"contentValues";
|
||||
CPEditableBinding = @"editable";
|
||||
CPEnabledBinding = @"enabled";
|
||||
CPFontBinding = @"font";
|
||||
CPFontNameBinding = @"fontName";
|
||||
CPFontBoldBinding = @"fontBold";
|
||||
CPHiddenBinding = @"hidden";
|
||||
CPFilterPredicateBinding = @"filterPredicate";
|
||||
CPMaxValueBinding = @"maxValue";
|
||||
CPMinValueBinding = @"minValue";
|
||||
CPPredicateBinding = @"predicate";
|
||||
CPSelectedIndexBinding = @"selectedIndex";
|
||||
CPSelectedLabelBinding = @"selectedLabel";
|
||||
CPSelectedObjectBinding = @"selectedObject";
|
||||
CPSelectedObjectsBinding = @"selectedObjects";
|
||||
CPSelectedTagBinding = @"selectedTag";
|
||||
CPSelectedValueBinding = @"selectedValue";
|
||||
CPSelectedValuesBinding = @"selectedValues";
|
||||
CPSelectionIndexesBinding = @"selectionIndexes";
|
||||
CPTextColorBinding = @"textColor";
|
||||
CPTitleBinding = @"title";
|
||||
CPToolTipBinding = @"toolTip";
|
||||
CPValueBinding = @"value";
|
||||
CPValueURLBinding = @"valueURL";
|
||||
CPValuePathBinding = @"valuePath";
|
||||
CPDataBinding = @"data";
|
||||
|
||||
//Binding options constants
|
||||
CPAllowsEditingMultipleValuesSelectionBindingOption = @"CPAllowsEditingMultipleValuesSelection";
|
||||
CPAllowsNullArgumentBindingOption = @"CPAllowsNullArgument";
|
||||
CPConditionallySetsEditableBindingOption = @"CPConditionallySetsEditable";
|
||||
CPConditionallySetsEnabledBindingOption = @"CPConditionallySetsEnabled";
|
||||
CPConditionallySetsHiddenBindingOption = @"CPConditionallySetsHidden";
|
||||
CPContinuouslyUpdatesValueBindingOption = @"CPContinuouslyUpdatesValue";
|
||||
CPCreatesSortDescriptorBindingOption = @"CPCreatesSortDescriptor";
|
||||
CPDeletesObjectsOnRemoveBindingsOption = @"CPDeletesObjectsOnRemove";
|
||||
CPDisplayNameBindingOption = @"CPDisplayName";
|
||||
CPDisplayPatternBindingOption = @"CPDisplayPattern";
|
||||
CPHandlesContentAsCompoundValueBindingOption = @"CPHandlesContentAsCompoundValue";
|
||||
CPInsertsNullPlaceholderBindingOption = @"CPInsertsNullPlaceholder";
|
||||
CPInvokesSeparatelyWithArrayObjectsBindingOption = @"CPInvokesSeparatelyWithArrayObjects";
|
||||
CPMultipleValuesPlaceholderBindingOption = @"CPMultipleValuesPlaceholder";
|
||||
CPNoSelectionPlaceholderBindingOption = @"CPNoSelectionPlaceholder";
|
||||
CPNotApplicablePlaceholderBindingOption = @"CPNotApplicablePlaceholder";
|
||||
CPNullPlaceholderBindingOption = @"CPNullPlaceholder";
|
||||
CPPredicateFormatBindingOption = @"CPPredicateFormat";
|
||||
CPRaisesForNotApplicableKeysBindingOption = @"CPRaisesForNotApplicableKeys";
|
||||
CPSelectorNameBindingOption = @"CPSelectorName";
|
||||
CPSelectsAllWhenSettingContentBindingOption = @"CPSelectsAllWhenSettingContent";
|
||||
CPValidatesImmediatelyBindingOption = @"CPValidatesImmediately";
|
||||
CPValueTransformerNameBindingOption = @"CPValueTransformerName";
|
||||
CPValueTransformerBindingOption = @"CPValueTransformer";
|
||||
|
||||
CPIsControllerMarker = function(/*id*/anObject)
|
||||
{
|
||||
return anObject === CPMultipleValuesMarker || anObject === CPNoSelectionMarker || anObject === CPNotApplicableMarker || anObject === CPNullMarker;
|
||||
};
|
||||
|
||||
var CPBinderPlaceholderMarkers = [CPMultipleValuesMarker, CPNoSelectionMarker, CPNotApplicableMarker, CPNullMarker],
|
||||
CPBinderPlaceholderOptions = [CPMultipleValuesPlaceholderBindingOption, CPNoSelectionPlaceholderBindingOption, CPNotApplicablePlaceholderBindingOption, CPNullPlaceholderBindingOption];
|
||||
|
||||
@@ -1,415 +0,0 @@
|
||||
/*
|
||||
* CPLevelIndicator.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexander Ljungberg.
|
||||
* Copyright 2011, WireLoad 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 "CPControl.j"
|
||||
|
||||
CPTickMarkBelow = 0;
|
||||
CPTickMarkAbove = 1;
|
||||
CPTickMarkLeft = CPTickMarkAbove;
|
||||
CPTickMarkRight = CPTickMarkBelow;
|
||||
|
||||
CPRelevancyLevelIndicatorStyle = 0;
|
||||
CPContinuousCapacityLevelIndicatorStyle = 1;
|
||||
CPDiscreteCapacityLevelIndicatorStyle = 2;
|
||||
CPRatingLevelIndicatorStyle = 3;
|
||||
|
||||
var _CPLevelIndicatorBezelColor = nil,
|
||||
_CPLevelIndicatorSegmentEmptyColor = nil,
|
||||
_CPLevelIndicatorSegmentNormalColor = nil,
|
||||
_CPLevelIndicatorSegmentWarningColor = nil,
|
||||
_CPLevelIndicatorSegmentCriticalColor = nil,
|
||||
|
||||
_CPLevelIndicatorSpacing = 1;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPLevelIndicator
|
||||
|
||||
CPLevelIndicator is a control which indicates a value visually on a scale.
|
||||
*/
|
||||
@implementation CPLevelIndicator : CPControl
|
||||
{
|
||||
CPLevelIndicator _levelIndicatorStyle @accessors(property=levelIndicatorStyle);
|
||||
double _minValue @accessors(property=minValue);
|
||||
double _maxValue @accessors(property=maxValue);
|
||||
double _warningValue @accessors(property=warningValue);
|
||||
double _criticalValue @accessors(property=criticalValue);
|
||||
CPTickMarkPosition _tickMarkPosition @accessors(property=tickMarkPosition);
|
||||
int _numberOfTickMarks @accessors(property=numberOfTickMarks);
|
||||
int _numberOfMajorTickMarks @accessors(property=numberOfMajorTickMarks);
|
||||
|
||||
BOOL _isEditable;
|
||||
|
||||
BOOL _isTracking;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPLevelIndicator class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:CPLevelIndicator];
|
||||
|
||||
_CPLevelIndicatorBezelColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-bezel-left.png"] size:CGSizeMake(3.0, 18.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-bezel-center.png"] size:CGSizeMake(1.0, 18.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-bezel-right.png"] size:CGSizeMake(3.0, 18.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
|
||||
_CPLevelIndicatorSegmentEmptyColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-empty-left.png"] size:CGSizeMake(3.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-empty-center.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-empty-right.png"] size:CGSizeMake(3.0, 17.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
|
||||
_CPLevelIndicatorSegmentNormalColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-normal-left.png"] size:CGSizeMake(3.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-normal-center.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-normal-right.png"] size:CGSizeMake(3.0, 17.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
|
||||
_CPLevelIndicatorSegmentWarningColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-warning-left.png"] size:CGSizeMake(3.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-warning-center.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-warning-right.png"] size:CGSizeMake(3.0, 17.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
|
||||
_CPLevelIndicatorSegmentCriticalColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-critical-left.png"] size:CGSizeMake(3.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-critical-center.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-critical-right.png"] size:CGSizeMake(3.0, 17.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_levelIndicatorStyle = CPDiscreteCapacityLevelIndicatorStyle;
|
||||
_maxValue = 2;
|
||||
_warningValue = 2;
|
||||
_criticalValue = 2;
|
||||
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:"bezel"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
// TODO Make themable.
|
||||
[bezelView setBackgroundColor:_CPLevelIndicatorBezelColor];
|
||||
|
||||
var segmentCount = _maxValue - _minValue;
|
||||
|
||||
if (segmentCount <= 0)
|
||||
return;
|
||||
|
||||
var filledColor = _CPLevelIndicatorSegmentNormalColor,
|
||||
value = [self doubleValue];
|
||||
|
||||
if (value <= _criticalValue)
|
||||
filledColor = _CPLevelIndicatorSegmentCriticalColor;
|
||||
else if (value <= _warningValue)
|
||||
filledColor = _CPLevelIndicatorSegmentWarningColor;
|
||||
|
||||
for (var i = 0; i < segmentCount; i++)
|
||||
{
|
||||
var segmentView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:bezelView];
|
||||
|
||||
[segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : _CPLevelIndicatorSegmentEmptyColor];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
return [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
|
||||
{
|
||||
// TODO Put into theme attributes.
|
||||
var bezelHeight = 18,
|
||||
segmentHeight = 17,
|
||||
bounds = _CGRectCreateCopy([self bounds]);
|
||||
|
||||
if (aViewName == "bezel")
|
||||
{
|
||||
bounds.origin.y = (_CGRectGetHeight(bounds) - bezelHeight) / 2.0;
|
||||
bounds.size.height = bezelHeight;
|
||||
return bounds;
|
||||
}
|
||||
else if (aViewName.indexOf("segment-bezel") === 0)
|
||||
{
|
||||
var segment = parseInt(aViewName.substring("segment-bezel-".length), 10),
|
||||
segmentCount = _maxValue - _minValue;
|
||||
|
||||
if (segment >= segmentCount)
|
||||
return _CGRectMakeZero();
|
||||
|
||||
var basicSegmentWidth = bounds.size.width / segmentCount,
|
||||
segmentFrame = CGRectCreateCopy([self bounds]);
|
||||
|
||||
segmentFrame.origin.y = (_CGRectGetHeight(bounds) - bezelHeight) / 2.0;
|
||||
segmentFrame.origin.x = FLOOR(segment * basicSegmentWidth);
|
||||
segmentFrame.size.width = (segment == segmentCount - 1) ? bounds.size.width - segmentFrame.origin.x : FLOOR(((segment + 1) * basicSegmentWidth)) - FLOOR((segment * basicSegmentWidth)) - _CPLevelIndicatorSpacing;
|
||||
segmentFrame.size.height = segmentHeight;
|
||||
|
||||
return segmentFrame;
|
||||
}
|
||||
|
||||
return _CGRectMakeZero();
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether or not the receiver level indicator can be edited.
|
||||
*/
|
||||
- (void)setEditable:(BOOL)shouldBeEditable
|
||||
{
|
||||
if (_isEditable === shouldBeEditable)
|
||||
return;
|
||||
|
||||
_isEditable = shouldBeEditable;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the textfield is currently editable by the user.
|
||||
*/
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return _isEditable;
|
||||
}
|
||||
|
||||
- (CPView)hitTest:(CPPoint)aPoint
|
||||
{
|
||||
// Don't swallow clicks when displayed in a table.
|
||||
if (![self isEditable])
|
||||
return nil;
|
||||
|
||||
return [super hitTest:aPoint];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEditable] || ![self isEnabled])
|
||||
return;
|
||||
|
||||
[self _trackMouse:anEvent];
|
||||
}
|
||||
|
||||
- (void)_trackMouse:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type];
|
||||
|
||||
if (type == CPLeftMouseDown || type == CPLeftMouseDragged)
|
||||
{
|
||||
var segmentCount = _maxValue - _minValue;
|
||||
|
||||
if (segmentCount <= 0)
|
||||
return;
|
||||
|
||||
var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
bounds = [self bounds],
|
||||
oldValue = [self doubleValue],
|
||||
newValue = oldValue;
|
||||
|
||||
// Moving the mouse outside of the widget to the left sets it
|
||||
// to its minimum, and moving outside on the right sets it to
|
||||
// its maximum.
|
||||
if (type == CPLeftMouseDragged && location.x < 0)
|
||||
{
|
||||
newValue = _minValue;
|
||||
}
|
||||
else if (type == CPLeftMouseDragged && location.x > bounds.size.width)
|
||||
{
|
||||
newValue = _maxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < segmentCount; i++)
|
||||
{
|
||||
var rect = [self rectForEphemeralSubviewNamed:"segment-bezel-" + i];
|
||||
|
||||
// Once we're tracking the mouse, we only care about horizontal mouse movement.
|
||||
if (location.x >= CGRectGetMinX(rect) && location.x < CGRectGetMaxX(rect))
|
||||
{
|
||||
newValue = (_minValue + i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newValue != oldValue)
|
||||
[self setDoubleValue:newValue];
|
||||
|
||||
// Track the mouse to support click and slide value editing.
|
||||
_isTracking = YES;
|
||||
[CPApp setTarget:self selector:@selector(_trackMouse:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
if ([self isContinuous])
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
else if (_isTracking)
|
||||
{
|
||||
_isTracking = NO;
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
- (CPLevelIndicatorStyle)style;
|
||||
- (void)setLevelIndicatorStyle:(CPLevelIndicatorStyle)style;
|
||||
*/
|
||||
|
||||
- (void)setMinValue:(double)minValue
|
||||
{
|
||||
if (_minValue === minValue)
|
||||
return;
|
||||
_minValue = minValue;
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setMaxValue:(double)maxValue
|
||||
{
|
||||
if (_maxValue === maxValue)
|
||||
return;
|
||||
_maxValue = maxValue;
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setWarningValue:(double)warningValue;
|
||||
{
|
||||
if (_warningValue === warningValue)
|
||||
return;
|
||||
_warningValue = warningValue;
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setCriticalValue:(double)criticalValue;
|
||||
{
|
||||
if (_criticalValue === criticalValue)
|
||||
return;
|
||||
_criticalValue = criticalValue;
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
/*
|
||||
- (CPTickMarkPosition)tickMarkPosition;
|
||||
- (void)setTickMarkPosition:(CPTickMarkPosition)position;
|
||||
|
||||
- (int)numberOfTickMarks;
|
||||
- (void)setNumberOfTickMarks:(int)count;
|
||||
|
||||
- (int)numberOfMajorTickMarks;
|
||||
- (void)setNumberOfMajorTickMarks:(int)count;
|
||||
|
||||
- (double)tickMarkValueAtIndex:(int)index;
|
||||
- (CGRect)rectOfTickMarkAtIndex:(int)index;
|
||||
*/
|
||||
|
||||
@end
|
||||
|
||||
var CPLevelIndicatorStyleKey = "CPLevelIndicatorStyleKey",
|
||||
CPLevelIndicatorMinValueKey = "CPLevelIndicatorMinValueKey",
|
||||
CPLevelIndicatorMaxValueKey = "CPLevelIndicatorMaxValueKey",
|
||||
CPLevelIndicatorWarningValueKey = "CPLevelIndicatorWarningValueKey",
|
||||
CPLevelIndicatorCriticalValueKey = "CPLevelIndicatorCriticalValueKey",
|
||||
CPLevelIndicatorTickMarkPositionKey = "CPLevelIndicatorTickMarkPositionKey",
|
||||
CPLevelIndicatorNumberOfTickMarksKey = "CPLevelIndicatorNumberOfTickMarksKey",
|
||||
CPLevelIndicatorNumberOfMajorTickMarksKey = "CPLevelIndicatorNumberOfMajorTickMarksKey",
|
||||
CPLevelIndicatorIsEditableKey = "CPLevelIndicatorIsEditableKey";
|
||||
|
||||
@implementation CPLevelIndicator (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_levelIndicatorStyle = [aCoder decodeIntForKey:CPLevelIndicatorStyleKey];
|
||||
_minValue = [aCoder decodeDoubleForKey:CPLevelIndicatorMinValueKey];
|
||||
_maxValue = [aCoder decodeDoubleForKey:CPLevelIndicatorMaxValueKey];
|
||||
_warningValue = [aCoder decodeDoubleForKey:CPLevelIndicatorWarningValueKey];
|
||||
_criticalValue = [aCoder decodeDoubleForKey:CPLevelIndicatorCriticalValueKey];
|
||||
_tickMarkPosition = [aCoder decodeIntForKey:CPLevelIndicatorTickMarkPositionKey];
|
||||
_numberOfTickMarks = [aCoder decodeIntForKey:CPLevelIndicatorNumberOfTickMarksKey];
|
||||
_numberOfMajorTickMarks = [aCoder decodeIntForKey:CPLevelIndicatorNumberOfMajorTickMarksKey];
|
||||
|
||||
_isEditable = [aCoder decodeBoolForKey:CPLevelIndicatorIsEditableKey];
|
||||
|
||||
[self _init];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeInt:_levelIndicatorStyle forKey:CPLevelIndicatorStyleKey];
|
||||
[aCoder encodeDouble:_minValue forKey:CPLevelIndicatorMinValueKey];
|
||||
[aCoder encodeDouble:_maxValue forKey:CPLevelIndicatorMaxValueKey];
|
||||
[aCoder encodeDouble:_warningValue forKey:CPLevelIndicatorWarningValueKey];
|
||||
[aCoder encodeDouble:_criticalValue forKey:CPLevelIndicatorCriticalValueKey];
|
||||
[aCoder encodeInt:_tickMarkPosition forKey:CPLevelIndicatorTickMarkPositionKey];
|
||||
[aCoder encodeInt:_numberOfTickMarks forKey:CPLevelIndicatorNumberOfTickMarksKey];
|
||||
[aCoder encodeInt:_numberOfMajorTickMarks forKey:CPLevelIndicatorNumberOfMajorTickMarksKey];
|
||||
[aCoder encodeBool:_isEditable forKey:CPLevelIndicatorIsEditableKey];
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,553 +0,0 @@
|
||||
@import "CPPanel.j"
|
||||
@import "_CPMenuWindow.j"
|
||||
|
||||
|
||||
var MENUBAR_HEIGHT = 28.0,
|
||||
MENUBAR_MARGIN = 10.0,
|
||||
MENUBAR_LEFT_MARGIN = 10.0,
|
||||
MENUBAR_RIGHT_MARGIN = 10.0;
|
||||
|
||||
var _CPMenuBarWindowBackgroundColor = nil,
|
||||
_CPMenuBarWindowFont = nil;
|
||||
|
||||
@implementation _CPMenuBarWindow : CPPanel
|
||||
{
|
||||
CPMenu _menu;
|
||||
CPView _highlightView;
|
||||
CPArray _menuItemViews;
|
||||
|
||||
CPMenuItem _trackingMenuItem;
|
||||
|
||||
CPImageView _iconImageView;
|
||||
CPTextField _titleField;
|
||||
|
||||
CPColor _textColor;
|
||||
CPColor _titleColor;
|
||||
|
||||
CPColor _textShadowColor;
|
||||
CPColor _titleShadowColor;
|
||||
|
||||
CPColor _highlightColor;
|
||||
CPColor _highlightTextColor;
|
||||
CPColor _highlightTextShadowColor;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [_CPMenuBarWindow class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPMenuBarWindowFont = [CPFont boldSystemFontOfSize:[CPFont systemFontSize]];
|
||||
}
|
||||
|
||||
+ (CPFont)font
|
||||
{
|
||||
return _CPMenuBarWindowFont;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
// This only shows up in browser land, so don't bother calculating metrics in desktop.
|
||||
var contentRect = [[CPPlatformWindow primaryPlatformWindow] contentBounds];
|
||||
|
||||
contentRect.size.height = MENUBAR_HEIGHT;
|
||||
|
||||
self = [super initWithContentRect:contentRect styleMask:CPBorderlessWindowMask];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setLevel:CPMainMenuWindowLevel];
|
||||
[self setAutoresizingMask:CPWindowWidthSizable];
|
||||
|
||||
var contentView = [self contentView];
|
||||
|
||||
[contentView setAutoresizesSubviews:NO];
|
||||
|
||||
[self setBecomesKeyOnlyIfNeeded:YES];
|
||||
|
||||
//
|
||||
_iconImageView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 16.0, 16.0)];
|
||||
|
||||
[contentView addSubview:_iconImageView];
|
||||
|
||||
_titleField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_titleField setFont:[CPFont boldSystemFontOfSize:[CPFont systemFontSize] + 1]];
|
||||
[_titleField setAlignment:CPCenterTextAlignment];
|
||||
[_titleField setTextShadowOffset:CGSizeMake(0, 1)];
|
||||
|
||||
[contentView addSubview:_titleField];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setTitle:(CPString)aTitle
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var bundleName = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPBundleName"];
|
||||
|
||||
if (![bundleName length])
|
||||
document.title = aTitle;
|
||||
else if ([aTitle length])
|
||||
document.title = aTitle + @" - " + bundleName;
|
||||
else
|
||||
document.title = bundleName;
|
||||
#endif
|
||||
|
||||
[_titleField setStringValue:aTitle];
|
||||
[_titleField sizeToFit];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (void)setIconImage:(CPImage)anImage
|
||||
{
|
||||
[_iconImageView setImage:anImage];
|
||||
[_iconImageView setHidden:anImage == nil];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (void)setIconImageAlphaValue:(float)anAlphaValue
|
||||
{
|
||||
[_iconImageView setAlphaValue:anAlphaValue];
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
if (!aColor)
|
||||
{
|
||||
if (!_CPMenuBarWindowBackgroundColor)
|
||||
_CPMenuBarWindowBackgroundColor = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[_CPMenuBarWindow class]] pathForResource:@"_CPMenuBarWindow/_CPMenuBarWindowBackground.png"] size:CGSizeMake(1.0, 28.0)]];
|
||||
|
||||
[[self contentView] setBackgroundColor:_CPMenuBarWindowBackgroundColor];
|
||||
}
|
||||
else
|
||||
[[self contentView] setBackgroundColor:aColor];
|
||||
}
|
||||
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
{
|
||||
if (_textColor == aColor)
|
||||
return;
|
||||
|
||||
_textColor = aColor;
|
||||
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(setTextColor:) withObject:_textColor];
|
||||
}
|
||||
|
||||
- (void)setTitleColor:(CPColor)aColor
|
||||
{
|
||||
if (_titleColor == aColor)
|
||||
return;
|
||||
|
||||
_titleColor = aColor;
|
||||
|
||||
[_titleField setTextColor:aColor ? aColor : [CPColor blackColor]];
|
||||
}
|
||||
|
||||
- (void)setTextShadowColor:(CPColor)aColor
|
||||
{
|
||||
if (_textShadowColor == aColor)
|
||||
return;
|
||||
|
||||
_textShadowColor = aColor;
|
||||
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(setTextShadowColor:) withObject:_textShadowColor];
|
||||
}
|
||||
|
||||
- (void)setTitleShadowColor:(CPColor)aColor
|
||||
{
|
||||
if (_titleShadowColor == aColor)
|
||||
return;
|
||||
|
||||
_titleShadowColor = aColor;
|
||||
|
||||
[_titleField setTextShadowColor:aColor ? aColor : [CPColor whiteColor]];
|
||||
}
|
||||
|
||||
- (void)setHighlightColor:(CPColor)aColor
|
||||
{
|
||||
if (_highlightColor == aColor)
|
||||
return;
|
||||
|
||||
_highlightColor = aColor;
|
||||
}
|
||||
|
||||
- (void)setHighlightTextColor:(CPColor)aColor
|
||||
{
|
||||
if (_highlightTextColor == aColor)
|
||||
return;
|
||||
|
||||
_highlightTextColor = aColor;
|
||||
|
||||
// [_menuItemViews makeObjectsPerformSelector:@selector(setActivateColor:) withObject:_highlightTextColor];
|
||||
}
|
||||
|
||||
- (void)setHighlightTextShadowColor:(CPColor)aColor
|
||||
{
|
||||
if (_highlightTextShadowColor == aColor)
|
||||
return;
|
||||
|
||||
_highlightTextShadowColor = aColor;
|
||||
|
||||
// [_menuItemViews makeObjectsPerformSelector:@selector(setActivateShadowColor:) withObject:_highlightTextShadowColor];
|
||||
}
|
||||
|
||||
- (void)setMenu:(CPMenu)aMenu
|
||||
{
|
||||
if (_menu == aMenu)
|
||||
return;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_menu)
|
||||
{
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPMenuDidAddItemNotification
|
||||
object:_menu];
|
||||
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPMenuDidChangeItemNotification
|
||||
object:_menu];
|
||||
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPMenuDidRemoveItemNotification
|
||||
object:_menu];
|
||||
|
||||
var items = [_menu itemArray],
|
||||
count = items.length;
|
||||
|
||||
while (count--)
|
||||
[[items[count] _menuItemView] removeFromSuperview];
|
||||
}
|
||||
|
||||
_menu = aMenu;
|
||||
|
||||
if (_menu)
|
||||
{
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(menuDidAddItem:)
|
||||
name:CPMenuDidAddItemNotification
|
||||
object:_menu];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(menuDidChangeItem:)
|
||||
name:CPMenuDidChangeItemNotification
|
||||
object:_menu];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(menuDidRemoveItem:)
|
||||
name:CPMenuDidRemoveItemNotification
|
||||
object:_menu];
|
||||
}
|
||||
|
||||
_menuItemViews = [];
|
||||
|
||||
var contentView = [self contentView],
|
||||
items = [_menu itemArray],
|
||||
count = items.length;
|
||||
|
||||
for (var index = 0; index < count; ++index)
|
||||
{
|
||||
var item = items[index],
|
||||
menuItemView = [item _menuItemView];
|
||||
|
||||
_menuItemViews.push(menuItemView);
|
||||
|
||||
[menuItemView setTextColor:_textColor];
|
||||
[menuItemView setHidden:[item isHidden]];
|
||||
|
||||
[menuItemView synchronizeWithMenuItem];
|
||||
|
||||
[contentView addSubview:menuItemView];
|
||||
}
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (void)menuDidChangeItem:(CPNotification)aNotification
|
||||
{
|
||||
var menuItem = [_menu itemAtIndex:[[aNotification userInfo] objectForKey:@"CPMenuItemIndex"]],
|
||||
menuItemView = [menuItem _menuItemView];
|
||||
|
||||
[menuItemView setHidden:[menuItem isHidden]];
|
||||
[menuItemView synchronizeWithMenuItem];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (void)menuDidAddItem:(CPNotification)aNotification
|
||||
{
|
||||
var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"],
|
||||
menuItem = [_menu itemAtIndex:index],
|
||||
menuItemView = [menuItem _menuItemView];
|
||||
|
||||
[_menuItemViews insertObject:menuItemView atIndex:index];
|
||||
|
||||
[menuItemView setTextColor:_textColor];
|
||||
[menuItemView setHidden:[menuItem isHidden]];
|
||||
|
||||
[menuItemView synchronizeWithMenuItem];
|
||||
|
||||
[[self contentView] addSubview:menuItemView];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (void)menuDidRemoveItem:(CPNotification)aNotification
|
||||
{
|
||||
var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"],
|
||||
menuItemView = [_menuItemViews objectAtIndex:index];
|
||||
|
||||
[_menuItemViews removeObjectAtIndex:index];
|
||||
|
||||
[menuItemView removeFromSuperview];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
var constraintRect = CGRectInset([[self platformWindow] visibleFrame], 5.0, 0.0);
|
||||
|
||||
constraintRect.size.height -= 5.0;
|
||||
|
||||
[[_CPMenuManager sharedMenuManager]
|
||||
beginTracking:anEvent
|
||||
menuContainer:self
|
||||
constraintRect:constraintRect
|
||||
callback:function(aMenuContainer, aMenu)
|
||||
{
|
||||
[aMenu _performActionOfHighlightedItemChain];
|
||||
[aMenu _highlightItemAtIndex:CPNotFound];
|
||||
}];
|
||||
}
|
||||
|
||||
- (CPFont)font
|
||||
{
|
||||
[CPFont systemFontOfSize:[CPFont systemFontSize]];
|
||||
}
|
||||
|
||||
- (void)tile
|
||||
{
|
||||
var items = [_menu itemArray],
|
||||
index = 0,
|
||||
count = items.length,
|
||||
|
||||
x = MENUBAR_LEFT_MARGIN,
|
||||
y = 0.0,
|
||||
isLeftAligned = YES;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var item = items[index];
|
||||
|
||||
if ([item isSeparatorItem])
|
||||
{
|
||||
x = CGRectGetWidth([self frame]) - MENUBAR_RIGHT_MARGIN;
|
||||
isLeftAligned = NO;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([item isHidden])
|
||||
continue;
|
||||
|
||||
var menuItemView = [item _menuItemView],
|
||||
frame = [menuItemView frame];
|
||||
|
||||
if (isLeftAligned)
|
||||
{
|
||||
[menuItemView setFrame:CGRectMake(x, 0.0, CGRectGetWidth(frame), MENUBAR_HEIGHT)];
|
||||
|
||||
x += CGRectGetWidth([menuItemView frame]);
|
||||
}
|
||||
else
|
||||
{
|
||||
[menuItemView setFrame:CGRectMake(x - CGRectGetWidth(frame), 0.0, CGRectGetWidth(frame), MENUBAR_HEIGHT)];
|
||||
|
||||
x = CGRectGetMinX([menuItemView frame]);
|
||||
}
|
||||
}
|
||||
|
||||
var bounds = [[self contentView] bounds],
|
||||
titleFrame = [_titleField frame];
|
||||
|
||||
if ([_iconImageView isHidden])
|
||||
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(titleFrame)) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
|
||||
else
|
||||
{
|
||||
var iconFrame = [_iconImageView frame],
|
||||
iconWidth = CGRectGetWidth(iconFrame),
|
||||
totalWidth = iconWidth + CGRectGetWidth(titleFrame);
|
||||
|
||||
[_iconImageView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(iconFrame)) / 2.0)];
|
||||
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0 + iconWidth, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)aRect display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate
|
||||
{
|
||||
var size = [self frame].size;
|
||||
|
||||
[super setFrame:aRect display:shouldDisplay animate:shouldAnimate];
|
||||
|
||||
if (!_CGSizeEqualToSize(size, aRect.size))
|
||||
[self tile];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMenuBarWindow (CPMenuContainer)
|
||||
|
||||
- (BOOL)isMenuBar
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CGRect)globalFrame
|
||||
{
|
||||
return [self frame];
|
||||
}
|
||||
|
||||
- (_CPManagerScrollingState)scrollingStateForPoint:(CGPoint)aGlobalLocation
|
||||
{
|
||||
return _CPMenuManagerScrollingStateNone;
|
||||
}
|
||||
|
||||
- (CPInteger)itemIndexAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
var items = [_menu itemArray],
|
||||
index = items.length;
|
||||
|
||||
while (index--)
|
||||
{
|
||||
var item = items[index];
|
||||
|
||||
if ([item isHidden] || [item isSeparatorItem])
|
||||
continue;
|
||||
|
||||
if (CGRectContainsPoint([self rectForItemAtIndex:index], aPoint))
|
||||
return index;
|
||||
}
|
||||
|
||||
return CPNotFound;
|
||||
}
|
||||
|
||||
- (CGRect)rectForItemAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
var menuItem = [_menu itemAtIndex:anIndex === CPNotFound ? 0 : anIndex];
|
||||
|
||||
return [[menuItem _menuItemView] frame];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMenuBarView : _CPMenuView
|
||||
{
|
||||
}
|
||||
|
||||
- (CGRect)rectForItemAtIndex:(int)anIndex
|
||||
{
|
||||
return [_menuItemViews[anIndex === CPNotFound ? 0 : anIndex] frame];
|
||||
}
|
||||
|
||||
- (int)itemIndexAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
var bounds = [self bounds];
|
||||
|
||||
if (!CGRectContainsPoint(bounds, aPoint))
|
||||
return CPNotFound;
|
||||
|
||||
var x = aPoint.x,
|
||||
low = 0,
|
||||
high = _visibleMenuItemInfos.length - 1;
|
||||
|
||||
while (low <= high)
|
||||
{
|
||||
var middle = FLOOR(low + (high - low) / 2),
|
||||
info = _visibleMenuItemInfos[middle],
|
||||
frame = [info.view frame];
|
||||
|
||||
if (x < CGRectGetMinX(frame))
|
||||
high = middle - 1;
|
||||
|
||||
else if (x > CGRectGetMaxX(frame))
|
||||
low = middle + 1;
|
||||
|
||||
else
|
||||
return info.index;
|
||||
}
|
||||
|
||||
return CPNotFound;
|
||||
}
|
||||
|
||||
- (void)tile
|
||||
{
|
||||
var items = [_menu itemArray],
|
||||
index = 0,
|
||||
count = items.length,
|
||||
|
||||
x = MENUBAR_LEFT_MARGIN,
|
||||
y = 0.0,
|
||||
isLeftAligned = YES;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var item = items[index];
|
||||
|
||||
if ([item isSeparatorItem])
|
||||
{
|
||||
x = CGRectGetWidth([self frame]) - MENUBAR_RIGHT_MARGIN;
|
||||
isLeftAligned = NO;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([item isHidden])
|
||||
continue;
|
||||
|
||||
var menuItemView = [item _menuItemView],
|
||||
frame = [menuItemView frame];
|
||||
|
||||
if (isLeftAligned)
|
||||
{
|
||||
[menuItemView setFrameOrigin:CGPointMake(x, (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)];
|
||||
|
||||
x += CGRectGetWidth([menuItemView frame]) + MENUBAR_MARGIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
[menuItemView setFrameOrigin:CGPointMake(x - CGRectGetWidth(frame), (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)];
|
||||
|
||||
x = CGRectGetMinX([menuItemView frame]) - MENUBAR_MARGIN;
|
||||
}
|
||||
}
|
||||
|
||||
var bounds = [[self contentView] bounds],
|
||||
titleFrame = [_titleField frame];
|
||||
|
||||
if ([_iconImageView isHidden])
|
||||
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(titleFrame)) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
|
||||
else
|
||||
{
|
||||
var iconFrame = [_iconImageView frame],
|
||||
iconWidth = CGRectGetWidth(iconFrame),
|
||||
totalWidth = iconWidth + CGRectGetWidth(titleFrame);
|
||||
|
||||
[_iconImageView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(iconFrame)) / 2.0)];
|
||||
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0 + iconWidth, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,703 +0,0 @@
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
|
||||
_CPMenuManagerScrollingStateUp = -1;
|
||||
_CPMenuManagerScrollingStateDown = 1;
|
||||
_CPMenuManagerScrollingStateNone = 0;
|
||||
|
||||
var STICKY_TIME_INTERVAL = 0.5,
|
||||
SharedMenuManager = nil;
|
||||
|
||||
@implementation _CPMenuManager: CPObject
|
||||
{
|
||||
CPTimeInterval _startTime;
|
||||
BOOL _mouseWasDragged;
|
||||
int _scrollingState;
|
||||
CGPoint _lastGlobalLocation;
|
||||
|
||||
CPView _lastMouseOverMenuView;
|
||||
|
||||
CGRect _constraintRect;
|
||||
CPMutableArray _menuContainerStack;
|
||||
|
||||
Function _trackingCallback;
|
||||
|
||||
CPString _keyBuffer;
|
||||
|
||||
CPMenuItem _previousActiveItem;
|
||||
int _showTimerID;
|
||||
}
|
||||
|
||||
+ (_CPMenuManager)sharedMenuManager
|
||||
{
|
||||
if (!SharedMenuManager)
|
||||
SharedMenuManager = [[_CPMenuManager alloc] init];
|
||||
|
||||
return SharedMenuManager;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (SharedMenuManager)
|
||||
return SharedMenuManager;
|
||||
|
||||
return [super init];
|
||||
}
|
||||
|
||||
- (id)trackingMenuContainer
|
||||
{
|
||||
return _menuContainerStack[0];
|
||||
}
|
||||
|
||||
- (CPMenu)trackingMenu
|
||||
{
|
||||
return [[self trackingMenuContainer] menu];
|
||||
}
|
||||
|
||||
- (void)beginTracking:(CPEvent)anEvent
|
||||
menuContainer:(id)aMenuContainer
|
||||
constraintRect:(CGRect)aRect
|
||||
callback:(Function)aCallback
|
||||
{
|
||||
var menu = [aMenuContainer menu];
|
||||
|
||||
if ([menu numberOfItems] <= 0)
|
||||
return;
|
||||
|
||||
CPApp._activeMenu = menu;
|
||||
|
||||
_startTime = [anEvent timestamp];
|
||||
_scrollingState = _CPMenuManagerScrollingStateNone;
|
||||
|
||||
_constraintRect = aRect;
|
||||
_menuContainerStack = [aMenuContainer];
|
||||
_trackingCallback = aCallback;
|
||||
|
||||
if (menu === [CPApp mainMenu])
|
||||
{
|
||||
var globalLocation = [anEvent globalLocation],
|
||||
|
||||
// Find which menu window the mouse is currently on top of
|
||||
menuLocation = [aMenuContainer convertGlobalToBase:globalLocation],
|
||||
|
||||
// Find out the item the mouse is currently on top of
|
||||
activeItemIndex = [aMenuContainer itemIndexAtPoint:menuLocation],
|
||||
activeItem = activeItemIndex !== CPNotFound ? [menu itemAtIndex:activeItemIndex] : nil;
|
||||
|
||||
_menuBarButtonItemIndex = activeItemIndex;
|
||||
_menuBarButtonMenuContainer = aMenuContainer;
|
||||
|
||||
if ([activeItem _isMenuBarButton])
|
||||
return [self trackMenuBarButtonEvent:anEvent];
|
||||
}
|
||||
|
||||
_mouseWasDragged = NO;
|
||||
|
||||
[self trackEvent:anEvent];
|
||||
}
|
||||
|
||||
- (void)trackEvent:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type],
|
||||
trackingMenu = [self trackingMenu];
|
||||
|
||||
// Close Menu Event.
|
||||
if (type === CPAppKitDefined)
|
||||
return [self completeTracking];
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackEvent:) forNextEventMatchingMask:CPKeyDownMask | CPPeriodicMask | CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPRightMouseUpMask | CPAppKitDefinedMask | CPScrollWheelMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
if (type === CPKeyDown)
|
||||
{
|
||||
var menu = trackingMenu,
|
||||
submenu = [[menu highlightedItem] submenu];
|
||||
|
||||
// get the current active menu
|
||||
while (submenu && [submenu._menuWindow isVisible])
|
||||
{
|
||||
menu = submenu;
|
||||
submenu = [[menu highlightedItem] submenu];
|
||||
}
|
||||
|
||||
if ([menu numberOfItems])
|
||||
[self interpretKeyEvent:anEvent forMenu:menu];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_keyBuffer)
|
||||
{
|
||||
if (([anEvent timestamp] - _startTime) > (STICKY_TIME_INTERVAL + [activeMenu numberOfItems] / 2))
|
||||
[self selectNextItemBeginningWith:_keyBuffer inMenu:menu clearBuffer:YES];
|
||||
|
||||
if (type === CPPeriodic)
|
||||
return;
|
||||
}
|
||||
|
||||
// Periodic events don't have a valid location.
|
||||
var globalLocation = type === CPPeriodic ? _lastGlobalLocation : [anEvent globalLocation];
|
||||
|
||||
if (!globalLocation)
|
||||
return;
|
||||
|
||||
// Find which menu window the mouse is currently on top of
|
||||
var activeMenuContainer = [self menuContainerForPoint:globalLocation],
|
||||
activeMenu = [activeMenuContainer menu],
|
||||
menuLocation = [activeMenuContainer convertGlobalToBase:globalLocation],
|
||||
|
||||
// Find out the item the mouse is currently on top of
|
||||
activeItemIndex = activeMenuContainer ? [activeMenuContainer itemIndexAtPoint:menuLocation] : CPNotFound,
|
||||
activeItem = activeItemIndex !== CPNotFound ? [activeMenu itemAtIndex:activeItemIndex] : nil;
|
||||
|
||||
// unhighlight when mouse is moved off the menu
|
||||
if (_lastGlobalLocation && CGRectContainsPoint([activeMenuContainer globalFrame], _lastGlobalLocation)
|
||||
&& !CGRectContainsPoint([activeMenuContainer globalFrame], globalLocation))
|
||||
[activeMenu _highlightItemAtIndex:CPNotFound];
|
||||
|
||||
// Remember this for the next periodic event.
|
||||
_lastGlobalLocation = globalLocation;
|
||||
|
||||
// If the item isn't enabled its as if we clicked on nothing.
|
||||
if (![activeItem isEnabled] || [activeItem _isMenuBarButton])
|
||||
{
|
||||
activeItemIndex = CPNotFound;
|
||||
activeItem = nil;
|
||||
}
|
||||
|
||||
var mouseOverMenuView = [activeItem view];
|
||||
|
||||
if (type === CPScrollWheel && ![activeMenuContainer isMenuBar])
|
||||
[activeMenuContainer scrollByDelta:[anEvent deltaY]];
|
||||
|
||||
if (type === CPPeriodic)
|
||||
{
|
||||
if (_scrollingState === _CPMenuManagerScrollingStateUp)
|
||||
[activeMenuContainer scrollUp];
|
||||
else if (_scrollingState === _CPMenuManagerScrollingStateDown)
|
||||
[activeMenuContainer scrollDown];
|
||||
}
|
||||
|
||||
// If we're over a custom menu view...
|
||||
if (mouseOverMenuView)
|
||||
{
|
||||
if (!_lastMouseOverMenuView)
|
||||
[activeMenu _highlightItemAtIndex:CPNotFound];
|
||||
|
||||
if (_lastMouseOverMenuView != mouseOverMenuView)
|
||||
{
|
||||
[mouseOverMenuView mouseExited:anEvent];
|
||||
// FIXME: Possibly multiple of these?
|
||||
[_lastMouseOverMenuView mouseEntered:anEvent];
|
||||
|
||||
_lastMouseOverMenuView = mouseOverMenuView;
|
||||
}
|
||||
|
||||
var menuContainerWindow = activeMenuContainer;
|
||||
|
||||
if (![menuContainerWindow isKindOfClass:CPWindow])
|
||||
menuContainerWindow = [menuContainerWindow window];
|
||||
|
||||
[menuContainerWindow
|
||||
sendEvent:[CPEvent mouseEventWithType:type
|
||||
location:menuLocation
|
||||
modifierFlags:[anEvent modifierFlags]
|
||||
timestamp:[anEvent timestamp]
|
||||
windowNumber:menuContainerWindow
|
||||
context:nil
|
||||
eventNumber:0
|
||||
clickCount:[anEvent clickCount]
|
||||
pressure:[anEvent pressure]]];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_lastMouseOverMenuView)
|
||||
{
|
||||
[_lastMouseOverMenuView mouseExited:anEvent];
|
||||
_lastMouseOverMenuView = nil;
|
||||
}
|
||||
|
||||
if (activeItemIndex != CPNotFound)
|
||||
[activeMenu _highlightItemAtIndex:activeItemIndex];
|
||||
|
||||
if (type === CPMouseMoved || type === CPLeftMouseDragged || type === CPLeftMouseDown || type === CPPeriodic)
|
||||
{
|
||||
if (type === CPLeftMouseDragged)
|
||||
_mouseWasDragged = YES;
|
||||
|
||||
var oldScrollingState = _scrollingState;
|
||||
|
||||
_scrollingState = [activeMenuContainer scrollingStateForPoint:globalLocation];
|
||||
|
||||
if (_scrollingState !== oldScrollingState)
|
||||
{
|
||||
if (_scrollingState === _CPMenuManagerScrollingStateNone)
|
||||
[CPEvent stopPeriodicEvents];
|
||||
else if (oldScrollingState === _CPMenuManagerScrollingStateNone)
|
||||
[CPEvent startPeriodicEventsAfterDelay:0.0 withPeriod:0.04];
|
||||
}
|
||||
}
|
||||
else if (type === CPLeftMouseUp || type === CPRightMouseUp)
|
||||
{
|
||||
/*
|
||||
There are a few possibilites:
|
||||
|
||||
1. The user clicks and releases without dragging within the sticky time.
|
||||
This is considered a regular mouse click and not a drag. In this case
|
||||
we allow the user to track the menu by moving the mouse. The next
|
||||
mouse up will end tracking.
|
||||
|
||||
2. The user clicks and releases without dragging after the sticky time.
|
||||
This is considered a drag and release and tracking ends.
|
||||
|
||||
3. The user clicks, drags and then releases. Tracking ends.
|
||||
*/
|
||||
if (_mouseWasDragged || ([anEvent timestamp] - _startTime > STICKY_TIME_INTERVAL))
|
||||
{
|
||||
/*
|
||||
Close the menu if:
|
||||
|
||||
1. The mouse was dragged.
|
||||
2. The mouse is released in the menubar.
|
||||
3. The current item has a submenu with a custom action.
|
||||
*/
|
||||
if (_mouseWasDragged ||
|
||||
[activeMenuContainer isMenuBar] ||
|
||||
[activeItem action] !== @selector(submenuAction:))
|
||||
{
|
||||
[trackingMenu cancelTracking];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent previous selected menu items from opening by stopping the timer if a
|
||||
// new item is selected before the timer runs out
|
||||
if (_previousActiveItem !== activeItem)
|
||||
{
|
||||
clearTimeout(_showTimerID);
|
||||
_showTimerID = undefined;
|
||||
}
|
||||
|
||||
// If the item has a submenu, show it.
|
||||
if ([activeItem hasSubmenu]) // && [activeItem action] === @selector(submenuAction:))
|
||||
{
|
||||
var activeItemRect = [activeMenuContainer rectForItemAtIndex:activeItemIndex],
|
||||
newMenuOrigin;
|
||||
|
||||
if ([activeMenuContainer isMenuBar])
|
||||
newMenuOrigin = CGPointMake(CGRectGetMinX(activeItemRect), CGRectGetMaxY(activeItemRect));
|
||||
else
|
||||
newMenuOrigin = CGPointMake(CGRectGetMaxX(activeItemRect), CGRectGetMinY(activeItemRect));
|
||||
|
||||
newMenuOrigin = [activeMenuContainer convertBaseToGlobal:newMenuOrigin];
|
||||
|
||||
// Only start a new timer if the previous was cancelled
|
||||
if (_showTimerID === undefined)
|
||||
{
|
||||
// Close the current menu item because we are going to select a new one after a short delay
|
||||
[self showMenu:nil fromMenu:activeMenu atPoint:CGPointMakeZero()];
|
||||
|
||||
if (![activeMenuContainer isMenuBar])
|
||||
{
|
||||
_showTimerID = setTimeout(function()
|
||||
{
|
||||
[self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:newMenuOrigin];
|
||||
}, 250);
|
||||
}
|
||||
else
|
||||
[self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:newMenuOrigin];
|
||||
}
|
||||
}
|
||||
|
||||
// This handles both the case where we've moved away from the menu, and where
|
||||
// we've moved to an item without a submenu.
|
||||
else
|
||||
[self showMenu:nil fromMenu:activeMenu atPoint:CGPointMakeZero()];
|
||||
|
||||
_previousActiveItem = activeItem;
|
||||
}
|
||||
|
||||
- (void)trackMenuBarButtonEvent:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type];
|
||||
|
||||
if (type === CPAppKitDefined)
|
||||
return [self completeTracking];
|
||||
|
||||
var globalLocation = [anEvent globalLocation],
|
||||
|
||||
// Find which menu window the mouse is currently on top of
|
||||
menu = [self trackingMenu],
|
||||
trackingMenuContainer = [self trackingMenuContainer],
|
||||
menuLocation = [trackingMenuContainer convertGlobalToBase:globalLocation];
|
||||
|
||||
if ([trackingMenuContainer itemIndexAtPoint:menuLocation] === _menuBarButtonItemIndex)
|
||||
[menu _highlightItemAtIndex:_menuBarButtonItemIndex];
|
||||
else
|
||||
[menu _highlightItemAtIndex:CPNotFound];
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackMenuBarButtonEvent:) forNextEventMatchingMask:CPPeriodicMask | CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPAppKitDefinedMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
if (type === CPLeftMouseUp)
|
||||
[menu cancelTracking];
|
||||
}
|
||||
|
||||
- (void)completeTracking
|
||||
{
|
||||
var trackingMenu = [self trackingMenu];
|
||||
|
||||
// Stop all periodic events at this point.
|
||||
[CPEvent stopPeriodicEvents];
|
||||
|
||||
// Hide all submenus.
|
||||
[self showMenu:nil fromMenu:trackingMenu atPoint:nil];
|
||||
[trackingMenu _menuDidClose];
|
||||
|
||||
if (_trackingCallback)
|
||||
_trackingCallback([self trackingMenuContainer], trackingMenu);
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPMenuDidEndTrackingNotification
|
||||
object:trackingMenu];
|
||||
|
||||
CPApp._activeMenu = nil;
|
||||
}
|
||||
|
||||
- (id)menuContainerForPoint:(float)aGlobalLocation
|
||||
{
|
||||
var count = [_menuContainerStack count],
|
||||
firstMenuContainer = _menuContainerStack[0];
|
||||
|
||||
// Trivial case.
|
||||
if (count === 1)
|
||||
return firstMenuContainer;
|
||||
|
||||
var firstMenuWindowIndex = 0,
|
||||
lastMenuWindowIndex = count - 1;
|
||||
|
||||
// If the first menu container is a bar, then it has to fall strictly within its bounds.
|
||||
if ([firstMenuContainer isMenuBar])
|
||||
{
|
||||
if (CGRectContainsPoint([firstMenuContainer globalFrame], aGlobalLocation))
|
||||
return firstMenuContainer;
|
||||
|
||||
firstMenuWindowIndex = 1;
|
||||
}
|
||||
|
||||
var index = count,
|
||||
x = aGlobalLocation.x,
|
||||
closerDeltaX = Infinity,
|
||||
closerMenuContainer = nil;
|
||||
|
||||
while (index-- > firstMenuWindowIndex)
|
||||
{
|
||||
var menuContainer = _menuContainerStack[index],
|
||||
menuContainerFrame = [menuContainer globalFrame],
|
||||
menuContainerMinX = _CGRectGetMinX(menuContainerFrame),
|
||||
menuContainerMaxX = _CGRectGetMaxX(menuContainerFrame);
|
||||
|
||||
// If within the x bounds of this menu container, return it.
|
||||
if (x < menuContainerMaxX && x >= menuContainerMinX)
|
||||
return menuContainer;
|
||||
|
||||
// If this is either the first or last menu *window*, check to see how close we are to it.
|
||||
if (index === firstMenuWindowIndex || index === lastMenuWindowIndex)
|
||||
{
|
||||
var deltaX = ABS(x < menuContainerMinX ? menuContainerMinX - x : menuContainerMaxX - x);
|
||||
|
||||
if (deltaX < closerDeltaX)
|
||||
{
|
||||
closerMenuContainer = menuContainer;
|
||||
closerDeltaX = deltaX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closerMenuContainer;
|
||||
}
|
||||
|
||||
- (void)showMenu:(CPMenu)newMenu fromMenu:(CPMenu)baseMenu atPoint:(CGPoint)aGlobalLocation
|
||||
{
|
||||
var count = _menuContainerStack.length,
|
||||
index = count;
|
||||
|
||||
[newMenu _menuWillOpen];
|
||||
|
||||
// Hide all menus up to the base menu...
|
||||
while (index--)
|
||||
{
|
||||
var menuContainer = _menuContainerStack[index],
|
||||
menu = [menuContainer menu];
|
||||
|
||||
// If we reach the base menu, or this menu is already being shown, break.
|
||||
if (menu === baseMenu)
|
||||
break;
|
||||
|
||||
// If this menu is already being shown, unhighlight and return.
|
||||
if (menu === newMenu) //&& [menu supermenu] === baseMenu)
|
||||
return [newMenu _highlightItemAtIndex:CPNotFound];
|
||||
|
||||
[menuContainer orderOut:self];
|
||||
[menuContainer setMenu:nil];
|
||||
|
||||
[_CPMenuWindow poolMenuWindow:menuContainer];
|
||||
[_menuContainerStack removeObjectAtIndex:index];
|
||||
|
||||
[menu _menuDidClose];
|
||||
}
|
||||
|
||||
if (!newMenu)
|
||||
return;
|
||||
|
||||
// Unhighlight any previously highlighted item.
|
||||
[newMenu _highlightItemAtIndex:CPNotFound];
|
||||
|
||||
var menuWindow = [_CPMenuWindow menuWindowWithMenu:newMenu font:[_menuContainerStack[0] font]];
|
||||
|
||||
[_menuContainerStack addObject:menuWindow];
|
||||
|
||||
[menuWindow setConstraintRect:_constraintRect];
|
||||
|
||||
// If our parent menu is a menu bar...
|
||||
if (baseMenu === [self trackingMenu] && [[self trackingMenuContainer] isMenuBar])
|
||||
[menuWindow setBackgroundStyle:_CPMenuWindowMenuBarBackgroundStyle];
|
||||
else
|
||||
[menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
|
||||
|
||||
[menuWindow setFrameOrigin:aGlobalLocation];
|
||||
[menuWindow orderFront:self];
|
||||
}
|
||||
|
||||
/// handle keyboard navigation
|
||||
- (void)interpretKeyEvent:(CPEvent)anEvent forMenu:(CPMenu)menu
|
||||
{
|
||||
var modifierFlags = [anEvent modifierFlags],
|
||||
character = [anEvent charactersIgnoringModifiers],
|
||||
selectorNames = [CPKeyBinding selectorsForKey:character modifierFlags:modifierFlags];
|
||||
|
||||
if (selectorNames)
|
||||
{
|
||||
var iter = [selectorNames objectEnumerator],
|
||||
obj;
|
||||
|
||||
while ((obj = [iter nextObject]) !== nil)
|
||||
{
|
||||
var aSelector = CPSelectorFromString(obj);
|
||||
|
||||
if ([self respondsToSelector:aSelector])
|
||||
[self performSelector:aSelector withObject:menu];
|
||||
}
|
||||
}
|
||||
else if (!(modifierFlags & (CPCommandKeyMask | CPControlKeyMask)))
|
||||
{
|
||||
if (!_keyBuffer)
|
||||
{
|
||||
_startTime = [anEvent timestamp];
|
||||
_keyBuffer = character;
|
||||
|
||||
[CPEvent stopPeriodicEvents];
|
||||
[CPEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.1];
|
||||
}
|
||||
else
|
||||
_keyBuffer += character;
|
||||
|
||||
[self selectNextItemBeginningWith:_keyBuffer inMenu:menu clearBuffer:NO];
|
||||
_lastGlobalLocation = Nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)selectNextItemBeginningWith:(CPString)characters inMenu:(CPMenu)menu clearBuffer:(BOOL)shouldClear
|
||||
{
|
||||
var iter = [[menu itemArray] objectEnumerator],
|
||||
obj;
|
||||
|
||||
while ((obj = [iter nextObject]) !== nil)
|
||||
{
|
||||
if ([obj isHidden] || ![obj isEnabled])
|
||||
continue;
|
||||
|
||||
if ([[[obj title] commonPrefixWithString:characters options:CPCaseInsensitiveSearch] length] == [characters length])
|
||||
{
|
||||
[menu _highlightItemAtIndex:iter._index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldClear)
|
||||
{
|
||||
[CPEvent stopPeriodicEvents];
|
||||
_keyBuffer = Nil;
|
||||
}
|
||||
else
|
||||
_startTime = [CPEvent currentTimestamp];
|
||||
}
|
||||
|
||||
- (void)scrollToBeginningOfDocument:(CPMenu)menu
|
||||
{
|
||||
[menu _highlightItemAtIndex:0];
|
||||
}
|
||||
|
||||
- (void)scrollToEndOfDocument:(CPMenu)menu
|
||||
{
|
||||
[menu _highlightItemAtIndex:[menu numberOfItems] - 1];
|
||||
}
|
||||
|
||||
- (void)scrollPageDown:(CPMenu)menu
|
||||
{
|
||||
var menuWindow = menu._menuWindow,
|
||||
menuClipView = menuWindow._menuClipView,
|
||||
bottom = [menuClipView bounds].size.height,
|
||||
first = [menuWindow itemIndexAtPoint:CGPointMake(1, 10)],
|
||||
last = [menuWindow itemIndexAtPoint:CGPointMake(1, bottom)],
|
||||
current = [menu indexOfItem:[menu highlightedItem]];
|
||||
|
||||
if (current == CPNotFound)
|
||||
{
|
||||
[menu _highlightItemAtIndex:0];
|
||||
return;
|
||||
}
|
||||
|
||||
next = current + (last - first);
|
||||
|
||||
if (next < [menu numberOfItems])
|
||||
[menu _highlightItemAtIndex:next];
|
||||
else
|
||||
[menu _highlightItemAtIndex:[menu numberOfItems] - 1];
|
||||
|
||||
var item = [menu highlightedItem];
|
||||
|
||||
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
|
||||
[self moveDown:menu];
|
||||
}
|
||||
|
||||
- (void)scrollPageUp:(CPMenu)menu
|
||||
{
|
||||
var menuWindow = menu._menuWindow,
|
||||
menuClipView = menuWindow._menuClipView,
|
||||
bottom = [menuClipView bounds].size.height,
|
||||
first = [menuWindow itemIndexAtPoint:CGPointMake(1, 10)],
|
||||
last = [menuWindow itemIndexAtPoint:CGPointMake(1, bottom)],
|
||||
current = [menu indexOfItem:[menu highlightedItem]];
|
||||
|
||||
if (current == CPNotFound)
|
||||
{
|
||||
[menu _highlightItemAtIndex:0];
|
||||
return;
|
||||
}
|
||||
|
||||
next = current - (last - first);
|
||||
|
||||
if (next < 0)
|
||||
[menu _highlightItemAtIndex:0];
|
||||
else
|
||||
[menu _highlightItemAtIndex:next];
|
||||
|
||||
var item = [menu highlightedItem];
|
||||
|
||||
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
|
||||
[self moveUp:menu];
|
||||
}
|
||||
|
||||
- (void)moveLeft:(CPMenu)menu
|
||||
{
|
||||
if ([menu supermenu])
|
||||
{
|
||||
if ([menu supermenu] == [CPApp mainMenu])
|
||||
{
|
||||
[self showMenu:nil fromMenu:[menu supermenu] atPoint:CGPointMakeZero()];
|
||||
[self moveUp:[CPApp mainMenu]];
|
||||
|
||||
var activeItem = [[CPApp mainMenu] highlightedItem],
|
||||
menuLocation = CGPointMake([[activeItem _menuItemView] frameOrigin].x , [[activeItem _menuItemView] frameSize].height);
|
||||
|
||||
[self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:menuLocation];
|
||||
}
|
||||
else
|
||||
[self showMenu:nil fromMenu:[menu supermenu] atPoint:CGPointMakeZero()];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveRight:(CPMenu)menu
|
||||
{
|
||||
var activeItem = [menu highlightedItem];
|
||||
|
||||
if ([activeItem hasSubmenu])
|
||||
{
|
||||
if ([[activeItem submenu] numberOfItems])
|
||||
{
|
||||
var activeItemIndex = [menu indexOfItem:activeItem],
|
||||
activeMenuContainer = menu._menuWindow,
|
||||
activeItemRect = [activeMenuContainer rectForItemAtIndex:activeItemIndex],
|
||||
newMenuOrigin;
|
||||
|
||||
if ([activeMenuContainer isMenuBar])
|
||||
newMenuOrigin = CGPointMake(CGRectGetMinX(activeItemRect), CGRectGetMaxY(activeItemRect));
|
||||
else
|
||||
newMenuOrigin = CGPointMake(CGRectGetMaxX(activeItemRect), CGRectGetMinY(activeItemRect));
|
||||
|
||||
newMenuOrigin = [activeMenuContainer convertBaseToGlobal:newMenuOrigin];
|
||||
|
||||
[self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:newMenuOrigin];
|
||||
[self moveDown:[activeItem submenu]];
|
||||
}
|
||||
}
|
||||
else if ([self trackingMenu] == [CPApp mainMenu])
|
||||
{
|
||||
[self showMenu:nil fromMenu:menu atPoint:CGPointMakeZero()];
|
||||
[self moveDown:[CPApp mainMenu]];
|
||||
|
||||
var activeItem = [[CPApp mainMenu] highlightedItem],
|
||||
menuLocation = CGPointMake([[activeItem _menuItemView] frameOrigin].x , [[activeItem _menuItemView] frameSize].height);
|
||||
|
||||
[self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:menuLocation];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveDown:(CPMenu)menu
|
||||
{
|
||||
var index = menu._highlightedIndex + 1;
|
||||
|
||||
if (index < [menu numberOfItems])
|
||||
{
|
||||
[menu _highlightItemAtIndex:index];
|
||||
|
||||
var item = [menu highlightedItem];
|
||||
|
||||
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
|
||||
[self moveDown:menu];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveUp:(CPMenu)menu
|
||||
{
|
||||
var index = menu._highlightedIndex - 1;
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
if (index != CPNotFound)
|
||||
[menu _highlightItemAtIndex:[menu numberOfItems] - 1];
|
||||
return;
|
||||
}
|
||||
[menu _highlightItemAtIndex:index];
|
||||
|
||||
var item = [menu highlightedItem];
|
||||
|
||||
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
|
||||
[self moveUp:menu];
|
||||
}
|
||||
|
||||
- (void)insertNewline:(CPMenu)menu
|
||||
{
|
||||
if ([[menu highlightedItem] hasSubmenu])
|
||||
[self moveRight:menu];
|
||||
else
|
||||
[menu cancelTracking]
|
||||
}
|
||||
|
||||
- (void)cancelOperation:(CPMenu)menu
|
||||
{
|
||||
[menu _highlightItemAtIndex:CPNotFound];
|
||||
[CPEvent stopPeriodicEvents];
|
||||
[[self trackingMenu] cancelTracking];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,560 +0,0 @@
|
||||
@import "CPWindow.j"
|
||||
|
||||
|
||||
var _CPMenuWindowPool = [],
|
||||
_CPMenuWindowPoolCapacity = 5,
|
||||
|
||||
_CPMenuWindowBackgroundColors = [];
|
||||
|
||||
_CPMenuWindowMenuBarBackgroundStyle = 0;
|
||||
_CPMenuWindowPopUpBackgroundStyle = 1;
|
||||
_CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
var STICKY_TIME_INTERVAL = 500,
|
||||
|
||||
TOP_MARGIN = 5.0,
|
||||
LEFT_MARGIN = 1.0,
|
||||
RIGHT_MARGIN = 1.0,
|
||||
BOTTOM_MARGIN = 5.0,
|
||||
|
||||
SCROLL_INDICATOR_HEIGHT = 16.0;
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
@implementation _CPMenuWindow : CPWindow
|
||||
{
|
||||
_CPMenuView _menuView;
|
||||
CPClipView _menuClipView;
|
||||
|
||||
CPImageView _moreAboveView;
|
||||
CPImageView _moreBelowView;
|
||||
|
||||
CGRect _unconstrainedFrame;
|
||||
CGRect _constraintRect;
|
||||
}
|
||||
|
||||
+ (id)menuWindowWithMenu:(CPMenu)aMenu font:(CPFont)aFont
|
||||
{
|
||||
var menuWindow = nil;
|
||||
|
||||
if (_CPMenuWindowPool.length)
|
||||
{
|
||||
menuWindow = _CPMenuWindowPool.pop();
|
||||
|
||||
// Do this so that coordinates will be accurate.
|
||||
[menuWindow setFrameOrigin:CGPointMakeZero()];
|
||||
}
|
||||
else
|
||||
menuWindow = [[_CPMenuWindow alloc] init];
|
||||
|
||||
[menuWindow setFont:aFont];
|
||||
[menuWindow setMenu:aMenu];
|
||||
[menuWindow setMinWidth:[aMenu minimumWidth]];
|
||||
|
||||
return menuWindow;
|
||||
}
|
||||
|
||||
+ (void)poolMenuWindow:(_CPMenuWindow)aMenuWindow
|
||||
{
|
||||
if (!aMenuWindow || _CPMenuWindowPool.length >= _CPMenuWindowPoolCapacity)
|
||||
return;
|
||||
|
||||
_CPMenuWindowPool.push(aMenuWindow);
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [_CPMenuWindow class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPMenuWindowMoreAboveImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowMoreAbove.png"] size:CGSizeMake(38.0, 18.0)];
|
||||
_CPMenuWindowMoreBelowImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowMoreBelow.png"] size:CGSizeMake(38.0, 18.0)];
|
||||
}
|
||||
|
||||
- (id)initWithContentRect:(CGRect)aRect styleMask:(unsigned)aStyleMask
|
||||
{
|
||||
_constraintRect = _CGRectMakeZero();
|
||||
_unconstrainedFrame = _CGRectMakeZero();
|
||||
|
||||
self = [super initWithContentRect:aRect styleMask:CPBorderlessWindowMask];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setLevel:CPPopUpMenuWindowLevel];
|
||||
[self setHasShadow:YES];
|
||||
[self setShadowStyle:CPMenuWindowShadowStyle];
|
||||
[self setAcceptsMouseMovedEvents:YES];
|
||||
|
||||
var contentView = [self contentView];
|
||||
|
||||
_menuView = [[_CPMenuView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
_menuClipView = [[CPClipView alloc] initWithFrame:CGRectMake(LEFT_MARGIN, TOP_MARGIN, 0.0, 0.0)];
|
||||
[_menuClipView setDocumentView:_menuView];
|
||||
|
||||
[contentView addSubview:_menuClipView];
|
||||
|
||||
_moreAboveView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_moreAboveView setImage:_CPMenuWindowMoreAboveImage];
|
||||
[_moreAboveView setFrameSize:[_CPMenuWindowMoreAboveImage size]];
|
||||
|
||||
[contentView addSubview:_moreAboveView];
|
||||
|
||||
_moreBelowView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_moreBelowView setImage:_CPMenuWindowMoreBelowImage];
|
||||
[_moreBelowView setFrameSize:[_CPMenuWindowMoreBelowImage size]];
|
||||
|
||||
[contentView addSubview:_moreBelowView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (float)_standardLeftMargin
|
||||
{
|
||||
return LEFT_MARGIN;
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
[_menuView setFont:aFont];
|
||||
}
|
||||
|
||||
- (CPFont)font
|
||||
{
|
||||
return [_menuView font];
|
||||
}
|
||||
|
||||
+ (CPColor)backgroundColorForBackgroundStyle:(_CPMenuWindowBackgroundStyle)aBackgroundStyle
|
||||
{
|
||||
var color = _CPMenuWindowBackgroundColors[aBackgroundStyle];
|
||||
|
||||
if (!color)
|
||||
{
|
||||
var bundle = [CPBundle bundleForClass:[self class]];
|
||||
|
||||
if (aBackgroundStyle == _CPMenuWindowPopUpBackgroundStyle)
|
||||
color = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded0.png"] size:CGSizeMake(4.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow1.png"] size:CGSizeMake(1.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded2.png"] size:CGSizeMake(4.0, 4.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 1.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded6.png"] size:CGSizeMake(4.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow7.png"] size:CGSizeMake(1.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded8.png"] size:CGSizeMake(4.0, 4.0)]
|
||||
]]];
|
||||
|
||||
else if (aBackgroundStyle == _CPMenuWindowMenuBarBackgroundStyle)
|
||||
color = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 0.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 0.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 0.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 1.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded6.png"] size:CGSizeMake(4.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow7.png"] size:CGSizeMake(1.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded8.png"] size:CGSizeMake(4.0, 4.0)]
|
||||
]]];
|
||||
|
||||
_CPMenuWindowBackgroundColors[aBackgroundStyle] = color;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
- (void)setBackgroundStyle:(_CPMenuWindowBackgroundStyle)aBackgroundStyle
|
||||
{
|
||||
[self setBackgroundColor:[[self class] backgroundColorForBackgroundStyle:aBackgroundStyle]];
|
||||
}
|
||||
|
||||
- (void)setMenu:(CPMenu)aMenu
|
||||
{
|
||||
[aMenu _setMenuWindow:self];
|
||||
[_menuView setMenu:aMenu];
|
||||
|
||||
var menuViewSize = [_menuView frame].size;
|
||||
|
||||
[self setFrameSize:CGSizeMake(LEFT_MARGIN + menuViewSize.width + RIGHT_MARGIN, TOP_MARGIN + menuViewSize.height + BOTTOM_MARGIN)];
|
||||
|
||||
[_menuView scrollPoint:CGPointMake(0.0, 0.0)];
|
||||
[_menuClipView setFrame:CGRectMake(LEFT_MARGIN, TOP_MARGIN, menuViewSize.width, menuViewSize.height)];
|
||||
}
|
||||
|
||||
- (void)setMinWidth:(float)aWidth
|
||||
{
|
||||
var size = [self unconstrainedFrame].size;
|
||||
|
||||
[self setFrameSize:CGSizeMake(MAX(size.width, aWidth), size.height)];
|
||||
}
|
||||
|
||||
- (CPMenu)menu
|
||||
{
|
||||
return [_menuView menu];
|
||||
}
|
||||
|
||||
- (_CPMenuView)_menuView
|
||||
{
|
||||
return _menuView;
|
||||
}
|
||||
|
||||
- (void)orderFront:(id)aSender
|
||||
{
|
||||
[[self menu] update];
|
||||
[self setFrame:_unconstrainedFrame];
|
||||
|
||||
[super orderFront:aSender];
|
||||
}
|
||||
|
||||
- (void)setConstraintRect:(CGRect)aRect
|
||||
{
|
||||
_constraintRect = aRect;
|
||||
|
||||
[self setFrame:_unconstrainedFrame];
|
||||
}
|
||||
|
||||
- (CGRect)unconstrainedFrame
|
||||
{
|
||||
return _CGRectMakeCopy(_unconstrainedFrame);
|
||||
}
|
||||
|
||||
// We need this because if not this will call setFrame: with -frame instead of -unconstrainedFrame, turning
|
||||
// the constrained frame into the unconstrained frame.
|
||||
- (void)setFrameOrigin:(CGPoint)aPoint
|
||||
{
|
||||
[super setFrame:_CGRectMake(aPoint.x, aPoint.y, _CGRectGetWidth(_unconstrainedFrame), _CGRectGetHeight(_unconstrainedFrame))];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrame:_CGRectMake(_CGRectGetMinX(_unconstrainedFrame), _CGRectGetMinY(_unconstrainedFrame), aSize.width, aSize.height)];
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)aFrame display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate
|
||||
{
|
||||
// FIXME: There are integral window issues with platform windows.
|
||||
// FIXME: This gets called far too often.
|
||||
_unconstrainedFrame = _CGRectMakeCopy(aFrame);
|
||||
|
||||
var constrainedFrame = CGRectIntersection(_unconstrainedFrame, _constraintRect);
|
||||
|
||||
// We don't want to simply intersect the visible frame and the unconstrained frame.
|
||||
// We should be allowing as much of the width to fit as possible (pushing back and forward).
|
||||
constrainedFrame.origin.x = CGRectGetMinX(_unconstrainedFrame);
|
||||
constrainedFrame.size.width = CGRectGetWidth(_unconstrainedFrame);
|
||||
|
||||
if (CGRectGetWidth(constrainedFrame) > CGRectGetWidth(_constraintRect))
|
||||
constrainedFrame.size.width = CGRectGetWidth(_constraintRect);
|
||||
|
||||
if (CGRectGetMaxX(constrainedFrame) > CGRectGetMaxX(_constraintRect))
|
||||
constrainedFrame.origin.x -= CGRectGetMaxX(constrainedFrame) - CGRectGetMaxX(_constraintRect);
|
||||
|
||||
if (CGRectGetMinX(constrainedFrame) < CGRectGetMinX(_constraintRect))
|
||||
constrainedFrame.origin.x = CGRectGetMinX(_constraintRect);
|
||||
|
||||
[super setFrame:constrainedFrame display:shouldDisplay animate:shouldAnimate];
|
||||
|
||||
// This needs to happen before changing the frame.
|
||||
var menuViewOrigin = CGPointMake(CGRectGetMinX(aFrame) + LEFT_MARGIN, CGRectGetMinY(aFrame) + TOP_MARGIN),
|
||||
moreAbove = menuViewOrigin.y < CGRectGetMinY(constrainedFrame) + TOP_MARGIN,
|
||||
moreBelow = menuViewOrigin.y + CGRectGetHeight([_menuView frame]) > CGRectGetMaxY(constrainedFrame) - BOTTOM_MARGIN,
|
||||
|
||||
topMargin = TOP_MARGIN,
|
||||
bottomMargin = BOTTOM_MARGIN,
|
||||
|
||||
contentView = [self contentView],
|
||||
bounds = [contentView bounds];
|
||||
|
||||
if (moreAbove)
|
||||
{
|
||||
topMargin += SCROLL_INDICATOR_HEIGHT;
|
||||
|
||||
var frame = [_moreAboveView frame];
|
||||
|
||||
[_moreAboveView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(frame)) / 2.0, (TOP_MARGIN + SCROLL_INDICATOR_HEIGHT - CGRectGetHeight(frame)) / 2.0)];
|
||||
}
|
||||
|
||||
[_moreAboveView setHidden:!moreAbove];
|
||||
|
||||
if (moreBelow)
|
||||
{
|
||||
bottomMargin += SCROLL_INDICATOR_HEIGHT;
|
||||
|
||||
[_moreBelowView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth([_moreBelowView frame])) / 2.0, CGRectGetHeight(bounds) - SCROLL_INDICATOR_HEIGHT - BOTTOM_MARGIN)];
|
||||
}
|
||||
|
||||
[_moreBelowView setHidden:!moreBelow];
|
||||
|
||||
var clipFrame = CGRectMakeZero();
|
||||
|
||||
clipFrame.origin.x = LEFT_MARGIN;
|
||||
clipFrame.origin.y = topMargin;
|
||||
clipFrame.size.width = CGRectGetWidth(constrainedFrame) - LEFT_MARGIN - RIGHT_MARGIN;
|
||||
clipFrame.size.height = CGRectGetHeight(constrainedFrame) - topMargin - bottomMargin;
|
||||
|
||||
[_menuClipView setFrame:clipFrame];
|
||||
[_menuView setFrameSize:CGSizeMake(CGRectGetWidth(clipFrame), CGRectGetHeight([_menuView frame]))];
|
||||
|
||||
[_menuView scrollPoint:CGPointMake(0.0, [self convertBaseToGlobal:clipFrame.origin].y - menuViewOrigin.y)];
|
||||
}
|
||||
|
||||
- (BOOL)hasMinimumNumberOfVisibleItems
|
||||
{
|
||||
var visibleRect = [_menuView visibleRect];
|
||||
|
||||
// Clearly if the entire view isn't visible the minimum won't be visible.
|
||||
if (CGRectIsEmpty(visibleRect))
|
||||
return NO;
|
||||
|
||||
var numberOfUnhiddenItems = [_menuView numberOfUnhiddenItems],
|
||||
minimumNumberOfVisibleItems = MIN(numberOfUnhiddenItems, 3),
|
||||
count = 0,
|
||||
index = [_menuView itemIndexAtPoint:[_menuView convertPoint:[_menuClipView frame].origin fromView:nil]];
|
||||
|
||||
for (; index < numberOfUnhiddenItems && count < minimumNumberOfVisibleItems; ++index)
|
||||
{
|
||||
var itemRect = [_menuView rectForUnhiddenItemAtIndex:index],
|
||||
visibleItemRect = CGRectIntersection(visibleRect, itemRect);
|
||||
|
||||
// As soon as we get to the first unhidden item that is no longer visible, stop.
|
||||
if (CGRectIsEmpty(visibleItemRect))
|
||||
break;
|
||||
|
||||
// If the item is *completely* visible, count it.
|
||||
if (CGRectEqualToRect(visibleItemRect, itemRect))
|
||||
++count;
|
||||
}
|
||||
|
||||
return count >= minimumNumberOfVisibleItems;
|
||||
}
|
||||
|
||||
- (BOOL)canScrollUp
|
||||
{
|
||||
return ![_moreAboveView isHidden];
|
||||
}
|
||||
|
||||
- (BOOL)canScrollDown
|
||||
{
|
||||
return ![_moreBelowView isHidden];
|
||||
}
|
||||
|
||||
- (BOOL)canScroll
|
||||
{
|
||||
return [self canScrollUp] || [self canScrollDown];
|
||||
}
|
||||
|
||||
- (void)scrollByDelta:(float)theDelta
|
||||
{
|
||||
if (theDelta === 0.0)
|
||||
return;
|
||||
|
||||
if (theDelta > 0.0 && ![self canScrollDown])
|
||||
return;
|
||||
|
||||
if (theDelta < 0.0 && ![self canScrollUp])
|
||||
return;
|
||||
|
||||
_unconstrainedFrame.origin.y -= theDelta;
|
||||
[self setFrame:_unconstrainedFrame];
|
||||
}
|
||||
|
||||
- (void)scrollUp
|
||||
{
|
||||
[self scrollByDelta:-10.0];
|
||||
}
|
||||
|
||||
- (void)scrollDown
|
||||
{
|
||||
[self scrollByDelta:10.0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMenuWindow (CPMenuContainer)
|
||||
|
||||
- (CGRect)globalFrame
|
||||
{
|
||||
return [self frame];
|
||||
}
|
||||
|
||||
- (BOOL)isMenuBar
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (_CPManagerScrollingState)scrollingStateForPoint:(CGPoint)aGlobalLocation
|
||||
{
|
||||
var frame = [self frame];
|
||||
if (!CPRectContainsPoint(frame,aGlobalLocation) || ![self canScroll])
|
||||
return _CPMenuManagerScrollingStateNone;
|
||||
|
||||
// If we're at or above of the top scroll indicator...
|
||||
if (aGlobalLocation.y < CGRectGetMinY(frame) + TOP_MARGIN + SCROLL_INDICATOR_HEIGHT && ![_moreAboveView isHidden])
|
||||
return _CPMenuManagerScrollingStateUp;
|
||||
|
||||
// If we're at or below the bottom scroll indicator...
|
||||
if (aGlobalLocation.y > CGRectGetMaxY(frame) - BOTTOM_MARGIN - SCROLL_INDICATOR_HEIGHT && ![_moreBelowView isHidden])
|
||||
return _CPMenuManagerScrollingStateDown;
|
||||
|
||||
return _CPMenuManagerScrollingStateNone;
|
||||
}
|
||||
|
||||
- (float)deltaYForItemAtIndex:(int)anIndex
|
||||
{
|
||||
return TOP_MARGIN + CGRectGetMinY([_menuView rectForItemAtIndex:anIndex]);
|
||||
}
|
||||
|
||||
- (CGPoint)rectForItemAtIndex:(int)anIndex
|
||||
{
|
||||
return [_menuView convertRect:[_menuView rectForItemAtIndex:anIndex] toView:nil];
|
||||
}
|
||||
|
||||
- (int)itemIndexAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
// Don't return indexes of items that aren't visible.
|
||||
if (!CGRectContainsPoint([_menuClipView bounds], [_menuClipView convertPoint:aPoint fromView:nil]))
|
||||
return NO;
|
||||
|
||||
return [_menuView itemIndexAtPoint:[_menuView convertPoint:aPoint fromView:nil]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
|
||||
@implementation _CPMenuView : CPView
|
||||
{
|
||||
CPArray _menuItemViews;
|
||||
CPArray _visibleMenuItemInfos;
|
||||
|
||||
CPFont _font @accessors(property=font);
|
||||
}
|
||||
|
||||
- (unsigned)numberOfUnhiddenItems
|
||||
{
|
||||
return _visibleMenuItemInfos.length;
|
||||
}
|
||||
|
||||
- (CGRect)rectForUnhiddenItemAtIndex:(int)anIndex
|
||||
{
|
||||
return [self rectForItemAtIndex:_visibleMenuItemInfos[anIndex].index];
|
||||
}
|
||||
|
||||
- (CGRect)rectForItemAtIndex:(int)anIndex
|
||||
{
|
||||
return [_menuItemViews[anIndex === CPNotFound ? 0 : anIndex] frame];
|
||||
}
|
||||
|
||||
- (int)itemIndexAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
var x = aPoint.x,
|
||||
bounds = [self bounds];
|
||||
|
||||
if (x < CGRectGetMinX(bounds) || x > CGRectGetMaxX(bounds))
|
||||
return CPNotFound;
|
||||
|
||||
var y = aPoint.y,
|
||||
low = 0,
|
||||
high = _visibleMenuItemInfos.length - 1;
|
||||
|
||||
while (low <= high)
|
||||
{
|
||||
var middle = FLOOR(low + (high - low) / 2),
|
||||
info = _visibleMenuItemInfos[middle],
|
||||
frame = [info.view frame];
|
||||
|
||||
if (y < CGRectGetMinY(frame))
|
||||
high = middle - 1;
|
||||
|
||||
else if (y > CGRectGetMaxY(frame))
|
||||
low = middle + 1;
|
||||
|
||||
else
|
||||
return info.index;
|
||||
}
|
||||
|
||||
return CPNotFound;
|
||||
}
|
||||
|
||||
- (void)tile
|
||||
{
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
|
||||
_menuItemViews = [];
|
||||
_visibleMenuItemInfos = [];
|
||||
|
||||
var menu = [self menu];
|
||||
|
||||
if (!menu)
|
||||
return;
|
||||
|
||||
var items = [menu itemArray],
|
||||
index = 0,
|
||||
count = [items count],
|
||||
maxWidth = 0,
|
||||
y = 0,
|
||||
showsStateColumn = [menu showsStateColumn];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var item = items[index],
|
||||
view = [item _menuItemView];
|
||||
|
||||
_menuItemViews.push(view);
|
||||
|
||||
if ([item isHidden])
|
||||
continue;
|
||||
|
||||
_visibleMenuItemInfos.push({ view:view, index:index });
|
||||
|
||||
[view setFont:_font];
|
||||
[view setShowsStateColumn:showsStateColumn];
|
||||
[view synchronizeWithMenuItem];
|
||||
|
||||
[view setFrameOrigin:CGPointMake(0.0, y)];
|
||||
|
||||
[self addSubview:view];
|
||||
|
||||
var size = [view minSize],
|
||||
width = size.width;
|
||||
|
||||
if (maxWidth < width)
|
||||
maxWidth = width;
|
||||
|
||||
y += size.height;
|
||||
}
|
||||
|
||||
for (index = 0; index < count; ++index)
|
||||
{
|
||||
var view = _menuItemViews[index];
|
||||
|
||||
[view setFrameSize:CGSizeMake(maxWidth, CGRectGetHeight([view frame]))];
|
||||
}
|
||||
|
||||
[self setAutoresizesSubviews:NO];
|
||||
[self setFrameSize:CGSizeMake(maxWidth, y)];
|
||||
[self setAutoresizesSubviews:YES];
|
||||
}
|
||||
|
||||
- (void)setMenu:(CPMenu)aMenu
|
||||
{
|
||||
[super setMenu:aMenu];
|
||||
[self tile];
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,237 +0,0 @@
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
var HORIZONTAL_MARGIN = 8.0,
|
||||
SUBMENU_INDICATOR_MARGIN = 3.0,
|
||||
VERTICAL_MARGIN = 4.0;
|
||||
|
||||
var SelectionColor = nil,
|
||||
SUBMENU_INDICATOR_COLOR = nil,
|
||||
_CPMenuItemSelectionColor = nil,
|
||||
_CPMenuItemTextShadowColor = nil,
|
||||
|
||||
_CPMenuItemDefaultStateImages = [],
|
||||
_CPMenuItemDefaultStateHighlightedImages = [];
|
||||
|
||||
@implementation _CPMenuItemMenuBarView : CPView
|
||||
{
|
||||
CPMenuItem _menuItem @accessors(property=menuItem);
|
||||
|
||||
CPFont _font;
|
||||
CPColor _textColor;
|
||||
CPColor _textShadowColor;
|
||||
|
||||
BOOL _isDirty;
|
||||
|
||||
_CPImageAndTextView _imageAndTextView;
|
||||
CPView _submenuIndicatorView;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [_CPMenuItemMenuBarView class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
SelectionColor = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuBarWindow/_CPMenuBarWindowBackgroundSelected.png"] size:CGSizeMake(1.0, 28.0)]];
|
||||
|
||||
SUBMENU_INDICATOR_COLOR = [CPColor grayColor];
|
||||
|
||||
_CPMenuItemSelectionColor = [CPColor colorWithCalibratedRed:95.0 / 255.0 green:131.0 / 255.0 blue:185.0 / 255.0 alpha:1.0];
|
||||
_CPMenuItemTextShadowColor = [CPColor colorWithCalibratedRed:26.0 / 255.0 green: 73.0 / 255.0 blue:109.0 / 255.0 alpha:1.0];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOffState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOffState] = nil;
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnState.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnStateHighlighted.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPMixedState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPMixedState] = nil;
|
||||
}
|
||||
|
||||
+ (id)view
|
||||
{
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_imageAndTextView = [[_CPImageAndTextView alloc] initWithFrame:CGRectMake(HORIZONTAL_MARGIN, 0.0, 0.0, 0.0)];
|
||||
|
||||
[_imageAndTextView setImagePosition:CPImageLeft];
|
||||
[_imageAndTextView setImageOffset:3.0];
|
||||
[_imageAndTextView setTextShadowOffset:CGSizeMake(0.0, 1.0)];
|
||||
[_imageAndTextView setAutoresizingMask:CPViewMinYMargin | CPViewMaxYMargin];
|
||||
|
||||
[self addSubview:_imageAndTextView];
|
||||
|
||||
_submenuIndicatorView = [[_CPMenuItemMenuBarSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 9.0, 6.0)];
|
||||
|
||||
[_submenuIndicatorView setAutoresizingMask:CPViewMinYMargin | CPViewMaxYMargin];
|
||||
|
||||
[self addSubview:_submenuIndicatorView];
|
||||
|
||||
[self setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPColor)textColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return [CPColor lightGrayColor];
|
||||
|
||||
return _textColor || [CPColor colorWithCalibratedRed:70.0 / 255.0 green:69.0 / 255.0 blue:69.0 / 255.0 alpha:1.0];
|
||||
}
|
||||
|
||||
- (CPColor)textShadowColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return [CPColor colorWithWhite:0.8 alpha:0.8];
|
||||
|
||||
return _textShadowColor || [CPColor colorWithWhite:1.0 alpha:0.8];
|
||||
}
|
||||
|
||||
- (void)update
|
||||
{
|
||||
var x = HORIZONTAL_MARGIN,
|
||||
height = 0.0;
|
||||
|
||||
[_imageAndTextView setFont:[_menuItem font] || [_CPMenuBarWindow font]];
|
||||
[_imageAndTextView setVerticalAlignment:CPCenterVerticalTextAlignment];
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
[_imageAndTextView setText:[_menuItem title]];
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
[_imageAndTextView setTextShadowOffset:CGSizeMake(0.0, 1.0)];
|
||||
[_imageAndTextView sizeToFit];
|
||||
|
||||
var imageAndTextViewFrame = [_imageAndTextView frame];
|
||||
|
||||
imageAndTextViewFrame.origin.x = x;
|
||||
x += CGRectGetWidth(imageAndTextViewFrame);
|
||||
height = MAX(height, CGRectGetHeight(imageAndTextViewFrame));
|
||||
|
||||
var hasSubmenuIndicator = [_menuItem hasSubmenu] && [_menuItem action];
|
||||
|
||||
if (hasSubmenuIndicator)
|
||||
{
|
||||
[_submenuIndicatorView setHidden:NO];
|
||||
[_submenuIndicatorView setColor:[self textColor]];
|
||||
[_submenuIndicatorView setShadowColor:[self textShadowColor]];
|
||||
|
||||
var submenuViewFrame = [_submenuIndicatorView frame];
|
||||
|
||||
submenuViewFrame.origin.x = x + SUBMENU_INDICATOR_MARGIN;
|
||||
|
||||
x = CGRectGetMaxX(submenuViewFrame);
|
||||
height = MAX(height, CGRectGetHeight(submenuViewFrame));
|
||||
}
|
||||
else
|
||||
[_submenuIndicatorView setHidden:YES];
|
||||
|
||||
height += 2.0 * VERTICAL_MARGIN;
|
||||
|
||||
imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0);
|
||||
[_imageAndTextView setFrame:imageAndTextViewFrame];
|
||||
|
||||
if (hasSubmenuIndicator)
|
||||
{
|
||||
submenuViewFrame.origin.y = FLOOR((height - CGRectGetHeight(submenuViewFrame)) / 2.0) + 1.0;
|
||||
[_submenuIndicatorView setFrame:submenuViewFrame];
|
||||
}
|
||||
|
||||
[self setAutoresizesSubviews:NO];
|
||||
[self setFrameSize:CGSizeMake(x + HORIZONTAL_MARGIN, height)];
|
||||
[self setAutoresizesSubviews:YES];
|
||||
}
|
||||
|
||||
- (void)highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
// FIXME: This should probably be even throw.
|
||||
if (![_menuItem isEnabled])
|
||||
shouldHighlight = NO;
|
||||
|
||||
if (shouldHighlight)
|
||||
{
|
||||
if (![_menuItem _isMenuBarButton])
|
||||
[self setBackgroundColor:SelectionColor];
|
||||
|
||||
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
|
||||
[_imageAndTextView setTextColor:[CPColor whiteColor]];
|
||||
[_imageAndTextView setTextShadowColor:_CPMenuItemTextShadowColor];
|
||||
|
||||
[_submenuIndicatorView setColor:[CPColor whiteColor]];
|
||||
[_submenuIndicatorView setShadowColor:[CPColor colorWithWhite:0.1 alpha:0.7]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setBackgroundColor:nil];
|
||||
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
|
||||
[_submenuIndicatorView setColor:[self textColor]];
|
||||
[_submenuIndicatorView setShadowColor:[self textShadowColor]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMenuItemMenuBarSubmenuIndicatorView : CPView
|
||||
{
|
||||
CPColor _color;
|
||||
CPColor _shadowColor;
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
if (_color === aColor)
|
||||
return;
|
||||
|
||||
_color = aColor;
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setShadowColor:(CPColor)aColor
|
||||
{
|
||||
if (_shadowColor === aColor)
|
||||
return;
|
||||
|
||||
_shadowColor = aColor;
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds];
|
||||
|
||||
bounds.size.height -= 1.0;
|
||||
bounds.size.width -= 2.0;
|
||||
bounds.origin.x += 1.0;
|
||||
|
||||
CGContextBeginPath(context);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(bounds), CGRectGetMinY(bounds));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), CGRectGetMinY(bounds));
|
||||
CGContextAddLineToPoint(context, CGRectGetMidX(bounds), CGRectGetMaxY(bounds));
|
||||
|
||||
CGContextClosePath(context);
|
||||
|
||||
CGContextSetShadowWithColor(context, CGSizeMake(0.0, 1.0), 1.1, _shadowColor || [CPColor whiteColor]);
|
||||
CGContextSetFillColor(context, _color || [CPColor blackColor]);
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,38 +0,0 @@
|
||||
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
@implementation _CPMenuItemSeparatorView : CPView
|
||||
{
|
||||
}
|
||||
|
||||
+ (id)view
|
||||
{
|
||||
return [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 10.0)];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
[self setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds];
|
||||
|
||||
CGContextBeginPath(context);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
|
||||
|
||||
CGContextSetStrokeColor(context, [CPColor lightGrayColor]);
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,306 +0,0 @@
|
||||
@import "CPControl.j"
|
||||
|
||||
|
||||
var LEFT_MARGIN = 3.0,
|
||||
RIGHT_MARGIN = 14.0 + 3.0,
|
||||
STATE_COLUMN_WIDTH = 14.0,
|
||||
INDENTATION_WIDTH = 17.0,
|
||||
VERTICAL_MARGIN = 4.0,
|
||||
|
||||
RIGHT_COLUMNS_MARGIN = 30.0,
|
||||
KEY_EQUIVALENT_MARGIN = 10.0;
|
||||
|
||||
var SUBMENU_INDICATOR_COLOR = nil,
|
||||
_CPMenuItemSelectionColor = nil,
|
||||
_CPMenuItemTextShadowColor = nil,
|
||||
|
||||
_CPMenuItemDefaultStateImages = [],
|
||||
_CPMenuItemDefaultStateHighlightedImages = [];
|
||||
|
||||
@implementation _CPMenuItemStandardView : CPView
|
||||
{
|
||||
CPMenuItem _menuItem @accessors(property=menuItem);
|
||||
|
||||
CPFont _font;
|
||||
CPColor _textColor;
|
||||
CPColor _textShadowColor;
|
||||
|
||||
CGSize _minSize @accessors(readonly, property=minSize);
|
||||
BOOL _isDirty;
|
||||
|
||||
CPImageView _stateView;
|
||||
_CPImageAndTextView _imageAndTextView;
|
||||
_CPImageAndTextView _keyEquivalentView;
|
||||
CPView _submenuIndicatorView;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [_CPMenuItemStandardView class])
|
||||
return;
|
||||
|
||||
SUBMENU_INDICATOR_COLOR = [CPColor grayColor];
|
||||
|
||||
_CPMenuItemSelectionColor = [CPColor colorWithCalibratedRed:95.0 / 255.0 green:131.0 / 255.0 blue:185.0 / 255.0 alpha:1.0];
|
||||
_CPMenuItemTextShadowColor = [CPColor colorWithCalibratedRed:26.0 / 255.0 green: 73.0 / 255.0 blue:109.0 / 255.0 alpha:1.0];
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOffState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOffState] = nil;
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnState.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnStateHighlighted.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPMixedState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPMixedState] = nil;
|
||||
}
|
||||
|
||||
+ (id)view
|
||||
{
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
+ (float)_standardLeftMargin
|
||||
{
|
||||
return LEFT_MARGIN + STATE_COLUMN_WIDTH;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_stateView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)];
|
||||
|
||||
[_stateView setImageScaling:CPImageScaleNone];
|
||||
|
||||
[self addSubview:_stateView];
|
||||
|
||||
_imageAndTextView = [[_CPImageAndTextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)];
|
||||
|
||||
[_imageAndTextView setImagePosition:CPImageLeft];
|
||||
[_imageAndTextView setTextShadowOffset:CGSizeMake(0.0, 1.0)];
|
||||
|
||||
[self addSubview:_imageAndTextView];
|
||||
|
||||
_keyEquivalentView = [[_CPImageAndTextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)];
|
||||
|
||||
[_keyEquivalentView setImagePosition:CPNoImage];
|
||||
[_keyEquivalentView setTextShadowOffset:CGSizeMake(0.0, 1.0)];
|
||||
[_keyEquivalentView setAutoresizingMask:CPViewMinXMargin];
|
||||
|
||||
[self addSubview:_keyEquivalentView];
|
||||
|
||||
_submenuIndicatorView = [[_CPMenuItemSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 8.0, 10.0)];
|
||||
|
||||
[_submenuIndicatorView setColor:SUBMENU_INDICATOR_COLOR];
|
||||
[_submenuIndicatorView setAutoresizingMask:CPViewMinXMargin];
|
||||
|
||||
[self addSubview:_submenuIndicatorView];
|
||||
|
||||
[self setAutoresizingMask:CPViewWidthSizable];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPColor)textColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return [CPColor lightGrayColor];
|
||||
|
||||
return _textColor || [CPColor colorWithCalibratedRed:70.0 / 255.0 green:69.0 / 255.0 blue:69.0 / 255.0 alpha:1.0];
|
||||
}
|
||||
|
||||
- (CPColor)textShadowColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return nil;
|
||||
|
||||
return _textShadowColor || [CPColor colorWithWhite:1.0 alpha:0.8];
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
_font = aFont;
|
||||
}
|
||||
|
||||
- (void)update
|
||||
{
|
||||
var x = LEFT_MARGIN + [_menuItem indentationLevel] * INDENTATION_WIDTH,
|
||||
height = 0.0,
|
||||
hasStateColumn = [[_menuItem menu] showsStateColumn];
|
||||
|
||||
if (hasStateColumn)
|
||||
{
|
||||
[_stateView setHidden:NO];
|
||||
[_stateView setImage:_CPMenuItemDefaultStateImages[[_menuItem state]] || nil];
|
||||
|
||||
x += STATE_COLUMN_WIDTH;
|
||||
}
|
||||
else
|
||||
[_stateView setHidden:YES];
|
||||
|
||||
[_imageAndTextView setFont:[_menuItem font] || _font];
|
||||
[_imageAndTextView setVerticalAlignment:CPCenterVerticalTextAlignment];
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
[_imageAndTextView setText:[_menuItem title]];
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
[_imageAndTextView setTextShadowOffset:CGSizeMake(0.0, 1.0)];
|
||||
[_imageAndTextView sizeToFit];
|
||||
|
||||
var imageAndTextViewFrame = [_imageAndTextView frame];
|
||||
|
||||
imageAndTextViewFrame.origin.x = x;
|
||||
x += CGRectGetWidth(imageAndTextViewFrame);
|
||||
height = MAX(height, CGRectGetHeight(imageAndTextViewFrame));
|
||||
|
||||
var hasKeyEquivalent = !![_menuItem keyEquivalent],
|
||||
hasSubmenu = [_menuItem hasSubmenu];
|
||||
|
||||
if (hasKeyEquivalent || hasSubmenu)
|
||||
x += RIGHT_COLUMNS_MARGIN;
|
||||
|
||||
if (hasKeyEquivalent)
|
||||
{
|
||||
[_keyEquivalentView setFont:[_menuItem font] || _font];
|
||||
[_keyEquivalentView setVerticalAlignment:CPCenterVerticalTextAlignment];
|
||||
[_keyEquivalentView setImage:[_menuItem image]];
|
||||
[_keyEquivalentView setText:[_menuItem keyEquivalentStringRepresentation]];
|
||||
[_keyEquivalentView setTextColor:[self textColor]];
|
||||
[_keyEquivalentView setTextShadowColor:[self textShadowColor]];
|
||||
[_keyEquivalentView setTextShadowOffset:CGSizeMake(0, 1)];
|
||||
[_keyEquivalentView setFrameOrigin:CGPointMake(x, VERTICAL_MARGIN)];
|
||||
[_keyEquivalentView sizeToFit];
|
||||
|
||||
var keyEquivalentViewFrame = [_keyEquivalentView frame];
|
||||
|
||||
keyEquivalentViewFrame.origin.x = x;
|
||||
x += CGRectGetWidth(keyEquivalentViewFrame);
|
||||
height = MAX(height, CGRectGetHeight(keyEquivalentViewFrame));
|
||||
|
||||
if (hasSubmenu)
|
||||
x += RIGHT_COLUMNS_MARGIN;
|
||||
}
|
||||
else
|
||||
[_keyEquivalentView setHidden:YES];
|
||||
|
||||
if (hasSubmenu)
|
||||
{
|
||||
[_submenuIndicatorView setHidden:NO];
|
||||
|
||||
var submenuViewFrame = [_submenuIndicatorView frame];
|
||||
|
||||
submenuViewFrame.origin.x = x;
|
||||
|
||||
x += CGRectGetWidth(submenuViewFrame);
|
||||
height = MAX(height, CGRectGetHeight(submenuViewFrame));
|
||||
}
|
||||
else
|
||||
[_submenuIndicatorView setHidden:YES];
|
||||
|
||||
height += 2.0 * VERTICAL_MARGIN;
|
||||
|
||||
imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0);
|
||||
[_imageAndTextView setFrame:imageAndTextViewFrame];
|
||||
|
||||
if (hasStateColumn)
|
||||
[_stateView setFrameSize:CGSizeMake(STATE_COLUMN_WIDTH, height)];
|
||||
|
||||
if (hasKeyEquivalent)
|
||||
{
|
||||
keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0);
|
||||
[_keyEquivalentView setFrame:keyEquivalentViewFrame];
|
||||
}
|
||||
|
||||
if (hasSubmenu)
|
||||
{
|
||||
submenuViewFrame.origin.y = FLOOR((height - CGRectGetHeight(submenuViewFrame)) / 2.0);
|
||||
[_submenuIndicatorView setFrame:submenuViewFrame];
|
||||
}
|
||||
|
||||
_minSize = CGSizeMake(x + RIGHT_MARGIN, height);
|
||||
|
||||
[self setAutoresizesSubviews:NO];
|
||||
[self setFrameSize:_minSize];
|
||||
[self setAutoresizesSubviews:YES];
|
||||
}
|
||||
|
||||
- (void)highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
// FIXME: This should probably be even throw.
|
||||
if (![_menuItem isEnabled])
|
||||
return;
|
||||
|
||||
if (shouldHighlight)
|
||||
{
|
||||
[self setBackgroundColor:_CPMenuItemSelectionColor];
|
||||
|
||||
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
|
||||
[_imageAndTextView setTextColor:[CPColor whiteColor]];
|
||||
[_keyEquivalentView setTextColor:[CPColor whiteColor]];
|
||||
[_submenuIndicatorView setColor:[CPColor whiteColor]];
|
||||
|
||||
[_imageAndTextView setTextShadowColor:_CPMenuItemTextShadowColor];
|
||||
[_keyEquivalentView setTextShadowColor:_CPMenuItemTextShadowColor];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setBackgroundColor:nil];
|
||||
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_keyEquivalentView setTextColor:[self textColor]];
|
||||
[_submenuIndicatorView setColor:SUBMENU_INDICATOR_COLOR];
|
||||
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
[_keyEquivalentView setTextShadowColor:[self textShadowColor]];
|
||||
}
|
||||
|
||||
if ([[_menuItem menu] showsStateColumn])
|
||||
{
|
||||
if (shouldHighlight)
|
||||
[_stateView setImage:_CPMenuItemDefaultStateHighlightedImages[[_menuItem state]] || nil];
|
||||
else
|
||||
[_stateView setImage:_CPMenuItemDefaultStateImages[[_menuItem state]] || nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMenuItemSubmenuIndicatorView : CPView
|
||||
{
|
||||
CPColor _color;
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
if (_color === aColor)
|
||||
return;
|
||||
|
||||
_color = aColor;
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds];
|
||||
|
||||
CGContextBeginPath(context);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(bounds), CGRectGetMinY(bounds));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), CGRectGetMidY(bounds));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(bounds), CGRectGetMaxY(bounds));
|
||||
|
||||
CGContextClosePath(context);
|
||||
|
||||
CGContextSetFillColor(context, _color);
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,256 +0,0 @@
|
||||
|
||||
@import <AppKit/CPControl.j>
|
||||
|
||||
@import "_CPMenuItemSeparatorView.j"
|
||||
@import "_CPMenuItemStandardView.j"
|
||||
@import "_CPMenuItemMenuBarView.j"
|
||||
|
||||
|
||||
var LEFT_MARGIN = 3.0,
|
||||
RIGHT_MARGIN = 16.0,
|
||||
STATE_COLUMN_WIDTH = 14.0,
|
||||
INDENTATION_WIDTH = 17.0,
|
||||
VERTICAL_MARGIN = 4.0;
|
||||
|
||||
var _CPMenuItemSelectionColor = nil,
|
||||
_CPMenuItemTextShadowColor = nil,
|
||||
|
||||
_CPMenuItemDefaultStateImages = [],
|
||||
_CPMenuItemDefaultStateHighlightedImages = [];
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
@implementation _CPMenuItemView : CPView
|
||||
{
|
||||
CPMenuItem _menuItem;
|
||||
CPView _view;
|
||||
|
||||
CPFont _font;
|
||||
CPColor _textColor;
|
||||
CPColor _textShadowColor;
|
||||
|
||||
CGSize _minSize;
|
||||
BOOL _isDirty;
|
||||
BOOL _showsStateColumn;
|
||||
|
||||
_CPImageAndTextView _imageAndTextView;
|
||||
CPView _submenuView;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [_CPMenuItemView class])
|
||||
return;
|
||||
|
||||
_CPMenuItemSelectionColor = [CPColor colorWithCalibratedRed:95.0 / 255.0 green:131.0 / 255.0 blue:185.0 / 255.0 alpha:1.0];
|
||||
_CPMenuItemTextShadowColor = [CPColor colorWithCalibratedRed:26.0 / 255.0 green: 73.0 / 255.0 blue:109.0 / 255.0 alpha:1.0];
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOffState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOffState] = nil;
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnState.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnStateHighlighted.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPMixedState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPMixedState] = nil;
|
||||
}
|
||||
|
||||
+ (float)leftMargin
|
||||
{
|
||||
return LEFT_MARGIN + STATE_COLUMN_WIDTH;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame forMenuItem:(CPMenuItem)aMenuItem
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_menuItem = aMenuItem;
|
||||
_showsStateColumn = YES;
|
||||
_isDirty = YES;
|
||||
|
||||
[self setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[self synchronizeWithMenuItem];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)minSize
|
||||
{
|
||||
return _minSize;
|
||||
}
|
||||
|
||||
- (void)setDirty
|
||||
{
|
||||
_isDirty = YES;
|
||||
}
|
||||
|
||||
- (void)synchronizeWithMenuItem
|
||||
{
|
||||
var menuItemView = [_menuItem view];
|
||||
|
||||
if ([_menuItem isSeparatorItem])
|
||||
{
|
||||
if (![_view isKindOfClass:[_CPMenuItemSeparatorView class]])
|
||||
{
|
||||
[_view removeFromSuperview];
|
||||
_view = [_CPMenuItemSeparatorView view];
|
||||
}
|
||||
}
|
||||
else if (menuItemView)
|
||||
{
|
||||
if (_view !== menuItemView)
|
||||
{
|
||||
[_view removeFromSuperview];
|
||||
_view = menuItemView;
|
||||
}
|
||||
}
|
||||
|
||||
else if ([_menuItem menu] == [CPApp mainMenu])
|
||||
{
|
||||
if (![_view isKindOfClass:[_CPMenuItemMenuBarView class]])
|
||||
{
|
||||
[_view removeFromSuperview];
|
||||
_view = [_CPMenuItemMenuBarView view];
|
||||
}
|
||||
|
||||
[_view setMenuItem:_menuItem];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (![_view isKindOfClass:[_CPMenuItemStandardView class]])
|
||||
{
|
||||
[_view removeFromSuperview];
|
||||
_view = [_CPMenuItemStandardView view];
|
||||
}
|
||||
|
||||
[_view setMenuItem:_menuItem];
|
||||
}
|
||||
|
||||
if ([_view superview] !== self)
|
||||
[self addSubview:_view];
|
||||
|
||||
if ([_view respondsToSelector:@selector(update)])
|
||||
[_view update];
|
||||
|
||||
_minSize = [_view frame].size;
|
||||
[self setAutoresizesSubviews:NO];
|
||||
[self setFrameSize:_minSize];
|
||||
[self setAutoresizesSubviews:YES];
|
||||
}
|
||||
|
||||
- (void)setShowsStateColumn:(BOOL)shouldShowStateColumn
|
||||
{
|
||||
_showsStateColumn = shouldShowStateColumn;
|
||||
}
|
||||
|
||||
- (void)highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(highlight:)])
|
||||
[_view highlight:shouldHighlight];
|
||||
}
|
||||
|
||||
- (BOOL)eventOnSubmenu:(CPEvent)anEvent
|
||||
{
|
||||
if (![_menuItem hasSubmenu])
|
||||
return NO;
|
||||
|
||||
return CGRectContainsPoint([_submenuView frame], [self convertPoint:[anEvent locationInWindow] fromView:nil]);
|
||||
}
|
||||
|
||||
- (BOOL)isHidden
|
||||
{
|
||||
return [_menuItem isHidden];
|
||||
}
|
||||
|
||||
- (CPMenuItem)menuItem
|
||||
{
|
||||
return _menuItem;
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
if ([_font isEqual:aFont])
|
||||
return;
|
||||
|
||||
_font = aFont;
|
||||
|
||||
if ([_view respondsToSelector:@selector(setFont:)])
|
||||
[_view setFont:aFont];
|
||||
|
||||
[self setDirty];
|
||||
}
|
||||
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
{
|
||||
if (_textColor == aColor)
|
||||
return;
|
||||
|
||||
_textColor = aColor;
|
||||
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_submenuView setColor:[self textColor]];
|
||||
}
|
||||
|
||||
- (CPColor)textColor
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setTextShadowColor:(CPColor)aColor
|
||||
{
|
||||
if (_textShadowColor == aColor)
|
||||
return;
|
||||
|
||||
_textShadowColor = aColor;
|
||||
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
//[_submenuView setColor:[self textColor]];
|
||||
}
|
||||
|
||||
- (CPColor)textShadowColor
|
||||
{
|
||||
return [_menuItem isEnabled] ? (_textShadowColor ? _textShadowColor : [CPColor colorWithWhite:1.0 alpha:0.8]) : [CPColor colorWithWhite:0.8 alpha:0.8];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMenuItemArrowView : CPView
|
||||
{
|
||||
CPColor _color;
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
if (_color == aColor)
|
||||
return;
|
||||
|
||||
_color = aColor;
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextBeginPath(context);
|
||||
|
||||
CGContextMoveToPoint(context, 1.0, 4.0);
|
||||
CGContextAddLineToPoint(context, 9.0, 4.0);
|
||||
CGContextAddLineToPoint(context, 5.0, 8.0);
|
||||
CGContextAddLineToPoint(context, 1.0, 4.0);
|
||||
|
||||
CGContextClosePath(context);
|
||||
|
||||
CGContextSetFillColor(context, _color);
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,804 +0,0 @@
|
||||
/*
|
||||
* CPObjectController.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2009, 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/CPDictionary.j>
|
||||
@import <Foundation/CPCountedSet.j>
|
||||
|
||||
@import "CPController.j"
|
||||
|
||||
/*!
|
||||
@class
|
||||
|
||||
CPObjectController is a bindings-compatible controller class.
|
||||
Properties of the content object of an object of this class can be bound to user interface elements to change and access their values.
|
||||
|
||||
The content of an CPObjectController instance is an CPMutableDictionary object by default.
|
||||
This allows a single CPObjectController instance to be used to manage several properties accessed by key value paths.
|
||||
The default content object class can be changed by calling setObjectClass:, which a subclass must override.
|
||||
*/
|
||||
@implementation CPObjectController : CPController
|
||||
{
|
||||
id _contentObject;
|
||||
id _selection;
|
||||
|
||||
Class _objectClass;
|
||||
CPString _objectClassName;
|
||||
|
||||
BOOL _isEditable;
|
||||
BOOL _automaticallyPreparesContent;
|
||||
|
||||
CPCountedSet _observedKeys;
|
||||
}
|
||||
|
||||
+ (id)initialize
|
||||
{
|
||||
if (self !== [CPObjectController class])
|
||||
return;
|
||||
|
||||
[self exposeBinding:@"editable"];
|
||||
[self exposeBinding:@"contentObject"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingContentObject
|
||||
{
|
||||
return [CPSet setWithObjects:"content"];
|
||||
}
|
||||
|
||||
+ (BOOL)automaticallyNotifiesObserversForKey:(CPString)aKey
|
||||
{
|
||||
if (aKey === @"contentObject")
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingCanAdd
|
||||
{
|
||||
return [CPSet setWithObject:"editable"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingCanInsert
|
||||
{
|
||||
return [CPSet setWithObject:"editable"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingCanRemove
|
||||
{
|
||||
return [CPSet setWithObjects:"editable", "selection"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithContent:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Inits and returns a CPObjectController object with the given content.
|
||||
|
||||
@param id aContent - The object the controller will use.
|
||||
@return id the CPObjectConroller instance.
|
||||
*/
|
||||
- (id)initWithContent:(id)aContent
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
[self setContent:aContent];
|
||||
[self setEditable:YES];
|
||||
[self setObjectClass:[CPMutableDictionary class]];
|
||||
|
||||
_observedKeys = [[CPCountedSet alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the controller's content object.
|
||||
@return id - The content object of the controller.
|
||||
*/
|
||||
- (id)content
|
||||
{
|
||||
return _contentObject;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the content object for the controller.
|
||||
@param id aContent - The new content object for the controller.
|
||||
*/
|
||||
- (void)setContent:(id)aContent
|
||||
{
|
||||
[self willChangeValueForKey:@"contentObject"];
|
||||
[self _selectionWillChange];
|
||||
|
||||
_contentObject = aContent;
|
||||
|
||||
[self _selectionDidChange];
|
||||
[self didChangeValueForKey:@"contentObject"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_setContentObject:(id)aContent
|
||||
{
|
||||
[self setContent:aContent];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (id)_contentObject
|
||||
{
|
||||
return [self content];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the controller automatically creates and inserts new content objects automatically when loading from a cib file.
|
||||
If you pass YES and the controller uses prepareContent to create the content object.
|
||||
The default is NO.
|
||||
|
||||
@param BOOL shouldAutomaticallyPrepareContent - YES if the content should be prepared, otherwise NO.
|
||||
*/
|
||||
- (void)setAutomaticallyPreparesContent:(BOOL)shouldAutomaticallyPrepareContent
|
||||
{
|
||||
_automaticallyPreparesContent = shouldAutomaticallyPrepareContent;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns if the controller prepares the content automatically.
|
||||
@return BOOL - YES if the content is prepared, otherwise NO.
|
||||
*/
|
||||
- (BOOL)automaticallyPreparesContent
|
||||
{
|
||||
return _automaticallyPreparesContent;
|
||||
}
|
||||
|
||||
/*!
|
||||
Overridden by a subclass that require control over the creation of new objects.
|
||||
*/
|
||||
- (void)prepareContent
|
||||
{
|
||||
[self setContent:[self newObject]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the object class when creating new objects.
|
||||
@param Class - the class of new objects that will be created.
|
||||
*/
|
||||
- (void)setObjectClass:(Class)aClass
|
||||
{
|
||||
_objectClass = aClass;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the class of what new objects will be when they are created.
|
||||
|
||||
@return Class - The class of new objects.
|
||||
*/
|
||||
- (Class)objectClass
|
||||
{
|
||||
return _objectClass;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (id)_defaultNewObject
|
||||
{
|
||||
return [[[self objectClass] alloc] init];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and returns a new object of the appropriate class.
|
||||
@return id - The object created.
|
||||
*/
|
||||
- (id)newObject
|
||||
{
|
||||
return [self _defaultNewObject];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the controller's content object.
|
||||
@param id anObject - The object to set for the controller.
|
||||
*/
|
||||
- (void)addObject:(id)anObject
|
||||
{
|
||||
[self setContent:anObject];
|
||||
|
||||
var binderClass = [[self class] _binderClassForBinding:@"contentObject"];
|
||||
[[binderClass getBinding:@"contentObject" forObject:self] reverseSetValueFor:@"contentObject"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes a given object from the controller.
|
||||
@param id anObject - The object to remove from the receiver.
|
||||
*/
|
||||
- (void)removeObject:(id)anObject
|
||||
{
|
||||
if ([self content] === anObject)
|
||||
[self setContent:nil];
|
||||
|
||||
var binderClass = [[self class] _binderClassForBinding:@"contentObject"];
|
||||
[[binderClass getBinding:@"contentObject" forObject:self] reverseSetValueFor:@"contentObject"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and adds a sets the object as the controller's content.
|
||||
@param id aSender - The sender of the message.
|
||||
*/
|
||||
- (void)add:(id)aSender
|
||||
{
|
||||
// FIXME: This should happen on the next run loop?
|
||||
[self addObject:[self newObject]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@return BOOL - YES if you can added to the controller using add:
|
||||
*/
|
||||
- (BOOL)canAdd
|
||||
{
|
||||
return [self isEditable];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes the content object from the controller.
|
||||
@param id aSender - The sender of the message.
|
||||
*/
|
||||
- (void)remove:(id)aSender
|
||||
{
|
||||
// FIXME: This should happen on the next run loop?
|
||||
[self removeObject:[self content]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@return BOOL - Returns YES if you can remove the controller's content using remove:
|
||||
*/
|
||||
- (BOOL)canRemove
|
||||
{
|
||||
return [self isEditable] && [[self selectedObjects] count];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the controller allows for the editing of the content.
|
||||
@param BOOL shouldBeEditable - YES if the content should be editable, otherwise NO.
|
||||
*/
|
||||
- (void)setEditable:(BOOL)shouldBeEditable
|
||||
{
|
||||
_isEditable = shouldBeEditable;
|
||||
}
|
||||
|
||||
/*!
|
||||
@return BOOL - Returns YES if the content of the controller is editable, otherwise NO.
|
||||
*/
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return _isEditable;
|
||||
}
|
||||
|
||||
/*!
|
||||
@return CPArray - Returns an array of all objects to be affected by editing.
|
||||
*/
|
||||
- (CPArray)selectedObjects
|
||||
{
|
||||
return [[_CPObservableArray alloc] initWithArray:[_contentObject]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a proxy object representing the controller's selection.
|
||||
*/
|
||||
- (id)selection
|
||||
{
|
||||
return _selection;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_selectionWillChange
|
||||
{
|
||||
[_selection controllerWillChange];
|
||||
[self willChangeValueForKey:@"selection"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_selectionDidChange
|
||||
{
|
||||
if (_selection === undefined || _selection === nil)
|
||||
_selection = [[CPControllerSelectionProxy alloc] initWithController:self];
|
||||
|
||||
[_selection controllerDidChange];
|
||||
[self didChangeValueForKey:@"selection"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@return id - Returns the keys which are being observed.
|
||||
*/
|
||||
- (id)observedKeys
|
||||
{
|
||||
return _observedKeys;
|
||||
}
|
||||
|
||||
- (void)addObserver:(id)anObserver forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
|
||||
{
|
||||
[_observedKeys addObject:aKeyPath];
|
||||
[super addObserver:anObserver forKeyPath:aKeyPath options:options context:context];
|
||||
}
|
||||
|
||||
- (void)removeObserver:(id)anObserver forKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
[_observedKeys removeObject:aKeyPath];
|
||||
[super removeObserver:anObserver forKeyPath:aKeyPath];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPObjectControllerContentKey = @"CPObjectControllerContentKey",
|
||||
CPObjectControllerObjectClassNameKey = @"CPObjectControllerObjectClassNameKey",
|
||||
CPObjectControllerIsEditableKey = @"CPObjectControllerIsEditableKey",
|
||||
CPObjectControllerAutomaticallyPreparesContentKey = @"CPObjectControllerAutomaticallyPreparesContentKey";
|
||||
|
||||
@implementation CPObjectController (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
var objectClassName = [aCoder decodeObjectForKey:CPObjectControllerObjectClassNameKey],
|
||||
objectClass = CPClassFromString(objectClassName);
|
||||
|
||||
[self setObjectClass:objectClass || [CPMutableDictionary class]];
|
||||
[self setEditable:[aCoder decodeBoolForKey:CPObjectControllerIsEditableKey]];
|
||||
[self setAutomaticallyPreparesContent:[aCoder decodeBoolForKey:CPObjectControllerAutomaticallyPreparesContentKey]];
|
||||
[self setContent:[aCoder decodeObjectForKey:CPObjectControllerContentKey]];
|
||||
|
||||
_observedKeys = [[CPCountedSet alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:[self content] forKey:CPObjectControllerContentKey];
|
||||
|
||||
if (_objectClass)
|
||||
[aCoder encodeObject:CPStringFromClass(_objectClass) forKey:CPObjectControllerObjectClassNameKey];
|
||||
else if (_objectClassName)
|
||||
[aCoder encodeObject:_objectClassName forKey:CPObjectControllerObjectClassNameKey];
|
||||
|
||||
[aCoder encodeBool:[self isEditable] forKey:CPObjectControllerIsEditableKey];
|
||||
[aCoder encodeBool:[self automaticallyPreparesContent] forKey:CPObjectControllerAutomaticallyPreparesContentKey];
|
||||
}
|
||||
|
||||
- (void)awakeFromCib
|
||||
{
|
||||
if (![self content] && [self automaticallyPreparesContent])
|
||||
[self prepareContent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPObservationProxy : CPObject
|
||||
{
|
||||
id _keyPath;
|
||||
id _observer;
|
||||
id _object;
|
||||
|
||||
BOOL _notifyObject;
|
||||
|
||||
id _context;
|
||||
int _options;
|
||||
}
|
||||
|
||||
- (id)initWithKeyPath:(id)aKeyPath observer:(id)anObserver object:(id)anObject
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_keyPath = aKeyPath;
|
||||
_observer = anObserver;
|
||||
_object = anObject;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)observer
|
||||
{
|
||||
return _observer;
|
||||
}
|
||||
|
||||
- (id)keyPath
|
||||
{
|
||||
return _keyPath;
|
||||
}
|
||||
|
||||
- (id)context
|
||||
{
|
||||
return _context;
|
||||
}
|
||||
|
||||
- (int)options
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
|
||||
- (void)setNotifyObject:(BOOL)notify
|
||||
{
|
||||
_notifyObject = notify;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)anObject
|
||||
{
|
||||
if ([anObject class] === [self class])
|
||||
{
|
||||
if (anObject._observer === _observer && [anObject._keyPath isEqual:_keyPath] && [anObject._object isEqual:_object])
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)change context:(id)context
|
||||
{
|
||||
if (_notifyObject)
|
||||
[_object observeValueForKeyPath:aKeyPath ofObject:_object change:change context:context];
|
||||
|
||||
[_observer observeValueForKeyPath:aKeyPath ofObject:_object change:change context:context];
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return [super description] + [CPString stringWithFormat:@"observation proxy for %@ on key path %@", _observer, _keyPath];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// FIXME: This should subclass CPMutableArray not _CPJavaScriptArray
|
||||
@implementation _CPObservableArray : _CPJavaScriptArray
|
||||
{
|
||||
CPArray _observationProxies;
|
||||
}
|
||||
|
||||
+ (id)alloc
|
||||
{
|
||||
var a = [];
|
||||
a.isa = self;
|
||||
|
||||
var ivars = class_copyIvarList(self),
|
||||
count = ivars.length;
|
||||
|
||||
while (count--)
|
||||
a[ivar_getName(ivars[count])] = nil;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return "<_CPObservableArray: " + [super description] + " >";
|
||||
}
|
||||
|
||||
- (id)initWithArray:(CPArray)anArray
|
||||
{
|
||||
self = [super initWithArray:anArray];
|
||||
|
||||
self.isa = [_CPObservableArray class];
|
||||
self._observationProxies = [];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addObserver:(id)anObserver forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
|
||||
{
|
||||
if (aKeyPath.indexOf("@") === 0)
|
||||
{
|
||||
var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObserver object:self];
|
||||
|
||||
proxy._options = options;
|
||||
proxy._context = context;
|
||||
|
||||
[_observationProxies addObject:proxy];
|
||||
|
||||
var dotIndex = aKeyPath.indexOf("."),
|
||||
remaining = aKeyPath.substring(dotIndex + 1),
|
||||
indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
|
||||
|
||||
[self addObserver:proxy toObjectsAtIndexes:indexes forKeyPath:remaining options:options context:context];
|
||||
}
|
||||
else
|
||||
{
|
||||
var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
|
||||
[self addObserver:anObserver toObjectsAtIndexes:indexes forKeyPath:aKeyPath options:options context:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeObserver:(id)anObserver forKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
if (aKeyPath.indexOf("@") === 0)
|
||||
{
|
||||
var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObserver object:self],
|
||||
index = [_observationProxies indexOfObject:proxy];
|
||||
|
||||
proxy = [_observationProxies objectAtIndex:index];
|
||||
|
||||
var dotIndex = aKeyPath.indexOf("."),
|
||||
remaining = aKeyPath.substring(dotIndex + 1),
|
||||
indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
|
||||
|
||||
[self removeObserver:proxy fromObjectsAtIndexes:indexes forKeyPath:remaining];
|
||||
}
|
||||
else
|
||||
{
|
||||
var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self count])];
|
||||
[self removeObserver:anObserver fromObjectsAtIndexes:indexes forKeyPath:aKeyPath];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)insertObject:(id)anObject atIndex:(unsigned)anIndex
|
||||
{
|
||||
for (var i = 0, count = [_observationProxies count]; i < count; i++)
|
||||
{
|
||||
var proxy = [_observationProxies objectAtIndex:i],
|
||||
keyPath = [proxy keyPath],
|
||||
operator = keyPath.indexOf(".") === 0;
|
||||
|
||||
if (operator)
|
||||
[self willChangeValueForKey:keyPath];
|
||||
|
||||
[anObject addObserver:proxy forKeyPath:keyPath options:[proxy options] context:[proxy context]];
|
||||
|
||||
if (operator)
|
||||
[self didChangeValueForKey:keyPath];
|
||||
}
|
||||
|
||||
[super insertObject:anObject atIndex:anIndex];
|
||||
}
|
||||
|
||||
- (void)removeObjectAtIndex:(unsigned)anIndex
|
||||
{
|
||||
var currentObject = [self objectAtIndex:anIndex];
|
||||
|
||||
for (var i = 0, count = [_observationProxies count]; i < count; i++)
|
||||
{
|
||||
var proxy = [_observationProxies objectAtIndex:i],
|
||||
keyPath = [proxy keyPath],
|
||||
operator = keyPath.indexOf(".") === 0;
|
||||
|
||||
if (operator)
|
||||
[self willChangeValueForKey:keyPath];
|
||||
|
||||
[currentObject removeObserver:proxy forKeyPath:keyPath];
|
||||
|
||||
if (operator)
|
||||
[self didChangeValueForKey:keyPath];
|
||||
}
|
||||
|
||||
[super removeObjectAtIndex:anIndex];
|
||||
}
|
||||
|
||||
- (_CPObservableArray)objectsAtIndexes:(CPIndexSet)theIndexes
|
||||
{
|
||||
return [_CPObservableArray arrayWithArray:[super objectsAtIndexes:theIndexes]];
|
||||
}
|
||||
|
||||
- (void)addObject:(id)anObject
|
||||
{
|
||||
[self insertObject:anObject atIndex:[self count]];
|
||||
}
|
||||
|
||||
- (void)removeLastObject
|
||||
{
|
||||
[self removeObjectAtIndex:[self count]];
|
||||
}
|
||||
|
||||
- (void)replaceObjectAtIndex:(unsigned)anIndex withObject:(id)anObject
|
||||
{
|
||||
var currentObject = [self objectAtIndex:anIndex];
|
||||
|
||||
for (var i = 0, count = [_observationProxies count]; i < count; i++)
|
||||
{
|
||||
var proxy = [_observationProxies objectAtIndex:i],
|
||||
keyPath = [proxy keyPath],
|
||||
operator = keyPath.indexOf(".") === 0;
|
||||
|
||||
if (operator)
|
||||
[self willChangeValueForKey:keyPath];
|
||||
|
||||
[currentObject removeObserver:proxy forKeyPath:keyPath];
|
||||
[anObject addObserver:proxy forKeyPath:keyPath options:[proxy options] context:[proxy context]];
|
||||
|
||||
if (operator)
|
||||
[self didChangeValueForKey:keyPath];
|
||||
}
|
||||
|
||||
[super replaceObjectAtIndex:anIndex withObject:anObject];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPControllerSelectionProxy : CPObject
|
||||
{
|
||||
id _controller;
|
||||
id _keys;
|
||||
|
||||
CPDictionary _cachedValues;
|
||||
CPArray _observationProxies;
|
||||
|
||||
Object _observedObjectsByKeyPath;
|
||||
}
|
||||
|
||||
- (id)initWithController:(id)aController
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_cachedValues = [CPDictionary dictionary];
|
||||
_observationProxies = [CPArray array];
|
||||
_controller = aController;
|
||||
_observedObjectsByKeyPath = {};
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)_controllerMarkerForValues:(CPArray)theValues
|
||||
{
|
||||
var count = [theValues count],
|
||||
value;
|
||||
|
||||
if (!count)
|
||||
value = CPNoSelectionMarker;
|
||||
else if (count === 1)
|
||||
value = [theValues objectAtIndex:0];
|
||||
else
|
||||
{
|
||||
if ([_controller alwaysUsesMultipleValuesMarker])
|
||||
value = CPMultipleValuesMarker;
|
||||
else
|
||||
{
|
||||
value = [theValues objectAtIndex:0];
|
||||
|
||||
for (var i = 0, count = [theValues count]; i < count && value != CPMultipleValuesMarker; i++)
|
||||
{
|
||||
if (![value isEqual:[theValues objectAtIndex:i]])
|
||||
value = CPMultipleValuesMarker;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value === nil || value.isa && [value isEqual:[CPNull null]])
|
||||
value = CPNullMarker;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (id)valueForKeyPath:(CPString)theKeyPath
|
||||
{
|
||||
var values = [[_controller selectedObjects] valueForKeyPath:theKeyPath],
|
||||
value = [self _controllerMarkerForValues:values];
|
||||
|
||||
[_cachedValues setObject:value forKey:theKeyPath];
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (id)valueForKey:(CPString)theKeyPath
|
||||
{
|
||||
return [self valueForKeyPath:theKeyPath];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)theValue forKeyPath:(CPString)theKeyPath
|
||||
{
|
||||
[[_controller selectedObjects] setValue:theValue forKeyPath:theKeyPath];
|
||||
[_cachedValues removeObjectForKey:theKeyPath];
|
||||
|
||||
// Allow handlesContentAsCompoundValue to work, based on observation of Cocoa's
|
||||
// NSArrayController - when handlesContentAsCompoundValue and setValue:forKey:@"selection.X"
|
||||
// is called, the array controller causes the compound value to be rewritten if
|
||||
// handlesContentAsCompoundValue == YES. Note that
|
||||
// A) this doesn't use observation (observe: X is not visible in backtraces)
|
||||
// B) it only happens through the selection proxy and not on arrangedObject.X, content.X
|
||||
// or even selectedObjects.X.
|
||||
// FIXME The main code for this should somehow be in CPArrayController and also work
|
||||
// for table based row edits.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:_controller] _contentArrayDidChange];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)theValue forKey:(CPString)theKeyPath
|
||||
{
|
||||
[self setValue:theKeyPath forKeyPath:theKeyPath];
|
||||
}
|
||||
|
||||
- (unsigned)count
|
||||
{
|
||||
return [_cachedValues count];
|
||||
}
|
||||
|
||||
- (id)keyEnumerator
|
||||
{
|
||||
return [_cachedValues keyEnumerator];
|
||||
}
|
||||
|
||||
- (void)controllerWillChange
|
||||
{
|
||||
_keys = [_cachedValues allKeys];
|
||||
|
||||
if (!_keys)
|
||||
return;
|
||||
|
||||
for (var i = 0, count = _keys.length; i < count; i++)
|
||||
[self willChangeValueForKey:_keys[i]];
|
||||
|
||||
[_cachedValues removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)controllerDidChange
|
||||
{
|
||||
[_cachedValues removeAllObjects];
|
||||
|
||||
if (!_keys)
|
||||
return;
|
||||
|
||||
for (var i = 0, count = _keys.length; i < count; i++)
|
||||
[self didChangeValueForKey:_keys[i]];
|
||||
|
||||
_keys = nil;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)change context:(id)context
|
||||
{
|
||||
[_cachedValues removeObjectForKey:aKeyPath];
|
||||
}
|
||||
|
||||
- (void)addObserver:(id)anObject forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
|
||||
{
|
||||
var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObject object:self];
|
||||
|
||||
[proxy setNotifyObject:YES];
|
||||
[_observationProxies addObject:proxy];
|
||||
|
||||
// We keep a reference to the observed objects because removeObserver: will be called after the selection changes.
|
||||
var observedObjects = [_controller selectedObjects];
|
||||
_observedObjectsByKeyPath[aKeyPath] = observedObjects;
|
||||
[observedObjects addObserver:proxy forKeyPath:aKeyPath options:options context:context];
|
||||
}
|
||||
|
||||
- (void)removeObserver:(id)anObject forKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObject object:self],
|
||||
index = [_observationProxies indexOfObject:proxy];
|
||||
|
||||
var observedObjects = _observedObjectsByKeyPath[aKeyPath];
|
||||
[observedObjects removeObserver:[_observationProxies objectAtIndex:index] forKeyPath:aKeyPath];
|
||||
|
||||
[_observationProxies removeObjectAtIndex:index];
|
||||
|
||||
_observedObjects = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* CPOpenPanel.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2009, 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 "CPPanel.j"
|
||||
|
||||
|
||||
@implementation CPOpenPanel : CPPanel
|
||||
{
|
||||
BOOL _canChooseFiles @accessors(property=canChooseFiles);
|
||||
BOOL _canChooseDirectories @accessors(property=canChooseDirectories);
|
||||
BOOL _allowsMultipleSelection @accessors(property=allowsMultipleSelection);
|
||||
CPURL _directoryURL @accessors(property=directoryURL);
|
||||
CPArray _URLs;
|
||||
}
|
||||
|
||||
+ (id)openPanel
|
||||
{
|
||||
return [[CPOpenPanel alloc] init];
|
||||
}
|
||||
|
||||
- (CPInteger)runModal
|
||||
{
|
||||
if (typeof window["cpOpenPanel"] === "function")
|
||||
{
|
||||
// FIXME: Is this correct???
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
|
||||
var options = { directoryURL: [self directoryURL],
|
||||
canChooseFiles: [self canChooseFiles],
|
||||
canChooseDirectories: [self canChooseDirectories],
|
||||
allowsMultipleSelection: [self allowsMultipleSelection] };
|
||||
|
||||
var result = window.cpOpenPanel(options);
|
||||
|
||||
_URLs = result.URLs;
|
||||
|
||||
return result.button;
|
||||
}
|
||||
|
||||
throw "-runModal is unimplemented.";
|
||||
}
|
||||
|
||||
- (CPArray)URLs
|
||||
{
|
||||
return _URLs;
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* CPPanel.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CPWindow.j"
|
||||
|
||||
|
||||
CPOKButton = 1;
|
||||
CPCancelButton = 0;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPPanel
|
||||
|
||||
The CPPanel class defines objects that manage the panels of the Cappuccino user interface. A panel is a window that serves an auxiliary function within an application. It generally displays controls that the user can act on to give instructions to the application or to modify the contents of a standard window.</p>
|
||||
|
||||
<p>Panels behave differently from standard windows in only a small number of ways, but the ways are important to the user interface:</p>
|
||||
|
||||
<ul>
|
||||
<li>Panels can assume key window status but not main window status. (The key window receives keyboard events. The main window is the primary focus of user actions; it might contain the document the user is working on, for example.)</li>
|
||||
</ul>
|
||||
|
||||
<p>To aid in their auxiliary role, panels can be assigned special behaviors:</p>
|
||||
|
||||
<ul>
|
||||
<li>A panel can be precluded from becoming the key window until the user makes a selection (makes some view in the panel the first responder) indicating an intention to begin typing. This prevents key window status from shifting to the panel unnecessarily.</li>
|
||||
<li>Palettes and similar panels can be made to float above standard windows and other panels. This prevents them from being covered and keeps them readily available to the user.</li>
|
||||
<li>A panel can be made to work even when there's an attention panel on-screen. This permits actions within the panel to affect the attention panel.</li>
|
||||
</ul>
|
||||
*/
|
||||
|
||||
/*
|
||||
@global
|
||||
@class CPWindow
|
||||
*/
|
||||
CPDocModalWindowMask = 1 << 6;
|
||||
|
||||
@implementation CPPanel : CPWindow
|
||||
{
|
||||
BOOL _becomesKeyOnlyIfNeeded;
|
||||
BOOL _worksWhenModal;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the receiver is a floating panel (like a palette).
|
||||
*/
|
||||
- (BOOL)isFloatingPanel
|
||||
{
|
||||
return [self level] == CPFloatingWindowLevel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver to be a floating panel. \c YES
|
||||
makes the window a floating panel. \c NO makes it a normal window.
|
||||
@param isFloatingPanel specifies whether to make it floating
|
||||
*/
|
||||
- (void)setFloatingPanel:(BOOL)isFloatingPanel
|
||||
{
|
||||
[self setLevel:isFloatingPanel ? CPFloatingWindowLevel : CPNormalWindowLevel];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the window only becomes key
|
||||
if needed. \c NO means it behaves just like other windows.
|
||||
*/
|
||||
- (BOOL)becomesKeyOnlyIfNeeded
|
||||
{
|
||||
return _becomesKeyOnlyIfNeeded;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the the window becomes key only if needed.
|
||||
@param shouldBecomeKeyOnlyIfNeeded \c YES makes the window become key only if needed
|
||||
*/
|
||||
- (void)setBecomesKeyOnlyIfNeeded:(BOOL)shouldBecomeKeyOnlyIfNeeded
|
||||
{
|
||||
_becomesKeyOnlyIfNeeded = shouldBecomeKeyOnlyIfNeeded
|
||||
}
|
||||
|
||||
- (BOOL)worksWhenModal
|
||||
{
|
||||
return _worksWhenModal;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether this window can receive input while another window is running modally.
|
||||
@param shouldWorkWhenModal whether to receive input while another window is modal
|
||||
*/
|
||||
- (void)setWorksWhenModal:(BOOL)shouldWorkWhenModal
|
||||
{
|
||||
_worksWhenModal = shouldWorkWhenModal;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeMainWindow
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyWindow
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,445 +0,0 @@
|
||||
/*
|
||||
* CPPasteboard.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 <Foundation/CPArray.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPPropertyListSerialization.j>
|
||||
|
||||
|
||||
CPGeneralPboard = @"CPGeneralPboard";
|
||||
CPFontPboard = @"CPFontPboard";
|
||||
CPRulerPboard = @"CPRulerPboard";
|
||||
CPFindPboard = @"CPFindPboard";
|
||||
CPDragPboard = @"CPDragPboard";
|
||||
|
||||
CPColorPboardType = @"CPColorPboardType";
|
||||
CPFilenamesPboardType = @"CPFilenamesPboardType";
|
||||
CPFontPboardType = @"CPFontPboardType";
|
||||
CPHTMLPboardType = @"CPHTMLPboardType";
|
||||
CPStringPboardType = @"CPStringPboardType";
|
||||
CPURLPboardType = @"CPURLPboardType";
|
||||
CPImagesPboardType = @"CPImagesPboardType";
|
||||
CPVideosPboardType = @"CPVideosPboardType";
|
||||
|
||||
UTF8PboardType = @"public.utf8-plain-text";
|
||||
|
||||
// Deprecated
|
||||
CPImagePboardType = @"CPImagePboardType";
|
||||
|
||||
|
||||
var CPPasteboards = nil,
|
||||
supportsNativePasteboard = NO;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPPasteboard
|
||||
|
||||
CPPasteBoard is the object responsible for cut/copy/paste and drag&drop operations.
|
||||
*/
|
||||
@implementation CPPasteboard : CPObject
|
||||
{
|
||||
CPArray _types;
|
||||
CPDictionary _owners;
|
||||
CPDictionary _provided;
|
||||
|
||||
unsigned _changeCount;
|
||||
CPString _stateUID;
|
||||
|
||||
WebScriptObject _nativePasteboard;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPPasteboard class])
|
||||
return;
|
||||
|
||||
[self setVersion:1.0];
|
||||
|
||||
CPPasteboards = [CPDictionary dictionary];
|
||||
|
||||
if (typeof window.cpPasteboardWithName !== "undefined")
|
||||
supportsNativePasteboard = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new instance of a pasteboard
|
||||
*/
|
||||
+ (id)generalPasteboard
|
||||
{
|
||||
return [CPPasteboard pasteboardWithName:CPGeneralPboard];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a pasteboard with the specified name. If the pasteboard doesn't exist, it will be created.
|
||||
@param aName the name of the pasteboard
|
||||
@return the requested pasteboard
|
||||
*/
|
||||
+ (id)pasteboardWithName:(CPString)aName
|
||||
{
|
||||
var pasteboard = [CPPasteboards objectForKey:aName];
|
||||
|
||||
if (pasteboard)
|
||||
return pasteboard;
|
||||
|
||||
pasteboard = [[CPPasteboard alloc] _initWithName:aName];
|
||||
[CPPasteboards setObject:pasteboard forKey:aName];
|
||||
|
||||
return pasteboard;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithName:(CPString)aName
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_name = aName;
|
||||
_types = [];
|
||||
|
||||
_owners = [CPDictionary dictionary];
|
||||
_provided = [CPDictionary dictionary];
|
||||
|
||||
_changeCount = 0;
|
||||
|
||||
if (supportsNativePasteboard)
|
||||
{
|
||||
_nativePasteboard = window.cpPasteboardWithName(aName);
|
||||
[self _synchronizePasteboard];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds supported data types to the pasteboard
|
||||
@param types the data types
|
||||
@param anOwner the object that contains the data types
|
||||
@return the pasteboard's change count
|
||||
*/
|
||||
- (unsigned)addTypes:(CPArray)types owner:(id)anOwner
|
||||
{
|
||||
var i = 0,
|
||||
count = types.length;
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
var type = types[i];
|
||||
|
||||
if (![_owners objectForKey:type])
|
||||
{
|
||||
[_types addObject:type];
|
||||
[_provided removeObjectForKey:type];
|
||||
}
|
||||
|
||||
[_owners setObject:anOwner forKey:type];
|
||||
}
|
||||
|
||||
if (_nativePasteboard)
|
||||
{
|
||||
var nativeTypes = [types copy];
|
||||
if ([types containsObject:CPStringPboardType])
|
||||
nativeTypes.push(UTF8PboardType);
|
||||
|
||||
_nativePasteboard.addTypes_(nativeTypes);
|
||||
}
|
||||
|
||||
return ++_changeCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the data types that this pasteboard will contain.
|
||||
@param type the data types it will support
|
||||
@param anOwner the object that contains the the data
|
||||
@return the pasteboard's change count
|
||||
*/
|
||||
- (unsigned)declareTypes:(CPArray)types owner:(id)anOwner
|
||||
{
|
||||
[self _declareTypes:types owner:anOwner updateNativePasteboard:YES];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (unsigned)_declareTypes:(CPArray)types owner:(id)anOwner updateNativePasteboard:(BOOL)shouldUpdate
|
||||
{
|
||||
[_types setArray:types];
|
||||
|
||||
_owners = [CPDictionary dictionary];
|
||||
_provided = [CPDictionary dictionary];
|
||||
|
||||
var count = _types.length;
|
||||
|
||||
while (count--)
|
||||
[_owners setObject:anOwner forKey:_types[count]];
|
||||
|
||||
if (_nativePasteboard && shouldUpdate)
|
||||
{
|
||||
var nativeTypes = [types copy];
|
||||
if ([types containsObject:CPStringPboardType])
|
||||
nativeTypes.push(UTF8PboardType);
|
||||
|
||||
_nativePasteboard.declareTypes_(nativeTypes);
|
||||
_changeCount = _nativePasteboard.changeCount();
|
||||
}
|
||||
return ++_changeCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the pasteboard data for the specified type
|
||||
@param aData the data
|
||||
@param aType the data type being set
|
||||
@return \c YES if the data was successfully written to the pasteboard
|
||||
*/
|
||||
- (BOOL)setData:(CPData)aData forType:(CPString)aType
|
||||
{
|
||||
[_provided setObject:aData forKey:aType];
|
||||
|
||||
if (aType === CPStringPboardType)
|
||||
[self setData:aData forType:UTF8PboardType];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes the specified property list as data for the specified type
|
||||
@param aPropertyList the property list to write
|
||||
@param aType the data type
|
||||
@return \c YES if the property list was successfully written to the pasteboard
|
||||
*/
|
||||
- (BOOL)setPropertyList:(id)aPropertyList forType:(CPString)aType
|
||||
{
|
||||
return [self setData:[CPPropertyListSerialization dataFromPropertyList:aPropertyList format:CPPropertyList280NorthFormat_v1_0] forType:aType];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the specified string as data for the specified type
|
||||
@param aString the string to write
|
||||
@param aType the data type
|
||||
@return \c YES if the string was successfully written to the pasteboard
|
||||
*/
|
||||
- (void)setString:(CPString)aString forType:(CPString)aType
|
||||
{
|
||||
[self setPropertyList:aString forType:aType];
|
||||
}
|
||||
|
||||
// Determining Types
|
||||
/*!
|
||||
Checks the pasteboard's types for a match with the types listen in the specified array. The array should
|
||||
be ordered by the requestor's most preferred data type first.
|
||||
@param anArray an array of requested types ordered by preference
|
||||
@return the highest match with the pasteboard's supported types or \c nil if no match was found
|
||||
*/
|
||||
- (CPString)availableTypeFromArray:(CPArray)anArray
|
||||
{
|
||||
return [[self types] firstObjectCommonWithArray:anArray];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the pasteboards supported types
|
||||
*/
|
||||
- (CPArray)types
|
||||
{
|
||||
[self _synchronizePasteboard];
|
||||
return _types;
|
||||
}
|
||||
|
||||
// Reading data
|
||||
/*!
|
||||
Returns the number of changes that have occurred to this pasteboard
|
||||
*/
|
||||
- (unsigned)changeCount
|
||||
{
|
||||
return _changeCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the pasteboard data for the specified data type
|
||||
@param aType the requested data type
|
||||
@return the requested data or \c nil if the data doesn't exist
|
||||
*/
|
||||
- (CPData)dataForType:(CPString)aType
|
||||
{
|
||||
var data = [_provided objectForKey:aType];
|
||||
|
||||
if (data)
|
||||
return data;
|
||||
|
||||
var owner = [_owners objectForKey:aType];
|
||||
|
||||
if (owner)
|
||||
{
|
||||
[owner pasteboard:self provideDataForType:aType];
|
||||
return [_provided objectForKey:aType];
|
||||
}
|
||||
|
||||
if (aType === CPStringPboardType)
|
||||
return [self dataForType:UTF8PboardType];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)_synchronizePasteboard
|
||||
{
|
||||
if (_nativePasteboard && _nativePasteboard.changeCount() > _changeCount)
|
||||
{
|
||||
var nativeTypes = [_nativePasteboard.types() copy];
|
||||
if ([nativeTypes containsObject:UTF8PboardType])
|
||||
nativeTypes.push(CPStringPboardType);
|
||||
|
||||
[self _declareTypes:nativeTypes owner:self updateNativePasteboard:NO];
|
||||
|
||||
_changeCount = _nativePasteboard.changeCount();
|
||||
}
|
||||
}
|
||||
|
||||
/*! @ignore
|
||||
method provided for integration with native pasteboard
|
||||
*/
|
||||
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
|
||||
{
|
||||
if (aType === CPStringPboardType)
|
||||
{
|
||||
var string = _nativePasteboard.stringForType_(UTF8PboardType);
|
||||
|
||||
[self setString:string forType:CPStringPboardType];
|
||||
[self setString:string forType:UTF8PboardType];
|
||||
}
|
||||
else
|
||||
[self setString:_nativePasteboard.stringForType_(aType) forType:aType];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the property list for the specified data type
|
||||
@param aType the requested data type
|
||||
@return the property list or \c nil if the list was not found
|
||||
*/
|
||||
- (id)propertyListForType:(CPString)aType
|
||||
{
|
||||
var data = [self dataForType:aType];
|
||||
|
||||
if (data)
|
||||
return [CPPropertyListSerialization propertyListFromData:data format:CPPropertyList280NorthFormat_v1_0];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the string for the specified data type
|
||||
@param aType the requested data type
|
||||
@return the string or \c nil if the string was not found
|
||||
*/
|
||||
- (CPString)stringForType:(CPString)aType
|
||||
{
|
||||
return [self propertyListForType:aType];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (CPString)_generateStateUID
|
||||
{
|
||||
var bits = 32;
|
||||
|
||||
_stateUID = @"";
|
||||
|
||||
while (bits--)
|
||||
_stateUID += FLOOR(RAND() * 16.0).toString(16).toUpperCase();
|
||||
|
||||
return _stateUID;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (CPString)_stateUID
|
||||
{
|
||||
return _stateUID;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
var DOMDataTransferPasteboard = nil;
|
||||
|
||||
@implementation _CPDOMDataTransferPasteboard : CPPasteboard
|
||||
{
|
||||
DataTransfer _dataTransfer;
|
||||
}
|
||||
|
||||
+ (_CPDOMDataTransferPasteboard)DOMDataTransferPasteboard
|
||||
{
|
||||
if (!DOMDataTransferPasteboard)
|
||||
DOMDataTransferPasteboard = [[_CPDOMDataTransferPasteboard alloc] init];
|
||||
|
||||
return DOMDataTransferPasteboard;
|
||||
}
|
||||
|
||||
- (void)_setDataTransfer:(DataTransfer)aDataTransfer
|
||||
{
|
||||
_dataTransfer = aDataTransfer;
|
||||
}
|
||||
|
||||
- (void)_setPasteboard:(CPPasteboard)aPasteboard
|
||||
{
|
||||
_dataTransfer.clearData();
|
||||
|
||||
var types = [aPasteboard types],
|
||||
count = types.length;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var type = types[count];
|
||||
|
||||
if (type === CPStringPboardType)
|
||||
_dataTransfer.setData(type, [aPasteboard stringForType:type]);
|
||||
else
|
||||
_dataTransfer.setData(type, [[aPasteboard dataForType:type] rawString]);
|
||||
}
|
||||
}
|
||||
|
||||
- (CPArray)types
|
||||
{
|
||||
return Array.prototype.slice.apply(_dataTransfer.types);
|
||||
}
|
||||
|
||||
- (CPData)dataForType:(CPString)aType
|
||||
{
|
||||
var dataString = _dataTransfer.getData(aType);
|
||||
|
||||
if (aType === CPStringPboardType)
|
||||
return [CPData dataFromPropertyList:dataString format:kCFPropertyList280NorthFormat_v1_0];
|
||||
|
||||
return [CPData dataWithRawString:dataString];
|
||||
}
|
||||
|
||||
- (id)propertyListForType:(CPString)aType
|
||||
{
|
||||
if (aType === CPStringPboardType)
|
||||
return _dataTransfer.getData(aType);
|
||||
|
||||
return [CPPropertyListSerialization propertyListFromData:[self dataForType:aType] format:CPPropertyListUnknownFormat];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,385 +0,0 @@
|
||||
/*
|
||||
* CPPopover.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Antoine Mercadal.
|
||||
* Copyright 2011 Antoine Mercadal.
|
||||
*
|
||||
* 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 "CPButton.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPResponder.j"
|
||||
@import "CPView.j"
|
||||
@import "_CPAttachedWindow.j"
|
||||
|
||||
|
||||
CPPopoverBehaviorApplicationDefined = 0;
|
||||
CPPopoverBehaviorTransient = 1;
|
||||
CPPopoverBehaviorSemitransient = 2;
|
||||
|
||||
var CPPopoverDelegate_popover_willShow_ = 1 << 0,
|
||||
CPPopoverDelegate_popover_didShow_ = 1 << 1,
|
||||
CPPopoverDelegate_popover_shouldClose_ = 1 << 2,
|
||||
CPPopoverDelegate_popover_willClose_ = 1 << 3,
|
||||
CPPopoverDelegate_popover_didClose_ = 1 << 4;
|
||||
|
||||
|
||||
/*! @ingroup appkit
|
||||
@class CPPopover
|
||||
|
||||
This class represent a widget that displays a attached
|
||||
view relative to another one.
|
||||
|
||||
Delegate can implement:
|
||||
|
||||
@code
|
||||
– popoverShouldClose:(CPPopover)aPopOver
|
||||
– popoverWillShow:(CPPopover)aPopOver
|
||||
– popoverDidShow:(CPPopover)aPopOver
|
||||
– popoverWillClose:(CPPopover)aPopOver
|
||||
– popoverDidClose:(CPPopover)aPopOver
|
||||
@endcode
|
||||
*/
|
||||
@implementation CPPopover : CPResponder
|
||||
{
|
||||
@outlet CPViewController _contentViewController @accessors(property=contentViewController);
|
||||
@outlet id _delegate @accessors(getter=delegate);
|
||||
|
||||
BOOL _animates @accessors(property=animates);
|
||||
int _appearance @accessors(property=appearance);
|
||||
int _behavior @accessors(getter=behavior);
|
||||
|
||||
_CPAttachedWindow _attachedWindow;
|
||||
int _implementedDelegateMethods;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
/*!
|
||||
Initialize the CPPopover witn default values
|
||||
|
||||
@returns an initialized CPPopover
|
||||
*/
|
||||
- (CPPopover)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_animates = YES;
|
||||
_appearance = CPPopoverAppearanceMinimal;
|
||||
_behavior = CPPopoverBehaviorApplicationDefined;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Getters / Setters
|
||||
|
||||
/*!
|
||||
Returns the current rect of the popover
|
||||
|
||||
@return CGRect representing the frame of the popover
|
||||
*/
|
||||
- (CGRect)positioningRect
|
||||
{
|
||||
if (![_attachedWindow isVisible])
|
||||
return CGRectMakeZero();
|
||||
return [_attachedWindow frame];
|
||||
}
|
||||
|
||||
/*! Sets the frame of the popover
|
||||
@param aRect the desired frame
|
||||
*/
|
||||
- (void)setPositioningRect:(CGRect)aRect
|
||||
{
|
||||
if (![_attachedWindow isVisible])
|
||||
return;
|
||||
[_attachedWindow setFrame:aRect];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the size of the popover's view
|
||||
|
||||
@return CGSize representing the size of the popover's view
|
||||
*/
|
||||
- (CGSize)contentSize
|
||||
{
|
||||
if (![_attachedWindow isVisible])
|
||||
return CGRectMakeZero();
|
||||
return [[_contentViewController view] frameSize];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the size of of the popover's view
|
||||
|
||||
@param aSize the desired size
|
||||
*/
|
||||
- (void)setContentSize:(CPSize)aSize
|
||||
{
|
||||
[[_contentViewController view] setFrameSize:aSize];
|
||||
}
|
||||
|
||||
/*!
|
||||
Indicates if CPPopover is visible
|
||||
|
||||
@returns \c YES if visible
|
||||
*/
|
||||
- (BOOL)isShown
|
||||
{
|
||||
return [_attachedWindow isVisible];
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the behavior of the CPPopover. It can be:
|
||||
|
||||
- \c CPPopoverBehaviorTransient: the popover will close if another control outside the popover becomes the responder
|
||||
- \c CPPopoverBehaviorApplicationDefined: (DEFAULT) the application is responsible for closing the popover
|
||||
|
||||
@param aBehavior the desired behavior
|
||||
*/
|
||||
- (void)setBehavior:(int)aBehavior
|
||||
{
|
||||
if (_behavior == aBehavior)
|
||||
return;
|
||||
|
||||
_behavior = aBehavior;
|
||||
[_attachedWindow setStyleMask:[self styleMaskForBehavior]];
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate === aDelegate)
|
||||
return;
|
||||
|
||||
_delegate = aDelegate;
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(popoverWillShow:)])
|
||||
_implementedDelegateMethods |= CPPopoverDelegate_popover_willShow_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(popoverDidShow:)])
|
||||
_implementedDelegateMethods |= CPPopoverDelegate_popover_didShow_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(popoverShouldClose:)])
|
||||
_implementedDelegateMethods |= CPPopoverDelegate_popover_shouldClose_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(popoverWillClose:)])
|
||||
_implementedDelegateMethods |= CPPopoverDelegate_popover_willClose_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(popoverDidClose:)])
|
||||
_implementedDelegateMethods |= CPPopoverDelegate_popover_didClose_;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Positioning
|
||||
|
||||
/*!
|
||||
Show the popover
|
||||
|
||||
@param positioningRect if set, the popover will be positionned to a random rect relative to the window
|
||||
@param positioningView if set, the popover will be positioned relative to this view
|
||||
@param preferredEdge: \c CPRectEdge representing the preferred positioning.
|
||||
*/
|
||||
- (void)showRelativeToRect:(CGRect)positioningRect ofView:(CPView)positioningView preferredEdge:(CPRectEdge)preferredEdge
|
||||
{
|
||||
if (!positioningView)
|
||||
[CPException raise:CPInvalidArgumentException reason:"positionView must not be nil"];
|
||||
|
||||
if (!_contentViewController)
|
||||
[CPException raise:CPInternalInconsistencyException reason:@"contentViewController must not be nil"];
|
||||
|
||||
// If the popover is currently closing, do nothing. That is what Cocoa does.
|
||||
if ([_attachedWindow isClosing])
|
||||
return;
|
||||
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_willShow_)
|
||||
[_delegate popoverWillShow:self];
|
||||
|
||||
if (!_attachedWindow)
|
||||
{
|
||||
_attachedWindow = [[_CPAttachedWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:[self styleMaskForBehavior]];
|
||||
|
||||
var parentWindow = [positioningView window];
|
||||
|
||||
if (![parentWindow isKindOfClass:_CPAttachedWindow])
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(parentWindowWillClose:) name:CPWindowWillCloseNotification object:parentWindow];
|
||||
}
|
||||
|
||||
[_attachedWindow setAppearance:_appearance];
|
||||
[_attachedWindow setAnimates:_animates];
|
||||
[_attachedWindow setDelegate:self];
|
||||
[_attachedWindow setMovableByWindowBackground:NO];
|
||||
[_attachedWindow setFrame:[_attachedWindow frameRectForContentRect:[[_contentViewController view] frame]]];
|
||||
[_attachedWindow setContentView:[_contentViewController view]];
|
||||
[_attachedWindow positionRelativeToRect:positioningRect ofView:positioningView preferredEdge:preferredEdge];
|
||||
|
||||
if (!_animates && _implementedDelegateMethods & CPPopoverDelegate_popover_didShow_)
|
||||
[_delegate popoverDidShow:self];
|
||||
}
|
||||
|
||||
- (unsigned)styleMaskForBehavior
|
||||
{
|
||||
return (_behavior == CPPopoverBehaviorTransient) ? CPClosableOnBlurWindowMask : 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Closes the popover
|
||||
*/
|
||||
- (void)close
|
||||
{
|
||||
[self _close];
|
||||
}
|
||||
|
||||
/*!
|
||||
Closes the popover
|
||||
*/
|
||||
- (void)_close
|
||||
{
|
||||
if ([_attachedWindow isClosing] || ![self isShown])
|
||||
return;
|
||||
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_willClose_)
|
||||
[_delegate popoverWillClose:self];
|
||||
|
||||
[_attachedWindow close];
|
||||
|
||||
// popoverDidClose will be sent from attachedWindowDidClose, since
|
||||
// the attached window will close asynchronously when animating.
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Action
|
||||
|
||||
/*!
|
||||
Close the popover
|
||||
|
||||
@param sender the sender of the action
|
||||
*/
|
||||
- (IBAction)performClose:(id)sender
|
||||
{
|
||||
if ([_attachedWindow isClosing])
|
||||
return;
|
||||
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_shouldClose_)
|
||||
if (![_delegate popoverShouldClose:self])
|
||||
return;
|
||||
|
||||
[self _close];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Delegates
|
||||
|
||||
/*! @ignore */
|
||||
- (BOOL)attachedWindowShouldClose:(_CPAttachedWindow)anAttachedWindow
|
||||
{
|
||||
[self performClose:self];
|
||||
|
||||
// We return NO, because we want the CPPopover to determine
|
||||
// if the attached window can be closed and to give us a chance
|
||||
// to send delegate messages.
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)attachedWindowDidClose:(_CPAttachedWindow)anAttachedWindow
|
||||
{
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_didClose_)
|
||||
[_delegate popoverDidClose:self];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)attachedWindowDidShow:(_CPAttachedWindow)anAttachedWindow
|
||||
{
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_didShow_)
|
||||
[_delegate popoverDidShow:self];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Notifications
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
|
||||
This method is called only when a non-popover parent window
|
||||
is closing. In that case popovers should order out.
|
||||
*/
|
||||
- (void)parentWindowWillClose:(CPNotification)aNotification
|
||||
{
|
||||
[_attachedWindow orderOut:nil];
|
||||
[self performClose:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPPopover (Deprecated)
|
||||
|
||||
- (void)setBehaviour:(int)aBehavior
|
||||
{
|
||||
_CPReportLenientDeprecation(self, _cmd, @selector(setBehavior:));
|
||||
|
||||
[self setBehavior:aBehavior];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPPopoverNeedsNewAttachedWindowKey = @"CPPopoverNeedsNewAttachedWindowKey",
|
||||
CPPopoverAppearanceKey = @"CPPopoverAppearanceKey",
|
||||
CPPopoverAnimatesKey = @"CPPopoverAnimatesKey",
|
||||
CPPopoverContentViewControllerKey = @"CPPopoverContentViewControllerKey",
|
||||
CPPopoverDelegateKey = @"CPPopoverDelegateKey",
|
||||
CPPopoverBehaviorKey = @"CPPopoverBehaviorKey";
|
||||
|
||||
@implementation CPPopover (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_appearance = [aCoder decodeIntForKey:CPPopoverAppearanceKey];
|
||||
_animates = [aCoder decodeBoolForKey:CPPopoverAnimatesKey];
|
||||
_contentViewController = [aCoder decodeObjectForKey:CPPopoverContentViewControllerKey];
|
||||
[self setDelegate:[aCoder decodeObjectForKey:CPPopoverDelegateKey]];
|
||||
[self setBehavior:[aCoder decodeIntForKey:CPPopoverBehaviorKey]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeInt:_appearance forKey:CPPopoverAppearanceKey];
|
||||
[aCoder encodeBool:_animates forKey:CPPopoverAnimatesKey];
|
||||
[aCoder encodeObject:_contentViewController forKey:CPPopoverContentViewControllerKey];
|
||||
[aCoder encodeObject:_delegate forKey:CPPopoverDelegateKey];
|
||||
[aCoder encodeInt:_behavior forKey:CPPopoverBehaviorKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,521 +0,0 @@
|
||||
/*
|
||||
* CPProgressIndicator.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CGGeometry.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPProgressIndicatorStyle
|
||||
*/
|
||||
CPProgressIndicatorBarStyle = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPProgressIndicatorStyle
|
||||
*/
|
||||
CPProgressIndicatorSpinningStyle = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPProgressIndicatorStyle
|
||||
*/
|
||||
CPProgressIndicatorHUDBarStyle = 2;
|
||||
|
||||
var CPProgressIndicatorSpinningStyleColors = nil,
|
||||
|
||||
CPProgressIndicatorClassName = nil,
|
||||
CPProgressIndicatorStyleIdentifiers = nil,
|
||||
CPProgressIndicatorStyleSizes = nil;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPProgressIndicator
|
||||
|
||||
This class is used in a Cappuccino GUI to display the progress of a
|
||||
function or task. If the duration of the task is unknown, there is
|
||||
also an indeterminate mode for the indicator.
|
||||
*/
|
||||
@implementation CPProgressIndicator : CPView
|
||||
{
|
||||
double _minValue;
|
||||
double _maxValue;
|
||||
|
||||
double _doubleValue;
|
||||
|
||||
CPControlSize _controlSize;
|
||||
|
||||
BOOL _isIndeterminate;
|
||||
CPProgressIndicatorStyle _style;
|
||||
|
||||
BOOL _isAnimating;
|
||||
|
||||
BOOL _isDisplayedWhenStoppedSet;
|
||||
BOOL _isDisplayedWhenStopped;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPProgressIndicator class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
CPProgressIndicatorSpinningStyleColors = [];
|
||||
|
||||
CPProgressIndicatorSpinningStyleColors[CPMiniControlSize] = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:
|
||||
[bundle pathForResource:@"CPProgressIndicator/CPProgressIndicatorSpinningStyleMini.gif"] size:CGSizeMake(16.0, 16.0)]];
|
||||
CPProgressIndicatorSpinningStyleColors[CPSmallControlSize] = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:
|
||||
[bundle pathForResource:@"CPProgressIndicator/CPProgressIndicatorSpinningStyleSmall.gif"] size:CGSizeMake(32.0, 32.0)]];
|
||||
CPProgressIndicatorSpinningStyleColors[CPRegularControlSize] = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:
|
||||
[bundle pathForResource:@"CPProgressIndicator/CPProgressIndicatorSpinningStyleRegular.gif"] size:CGSizeMake(64.0, 64.0)]];
|
||||
|
||||
CPProgressIndicatorBezelBorderViewPool = [];
|
||||
|
||||
var start = CPProgressIndicatorBarStyle,
|
||||
end = CPProgressIndicatorHUDBarStyle;
|
||||
|
||||
for (; start <= end; ++start)
|
||||
{
|
||||
CPProgressIndicatorBezelBorderViewPool[start] = [];
|
||||
CPProgressIndicatorBezelBorderViewPool[start][CPMiniControlSize] = [];
|
||||
CPProgressIndicatorBezelBorderViewPool[start][CPSmallControlSize] = [];
|
||||
CPProgressIndicatorBezelBorderViewPool[start][CPRegularControlSize] = [];
|
||||
}
|
||||
|
||||
CPProgressIndicatorClassName = [self className];
|
||||
CPProgressIndicatorStyleIdentifiers = [];
|
||||
|
||||
CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle] = @"Bar";
|
||||
CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorSpinningStyle] = @"Spinny";
|
||||
CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorHUDBarStyle] = @"HUDBar";
|
||||
|
||||
var regularIdentifier = _CPControlIdentifierForControlSize(CPRegularControlSize),
|
||||
smallIdentifier = _CPControlIdentifierForControlSize(CPSmallControlSize),
|
||||
miniIdentifier = _CPControlIdentifierForControlSize(CPMiniControlSize);
|
||||
|
||||
CPProgressIndicatorStyleSizes = [];
|
||||
|
||||
// Bar Style
|
||||
var prefixes = [
|
||||
CPProgressIndicatorClassName + @"BezelBorder" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle],
|
||||
CPProgressIndicatorClassName + @"Bar" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle],
|
||||
CPProgressIndicatorClassName + @"BezelBorder" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorHUDBarStyle],
|
||||
CPProgressIndicatorClassName + @"Bar" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorHUDBarStyle]
|
||||
];
|
||||
|
||||
for (var i = 0, count = prefixes.length; i < count; i++)
|
||||
{
|
||||
var prefix = prefixes[i];
|
||||
CPProgressIndicatorStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(3.0, 16.0), _CGSizeMake(1.0, 16.0), _CGSizeMake(3.0, 16.0)];
|
||||
CPProgressIndicatorStyleSizes[prefix + smallIdentifier] = [_CGSizeMake(3.0, 16.0), _CGSizeMake(1.0, 16.0), _CGSizeMake(3.0, 16.0)];
|
||||
CPProgressIndicatorStyleSizes[prefix + miniIdentifier] = [_CGSizeMake(3.0, 16.0), _CGSizeMake(1.0, 16.0), _CGSizeMake(3.0, 16.0)];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_minValue = 0.0;
|
||||
_maxValue = 100.0;
|
||||
|
||||
_doubleValue = 0.0;
|
||||
|
||||
_style = CPProgressIndicatorBarStyle;
|
||||
_isDisplayedWhenStoppedSet = NO;
|
||||
|
||||
_controlSize = CPRegularControlSize;
|
||||
|
||||
[self updateBackgroundColor];
|
||||
[self drawBar];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (void)setUsesThreadedAnimation:(BOOL)aFlag
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Starts the animation of the progress indicator in indeterminate mode.
|
||||
@param the requesting object
|
||||
*/
|
||||
- (void)startAnimation:(id)aSender
|
||||
{
|
||||
_isAnimating = YES;
|
||||
|
||||
[self _hideOrDisplay];
|
||||
}
|
||||
|
||||
/*!
|
||||
Stops the animation of the progress indicator in indeterminate mode.
|
||||
@param the requesting object
|
||||
*/
|
||||
- (void)stopAnimation:(id)aSender
|
||||
{
|
||||
_isAnimating = NO;
|
||||
|
||||
[self _hideOrDisplay];
|
||||
}
|
||||
|
||||
/*!
|
||||
Always returns \c NO. Cappuccino does not have multiple threads.
|
||||
*/
|
||||
- (BOOL)usesThreadedAnimation
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Advancing the Progress Bar
|
||||
/*!
|
||||
Increases the progress of the bar by the specified value.
|
||||
@param aValue the amount to increase the progress value
|
||||
*/
|
||||
- (void)incrementBy:(double)aValue
|
||||
{
|
||||
[self setDoubleValue:_doubleValue + aValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the progress value of the indicator.
|
||||
*/
|
||||
- (void)setDoubleValue:(double)aValue
|
||||
{
|
||||
_doubleValue = MIN(MAX(aValue, _minValue), _maxValue);
|
||||
|
||||
[self drawBar];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the value of the progress indicator.
|
||||
*/
|
||||
- (double)doubleValue
|
||||
{
|
||||
return _doubleValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the minimum value of the progress indicator. The default is 0.0.
|
||||
@param aValue the new minimum value
|
||||
*/
|
||||
- (void)setMinValue:(double)aValue
|
||||
{
|
||||
_minValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the minimum value of the progress indicator.
|
||||
*/
|
||||
- (double)minValue
|
||||
{
|
||||
return _minValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the maximum value of the progress indicator. The default is 100.0.
|
||||
@param aValue the new maximum value.
|
||||
*/
|
||||
- (void)setMaxValue:(double)aValue
|
||||
{
|
||||
_maxValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the maximum value of the progress indicator.
|
||||
*/
|
||||
- (double)maxValue
|
||||
{
|
||||
return _maxValue;
|
||||
}
|
||||
|
||||
// Setting the Appearance
|
||||
/*!
|
||||
Sets the progress indicator's size.
|
||||
@param aControlSize the new size
|
||||
*/
|
||||
- (void)setControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
if (_controlSize == aControlSize)
|
||||
return;
|
||||
|
||||
_controlSize = aControlSize;
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the progress indicator's size
|
||||
*/
|
||||
- (CPControlSize)controlSize
|
||||
{
|
||||
return _controlSize;
|
||||
}
|
||||
|
||||
/*
|
||||
Not yet implemented
|
||||
*/
|
||||
- (void)setControlTint:(CPControlTint)aControlTint
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
Not yet implemented.
|
||||
*/
|
||||
- (CPControlTint)controlTint
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Not yet implemented.
|
||||
*/
|
||||
- (void)setBezeled:(BOOL)isBezeled
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
Not yet implemented.
|
||||
*/
|
||||
- (BOOL)isBezeled
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Specifies whether this progress indicator should be indeterminate or display progress based on it's max and min.
|
||||
@param isDeterminate \c YES makes the indicator indeterminate
|
||||
*/
|
||||
- (void)setIndeterminate:(BOOL)isIndeterminate
|
||||
{
|
||||
if (_isIndeterminate == isIndeterminate)
|
||||
return;
|
||||
|
||||
_isIndeterminate = isIndeterminate;
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the progress bar is indeterminate.
|
||||
*/
|
||||
- (BOOL)isIndeterminate
|
||||
{
|
||||
return _isIndeterminate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the progress indicator's style
|
||||
@param aStyle the style to set it to
|
||||
*/
|
||||
- (void)setStyle:(CPProgressIndicatorStyle)aStyle
|
||||
{
|
||||
if (_style == aStyle)
|
||||
return;
|
||||
|
||||
_style = aStyle;
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
|
||||
/*!
|
||||
Resizes the indicator based on it's style.
|
||||
*/
|
||||
- (void)sizeToFit
|
||||
{
|
||||
if (_style == CPProgressIndicatorSpinningStyle)
|
||||
[self setFrameSize:[[CPProgressIndicatorSpinningStyleColors[_controlSize] patternImage] size]];
|
||||
else
|
||||
[self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), CPProgressIndicatorStyleSizes[
|
||||
CPProgressIndicatorClassName + @"BezelBorder" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle] + _CPControlIdentifierForControlSize(_controlSize)][0].height)];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the indicator should be displayed when it isn't animating. By default this is \c YES if the style
|
||||
is CPProgressIndicatorBarStyle, and \c NO if it's CPProgressIndicatorSpinningStyle.
|
||||
@param isDisplayedWhenStopped \c YES means the indicator will be displayed when it's not animating.
|
||||
*/
|
||||
- (void)setDisplayedWhenStopped:(BOOL)isDisplayedWhenStopped
|
||||
{
|
||||
if (_isDisplayedWhenStoppedSet && _isDisplayedWhenStopped == isDisplayedWhenStopped)
|
||||
return;
|
||||
|
||||
_isDisplayedWhenStoppedSet = YES;
|
||||
|
||||
_isDisplayedWhenStopped = isDisplayedWhenStopped;
|
||||
|
||||
[self _hideOrDisplay];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the progress bar is displayed when not animating.
|
||||
*/
|
||||
- (BOOL)isDisplayedWhenStopped
|
||||
{
|
||||
if (_isDisplayedWhenStoppedSet)
|
||||
return _isDisplayedWhenStopped;
|
||||
|
||||
if (_style == CPProgressIndicatorBarStyle || _style == CPProgressIndicatorHUDBarStyle)
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_hideOrDisplay
|
||||
{
|
||||
[self setHidden:!_isAnimating && ![self isDisplayedWhenStopped]];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[self drawBar];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)drawBar
|
||||
{
|
||||
if (_style == CPProgressIndicatorSpinningStyle)
|
||||
return;
|
||||
|
||||
var barView = [self layoutEphemeralSubviewNamed:"bar-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
[barView setBackgroundColor:_CPControlThreePartImagePattern(
|
||||
NO,
|
||||
CPProgressIndicatorStyleSizes,
|
||||
CPProgressIndicatorClassName,
|
||||
@"Bar",
|
||||
CPProgressIndicatorStyleIdentifiers[_style],
|
||||
_CPControlIdentifierForControlSize(_controlSize))];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
return [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
|
||||
{
|
||||
if (aViewName === @"bar-view" && _style !== CPProgressIndicatorSpinningStyle)
|
||||
{
|
||||
var width = CGRectGetWidth([self bounds]),
|
||||
barWidth = width * ((_doubleValue - _minValue) / (_maxValue - _minValue));
|
||||
|
||||
if (barWidth > 0.0 && barWidth < 4.0)
|
||||
barWidth = 4.0;
|
||||
|
||||
return _CGRectMake(0, 0, barWidth, 16.0);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)updateBackgroundColor
|
||||
{
|
||||
if (YES)//_isBezeled)
|
||||
{
|
||||
if (_style == CPProgressIndicatorSpinningStyle)
|
||||
{
|
||||
// This will cause the bar view to go away due to having a nil rect when _style == CPProgressIndicatorSpinningStyle.
|
||||
[self layoutEphemeralSubviewNamed:"bar-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
[self setBackgroundColor:CPProgressIndicatorSpinningStyleColors[_controlSize]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setBackgroundColor:_CPControlThreePartImagePattern(
|
||||
NO,
|
||||
CPProgressIndicatorStyleSizes,
|
||||
CPProgressIndicatorClassName,
|
||||
@"BezelBorder",
|
||||
CPProgressIndicatorStyleIdentifiers[_style],
|
||||
_CPControlIdentifierForControlSize(_controlSize))];
|
||||
|
||||
[self drawBar];
|
||||
}
|
||||
}
|
||||
else
|
||||
[self setBackgroundColor:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPProgressIndicator (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_minValue = [aCoder decodeObjectForKey:@"_minValue"];
|
||||
_maxValue = [aCoder decodeObjectForKey:@"_maxValue"];
|
||||
_doubleValue = [aCoder decodeObjectForKey:@"_doubleValue"];
|
||||
_controlSize = [aCoder decodeObjectForKey:@"_controlSize"];
|
||||
_isIndeterminate = [aCoder decodeObjectForKey:@"_isIndeterminate"];
|
||||
_style = [aCoder decodeIntForKey:@"_style"];
|
||||
_isAnimating = [aCoder decodeObjectForKey:@"_isAnimating"];
|
||||
_isDisplayedWhenStoppedSet = [aCoder decodeObjectForKey:@"_isDisplayedWhenStoppedSet"];
|
||||
_isDisplayedWhenStopped = [aCoder decodeObjectForKey:@"_isDisplayedWhenStopped"];
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
// Don't encode the background colour. It can be recreated based on the flags and if encoded causes hardcoded
|
||||
// image paths in the cib while just wasting space.
|
||||
var backgroundColor = [self backgroundColor];
|
||||
[self setBackgroundColor:nil];
|
||||
[super encodeWithCoder:aCoder];
|
||||
[self setBackgroundColor:backgroundColor];
|
||||
|
||||
[aCoder encodeObject:_minValue forKey:@"_minValue"];
|
||||
[aCoder encodeObject:_maxValue forKey:@"_maxValue"];
|
||||
[aCoder encodeObject:_doubleValue forKey:@"_doubleValue"];
|
||||
[aCoder encodeObject:_controlSize forKey:@"_controlSize"];
|
||||
[aCoder encodeObject:_isIndeterminate forKey:@"_isIndeterminate"];
|
||||
[aCoder encodeInt:_style forKey:@"_style"];
|
||||
[aCoder encodeObject:_isAnimating forKey:@"_isAnimating"];
|
||||
[aCoder encodeObject:_isDisplayedWhenStoppedSet forKey:@"_isDisplayedWhenStoppedSet"];
|
||||
[aCoder encodeObject:_isDisplayedWhenStopped forKey:@"_isDisplayedWhenStopped"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,286 +0,0 @@
|
||||
/*
|
||||
* CPRadio.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 <Foundation/CPSet.j>
|
||||
|
||||
@import "CPButton.j"
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
|
||||
from this mailing list thread:
|
||||
http://groups.google.com/group/objectivej/browse_thread/thread/7c41cbd9cbee9ea3
|
||||
|
||||
-----------------------------------
|
||||
|
||||
Creating a checkbox is easy enough:
|
||||
|
||||
checkbox = [[CPCheckBox alloc] initWithFrame:aFrame];
|
||||
|
||||
That's basically all there is to it. Radio buttons are very similar,
|
||||
the key difference is the introduction of a new class CPRadioGroup,
|
||||
which defines which radio buttons are part of the same group:
|
||||
|
||||
[myRadioButton setRadioGroup:aRadioGroup];
|
||||
|
||||
Every radio button receives a unique radio group by default (so if you
|
||||
do nothing further, they will all behave independently), but you can
|
||||
use an existing radio button's group with other buttons as so:
|
||||
|
||||
button1 = [[CPRadio alloc] initWithFrame:aFrame];
|
||||
...
|
||||
button2 = [[CPRadio alloc] initWithFrame:aFrame radioGroup:[button1
|
||||
radioGroup]];
|
||||
...
|
||||
button3 = [[CPRadio alloc] initWithFrame:aFrame radioGroup:[button1
|
||||
radioGroup]];
|
||||
...etc...
|
||||
|
||||
Here, all the radio buttons will act "together". [[button1 radioGroup]
|
||||
allRadios] returns every button that's part of this group, and
|
||||
[[button1 radioGroup] selectedRadio] returns the currently selected
|
||||
option.
|
||||
|
||||
*/
|
||||
|
||||
CPRadioImageOffset = 4.0;
|
||||
|
||||
@implementation CPRadio : CPButton
|
||||
{
|
||||
CPRadioGroup _radioGroup;
|
||||
}
|
||||
|
||||
+ (id)radioWithTitle:(CPString)aTitle theme:(CPTheme)aTheme
|
||||
{
|
||||
return [self buttonWithTitle:aTitle theme:aTheme];
|
||||
}
|
||||
|
||||
+ (id)radioWithTitle:(CPString)aTitle
|
||||
{
|
||||
return [self buttonWithTitle:aTitle];
|
||||
}
|
||||
|
||||
+ (CPButton)standardButtonWithTitle:(CPString)aTitle
|
||||
{
|
||||
var button = [[CPRadio alloc] init];
|
||||
|
||||
[button setTitle:aTitle];
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"radio";
|
||||
}
|
||||
|
||||
// Designated Initializer
|
||||
- (id)initWithFrame:(CGRect)aFrame radioGroup:(CPRadioGroup)aRadioGroup
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setRadioGroup:aRadioGroup];
|
||||
|
||||
[self setHighlightsBy:CPContentsCellMask];
|
||||
[self setShowsStateBy:CPContentsCellMask];
|
||||
|
||||
// Defaults?
|
||||
[self setImagePosition:CPImageLeft];
|
||||
[self setAlignment:CPLeftTextAlignment];
|
||||
|
||||
[self setBordered:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
return [self initWithFrame:aFrame radioGroup:[CPRadioGroup new]];
|
||||
}
|
||||
|
||||
- (CPInteger)nextState
|
||||
{
|
||||
return CPOnState;
|
||||
}
|
||||
|
||||
- (void)setRadioGroup:(CPRadioGroup)aRadioGroup
|
||||
{
|
||||
if (_radioGroup === aRadioGroup)
|
||||
return;
|
||||
|
||||
[_radioGroup _removeRadio:self];
|
||||
_radioGroup = aRadioGroup;
|
||||
[_radioGroup _addRadio:self];
|
||||
}
|
||||
|
||||
- (CPRadioGroup)radioGroup
|
||||
{
|
||||
return _radioGroup;
|
||||
}
|
||||
|
||||
- (void)setObjectValue:(id)aValue
|
||||
{
|
||||
[super setObjectValue:aValue];
|
||||
|
||||
if ([self state] === CPOnState)
|
||||
[_radioGroup _setSelectedRadio:self];
|
||||
}
|
||||
|
||||
- (void)sendAction:(SEL)anAction to:(id)anObject
|
||||
{
|
||||
[super sendAction:anAction to:anObject];
|
||||
|
||||
if (_radioGroup)
|
||||
[CPApp sendAction:[_radioGroup action] to:[_radioGroup target] from:_radioGroup];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
|
||||
|
||||
@implementation CPRadio (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
_radioGroup = [aCoder decodeObjectForKey:CPRadioRadioGroupKey];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_radioGroup forKey:CPRadioRadioGroupKey];
|
||||
}
|
||||
|
||||
- (CPImage)image
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image"];
|
||||
}
|
||||
|
||||
- (CPImage)alternateImage
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image"];
|
||||
}
|
||||
|
||||
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
||||
{
|
||||
var startedTracking = [super startTrackingAt:aPoint];
|
||||
[self highlight:YES];
|
||||
return startedTracking;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPRadioGroup : CPObject
|
||||
{
|
||||
CPSet _radios;
|
||||
CPRadio _selectedRadio;
|
||||
|
||||
id _target @accessors(property=target);
|
||||
SEL _action @accessors(property=action);
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_radios = [CPSet set];
|
||||
_selectedRadio = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_addRadio:(CPRadio)aRadio
|
||||
{
|
||||
[_radios addObject:aRadio];
|
||||
|
||||
if ([aRadio state] === CPOnState)
|
||||
[self _setSelectedRadio:aRadio];
|
||||
}
|
||||
|
||||
- (void)_removeRadio:(CPRadio)aRadio
|
||||
{
|
||||
if (_selectedRadio === aRadio)
|
||||
_selectedRadio = nil;
|
||||
|
||||
[_radios removeObject:aRadio];
|
||||
}
|
||||
|
||||
- (void)_setSelectedRadio:(CPRadio)aRadio
|
||||
{
|
||||
if (_selectedRadio === aRadio)
|
||||
return;
|
||||
|
||||
[_selectedRadio setState:CPOffState];
|
||||
_selectedRadio = aRadio;
|
||||
}
|
||||
|
||||
- (CPRadio)selectedRadio
|
||||
{
|
||||
return _selectedRadio;
|
||||
}
|
||||
|
||||
- (CPArray)radios
|
||||
{
|
||||
return [_radios allObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPRadioGroupRadiosKey = @"CPRadioGroupRadiosKey",
|
||||
CPRadioGroupSelectedRadioKey = @"CPRadioGroupSelectedRadioKey";
|
||||
|
||||
@implementation CPRadioGroup (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_radios = [aCoder decodeObjectForKey:CPRadioGroupRadiosKey];
|
||||
_selectedRadio = [aCoder decodeObjectForKey:CPRadioGroupSelectedRadioKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_radios forKey:CPRadioGroupRadiosKey];
|
||||
[aCoder encodeObject:_selectedRadio forKey:CPRadioGroupSelectedRadioKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,388 +0,0 @@
|
||||
/*
|
||||
* CPResponder.j
|
||||
* AppKit
|
||||
*
|
||||
* 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>
|
||||
|
||||
|
||||
CPDeleteKeyCode = 8;
|
||||
CPTabKeyCode = 9;
|
||||
CPReturnKeyCode = 13;
|
||||
CPEscapeKeyCode = 27;
|
||||
CPSpaceKeyCode = 32;
|
||||
CPPageUpKeyCode = 33;
|
||||
CPPageDownKeyCode = 34;
|
||||
CPLeftArrowKeyCode = 37;
|
||||
CPUpArrowKeyCode = 38;
|
||||
CPRightArrowKeyCode = 39;
|
||||
CPDownArrowKeyCode = 40;
|
||||
CPDeleteForwardKeyCode = 46;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPResponder
|
||||
|
||||
Subclasses of CPResonder can be part of the responder chain.
|
||||
*/
|
||||
@implementation CPResponder : CPObject
|
||||
{
|
||||
CPMenu _menu;
|
||||
CPResponder _nextResponder;
|
||||
}
|
||||
|
||||
// Changing the first responder
|
||||
/*!
|
||||
Returns \c YES if the receiver is able to become the first responder. \c NO otherwise.
|
||||
*/
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that it will become the first responder. The receiver can reject first
|
||||
responder if it returns \c NO. The default implementation always returns \c YES.
|
||||
@return \c YES if the receiver accepts first responder status.
|
||||
*/
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that it has been asked to give up first responder status.
|
||||
@return \c YES if the receiver is willing to give up first responder status.
|
||||
*/
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Setting the next responder
|
||||
/*!
|
||||
Sets the receiver's next responder.
|
||||
@param aResponder the responder after the receiver
|
||||
*/
|
||||
- (void)setNextResponder:(CPResponder)aResponder
|
||||
{
|
||||
_nextResponder = aResponder;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the responder after the receiver.
|
||||
*/
|
||||
- (CPResponder)nextResponder
|
||||
{
|
||||
return _nextResponder;
|
||||
}
|
||||
|
||||
/*!
|
||||
Called to interpret a series of key events.
|
||||
@param events an array of key CPEvents
|
||||
*/
|
||||
- (void)interpretKeyEvents:(CPArray)events
|
||||
{
|
||||
var index = 0,
|
||||
count = [events count];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var event = events[index],
|
||||
modifierFlags = [event modifierFlags],
|
||||
character = [event charactersIgnoringModifiers],
|
||||
selectorNames = [CPKeyBinding selectorsForKey:character modifierFlags:modifierFlags];
|
||||
|
||||
if (selectorNames)
|
||||
{
|
||||
for (var s = 0, scount = selectorNames.length; s < scount; s++)
|
||||
{
|
||||
var selector = selectorNames[s];
|
||||
if (!selector)
|
||||
continue;
|
||||
|
||||
[self doCommandBySelector:CPSelectorFromString(selector)];
|
||||
}
|
||||
}
|
||||
else if (!(modifierFlags & (CPCommandKeyMask | CPControlKeyMask)) && [self respondsToSelector:@selector(insertText:)])
|
||||
[self insertText:[event characters]];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has clicked the mouse down in its area.
|
||||
@param anEvent contains information about the click
|
||||
*/
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has clicked the right mouse down in its area.
|
||||
@param anEvent contains information about the right click
|
||||
*/
|
||||
- (void)rightMouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has initiated a drag
|
||||
over it. A drag is a mouse movement while the left button is down.
|
||||
@param anEvent contains information about the drag
|
||||
*/
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has released the left mouse button.
|
||||
@param anEvent contains information about the release
|
||||
*/
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has released the right mouse button.
|
||||
@param anEvent contains information about the release
|
||||
*/
|
||||
- (void)rightMouseUp:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has moved the mouse (with no buttons down).
|
||||
@param anEvent contains information about the movement
|
||||
*/
|
||||
- (void)mouseMoved:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the mouse exited the receiver's area.
|
||||
@param anEvent contains information about the exit
|
||||
*/
|
||||
- (void)mouseExited:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the mouse scroll wheel has moved.
|
||||
@param anEvent information about the scroll
|
||||
*/
|
||||
- (void)scrollWheel:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has pressed a key.
|
||||
@param anEvent information about the key press
|
||||
*/
|
||||
- (void)keyDown:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has released a key.
|
||||
@param anEvent information about the key press
|
||||
*/
|
||||
- (void)keyUp:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has pressed or released a modifier key (Shift, Control, and so on).
|
||||
@param anEvent information about the key press
|
||||
*/
|
||||
- (void)flagsChanged:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Overridden by subclasses to handle a key equivalent.
|
||||
|
||||
If the character code or codes in \c anEvent match the receiver’s key equivalent,
|
||||
the receiver should respond to the event and return \c YES. The default implementation
|
||||
does nothing and returns \c NO.
|
||||
|
||||
You should extract the characters for a key equivalent using
|
||||
\ref CPEvent::charactersIgnoringModifiers "[anEvent charactersIgnoringModifiers]".
|
||||
|
||||
@param anEvent An event object that represents the key equivalent pressed
|
||||
@return \c YES if theEvent is a key equivalent that the receiver handled, \c NO if it is not a key equivalent that it should handle.
|
||||
*/
|
||||
- (BOOL)performKeyEquivalent:(CPEvent)anEvent
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Action Methods
|
||||
/*!
|
||||
Insert a line break at the caret position or selection.
|
||||
@param aSender the object requesting this
|
||||
*/
|
||||
- (void)insertLineBreak:(id)aSender
|
||||
{
|
||||
[self insertNewline:aSender];
|
||||
}
|
||||
|
||||
/*!
|
||||
Insert a line break at the caret position or selection.
|
||||
@param aSender the object requesting this
|
||||
*/
|
||||
- (void)insertNewline:(id)aSender
|
||||
{
|
||||
[[self nextResponder] insertNewline:aSender];
|
||||
}
|
||||
|
||||
- (void)insertTab:(id)sender
|
||||
{
|
||||
}
|
||||
|
||||
- (void)insertBackTab:(id)sender
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Inserts some text at the caret position or selection.
|
||||
@param aString the string to insert
|
||||
*/
|
||||
- (void)insertText:(CPString)aString
|
||||
{
|
||||
}
|
||||
|
||||
// Dispatch methods
|
||||
/*!
|
||||
The receiver will attempt to perform the command,
|
||||
if it responds to it. If not, the \c -nextResponder will be called to do it.
|
||||
@param aSelector the command to attempt
|
||||
*/
|
||||
- (void)doCommandBySelector:(SEL)aSelector
|
||||
{
|
||||
if ([self respondsToSelector:aSelector])
|
||||
[self performSelector:aSelector];
|
||||
else
|
||||
[_nextResponder doCommandBySelector:aSelector];
|
||||
}
|
||||
|
||||
/*!
|
||||
The receiver will attempt to perform the command, or pass it on to the next responder if it doesn't respond to it.
|
||||
@param aSelector the command to perform
|
||||
@param anObject the argument to the method
|
||||
@return \c YES if the receiver was able to perform the command, or a responder down the chain was
|
||||
able to perform the command.
|
||||
*/
|
||||
- (BOOL)tryToPerform:(SEL)aSelector with:(id)anObject
|
||||
{
|
||||
if ([self respondsToSelector:aSelector])
|
||||
{
|
||||
[self performSelector:aSelector withObject:anObject];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [_nextResponder tryToPerform:aSelector with:anObject];
|
||||
}
|
||||
|
||||
// Managing a Responder's menu
|
||||
|
||||
- (void)setMenu:(CPMenu)aMenu
|
||||
{
|
||||
_menu = aMenu;
|
||||
}
|
||||
|
||||
- (CPMenu)menu
|
||||
{
|
||||
return _menu;
|
||||
}
|
||||
|
||||
// Getting the Undo Manager
|
||||
/*!
|
||||
Returns the undo manager for the receiver.
|
||||
*/
|
||||
- (CPUndoManager)undoManager
|
||||
{
|
||||
return [_nextResponder performSelector:_cmd];
|
||||
}
|
||||
|
||||
// Terminating the responder chain
|
||||
/*!
|
||||
Called when an event finds no suitable responder.
|
||||
@param anEventSelector the command that failed
|
||||
*/
|
||||
- (void)noResponderFor:(SEL)anEventSelector
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPResponderNextResponderKey = @"CPResponderNextResponderKey",
|
||||
CPResponderMenuKey = @"CPResponderMenuKey";
|
||||
|
||||
@implementation CPResponder (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the responder with data from a coder.
|
||||
@param aCoder the coder from which data will be read
|
||||
@return the initialized responder
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setNextResponder:[aCoder decodeObjectForKey:CPResponderNextResponderKey]];
|
||||
[self setMenu:[aCoder decodeObjectForKey:CPResponderMenuKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Archives the responder to a coder.
|
||||
@param aCoder the coder to which the responder will be archived
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
// This will come out nil on the other side with decodeObjectForKey:
|
||||
if (_nextResponder !== nil)
|
||||
[aCoder encodeConditionalObject:_nextResponder forKey:CPResponderNextResponderKey];
|
||||
|
||||
[aCoder encodeObject:_menu forKey:CPResponderMenuKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,553 +0,0 @@
|
||||
/*
|
||||
* CPPredicateEditor.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by cacaodev.
|
||||
* Copyright 2011, cacaodev.
|
||||
*
|
||||
* 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 "CPRuleEditor.j"
|
||||
@import "_CPPredicateEditorTree.j"
|
||||
@import "_CPPredicateEditorRowNode.j"
|
||||
@import "CPPredicateEditorRowTemplate.j"
|
||||
|
||||
@implementation CPPredicateEditor : CPRuleEditor
|
||||
{
|
||||
CPArray _allTemplates;
|
||||
CPArray _rootTrees;
|
||||
CPArray _rootHeaderTrees;
|
||||
id _predicateTarget @accessors(property=target);
|
||||
SEL _predicateAction @accessors(property=action);
|
||||
}
|
||||
|
||||
#pragma mark public methods
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPPredicateEditor
|
||||
|
||||
@brief CPPredicateEditor is a subclass of CPRuleEditor that is specialized for editing CPPredicate objects.
|
||||
|
||||
CPPredicateEditor provides a CPPredicate property—objectValue (inherited from CPControl)—that you can get and set directly, and that you can bind using bindings (you typically configure a predicate editor in Interface Builder). CPPredicateEditor depends on another class, CPPredicateEditorRowTemplate, that describes the available predicates and how to display them.
|
||||
|
||||
Unlike CPRuleEditor, CPPredicateEditor does not depend on its delegate to populate its rows (and does not call the populating delegate methods). Instead, its rows are populated from its objectValue property (an instance of CPPredicate). CPPredicateEditor relies on instances CPPredicateEditorRowTemplate, which are responsible for mapping back and forth between the displayed view values and various predicates.
|
||||
|
||||
CPPredicateEditor exposes one property, rowTemplates, which is an array of CPPredicateEditorRowTemplate objects.
|
||||
*/
|
||||
|
||||
/*!
|
||||
@brief Returns the row templates for the receiver.
|
||||
@return The row templates for the receiver.
|
||||
@discussion Until otherwise set, this contains a single compound CPPredicateEditorRowTemplate object.
|
||||
@see setRowTemplates:
|
||||
*/
|
||||
- (CPArray)rowTemplates
|
||||
{
|
||||
return _allTemplates;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Sets the row templates for the receiver.
|
||||
@param rowTemplates An array of CPPredicateEditorRowTemplate objects.
|
||||
@see rowTemplates
|
||||
*/
|
||||
- (void)setRowTemplates:(id)rowTemplates
|
||||
{
|
||||
if (_allTemplates == rowTemplates)
|
||||
return;
|
||||
|
||||
_allTemplates = rowTemplates;
|
||||
|
||||
[self _updateItemsByCompoundTemplates];
|
||||
[self _updateItemsBySimpleTemplates];
|
||||
|
||||
if ([self numberOfRows] > 0)
|
||||
{
|
||||
var predicate = [super predicate];
|
||||
[self _reflectPredicate:predicate];
|
||||
}
|
||||
}
|
||||
|
||||
/*! @cond */
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (theBinding == CPValueBinding)
|
||||
return [CPPredicateEditorValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
- (id)_replacementKeyPathForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding == CPValueBinding)
|
||||
return @"predicate";
|
||||
|
||||
return [super _replacementKeyPathForBinding:aBinding];
|
||||
}
|
||||
|
||||
- (void)_initRuleEditorShared
|
||||
{
|
||||
[super _initRuleEditorShared];
|
||||
|
||||
_rootTrees = [CPArray array];
|
||||
_rootHeaderTrees = [CPArray array];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
if (self != nil)
|
||||
{
|
||||
var initialTemplate = [[CPPredicateEditorRowTemplate alloc] initWithCompoundTypes:[CPAndPredicateType, CPOrPredicateType]];
|
||||
_allTemplates = [CPArray arrayWithObject:initialTemplate];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)objectValue
|
||||
{
|
||||
return [super predicate];
|
||||
}
|
||||
|
||||
- (void)_updateItemsBySimpleTemplates
|
||||
{
|
||||
var templates = [CPMutableArray array],
|
||||
count = [_allTemplates count],
|
||||
t;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var t = _allTemplates[count];
|
||||
if ([t _rowType] == CPRuleEditorRowTypeSimple)
|
||||
[templates insertObject:t atIndex:0];
|
||||
}
|
||||
|
||||
var trees = [self _constructTreesForTemplates:templates];
|
||||
if ([trees count] > 0)
|
||||
_rootTrees = [self _mergeTree:trees];
|
||||
}
|
||||
|
||||
- (void)_updateItemsByCompoundTemplates
|
||||
{
|
||||
var templates = [CPMutableArray array],
|
||||
count = [_allTemplates count],
|
||||
t;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var t = _allTemplates[count];
|
||||
if ([t _rowType] == CPRuleEditorRowTypeCompound)
|
||||
[templates insertObject:t atIndex:0];
|
||||
}
|
||||
|
||||
var trees = [self _constructTreesForTemplates:templates];
|
||||
if ([trees count] > 0)
|
||||
_rootHeaderTrees = [self _mergeTree:trees];
|
||||
}
|
||||
|
||||
- (CPArray)_constructTreesForTemplates:(id)templates
|
||||
{
|
||||
var trees = [CPMutableArray array],
|
||||
count = [templates count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var tree = [self _constructTreeForTemplate:templates[i]];
|
||||
[trees addObjectsFromArray:tree];
|
||||
}
|
||||
|
||||
return trees;
|
||||
}
|
||||
|
||||
- (CPMutableArray)_mergeTree:(CPArray)aTree
|
||||
{
|
||||
var mergedTree = [CPMutableArray array];
|
||||
if (aTree == nil)
|
||||
return mergedTree;
|
||||
|
||||
var icount = [aTree count];
|
||||
for (var i = 0; i < icount; i++)
|
||||
{
|
||||
var anode = [aTree objectAtIndex:i],
|
||||
jcount = [mergedTree count],
|
||||
merged = NO;
|
||||
|
||||
for (var j = 0; j < jcount; j++)
|
||||
{
|
||||
var mergednode = [mergedTree objectAtIndex:j];
|
||||
|
||||
if ([[mergednode title] isEqualToString:[anode title]])
|
||||
{
|
||||
var children1 = [mergednode children],
|
||||
children2 = [anode children],
|
||||
children12 = [children1 arrayByAddingObjectsFromArray:children2],
|
||||
mergedChildren = [self _mergeTree:children12];
|
||||
|
||||
[mergednode setChildren:mergedChildren];
|
||||
merged = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!merged)
|
||||
[mergedTree addObject:anode];
|
||||
}
|
||||
|
||||
return mergedTree;
|
||||
}
|
||||
|
||||
- (id)_constructTreeForTemplate:(CPPredicateEditorRowTemplate)aTemplate
|
||||
{
|
||||
var tree = [CPArray array],
|
||||
templateViews = [aTemplate templateViews],
|
||||
count = [templateViews count];
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var children = [CPArray array],
|
||||
itemsCount = 0,
|
||||
menuIndex = -1,
|
||||
itemsArray,
|
||||
|
||||
templateView = [templateViews objectAtIndex:count],
|
||||
isPopup = [templateView isKindOfClass:[CPPopUpButton class]];
|
||||
|
||||
if (isPopup)
|
||||
{
|
||||
itemArray = [[templateView itemArray] valueForKey:@"title"];
|
||||
itemsCount = [itemArray count];
|
||||
menuIndex = 0;
|
||||
}
|
||||
|
||||
for (; menuIndex < itemsCount; menuIndex++)
|
||||
{
|
||||
var item = [_CPPredicateEditorTree new];
|
||||
[item setIndexIntoTemplate:count];
|
||||
[item setTemplate:aTemplate];
|
||||
[item setMenuItemIndex:menuIndex];
|
||||
if (isPopup)
|
||||
[item setTitle:[itemArray objectAtIndex:menuIndex]];
|
||||
|
||||
[children addObject:item];
|
||||
}
|
||||
|
||||
[children makeObjectsPerformSelector:@selector(setChildren:) withObject:tree];
|
||||
tree = children;
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
#pragma mark Set the Predicate
|
||||
|
||||
- (void)setObjectValue:(id)objectValue
|
||||
{
|
||||
var ov = [self objectValue];
|
||||
if ((ov == nil) != (objectValue == nil) || ![ov isEqual:objectValue])
|
||||
{
|
||||
[self _setPredicate:objectValue];
|
||||
[self _reflectPredicate:objectValue];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_reflectPredicate:(id)predicate
|
||||
{
|
||||
var animation = _currentAnimation;
|
||||
_currentAnimation = nil;
|
||||
_sendAction = NO;
|
||||
|
||||
if (predicate != nil)
|
||||
{
|
||||
if ((_nestingMode == CPRuleEditorNestingModeSimple || _nestingMode == CPRuleEditorNestingModeCompound)
|
||||
&& [predicate isKindOfClass:[CPComparisonPredicate class]])
|
||||
predicate = [[CPCompoundPredicate alloc] initWithType:[self _compoundPredicateTypeForRootRows] subpredicates:[CPArray arrayWithObject:predicate]];
|
||||
|
||||
var row = [self _rowObjectFromPredicate:predicate];
|
||||
if (row != nil)
|
||||
[_boundArrayOwner setValue:[CPArray arrayWithObject:row] forKey:_boundArrayKeyPath];
|
||||
}
|
||||
|
||||
[self setAnimation:animation];
|
||||
}
|
||||
|
||||
- (id)_rowObjectFromPredicate:(CPPredicate)predicate
|
||||
{
|
||||
var quality, // TODO: We should use this ref somewhere !
|
||||
type,
|
||||
matchedTemplate = [CPPredicateEditorRowTemplate _bestMatchForPredicate:predicate inTemplates:[self rowTemplates] quality:quality];
|
||||
|
||||
if (matchedTemplate == nil)
|
||||
return nil;
|
||||
|
||||
var copyTemplate = [matchedTemplate copy],
|
||||
subpredicates = [matchedTemplate displayableSubpredicatesOfPredicate:predicate];
|
||||
|
||||
if (subpredicates == nil)
|
||||
{
|
||||
[copyTemplate _setComparisonPredicate:predicate];
|
||||
type = CPRuleEditorRowTypeSimple;
|
||||
}
|
||||
else
|
||||
{
|
||||
[copyTemplate _setCompoundPredicate:predicate];
|
||||
type = CPRuleEditorRowTypeCompound;
|
||||
}
|
||||
|
||||
var row = [self _rowFromTemplate:copyTemplate originalTemplate:matchedTemplate withRowType:type];
|
||||
|
||||
if (subpredicates == nil)
|
||||
return row;
|
||||
|
||||
var count = [subpredicates count],
|
||||
subrows = [CPMutableArray array];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var subrow = [self _rowObjectFromPredicate:subpredicates[i]];
|
||||
if (subrow != nil)
|
||||
[subrows addObject:subrow];
|
||||
}
|
||||
|
||||
[row setValue:subrows forKey:[super subrowsKeyPath]];
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
- (id)_rowFromTemplate:(CPPredicateEditorRowTemplate)aTemplate originalTemplate:(CPPredicateEditorRowTemplate)originalTemplate withRowType:(CPRuleEditorRowType)rowType
|
||||
{
|
||||
var criteria = [CPArray array],
|
||||
values = [CPArray array],
|
||||
templateViews = [aTemplate templateViews],
|
||||
rootItems,
|
||||
count;
|
||||
|
||||
rootItems = (rowType == CPRuleEditorRowTypeSimple) ? _rootTrees : _rootHeaderTrees;
|
||||
|
||||
while ((count = [rootItems count]) > 0)
|
||||
{
|
||||
var treeChild;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
treeChild = [rootItems objectAtIndex:i];
|
||||
|
||||
var currentView = [templateViews objectAtIndex:[treeChild indexIntoTemplate]],
|
||||
title = [treeChild title];
|
||||
|
||||
if (title == nil || [title isEqual:[currentView titleOfSelectedItem]])
|
||||
{
|
||||
var node = [_CPPredicateEditorRowNode rowNodeFromTree:treeChild];
|
||||
[node applyTemplate:aTemplate withViews:templateViews forOriginalTemplate:originalTemplate];
|
||||
|
||||
[criteria addObject:node];
|
||||
[values addObject:[node displayValue]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rootItems = [treeChild children];
|
||||
}
|
||||
|
||||
var row = [CPDictionary dictionaryWithObjectsAndKeys:criteria, @"criteria", values, @"displayValues", rowType, @"rowType"];
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
#pragma mark Get the predicate
|
||||
|
||||
- (void)_updatePredicate
|
||||
{
|
||||
[self _updatePredicateFromRows];
|
||||
}
|
||||
|
||||
- (void)_updatePredicateFromRows
|
||||
{
|
||||
var rootRowsArray = [super _rootRowsArray],
|
||||
subpredicates = [CPMutableArray array],
|
||||
count,
|
||||
count2 = count = [rootRowsArray count],
|
||||
predicate;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var item = [rootRowsArray objectAtIndex:count],
|
||||
subpredicate = [self _predicateFromRowItem:item];
|
||||
|
||||
if (subpredicate != nil)
|
||||
[subpredicates insertObject:subpredicate atIndex:0];
|
||||
}
|
||||
|
||||
if (_nestingMode != CPRuleEditorNestingModeList && count2 == 1)
|
||||
predicate = [subpredicates lastObject];
|
||||
else
|
||||
predicate = [[CPCompoundPredicate alloc] initWithType:[self _compoundPredicateTypeForRootRows] subpredicates:subpredicates];
|
||||
|
||||
[self _setPredicate:predicate];
|
||||
}
|
||||
|
||||
- (id)_predicateFromRowItem:(id)rowItem
|
||||
{
|
||||
var subpredicates = [CPArray array],
|
||||
rowType = [rowItem valueForKey:_typeKeyPath];
|
||||
|
||||
if (rowType == CPRuleEditorRowTypeCompound)
|
||||
{
|
||||
var subrows = [rowItem valueForKey:_subrowsArrayKeyPath],
|
||||
count = [subrows count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var subrow = [subrows objectAtIndex:i],
|
||||
predicate = [self _predicateFromRowItem:subrow];
|
||||
|
||||
[subpredicates addObject:predicate];
|
||||
}
|
||||
}
|
||||
|
||||
var criteria = [rowItem valueForKey:_itemsKeyPath],
|
||||
displayValues = [rowItem valueForKey:_valuesKeyPath],
|
||||
count = [criteria count],
|
||||
lastItem = [criteria lastObject],
|
||||
template = [lastItem templateForRow],
|
||||
templateViews = [template templateViews];
|
||||
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
var view = [templateViews objectAtIndex:j],
|
||||
value = [displayValues objectAtIndex:j];
|
||||
[[criteria objectAtIndex:j] setTemplateViews:templateViews];
|
||||
|
||||
if ([view isKindOfClass:[CPPopUpButton class]])
|
||||
[view selectItemWithTitle:value];
|
||||
else if ([view respondsToSelector:@selector(setObjectValue:)])
|
||||
[view setObjectValue:[value objectValue]];
|
||||
}
|
||||
|
||||
return [template predicateWithSubpredicates:subpredicates];
|
||||
}
|
||||
|
||||
- (CPCompoundPredicateType)_compoundPredicateTypeForRootRows
|
||||
{
|
||||
return CPAndPredicateType;
|
||||
}
|
||||
|
||||
#pragma mark Control delegate
|
||||
|
||||
- (void)_sendRuleAction
|
||||
{
|
||||
[super _sendRuleAction];
|
||||
}
|
||||
|
||||
- (BOOL)_sendsActionOnIncompleteTextChange
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*
|
||||
- (void)_setDefaultTargetAndActionOnView:(CPView)view
|
||||
{
|
||||
if ([view isKindOfClass:[CPControl class]])
|
||||
{
|
||||
[view setTarget:self];
|
||||
[view setAction:@selector(_templateControlValueDidChange:)];
|
||||
}
|
||||
}
|
||||
- (void)_templateControlValueDidChange:(id)sender
|
||||
{
|
||||
}
|
||||
- (void)controlTextDidBeginEditing:(CPNotification)notification
|
||||
{
|
||||
}
|
||||
- (void)controlTextDidEndEditing:(CPNotification)notification
|
||||
{
|
||||
}
|
||||
- (void)controlTextDidChange:(CPNotification)notification
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
#pragma mark RuleEditor delegate methods
|
||||
|
||||
- (int)_queryNumberOfChildrenOfItem:(id)rowItem withRowType:(int)type
|
||||
{
|
||||
if (rowItem == nil)
|
||||
{
|
||||
var trees = (type == CPRuleEditorRowTypeSimple) ? _rootTrees : _rootHeaderTrees;
|
||||
return [trees count];
|
||||
}
|
||||
return [[rowItem children] count];
|
||||
}
|
||||
|
||||
- (id)_queryChild:(int)childIndex ofItem:(id)rowItem withRowType:(int)type
|
||||
{
|
||||
if (rowItem == nil)
|
||||
{
|
||||
var trees = (type == CPRuleEditorRowTypeSimple) ? _rootTrees : _rootHeaderTrees;
|
||||
return [_CPPredicateEditorRowNode rowNodeFromTree:trees[childIndex]];
|
||||
}
|
||||
|
||||
return [[rowItem children] objectAtIndex:childIndex];
|
||||
}
|
||||
|
||||
- (id)_queryValueForItem:(id)rowItem inRow:(int)rowIndex
|
||||
{
|
||||
return [rowItem displayValue];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPPredicateTemplatesKey = @"CPPredicateTemplates";
|
||||
|
||||
@implementation CPPredicateEditor (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(id)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self != nil)
|
||||
{
|
||||
var nibTemplates = [aCoder decodeObjectForKey:CPPredicateTemplatesKey];
|
||||
|
||||
if (nibTemplates != nil)
|
||||
[self setRowTemplates:nibTemplates];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(id)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
[aCoder encodeObject:_allTemplates forKey:CPPredicateTemplatesKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPPredicateEditorValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source _reflectPredicate:nil];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source _reflectPredicate:aValue];
|
||||
}
|
||||
|
||||
@end
|
||||
/*! @endcond */
|
||||
@@ -1,790 +0,0 @@
|
||||
/*
|
||||
* CPPredicateEditorRowTemplate.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by cacaodev.
|
||||
* Copyright 2011, cacaodev.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
CPUndefinedAttributeType = 0;
|
||||
CPInteger16AttributeType = 100;
|
||||
CPInteger32AttributeType = 200;
|
||||
CPInteger64AttributeType = 300;
|
||||
CPDecimalAttributeType = 400;
|
||||
CPDoubleAttributeType = 500;
|
||||
CPFloatAttributeType = 600;
|
||||
CPStringAttributeType = 700;
|
||||
CPBooleanAttributeType = 800;
|
||||
CPDateAttributeType = 900;
|
||||
CPBinaryDataAttributeType = 1000;
|
||||
CPTransformableAttributeType = 1800;
|
||||
|
||||
@implementation CPPredicateEditorRowTemplate : CPObject
|
||||
{
|
||||
int _templateType @accessors(readwrite, getter=_templateType, setter=_setTemplateType:);
|
||||
unsigned _predicateOptions @accessors(readwrite, setter=_setOptions:);
|
||||
unsigned _predicateModifier @accessors(readwrite, setter=_setModifier:);
|
||||
unsigned _leftAttributeType @accessors(readwrite, getter=leftAttributeType, setter=_setLeftAttributeType:);
|
||||
unsigned _rightAttributeType @accessors(readwrite, getter=rightAttributeType, setter=_setRightAttributeType:);
|
||||
BOOL _leftIsWildcard @accessors(property=leftIsWildcard);
|
||||
BOOL _rightIsWildcard @accessors(property=rightIsWildcard);
|
||||
CPArray _views @accessors(setter=setTemplateViews:);
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPPredicateEditorRowTemplate
|
||||
|
||||
@brief CPPredicateEditorRowTemplate describes available predicates and how to display them.
|
||||
|
||||
You can create instances of CPPredicateEditorRowTemplate programmatically or in Interface Builder. By default, a non-compound row template has three views: a popup (or static text field) on the left, a popup or static text field for operators, and either a popup or other view on the right. You can subclass CPPredicateEditorRowTemplate to create a row template with different numbers or types of views.
|
||||
|
||||
CPPredicateEditorRowTemplate is a concrete class, but it has five primitive methods which are called by CPPredicateEditor: -#templateViews, -#matchForPredicate:, -#setPredicate:, -#displayableSubpredicatesOfPredicate:, and -#predicateWithSubpredicates:. CPPredicateEditorRowTemplate implements all of them, but you can override them for custom templates. The primitive methods are used by an instance of CPPredicateEditor as follows.
|
||||
|
||||
First, an instance of CPPredicateEditor is created, and some row templates are set on it—either through a nib file or programmatically. The first thing predicate editor does is ask each of the templates for their views, using templateViews.
|
||||
|
||||
After setting up the predicate editor, you typically send it a CPPredicateEditor#setObjectValue: message to restore a saved predicate. CPPredicateEditor needs to determine which of its templates should display each predicate in the predicate tree. It does this by sending each of its row templates a matchForPredicate: message and choosing the one that returns the highest value.
|
||||
|
||||
After finding the best match for a predicate, CPPredicateEditor copies that template to get fresh views, inserts them into the proper row, and then sets the predicate on the template using setPredicate:. Within that method, the CPPredicateEditorRowTemplate object must set its views' values to represent that predicate.
|
||||
|
||||
CPPredicateEditorRowTemplate next asks the template for the “displayable sub-predicates” of the predicate by sending a -#displayableSubpredicatesOfPredicate: message. If a template represents a predicate in its entirety, or if the predicate has no subpredicates, it can return nil for this. Otherwise, it should return a list of predicates to be made into sub-rows of that template's row. The whole process repeats for each sub-predicate.
|
||||
|
||||
At this point, the user sees the predicate that was saved. If the user then makes some changes to the views of the templates, this causes CPPredicateEditor to recompute its predicate by asking each of the templates to return the predicate represented by the new view values, passing in the subpredicates represented by the sub-rows (an empty array if there are none, or nil if they aren't supported by that predicate type).
|
||||
*/
|
||||
|
||||
/*!
|
||||
@name Initializing a Template
|
||||
*/
|
||||
|
||||
/*!
|
||||
@brief Initializes and returns a “pop-up-pop-up-pop-up”-style row template.
|
||||
@param leftExpressions An array of CPExpression objects that represent the left hand side of a predicate.
|
||||
@param rightExpressions An array of CPExpression objects that represent the right hand side of a predicate.
|
||||
@param modifier A modifier for the predicate (see @c CPComparisonPredicateModifier for possible values).
|
||||
@param operators An array of CPNumber objects specifying the operator type (see @c CPPredicateOperatorType for possible values).
|
||||
@param options Options for the predicate (see @c CPComparisonPredicateOptions for possible values).
|
||||
@return A row template of the “pop-up-pop-up-pop-up”-form, with the left and right popups representing the left and right expression arrays -#leftExpressions and -#rightExpressions, and the center popup representing the operators.
|
||||
*/
|
||||
- (id)initWithLeftExpressions:(CPArray)leftExpressions rightExpressions:(CPArray)rightExpressions modifier:(int)modifier operators:(CPArray)operators options:(int)options
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_templateType = 1;
|
||||
_leftIsWildcard = NO;
|
||||
_rightIsWildcard = NO;
|
||||
_leftAttributeType = 0;
|
||||
_rightAttributeType = 0;
|
||||
_predicateModifier = modifier;
|
||||
_predicateOptions = options;
|
||||
|
||||
var leftView = [self _viewFromExpressions:leftExpressions],
|
||||
rightView = [self _viewFromExpressions:rightExpressions],
|
||||
middleView = [self _viewFromOperatorTypes:operators];
|
||||
|
||||
_views = [[CPArray alloc] initWithObjects:leftView, middleView, rightView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Initializes and returns a “pop-up-pop-up-view”-style row template.
|
||||
@param leftExpressions An array of CPExpression objects that represent the left hand side of a predicate.
|
||||
@param attributeType An attribute type for the right hand side of a predicate. This value dictates the type of view created, and how the control’s object value is coerced before putting it into a predicate.
|
||||
@param modifier A modifier for the predicate (see @c CPComparisonPredicateModifier for possible values).
|
||||
@param operators An array of CPNumber objects specifying the operator type (see @c CPPredicateOperatorType for possible values).
|
||||
@param options Options for the predicate (see CPComparisonPredicateOptions for possible values).
|
||||
@return A row template initialized using the given arguments.
|
||||
*/
|
||||
- (id)initWithLeftExpressions:(CPArray )leftExpressions rightExpressionAttributeType:(CPAttributeType)attributeType modifier:(CPComparisonPredicateModifier)modifier operators:(CPArray )operators options:(int)options
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
var leftView = [self _viewFromExpressions:leftExpressions],
|
||||
middleView = [self _viewFromOperatorTypes:operators],
|
||||
rightView = [self _viewFromAttributeType:attributeType];
|
||||
|
||||
_templateType = 1;
|
||||
_leftIsWildcard = NO;
|
||||
_rightIsWildcard = YES;
|
||||
_leftAttributeType = 0;
|
||||
_rightAttributeType = attributeType;
|
||||
_predicateModifier = modifier;
|
||||
_predicateOptions = options;
|
||||
_views = [[CPArray alloc] initWithObjects:leftView, middleView, rightView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Initializes and returns a row template suitable for displaying compound predicates.
|
||||
@param compoundTypes An array of CPNumber objects specifying compound predicate types. See @c CPCompoundPredicateTypes for possible values.
|
||||
@return A row template initialized for displaying compound predicates of the types specified by @a compoundTypes.
|
||||
@discussion CPPredicateEditor contains such a template by default.
|
||||
*/
|
||||
- (id)initWithCompoundTypes:(CPArray )compoundTypes
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
var leftView = [self _viewFromCompoundTypes:compoundTypes],
|
||||
rightView = [[CPPopUpButton alloc] init];
|
||||
|
||||
[rightView addItemWithTitle:@"of the following are true"];
|
||||
|
||||
_templateType = 2;
|
||||
_leftIsWildcard = NO;
|
||||
_rightIsWildcard = NO;
|
||||
_rightAttributeType = 0;
|
||||
_views = [[CPArray alloc] initWithObjects:leftView, rightView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
@name Primitive Methods
|
||||
*/
|
||||
|
||||
/*!
|
||||
@brief Returns a positive number if the receiver can represent a given predicate, and 0 if it cannot.
|
||||
@return A positive number if the template can represent predicate, and @c 0 if it cannot.
|
||||
@discussion By default, returns values in the range @c 0 to @c 1.
|
||||
The highest match among all the templates determines which template is responsible for displaying the predicate. You can override this to determine which predicates your custom template handles.
|
||||
*/
|
||||
- (double)matchForPredicate:(CPPredicate)predicate
|
||||
{
|
||||
// How exactly this value (float 0-1) is computed ?
|
||||
if ([self _templateType] == 2 && [predicate isKindOfClass:[CPCompoundPredicate class]])
|
||||
{
|
||||
if ([[self compoundTypes] containsObject:[predicate compoundPredicateType]])
|
||||
return 1;
|
||||
}
|
||||
else if ([self _templateType] == 1 && [predicate isKindOfClass:[CPComparisonPredicate class]])
|
||||
{
|
||||
if (!_leftIsWildcard && ![[self leftExpressions] containsObject:[predicate leftExpression]])
|
||||
return 0;
|
||||
|
||||
if (![[self operators] containsObject:[predicate predicateOperatorType]])
|
||||
return 0;
|
||||
|
||||
if (!_rightIsWildcard && ![[self rightExpressions] containsObject:[predicate rightExpression]])
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the views for the receiver.
|
||||
@return The views for the receiver.
|
||||
@discussion Instances of CPPopUpButton are treated specially by CPPredicateEditor; their menu items are merged into a single popup button, and matching menu item titles are combined. In this way, a single tree is built from the separate templates.
|
||||
*/
|
||||
- (CPArray)templateViews
|
||||
{
|
||||
return _views;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Sets the value of the views according to the given predicate.
|
||||
@param predicate The predicate value for the receiver.
|
||||
@discussion This method is only called if -#matchForPredicate: returned a positive value for the receiver.
|
||||
|
||||
You can override this to set the values of custom views.
|
||||
*/
|
||||
- (void)setPredicate:(CPPredicate)predicate
|
||||
{
|
||||
if (_templateType == 2)
|
||||
[self _setCompoundPredicate:predicate];
|
||||
else
|
||||
[self _setComparisonPredicate:predicate];
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the subpredicates that should be made sub-rows of a given predicate.
|
||||
@param predicate A predicate object.
|
||||
@return The subpredicates that should be made sub-rows of @a predicate. For compound predicates (instances of CPCompoundPredicate), the array of subpredicates; for other types of predicate, returns @c nil. If a template represents a predicate in its entirety, or if the predicate has no subpredicates, returns @c nil.
|
||||
@discussion You can override this method to create custom templates that handle complicated compound predicates.
|
||||
*/
|
||||
- (CPArray)displayableSubpredicatesOfPredicate:(CPPredicate)predicate
|
||||
{
|
||||
if ([predicate isKindOfClass:[CPCompoundPredicate class]])
|
||||
{
|
||||
var subpredicates = [predicate subpredicates];
|
||||
if ([subpredicates count] == 0)
|
||||
return nil;
|
||||
|
||||
return subpredicates;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the predicate represented by the receiver’s views' values and the given sub-predicates.
|
||||
@param subpredicates An array of predicates.
|
||||
@return The predicate represented by the values of the template's views and the given @a subpredicates. You can override this method to return the predicate represented by your custom views.
|
||||
@discussion This method is only called if -#matchForPredicate: returned a positive value for the receiver.
|
||||
|
||||
You can override this method to return the predicate represented by a custom view.
|
||||
*/
|
||||
- (CPPredicate)predicateWithSubpredicates:(CPArray)subpredicates
|
||||
{
|
||||
if (_templateType == 2)
|
||||
{
|
||||
var type = [[_views[0] selectedItem] representedObject];
|
||||
return [[CPCompoundPredicate alloc] initWithType:type subpredicates:subpredicates];
|
||||
}
|
||||
|
||||
if (_templateType == 1)
|
||||
{
|
||||
var lhs = [self _leftExpression],
|
||||
rhs = [self _rightExpression],
|
||||
operator = [[_views[1] selectedItem] representedObject];
|
||||
|
||||
return [CPComparisonPredicate predicateWithLeftExpression:lhs
|
||||
rightExpression:rhs
|
||||
modifier:[self modifier]
|
||||
type:operator
|
||||
options:[self options]];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
@name Information About a Row Template
|
||||
*/
|
||||
|
||||
/*!
|
||||
@brief Returns the left hand expressions for the receiver.
|
||||
@return The left hand expressions for the receiver.
|
||||
*/
|
||||
- (CPArray)leftExpressions
|
||||
{
|
||||
if (_templateType == 1 && !_leftIsWildcard)
|
||||
{
|
||||
var view = [_views objectAtIndex:0];
|
||||
return [[view itemArray] valueForKey:@"representedObject"];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the right hand expressions for the receiver.
|
||||
@return The right hand expressions for the receiver.
|
||||
*/
|
||||
- (CPArray)rightExpressions
|
||||
{
|
||||
if (_templateType == 1 && !_rightIsWildcard)
|
||||
{
|
||||
var view = [_views objectAtIndex:2];
|
||||
return [[view itemArray] valueForKey:@"representedObject"];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the compound predicate types for the receiver.
|
||||
@return An array of CPNumber objects specifying compound predicate types. See @c CompoundPredicateTypes for possible values.
|
||||
*/
|
||||
- (CPArray)compoundTypes
|
||||
{
|
||||
if (_templateType == 2)
|
||||
{
|
||||
var view = [_views objectAtIndex:0];
|
||||
return [[view itemArray] valueForKey:@"representedObject"];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the comparison predicate modifier for the receiver.
|
||||
@return The comparison predicate modifier for the receiver.
|
||||
*/
|
||||
- (CPComparisonPredicateModifier)modifier
|
||||
{
|
||||
if (_templateType == 1)
|
||||
return _predicateModifier;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns Returns the array of operators for the receiver.
|
||||
@return The array of operators for the receiver.
|
||||
*/
|
||||
- (CPArray)operators
|
||||
{
|
||||
if (_templateType == 1)
|
||||
{
|
||||
var view = [_views objectAtIndex:1];
|
||||
return [[view itemArray] valueForKey:@"representedObject"];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the comparison predicate options for the receiver.
|
||||
@return The comparison predicate options for the receiver. See @c CPComparisonPredicateOptions for possible values. Returns @c 0 if this does not apply (for example, for a compound template initialized with -#initWithCompoundTypes:).
|
||||
*/
|
||||
- (int)options
|
||||
{
|
||||
if (_templateType == 1)
|
||||
return _predicateOptions;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the attribute type of the receiver’s right expression.
|
||||
@return The attribute type of the receiver’s right expression.
|
||||
*/
|
||||
- (CPAttributeType)rightExpressionAttributeType
|
||||
{
|
||||
return _rightAttributeType;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Returns the attribute type of the receiver’s left expression.
|
||||
@return The attribute type of the receiver’s left expression.
|
||||
*/
|
||||
- (CPAttributeType)leftExpressionAttributeType
|
||||
{
|
||||
return _leftAttributeType;
|
||||
}
|
||||
|
||||
/*! @cond */
|
||||
+ (id)_bestMatchForPredicate:(CPPredicate)predicate inTemplates:(CPArray)templates quality:(double)quality
|
||||
{
|
||||
var count = [templates count],
|
||||
match_value = 0,
|
||||
templateIndex = CPNotFound,
|
||||
i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
var template = [templates objectAtIndex:i],
|
||||
amatch = [template matchForPredicate:predicate];
|
||||
|
||||
if (amatch > match_value)
|
||||
{
|
||||
templateIndex = i;
|
||||
match_value = amatch;
|
||||
}
|
||||
}
|
||||
|
||||
if (templateIndex == CPNotFound)
|
||||
{
|
||||
[CPException raise:CPRangeException reason:@"Unable to find template matching predicate: " + [predicate predicateFormat]];
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [templates objectAtIndex:templateIndex];
|
||||
}
|
||||
|
||||
- (void)_setCompoundPredicate:(CPCompoundPredicate)predicate
|
||||
{
|
||||
var left = [_views objectAtIndex:0],
|
||||
type = [predicate compoundPredicateType],
|
||||
index = [left indexOfItemWithRepresentedObject:type];
|
||||
|
||||
[left selectItemAtIndex:index];
|
||||
}
|
||||
|
||||
- (void)_setComparisonPredicate:(CPComparisonPredicate)predicate
|
||||
{
|
||||
var left = [_views objectAtIndex:0],
|
||||
middle = [_views objectAtIndex:1],
|
||||
right = [_views objectAtIndex:2],
|
||||
leftExpression = [predicate leftExpression],
|
||||
rightExpression = [predicate rightExpression],
|
||||
operator = [predicate predicateOperatorType];
|
||||
|
||||
if (_leftIsWildcard)
|
||||
[left setObjectValue:[leftExpression constantValue]];
|
||||
else
|
||||
{
|
||||
var index = [left indexOfItemWithRepresentedObject:leftExpression];
|
||||
[left selectItemAtIndex:index];
|
||||
}
|
||||
|
||||
var op_index = [middle indexOfItemWithRepresentedObject:operator];
|
||||
[middle selectItemAtIndex:op_index];
|
||||
|
||||
if (_rightIsWildcard)
|
||||
[right setObjectValue:[rightExpression constantValue]];
|
||||
else
|
||||
{
|
||||
var index = [right indexOfItemWithRepresentedObject:rightExpression];
|
||||
[right selectItemAtIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPExpression)_leftExpression
|
||||
{
|
||||
return [self _expressionFromView:_views[0] forAttributeType:_leftAttributeType];
|
||||
}
|
||||
|
||||
- (CPExpression)_rightExpression
|
||||
{
|
||||
return [self _expressionFromView:_views[2] forAttributeType:_rightAttributeType];
|
||||
}
|
||||
|
||||
- (CPExpression)_expressionFromView:(CPView)aView forAttributeType:(CPAttributeType)attributeType
|
||||
{
|
||||
if (attributeType == 0)
|
||||
return [[aView selectedItem] representedObject];
|
||||
|
||||
var value;
|
||||
if (attributeType >= CPInteger16AttributeType && attributeType <= CPFloatAttributeType)
|
||||
value = [aView intValue];
|
||||
else if (attributeType == CPBooleanAttributeType)
|
||||
value = [aView state];
|
||||
else
|
||||
value = [aView stringValue];
|
||||
|
||||
return [CPExpression expressionForConstantValue:value];
|
||||
}
|
||||
|
||||
- (int)_rowType
|
||||
{
|
||||
return (_templateType - 1);
|
||||
}
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:self]];
|
||||
}
|
||||
|
||||
+ (id)_operatorsForAttributeType:(CPAttributeType)attributeType
|
||||
{
|
||||
var operators_array = [CPMutableArray array];
|
||||
|
||||
switch (attributeType)
|
||||
{
|
||||
case CPInteger16AttributeType : [operators_array addObjects:4,5,0,2,1,3];
|
||||
break;
|
||||
case CPInteger32AttributeType : [operators_array addObjects:4,5,0,2,1,3];
|
||||
break;
|
||||
case CPInteger64AttributeType : [operators_array addObjects:4,5,0,2,1,3];
|
||||
break;
|
||||
case CPDecimalAttributeType : [operators_array addObjects:4,5,0,2,1,3];
|
||||
break;
|
||||
case CPDoubleAttributeType : [operators_array addObjects:4,5,0,2,1,3];
|
||||
break;
|
||||
case CPFloatAttributeType : [operators_array addObjects:4,5,0,2,1,3];
|
||||
break;
|
||||
case CPStringAttributeType : [operators_array addObjects:99,4,5,8,9];
|
||||
break;
|
||||
case CPBooleanAttributeType : [operators_array addObjects:4,5];
|
||||
break;
|
||||
case CPDateAttributeType : [operators_array addObjects:4,5,0,2,1,3];
|
||||
break;
|
||||
default : CPLogConsole("Cannot create operators for an CPAttributeType " + attributeType);
|
||||
break;
|
||||
}
|
||||
|
||||
return operators_array;
|
||||
}
|
||||
|
||||
- (int)_templateType
|
||||
{
|
||||
return _templateType;
|
||||
}
|
||||
|
||||
- (id)_displayValueForPredicateOperator:(int)operator
|
||||
{
|
||||
var value;
|
||||
|
||||
switch (operator)
|
||||
{
|
||||
case CPLessThanPredicateOperatorType : value = @"is less than";
|
||||
break;
|
||||
case CPLessThanOrEqualToPredicateOperatorType : value = @"is less than or equal to";
|
||||
break;
|
||||
case CPGreaterThanPredicateOperatorType : value = @"is greater than";
|
||||
break;
|
||||
case CPGreaterThanOrEqualToPredicateOperatorType : value = @"is greater than or equal to";
|
||||
break;
|
||||
case CPEqualToPredicateOperatorType : value = @"is";
|
||||
break;
|
||||
case CPNotEqualToPredicateOperatorType : value = @"is not";
|
||||
break;
|
||||
case CPMatchesPredicateOperatorType : value = @"matches";
|
||||
break;
|
||||
case CPLikePredicateOperatorType : value = @"is like";
|
||||
break;
|
||||
case CPBeginsWithPredicateOperatorType : value = @"begins with";
|
||||
break;
|
||||
case CPEndsWithPredicateOperatorType : value = @"ends with";
|
||||
break;
|
||||
case CPInPredicateOperatorType : value = @"in";
|
||||
break;
|
||||
case CPContainsPredicateOperatorType : value = @"contains";
|
||||
break;
|
||||
case CPBetweenPredicateOperatorType : value = @"between";
|
||||
break;
|
||||
default : CPLogConsole(@"unknown predicate operator %d" + operator);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (id)_displayValueForCompoundPredicateType:(unsigned int)predicateType
|
||||
{
|
||||
var value;
|
||||
switch (predicateType)
|
||||
{
|
||||
case CPNotPredicateType: value = @"None";
|
||||
break;
|
||||
case CPAndPredicateType: value = @"All";
|
||||
break;
|
||||
case CPOrPredicateType: value = @"Any";
|
||||
break;
|
||||
default : value = [CPString stringWithFormat:@"unknown compound predicate type %d",predicateType];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (id)_displayValueForConstantValue:(id)value
|
||||
{
|
||||
return [value description]; // number, date, string, ... localize
|
||||
}
|
||||
|
||||
- (id)_displayValueForKeyPath:(CPString)keyPath
|
||||
{
|
||||
return keyPath; // localize
|
||||
}
|
||||
|
||||
- (CPPopUpButton)_viewFromExpressions:(CPArray)expressions
|
||||
{
|
||||
var popup = [[CPPopUpButton alloc] initWithFrame:CPMakeRect(0, 0, 100, 18)],
|
||||
count = [expressions count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var exp = expressions[i],
|
||||
type = [exp expressionType],
|
||||
title;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CPKeyPathExpressionType: title = [self _displayValueForKeyPath:[exp keyPath]];
|
||||
break;
|
||||
case CPConstantValueExpressionType: title = [self _displayValueForConstantValue:[exp constantValue]];
|
||||
break;
|
||||
default: [CPException raise:CPInvalidArgumentException reason:@"Invalid Expression type " + type];
|
||||
break;
|
||||
}
|
||||
|
||||
var item = [[CPMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
|
||||
[item setRepresentedObject:exp];
|
||||
[popup addItem:item];
|
||||
}
|
||||
|
||||
[popup sizeToFit];
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
- (CPPopUpButton)_viewFromOperatorTypes:(CPArray)operators
|
||||
{
|
||||
var popup = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0, 0, 100, 18)],
|
||||
count = [operators count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var op = operators[i],
|
||||
title = [self _displayValueForPredicateOperator:op],
|
||||
item = [[CPMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
|
||||
|
||||
[item setRepresentedObject:op];
|
||||
[popup addItem:item];
|
||||
}
|
||||
|
||||
[popup sizeToFit];
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
- (CPView)_viewFromCompoundTypes:(CPArray)compoundTypes
|
||||
{
|
||||
var popup = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0, 0, 100, 18)],
|
||||
count = [compoundTypes count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var type = compoundTypes[i],
|
||||
title = [self _displayValueForCompoundPredicateType:type],
|
||||
item = [[CPMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
|
||||
|
||||
[item setRepresentedObject:type];
|
||||
[popup addItem:item];
|
||||
}
|
||||
|
||||
[popup sizeToFit];
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
- (CPView)_viewFromAttributeType:(CPAttributeType)attributeType
|
||||
{
|
||||
var view;
|
||||
|
||||
if (attributeType >= CPInteger16AttributeType && attributeType <= CPFloatAttributeType)
|
||||
{
|
||||
view = [self _textFieldWithFrame:CGRectMake(0, 0, 50, 26)];
|
||||
}
|
||||
else if (attributeType == CPStringAttributeType)
|
||||
{
|
||||
view = [self _textFieldWithFrame:CGRectMake(0, 0, 150, 26)];
|
||||
}
|
||||
else if (attributeType == CPBooleanAttributeType)
|
||||
{
|
||||
view = [[CPCheckBox alloc] initWithFrame:CGRectMake(0, 0, 50, 26)];
|
||||
}
|
||||
else if (attributeType == CPDateAttributeType)
|
||||
view = [[CPDatePicker alloc] initWithFrame:CGRectMake(0, 0, 150, 26)];
|
||||
else
|
||||
return nil;
|
||||
|
||||
[view setTag:attributeType];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
- (CPTextField)_textFieldWithFrame:(CGRect)frame
|
||||
{
|
||||
var textField = [[CPTextField alloc] initWithFrame:frame];
|
||||
[textField setBezeled:YES];
|
||||
[textField setBezelStyle:CPTextFieldSquareBezel];
|
||||
[textField setBordered:YES];
|
||||
[textField setEditable:YES];
|
||||
[textField setFont:[CPFont systemFontOfSize:10]];
|
||||
[textField setSendsActionOnEndEditing:YES];
|
||||
|
||||
return textField;
|
||||
}
|
||||
|
||||
- (void)_setOptions:(unsigned int)options
|
||||
{
|
||||
_predicateOptions = options;
|
||||
}
|
||||
|
||||
- (void)_setModifier:(unsigned int)modifier
|
||||
{
|
||||
_predicateModifier = modifier;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
if (_templateType == 2)
|
||||
return [CPString stringWithFormat:@"<%@ %p %@>",[self className],self,[[self compoundTypes] componentsJoinedByString:@", "]];
|
||||
else if (_templateType == 1 && _rightIsWildcard)
|
||||
return [CPString stringWithFormat:@"<%@ %p [%@] [%@] %d>",[self className],self,[[self leftExpressions] componentsJoinedByString:@", "],[[self operators] componentsJoinedByString:@", "],[self rightExpressionAttributeType]];
|
||||
else
|
||||
return [CPString stringWithFormat:@"<%@ %p [%@] [%@] [%@]>",[self className],self,[[self leftExpressions] componentsJoinedByString:@", "],[[self operators] componentsJoinedByString:@", "],[[self rightExpressions] componentsJoinedByString:@", "]];
|
||||
}
|
||||
|
||||
/*
|
||||
- (void)_setLeftExpressionObject:(id)object
|
||||
{
|
||||
}
|
||||
- (void)_setRightExpressionObject:(id)object
|
||||
{
|
||||
}
|
||||
- (BOOL)_predicateIsNoneAreTrue:(id)predicate
|
||||
{
|
||||
}
|
||||
- (id)_viewFromExpressionObject:(id)object
|
||||
{
|
||||
}
|
||||
*/
|
||||
@end
|
||||
|
||||
var CPPredicateTemplateTypeKey = @"CPPredicateTemplateType",
|
||||
CPPredicateTemplateOptionsKey = @"CPPredicateTemplateOptions",
|
||||
CPPredicateTemplateModifierKey = @"CPPredicateTemplateModifier",
|
||||
CPPredicateTemplateLeftAttributeTypeKey = @"CPPredicateTemplateLeftAttributeType",
|
||||
CPPredicateTemplateRightAttributeTypeKey = @"CPPredicateTemplateRightAttributeType",
|
||||
CPPredicateTemplateLeftIsWildcardKey = @"CPPredicateTemplateLeftIsWildcard",
|
||||
CPPredicateTemplateRightIsWildcardKey = @"CPPredicateTemplateRightIsWildcard",
|
||||
CPPredicateTemplateViewsKey = @"CPPredicateTemplateViews";
|
||||
|
||||
@implementation CPPredicateEditorRowTemplate (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_templateType = [coder decodeIntForKey:CPPredicateTemplateTypeKey];
|
||||
_predicateOptions = [coder decodeIntForKey:CPPredicateTemplateOptionsKey];
|
||||
_predicateModifier = [coder decodeIntForKey:CPPredicateTemplateModifierKey];
|
||||
_leftAttributeType = [coder decodeIntForKey:CPPredicateTemplateLeftAttributeTypeKey];
|
||||
_rightAttributeType = [coder decodeIntForKey:CPPredicateTemplateRightAttributeTypeKey];
|
||||
_leftIsWildcard = [coder decodeBoolForKey:CPPredicateTemplateLeftIsWildcardKey];
|
||||
_rightIsWildcard = [coder decodeBoolForKey:CPPredicateTemplateRightIsWildcardKey];
|
||||
_views = [coder decodeObjectForKey:CPPredicateTemplateViewsKey];
|
||||
|
||||
// In Xcode 4, when the menu item title == template's expression keypath, representedObject is empty.
|
||||
// So we need to regenerate expressions from titles.
|
||||
if (_templateType == 1 && _leftIsWildcard == NO)
|
||||
{
|
||||
var itemArray = [_views[0] itemArray],
|
||||
count = [itemArray count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var item = itemArray[i];
|
||||
if ([item representedObject] == nil)
|
||||
{
|
||||
var exp = [CPExpression expressionForKeyPath:[item title]];
|
||||
[item setRepresentedObject:exp];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[coder encodeInt:_templateType forKey:CPPredicateTemplateTypeKey];
|
||||
[coder encodeInt:_predicateOptions forKey:CPPredicateTemplateOptionsKey];
|
||||
[coder encodeInt:_predicateModifier forKey:CPPredicateTemplateModifierKey];
|
||||
[coder encodeInt:_leftAttributeType forKey:CPPredicateTemplateLeftAttributeTypeKey];
|
||||
[coder encodeInt:_rightAttributeType forKey:CPPredicateTemplateRightAttributeTypeKey];
|
||||
[coder encodeBool:_leftIsWildcard forKey:CPPredicateTemplateLeftIsWildcardKey];
|
||||
[coder encodeBool:_rightIsWildcard forKey:CPPredicateTemplateRightIsWildcardKey];
|
||||
[coder encodeObject:_views forKey:CPPredicateTemplateViewsKey];
|
||||
}
|
||||
|
||||
@end
|
||||
/*! @endcond */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@class _CPPredicateEditorTree;
|
||||
@implementation _CPPredicateEditorRowNode : CPObject
|
||||
{
|
||||
_CPPredicateEditorTree tree @accessors;
|
||||
CPMutableArray templateViews @accessors;
|
||||
CPMutableArray copiedTemplateContainer @accessors;
|
||||
CPArray children @accessors(copy);
|
||||
}
|
||||
|
||||
+ (id)rowNodeFromTree:(id)aTree
|
||||
{
|
||||
return [_CPPredicateEditorRowNode _rowNodeFromTree:aTree withTemplateTable:{}];
|
||||
}
|
||||
|
||||
+ (id)_rowNodeFromTree:(id)aTree withTemplateTable:(id)templateTable
|
||||
{
|
||||
var views,
|
||||
copiedContainer;
|
||||
|
||||
var uuid = [[aTree template] UID],
|
||||
cachedNode = templateTable[uuid],
|
||||
node = [[_CPPredicateEditorRowNode alloc] init];
|
||||
|
||||
[node setTree:aTree];
|
||||
|
||||
if (!cachedNode)
|
||||
{
|
||||
views = [CPMutableArray array];
|
||||
copiedContainer = [CPMutableArray array];
|
||||
templateTable[uuid] = node;
|
||||
}
|
||||
else
|
||||
{
|
||||
views = [cachedNode templateViews];
|
||||
copiedContainer = [cachedNode copiedTemplateContainer];
|
||||
}
|
||||
|
||||
[node setTemplateViews:views];
|
||||
[node setCopiedTemplateContainer:copiedContainer];
|
||||
|
||||
var nodeChildren = [CPMutableArray array],
|
||||
treeChildren = [aTree children],
|
||||
count = [treeChildren count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var treeChild = treeChildren[i],
|
||||
child = [_CPPredicateEditorRowNode _rowNodeFromTree:treeChild withTemplateTable:templateTable];
|
||||
|
||||
[nodeChildren addObject:child];
|
||||
}
|
||||
|
||||
[node setChildren:nodeChildren];
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
- (BOOL)applyTemplate:(id)template withViews:(id)views forOriginalTemplate:(id)originalTemplate
|
||||
{
|
||||
var t = [tree template],
|
||||
count = [children count];
|
||||
|
||||
if (t !== template)
|
||||
{
|
||||
[templateViews setArray:views];
|
||||
[copiedTemplateContainer removeAllObjects];
|
||||
[copiedTemplateContainer addObject:template];
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
[children[i] applyTemplate:template withViews:views forOriginalTemplate:originalTemplate];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)node
|
||||
{
|
||||
if (![node isKindOfClass:[_CPPredicateEditorRowNode class]])
|
||||
return NO;
|
||||
|
||||
return (tree === [node tree]);
|
||||
}
|
||||
|
||||
- (void)copyTemplateIfNecessary
|
||||
{
|
||||
if ([copiedTemplateContainer count] === 0)
|
||||
{
|
||||
var copy = [[tree template] copy];
|
||||
[copiedTemplateContainer addObject:copy];
|
||||
[templateViews addObjectsFromArray:[copy templateViews]];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPView)templateView
|
||||
{
|
||||
[self copyTemplateIfNecessary];
|
||||
return [templateViews objectAtIndex:[tree indexIntoTemplate]];
|
||||
}
|
||||
|
||||
- (id)templateForRow
|
||||
{
|
||||
[self copyTemplateIfNecessary];
|
||||
return [copiedTemplateContainer lastObject];
|
||||
}
|
||||
|
||||
- (CPString)title
|
||||
{
|
||||
return [tree title];
|
||||
}
|
||||
|
||||
- (id)displayValue
|
||||
{
|
||||
var title = [self title];
|
||||
|
||||
if (title !== nil)
|
||||
return title;
|
||||
|
||||
return [self templateView];
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return [CPString stringWithFormat:@"<%@ %@ %@ tree:%@ tviews:%@", [self className],[self UID], [self title], [tree UID], [templateViews description]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@implementation _CPPredicateEditorTree : CPObject
|
||||
{
|
||||
CPPredicateEditorRowTemplate template @accessors;
|
||||
CPString title @accessors(copy);
|
||||
CPArray children @accessors(copy);
|
||||
CPInteger indexIntoTemplate @accessors;
|
||||
CPInteger menuItemIndex @accessors;
|
||||
}
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var tree = [[_CPPredicateEditorTree alloc] init];
|
||||
[tree setTemplate:template];
|
||||
[tree setTitle:title];
|
||||
[tree setMenuItemIndex:menuItemIndex];
|
||||
[tree setIndexIntoTemplate:indexIntoTemplate];
|
||||
[tree setChildren:children];
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return [CPString stringWithFormat:@"<%@: %p (%@) [%d-%d] T:%p at:%d> [\r%@\r]", [self className], self, title, indexIntoTemplate, menuItemIndex, template, [template rightExpressionAttributeType], children];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)?");
|
||||
|
||||
@implementation _CPRuleEditorLocalizer : CPObject
|
||||
{
|
||||
CPDictionary _dictionary @accessors(property=dictionary);
|
||||
CPURLConnection connection;
|
||||
CPURLRequest resquest;
|
||||
}
|
||||
|
||||
- (void)loadContentOfURL:(CPURL)aURL
|
||||
{
|
||||
request = [CPURLRequest requestWithURL:aURL];
|
||||
connection = [CPURLConnection connectionWithRequest:request delegate:self];
|
||||
}
|
||||
|
||||
- (void)reloadIfNeeded
|
||||
{
|
||||
if (connection !== nil) // Connection waiting
|
||||
{
|
||||
connection = nil;
|
||||
|
||||
var data = [CPURLConnection sendSynchronousRequest:request returningResponse:NULL];
|
||||
[self loadContent:[data rawString]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)rawString
|
||||
{
|
||||
if (connection !== nil && rawString !== nil)
|
||||
[self loadContent:rawString];
|
||||
|
||||
connection = nil;
|
||||
}
|
||||
|
||||
- (void)loadContent:(CPString)aContent
|
||||
{
|
||||
var dict = [CPDictionary dictionary],
|
||||
lines = [aContent componentsSeparatedByString:"\n"],
|
||||
count = [lines count];
|
||||
|
||||
for (var i = 0 ; i < count ; i++)
|
||||
{
|
||||
var line = [lines objectAtIndex:i];
|
||||
|
||||
if (line.length > 1)
|
||||
{
|
||||
var match = LocalizerStringsRegex.exec(line);
|
||||
|
||||
if (match && match.length >= 3)
|
||||
[dict setObject:match[2] forKey:match[1]];
|
||||
}
|
||||
}
|
||||
|
||||
_dictionary = [CPDictionary dictionaryWithDictionary:dict];
|
||||
}
|
||||
|
||||
- (CPString)localizedStringForString:(CPString)aString
|
||||
{
|
||||
[self reloadIfNeeded];
|
||||
|
||||
if (_dictionary !== nil && aString !== nil)
|
||||
{
|
||||
var localized = [_dictionary objectForKey:aString];
|
||||
|
||||
if (localized !== nil)
|
||||
return localized;
|
||||
}
|
||||
|
||||
return aString;
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
var GRADIENT_START_COLOR = "#fcfcfc",
|
||||
GRADIENT_END_COLOR = "#dfdfdf",
|
||||
BORDER_COLOR = "#BDBDBD";
|
||||
|
||||
var GRADIENT_NORMAL,
|
||||
GRADIENT_HIGHLIGHTED,
|
||||
GRADIENT_PROPERTY;
|
||||
|
||||
if (CPBrowserIsEngine(CPWebKitBrowserEngine))
|
||||
{
|
||||
GRADIENT_NORMAL = "-webkit-gradient(linear, left top, left bottom, from(" + GRADIENT_START_COLOR + "), to(" + GRADIENT_END_COLOR + "))",
|
||||
GRADIENT_HIGHLIGHTED = "-webkit-gradient(linear, left top, left bottom, from(" + GRADIENT_END_COLOR + "), to(" + GRADIENT_START_COLOR + "))";
|
||||
GRADIENT_PROPERTY = "background";
|
||||
}
|
||||
else if (CPBrowserIsEngine(CPGeckoBrowserEngine))
|
||||
{
|
||||
GRADIENT_NORMAL = "-moz-linear-gradient(top, " + GRADIENT_START_COLOR + ", " + GRADIENT_END_COLOR + ")",
|
||||
GRADIENT_HIGHLIGHTED = "-moz-linear-gradient(top, " + GRADIENT_END_COLOR + ", " + GRADIENT_START_COLOR + ")";
|
||||
GRADIENT_PROPERTY = "background";
|
||||
}
|
||||
else if (CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
GRADIENT_NORMAL = "progid:DXImageTransform.Microsoft.gradient(startColorstr='" + GRADIENT_START_COLOR + "', endColorstr='" + GRADIENT_END_COLOR + "')";
|
||||
GRADIENT_HIGHLIGHTED = "progid:DXImageTransform.Microsoft.gradient(startColorstr='" + GRADIENT_END_COLOR + "', endColorstr='" + GRADIENT_START_COLOR + "')";
|
||||
GRADIENT_PROPERTY = "filter";
|
||||
}else
|
||||
{
|
||||
GRADIENT_NORMAL = GRADIENT_START_COLOR;
|
||||
GRADIENT_HIGHLIGHTED = GRADIENT_END_COLOR;
|
||||
GRADIENT_PROPERTY = "background";
|
||||
}
|
||||
|
||||
@implementation _CPRuleEditorPopUpButton : CPPopUpButton
|
||||
{
|
||||
CPInteger radius;
|
||||
}
|
||||
|
||||
- (void)_sharedInit
|
||||
{
|
||||
[self setBordered:NO];
|
||||
|
||||
var style = _DOMElement.style;
|
||||
style.border = "1px solid " + BORDER_COLOR;
|
||||
style[GRADIENT_PROPERTY] = GRADIENT_NORMAL;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
[self _sharedInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
[self _sharedInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)hitTest:(CPPoint)point
|
||||
{
|
||||
if (!CPRectContainsPoint([self frame], point) || ![self sliceIsEditable])
|
||||
return nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)sliceIsEditable
|
||||
{
|
||||
var superview = [self superview];
|
||||
return ![superview isKindOfClass:[_CPRuleEditorViewSlice]] || [superview isEditable];
|
||||
}
|
||||
|
||||
- (BOOL)trackMouse:(CPEvent)theEvent
|
||||
{
|
||||
if (![self sliceIsEditable])
|
||||
return NO;
|
||||
|
||||
return [super trackMouse:theEvent];
|
||||
}
|
||||
|
||||
- (CGRect)contentRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var contentRect = [super contentRectForBounds:bounds];
|
||||
contentRect.origin.x += radius;
|
||||
contentRect.size.width -= 2 * radius;
|
||||
|
||||
return contentRect;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
radius = FLOOR(CGRectGetHeight([self bounds]) / 2);
|
||||
var style = _DOMElement.style,
|
||||
radiusCSS = radius + "px";
|
||||
|
||||
style.borderRadius = radiusCSS;
|
||||
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds],
|
||||
arrow_width = FLOOR(CGRectGetHeight(bounds) / 3.5);
|
||||
|
||||
CGContextTranslateCTM(context, CGRectGetWidth(bounds) - radius - arrow_width, CGRectGetHeight(bounds) / 2);
|
||||
|
||||
var arrowsPath = [CPBezierPath bezierPath];
|
||||
[arrowsPath moveToPoint:CGPointMake(0, 1)];
|
||||
[arrowsPath lineToPoint:CGPointMake(arrow_width, 1)];
|
||||
[arrowsPath lineToPoint:CGPointMake(arrow_width / 2, arrow_width + 1)];
|
||||
[arrowsPath closePath];
|
||||
|
||||
CGContextSetFillColor(context, [CPColor colorWithWhite:101 / 255 alpha:1]);
|
||||
[arrowsPath fill];
|
||||
|
||||
CGContextScaleCTM(context, 1 , -1);
|
||||
[arrowsPath fill];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPRuleEditorButton : CPButton
|
||||
{
|
||||
CPInteger radius;
|
||||
}
|
||||
|
||||
- (void)_sharedInit
|
||||
{
|
||||
[self setBordered:NO];
|
||||
|
||||
var style = _DOMElement.style;
|
||||
style.border = "1px solid " + BORDER_COLOR;
|
||||
style[GRADIENT_PROPERTY] = GRADIENT_NORMAL;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
[self _sharedInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
[self _sharedInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
radius = FLOOR(CGRectGetHeight([self bounds]) / 2);
|
||||
|
||||
var style = _DOMElement.style,
|
||||
radiusCSS = radius + "px";
|
||||
|
||||
style.borderRadius = radiusCSS;
|
||||
style[GRADIENT_PROPERTY] = ([self isHighlighted]) ? GRADIENT_HIGHLIGHTED : GRADIENT_NORMAL;
|
||||
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@implementation _CPRuleEditorViewSlice : CPView
|
||||
{
|
||||
CPRuleEditor _ruleEditor;
|
||||
int _indentation @accessors(property=indentation);
|
||||
int _rowIndex @accessors(property=rowIndex);
|
||||
CPRect _animationTargetRect @accessors(property=_animationTargetRect);
|
||||
BOOL _selected @accessors(getter=_isSelected, setter=_setSelected:);
|
||||
BOOL _lastSelected @accessors(getter=_isLastSelected, setter=_setLastSelected:);
|
||||
CPColor _backgroundColor @accessors(property=backgroundColor);
|
||||
}
|
||||
|
||||
- (void)removeFromSuperview
|
||||
{
|
||||
[super removeFromSuperview];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame ruleEditorView:(id)editor
|
||||
{
|
||||
if (self = [super initWithFrame:frame])
|
||||
{
|
||||
_ruleEditor = editor;
|
||||
_selected = NO;
|
||||
_lastSelected = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_setSelected:(BOOL)select
|
||||
{
|
||||
if (select == _selected)
|
||||
return;
|
||||
|
||||
var selector = select ? @"setThemeState:" : @"unsetThemeState:";
|
||||
[[self subviews] makeObjectsPerformSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelectedDataView];
|
||||
_selected = select;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CPRect)rect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds],
|
||||
maxX = CGRectGetWidth(bounds),
|
||||
maxY = CGRectGetHeight(bounds);
|
||||
|
||||
// Draw background
|
||||
if ([self _isSelected])
|
||||
_backgroundColor = [_ruleEditor _selectedRowColor];
|
||||
else
|
||||
{
|
||||
var colors = [_ruleEditor _backgroundColors],
|
||||
count = [colors count];
|
||||
_backgroundColor = [colors objectAtIndex:(_rowIndex % count)];
|
||||
}
|
||||
|
||||
CGContextSetFillColor(context, _backgroundColor);
|
||||
CGContextFillRect(context, rect);
|
||||
|
||||
// Draw Top Border
|
||||
CGContextBeginPath(context);
|
||||
CGContextMoveToPoint(context, 0, 0);
|
||||
CGContextAddLineToPoint(context, maxX, 0);
|
||||
CGContextClosePath(context);
|
||||
CGContextSetStrokeColor(context, [_ruleEditor _sliceTopBorderColor]);
|
||||
CGContextStrokePath(context);
|
||||
|
||||
// Draw Bottom Border
|
||||
CGContextBeginPath(context);
|
||||
CGContextMoveToPoint(context, 0, maxY);
|
||||
CGContextAddLineToPoint(context, maxX, maxY);
|
||||
CGContextClosePath(context);
|
||||
var bottomColor = (_rowIndex == [_ruleEditor _lastRow]) ? [_ruleEditor _sliceLastBottomBorderColor] : [_ruleEditor _sliceBottomBorderColor];
|
||||
CGContextSetStrokeColor(context, bottomColor);
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)theEvent
|
||||
{
|
||||
if (editable)
|
||||
[_ruleEditor _mouseDownOnSlice:self withEvent:theEvent];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)theEvent
|
||||
{
|
||||
if (editable)
|
||||
[_ruleEditor _mouseUpOnSlice:self withEvent:theEvent];
|
||||
}
|
||||
|
||||
/*
|
||||
- (void)rightMouseDown:(CPEvent)theEvent
|
||||
{
|
||||
[_ruleEditor _rightMouseDownOnSlice:self withEvent:theEvent];
|
||||
}
|
||||
*/
|
||||
|
||||
// =========
|
||||
// ! DEBUG
|
||||
// =========
|
||||
- (CPString)description
|
||||
{
|
||||
return [CPString stringWithFormat:@"<%@ %p index:%d indentation:%d>",[self className],self,[self rowIndex],[self indentation]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,534 +0,0 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@import "_CPRuleEditorViewSlice.j"
|
||||
@import "_CPRuleEditorPopUpButton.j"
|
||||
@import "CPRuleEditor.j"
|
||||
|
||||
var CONTROL_HEIGHT = 16.,
|
||||
BUTTON_HEIGHT = 16.;
|
||||
|
||||
@implementation _CPRuleEditorViewSliceRow : _CPRuleEditorViewSlice
|
||||
{
|
||||
CPMutableArray _ruleOptionViews;
|
||||
CPMutableArray _ruleOptionFrames;
|
||||
CPMutableArray _correspondingRuleItems;
|
||||
CPMutableArray _ruleOptionInitialViewFrames;
|
||||
CPButton _addButton;
|
||||
CPButton _subtractButton;
|
||||
BOOL editable;
|
||||
CPRuleEditorRowType _rowType @accessors;
|
||||
CPRuleEditorRowType _plusButtonRowType;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame ruleEditorView:(id)editor
|
||||
{
|
||||
if (self = [super initWithFrame:frame ruleEditorView:editor])
|
||||
[self _initShared];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_initShared
|
||||
{
|
||||
_correspondingRuleItems = [[CPMutableArray alloc] init];
|
||||
_ruleOptionFrames = [[CPMutableArray alloc] init];
|
||||
_ruleOptionInitialViewFrames = [[CPMutableArray alloc] init];
|
||||
_ruleOptionViews = [[CPMutableArray alloc] init];
|
||||
editable = [_ruleEditor isEditable];
|
||||
|
||||
_addButton = [self _createAddRowButton];
|
||||
_subtractButton = [self _createDeleteRowButton];
|
||||
[_addButton setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
|
||||
[_subtractButton setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
|
||||
[_addButton setHidden:!editable];
|
||||
[_subtractButton setHidden:!editable];
|
||||
[self addSubview:_addButton];
|
||||
[self addSubview:_subtractButton];
|
||||
|
||||
[self setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
var center = [CPNotificationCenter defaultCenter];
|
||||
[center addObserver:self selector:@selector(_textDidChange:) name:CPControlTextDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (CPButton)_createRowButton
|
||||
{
|
||||
var button = [[_CPRuleEditorButton alloc] initWithFrame:CGRectMakeZero()];
|
||||
[button setFont:[CPFont boldFontWithName:@"Apple Symbol" size:12.0]];
|
||||
[button setTextColor:[CPColor colorWithWhite:150 / 255 alpha:1]];
|
||||
[button setAlignment:CPCenterTextAlignment];
|
||||
[button setAutoresizingMask:CPViewMinXMargin];
|
||||
[button setImagePosition:CPImageOnly];
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
- (CPButton)_createAddRowButton
|
||||
{
|
||||
var button = [self _createRowButton];
|
||||
|
||||
[button setImage:[_ruleEditor _addImage]];
|
||||
[button setAction:@selector(_addOption:)];
|
||||
[button setTarget:self];
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
- (CPButton)_createDeleteRowButton
|
||||
{
|
||||
var button = [self _createRowButton];
|
||||
|
||||
[button setImage:[_ruleEditor _removeImage]];
|
||||
[button setAction:@selector(_deleteOption:)];
|
||||
[button setTarget:self];
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
- (CPMenuItem)_createMenuItemWithTitle:(CPString )title
|
||||
{
|
||||
title = [[_ruleEditor standardLocalizer] localizedStringForString:title];
|
||||
var mItem = [[CPMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
|
||||
return mItem;
|
||||
}
|
||||
|
||||
- (CPPopUpButton)_createPopUpButtonWithItems:(CPArray)itemsArray selectedItemIndex:(int)index
|
||||
{
|
||||
var title = [[itemsArray objectAtIndex:index] title],
|
||||
font = [_ruleEditor font],
|
||||
width = [title sizeWithFont:font].width + 20,
|
||||
rect = CGRectMake(0, 0, (width - width % 40) + 80, CONTROL_HEIGHT),
|
||||
|
||||
popup = [[_CPRuleEditorPopUpButton alloc] initWithFrame:rect];
|
||||
|
||||
[popup setTextColor:[CPColor colorWithWhite:101 / 255 alpha:1]];
|
||||
[popup setValue:font forThemeAttribute:@"font"];
|
||||
|
||||
var count = [itemsArray count];
|
||||
for (var i = 0; i < count; i++)
|
||||
[popup addItem:[itemsArray objectAtIndex:i]];
|
||||
|
||||
[popup selectItemAtIndex:index];
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
- (CPMenuItem)_createMenuSeparatorItem
|
||||
{
|
||||
return [CPMenuItem separatorItem];
|
||||
}
|
||||
|
||||
- (_CPRuleEditorTextField)_createStaticTextFieldWithStringValue:(CPString)text
|
||||
{
|
||||
var textField = [[_CPRuleEditorTextField alloc] initWithFrame:CPMakeRect(0, 0, 200, CONTROL_HEIGHT)],
|
||||
refont = [_ruleEditor font],
|
||||
font = [CPFont fontWithName:[refont familyName] size:[refont size] + 2],
|
||||
|
||||
localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text];
|
||||
|
||||
[textField setValue:font forThemeAttribute:@"font"];
|
||||
[textField setStringValue:localizedText];
|
||||
[textField sizeToFit];
|
||||
|
||||
return textField;
|
||||
}
|
||||
|
||||
- (void)_addOption:(id)sender
|
||||
{
|
||||
if (_rowIndex == [_ruleEditor numberOfRows] - 1)
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
var type = _plusButtonRowType;
|
||||
if ([_ruleEditor nestingMode] == CPRuleEditorNestingModeCompound && ([[CPApp currentEvent] modifierFlags] & CPAlternateKeyMask))
|
||||
type = CPRuleEditorRowTypeCompound;
|
||||
|
||||
[_ruleEditor _addOptionFromSlice:self ofRowType:type];
|
||||
}
|
||||
|
||||
- (void)_deleteOption:(id)sender
|
||||
{
|
||||
[_ruleEditor _deleteSlice:self];
|
||||
}
|
||||
|
||||
- (void)_ruleOptionPopupChangedAction:(CPMenuItem )sender
|
||||
{
|
||||
var layoutdict = [sender representedObject],
|
||||
newItem = [layoutdict objectForKey:@"item"],
|
||||
indexInCriteria = [layoutdict objectForKey:@"indexInCriteria"],
|
||||
oldItem = [_correspondingRuleItems objectAtIndex:indexInCriteria];
|
||||
|
||||
if (![newItem isEqual:oldItem])
|
||||
{
|
||||
[_correspondingRuleItems replaceObjectAtIndex:indexInCriteria withObject:newItem];
|
||||
[_ruleEditor _changedItem:oldItem toItem:newItem inRow:_rowIndex atCriteriaIndex:indexInCriteria];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)validateMenuItem:(CPMenuItem )menuItem
|
||||
{
|
||||
return [_ruleEditor _validateItem:menuItem value:[[menuItem representedObject] valueForKey:@"item"] inRow:_rowIndex];
|
||||
}
|
||||
|
||||
- (void)_emptyRulePartSubviews
|
||||
{
|
||||
var count = [_ruleOptionViews count];
|
||||
|
||||
while (count--)
|
||||
[_ruleOptionViews[count] removeFromSuperview];
|
||||
|
||||
[_ruleOptionViews removeAllObjects];
|
||||
[_ruleOptionFrames removeAllObjects];
|
||||
[_ruleOptionInitialViewFrames removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)_reconfigureSubviews
|
||||
{
|
||||
var ruleItems,
|
||||
criteria,
|
||||
repObject,
|
||||
menuItem,
|
||||
ruleView,
|
||||
criterion,
|
||||
parent,
|
||||
numberOfCriteria,
|
||||
numberOfChildren,
|
||||
firstResponderIndex,
|
||||
|
||||
ruleItems = [CPMutableArray array],
|
||||
responder = [[self window] firstResponder];
|
||||
|
||||
[self _emptyRulePartSubviews];
|
||||
|
||||
criteria = [_ruleEditor criteriaForRow:_rowIndex];
|
||||
numberOfCriteria = [criteria count];
|
||||
|
||||
firstResponderIndex = numberOfCriteria - 1;
|
||||
if (responder)
|
||||
firstResponderIndex = [_ruleOptionViews indexOfObjectIdenticalTo:responder];
|
||||
|
||||
for (var i = 0; i < numberOfCriteria; i++)
|
||||
{
|
||||
ruleView = nil;
|
||||
parent = nil;
|
||||
criterion = [criteria objectAtIndex:i];
|
||||
|
||||
if (i > 0)
|
||||
parent = [criteria objectAtIndex:i - 1];
|
||||
|
||||
var childItems = [],
|
||||
childValues = [];
|
||||
|
||||
[_ruleEditor _getAllAvailableItems:childItems values:childValues asChildrenOfItem:parent inRow:_rowIndex];
|
||||
|
||||
numberOfChildren = [childItems count];
|
||||
if (numberOfChildren > 1)
|
||||
{
|
||||
var menuItems = [CPMutableArray arrayWithCapacity:numberOfChildren],
|
||||
selectedIndex = [childItems indexOfObject:criterion];
|
||||
|
||||
if (selectedIndex == CPNotFound)
|
||||
break;
|
||||
|
||||
for (var j = 0; j < numberOfChildren; ++j)
|
||||
{
|
||||
var childItem = [childItems objectAtIndex:j],
|
||||
childValue = [childValues objectAtIndex:j];
|
||||
|
||||
if ([childValue isKindOfClass:[CPMenuItem class]])
|
||||
{
|
||||
[[childValue menu] removeItem:childValue];
|
||||
menuItem = childValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([childValue isEqualToString:@""])
|
||||
menuItem = [self _createMenuSeparatorItem];
|
||||
else
|
||||
{
|
||||
menuItem = [self _createMenuItemWithTitle:childValue];
|
||||
[menuItem setTarget:self];
|
||||
[menuItem setAction:@selector(_ruleOptionPopupChangedAction:)];
|
||||
}
|
||||
}
|
||||
|
||||
repObject = [CPDictionary dictionaryWithObjectsAndKeys:childItem, @"item", childValue, @"value", i, @"indexInCriteria"];
|
||||
[menuItem setRepresentedObject:repObject];
|
||||
[menuItems addObject:menuItem];
|
||||
}
|
||||
|
||||
ruleView = [self _createPopUpButtonWithItems:menuItems selectedItemIndex:selectedIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = [childValues objectAtIndex:0],
|
||||
type = [value valueType];
|
||||
|
||||
if (type === 0)
|
||||
ruleView = [self _createStaticTextFieldWithStringValue:value];
|
||||
else
|
||||
{
|
||||
if (type !== 1)
|
||||
{
|
||||
[CPException raise:CPInternalInconsistencyException reason:@"Display value must be a string or a menu item"];
|
||||
continue;
|
||||
}
|
||||
|
||||
ruleView = value;
|
||||
[ruleView setTarget:self];
|
||||
[ruleView setAction:@selector(_sendRuleAction:)];
|
||||
if ([ruleView respondsToSelector:@selector(setDelegate:)])
|
||||
[ruleView setDelegate:self];
|
||||
}
|
||||
}
|
||||
|
||||
if (ruleView != nil)
|
||||
{
|
||||
[_ruleOptionViews addObject:ruleView];
|
||||
var frame = [ruleView frame];
|
||||
[_ruleOptionInitialViewFrames addObject:frame];
|
||||
[_ruleOptionFrames addObject:frame];
|
||||
|
||||
if (!criterion)
|
||||
criterion = [CPNull null];
|
||||
|
||||
[ruleItems addObject:criterion];
|
||||
}
|
||||
}
|
||||
|
||||
[_correspondingRuleItems setArray:ruleItems];
|
||||
|
||||
if (!editable)
|
||||
[self _updateEnabledStateForSubviews];
|
||||
|
||||
[self _relayoutSubviewsWidthChanged:YES];
|
||||
|
||||
if (firstResponderIndex != CPNotFound)
|
||||
{
|
||||
var aView = [_ruleOptionViews objectAtIndex:firstResponderIndex];
|
||||
[[self window] makeFirstResponder:aView]; // This is not working. bug in CPPopUpButton firstResponder ?
|
||||
}
|
||||
|
||||
//[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)_updateEnabledStateForSubviews
|
||||
{
|
||||
[_ruleOptionViews makeObjectsPerformSelector:@selector(setEnabled:) withObject:NO];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[self _relayoutSubviewsWidthChanged:YES];
|
||||
}
|
||||
|
||||
- (void)_relayoutSubviewsWidthChanged:(BOOL)widthChanged
|
||||
{
|
||||
var optionViewOriginX,
|
||||
leftHorizontalPadding,
|
||||
leftButtonMinX,
|
||||
rowHeight = [_ruleEditor rowHeight],
|
||||
count = [_ruleOptionViews count],
|
||||
sliceFrame = [self frame],
|
||||
|
||||
buttonFrame = CGRectMake(CGRectGetWidth(sliceFrame) - BUTTON_HEIGHT - [self _rowButtonsRightHorizontalPadding], ([_ruleEditor rowHeight] - BUTTON_HEIGHT) / 2 - 2, BUTTON_HEIGHT, BUTTON_HEIGHT);
|
||||
|
||||
[_addButton setFrame:buttonFrame];
|
||||
buttonFrame.origin.x -= BUTTON_HEIGHT + [self _rowButtonsInterviewHorizontalPadding];
|
||||
[_subtractButton setFrame:buttonFrame];
|
||||
|
||||
if (widthChanged)
|
||||
{
|
||||
optionViewOriginX = [self _leftmostViewFixedHorizontalPadding] + [self _indentationHorizontalPadding] * _indentation;
|
||||
leftHorizontalPadding = [self _rowButtonsLeftHorizontalPadding];
|
||||
leftButtonMinX = CGRectGetMinX(buttonFrame);
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var ruleOptionView = _ruleOptionViews[i],
|
||||
optionFrame = _ruleOptionFrames[i];
|
||||
|
||||
optionFrame.origin.y = (rowHeight - CGRectGetHeight(optionFrame)) / 2 - 2;
|
||||
|
||||
// small positioning fix
|
||||
if ([ruleOptionView isKindOfClass:CPTextField])
|
||||
{
|
||||
optionFrame.origin.y += 2;
|
||||
[_ruleOptionViews[i] setValue:CGInsetMake(7, 7, 7, 8) forThemeAttribute:@"content-inset"];
|
||||
}
|
||||
|
||||
|
||||
if (widthChanged)
|
||||
{
|
||||
optionFrame.origin.x = optionViewOriginX;
|
||||
if (i == count - 1 && ![self _isRulePopup:ruleOptionView])
|
||||
{
|
||||
var initialFrame = _ruleOptionInitialViewFrames[i];
|
||||
optionFrame.size.width = MIN(CGRectGetWidth(initialFrame), leftButtonMinX - leftHorizontalPadding - optionViewOriginX);
|
||||
}
|
||||
}
|
||||
|
||||
[ruleOptionView setFrame:optionFrame];
|
||||
[self addSubview:ruleOptionView];
|
||||
|
||||
if (widthChanged)
|
||||
optionViewOriginX += CGRectGetWidth(optionFrame) + [self _interviewHorizontalPadding];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_updateButtonVisibilities
|
||||
{
|
||||
[_addButton setHidden:[_ruleEditor _shouldHideAddButtonForSlice:self]];
|
||||
[_subtractButton setHidden:[_ruleEditor _shouldHideSubtractButtonForSlice:self]];
|
||||
}
|
||||
|
||||
- (void)_configurePlusButtonByRowType:(CPRuleEditorRowType)type
|
||||
{
|
||||
[self _setRowTypeToAddFromPlusButton:type];
|
||||
}
|
||||
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return editable;
|
||||
}
|
||||
|
||||
- (void)setEditable:(BOOL)value
|
||||
{
|
||||
editable = value;
|
||||
// [self _updateEnabledStateForSubviews];
|
||||
[self _updateButtonVisibilities];
|
||||
}
|
||||
|
||||
- (float)_alignmentGridWidth
|
||||
{
|
||||
return [_ruleEditor _alignmentGridWidth];
|
||||
}
|
||||
|
||||
- (float)_indentationHorizontalPadding
|
||||
{
|
||||
return 30.;
|
||||
}
|
||||
|
||||
- (float)_interviewHorizontalPadding
|
||||
{
|
||||
return 6.;
|
||||
}
|
||||
|
||||
- (float)_leftmostViewFixedHorizontalPadding
|
||||
{
|
||||
return 7.;
|
||||
}
|
||||
|
||||
- (float)_minimumVerticalPopupPadding
|
||||
{
|
||||
return 2.;
|
||||
}
|
||||
|
||||
- (float)_rowButtonsInterviewHorizontalPadding
|
||||
{
|
||||
return 6.;
|
||||
}
|
||||
|
||||
- (float)_rowButtonsLeftHorizontalPadding
|
||||
{
|
||||
return 10.;
|
||||
}
|
||||
|
||||
- (float)_rowButtonsRightHorizontalPadding
|
||||
{
|
||||
return ([[_ruleEditor superview] isKindOfClass:[CPClipView class]]) ? 18. : 10.;
|
||||
}
|
||||
|
||||
- (void)_setRowTypeToAddFromPlusButton:(int)type
|
||||
{
|
||||
_plusButtonRowType = type;
|
||||
}
|
||||
|
||||
- (void)setNeedsDisplay:(BOOL)flag
|
||||
{
|
||||
[super setNeedsDisplay:flag];
|
||||
}
|
||||
|
||||
- (BOOL)_nestingModeShouldHideAddButton
|
||||
{
|
||||
return [_ruleEditor _applicableNestingMode] == CPRuleEditorNestingModeSingle;
|
||||
}
|
||||
|
||||
- (BOOL)_nestingModeShouldHideSubtractButton
|
||||
{
|
||||
return [_ruleEditor _applicableNestingMode] == CPRuleEditorNestingModeSingle;
|
||||
}
|
||||
|
||||
- (BOOL)containsDisplayValue:(id)value
|
||||
{
|
||||
return [[_ruleEditor displayValuesForRow:_rowIndex] containsObject:value];
|
||||
// Ou alors avec _correspondingRuleItems
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
{
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CPRect)rect
|
||||
{
|
||||
[super drawRect:rect];
|
||||
}
|
||||
|
||||
- (BOOL)_isRulePopup:(CPView)view
|
||||
{
|
||||
if ([view isKindOfClass:[_CPRuleEditorPopUpButton class]])
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)_isRuleStaticTextField:(CPView)view
|
||||
{
|
||||
if ([view isKindOfClass:[_CPRuleEditorTextField class]])
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)_sendRuleAction:(id)sender
|
||||
{
|
||||
[_ruleEditor _updatePredicate];
|
||||
[_ruleEditor _sendRuleAction];
|
||||
}
|
||||
|
||||
- (void)_textDidChange:(CPNotification)aNotif
|
||||
{
|
||||
if ([[aNotif object] superview] == self && [_ruleEditor _sendsActionOnIncompleteTextChange])
|
||||
[self _sendRuleAction:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPRuleEditorTextField : CPTextField
|
||||
{
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil)
|
||||
{
|
||||
[self setBordered:NO];
|
||||
[self setEditable:NO];
|
||||
[self setDrawsBackground:NO];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)hitTest:(CPPoint)point
|
||||
{
|
||||
if (!CPRectContainsPoint([self frame], point))
|
||||
return nil;
|
||||
|
||||
return [self superview];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* CPSavePanel.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPPanel.j"
|
||||
|
||||
|
||||
@implementation CPSavePanel : CPPanel
|
||||
{
|
||||
CPURL _URL;
|
||||
|
||||
BOOL _isExtensionHidden @accessors(getter=isExtensionHidden, setter=setExtensionHidden:);
|
||||
BOOL _canSelectHiddenExtension @accessors(property=canSelectHiddenExtension);
|
||||
BOOL _allowsOtherFileTypes @accessors(property=allowsOtherFileTypes);
|
||||
BOOL _canCreateDirectories @accessors(property=canCreateDirectories);
|
||||
|
||||
CPArray _allowedFileTypes @accessors(property=allowedFileTypes);
|
||||
}
|
||||
|
||||
+ (id)savePanel
|
||||
{
|
||||
return [[CPSavePanel alloc] init];
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_canCreateDirectories = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPInteger)runModal
|
||||
{
|
||||
// FIXME: Is this correct???
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
|
||||
if (typeof window["cpSavePanel"] === "function")
|
||||
{
|
||||
var resultObject = window.cpSavePanel({
|
||||
isExtensionHidden: _isExtensionHidden,
|
||||
canSelectHiddenExtension: _canSelectHiddenExtension,
|
||||
allowsOtherFileTypes: _allowsOtherFileTypes,
|
||||
canCreateDirectories: _canCreateDirectories,
|
||||
allowedFileTypes: _allowedFileTypes
|
||||
}),
|
||||
result = resultObject.button;
|
||||
|
||||
_URL = result ? [CPURL URLWithString:resultObject.URL] : nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: This is not the best way to do this.
|
||||
var documentName = window.prompt("Document Name:"),
|
||||
result = documentName !== null;
|
||||
|
||||
_URL = result ? [[self class] proposedFileURLWithDocumentName:documentName] : nil;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CPURL)URL
|
||||
{
|
||||
return _URL;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* CPScreen.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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>
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPScreen
|
||||
|
||||
A CPScreen object describes the attributes of a display device available
|
||||
to Cappuccino.
|
||||
*/
|
||||
@implementation CPScreen : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the position and size of the visible area of the receiving screen.
|
||||
This will normally be smaller than the full size of the screen to account
|
||||
for system UI elements. For example, on a Mac the top of the visible frame
|
||||
is placed below the bottom of the menu bar.
|
||||
|
||||
@return the visible screen rectangle
|
||||
*/
|
||||
- (CGRect)visibleFrame
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
return _CGRectMake(window.screen.availLeft, window.screen.availTop, window.screen.availWidth, window.screen.availHeight);
|
||||
#else
|
||||
return _CGRectMakeZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,856 +0,0 @@
|
||||
/*
|
||||
* CPScroller.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* Modified to match Lion style by Antoine Mercadal 2011
|
||||
* <antoine.mercadal@archipelproject.org>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "../Foundation/Foundation.h"
|
||||
|
||||
@import "CPControl.j"
|
||||
|
||||
// CPScroller Constants
|
||||
CPScrollerNoPart = 0;
|
||||
CPScrollerDecrementPage = 1;
|
||||
CPScrollerKnob = 2;
|
||||
CPScrollerIncrementPage = 3;
|
||||
CPScrollerDecrementLine = 4;
|
||||
CPScrollerIncrementLine = 5;
|
||||
CPScrollerKnobSlot = 6;
|
||||
|
||||
CPScrollerIncrementArrow = 0;
|
||||
CPScrollerDecrementArrow = 1;
|
||||
|
||||
CPNoScrollerParts = 0;
|
||||
CPOnlyScrollerArrows = 1;
|
||||
CPAllScrollerParts = 2;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPScroller
|
||||
*/
|
||||
|
||||
var PARTS_ARRANGEMENT = [CPScrollerKnobSlot, CPScrollerDecrementLine, CPScrollerIncrementLine, CPScrollerKnob],
|
||||
NAMES_FOR_PARTS = {},
|
||||
PARTS_FOR_NAMES = {};
|
||||
|
||||
NAMES_FOR_PARTS[CPScrollerDecrementLine] = @"decrement-line";
|
||||
NAMES_FOR_PARTS[CPScrollerIncrementLine] = @"increment-line";
|
||||
NAMES_FOR_PARTS[CPScrollerKnobSlot] = @"knob-slot";
|
||||
NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
|
||||
|
||||
|
||||
CPScrollerStyleLegacy = 0;
|
||||
CPScrollerStyleOverlay = 1;
|
||||
|
||||
CPScrollerKnobStyleDefault = 0;
|
||||
CPScrollerKnobStyleDark = 1;
|
||||
CPScrollerKnobStyleLight = 2;
|
||||
|
||||
CPThemeStateScrollViewLegacy = CPThemeState("scroller-style-legacy");
|
||||
CPThemeStateScrollerKnobLight = CPThemeState("scroller-knob-light");
|
||||
CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
@implementation CPScroller : CPControl
|
||||
{
|
||||
CPControlSize _controlSize;
|
||||
CPUsableScrollerParts _usableParts;
|
||||
CPArray _partRects;
|
||||
|
||||
BOOL _isVertical @accessors(readonly, getter=isVertical);
|
||||
float _knobProportion;
|
||||
|
||||
CPScrollerPart _hitPart;
|
||||
|
||||
CPScrollerPart _trackingPart;
|
||||
float _trackingFloatValue;
|
||||
CGPoint _trackingStartPoint;
|
||||
|
||||
CPViewAnimation _animationScroller;
|
||||
|
||||
BOOL _allowFadingOut @accessors(getter=allowFadingOut);
|
||||
int _style;
|
||||
CPTimer _timerFadeOut;
|
||||
BOOL _isMouseOver;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Class methods
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return "scroller";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithJSObject:{
|
||||
@"scroller-width": 7.0,
|
||||
@"knob-slot-color": [CPNull null],
|
||||
@"decrement-line-color": [CPNull null],
|
||||
@"increment-line-color": [CPNull null],
|
||||
@"knob-color": [CPNull null],
|
||||
@"decrement-line-size":_CGSizeMakeZero(),
|
||||
@"increment-line-size":_CGSizeMakeZero(),
|
||||
@"track-inset":_CGInsetMakeZero(),
|
||||
@"knob-inset": _CGInsetMakeZero(),
|
||||
@"minimum-knob-length":21.0,
|
||||
@"track-border-overlay": 9.0
|
||||
}];
|
||||
}
|
||||
|
||||
+ (float)scrollerWidth
|
||||
{
|
||||
return [self scrollerWidthInStyle:CPScrollerStyleLegacy];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPScroller's width for a CPRegularControlSize.
|
||||
*/
|
||||
+ (float)scrollerWidthInStyle:(int)aStyle
|
||||
{
|
||||
var scroller = [[self alloc] init];
|
||||
|
||||
if (aStyle == CPScrollerStyleLegacy)
|
||||
return [scroller valueForThemeAttribute:@"scroller-width" inState:CPThemeStateScrollViewLegacy];
|
||||
return [scroller currentValueForThemeAttribute:@"scroller-width"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPScroller's overlay value.
|
||||
*/
|
||||
+ (float)scrollerOverlay
|
||||
{
|
||||
return [[[self alloc] init] currentValueForThemeAttribute:@"track-border-overlay"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the width of a CPScroller for the specified CPControlSize.
|
||||
@param aControlSize the size of a controller to return the width for
|
||||
*/
|
||||
+ (float)scrollerWidthForControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
return [self scrollerWidth];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_controlSize = CPRegularControlSize;
|
||||
_partRects = [];
|
||||
|
||||
[self setFloatValue:0.0];
|
||||
[self setKnobProportion:1.0];
|
||||
|
||||
_hitPart = CPScrollerNoPart;
|
||||
_allowFadingOut = YES;
|
||||
_isMouseOver = NO;
|
||||
_style = CPScrollerStyleOverlay;
|
||||
var paramAnimFadeOut = [CPDictionary dictionaryWithObjects:[self, CPViewAnimationFadeOutEffect]
|
||||
forKeys:[CPViewAnimationTargetKey, CPViewAnimationEffectKey]];
|
||||
|
||||
_animationScroller = [[CPViewAnimation alloc] initWithDuration:0.2 animationCurve:CPAnimationEaseInOut];
|
||||
[_animationScroller setViewAnimations:[paramAnimFadeOut]];
|
||||
[_animationScroller setDelegate:self];
|
||||
[self setAlphaValue:0.0];
|
||||
[self _calculateIsVertical];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Getters / Setters
|
||||
|
||||
/*!
|
||||
Returns the scroller's style
|
||||
*/
|
||||
- (void)style
|
||||
{
|
||||
return _style;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the scroller's control size
|
||||
@param aStyle the scroller style: CPScrollerStyleLegacy or CPScrollerStyleOverlay
|
||||
*/
|
||||
- (void)setStyle:(id)aStyle
|
||||
{
|
||||
if (_style != nil && _style === aStyle)
|
||||
return;
|
||||
|
||||
_style = aStyle;
|
||||
|
||||
if (_style === CPScrollerStyleLegacy)
|
||||
{
|
||||
[self fadeIn];
|
||||
[self setThemeState:CPThemeStateScrollViewLegacy];
|
||||
}
|
||||
else
|
||||
{
|
||||
_allowFadingOut = YES;
|
||||
[self unsetThemeState:CPThemeStateScrollViewLegacy];
|
||||
}
|
||||
|
||||
[self _adjustScrollerSize];
|
||||
}
|
||||
|
||||
- (void)setObjectValue:(id)aValue
|
||||
{
|
||||
[super setObjectValue:MIN(1.0, MAX(0.0, +aValue))];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the scroller's control size
|
||||
*/
|
||||
- (CPControlSize)controlSize
|
||||
{
|
||||
return _controlSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the scroller's size.
|
||||
@param aControlSize the scroller's size
|
||||
*/
|
||||
- (void)setControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
if (_controlSize == aControlSize)
|
||||
return;
|
||||
|
||||
_controlSize = aControlSize;
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return's the knob's proportion
|
||||
*/
|
||||
- (float)knobProportion
|
||||
{
|
||||
return _knobProportion;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the knob's proportion
|
||||
@param aProportion the desired proportion
|
||||
*/
|
||||
- (void)setKnobProportion:(float)aProportion
|
||||
{
|
||||
if (!_IS_NUMERIC(aProportion))
|
||||
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric"];
|
||||
|
||||
_knobProportion = MIN(1.0, MAX(0.0001, aProportion));
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Privates
|
||||
|
||||
/*! @ignore */
|
||||
- (void)_adjustScrollerSize
|
||||
{
|
||||
var frame = [self frame],
|
||||
scrollerWidth = [self currentValueForThemeAttribute:@"scroller-width"];
|
||||
|
||||
if ([self isVertical] && CGRectGetWidth(frame) !== scrollerWidth)
|
||||
frame.size.width = scrollerWidth;
|
||||
|
||||
if (![self isVertical] && CGRectGetHeight(frame) !== scrollerWidth)
|
||||
frame.size.height = scrollerWidth;
|
||||
|
||||
[self setFrame:frame];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)_performFadeOut:(CPTimer)aTimer
|
||||
{
|
||||
[self fadeOut];
|
||||
_timerFadeOut = nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Utilities
|
||||
|
||||
- (CGRect)rectForPart:(CPScrollerPart)aPart
|
||||
{
|
||||
if (aPart == CPScrollerNoPart)
|
||||
return _CGRectMakeZero();
|
||||
|
||||
return _partRects[aPart];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the part of the scroller that would be hit by \c aPoint.
|
||||
@param aPoint the simulated point hit
|
||||
@return the part of the scroller that intersects the point
|
||||
*/
|
||||
- (CPScrollerPart)testPart:(CGPoint)aPoint
|
||||
{
|
||||
aPoint = [self convertPoint:aPoint fromView:nil];
|
||||
|
||||
// The ordering of these tests is important. We check the knob and
|
||||
// page rects first since they may overlap with the arrows.
|
||||
|
||||
if (![self hasThemeState:CPThemeStateSelected])
|
||||
return CPScrollerNoPart;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
|
||||
return CPScrollerKnob;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementPage], aPoint))
|
||||
return CPScrollerDecrementPage;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementPage], aPoint))
|
||||
return CPScrollerIncrementPage;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementLine], aPoint))
|
||||
return CPScrollerDecrementLine;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementLine], aPoint))
|
||||
return CPScrollerIncrementLine;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerKnobSlot], aPoint))
|
||||
return CPScrollerKnobSlot;
|
||||
|
||||
return CPScrollerNoPart;
|
||||
}
|
||||
|
||||
/*!
|
||||
Check if there's enough space in the scroller to display the knob
|
||||
*/
|
||||
- (void)checkSpaceForParts
|
||||
{
|
||||
var bounds = [self bounds];
|
||||
|
||||
// Assume we won't be needing the arrows.
|
||||
if (_knobProportion === 1.0)
|
||||
{
|
||||
_usableParts = CPNoScrollerParts;
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = CGRectMakeZero();
|
||||
_partRects[CPScrollerKnob] = CGRectMakeZero();
|
||||
_partRects[CPScrollerIncrementPage] = CGRectMakeZero();
|
||||
_partRects[CPScrollerDecrementLine] = CGRectMakeZero();
|
||||
_partRects[CPScrollerIncrementLine] = CGRectMakeZero();
|
||||
|
||||
// In this case, the slot is the entirety of the scroller.
|
||||
_partRects[CPScrollerKnobSlot] = CGRectMakeCopy(bounds);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point we know we're going to need arrows.
|
||||
_usableParts = CPAllScrollerParts;
|
||||
|
||||
var knobInset = [self currentValueForThemeAttribute:@"knob-inset"],
|
||||
trackInset = [self currentValueForThemeAttribute:@"track-inset"],
|
||||
width = _CGRectGetWidth(bounds),
|
||||
height = _CGRectGetHeight(bounds);
|
||||
|
||||
if ([self isVertical])
|
||||
{
|
||||
var decrementLineSize = [self currentValueForThemeAttribute:"decrement-line-size"],
|
||||
incrementLineSize = [self currentValueForThemeAttribute:"increment-line-size"],
|
||||
effectiveDecrementLineHeight = decrementLineSize.height + trackInset.top,
|
||||
effectiveIncrementLineHeight = incrementLineSize.height + trackInset.bottom,
|
||||
slotSize = height - effectiveDecrementLineHeight - effectiveIncrementLineHeight,
|
||||
minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
|
||||
knobWidth = width - knobInset.left - knobInset.right,
|
||||
knobHeight = MAX(minimumKnobLength, (slotSize * _knobProportion)),
|
||||
knobLocation = effectiveDecrementLineHeight + (slotSize - knobHeight) * [self floatValue];
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = _CGRectMake(0.0, effectiveDecrementLineHeight, width, knobLocation - effectiveDecrementLineHeight);
|
||||
_partRects[CPScrollerKnob] = _CGRectMake(knobInset.left, knobLocation, knobWidth, knobHeight);
|
||||
_partRects[CPScrollerIncrementPage] = _CGRectMake(0.0, knobLocation + knobHeight, width, height - (knobLocation + knobHeight) - effectiveIncrementLineHeight);
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(trackInset.left, effectiveDecrementLineHeight, width - trackInset.left - trackInset.right, slotSize);
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMake(0.0, height - incrementLineSize.height, incrementLineSize.width, incrementLineSize.height);
|
||||
|
||||
if (height < knobHeight + decrementLineSize.height + incrementLineSize.height + trackInset.top + trackInset.bottom)
|
||||
_partRects[CPScrollerKnob] = _CGRectMakeZero();
|
||||
|
||||
if (height < decrementLineSize.height + incrementLineSize.height - 2)
|
||||
{
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(trackInset.left, 0, width - trackInset.left - trackInset.right, height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var decrementLineSize = [self currentValueForThemeAttribute:"decrement-line-size"],
|
||||
incrementLineSize = [self currentValueForThemeAttribute:"increment-line-size"],
|
||||
effectiveDecrementLineWidth = decrementLineSize.width + trackInset.left,
|
||||
effectiveIncrementLineWidth = incrementLineSize.width + trackInset.right,
|
||||
slotSize = width - effectiveDecrementLineWidth - effectiveIncrementLineWidth,
|
||||
minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
|
||||
knobWidth = MAX(minimumKnobLength, (slotSize * _knobProportion)),
|
||||
knobHeight = height - knobInset.top - knobInset.bottom,
|
||||
knobLocation = effectiveDecrementLineWidth + (slotSize - knobWidth) * [self floatValue];
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = _CGRectMake(effectiveDecrementLineWidth, 0.0, knobLocation - effectiveDecrementLineWidth, height);
|
||||
_partRects[CPScrollerKnob] = _CGRectMake(knobLocation, knobInset.top, knobWidth, knobHeight);
|
||||
_partRects[CPScrollerIncrementPage] = _CGRectMake(knobLocation + knobWidth, 0.0, width - (knobLocation + knobWidth) - effectiveIncrementLineWidth, height);
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(effectiveDecrementLineWidth, trackInset.top, slotSize, height - trackInset.top - trackInset.bottom);
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMake(width - incrementLineSize.width, 0.0, incrementLineSize.width, incrementLineSize.height);
|
||||
|
||||
if (width < knobWidth + decrementLineSize.width + incrementLineSize.width + trackInset.left + trackInset.right)
|
||||
_partRects[CPScrollerKnob] = _CGRectMakeZero();
|
||||
|
||||
if (width < decrementLineSize.width + incrementLineSize.width - 2)
|
||||
{
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(0.0, 0.0, width, slotSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns all the parts of the scroller that
|
||||
are usable for displaying.
|
||||
*/
|
||||
- (CPUsableScrollerParts)usableParts
|
||||
{
|
||||
return _usableParts;
|
||||
}
|
||||
|
||||
/*!
|
||||
Display the scroller
|
||||
*/
|
||||
- (void)fadeIn
|
||||
{
|
||||
if (_isMouseOver && _knobProportion != 1.0)
|
||||
[self setThemeState:CPThemeStateSelected];
|
||||
|
||||
if (_timerFadeOut)
|
||||
[_timerFadeOut invalidate];
|
||||
|
||||
[self setAlphaValue:1.0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Start the fade out anination
|
||||
*/
|
||||
- (void)fadeOut
|
||||
{
|
||||
if ([self hasThemeState:CPThemeStateScrollViewLegacy])
|
||||
return;
|
||||
|
||||
[_animationScroller startAnimation];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Drawing
|
||||
|
||||
/*!
|
||||
Draws the specified arrow and sets the highlight.
|
||||
@param anArrow the arrow to draw
|
||||
@param shouldHighlight sets whether the arrow should be highlighted
|
||||
*/
|
||||
- (void)drawArrow:(CPScrollerArrow)anArrow highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Draws the knob
|
||||
*/
|
||||
- (void)drawKnob
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Draws the knob's slot
|
||||
*/
|
||||
- (void)drawKnobSlot
|
||||
{
|
||||
}
|
||||
|
||||
- (CPView)createViewForPart:(CPScrollerPart)aPart
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
return _partRects[aName];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[self checkSpaceForParts];
|
||||
|
||||
var index = 0,
|
||||
count = PARTS_ARRANGEMENT.length,
|
||||
view;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var part = PARTS_ARRANGEMENT[index];
|
||||
|
||||
if (index === 0)
|
||||
view = [self layoutEphemeralSubviewNamed:part positioned:CPWindowBelow relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index + 1]];
|
||||
else
|
||||
view = [self layoutEphemeralSubviewNamed:part positioned:CPWindowAbove relativeToEphemeralSubviewNamed:PARTS_ARRANGEMENT[index - 1]];
|
||||
|
||||
if (view)
|
||||
[view setBackgroundColor:[self currentValueForThemeAttribute:NAMES_FOR_PARTS[part] + "-color"]];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Caches images for the scroll arrow and knob.
|
||||
*/
|
||||
- (void)drawParts
|
||||
{
|
||||
[self drawKnobSlot];
|
||||
[self drawKnob];
|
||||
[self drawArrow:CPScrollerDecrementArrow highlight:NO];
|
||||
[self drawArrow:CPScrollerIncrementArrow highlight:NO];
|
||||
}
|
||||
|
||||
// Event Handling
|
||||
/*!
|
||||
Returns the part of the scroller that was hit.
|
||||
*/
|
||||
- (CPScrollerPart)hitPart
|
||||
{
|
||||
return _hitPart;
|
||||
}
|
||||
|
||||
/*!
|
||||
Tracks the knob.
|
||||
@param anEvent the input event
|
||||
*/
|
||||
- (void)trackKnob:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type];
|
||||
|
||||
if (type === CPLeftMouseUp)
|
||||
{
|
||||
_hitPart = CPScrollerNoPart;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === CPLeftMouseDown)
|
||||
{
|
||||
_trackingFloatValue = [self floatValue];
|
||||
_trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
}
|
||||
|
||||
else if (type === CPLeftMouseDragged)
|
||||
{
|
||||
var knobRect = [self rectForPart:CPScrollerKnob],
|
||||
knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
|
||||
remainder = ![self isVertical] ? (_CGRectGetWidth(knobSlotRect) - _CGRectGetWidth(knobRect)) : (_CGRectGetHeight(knobSlotRect) - _CGRectGetHeight(knobRect));
|
||||
|
||||
if (remainder <= 0)
|
||||
[self setFloatValue:0.0];
|
||||
else
|
||||
{
|
||||
var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
delta = ![self isVertical] ? location.x - _trackingStartPoint.x : location.y - _trackingStartPoint.y;
|
||||
|
||||
[self setFloatValue:_trackingFloatValue + delta / remainder];
|
||||
}
|
||||
}
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackKnob:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
if (type === CPLeftMouseDragged)
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Tracks the scroll button.
|
||||
@param anEvent the input event
|
||||
*/
|
||||
- (void)trackScrollButtons:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type];
|
||||
|
||||
if (type === CPLeftMouseUp)
|
||||
{
|
||||
[self highlight:NO];
|
||||
[CPEvent stopPeriodicEvents];
|
||||
|
||||
_hitPart = CPScrollerNoPart;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === CPLeftMouseDown)
|
||||
{
|
||||
_trackingPart = [self hitPart];
|
||||
|
||||
_trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if ([anEvent modifierFlags] & CPAlternateKeyMask)
|
||||
{
|
||||
if (_trackingPart == CPScrollerDecrementLine)
|
||||
_hitPart = CPScrollerDecrementPage;
|
||||
|
||||
else if (_trackingPart == CPScrollerIncrementLine)
|
||||
_hitPart = CPScrollerIncrementPage;
|
||||
|
||||
else if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
|
||||
{
|
||||
var knobRect = [self rectForPart:CPScrollerKnob],
|
||||
knobWidth = ![self isVertical] ? _CGRectGetWidth(knobRect) : _CGRectGetHeight(knobRect),
|
||||
knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
|
||||
remainder = (![self isVertical] ? _CGRectGetWidth(knobSlotRect) : _CGRectGetHeight(knobSlotRect)) - knobWidth;
|
||||
|
||||
[self setFloatValue:((![self isVertical] ? _trackingStartPoint.x - _CGRectGetMinX(knobSlotRect) : _trackingStartPoint.y - _CGRectGetMinY(knobSlotRect)) - knobWidth / 2.0) / remainder];
|
||||
|
||||
_hitPart = CPScrollerKnob;
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
// Now just track the knob.
|
||||
return [self trackKnob:anEvent];
|
||||
}
|
||||
}
|
||||
|
||||
[self highlight:YES];
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
[CPEvent startPeriodicEventsAfterDelay:0.5 withPeriod:0.04];
|
||||
}
|
||||
|
||||
else if (type === CPLeftMouseDragged)
|
||||
{
|
||||
_trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
|
||||
{
|
||||
var hitPart = [self testPart:[anEvent locationInWindow]];
|
||||
|
||||
if (hitPart == CPScrollerDecrementPage || hitPart == CPScrollerIncrementPage)
|
||||
{
|
||||
_trackingPart = hitPart;
|
||||
_hitPart = hitPart;
|
||||
}
|
||||
}
|
||||
|
||||
[self highlight:CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint)];
|
||||
}
|
||||
else if (type == CPPeriodic && CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint))
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackScrollButtons:) forNextEventMatchingMask:CPPeriodicMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
}
|
||||
|
||||
- (void)_calculateIsVertical
|
||||
{
|
||||
// Recalculate isVertical.
|
||||
var bounds = [self bounds],
|
||||
width = _CGRectGetWidth(bounds),
|
||||
height = _CGRectGetHeight(bounds);
|
||||
|
||||
_isVertical = width < height ? 1 : (width > height ? 0 : -1);
|
||||
|
||||
if (_isVertical === 1)
|
||||
[self setThemeState:CPThemeStateVertical];
|
||||
else if (_isVertical === 0)
|
||||
[self unsetThemeState:CPThemeStateVertical];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[self checkSpaceForParts];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Overrides
|
||||
|
||||
- (id)currentValueForThemeAttribute:(CPString)anAttributeName
|
||||
{
|
||||
var themeState = _themeState;
|
||||
|
||||
if (NAMES_FOR_PARTS[_hitPart] + "-color" !== anAttributeName)
|
||||
themeState &= ~CPThemeStateHighlighted;
|
||||
|
||||
return [self valueForThemeAttribute:anAttributeName inState:themeState];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
_hitPart = [self testPart:[anEvent locationInWindow]];
|
||||
|
||||
switch (_hitPart)
|
||||
{
|
||||
case CPScrollerKnob: return [self trackKnob:anEvent];
|
||||
|
||||
case CPScrollerDecrementLine:
|
||||
case CPScrollerIncrementLine:
|
||||
case CPScrollerDecrementPage:
|
||||
case CPScrollerIncrementPage: return [self trackScrollButtons:anEvent];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(CPEvent)anEvent
|
||||
{
|
||||
[super mouseEntered:anEvent];
|
||||
|
||||
if (_timerFadeOut)
|
||||
[_timerFadeOut invalidate];
|
||||
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
_allowFadingOut = NO;
|
||||
_isMouseOver = YES;
|
||||
|
||||
if ([self alphaValue] > 0 && _knobProportion != 1.0)
|
||||
[self setThemeState:CPThemeStateSelected];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(CPEvent)anEvent
|
||||
{
|
||||
[super mouseExited:anEvent];
|
||||
|
||||
if ([self isHidden] || ![self isEnabled] || !_isMouseOver)
|
||||
return;
|
||||
|
||||
_allowFadingOut = YES;
|
||||
_isMouseOver = NO;
|
||||
|
||||
if (_timerFadeOut)
|
||||
[_timerFadeOut invalidate];
|
||||
|
||||
_timerFadeOut = [CPTimer scheduledTimerWithTimeInterval:1.2 target:self selector:@selector(_performFadeOut:) userInfo:nil repeats:NO];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Delegates
|
||||
|
||||
- (void)animationDidEnd:(CPAnimation)animation
|
||||
{
|
||||
[self unsetThemeState:CPThemeStateSelected];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPScrollerControlSizeKey = @"CPScrollerControlSize",
|
||||
CPScrollerKnobProportionKey = @"CPScrollerKnobProportion",
|
||||
CPScrollerStyleKey = @"CPScrollerStyleKey";
|
||||
|
||||
@implementation CPScroller (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_controlSize = CPRegularControlSize;
|
||||
|
||||
if ([aCoder containsValueForKey:CPScrollerControlSizeKey])
|
||||
_controlSize = [aCoder decodeIntForKey:CPScrollerControlSizeKey];
|
||||
|
||||
_knobProportion = 1.0;
|
||||
|
||||
if ([aCoder containsValueForKey:CPScrollerKnobProportionKey])
|
||||
_knobProportion = [aCoder decodeFloatForKey:CPScrollerKnobProportionKey];
|
||||
|
||||
_partRects = [];
|
||||
|
||||
_hitPart = CPScrollerNoPart;
|
||||
|
||||
_allowFadingOut = YES;
|
||||
_isMouseOver = NO;
|
||||
|
||||
var paramAnimFadeOut = [CPDictionary dictionaryWithObjects:[self, CPViewAnimationFadeOutEffect]
|
||||
forKeys:[CPViewAnimationTargetKey, CPViewAnimationEffectKey]];
|
||||
|
||||
_animationScroller = [[CPViewAnimation alloc] initWithDuration:0.2 animationCurve:CPAnimationEaseInOut];
|
||||
[_animationScroller setViewAnimations:[paramAnimFadeOut]];
|
||||
[_animationScroller setDelegate:self];
|
||||
[self setAlphaValue:0.0];
|
||||
|
||||
[self _calculateIsVertical];
|
||||
|
||||
[self setStyle:[aCoder decodeIntForKey:CPScrollerStyleKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeInt:_controlSize forKey:CPScrollerControlSizeKey];
|
||||
[aCoder encodeFloat:_knobProportion forKey:CPScrollerKnobProportionKey];
|
||||
[aCoder encodeInt:_style forKey:CPScrollerStyleKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPScroller (Deprecated)
|
||||
|
||||
/*!
|
||||
Sets the position and proportion of the knob.
|
||||
@param aValue the knob position (ranges from 0.0 to 1.0)
|
||||
@param aProportion the knob's proportion (ranges from 0.0 to 1.0)
|
||||
*/
|
||||
- (void)setFloatValue:(float)aValue knobProportion:(float)aProportion
|
||||
{
|
||||
[self setFloatValue:aValue];
|
||||
[self setKnobProportion:aProportion];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,813 +0,0 @@
|
||||
/*
|
||||
* CPSearchField.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by cacaodev.
|
||||
* Copyright 2009.
|
||||
*
|
||||
* 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 "CPButton.j"
|
||||
@import "CPTextField.j"
|
||||
|
||||
CPSearchFieldRecentsTitleMenuItemTag = 1000;
|
||||
CPSearchFieldRecentsMenuItemTag = 1001;
|
||||
CPSearchFieldClearRecentsMenuItemTag = 1002;
|
||||
CPSearchFieldNoRecentsMenuItemTag = 1003;
|
||||
|
||||
var CPSearchFieldSearchImage = nil,
|
||||
CPSearchFieldFindImage = nil,
|
||||
CPSearchFieldCancelImage = nil,
|
||||
CPSearchFieldCancelPressedImage = nil;
|
||||
|
||||
var SEARCH_BUTTON_DEFAULT_WIDTH = 25.0,
|
||||
CANCEL_BUTTON_DEFAULT_WIDTH = 22.0,
|
||||
BUTTON_DEFAULT_HEIGHT = 22.0;
|
||||
|
||||
var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotification";
|
||||
|
||||
var RECENT_SEARCH_PREFIX = @" ";
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPSearchField
|
||||
The CPSearchField class defines the programmatic interface for text fields that are optimized for text-based searches. A CPSearchField object directly inherits from the CPTextField class. The search field implemented by these classes presents a standard user interface for searches, including a search button, a cancel button, and a pop-up icon menu for listing recent search strings and custom search categories.
|
||||
|
||||
When the user types and then pauses, the text field's action message is sent to its target. You can query the text field's string value for the current text to search for. Do not rely on the sender of the action to be an CPMenu object because the menu may change. If you need to change the menu, modify the search menu template and call the setSearchMenuTemplate: method to update.
|
||||
*/
|
||||
@implementation CPSearchField : CPTextField
|
||||
{
|
||||
CPButton _searchButton;
|
||||
CPButton _cancelButton;
|
||||
CPMenu _searchMenuTemplate;
|
||||
CPMenu _searchMenu;
|
||||
|
||||
CPString _recentsAutosaveName;
|
||||
CPArray _recentSearches;
|
||||
|
||||
int _maximumRecents;
|
||||
BOOL _sendsWholeSearchString;
|
||||
BOOL _sendsSearchStringImmediately;
|
||||
BOOL _canResignFirstResponder;
|
||||
CPTimer _partialStringTimer;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"searchfield"
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPSearchField class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
CPSearchFieldSearchImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldSearch.png"] size:_CGSizeMake(SEARCH_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT)];
|
||||
CPSearchFieldFindImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldFind.png"] size:_CGSizeMake(SEARCH_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT)];
|
||||
CPSearchFieldCancelImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldCancel.png"] size:_CGSizeMake(CANCEL_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT)];
|
||||
CPSearchFieldCancelPressedImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldCancelPressed.png"] size:_CGSizeMake(CANCEL_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT)];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame])
|
||||
{
|
||||
_maximumRecents = 10;
|
||||
_sendsWholeSearchString = NO;
|
||||
_sendsSearchStringImmediately = NO;
|
||||
_recentsAutosaveName = nil;
|
||||
|
||||
[self _init];
|
||||
#if PLATFORM(DOM)
|
||||
_cancelButton._DOMElement.style.cursor = "default";
|
||||
_searchButton._DOMElement.style.cursor = "default";
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
_recentSearches = [CPArray array];
|
||||
|
||||
[self setBezeled:YES];
|
||||
[self setBezelStyle:CPTextFieldRoundedBezel];
|
||||
[self setBordered:YES];
|
||||
[self setEditable:YES];
|
||||
[self setContinuous:YES];
|
||||
|
||||
var bounds = [self bounds],
|
||||
cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]],
|
||||
searchButton = [[CPButton alloc] initWithFrame:[self searchButtonRectForBounds:bounds]];
|
||||
|
||||
[self setCancelButton:cancelButton];
|
||||
[self resetCancelButton];
|
||||
|
||||
[self setSearchButton:searchButton];
|
||||
[self resetSearchButton];
|
||||
|
||||
_canResignFirstResponder = YES;
|
||||
}
|
||||
|
||||
- (void)viewWillMoveToSuperview:(CPView)aView
|
||||
{
|
||||
[super viewWillMoveToSuperview:aView];
|
||||
|
||||
// First we remove any observer that may have been in place to avoid memory leakage.
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPControlTextDidChangeNotification object:self];
|
||||
|
||||
// Register the observe here if we need to.
|
||||
if (aView)
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_searchFieldTextDidChange:) name:CPControlTextDidChangeNotification object:self];
|
||||
}
|
||||
|
||||
// Managing Buttons
|
||||
/*!
|
||||
Sets the button used to display the search-button image
|
||||
@param button The search button.
|
||||
*/
|
||||
- (void)setSearchButton:(CPButton)button
|
||||
{
|
||||
if (button != _searchButton)
|
||||
{
|
||||
[_searchButton removeFromSuperview];
|
||||
_searchButton = button;
|
||||
|
||||
[_searchButton setFrame:[self searchButtonRectForBounds:[self bounds]]];
|
||||
[_searchButton setAutoresizingMask:CPViewMaxXMargin];
|
||||
[self addSubview:_searchButton];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the button used to display the search-button image.
|
||||
@return The search button.
|
||||
*/
|
||||
- (CPButton)searchButton
|
||||
{
|
||||
return _searchButton;
|
||||
}
|
||||
|
||||
/*!
|
||||
Resets the search button to its default attributes.
|
||||
This method resets the target, action, regular image, and pressed image. By default, when users click the search button or press the Return key, the action defined for the receiver is sent to its designated target. This method gives you a way to customize the search button for specific situations and then reset the button defaults without having to undo changes individually.
|
||||
*/
|
||||
- (void)resetSearchButton
|
||||
{
|
||||
var button = [self searchButton],
|
||||
searchButtonImage = (_searchMenuTemplate === nil) ? CPSearchFieldSearchImage : CPSearchFieldFindImage;
|
||||
|
||||
[button setBordered:NO];
|
||||
[button setImageScaling:CPImageScaleAxesIndependently];
|
||||
[button setImage:searchButtonImage];
|
||||
[button setAutoresizingMask:CPViewMaxXMargin];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button object used to display the cancel-button image.
|
||||
@param button The cancel button.
|
||||
*/
|
||||
- (void)setCancelButton:(CPButton)button
|
||||
{
|
||||
if (button != _cancelButton)
|
||||
{
|
||||
[_cancelButton removeFromSuperview];
|
||||
_cancelButton = button;
|
||||
|
||||
[_cancelButton setFrame:[self cancelButtonRectForBounds:[self bounds]]];
|
||||
[_cancelButton setAutoresizingMask:CPViewMinXMargin];
|
||||
[_cancelButton setTarget:self];
|
||||
[_cancelButton setAction:@selector(cancelOperation:)];
|
||||
[self _updateCancelButtonVisibility];
|
||||
[self addSubview:_cancelButton];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the button object used to display the cancel-button image.
|
||||
@return The cancel button.
|
||||
*/
|
||||
- (CPButton)cancelButton
|
||||
{
|
||||
return _cancelButton;
|
||||
}
|
||||
|
||||
/*!
|
||||
Resets the cancel button to its default attributes.
|
||||
This method resets the target, action, regular image, and pressed image. This method gives you a way to customize the cancel button for specific situations and then reset the button defaults without having to undo changes individually.
|
||||
*/
|
||||
- (void)resetCancelButton
|
||||
{
|
||||
var button = [self cancelButton];
|
||||
[button setBordered:NO];
|
||||
[button setImageScaling:CPImageScaleAxesIndependently];
|
||||
[button setImage:CPSearchFieldCancelImage];
|
||||
[button setAlternateImage:CPSearchFieldCancelPressedImage];
|
||||
[button setAutoresizingMask:CPViewMinXMargin];
|
||||
[button setTarget:self];
|
||||
[button setAction:@selector(cancelOperation:)];
|
||||
}
|
||||
|
||||
// Custom Layout
|
||||
/*!
|
||||
Modifies the bounding rectangle for the search-text field.
|
||||
@param rect The current bounding rectangle for the search text field.
|
||||
@return The updated bounding rectangle to use for the search text field. The default value is the value passed into the rect parameter.
|
||||
Subclasses can override this method to return a new bounding rectangle for the text-field object. You might use this method to provide a custom layout for the search field control.
|
||||
*/
|
||||
- (CGRect)searchTextRectForBounds:(CGRect)rect
|
||||
{
|
||||
var leftOffset = 0,
|
||||
width = _CGRectGetWidth(rect),
|
||||
bounds = [self bounds];
|
||||
|
||||
if (_searchButton)
|
||||
{
|
||||
var searchBounds = [self searchButtonRectForBounds:bounds];
|
||||
leftOffset = _CGRectGetMaxX(searchBounds) + 2;
|
||||
}
|
||||
|
||||
if (_cancelButton)
|
||||
{
|
||||
var cancelRect = [self cancelButtonRectForBounds:bounds];
|
||||
width = _CGRectGetMinX(cancelRect) - leftOffset;
|
||||
}
|
||||
|
||||
return _CGRectMake(leftOffset, _CGRectGetMinY(rect), width, _CGRectGetHeight(rect));
|
||||
}
|
||||
|
||||
/*!
|
||||
Modifies the bounding rectangle for the search button.
|
||||
@param rect The current bounding rectangle for the search button.
|
||||
Subclasses can override this method to return a new bounding rectangle for the search button. You might use this method to provide a custom layout for the search field control.
|
||||
*/
|
||||
- (CGRect)searchButtonRectForBounds:(CGRect)rect
|
||||
{
|
||||
return _CGRectMake(5, (_CGRectGetHeight(rect) - BUTTON_DEFAULT_HEIGHT) / 2, SEARCH_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
/*!
|
||||
Modifies the bounding rectangle for the cancel button.
|
||||
@param rect The updated bounding rectangle to use for the cancel button. The default value is the value passed into the rect parameter.
|
||||
Subclasses can override this method to return a new bounding rectangle for the cancel button. You might use this method to provide a custom layout for the search field control.
|
||||
*/
|
||||
- (CGRect)cancelButtonRectForBounds:(CGRect)rect
|
||||
{
|
||||
return _CGRectMake(_CGRectGetWidth(rect) - CANCEL_BUTTON_DEFAULT_WIDTH - 5, (_CGRectGetHeight(rect) - CANCEL_BUTTON_DEFAULT_WIDTH) / 2, BUTTON_DEFAULT_HEIGHT, BUTTON_DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
// Managing Menu Templates
|
||||
/*!
|
||||
Returns the menu template object used to dynamically construct the search pop-up icon menu.
|
||||
@return The current menu template.
|
||||
*/
|
||||
- (CPMenu)searchMenuTemplate
|
||||
{
|
||||
return _searchMenuTemplate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the menu template object used to dynamically construct the receiver's pop-up icon menu.
|
||||
@param menu The menu template to use.
|
||||
The receiver looks for the tag constants described in ŇMenu tagsÓ to determine how to populate the menu with items related to recent searches. See ŇConfiguring a Search MenuÓ for a sample of how you might set up the search menu template.
|
||||
*/
|
||||
- (void)setSearchMenuTemplate:(CPMenu)aMenu
|
||||
{
|
||||
_searchMenuTemplate = aMenu;
|
||||
|
||||
[self resetSearchButton];
|
||||
[self _loadRecentSearchList];
|
||||
[self _updateSearchMenu];
|
||||
}
|
||||
|
||||
// Managing Search Modes
|
||||
/*!
|
||||
Returns a Boolean value indicating whether the receiver sends the search action message when the user clicks the search button (or presses return) or after each keystroke.
|
||||
@return \c YES if the action message is sent all at once when the user clicks the search button or presses return; otherwise, NO if the search string is sent after each keystroke. The default value is NO.
|
||||
*/
|
||||
- (BOOL)sendsWholeSearchString
|
||||
{
|
||||
return _sendsWholeSearchString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the receiver sends the search action message when the user clicks the search button (or presses return) or after each keystroke.
|
||||
@param flag \c YES to send the action message all at once when the user clicks the search button or presses return; otherwise, NO to send the search string after each keystroke.
|
||||
*/
|
||||
- (void)setSendsWholeSearchString:(BOOL)flag
|
||||
{
|
||||
_sendsWholeSearchString = flag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a Boolean value indicating whether the receiver sends its action immediately upon being notified of changes to the search field text or after a brief pause.
|
||||
@return \c YES if the text field sends its action immediately upon notification of any changes to the search field; otherwise, NO.
|
||||
*/
|
||||
- (BOOL)sendsSearchStringImmediately
|
||||
{
|
||||
return _sendsSearchStringImmediately;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the text field sends its action message to the target immediately upon notification of any changes to the search field text or after a brief pause.
|
||||
@param flag \c YES to send the text field's action immediately upon notification of any changes to the search field; otherwise, NO if you want the text field to pause briefly before sending its action message. Pausing gives the user the opportunity to type more text into the search field before initiating the search.
|
||||
*/
|
||||
- (void)setSendsSearchStringImmediately:(BOOL)flag
|
||||
{
|
||||
_sendsSearchStringImmediately = flag;
|
||||
}
|
||||
|
||||
// Managing Recent Search Strings
|
||||
/*!
|
||||
Returns the maximum number of recent search strings to display in the custom search menu.
|
||||
@return The maximum number of search strings that can appear in the menu. This value is between 0 and 254.
|
||||
*/
|
||||
- (int)maximumRecents
|
||||
{
|
||||
return _maximumRecents;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the maximum number of search strings that can appear in the search menu.
|
||||
@param maxRecents The maximum number of search strings that can appear in the menu. This value can be between 0 and 254. Specifying a value less than 0 sets the value to the default, which is 10. Specifying a value greater than 254 sets the maximum to 254.
|
||||
*/
|
||||
- (void)setMaximumRecents:(int)max
|
||||
{
|
||||
if (max > 254)
|
||||
max = 254;
|
||||
else if (max < 0)
|
||||
max = 10;
|
||||
|
||||
_maximumRecents = max;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the list of recent search strings for the control.
|
||||
@return An array of \c CPString objects, each of which contains a search string either displayed in the search menu or from a recent autosave archive. If there have been no recent searches and no prior searches saved under an autosave name, this array may be empty.
|
||||
*/
|
||||
- (CPArray)recentSearches
|
||||
{
|
||||
return _recentSearches;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the list of recent search strings to list in the pop-up icon menu of the receiver.
|
||||
@param searches An array of CPString objects containing the search strings.
|
||||
You might use this method to set the recent list of searches from an archived copy.
|
||||
*/
|
||||
- (void)setRecentSearches:(CPArray)searches
|
||||
{
|
||||
var max = MIN([self maximumRecents], [searches count]),
|
||||
searches = [searches subarrayWithRange:CPMakeRange(0, max)];
|
||||
|
||||
_recentSearches = searches;
|
||||
[self _autosaveRecentSearchList];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the key under which the prior list of recent search strings has been archived.
|
||||
@return The autosave name, which is used as a key in the standard user defaults to save the recent searches. The default value is nil, which causes searches not to be autosaved.
|
||||
*/
|
||||
- (CPString)recentsAutosaveName
|
||||
{
|
||||
return _recentsAutosaveName;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the autosave name under which the receiver automatically archives the list of recent search strings.
|
||||
@param name The autosave name, which is used as a key in the standard user defaults to save the recent searches. If you specify nil or an empty string for this parameter, no autosave name is set and searches are not autosaved.
|
||||
*/
|
||||
- (void)setRecentsAutosaveName:(CPString)name
|
||||
{
|
||||
if (_recentsAutosaveName != nil)
|
||||
[self _deregisterForAutosaveNotification];
|
||||
|
||||
_recentsAutosaveName = name;
|
||||
|
||||
if (_recentsAutosaveName != nil)
|
||||
[self _registerForAutosaveNotification];
|
||||
}
|
||||
|
||||
// Private methods and subclassing
|
||||
|
||||
- (CGRect)contentRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var superbounds = [super contentRectForBounds:bounds];
|
||||
return [self searchTextRectForBounds:superbounds];
|
||||
}
|
||||
|
||||
+ (double)_keyboardDelayForPartialSearchString:(CPString)string
|
||||
{
|
||||
return (6 - MIN([string length], 4)) / 10;
|
||||
}
|
||||
|
||||
- (CPMenu)menu
|
||||
{
|
||||
return _searchMenu;
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque
|
||||
{
|
||||
return [super isOpaque] && [_cancelButton isOpaque] && [_searchButton isOpaque];
|
||||
}
|
||||
|
||||
- (void)_updateCancelButtonVisibility
|
||||
{
|
||||
[_cancelButton setHidden:([[self stringValue] length] === 0)];
|
||||
}
|
||||
|
||||
- (void)_searchFieldTextDidChange:(CPNotification)aNotification
|
||||
{
|
||||
if (![self sendsWholeSearchString])
|
||||
{
|
||||
if ([self sendsSearchStringImmediately])
|
||||
[self _sendPartialString];
|
||||
else
|
||||
{
|
||||
[_partialStringTimer invalidate];
|
||||
var timeInterval = [CPSearchField _keyboardDelayForPartialSearchString:[self stringValue]];
|
||||
|
||||
_partialStringTimer = [CPTimer scheduledTimerWithTimeInterval:timeInterval
|
||||
target:self
|
||||
selector:@selector(_sendPartialString)
|
||||
userInfo:nil
|
||||
repeats:NO];
|
||||
}
|
||||
}
|
||||
|
||||
[self _updateCancelButtonVisibility];
|
||||
}
|
||||
|
||||
- (void)_sendAction:(id)sender
|
||||
{
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
- (void)sendAction:(SEL)anAction to:(id)anObject
|
||||
{
|
||||
[super sendAction:anAction to:anObject];
|
||||
|
||||
[_partialStringTimer invalidate];
|
||||
|
||||
[self _addStringToRecentSearches:[self stringValue]];
|
||||
[self _updateCancelButtonVisibility];
|
||||
}
|
||||
|
||||
- (void)_addStringToRecentSearches:(CPString)string
|
||||
{
|
||||
if (string === nil || string === @"" || [_recentSearches containsObject:string])
|
||||
return;
|
||||
|
||||
var searches = [CPMutableArray arrayWithArray:_recentSearches];
|
||||
[searches addObject:string];
|
||||
[self setRecentSearches:searches];
|
||||
[self _updateSearchMenu];
|
||||
}
|
||||
|
||||
- (CPView)hitTest:(CGPoint)aPoint
|
||||
{
|
||||
// Make sure a hit anywhere within the search field returns the search field itself
|
||||
if (_CGRectContainsPoint([self frame], aPoint))
|
||||
return self;
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
return _canResignFirstResponder && [super resignFirstResponder];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
var location = [anEvent locationInWindow],
|
||||
point = [self convertPoint:location fromView:nil];
|
||||
|
||||
if (_CGRectContainsPoint([self searchButtonRectForBounds:[self bounds]], point))
|
||||
{
|
||||
if (_searchMenuTemplate == nil)
|
||||
{
|
||||
if ([_searchButton target] && [_searchButton action])
|
||||
[_searchButton mouseDown:anEvent];
|
||||
else
|
||||
[self _sendAction:self];
|
||||
}
|
||||
else
|
||||
[self _showMenu];
|
||||
}
|
||||
else if (_CGRectContainsPoint([self cancelButtonRectForBounds:[self bounds]], point))
|
||||
[_cancelButton mouseDown:anEvent];
|
||||
else
|
||||
[super mouseDown:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides the common case items for a recent searches menu. If there are not recent searches,
|
||||
displays a single disabled item:
|
||||
|
||||
No Recent Searches
|
||||
|
||||
If there are 1 more recent searches, it displays:
|
||||
|
||||
Recent Searches
|
||||
recent search 1
|
||||
recent search 2
|
||||
etc.
|
||||
---------------------
|
||||
Clear Recent Searches
|
||||
|
||||
If you wish to add items before or after the template, you can. If you put items
|
||||
before, a separator will automatically be placed before the default template item.
|
||||
If you add items after the default template, it is your responsibility to add a separator.
|
||||
|
||||
To add a custom item:
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"google"
|
||||
action:@selector(google:)
|
||||
keyEquivalent:@""];
|
||||
[item setTag:700];
|
||||
[item setTarget:self];
|
||||
[template addItem:item];
|
||||
|
||||
Be sure that your custom items do not use tags in the range 1000-1003 inclusive.
|
||||
If you wish to maintain state in custom menu items that you add, you will need to maintain
|
||||
the item state yourself, then in the action method of the custom items, modify the items
|
||||
in the search menu template and send [searchField setSearchMenuTemplate:template] to update the menu.
|
||||
*/
|
||||
- (CPMenu)defaultSearchMenuTemplate
|
||||
{
|
||||
var template = [[CPMenu alloc] init],
|
||||
item;
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"Recent Searches"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldRecentsTitleMenuItemTag];
|
||||
[item setEnabled:NO];
|
||||
[template addItem:item];
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"Recent search item"
|
||||
action:@selector(_searchFieldSearch:)
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldRecentsMenuItemTag];
|
||||
[item setTarget:self];
|
||||
[template addItem:item];
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"Clear Recent Searches"
|
||||
action:@selector(_searchFieldClearRecents:)
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldClearRecentsMenuItemTag];
|
||||
[item setTarget:self];
|
||||
[template addItem:item];
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"No Recent Searches"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldNoRecentsMenuItemTag];
|
||||
[item setEnabled:NO];
|
||||
[template addItem:item];
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
- (void)_updateSearchMenu
|
||||
{
|
||||
if (_searchMenuTemplate === nil)
|
||||
return;
|
||||
|
||||
var menu = [[CPMenu alloc] init],
|
||||
countOfRecents = [_recentSearches count],
|
||||
numberOfItems = [_searchMenuTemplate numberOfItems];
|
||||
|
||||
for (var i = 0; i < numberOfItems; i++)
|
||||
{
|
||||
var item = [[_searchMenuTemplate itemAtIndex:i] copy];
|
||||
|
||||
switch ([item tag])
|
||||
{
|
||||
case CPSearchFieldRecentsTitleMenuItemTag:
|
||||
if (countOfRecents === 0)
|
||||
continue;
|
||||
|
||||
if ([menu numberOfItems] > 0)
|
||||
[self _addSeparatorToMenu:menu];
|
||||
break;
|
||||
|
||||
case CPSearchFieldRecentsMenuItemTag:
|
||||
{
|
||||
var itemAction = @selector(_searchFieldSearch:);
|
||||
|
||||
for (var recentIndex = 0; recentIndex < countOfRecents; ++recentIndex)
|
||||
{
|
||||
// RECENT_SEARCH_PREFIX is a hack until CPMenuItem -setIndentationLevel works
|
||||
var recentItem = [[CPMenuItem alloc] initWithTitle:RECENT_SEARCH_PREFIX + [_recentSearches objectAtIndex:recentIndex]
|
||||
action:itemAction
|
||||
keyEquivalent:[item keyEquivalent]];
|
||||
[item setTarget:self];
|
||||
[menu addItem:recentItem];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
case CPSearchFieldClearRecentsMenuItemTag:
|
||||
if (countOfRecents === 0)
|
||||
continue;
|
||||
|
||||
if ([menu numberOfItems] > 0)
|
||||
[self _addSeparatorToMenu:menu];
|
||||
|
||||
[item setAction:@selector(_searchFieldClearRecents:)];
|
||||
[item setTarget:self];
|
||||
break;
|
||||
|
||||
case CPSearchFieldNoRecentsMenuItemTag:
|
||||
if (countOfRecents !== 0)
|
||||
continue;
|
||||
|
||||
if ([menu numberOfItems] > 0)
|
||||
[self _addSeparatorToMenu:menu];
|
||||
break;
|
||||
}
|
||||
|
||||
[item setEnabled:([item isEnabled] && [item action] != nil && [item target] != nil)];
|
||||
[menu addItem:item];
|
||||
}
|
||||
|
||||
[menu setDelegate:self];
|
||||
|
||||
_searchMenu = menu;
|
||||
}
|
||||
|
||||
- (void)_addSeparatorToMenu:(CPMenu)aMenu
|
||||
{
|
||||
var separator = [CPMenuItem separatorItem];
|
||||
[separator setEnabled:NO];
|
||||
[aMenu addItem:separator];
|
||||
}
|
||||
|
||||
- (void)menuWillOpen:(CPMenu)menu
|
||||
{
|
||||
_canResignFirstResponder = NO;
|
||||
}
|
||||
|
||||
- (void)menuDidClose:(CPMenu)menu
|
||||
{
|
||||
_canResignFirstResponder = YES;
|
||||
|
||||
[self becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)_showMenu
|
||||
{
|
||||
if (_searchMenu === nil || [_searchMenu numberOfItems] === 0 || ![self isEnabled])
|
||||
return;
|
||||
|
||||
var aFrame = [[self superview] convertRect:[self frame] toView:nil],
|
||||
location = CPMakePoint(aFrame.origin.x + 10, aFrame.origin.y + aFrame.size.height - 4);
|
||||
|
||||
var anEvent = [CPEvent mouseEventWithType:CPRightMouseDown location:location modifierFlags:0 timestamp:[[CPApp currentEvent] timestamp] windowNumber:[[self window] windowNumber] context:nil eventNumber:1 clickCount:1 pressure:0];
|
||||
|
||||
[self selectAll:nil];
|
||||
[CPMenu popUpContextMenu:_searchMenu withEvent:anEvent forView:self];
|
||||
}
|
||||
|
||||
- (void)_sendPartialString
|
||||
{
|
||||
[super sendAction:[self action] to:[self target]];
|
||||
[_partialStringTimer invalidate];
|
||||
}
|
||||
|
||||
- (void)cancelOperation:(id)sender
|
||||
{
|
||||
[self setObjectValue:@""];
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
[self _updateCancelButtonVisibility];
|
||||
}
|
||||
|
||||
- (void)_searchFieldSearch:(id)sender
|
||||
{
|
||||
var searchString = [[sender title] substringFromIndex:[RECENT_SEARCH_PREFIX length]];
|
||||
|
||||
if ([sender tag] != CPSearchFieldRecentsMenuItemTag)
|
||||
[self _addStringToRecentSearches:searchString];
|
||||
|
||||
[self setObjectValue:searchString];
|
||||
[self _sendPartialString];
|
||||
[self selectAll:nil];
|
||||
|
||||
[self _updateCancelButtonVisibility];
|
||||
}
|
||||
|
||||
- (void)_searchFieldClearRecents:(id)sender
|
||||
{
|
||||
[self setRecentSearches:[CPArray array]];
|
||||
[self _updateSearchMenu];
|
||||
[self setStringValue:@""];
|
||||
[self _updateCancelButtonVisibility];
|
||||
}
|
||||
|
||||
- (void)_registerForAutosaveNotification
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_updateAutosavedRecents:) name:CPAutosavedRecentsChangedNotification object:_recentsAutosaveName];
|
||||
}
|
||||
|
||||
- (void)_deregisterForAutosaveNotification
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPAutosavedRecentsChangedNotification object:_recentsAutosaveName];
|
||||
}
|
||||
|
||||
- (void)_autosaveRecentSearchList
|
||||
{
|
||||
if (_recentsAutosaveName != nil)
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPAutosavedRecentsChangedNotification object:_recentsAutosaveName];
|
||||
}
|
||||
|
||||
- (void)_updateAutosavedRecents:(id)notification
|
||||
{
|
||||
var name = [notification object];
|
||||
[[CPUserDefaults standardUserDefaults] setObject:_recentSearches forKey:name];
|
||||
}
|
||||
|
||||
- (void)_loadRecentSearchList
|
||||
{
|
||||
var name = [self recentsAutosaveName];
|
||||
if (name === nil)
|
||||
return;
|
||||
|
||||
var list = [[CPUserDefaults standardUserDefaults] objectForKey:name];
|
||||
|
||||
if (list !== nil)
|
||||
_recentSearches = list;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
|
||||
CPSendsWholeSearchStringKey = @"CPSendsWholeSearchStringKey",
|
||||
CPSendsSearchStringImmediatelyKey = @"CPSendsSearchStringImmediatelyKey",
|
||||
CPMaximumRecentsKey = @"CPMaximumRecentsKey",
|
||||
CPSearchMenuTemplateKey = @"CPSearchMenuTemplateKey";
|
||||
|
||||
@implementation CPSearchField (CPCoding)
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)coder
|
||||
{
|
||||
[_searchButton removeFromSuperview];
|
||||
[_cancelButton removeFromSuperview];
|
||||
|
||||
[super encodeWithCoder:coder];
|
||||
|
||||
if (_searchButton)
|
||||
[self addSubview:_searchButton];
|
||||
if (_cancelButton)
|
||||
[self addSubview:_cancelButton];
|
||||
|
||||
[coder encodeBool:_sendsWholeSearchString forKey:CPSendsWholeSearchStringKey];
|
||||
[coder encodeBool:_sendsSearchStringImmediately forKey:CPSendsSearchStringImmediatelyKey];
|
||||
[coder encodeInt:_maximumRecents forKey:CPMaximumRecentsKey];
|
||||
|
||||
if (_recentsAutosaveName)
|
||||
[coder encodeObject:_recentsAutosaveName forKey:CPRecentsAutosaveNameKey];
|
||||
|
||||
if (_searchMenuTemplate)
|
||||
[coder encodeObject:_searchMenuTemplate forKey:CPSearchMenuTemplateKey];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)coder
|
||||
{
|
||||
if (self = [super initWithCoder:coder])
|
||||
{
|
||||
[self setRecentsAutosaveName:[coder decodeObjectForKey:CPRecentsAutosaveNameKey]];
|
||||
_sendsWholeSearchString = [coder decodeBoolForKey:CPSendsWholeSearchStringKey];
|
||||
_sendsSearchStringImmediately = [coder decodeBoolForKey:CPSendsSearchStringImmediatelyKey];
|
||||
_maximumRecents = [coder decodeIntForKey:CPMaximumRecentsKey];
|
||||
|
||||
var template = [coder decodeObjectForKey:CPSearchMenuTemplateKey];
|
||||
|
||||
if (template)
|
||||
[self setSearchMenuTemplate:template];
|
||||
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* CPSecureTextField.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CPTextField.j"
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
*/
|
||||
@implementation CPSecureTextField : CPTextField
|
||||
{
|
||||
}
|
||||
|
||||
- (BOOL)isSecure
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,962 +0,0 @@
|
||||
/*
|
||||
* CPSegmentedControl.j
|
||||
* AppKit
|
||||
*
|
||||
* 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/CPArray.j>
|
||||
|
||||
@import "CPControl.j"
|
||||
|
||||
|
||||
CPSegmentSwitchTrackingSelectOne = 0;
|
||||
CPSegmentSwitchTrackingSelectAny = 1;
|
||||
CPSegmentSwitchTrackingMomentary = 2;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPSegmentedControl
|
||||
|
||||
This class is a horizontal button with multiple segments.
|
||||
*/
|
||||
@implementation CPSegmentedControl : CPControl
|
||||
{
|
||||
CPArray _segments;
|
||||
CPArray _themeStates;
|
||||
|
||||
int _selectedSegment;
|
||||
int _segmentStyle;
|
||||
CPSegmentSwitchTracking _trackingMode;
|
||||
|
||||
unsigned _trackingSegment;
|
||||
BOOL _trackingHighlighted;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return "segmented-control";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[CPCenterTextAlignment, CPCenterVerticalTextAlignment, CPImageLeft, CPImageScaleNone, _CGInsetMakeZero(), _CGInsetMakeZero(), [CPNull null], [CPNull null], [CPNull null], [CPNull null], 1.0, 24.0]
|
||||
forKeys:[@"alignment", @"vertical-alignment", @"image-position", @"image-scaling", @"bezel-inset", @"content-inset", @"left-segment-bezel-color", @"right-segment-bezel-color", @"center-segment-bezel-color", @"divider-bezel-color", @"divider-thickness", @"default-height"]];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aRect
|
||||
{
|
||||
_segments = [];
|
||||
_themeStates = [];
|
||||
|
||||
self = [super initWithFrame:aRect];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_selectedSegment = -1;
|
||||
|
||||
_trackingMode = CPSegmentSwitchTrackingSelectOne;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tag of the selected segment.
|
||||
*/
|
||||
- (int)selectedTag
|
||||
{
|
||||
return [_segments[_selectedSegment] tag];
|
||||
}
|
||||
|
||||
// Specifying the number of segments
|
||||
/*!
|
||||
Sets the number of segments in the button.
|
||||
@param aCount the number of segments on the button
|
||||
*/
|
||||
- (void)setSegmentCount:(unsigned)aCount
|
||||
{
|
||||
if (_segments.length == aCount)
|
||||
return;
|
||||
|
||||
var height = CGRectGetHeight([self bounds]),
|
||||
dividersBefore = MAX(0, _segments.length - 1),
|
||||
dividersAfter = MAX(0, aCount - 1);
|
||||
|
||||
if (_segments.length < aCount)
|
||||
{
|
||||
for (var index = _segments.length; index < aCount; ++index)
|
||||
{
|
||||
_segments[index] = [[_CPSegmentItem alloc] init];
|
||||
_themeStates[index] = CPThemeStateNormal;
|
||||
}
|
||||
}
|
||||
else if (aCount < _segments.length)
|
||||
{
|
||||
_segments.length = aCount;
|
||||
_themeStates.length = aCount;
|
||||
}
|
||||
|
||||
if (_selectedSegment >= _segments.length)
|
||||
_selectedSegment = -1;
|
||||
|
||||
// Make space for/remove space used by dividers.
|
||||
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"],
|
||||
delta = thickness * (dividersAfter - dividersBefore),
|
||||
frame = [self frame];
|
||||
|
||||
if (delta)
|
||||
[self setFrameSize:CGSizeMake(frame.size.width + delta, frame.size.height)];
|
||||
|
||||
[self tileWithChangedSegment:0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of segments in the button.
|
||||
*/
|
||||
- (unsigned)segmentCount
|
||||
{
|
||||
return _segments.length;
|
||||
}
|
||||
|
||||
// Specifying Selected Segment
|
||||
/*!
|
||||
Selects a segment.
|
||||
@param aSegment the segment to select
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (void)setSelectedSegment:(unsigned)aSegment
|
||||
{
|
||||
// setSelected:forSegment throws the exception for us (if necessary)
|
||||
[self setSelected:YES forSegment:aSegment];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the selected segment.
|
||||
*/
|
||||
- (unsigned)selectedSegment
|
||||
{
|
||||
return _selectedSegment;
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the button segment with the specified tag.
|
||||
*/
|
||||
- (BOOL)selectSegmentWithTag:(int)aTag
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
for (; index < _segments.length; ++index)
|
||||
if (_segments[index].tag == aTag)
|
||||
{
|
||||
[self setSelectedSegment:index];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Specifying Tracking Mode
|
||||
|
||||
- (BOOL)isTracking
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)setTrackingMode:(CPSegmentSwitchTracking)aTrackingMode
|
||||
{
|
||||
if (_trackingMode == aTrackingMode)
|
||||
return;
|
||||
|
||||
_trackingMode = aTrackingMode;
|
||||
|
||||
if (_trackingMode == CPSegmentSwitchTrackingSelectOne)
|
||||
{
|
||||
var index = 0,
|
||||
selected = NO;
|
||||
|
||||
for (; index < _segments.length; ++index)
|
||||
if ([_segments[index] selected])
|
||||
if (selected)
|
||||
[self setSelected:NO forSegment:index];
|
||||
else
|
||||
selected = YES;
|
||||
}
|
||||
|
||||
else if (_trackingMode == CPSegmentSwitchTrackingMomentary)
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
for (; index < _segments.length; ++index)
|
||||
if ([_segments[index] selected])
|
||||
[self setSelected:NO forSegment:index];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the control's tracking mode.
|
||||
*/
|
||||
- (CPSegmentSwitchTracking)trackingMode
|
||||
{
|
||||
return _trackingMode;
|
||||
}
|
||||
|
||||
// Working with Individual Segments
|
||||
/*!
|
||||
Sets the width of the specified segment.
|
||||
@param aWidth the new width for the segment
|
||||
@param aSegment the segment to set the width for
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (void)setWidth:(float)aWidth forSegment:(unsigned)aSegment
|
||||
{
|
||||
[_segments[aSegment] setWidth:aWidth];
|
||||
|
||||
[self tileWithChangedSegment:aSegment];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the width for the specified segment.
|
||||
@param aSegment the segment to get the width for
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (float)widthForSegment:(unsigned)aSegment
|
||||
{
|
||||
return [_segments[aSegment] width];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the image for the specified segment.
|
||||
@param anImage the image for the segment
|
||||
@param aSegment the segment to set the image on
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (void)setImage:(CPImage)anImage forSegment:(unsigned)aSegment
|
||||
{
|
||||
[_segments[aSegment] setImage:anImage];
|
||||
|
||||
[self tileWithChangedSegment:aSegment];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the image for the specified segment
|
||||
@param aSegment the segment to obtain the image for
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (CPImage)imageForSegment:(unsigned)aSegment
|
||||
{
|
||||
return [_segments[aSegment] image];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the label for the specified segment
|
||||
@param aLabel the label for the segment
|
||||
@param aSegment the segment to label
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (void)setLabel:(CPString)aLabel forSegment:(unsigned)aSegment
|
||||
{
|
||||
[_segments[aSegment] setLabel:aLabel];
|
||||
|
||||
[self tileWithChangedSegment:aSegment];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the label for the specified segment
|
||||
@param the segment to obtain the label for
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (CPString)labelForSegment:(unsigned)aSegment
|
||||
{
|
||||
return [_segments[aSegment] label];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the menu for the specified segment
|
||||
@param aMenu the menu to set
|
||||
@param aSegment the segment to set the menu on
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (void)setMenu:(CPMenu)aMenu forSegment:(unsigned)aSegment
|
||||
{
|
||||
[_segments[aSegment] setMenu:aMenu];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the menu for the specified segment.
|
||||
@param aSegment the segment to obtain the menu for
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (CPMenu)menuForSegment:(unsigned)aSegment
|
||||
{
|
||||
return [_segments[aSegment] menu];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the selection for the specified segment. If only one segment
|
||||
can be selected at a time, any other segment will be deselected.
|
||||
@param isSelected \c YES selects the segment. \c NO deselects it.
|
||||
@param aSegment the segment to set the selection for
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (void)setSelected:(BOOL)isSelected forSegment:(unsigned)aSegment
|
||||
{
|
||||
var segment = _segments[aSegment];
|
||||
|
||||
// If we're already in this state, bail.
|
||||
if ([segment selected] == isSelected)
|
||||
return;
|
||||
|
||||
[segment setSelected:isSelected];
|
||||
|
||||
_themeStates[aSegment] = isSelected ? CPThemeStateSelected : CPThemeStateNormal;
|
||||
|
||||
// We need to do some cleanup if we only allow one selection.
|
||||
if (isSelected)
|
||||
{
|
||||
var oldSelectedSegment = _selectedSegment;
|
||||
|
||||
_selectedSegment = aSegment;
|
||||
|
||||
if (_trackingMode == CPSegmentSwitchTrackingSelectOne && oldSelectedSegment != aSegment && oldSelectedSegment != -1)
|
||||
{
|
||||
[_segments[oldSelectedSegment] setSelected:NO];
|
||||
_themeStates[oldSelectedSegment] = CPThemeStateNormal;
|
||||
|
||||
[self drawSegmentBezel:oldSelectedSegment highlight:NO];
|
||||
}
|
||||
}
|
||||
|
||||
if (_trackingMode != CPSegmentSwitchTrackingMomentary)
|
||||
[self drawSegmentBezel:aSegment highlight:NO];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the specified segment is selected.
|
||||
@param aSegment the segment to check for selection
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (BOOL)isSelectedForSegment:(unsigned)aSegment
|
||||
{
|
||||
return [_segments[aSegment] selected];
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables/disables the specified segment.
|
||||
@param isEnabled \c YES enables the segment
|
||||
@param aSegment the segment to enable/disable
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)shouldBeEnabled forSegment:(unsigned)aSegment
|
||||
{
|
||||
if ([_segments[aSegment] enabled] === shouldBeEnabled)
|
||||
return;
|
||||
|
||||
[_segments[aSegment] setEnabled:shouldBeEnabled];
|
||||
|
||||
if (shouldBeEnabled)
|
||||
_themeStates[aSegment] &= ~CPThemeStateDisabled;
|
||||
else
|
||||
_themeStates[aSegment] |= CPThemeStateDisabled;
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the specified segment is enabled.
|
||||
@param aSegment the segment to check
|
||||
@throws CPRangeException if \c aSegment is out of bounds
|
||||
*/
|
||||
- (BOOL)isEnabledForSegment:(unsigned)aSegment
|
||||
{
|
||||
return [_segments[aSegment] enabled];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a tag for the specified segment.
|
||||
@param aTag the tag to set
|
||||
@param aSegment the segment to set the tag on
|
||||
*/
|
||||
- (void)setTag:(int)aTag forSegment:(unsigned)aSegment
|
||||
{
|
||||
[_segments[aSegment] setTag:aTag];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tag for the specified segment.
|
||||
@param aSegment the segment to obtain the tag for
|
||||
*/
|
||||
- (int)tagForSegment:(unsigned)aSegment
|
||||
{
|
||||
return [_segments[aSegment] tag];
|
||||
}
|
||||
|
||||
// Drawings
|
||||
/*!
|
||||
Draws the specified segment bezel
|
||||
@param aSegment the segment to draw the bezel for
|
||||
@param shouldHighlight \c YES highlights the bezel
|
||||
*/
|
||||
- (void)drawSegmentBezel:(int)aSegment highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
if (shouldHighlight)
|
||||
_themeStates[aSegment] |= CPThemeStateHighlighted;
|
||||
else
|
||||
_themeStates[aSegment] &= ~CPThemeStateHighlighted;
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (float)_leftOffsetForSegment:(unsigned)segment
|
||||
{
|
||||
var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
|
||||
|
||||
if (segment == 0)
|
||||
return bezelInset.left;
|
||||
|
||||
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
|
||||
|
||||
return [self _leftOffsetForSegment:segment - 1] + [self widthForSegment:segment - 1] + thickness;
|
||||
}
|
||||
|
||||
- (unsigned)_indexOfLastSegment
|
||||
{
|
||||
var lastSegmentIndex = [_segments count] - 1;
|
||||
|
||||
if (lastSegmentIndex < 0)
|
||||
lastSegmentIndex = 0;
|
||||
|
||||
return lastSegmentIndex;
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
var height = [self currentValueForThemeAttribute:@"default-height"],
|
||||
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"],
|
||||
bounds = [self bounds];
|
||||
|
||||
if (aName === "left-segment-bezel")
|
||||
{
|
||||
return CGRectMake(bezelInset.left, bezelInset.top, contentInset.left, height);
|
||||
}
|
||||
else if (aName === "right-segment-bezel")
|
||||
{
|
||||
return CPRectMake(CGRectGetWidth([self bounds]) - contentInset.right,
|
||||
bezelInset.top,
|
||||
contentInset.right,
|
||||
height);
|
||||
}
|
||||
else if (aName.indexOf("segment-bezel") === 0)
|
||||
{
|
||||
var segment = parseInt(aName.substring("segment-bezel-".length), 10),
|
||||
frame = CGRectCreateCopy([_segments[segment] frame]);
|
||||
|
||||
if (segment === 0)
|
||||
{
|
||||
frame.origin.x += contentInset.left;
|
||||
frame.size.width -= contentInset.left;
|
||||
}
|
||||
|
||||
if (segment === _segments.length - 1)
|
||||
frame.size.width = CGRectGetWidth([self bounds]) - contentInset.right - frame.origin.x;
|
||||
|
||||
return frame;
|
||||
}
|
||||
else if (aName.indexOf("divider-bezel") === 0)
|
||||
{
|
||||
var segment = parseInt(aName.substring("divider-bezel-".length), 10),
|
||||
width = [self widthForSegment:segment],
|
||||
left = [self _leftOffsetForSegment:segment],
|
||||
thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
|
||||
|
||||
return CGRectMake(left + width, bezelInset.top, thickness, height);
|
||||
}
|
||||
else if (aName.indexOf("segment-content") === 0)
|
||||
{
|
||||
var segment = parseInt(aName.substring("segment-content-".length), 10);
|
||||
|
||||
return [self contentFrameForSegment:segment];
|
||||
}
|
||||
|
||||
return [super rectForEphemeralSubviewNamed:aName];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if ([aName hasPrefix:@"segment-content"])
|
||||
return [[_CPImageAndTextView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
return [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (_segments.length <= 0)
|
||||
return;
|
||||
|
||||
var themeState = _themeStates[0];
|
||||
|
||||
themeState |= _themeState & CPThemeStateDisabled;
|
||||
|
||||
var leftCapColor = [self valueForThemeAttribute:@"left-segment-bezel-color"
|
||||
inState:themeState],
|
||||
|
||||
leftBezelView = [self layoutEphemeralSubviewNamed:@"left-segment-bezel"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
[leftBezelView setBackgroundColor:leftCapColor];
|
||||
|
||||
var themeState = _themeStates[_themeStates.length - 1];
|
||||
|
||||
themeState |= _themeState & CPThemeStateDisabled;
|
||||
|
||||
var rightCapColor = [self valueForThemeAttribute:@"right-segment-bezel-color"
|
||||
inState:themeState],
|
||||
|
||||
rightBezelView = [self layoutEphemeralSubviewNamed:@"right-segment-bezel"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
[rightBezelView setBackgroundColor:rightCapColor];
|
||||
|
||||
for (var i = 0, count = _themeStates.length; i < count; i++)
|
||||
{
|
||||
var themeState = _themeStates[i];
|
||||
|
||||
themeState |= _themeState & CPThemeStateDisabled;
|
||||
|
||||
var bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color"
|
||||
inState:themeState],
|
||||
|
||||
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
[bezelView setBackgroundColor:bezelColor];
|
||||
|
||||
|
||||
// layout image/title views
|
||||
var segment = _segments[i],
|
||||
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"segment-bezel-" + i];
|
||||
|
||||
[contentView setText:[segment label]];
|
||||
[contentView setImage:[segment image]];
|
||||
|
||||
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
|
||||
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:themeState]];
|
||||
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
|
||||
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
|
||||
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
|
||||
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
|
||||
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
|
||||
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
|
||||
|
||||
if ([segment image] && [segment label])
|
||||
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
|
||||
else if ([segment image])
|
||||
[contentView setImagePosition:CPImageOnly];
|
||||
|
||||
if (i == count - 1)
|
||||
continue;
|
||||
|
||||
var borderState = _themeStates[i] | _themeStates[i + 1];
|
||||
|
||||
borderState = (borderState & CPThemeStateSelected & ~CPThemeStateHighlighted) ? CPThemeStateSelected : CPThemeStateNormal;
|
||||
|
||||
borderState |= _themeState & CPThemeStateDisabled;
|
||||
|
||||
var borderColor = [self valueForThemeAttribute:@"divider-bezel-color"
|
||||
inState:borderState],
|
||||
|
||||
borderView = [self layoutEphemeralSubviewNamed:"divider-bezel-" + i
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
[borderView setBackgroundColor:borderColor];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Draws the specified segment
|
||||
@param aSegment the segment to draw
|
||||
@param shouldHighlight \c YES highlights the bezel
|
||||
*/
|
||||
- (void)drawSegment:(int)aSegment highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
}
|
||||
|
||||
- (void)tileWithChangedSegment:(unsigned)aSegment
|
||||
{
|
||||
if (aSegment >= _segments.length)
|
||||
return;
|
||||
|
||||
var segment = _segments[aSegment],
|
||||
segmentWidth = [segment width],
|
||||
themeState = _themeStates[aSegment] | (_themeState & CPThemeStateDisabled),
|
||||
contentInset = [self valueForThemeAttribute:@"content-inset" inState:themeState],
|
||||
font = [self font];
|
||||
|
||||
if (!segmentWidth)
|
||||
{
|
||||
if ([segment image] && [segment label])
|
||||
segmentWidth = [[segment label] sizeWithFont:font].width + [[segment image] size].width + contentInset.left + contentInset.right;
|
||||
else if (segment.image)
|
||||
segmentWidth = [[segment image] size].width + contentInset.left + contentInset.right;
|
||||
else if (segment.label)
|
||||
segmentWidth = [[segment label] sizeWithFont:font].width + contentInset.left + contentInset.right;
|
||||
else
|
||||
segmentWidth = 0.0;
|
||||
}
|
||||
|
||||
var delta = segmentWidth - CGRectGetWidth([segment frame]);
|
||||
|
||||
if (!delta)
|
||||
{
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update control size
|
||||
var frame = [self frame];
|
||||
|
||||
[self setFrameSize:CGSizeMake(CGRectGetWidth(frame) + delta, CGRectGetHeight(frame))];
|
||||
|
||||
// Update segment width
|
||||
[segment setWidth:segmentWidth];
|
||||
[segment setFrame:[self frameForSegment:aSegment]];
|
||||
|
||||
// Update following segments widths
|
||||
var index = aSegment + 1;
|
||||
|
||||
for (; index < _segments.length; ++index)
|
||||
{
|
||||
[_segments[index] frame].origin.x += delta;
|
||||
|
||||
[self drawSegmentBezel:index highlight:NO];
|
||||
[self drawSegment:index highlight:NO];
|
||||
}
|
||||
|
||||
[self drawSegmentBezel:aSegment highlight:NO];
|
||||
[self drawSegment:aSegment highlight:NO];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the bounding rectangle for the specified segment.
|
||||
@param aSegment the segment to get the rectangle for
|
||||
*/
|
||||
- (CGRect)frameForSegment:(unsigned)aSegment
|
||||
{
|
||||
return [self bezelFrameForSegment:aSegment];
|
||||
}
|
||||
|
||||
- (CGRect)bezelFrameForSegment:(unsigned)aSegment
|
||||
{
|
||||
var height = [self currentValueForThemeAttribute:@"default-height"],
|
||||
bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"],
|
||||
width = [self widthForSegment:aSegment],
|
||||
left = [self _leftOffsetForSegment:aSegment];
|
||||
|
||||
return CGRectMake(left, bezelInset.top, width, height);
|
||||
}
|
||||
|
||||
- (CGRect)contentFrameForSegment:(unsigned)aSegment
|
||||
{
|
||||
var height = [self currentValueForThemeAttribute:@"default-height"],
|
||||
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
width = [self widthForSegment:aSegment],
|
||||
left = [self _leftOffsetForSegment:aSegment];
|
||||
|
||||
return CGRectMake(left + contentInset.left, contentInset.top, width - contentInset.left - contentInset.right, height - contentInset.top - contentInset.bottom);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the segment that is hit by the specified point.
|
||||
@param aPoint the point to test for a segment hit
|
||||
@return the intersecting segment
|
||||
*/
|
||||
- (unsigned)testSegment:(CGPoint)aPoint
|
||||
{
|
||||
var location = [self convertPoint:aPoint fromView:nil],
|
||||
count = _segments.length;
|
||||
|
||||
while (count--)
|
||||
if (CGRectContainsPoint([_segments[count] frame], aPoint))
|
||||
return count;
|
||||
|
||||
if (_segments.length)
|
||||
{
|
||||
var adjustedLastFrame = CGRectCreateCopy([_segments[_segments.length - 1] frame]);
|
||||
adjustedLastFrame.size.width = CGRectGetWidth([self bounds]) - adjustedLastFrame.origin.x;
|
||||
|
||||
if (CGRectContainsPoint(adjustedLastFrame, aPoint))
|
||||
return _segments.length - 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
[self trackSegment:anEvent];
|
||||
}
|
||||
|
||||
// FIXME: this should be fixed way up in cpbutton/cpcontrol.
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Handles events for the segment
|
||||
@param anEvent the event to handle
|
||||
*/
|
||||
- (void)trackSegment:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type],
|
||||
location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if (type == CPLeftMouseUp)
|
||||
{
|
||||
if (_trackingSegment == -1)
|
||||
return;
|
||||
|
||||
if (_trackingSegment === [self testSegment:location])
|
||||
{
|
||||
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
|
||||
{
|
||||
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
|
||||
|
||||
// With ANY, _selectedSegment means last pressed.
|
||||
_selectedSegment = _trackingSegment;
|
||||
}
|
||||
else
|
||||
[self setSelected:YES forSegment:_trackingSegment];
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
|
||||
{
|
||||
[self setSelected:NO forSegment:_trackingSegment];
|
||||
|
||||
_selectedSegment = -1;
|
||||
}
|
||||
}
|
||||
|
||||
[self drawSegmentBezel:_trackingSegment highlight:NO];
|
||||
|
||||
_trackingSegment = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == CPLeftMouseDown)
|
||||
{
|
||||
var trackingSegment = [self testSegment:location];
|
||||
if (trackingSegment > -1 && [self isEnabledForSegment:trackingSegment])
|
||||
{
|
||||
_trackingHighlighted = YES;
|
||||
_trackingSegment = trackingSegment;
|
||||
[self drawSegmentBezel:_trackingSegment highlight:YES];
|
||||
}
|
||||
}
|
||||
|
||||
else if (type == CPLeftMouseDragged)
|
||||
{
|
||||
if (_trackingSegment == -1)
|
||||
return;
|
||||
|
||||
var highlighted = [self testSegment:location] === _trackingSegment;
|
||||
if (highlighted != _trackingHighlighted)
|
||||
{
|
||||
_trackingHighlighted = highlighted;
|
||||
|
||||
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
|
||||
}
|
||||
}
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
[super setFont:aFont];
|
||||
|
||||
[self tileWithChangedSegment:0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPSegmentedControlSegmentsKey = "CPSegmentedControlSegmentsKey",
|
||||
CPSegmentedControlSelectedKey = "CPSegmentedControlSelectedKey",
|
||||
CPSegmentedControlSegmentStyleKey = "CPSegmentedControlSegmentStyleKey",
|
||||
CPSegmentedControlTrackingModeKey = "CPSegmentedControlTrackingModeKey";
|
||||
|
||||
@implementation CPSegmentedControl (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
var frame = [self frame],
|
||||
originalWidth = frame.size.width;
|
||||
|
||||
frame.size.width = 0;
|
||||
|
||||
[self setFrame:frame];
|
||||
|
||||
_segments = [aCoder decodeObjectForKey:CPSegmentedControlSegmentsKey];
|
||||
_segmentStyle = [aCoder decodeIntForKey:CPSegmentedControlSegmentStyleKey];
|
||||
_themeStates = [];
|
||||
|
||||
if ([aCoder containsValueForKey:CPSegmentedControlSelectedKey])
|
||||
_selectedSegment = [aCoder decodeIntForKey:CPSegmentedControlSelectedKey];
|
||||
else
|
||||
_selectedSegment = -1;
|
||||
|
||||
if ([aCoder containsValueForKey:CPSegmentedControlTrackingModeKey])
|
||||
_trackingMode = [aCoder decodeIntForKey:CPSegmentedControlTrackingModeKey];
|
||||
else
|
||||
_trackingMode = CPSegmentSwitchTrackingSelectOne;
|
||||
|
||||
// HACK
|
||||
|
||||
for (var i = 0; i < _segments.length; i++)
|
||||
{
|
||||
_themeStates[i] = [_segments[i] selected] ? CPThemeStateSelected : CPThemeStateNormal;
|
||||
[self tileWithChangedSegment:i];
|
||||
}
|
||||
|
||||
var difference = MAX(originalWidth - [self frame].size.width, 0.0),
|
||||
remainingWidth = FLOOR(difference / _segments.length);
|
||||
|
||||
for (var i = 0; i < _segments.length; i++)
|
||||
[self setWidth:[_segments[i] width] + remainingWidth forSegment:i];
|
||||
|
||||
[self tileWithChangedSegment:0];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_segments forKey:CPSegmentedControlSegmentsKey];
|
||||
[aCoder encodeInt:_selectedSegment forKey:CPSegmentedControlSelectedKey];
|
||||
[aCoder encodeInt:_segmentStyle forKey:CPSegmentedControlSegmentStyleKey];
|
||||
[aCoder encodeInt:_trackingMode forKey:CPSegmentedControlTrackingModeKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation _CPSegmentItem : CPObject
|
||||
{
|
||||
CPImage image @accessors;
|
||||
CPString label @accessors;
|
||||
CPMenu menu @accessors;
|
||||
BOOL selected @accessors;
|
||||
BOOL enabled @accessors;
|
||||
int tag @accessors;
|
||||
int width @accessors;
|
||||
|
||||
CGRect frame @accessors;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
image = nil;
|
||||
label = @"";
|
||||
menu = nil;
|
||||
selected = NO;
|
||||
enabled = YES;
|
||||
tag = -1;
|
||||
width = 0;
|
||||
|
||||
frame = CGRectMakeZero();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPSegmentItemImageKey = "CPSegmentItemImageKey",
|
||||
CPSegmentItemLabelKey = "CPSegmentItemLabelKey",
|
||||
CPSegmentItemMenuKey = "CPSegmentItemMenuKey",
|
||||
CPSegmentItemSelectedKey = "CPSegmentItemSelectedKey",
|
||||
CPSegmentItemEnabledKey = "CPSegmentItemEnabledKey",
|
||||
CPSegmentItemTagKey = "CPSegmentItemTagKey",
|
||||
CPSegmentItemWidthKey = "CPSegmentItemWidthKey";
|
||||
|
||||
@implementation _CPSegmentItem (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
image = [aCoder decodeObjectForKey:CPSegmentItemImageKey];
|
||||
label = [aCoder decodeObjectForKey:CPSegmentItemLabelKey];
|
||||
menu = [aCoder decodeObjectForKey:CPSegmentItemMenuKey];
|
||||
selected = [aCoder decodeBoolForKey:CPSegmentItemSelectedKey];
|
||||
enabled = [aCoder decodeBoolForKey:CPSegmentItemEnabledKey];
|
||||
tag = [aCoder decodeIntForKey:CPSegmentItemTagKey];
|
||||
width = [aCoder decodeFloatForKey:CPSegmentItemWidthKey];
|
||||
|
||||
frame = CGRectMakeZero();
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:image forKey:CPSegmentItemImageKey];
|
||||
[aCoder encodeObject:label forKey:CPSegmentItemLabelKey];
|
||||
[aCoder encodeObject:menu forKey:CPSegmentItemMenuKey];
|
||||
[aCoder encodeBool:selected forKey:CPSegmentItemSelectedKey];
|
||||
[aCoder encodeBool:enabled forKey:CPSegmentItemEnabledKey];
|
||||
[aCoder encodeInt:tag forKey:CPSegmentItemTagKey];
|
||||
[aCoder encodeFloat:width forKey:CPSegmentItemWidthKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* CPShadow.j
|
||||
* AppKit
|
||||
*
|
||||
* 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>
|
||||
|
||||
|
||||
/*!
|
||||
@deprecated
|
||||
@class CPShadow
|
||||
|
||||
Instances of this class contain the attributes of a drop shadow used in Cappuccino.
|
||||
*/
|
||||
@implementation CPShadow : CPObject
|
||||
{
|
||||
CPSize _offset;
|
||||
float _blurRadius;
|
||||
CPColor _color;
|
||||
|
||||
CPString _cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a shadow with the specified attributes.
|
||||
@param anOffset the shadow's offset
|
||||
@param aBlurRadius the shadow's blur radius
|
||||
@param aColor the shadow's color
|
||||
@return the new shadow
|
||||
*/
|
||||
+ (id)shadowWithOffset:(CGSize)anOffset blurRadius:(float)aBlurRadius color:(CPColor)aColor
|
||||
{
|
||||
return [[CPShadow alloc] _initWithOffset:anOffset blurRadius:aBlurRadius color:aColor];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithOffset:(CPSize)anOffset blurRadius:(float)aBlurRadius color:(CPColor)aColor
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_offset = anOffset;
|
||||
_blurRadius = aBlurRadius;
|
||||
_color = aColor;
|
||||
|
||||
_cssString = [_color cssString] + " " + ROUND(anOffset.width) + @"px " + ROUND(anOffset.height) + @"px " + ROUND(_blurRadius) + @"px";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the shadow's offset.
|
||||
*/
|
||||
- (CGSize)shadowOffset
|
||||
{
|
||||
return _offset;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the shadow's blur radius
|
||||
*/
|
||||
- (float)shadowBlurRadius
|
||||
{
|
||||
return _blurRadius;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the shadow's color.
|
||||
*/
|
||||
- (CPColor)shadowColor
|
||||
{
|
||||
return _color;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a CSS string representation of the shadow.
|
||||
*/
|
||||
- (CPString)cssString
|
||||
{
|
||||
return _cssString;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* CPShadowView.j
|
||||
* AppKit
|
||||
*
|
||||
* 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/CPBundle.j>
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
CPLightShadow = 0;
|
||||
CPHeavyShadow = 1;
|
||||
|
||||
var CPShadowViewLightBackgroundColor = nil,
|
||||
CPShadowViewHeavyBackgroundColor = nil;
|
||||
|
||||
var LIGHT_LEFT_INSET = 3.0,
|
||||
LIGHT_RIGHT_INSET = 3.0,
|
||||
LIGHT_TOP_INSET = 3.0,
|
||||
LIGHT_BOTTOM_INSET = 5.0,
|
||||
|
||||
HEAVY_LEFT_INSET = 7.0,
|
||||
HEAVY_RIGHT_INSET = 7.0,
|
||||
HEAVY_TOP_INSET = 5.0,
|
||||
HEAVY_BOTTOM_INSET = 5.0;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
*/
|
||||
@implementation CPShadowView : CPView
|
||||
{
|
||||
CPShadowWeight _weight;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPShadowView class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:[self class]];
|
||||
|
||||
CPShadowViewLightBackgroundColor = CPColorWithImages([
|
||||
[@"CPShadowView/CPShadowViewLightTopLeft.png", 9.0, 9.0, bundle],
|
||||
[@"CPShadowView/CPShadowViewLightTop.png", 1.0, 9.0, bundle],
|
||||
[@"CPShadowView/CPShadowViewLightTopRight.png", 9.0, 9.0, bundle],
|
||||
|
||||
[@"CPShadowView/CPShadowViewLightLeft.png", 9.0, 1.0, bundle],
|
||||
nil,
|
||||
[@"CPShadowView/CPShadowViewLightRight.png", 9.0, 1.0, bundle],
|
||||
|
||||
[@"CPShadowView/CPShadowViewLightBottomLeft.png", 9.0, 9.0, bundle],
|
||||
[@"CPShadowView/CPShadowViewLightBottom.png", 1.0, 9.0, bundle],
|
||||
[@"CPShadowView/CPShadowViewLightBottomRight.png", 9.0, 9.0, bundle]
|
||||
]);
|
||||
|
||||
CPShadowViewHeavyBackgroundColor = CPColorWithImages([
|
||||
[@"CPShadowView/CPShadowViewHeavyTopLeft.png", 17.0, 17.0, bundle],
|
||||
[@"CPShadowView/CPShadowViewHeavyTop.png", 1.0, 17.0, bundle],
|
||||
[@"CPShadowView/CPShadowViewHeavyTopRight.png", 17.0, 17.0, bundle],
|
||||
|
||||
[@"CPShadowView/CPShadowViewHeavyLeft.png", 17.0, 1.0, bundle],
|
||||
nil,
|
||||
[@"CPShadowView/CPShadowViewHeavyRight.png", 17.0, 1.0, bundle],
|
||||
|
||||
[@"CPShadowView/CPShadowViewHeavyBottomLeft.png", 17.0, 17.0, bundle],
|
||||
[@"CPShadowView/CPShadowViewHeavyBottom.png", 1.0, 17.0, bundle],
|
||||
[@"CPShadowView/CPShadowViewHeavyBottomRight.png", 17.0, 17.0, bundle]
|
||||
]);
|
||||
}
|
||||
|
||||
+ (id)shadowViewEnclosingView:(CPView)aView
|
||||
{
|
||||
return [self shadowViewEnclosingView:aView withWeight:CPLightShadow];
|
||||
}
|
||||
|
||||
+ (id)shadowViewEnclosingView:(CPView)aView withWeight:(CPShadowWeight)aWeight
|
||||
{
|
||||
var shadowView = [[self alloc] initWithFrame:[aView frame]];
|
||||
|
||||
if (shadowView)
|
||||
{
|
||||
[shadowView setWeight:aWeight];
|
||||
|
||||
var size = [shadowView frame].size,
|
||||
width = size.width - [shadowView leftInset] - [shadowView rightInset],
|
||||
height = size.height - [shadowView topInset] - [shadowView bottomInset],
|
||||
enclosingView = [aView superview];
|
||||
|
||||
[shadowView setHitTests:[aView hitTests]];
|
||||
[shadowView setAutoresizingMask:[aView autoresizingMask]];
|
||||
[aView removeFromSuperview];
|
||||
[shadowView addSubview:aView];
|
||||
[aView setFrame:CGRectMake([shadowView leftInset], [shadowView topInset], width, height)]
|
||||
[enclosingView addSubview:shadowView];
|
||||
}
|
||||
|
||||
return shadowView;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_weight = CPLightShadow;
|
||||
|
||||
[self setBackgroundColor:CPShadowViewLightBackgroundColor];
|
||||
|
||||
[self setHitTests:NO];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setWeight:(CPShadowWeight)aWeight
|
||||
{
|
||||
if (_weight == aWeight)
|
||||
return;
|
||||
|
||||
_weight = aWeight;
|
||||
|
||||
if (_weight == CPLightShadow)
|
||||
[self setBackgroundColor:CPShadowViewLightBackgroundColor];
|
||||
|
||||
else
|
||||
[self setBackgroundColor:CPShadowViewHeavyBackgroundColor];
|
||||
}
|
||||
|
||||
- (float)leftInset
|
||||
{
|
||||
return _weight == CPLightShadow ? LIGHT_LEFT_INSET : HEAVY_LEFT_INSET;
|
||||
}
|
||||
|
||||
- (float)rightInset
|
||||
{
|
||||
return _weight == CPLightShadow ? LIGHT_RIGHT_INSET : HEAVY_RIGHT_INSET;
|
||||
}
|
||||
|
||||
- (float)topInset
|
||||
{
|
||||
return _weight == CPLightShadow ? LIGHT_TOP_INSET : HEAVY_TOP_INSET;
|
||||
}
|
||||
|
||||
- (float)bottomInset
|
||||
{
|
||||
return _weight == CPLightShadow ? LIGHT_BOTTOM_INSET : HEAVY_BOTTOM_INSET;
|
||||
}
|
||||
|
||||
- (float)horizontalInset
|
||||
{
|
||||
if (_weight == CPLightShadow)
|
||||
return LIGHT_LEFT_INSET + LIGHT_RIGHT_INSET;
|
||||
|
||||
return HEAVY_LEFT_INSET + HEAVY_RIGHT_INSET;
|
||||
}
|
||||
|
||||
- (float)verticalInset
|
||||
{
|
||||
if (_weight == CPLightShadow)
|
||||
return LIGHT_TOP_INSET + LIGHT_BOTTOM_INSET;
|
||||
|
||||
return HEAVY_TOP_INSET + HEAVY_BOTTOM_INSET;
|
||||
}
|
||||
|
||||
+ (CGRect)frameForContentFrame:(CGRect)aFrame withWeight:(CPShadowWeight)aWeight
|
||||
{
|
||||
if (aWeight == CPLightShadow)
|
||||
return CGRectMake(_CGRectGetMinX(aFrame) - LIGHT_LEFT_INSET, _CGRectGetMinY(aFrame) - LIGHT_TOP_INSET, _CGRectGetWidth(aFrame) + LIGHT_LEFT_INSET + LIGHT_RIGHT_INSET, _CGRectGetHeight(aFrame) + LIGHT_TOP_INSET + LIGHT_BOTTOM_INSET);
|
||||
else
|
||||
return CGRectMake(_CGRectGetMinX(aFrame) - HEAVY_LEFT_INSET, _CGRectGetMinY(aFrame) - HEAVY_TOP_INSET, _CGRectGetWidth(aFrame) + HEAVY_LEFT_INSET + HEAVY_RIGHT_INSET, _CGRectGetHeight(aFrame) + HEAVY_TOP_INSET + HEAVY_BOTTOM_INSET);
|
||||
}
|
||||
|
||||
- (CGRect)frameForContentFrame:(CGRect)aFrame
|
||||
{
|
||||
return [[self class] frameForContentFrame:aFrame withWeight:_weight];
|
||||
}
|
||||
|
||||
- (void)setFrameForContentFrame:(CGRect)aFrame
|
||||
{
|
||||
[self setFrame:[self frameForContentFrame:aFrame]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,469 +0,0 @@
|
||||
/*
|
||||
* CPSlider.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPControl.j"
|
||||
|
||||
|
||||
/*! SLIDER STATES */
|
||||
|
||||
CPLinearSlider = 0;
|
||||
CPCircularSlider = 1;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
*/
|
||||
|
||||
@implementation CPSlider : CPControl
|
||||
{
|
||||
double _minValue;
|
||||
double _maxValue;
|
||||
double _altIncrementValue;
|
||||
|
||||
BOOL _isVertical;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return "slider";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[[CPNull null], _CGSizeMakeZero(), 0.0, [CPNull null]]
|
||||
forKeys:[@"knob-color", @"knob-size", @"track-width", @"track-color"]];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_minValue = 0.0;
|
||||
_maxValue = 100.0;
|
||||
|
||||
[self setObjectValue:50.0];
|
||||
|
||||
[self setContinuous:YES];
|
||||
|
||||
[self _recalculateIsVertical];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setMinValue:(float)aMinimumValue
|
||||
{
|
||||
if (_minValue === aMinimumValue)
|
||||
return;
|
||||
|
||||
_minValue = aMinimumValue;
|
||||
|
||||
var doubleValue = [self doubleValue];
|
||||
|
||||
if (doubleValue < _minValue)
|
||||
[self setDoubleValue:_minValue];
|
||||
|
||||
// The relative position may have (did) change.
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (float)minValue
|
||||
{
|
||||
return _minValue;
|
||||
}
|
||||
|
||||
- (void)setMaxValue:(float)aMaximumValue
|
||||
{
|
||||
if (_maxValue === aMaximumValue)
|
||||
return;
|
||||
|
||||
_maxValue = aMaximumValue;
|
||||
|
||||
var doubleValue = [self doubleValue];
|
||||
|
||||
if (doubleValue > _maxValue)
|
||||
[self setDoubleValue:_maxValue];
|
||||
|
||||
// The relative position may have (did) change.
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (float)maxValue
|
||||
{
|
||||
return _maxValue;
|
||||
}
|
||||
|
||||
- (void)setObjectValue:(id)aValue
|
||||
{
|
||||
[super setObjectValue:MIN(MAX(aValue, _minValue), _maxValue)];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setSliderType:(CPSliderType)aSliderType
|
||||
{
|
||||
if (aSliderType === CPCircularSlider)
|
||||
[self setThemeState:CPThemeStateCircular];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateCircular];
|
||||
}
|
||||
|
||||
- (CPSliderType)sliderType
|
||||
{
|
||||
return [self hasThemeState:CPThemeStateCircular] ? CPCircularSlider : CPLinearSlider;
|
||||
}
|
||||
|
||||
- (CGRect)trackRectForBounds:(CGRect)bounds
|
||||
{
|
||||
if ([self hasThemeState:CPThemeStateCircular])
|
||||
{
|
||||
var originalBounds = CGRectCreateCopy(bounds);
|
||||
|
||||
bounds.size.width = MIN(bounds.size.width, bounds.size.height);
|
||||
bounds.size.height = bounds.size.width;
|
||||
|
||||
if (bounds.size.width < originalBounds.size.width)
|
||||
bounds.origin.x += (originalBounds.size.width - bounds.size.width) / 2.0;
|
||||
else
|
||||
bounds.origin.y += (originalBounds.size.height - bounds.size.height) / 2.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var trackWidth = [self currentValueForThemeAttribute:@"track-width"];
|
||||
|
||||
if (trackWidth <= 0)
|
||||
return _CGRectMakeZero();
|
||||
|
||||
if ([self isVertical])
|
||||
{
|
||||
bounds.origin.x = (_CGRectGetWidth(bounds) - trackWidth) / 2.0;
|
||||
bounds.size.width = trackWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.origin.y = (_CGRectGetHeight(bounds) - trackWidth) / 2.0;
|
||||
bounds.size.height = trackWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
- (CGRect)knobRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var knobSize = [self currentValueForThemeAttribute:@"knob-size"];
|
||||
|
||||
if (knobSize.width <= 0 || knobSize.height <= 0)
|
||||
return _CGRectMakeZero();
|
||||
|
||||
var knobRect = _CGRectMake(0.0, 0.0, knobSize.width, knobSize.height),
|
||||
trackRect = [self trackRectForBounds:bounds];
|
||||
|
||||
// No track, do our best to approximate a place for this thing.
|
||||
if (!trackRect || _CGRectIsEmpty(trackRect))
|
||||
trackRect = bounds;
|
||||
|
||||
if ([self hasThemeState:CPThemeStateCircular])
|
||||
{
|
||||
var angle = 3 * PI_2 - (1.0 - [self doubleValue] - _minValue) / (_maxValue - _minValue) * PI2,
|
||||
radius = CGRectGetWidth(trackRect) / 2.0 - 8.0;
|
||||
|
||||
knobRect.origin.x = radius * COS(angle) + CGRectGetMidX(trackRect) - 3.0;
|
||||
knobRect.origin.y = radius * SIN(angle) + CGRectGetMidY(trackRect) - 2.0;
|
||||
}
|
||||
else if ([self isVertical])
|
||||
{
|
||||
knobRect.origin.x = _CGRectGetMidX(trackRect) - knobSize.width / 2.0;
|
||||
knobRect.origin.y = ((_maxValue - [self doubleValue]) / (_maxValue - _minValue)) * (_CGRectGetHeight(trackRect) - knobSize.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
knobRect.origin.x = (([self doubleValue] - _minValue) / (_maxValue - _minValue)) * (_CGRectGetWidth(trackRect) - knobSize.width);
|
||||
knobRect.origin.y = _CGRectGetMidY(trackRect) - knobSize.height / 2.0;
|
||||
}
|
||||
|
||||
return knobRect;
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if (aName === "track-view")
|
||||
return [self trackRectForBounds:[self bounds]];
|
||||
|
||||
else if (aName === "knob-view")
|
||||
return [self knobRectForBounds:[self bounds]];
|
||||
|
||||
return [super rectForEphemeralSubviewNamed:aName];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if (aName === "track-view" || aName === "knob-view")
|
||||
{
|
||||
var view = [[CPView alloc] init];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
return [super createEphemeralSubviewNamed:aName];
|
||||
}
|
||||
|
||||
- (void)setAltIncrementValue:(float)anAltIncrementValue
|
||||
{
|
||||
_altIncrementValue = anAltIncrementValue;
|
||||
}
|
||||
|
||||
- (float)altIncrementValue
|
||||
{
|
||||
return _altIncrementValue;
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
[self _recalculateIsVertical];
|
||||
}
|
||||
|
||||
- (void)_recalculateIsVertical
|
||||
{
|
||||
// Recalculate isVertical.
|
||||
var bounds = [self bounds],
|
||||
width = _CGRectGetWidth(bounds),
|
||||
height = _CGRectGetHeight(bounds);
|
||||
|
||||
_isVertical = width < height ? 1 : (width > height ? 0 : -1);
|
||||
|
||||
if (_isVertical === 1)
|
||||
[self setThemeState:CPThemeStateVertical];
|
||||
else if (_isVertical === 0)
|
||||
[self unsetThemeState:CPThemeStateVertical];
|
||||
}
|
||||
|
||||
- (int)isVertical
|
||||
{
|
||||
return _isVertical;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var trackView = [self layoutEphemeralSubviewNamed:@"track-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:@"knob-view"];
|
||||
|
||||
if (trackView)
|
||||
[trackView setBackgroundColor:[self currentValueForThemeAttribute:@"track-color"]];
|
||||
|
||||
var knobView = [self layoutEphemeralSubviewNamed:@"knob-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"track-view"];
|
||||
|
||||
if (knobView)
|
||||
[knobView setBackgroundColor:[self currentValueForThemeAttribute:"knob-color"]];
|
||||
}
|
||||
|
||||
- (BOOL)tracksMouseOutsideOfFrame
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (float)_valueAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
knobRect = [self knobRectForBounds:bounds],
|
||||
trackRect = [self trackRectForBounds:bounds];
|
||||
|
||||
if ([self hasThemeState:CPThemeStateCircular])
|
||||
{
|
||||
var knobWidth = _CGRectGetWidth(knobRect);
|
||||
|
||||
trackRect.origin.x += knobWidth / 2;
|
||||
trackRect.size.width -= knobWidth;
|
||||
|
||||
var minValue = [self minValue],
|
||||
dx = aPoint.x - _CGRectGetMidX(trackRect),
|
||||
dy = aPoint.y - _CGRectGetMidY(trackRect);
|
||||
|
||||
return MAX(0.0, MIN(1.0, 1.0 - (3 * PI_2 - ATAN2(dy, dx)) % PI2 / PI2)) * ([self maxValue] - minValue) + minValue;
|
||||
}
|
||||
else if ([self isVertical])
|
||||
{
|
||||
var knobHeight = _CGRectGetHeight(knobRect);
|
||||
|
||||
trackRect.origin.y += knobHeight / 2;
|
||||
trackRect.size.height -= knobHeight;
|
||||
|
||||
var minValue = [self minValue];
|
||||
|
||||
return MAX(0.0, MIN(1.0, (_CGRectGetMaxY(trackRect) - aPoint.y) / _CGRectGetHeight(trackRect))) * ([self maxValue] - minValue) + minValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var knobWidth = _CGRectGetWidth(knobRect);
|
||||
|
||||
trackRect.origin.x += knobWidth / 2;
|
||||
trackRect.size.width -= knobWidth;
|
||||
|
||||
var minValue = [self minValue];
|
||||
|
||||
return MAX(0.0, MIN(1.0, (aPoint.x - _CGRectGetMinX(trackRect)) / _CGRectGetWidth(trackRect))) * ([self maxValue] - minValue) + minValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
knobRect = [self knobRectForBounds:_CGRectMakeCopy(bounds)];
|
||||
|
||||
if (_CGRectContainsPoint(knobRect, aPoint))
|
||||
_dragOffset = _CGSizeMake(_CGRectGetMidX(knobRect) - aPoint.x, _CGRectGetMidY(knobRect) - aPoint.y);
|
||||
else
|
||||
{
|
||||
var trackRect = [self trackRectForBounds:bounds];
|
||||
|
||||
if (trackRect && _CGRectContainsPoint(trackRect, aPoint))
|
||||
{
|
||||
_dragOffset = _CGSizeMakeZero();
|
||||
|
||||
[self setObjectValue:[self _valueAtPoint:aPoint]];
|
||||
}
|
||||
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self setHighlighted:YES];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
|
||||
{
|
||||
[self setObjectValue:[self _valueAtPoint:_CGPointMake(aPoint.x + _dragOffset.width, aPoint.y + _dragOffset.height)]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
|
||||
{
|
||||
[self setHighlighted:NO];
|
||||
|
||||
if ([_target respondsToSelector:@selector(sliderDidFinish:)])
|
||||
[_target sliderDidFinish:self];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isContinuous
|
||||
{
|
||||
return (_sendActionOn & CPLeftMouseDraggedMask) !== 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
should we have _continuous?
|
||||
*/
|
||||
- (void)setContinuous:(BOOL)flag
|
||||
{
|
||||
if (flag)
|
||||
_sendActionOn |= CPLeftMouseDraggedMask;
|
||||
else
|
||||
_sendActionOn &= ~CPLeftMouseDraggedMask;
|
||||
}
|
||||
|
||||
- (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
|
||||
{
|
||||
var count = objects.length,
|
||||
value = [objects[0] valueForKeyPath:aKeyPath];
|
||||
|
||||
[self setObjectValue:value];
|
||||
|
||||
while (count-- > 1)
|
||||
if (value !== ([objects[count] valueForKeyPath:aKeyPath]))
|
||||
return [self setFloatValue:1.0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPSliderMinValueKey = "CPSliderMinValueKey",
|
||||
CPSliderMaxValueKey = "CPSliderMaxValueKey",
|
||||
CPSliderAltIncrValueKey = "CPSliderAltIncrValueKey";
|
||||
|
||||
@implementation CPSlider (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
_minValue = [aCoder decodeDoubleForKey:CPSliderMinValueKey];
|
||||
_maxValue = [aCoder decodeDoubleForKey:CPSliderMaxValueKey];
|
||||
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_altIncrementValue = [aCoder decodeDoubleForKey:CPSliderAltIncrValueKey];
|
||||
|
||||
[self _recalculateIsVertical];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeDouble:_minValue forKey:CPSliderMinValueKey];
|
||||
[aCoder encodeDouble:_maxValue forKey:CPSliderMaxValueKey];
|
||||
[aCoder encodeDouble:_altIncrementValue forKey:CPSliderAltIncrValueKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPSlider (Deprecated)
|
||||
|
||||
- (id)value
|
||||
{
|
||||
CPLog.warn("[CPSlider value] is deprecated, use doubleValue or objectValue instead.");
|
||||
|
||||
return [self doubleValue];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue
|
||||
{
|
||||
CPLog.warn("[CPSlider setValue:] is deprecated, use setDoubleValue: or setObjectValue: instead.");
|
||||
|
||||
[self setObjectValue:aValue];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,375 +0,0 @@
|
||||
/*
|
||||
* CPApplication.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* 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 "CPView.j"
|
||||
@import "CPColorPicker.j"
|
||||
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
@implementation CPSliderColorPicker : CPColorPicker
|
||||
{
|
||||
CPView _contentView;
|
||||
|
||||
CPSlider _redSlider;
|
||||
CPSlider _greenSlider;
|
||||
CPSlider _blueSlider;
|
||||
CPSlider _hueSlider;
|
||||
CPSlider _saturationSlider;
|
||||
CPSlider _brightnessSlider;
|
||||
|
||||
CPTextField _rgbLabel;
|
||||
CPTextField _hsbLabel;
|
||||
CPTextField _redLabel;
|
||||
CPTextField _greenLabel;
|
||||
CPTextField _blueLabel;
|
||||
CPTextField _hueLabel;
|
||||
CPTextField _saturationLabel;
|
||||
CPTextField _brightnessLabel;
|
||||
CPTextField _hexLabel;
|
||||
CPTextField _hexValue;
|
||||
|
||||
CPTextField _hexValue;
|
||||
CPTextField _redValue;
|
||||
CPTextField _greenValue;
|
||||
CPTextField _blueValue;
|
||||
CPTextField _hueValue;
|
||||
CPTextField _saturationValue;
|
||||
CPTextField _brightnessValue;
|
||||
}
|
||||
|
||||
- (id)initWithPickerMask:(int)mask colorPanel:(CPColorPanel)owningColorPanel
|
||||
{
|
||||
return [super initWithPickerMask:mask colorPanel:owningColorPanel];
|
||||
}
|
||||
|
||||
- (id)initView
|
||||
{
|
||||
aFrame = CGRectMake(0, 0, CPColorPickerViewWidth, CPColorPickerViewHeight);
|
||||
|
||||
_contentView = [[CPView alloc] initWithFrame:aFrame];
|
||||
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
_rgbLabel = [[CPTextField alloc] initWithFrame:CGRectMake(0, 10, 100, 20)];
|
||||
[_rgbLabel setStringValue:"Red, Green, Blue"];
|
||||
[_rgbLabel setTextColor:[CPColor blackColor]];
|
||||
|
||||
_redLabel = [[CPTextField alloc] initWithFrame:CGRectMake(0, 37, 15, 20)];
|
||||
[_redLabel setStringValue:"R"];
|
||||
[_redLabel setTextColor:[CPColor blackColor]];
|
||||
|
||||
_redSlider = [[CPSlider alloc] initWithFrame:CGRectMake(15, 35, aFrame.size.width - 70, 20)];
|
||||
[_redSlider setMaxValue:1.0];
|
||||
[_redSlider setMinValue:0.0];
|
||||
[_redSlider setTarget:self];
|
||||
[_redSlider setAction:@selector(sliderChanged:)];
|
||||
[_redSlider setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
// red value input box
|
||||
_redValue = [[CPTextField alloc] initWithFrame:CGRectMake(aFrame.size.width - 45, 30, 45, 28)];
|
||||
[_redValue setAutoresizingMask:CPViewMinXMargin];
|
||||
[_redValue setEditable:YES];
|
||||
[_redValue setBezeled:YES];
|
||||
[_redValue setDelegate:self];
|
||||
[_contentView addSubview:_redValue];
|
||||
|
||||
_greenLabel = [[CPTextField alloc] initWithFrame:CGRectMake(0, 63, 15, 20)];
|
||||
[_greenLabel setStringValue:"G"];
|
||||
[_greenLabel setTextColor:[CPColor blackColor]];
|
||||
|
||||
_greenSlider = [[CPSlider alloc] initWithFrame:CGRectMake(15, 61, aFrame.size.width - 70, 20)];
|
||||
[_greenSlider setMaxValue:1.0];
|
||||
[_greenSlider setMinValue:0.0];
|
||||
[_greenSlider setTarget:self];
|
||||
[_greenSlider setAction:@selector(sliderChanged:)];
|
||||
[_greenSlider setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
// green value input box
|
||||
_greenValue = [[CPTextField alloc] initWithFrame:CGRectMake(aFrame.size.width - 45, 56, 45, 28)];
|
||||
[_greenValue setAutoresizingMask:CPViewMinXMargin];
|
||||
[_greenValue setEditable:YES];
|
||||
[_greenValue setBezeled:YES];
|
||||
[_greenValue setDelegate:self];
|
||||
[_contentView addSubview:_greenValue];
|
||||
|
||||
_blueLabel = [[CPTextField alloc] initWithFrame:CGRectMake(0, 90, 15, 20)];
|
||||
[_blueLabel setStringValue:"B"];
|
||||
[_blueLabel setTextColor:[CPColor blackColor]];
|
||||
|
||||
_blueSlider = [[CPSlider alloc] initWithFrame:CGRectMake(15, 87, aFrame.size.width - 70, 20)];
|
||||
[_blueSlider setMaxValue:1.0];
|
||||
[_blueSlider setMinValue:0.0];
|
||||
[_blueSlider setTarget:self];
|
||||
[_blueSlider setAction:@selector(sliderChanged:)];
|
||||
[_blueSlider setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
// blue value input box
|
||||
_blueValue = [[CPTextField alloc] initWithFrame:CGRectMake(aFrame.size.width - 45, 82, 45, 28)];
|
||||
[_blueValue setAutoresizingMask:CPViewMinXMargin];
|
||||
[_blueValue setEditable:YES];
|
||||
[_blueValue setBezeled:YES];
|
||||
[_blueValue setDelegate:self];
|
||||
[_contentView addSubview:_blueValue];
|
||||
|
||||
_hsbLabel = [[CPTextField alloc] initWithFrame:CGRectMake(0, 120, 190, 20)];
|
||||
[_hsbLabel setStringValue:"Hue, Saturation, Brightness"];
|
||||
[_hsbLabel setTextColor:[CPColor blackColor]];
|
||||
|
||||
_hueLabel = [[CPTextField alloc] initWithFrame:CGRectMake(0, 145, 15, 20)];
|
||||
[_hueLabel setStringValue:"H"];
|
||||
[_hueLabel setTextColor:[CPColor blackColor]];
|
||||
|
||||
_hueSlider = [[CPSlider alloc] initWithFrame:CGRectMake(15, 143, aFrame.size.width - 70, 20)];
|
||||
[_hueSlider setMaxValue:359.0];
|
||||
[_hueSlider setMinValue:0.0];
|
||||
[_hueSlider setTarget:self];
|
||||
[_hueSlider setAction:@selector(sliderChanged:)];
|
||||
[_hueSlider setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
// hue value input box
|
||||
_hueValue = [[CPTextField alloc] initWithFrame:CGRectMake(aFrame.size.width - 45, 138, 45, 28)];
|
||||
[_hueValue setAutoresizingMask:CPViewMinXMargin];
|
||||
[_hueValue setEditable:YES];
|
||||
[_hueValue setBezeled:YES];
|
||||
[_hueValue setDelegate:self];
|
||||
[_contentView addSubview:_hueValue];
|
||||
|
||||
_saturationLabel = [[CPTextField alloc] initWithFrame:CGRectMake(0, 170, 15, 20)];
|
||||
[_saturationLabel setStringValue:"S"];
|
||||
[_saturationLabel setTextColor:[CPColor blackColor]];
|
||||
|
||||
_saturationSlider = [[CPSlider alloc] initWithFrame:CGRectMake(15, 168, aFrame.size.width - 70, 20)];
|
||||
[_saturationSlider setMaxValue:100.0];
|
||||
[_saturationSlider setMinValue:0.0];
|
||||
[_saturationSlider setTarget:self];
|
||||
[_saturationSlider setAction:@selector(sliderChanged:)];
|
||||
[_saturationSlider setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
// saturation value input box
|
||||
_saturationValue = [[CPTextField alloc] initWithFrame:CGRectMake(aFrame.size.width - 45, 164, 45, 28)];
|
||||
[_saturationValue setAutoresizingMask:CPViewMinXMargin];
|
||||
[_saturationValue setEditable:YES];
|
||||
[_saturationValue setBezeled:YES];
|
||||
[_saturationValue setDelegate:self];
|
||||
[_contentView addSubview:_saturationValue];
|
||||
|
||||
_brightnessLabel = [[CPTextField alloc] initWithFrame:CGRectMake(0, 196, 15, 20)];
|
||||
[_brightnessLabel setStringValue:"B"];
|
||||
[_brightnessLabel setTextColor:[CPColor blackColor]];
|
||||
|
||||
_brightnessSlider = [[CPSlider alloc] initWithFrame:CGRectMake(15, 194, aFrame.size.width - 70, 20)];
|
||||
[_brightnessSlider setMaxValue:100.0];
|
||||
[_brightnessSlider setMinValue:0.0];
|
||||
[_brightnessSlider setTarget:self];
|
||||
[_brightnessSlider setAction:@selector(sliderChanged:)];
|
||||
[_brightnessSlider setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
// brightness value input box
|
||||
_brightnessValue = [[CPTextField alloc] initWithFrame:CGRectMake(aFrame.size.width - 45, 190, 45, 28)];
|
||||
[_brightnessValue setAutoresizingMask:CPViewMinXMargin];
|
||||
[_brightnessValue setEditable:YES];
|
||||
[_brightnessValue setBezeled:YES];
|
||||
[_brightnessValue setDelegate:self];
|
||||
[_contentView addSubview:_brightnessValue];
|
||||
|
||||
_hexLabel = [[CPTextField alloc] initWithFrame:CGRectMake(0, 230, 30, 20)];
|
||||
[_hexLabel setStringValue:"Hex"];
|
||||
[_hexLabel setTextColor:[CPColor blackColor]];
|
||||
|
||||
//hex input box
|
||||
_hexValue = [[CPTextField alloc] initWithFrame:CGRectMake(32, 225, 80, 28)];
|
||||
[_hexValue setEditable:YES];
|
||||
[_hexValue setBezeled:YES];
|
||||
[_hexValue setDelegate:self];
|
||||
[_contentView addSubview:_hexValue];
|
||||
|
||||
[_contentView addSubview:_rgbLabel];
|
||||
[_contentView addSubview:_redLabel];
|
||||
[_contentView addSubview:_greenLabel];
|
||||
[_contentView addSubview:_blueLabel];
|
||||
[_contentView addSubview:_redSlider];
|
||||
[_contentView addSubview:_greenSlider];
|
||||
[_contentView addSubview:_blueSlider];
|
||||
|
||||
[_contentView addSubview:_hsbLabel];
|
||||
[_contentView addSubview:_hueLabel];
|
||||
[_contentView addSubview:_saturationLabel];
|
||||
[_contentView addSubview:_brightnessLabel];
|
||||
[_contentView addSubview:_hueSlider];
|
||||
[_contentView addSubview:_saturationSlider];
|
||||
[_contentView addSubview:_brightnessSlider];
|
||||
|
||||
[_contentView addSubview:_hexLabel];
|
||||
}
|
||||
|
||||
- (CPView)provideNewView:(BOOL)initialRequest
|
||||
{
|
||||
if (initialRequest)
|
||||
[self initView];
|
||||
|
||||
return _contentView;
|
||||
}
|
||||
|
||||
- (int)currentMode
|
||||
{
|
||||
return CPSliderColorPickerMode;
|
||||
}
|
||||
|
||||
- (BOOL)supportsMode:(int)mode
|
||||
{
|
||||
return (mode == CPSliderColorPickerMode) ? YES : NO;
|
||||
}
|
||||
|
||||
- (void)sliderChanged:(id)sender
|
||||
{
|
||||
var newColor,
|
||||
colorPanel = [self colorPanel],
|
||||
alpha = [colorPanel opacity];
|
||||
|
||||
switch (sender)
|
||||
{
|
||||
case _hueSlider:
|
||||
case _saturationSlider:
|
||||
case _brightnessSlider: newColor = [CPColor colorWithHue:[_hueSlider floatValue]
|
||||
saturation:[_saturationSlider floatValue]
|
||||
brightness:[_brightnessSlider floatValue]
|
||||
alpha:alpha];
|
||||
|
||||
[self updateRGBSliders:newColor];
|
||||
break;
|
||||
|
||||
case _redSlider:
|
||||
case _greenSlider:
|
||||
case _blueSlider: newColor = [CPColor colorWithCalibratedRed:[_redSlider floatValue]
|
||||
green:[_greenSlider floatValue]
|
||||
blue:[_blueSlider floatValue]
|
||||
alpha:alpha];
|
||||
|
||||
[self updateHSBSliders:newColor];
|
||||
break;
|
||||
}
|
||||
|
||||
[self updateLabels];
|
||||
[self updateHex:newColor];
|
||||
[colorPanel setColor:newColor];
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
[self updateRGBSliders:aColor];
|
||||
[self updateHSBSliders:aColor];
|
||||
[self updateHex:aColor];
|
||||
[self updateLabels];
|
||||
}
|
||||
|
||||
- (void)updateHSBSliders:(CPColor)aColor
|
||||
{
|
||||
var hsb = [aColor hsbComponents];
|
||||
|
||||
[_hueSlider setFloatValue:hsb[0]];
|
||||
[_saturationSlider setFloatValue:hsb[1]];
|
||||
[_brightnessSlider setFloatValue:hsb[2]];
|
||||
}
|
||||
|
||||
- (void)updateHex:(CPColor)aColor
|
||||
{
|
||||
[_hexValue setStringValue:[aColor hexString]];
|
||||
}
|
||||
|
||||
- (void)updateRGBSliders:(CPColor)aColor
|
||||
{
|
||||
var rgb = [aColor components];
|
||||
|
||||
[_redSlider setFloatValue:rgb[0]];
|
||||
[_greenSlider setFloatValue:rgb[1]];
|
||||
[_blueSlider setFloatValue:rgb[2]];
|
||||
}
|
||||
|
||||
- (void)updateLabels
|
||||
{
|
||||
[_hueValue setStringValue:ROUND([_hueSlider floatValue])];
|
||||
[_saturationValue setStringValue:ROUND([_saturationSlider floatValue])];
|
||||
[_brightnessValue setStringValue:ROUND([_brightnessSlider floatValue])];
|
||||
|
||||
[_redValue setStringValue:ROUND([_redSlider floatValue] * 255)];
|
||||
[_greenValue setStringValue:ROUND([_greenSlider floatValue] * 255)];
|
||||
[_blueValue setStringValue:ROUND([_blueSlider floatValue] * 255)];
|
||||
}
|
||||
|
||||
- (CPImage)provideNewButtonImage
|
||||
{
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:CPColorPicker] pathForResource:"slider_button.png"] size:CGSizeMake(32, 32)];
|
||||
}
|
||||
|
||||
- (CPImage)provideNewAlternateButtonImage
|
||||
{
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:CPColorPicker] pathForResource:"slider_button_h.png"] size:CGSizeMake(32, 32)];
|
||||
}
|
||||
|
||||
- (void)controlTextDidEndEditing:(CPNotification)aNotification
|
||||
{
|
||||
var field = [aNotification object],
|
||||
value = [[field stringValue] stringByTrimmingWhitespace];
|
||||
|
||||
if (field === _hexValue)
|
||||
{
|
||||
var newColor = [CPColor colorWithHexString:value];
|
||||
|
||||
if (newColor)
|
||||
{
|
||||
[self setColor:newColor];
|
||||
[[self colorPanel] setColor:newColor];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (field)
|
||||
{
|
||||
case _redValue: [_redSlider setFloatValue:MAX(MIN(ROUND(value), 255) / 255.0, 0)];
|
||||
[self sliderChanged:_redSlider];
|
||||
break;
|
||||
|
||||
case _greenValue: [_greenSlider setFloatValue:MAX(MIN(ROUND(value), 255) / 255.0, 0)];
|
||||
[self sliderChanged:_greenSlider];
|
||||
break;
|
||||
|
||||
case _blueValue: [_blueSlider setFloatValue:MAX(MIN(ROUND(value), 255) / 255.0, 0)];
|
||||
[self sliderChanged:_blueSlider];
|
||||
break;
|
||||
|
||||
case _hueValue: [_hueSlider setFloatValue:MAX(MIN(ROUND(value), 360), 0)];
|
||||
[self sliderChanged:_hueSlider];
|
||||
break;
|
||||
|
||||
case _saturationValue: [_saturationSlider setFloatValue:MAX(MIN(ROUND(value), 100), 0)];
|
||||
[self sliderChanged:_saturationSlider];
|
||||
break;
|
||||
|
||||
case _brightnessValue: [_brightnessSlider setFloatValue:MAX(MIN(ROUND(value), 100), 0)];
|
||||
[self sliderChanged:_brightnessSlider];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,317 +0,0 @@
|
||||
/*
|
||||
* CPSound.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Antoine Mercadal
|
||||
* Copyright 2010, Antoine Mercadal
|
||||
*
|
||||
* 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>
|
||||
|
||||
CPSoundLoadStateEmpty = 0;
|
||||
CPSoundLoadStateLoading = 1;
|
||||
CPSoundLoadStateCanBePlayed = 2;
|
||||
CPSoundLoadStateError = 3;
|
||||
|
||||
CPSoundPlayBackStatePlay = 0;
|
||||
CPSoundPlayBackStateStop = 1;
|
||||
CPSoundPlayBackStatePause = 2;
|
||||
|
||||
|
||||
/*!
|
||||
CPSound provides a way to load and play sounds. In the browser it relies on the
|
||||
HTML5 audio tag being available.
|
||||
|
||||
CPSound delegate:
|
||||
- sound:didFinishPlaying: called when sound has finished to played
|
||||
|
||||
*/
|
||||
@implementation CPSound : CPObject
|
||||
{
|
||||
CPString _name @accessors(property=name);
|
||||
id _delegate @accessors(property=delegate);
|
||||
|
||||
BOOL _playRequestBeforeLoad;
|
||||
HTMLAudioElement _audioTag;
|
||||
int _loadStatus;
|
||||
int _playBackStatus;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_loadStatus = CPSoundLoadStateEmpty;
|
||||
_loops = NO;
|
||||
_audioTag = document.createElement("audio");
|
||||
_audioTag.preload = YES;
|
||||
_playRequestBeforeLoad = NO;
|
||||
|
||||
_audioTag.addEventListener("canplay", function()
|
||||
{
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
[self _soundDidload];
|
||||
}, true);
|
||||
|
||||
_audioTag.addEventListener("ended", function()
|
||||
{
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
[self _soundDidEnd];
|
||||
}, true);
|
||||
|
||||
_audioTag.addEventListener("error", function()
|
||||
{
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
[self _soundError];
|
||||
}, true);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initialize with the sound contents of the URL represented by aFile.
|
||||
|
||||
@param aFile CPString the path of the sound
|
||||
@param byRef ignored (Cocoa compatibility)
|
||||
*/
|
||||
- (id)initWithContentsOfFile:(CPString)aFile byReference:(BOOL)byRef
|
||||
{
|
||||
if (self = [self init])
|
||||
{
|
||||
_loadStatus = CPSoundLoadStateLoading;
|
||||
_audioTag.src = aFile;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initialize with the sound contents of the file located at aURL.
|
||||
|
||||
@param aURL CPURL containing the URL of the sound
|
||||
@param byRef ignored (Cocoa compatibility)
|
||||
*/
|
||||
- (id)initWithContentsOfURL:(CPURL)aURL byReference:(BOOL)byRef
|
||||
{
|
||||
return [self initWithContentsOfFile:[aURL absoluteString] byReference:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initialize with the sound contents of someData.
|
||||
|
||||
@param someData CPData containing the sound
|
||||
@param byRef ignored (Cocoa compatibility)
|
||||
*/
|
||||
- (id)initWithData:(CPData)someData
|
||||
{
|
||||
if (self = [self init])
|
||||
{
|
||||
_loadStatus = CPSoundLoadStateLoading;
|
||||
_audioTag.src = [someData rawString];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Events listener
|
||||
|
||||
/*! @ignore
|
||||
*/
|
||||
- (void)_soundDidload
|
||||
{
|
||||
_loadStatus = CPSoundLoadStateCanBePlayed;
|
||||
|
||||
if (_playRequestBeforeLoad)
|
||||
{
|
||||
_playRequestBeforeLoad = NO;
|
||||
[self play];
|
||||
}
|
||||
}
|
||||
|
||||
/*! @ignore
|
||||
*/
|
||||
- (void)_soundDidEnd
|
||||
{
|
||||
if (![self loops])
|
||||
[self stop];
|
||||
}
|
||||
|
||||
/*! @ignore
|
||||
*/
|
||||
- (void)_soundError
|
||||
{
|
||||
_loadStatus = CPSoundLoadStateError;
|
||||
CPLog.error("Cannot load sound. Maybe the format of your sound is not compatible with your browser.");
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Media controls
|
||||
|
||||
/*!
|
||||
Play the sound.
|
||||
|
||||
@return YES when the receiver is playing its audio data, NO otherwise.
|
||||
*/
|
||||
- (BOOL)play
|
||||
{
|
||||
if (_loadStatus === CPSoundLoadStateLoading)
|
||||
{
|
||||
_playRequestBeforeLoad = YES;
|
||||
return YES;
|
||||
}
|
||||
|
||||
if ((_loadStatus !== CPSoundLoadStateCanBePlayed)
|
||||
|| (_playBackStatus === CPSoundPlayBackStatePlay))
|
||||
return NO;
|
||||
|
||||
_audioTag.play();
|
||||
_playBackStatus = CPSoundPlayBackStatePlay;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Stop the sound.
|
||||
|
||||
@return YES when the receiver is playing its audio data, NO otherwise.
|
||||
*/
|
||||
- (BOOL)stop
|
||||
{
|
||||
if ((_loadStatus !== CPSoundLoadStateCanBePlayed)
|
||||
|| (_playBackStatus === CPSoundPlayBackStateStop))
|
||||
return NO;
|
||||
|
||||
_audioTag.pause();
|
||||
_audioTag.currentTime = 0.0;
|
||||
_playBackStatus = CPSoundPlayBackStateStop;
|
||||
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(sound:didFinishPlaying:)])
|
||||
[_delegate sound:self didFinishPlaying:YES];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Pause the sound.
|
||||
|
||||
@return YES when the receiver is playing its audio data, NO otherwise.
|
||||
*/
|
||||
- (BOOL)pause
|
||||
{
|
||||
if ((_loadStatus !== CPSoundLoadStateCanBePlayed)
|
||||
|| (_playBackStatus === CPSoundPlayBackStatePause))
|
||||
return NO;
|
||||
|
||||
_audioTag.pause();
|
||||
_playBackStatus = CPSoundPlayBackStatePause;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Resume playback of a paused sound.
|
||||
|
||||
@return YES when the receiver is playing its audio data, NO otherwise.
|
||||
*/
|
||||
- (BOOL)resume
|
||||
{
|
||||
if ((_loadStatus !== CPSoundLoadStateCanBePlayed)
|
||||
|| (_playBackStatus !== CPSoundPlayBackStatePause))
|
||||
return NO;
|
||||
|
||||
_audioTag.play();
|
||||
_playBackStatus = CPSoundPlayBackStatePlay;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return YES if the sound is in loop mode.
|
||||
|
||||
@return BOOL YES if in loop mode, NO otherwise
|
||||
*/
|
||||
- (BOOL)loops
|
||||
{
|
||||
return _audioTag.loop;
|
||||
}
|
||||
|
||||
/*!
|
||||
Specifies whether the sound should repeat.
|
||||
|
||||
@param BOOL YES for loop mode, NO otherwise
|
||||
*/
|
||||
- (void)setLoops:(BOOL)shouldLoop
|
||||
{
|
||||
_audioTag.loop = shouldLoop;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the volume of the receiver.
|
||||
|
||||
@return double from 0.0 to 1.0
|
||||
*/
|
||||
- (double)volume
|
||||
{
|
||||
return _audioTag.volume;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the volume the sound should be played at.
|
||||
|
||||
@param double a volume value between 0.0 and 1.0
|
||||
*/
|
||||
- (void)setVolume:(double)aVolume
|
||||
{
|
||||
if (aVolume > 1.0)
|
||||
aVolume = 1.0;
|
||||
else if (aVolume < 0.0)
|
||||
aVolume = 0.0;
|
||||
|
||||
_audioTag.volume = aVolume;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Accessors
|
||||
|
||||
/*!
|
||||
Returns the duration in seconds of the sound.
|
||||
|
||||
@return double the duration
|
||||
*/
|
||||
- (double)duration
|
||||
{
|
||||
return _audioTag.duration;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns if the sound is playing or not.
|
||||
|
||||
@return BOOL YES if the sound is playing, NO otherwise
|
||||
*/
|
||||
- (BOOL)isPlaying
|
||||
{
|
||||
return (_playBackStatus === CPSoundPlayBackStatePlay);
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
* CPStepper.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Antoine Mercadal
|
||||
* Copyright 2009, Antoine Mercadal
|
||||
*
|
||||
* 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 <AppKit/CPControl.j>
|
||||
@import <AppKit/CPButton.j>
|
||||
@import <AppKit/CPTextField.j>
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
CPStepper is an implementation of Cocoa NSStepper.
|
||||
|
||||
This control displays a two part button that can be used to increase or decrease a value with a given interval.
|
||||
*/
|
||||
@implementation CPStepper: CPControl
|
||||
{
|
||||
BOOL _valueWraps @accessors(property=valueWraps);
|
||||
BOOL _autorepeat @accessors(getter=autorepeat);
|
||||
int _increment @accessors(property=increment);
|
||||
int _maxValue @accessors(property=maxValue);
|
||||
int _minValue @accessors(property=minValue);
|
||||
|
||||
CPButton _buttonDown;
|
||||
CPButton _buttonUp;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
/*!
|
||||
Initializes a CPStepper with given values.
|
||||
@param aValue the initial value of the CPStepper
|
||||
@param minValue the minimal acceptable value of the stepper
|
||||
@param maxValue the maximal acceptable value of the stepper
|
||||
@return Initialized CPStepper
|
||||
*/
|
||||
+ (CPStepper)stepperWithInitialValue:(float)aValue minValue:(float)aMinValue maxValue:(float)aMaxValue
|
||||
{
|
||||
var stepper = [[CPStepper alloc] initWithFrame:_CGRectMake(0, 0, 19, 25)];
|
||||
[stepper setDoubleValue:aValue];
|
||||
[stepper setMinValue:aMinValue];
|
||||
[stepper setMaxValue:aMaxValue];
|
||||
|
||||
return stepper;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a CPStepper with default values:
|
||||
- minValue = 0.0
|
||||
- maxValue = 59.0
|
||||
- value = 0.0
|
||||
|
||||
@return Initialized CPStepper
|
||||
*/
|
||||
+ (CPStepper)stepper
|
||||
{
|
||||
return [CPStepper stepperWithInitialValue:0.0 minValue:0.0 maxValue:59.0];
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (theBinding == CPValueBinding || theBinding == CPMinValueBinding || theBinding == CPMaxValueBinding)
|
||||
return [_CPStepperValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
- (id)_replacementKeyPathForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding == CPValueBinding)
|
||||
return @"doubleValue";
|
||||
|
||||
return [super _replacementKeyPathForBinding:aBinding];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a CPStepper.
|
||||
@param aFrame the frame of the control
|
||||
@return initialized CPStepper
|
||||
*/
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_maxValue = 59.0;
|
||||
_minValue = 0.0;
|
||||
_increment = 1.0;
|
||||
_valueWraps = YES;
|
||||
_autorepeat = YES;
|
||||
|
||||
[self setDoubleValue:0.0];
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
_buttonUp = [[CPButton alloc] initWithFrame:_CGRectMakeZero()];
|
||||
[_buttonUp setContinuous:_autorepeat];
|
||||
[_buttonUp setTarget:self];
|
||||
[_buttonUp setAction:@selector(_buttonDidClick:)];
|
||||
[_buttonUp setAutoresizingMask:CPViewNotSizable];
|
||||
[self addSubview:_buttonUp];
|
||||
|
||||
_buttonDown = [[CPButton alloc] initWithFrame:_CGRectMakeZero()];
|
||||
[_buttonDown setContinuous:_autorepeat];
|
||||
[_buttonDown setTarget:self];
|
||||
[_buttonDown setAction:@selector(_buttonDidClick:)];
|
||||
[_buttonDown setAutoresizingMask:CPViewNotSizable];
|
||||
|
||||
[self setContinuous:_autorepeat];
|
||||
[self addSubview:_buttonDown];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Superclass overrides
|
||||
|
||||
/*!
|
||||
Set if the CPStepper is enabled or not.
|
||||
@param shouldEnabled BOOL that define if stepper is enabled or not.
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)shouldEnabled
|
||||
{
|
||||
[super setEnabled:shouldEnabled];
|
||||
|
||||
[_buttonUp setEnabled:shouldEnabled];
|
||||
[_buttonDown setEnabled:shouldEnabled];
|
||||
}
|
||||
|
||||
|
||||
- (void)setFrame:(CGRect)aFrame
|
||||
{
|
||||
var upSize = [self valueForThemeAttribute:@"up-button-size"],
|
||||
downSize = [self valueForThemeAttribute:@"down-button-size"],
|
||||
minSize = _CGSizeMake(upSize.width, upSize.height + downSize.height),
|
||||
frame = _CGRectMakeCopy(aFrame);
|
||||
|
||||
frame.size.width = MAX(minSize.width, frame.size.width);
|
||||
frame.size.height = MAX(minSize.height, frame.size.height);
|
||||
[super setFrame:frame];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var aFrame = [self frame],
|
||||
upSize = [self valueForThemeAttribute:@"up-button-size"],
|
||||
downSize = [self valueForThemeAttribute:@"down-button-size"],
|
||||
upFrame = _CGRectMake(aFrame.size.width - upSize.width, 0, upSize.width, upSize.height),
|
||||
downFrame = _CGRectMake(aFrame.size.width - downSize.width, upSize.height, downSize.width, downSize.height);
|
||||
[_buttonUp setFrame:upFrame];
|
||||
[_buttonDown setFrame:downFrame];
|
||||
|
||||
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inState:CPThemeStateBordered] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered];
|
||||
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inState:CPThemeStateBordered | CPThemeStateDisabled] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered | CPThemeStateDisabled];
|
||||
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inState:CPThemeStateBordered | CPThemeStateHighlighted] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered | CPThemeStateHighlighted];
|
||||
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inState:CPThemeStateBordered] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered];
|
||||
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inState:CPThemeStateBordered | CPThemeStateDisabled] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered | CPThemeStateDisabled];
|
||||
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inState:CPThemeStateBordered | CPThemeStateHighlighted] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered | CPThemeStateHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Set if CPStepper should autorepeat.
|
||||
@param shouldAutoRepeat if YES, the first mouse down does one increment (decrement) and, after each delay of 0.5 seconds
|
||||
*/
|
||||
- (void)setAutorepeat:(BOOL)shouldAutoRepeat
|
||||
{
|
||||
if (shouldAutoRepeat !== _autorepeat)
|
||||
{
|
||||
[_buttonUp setContinuous:shouldAutoRepeat];
|
||||
[_buttonDown setContinuous:shouldAutoRepeat];
|
||||
}
|
||||
|
||||
[self setContinuous:shouldAutoRepeat];
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the current value of the stepper.
|
||||
@param aValue a float containing the value
|
||||
*/
|
||||
- (void)setDoubleValue:(float)aValue
|
||||
{
|
||||
if (aValue > _maxValue)
|
||||
[super setDoubleValue:_valueWraps ? _minValue : _maxValue];
|
||||
else if (aValue < _minValue)
|
||||
[super setDoubleValue:_valueWraps ? _maxValue : _minValue];
|
||||
else
|
||||
[super setDoubleValue:aValue];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Actions
|
||||
|
||||
/*! @ignore */
|
||||
- (IBAction)_buttonDidClick:(id)aSender
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
if (aSender == _buttonUp)
|
||||
[self setDoubleValue:([self doubleValue] + _increment)];
|
||||
else
|
||||
[self setDoubleValue:([self doubleValue] - _increment)];
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Perform a programatic click on up button.
|
||||
@param aSender sender of the action
|
||||
*/
|
||||
- (IBAction)performClickUp:(id)aSender
|
||||
{
|
||||
[_buttonUp performClick:aSender];
|
||||
}
|
||||
|
||||
/*!
|
||||
Perform a programatic click on down button.
|
||||
@param aSender sender of the action
|
||||
*/
|
||||
- (IBAction)performClickDown:(id)aSender
|
||||
{
|
||||
[_buttonDown performClick:aSender];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Theming
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"stepper";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[[CPNull null], [CPNull null], _CGSizeMakeZero(), _CGSizeMakeZero()]
|
||||
forKeys:[@"bezel-color-up-button", @"bezel-color-down-button", @"up-button-size", @"down-button-size"]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPStepperValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
|
||||
{
|
||||
var placeholder = (aBinding == CPMaxValueBinding) ? [_source maxValue] : [_source minValue];
|
||||
|
||||
[super _updatePlaceholdersWithOptions:options];
|
||||
|
||||
[self _setPlaceholder:placeholder forMarker:CPMultipleValuesMarker isDefault:YES];
|
||||
[self _setPlaceholder:placeholder forMarker:CPNoSelectionMarker isDefault:YES];
|
||||
[self _setPlaceholder:placeholder forMarker:CPNotApplicableMarker isDefault:YES];
|
||||
[self _setPlaceholder:placeholder forMarker:CPNullMarker isDefault:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPStepperMinValue = @"CPStepperMinValue",
|
||||
CPStepperMaxValue = @"CPStepperMaxValue",
|
||||
CPStepperValueWraps = @"CPStepperValueWraps",
|
||||
CPStepperAutorepeat = @"CPStepperAutorepeat",
|
||||
CPStepperIncrement = @"CPStepperIncrement";
|
||||
|
||||
@implementation CPStepper (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_increment = [aCoder decodeIntForKey:CPStepperIncrement];
|
||||
_minValue = [aCoder decodeIntForKey:CPStepperMinValue];
|
||||
_maxValue = [aCoder decodeIntForKey:CPStepperMaxValue];
|
||||
_valueWraps = [aCoder decodeBoolForKey:CPStepperValueWraps];
|
||||
_autorepeat = [aCoder decodeBoolForKey:CPStepperAutorepeat];
|
||||
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeInt:_increment forKey:CPStepperIncrement];
|
||||
|
||||
if (_minValue)
|
||||
[aCoder encodeInt:_minValue forKey:CPStepperMinValue];
|
||||
if (_maxValue)
|
||||
[aCoder encodeInt:_maxValue forKey:CPStepperMaxValue];
|
||||
if (_valueWraps)
|
||||
[aCoder encodeBool:_valueWraps forKey:CPStepperValueWraps];
|
||||
if (_autorepeat)
|
||||
[aCoder encodeBool:_autorepeat forKey:CPStepperAutorepeat];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* CPStringDrawing.j
|
||||
* AppKit
|
||||
*
|
||||
* 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/CPString.j>
|
||||
|
||||
@import "CPPlatformString.j"
|
||||
|
||||
|
||||
var CPStringSizeWithFontInWidthCache = {};
|
||||
|
||||
CPStringSizeCachingEnabled = YES;
|
||||
|
||||
@implementation CPString (CPStringDrawing)
|
||||
|
||||
/*!
|
||||
Returns a dictionary with the items "ascender", "descender", "lineHeight"
|
||||
*/
|
||||
+ (CPDictionary)metricsOfFont:(CPFont)aFont
|
||||
{
|
||||
return [CPPlatformString metricsOfFont:aFont];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the string
|
||||
*/
|
||||
- (CPString)cssString
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)sizeWithFont:(CPFont)aFont
|
||||
{
|
||||
return [self sizeWithFont:aFont inWidth:NULL];
|
||||
}
|
||||
|
||||
- (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth
|
||||
{
|
||||
if (!CPStringSizeCachingEnabled)
|
||||
return [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
|
||||
|
||||
var cacheKey = self + [aFont cssString] + aWidth,
|
||||
size = CPStringSizeWithFontInWidthCache[cacheKey];
|
||||
|
||||
if (size === undefined)
|
||||
{
|
||||
size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
|
||||
CPStringSizeWithFontInWidthCache[cacheKey] = size;
|
||||
}
|
||||
|
||||
return CGSizeMakeCopy(size);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,539 +0,0 @@
|
||||
/*
|
||||
* 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]];
|
||||
|
||||
var selected = [aCoder decodeObjectForKey:CPTabViewSelectedItemKey];
|
||||
if (selected)
|
||||
[self selectTabViewItem:selected];
|
||||
|
||||
[self setTabViewType:[aCoder decodeIntForKey:CPTabViewTypeKey]];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (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
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* CPTabViewItem.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 "CPView.j"
|
||||
|
||||
|
||||
/*
|
||||
The tab is currently selected.
|
||||
@global
|
||||
@group CPTabState
|
||||
*/
|
||||
CPSelectedTab = 0;
|
||||
/*
|
||||
The tab is currently in the background (not selected).
|
||||
@global
|
||||
@group CPTabState
|
||||
*/
|
||||
CPBackgroundTab = 1;
|
||||
/*
|
||||
The tab of this item is currently being pressed by the user.
|
||||
@global
|
||||
@group CPTabState
|
||||
*/
|
||||
CPPressedTab = 2;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPTabViewItem
|
||||
|
||||
The class representation of an item in a CPTabView. One tab view item
|
||||
can be shown at a time in a CPTabView.
|
||||
*/
|
||||
@implementation CPTabViewItem : CPObject
|
||||
{
|
||||
id _identifier;
|
||||
CPString _label;
|
||||
|
||||
CPView _view;
|
||||
CPView _auxiliaryView;
|
||||
|
||||
CPTabView _tabView;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithIdentifier:@""];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the tab view item with the specified identifier.
|
||||
@return the initialized CPTabViewItem
|
||||
*/
|
||||
- (id)initWithIdentifier:(id)anIdentifier
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_identifier = anIdentifier;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Working With Labels
|
||||
/*!
|
||||
Sets the CPTabViewItem's label.
|
||||
@param aLabel the label for the item
|
||||
*/
|
||||
- (void)setLabel:(CPString)aLabel
|
||||
{
|
||||
_label = aLabel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPTabViewItem's label
|
||||
*/
|
||||
- (CPString)label
|
||||
{
|
||||
return _label;
|
||||
}
|
||||
|
||||
// Checking the Tab Display State
|
||||
/*!
|
||||
Returns the tab's current state.
|
||||
*/
|
||||
- (CPTabState)tabState
|
||||
{
|
||||
return _tabState;
|
||||
}
|
||||
|
||||
// Assigning an Identifier Object
|
||||
/*!
|
||||
Sets the item's identifier.
|
||||
@param anIdentifier the new identifier for the item
|
||||
*/
|
||||
- (void)setIdentifier:(id)anIdentifier
|
||||
{
|
||||
_identifier = anIdentifier;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tab's identifier.
|
||||
*/
|
||||
- (id)identifier
|
||||
{
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
// Assigning a View
|
||||
/*!
|
||||
Sets the view that gets displayed in this tab.
|
||||
*/
|
||||
- (void)setView:(CPView)aView
|
||||
{
|
||||
if (_view == aView)
|
||||
return;
|
||||
|
||||
_view = aView;
|
||||
|
||||
if ([_tabView selectedTabViewItem] == self)
|
||||
[_tabView _setContentViewFromItem:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tab's view.
|
||||
*/
|
||||
- (CPView)view
|
||||
{
|
||||
return _view;
|
||||
}
|
||||
|
||||
// Assigning an Auxiliary View
|
||||
/*!
|
||||
Sets the tab's auxiliary view.
|
||||
@param anAuxiliaryView the new auxiliary view
|
||||
*/
|
||||
- (void)setAuxiliaryView:(CPView)anAuxiliaryView
|
||||
{
|
||||
_auxiliaryView = anAuxiliaryView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tab's auxiliary view
|
||||
*/
|
||||
- (CPView)auxiliaryView
|
||||
{
|
||||
return _auxiliaryView;
|
||||
}
|
||||
|
||||
// Accessing the Parent Tab View
|
||||
/*!
|
||||
Returns the tab view that contains this item.
|
||||
*/
|
||||
- (CPTabView)tabView
|
||||
{
|
||||
return _tabView;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_setTabView:(CPTabView)aView
|
||||
{
|
||||
_tabView = aView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPTabViewItemIdentifierKey = "CPTabViewItemIdentifierKey",
|
||||
CPTabViewItemLabelKey = "CPTabViewItemLabelKey",
|
||||
CPTabViewItemViewKey = "CPTabViewItemViewKey",
|
||||
CPTabViewItemAuxViewKey = "CPTabViewItemAuxViewKey";
|
||||
|
||||
|
||||
@implementation CPTabViewItem (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_identifier = [aCoder decodeObjectForKey:CPTabViewItemIdentifierKey];
|
||||
_label = [aCoder decodeObjectForKey:CPTabViewItemLabelKey];
|
||||
|
||||
_view = [aCoder decodeObjectForKey:CPTabViewItemViewKey];
|
||||
_auxiliaryView = [aCoder decodeObjectForKey:CPTabViewItemAuxViewKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_identifier forKey:CPTabViewItemIdentifierKey];
|
||||
[aCoder encodeObject:_label forKey:CPTabViewItemLabelKey];
|
||||
|
||||
[aCoder encodeObject:_view forKey:CPTabViewItemViewKey];
|
||||
[aCoder encodeObject:_auxiliaryView forKey:CPTabViewItemAuxViewKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,846 +0,0 @@
|
||||
/*
|
||||
* CPTableColumn.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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/CPDictionary.j>
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPIndexSet.j>
|
||||
@import <Foundation/CPSortDescriptor.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CPTableHeaderView.j"
|
||||
|
||||
|
||||
CPTableColumnNoResizing = 0;
|
||||
CPTableColumnAutoresizingMask = 1 << 0;
|
||||
CPTableColumnUserResizingMask = 1 << 1;
|
||||
|
||||
/*!
|
||||
@class CPTableColumn
|
||||
|
||||
A CPTableColumn contains a dataview to display for its column of the CPTableView.
|
||||
A CPTableColumn determines its own size constrains and resizing behavior.
|
||||
|
||||
The default dataview is a CPTextField but you can set it to any view you'd like. See -setDataView: for documentation including theme states.
|
||||
|
||||
To customize the text of the column header you can simply call setStringValue: on the headerview of a table column.
|
||||
For example: [[myTableColumn headerView] setStringValue:"My Title"];
|
||||
*/
|
||||
@implementation CPTableColumn : CPObject
|
||||
{
|
||||
CPTableView _tableView;
|
||||
CPView _headerView;
|
||||
CPView _dataView;
|
||||
Object _dataViewData;
|
||||
|
||||
float _width;
|
||||
float _minWidth;
|
||||
float _maxWidth;
|
||||
unsigned _resizingMask;
|
||||
|
||||
id _identifier;
|
||||
BOOL _isEditable;
|
||||
CPSortDescriptor _sortDescriptorPrototype;
|
||||
BOOL _isHidden;
|
||||
CPString _headerToolTip;
|
||||
|
||||
BOOL _disableResizingPosting @accessors(property=disableResizingPosting);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithIdentifier:@""];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a newly created CPTableColumn with a given identifier.
|
||||
|
||||
*/
|
||||
- (id)initWithIdentifier:(id)anIdentifier
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_dataViewData = { };
|
||||
|
||||
_width = 100.0;
|
||||
_minWidth = 10.0;
|
||||
_maxWidth = 1000000.0;
|
||||
_resizingMask = CPTableColumnAutoresizingMask | CPTableColumnUserResizingMask;
|
||||
_disableResizingPosting = NO;
|
||||
|
||||
[self setIdentifier:anIdentifier];
|
||||
|
||||
var header = [[_CPTableColumnHeaderView alloc] initWithFrame:CGRectMakeZero()];
|
||||
[self setHeaderView:header];
|
||||
|
||||
[self setDataView:[CPTextField new]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the columns's parent tableview
|
||||
*/
|
||||
- (void)setTableView:(CPTableView)aTableView
|
||||
{
|
||||
_tableView = aTableView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the column's parent tableview
|
||||
*/
|
||||
- (CPTableView)tableView
|
||||
{
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
this method tries to resize a column called via the tableview via autoresizing
|
||||
it returns the delta from the actual resize and the proposed resize
|
||||
|
||||
for example if the column should have been resized 50px but the maxWidth was hit only
|
||||
after 25px then the return value would be 25px;
|
||||
|
||||
if no edge has been hit zero will be returned
|
||||
*/
|
||||
- (int)_tryToResizeToWidth:(int)width
|
||||
{
|
||||
var min = [self minWidth],
|
||||
max = [self maxWidth],
|
||||
newWidth = ROUND(MIN(MAX(width, min), max));
|
||||
|
||||
[self setWidth:newWidth];
|
||||
|
||||
return newWidth - width;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the width of the column
|
||||
Default value is: 100
|
||||
|
||||
If the value is greater than the maxWidth the maxWidth will be reset with the supplied width here
|
||||
If the value is less than the minWidth the minWidth will be reset with the supplied width.
|
||||
*/
|
||||
- (void)setWidth:(float)aWidth
|
||||
{
|
||||
aWidth = +aWidth;
|
||||
|
||||
if (_width === aWidth)
|
||||
return;
|
||||
|
||||
var newWidth = MIN(MAX(aWidth, [self minWidth]), [self maxWidth]);
|
||||
|
||||
if (_width === newWidth)
|
||||
return;
|
||||
|
||||
var oldWidth = _width;
|
||||
|
||||
_width = newWidth;
|
||||
|
||||
var tableView = [self tableView];
|
||||
|
||||
if (tableView)
|
||||
{
|
||||
var index = [[tableView tableColumns] indexOfObjectIdenticalTo:self],
|
||||
dirtyTableColumnRangeIndex = tableView._dirtyTableColumnRangeIndex;
|
||||
|
||||
if (dirtyTableColumnRangeIndex < 0)
|
||||
tableView._dirtyTableColumnRangeIndex = index;
|
||||
else
|
||||
tableView._dirtyTableColumnRangeIndex = MIN(index, tableView._dirtyTableColumnRangeIndex);
|
||||
|
||||
var rows = tableView._exposedRows,
|
||||
columns = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(index, [tableView._exposedColumns lastIndex] - index + 1)];
|
||||
|
||||
// FIXME: Would be faster with some sort of -setNeedsDisplayInColumns: that updates a dirtyTableColumnForDisplay cache; then marked columns would relayout their data views at display time.
|
||||
[tableView _layoutDataViewsInRows:rows columns:columns];
|
||||
[tableView tile];
|
||||
|
||||
if (!_disableResizingPosting)
|
||||
[self _postDidResizeNotificationWithOldWidth:oldWidth];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the column's width
|
||||
*/
|
||||
- (float)width
|
||||
{
|
||||
return _width;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the minimum width of the column.
|
||||
Default value is 10.
|
||||
*/
|
||||
- (void)setMinWidth:(float)aMinWidth
|
||||
{
|
||||
aMinWidth = +aMinWidth;
|
||||
|
||||
if (_minWidth === aMinWidth)
|
||||
return;
|
||||
|
||||
_minWidth = aMinWidth;
|
||||
|
||||
var width = [self width],
|
||||
newWidth = MAX(width, [self minWidth]);
|
||||
|
||||
if (width !== newWidth)
|
||||
[self setWidth:newWidth];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the minimum width of the column.
|
||||
*/
|
||||
- (float)minWidth
|
||||
{
|
||||
return _minWidth;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the maximum width of the table column.
|
||||
Default value is: 1000000
|
||||
*/
|
||||
- (void)setMaxWidth:(float)aMaxWidth
|
||||
{
|
||||
aMaxWidth = +aMaxWidth;
|
||||
|
||||
if (_maxWidth === aMaxWidth)
|
||||
return;
|
||||
|
||||
_maxWidth = aMaxWidth;
|
||||
|
||||
var width = [self width],
|
||||
newWidth = MIN(width, [self maxWidth]);
|
||||
|
||||
if (width !== newWidth)
|
||||
[self setWidth:newWidth];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the maximum width of the column
|
||||
*/
|
||||
- (float)maxWidth
|
||||
{
|
||||
return _maxWidth;
|
||||
}
|
||||
|
||||
/*!
|
||||
<pre>
|
||||
Set the resizing mask of the column.
|
||||
By default the column can be resized automatically with the tableview and manually by the user
|
||||
|
||||
Possible masking values are:
|
||||
CPTableColumnNoResizing
|
||||
CPTableColumnAutoresizingMask
|
||||
CPTableColumnUserResizingMask
|
||||
</pre>
|
||||
*/
|
||||
- (void)setResizingMask:(unsigned)aResizingMask
|
||||
{
|
||||
_resizingMask = aResizingMask;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns the resizing mask of the column
|
||||
*/
|
||||
- (unsigned)resizingMask
|
||||
{
|
||||
return _resizingMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sizes the column to fix the column header text.
|
||||
*/
|
||||
- (void)sizeToFit
|
||||
{
|
||||
var width = _CGRectGetWidth([_headerView frame]);
|
||||
|
||||
if (width < [self minWidth])
|
||||
[self setMinWidth:width];
|
||||
else if (width > [self maxWidth])
|
||||
[self setMaxWidth:width]
|
||||
|
||||
if (_width !== width)
|
||||
[self setWidth:width];
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Sets the header view for the column.
|
||||
The headerview handles the display of sort indicators, text, etc.
|
||||
|
||||
If you do not want a headerview for you table you should call setHeaderView: on your CPTableView instance.
|
||||
Passing nil here will throw an exception.
|
||||
|
||||
In order to customize the text of the column header see - (CPView)headerView;
|
||||
*/
|
||||
- (void)setHeaderView:(CPView)aView
|
||||
{
|
||||
if (!aView)
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Attempt to set nil header view on " + [self description]];
|
||||
|
||||
_headerView = aView;
|
||||
|
||||
var tableHeaderView = [_tableView headerView];
|
||||
|
||||
[tableHeaderView setNeedsLayout];
|
||||
[tableHeaderView setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the headerview for the column.
|
||||
|
||||
In order to change the text of the headerview for a column you should call setStringValue: on the headerview.
|
||||
For example: [[myTableColumn headerView] setStringValue:"My Column"];
|
||||
*/
|
||||
- (CPView)headerView
|
||||
{
|
||||
return _headerView;
|
||||
}
|
||||
|
||||
/*!
|
||||
This method sets the "prototype" view which will be used to create all table cells in this column.
|
||||
|
||||
It creates a snapshot of aView, using keyed archiving, which is then copied over and over for each
|
||||
individual cell that is shown. As a result, changes made after calling this method won't be reflected.
|
||||
|
||||
Example:
|
||||
@code
|
||||
[tableColumn setDataView:someView]; // snapshot taken
|
||||
[[tableColumn dataView] setSomething:x]; //won't work
|
||||
@endcode
|
||||
|
||||
This doesn't work because the snapshot is taken before the new property is applied. Instead, do:
|
||||
@code
|
||||
[someView setSomething:x];
|
||||
[tableColumn setDataView:someView];
|
||||
@endcode
|
||||
|
||||
@note you should implement CPKeyedArchiving otherwise you might see unexpected results.
|
||||
This is done by adding the following methods to your class:
|
||||
@endnote
|
||||
|
||||
@code
|
||||
- (id)initWithCoder(CPCoder)aCoder;
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder;
|
||||
@endcode
|
||||
|
||||
Example:
|
||||
Say you have two instance variables in your object that need to be set up each time an object is create.
|
||||
We will call these instance variables "image" and "text".
|
||||
Your CPCoding methods will look like the following:
|
||||
|
||||
@code
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
image = [aCoder decodeObjectForKey:"MyDataViewImage"];
|
||||
text = [aCoder decodeObjectForKey:"MyDataViewText"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:image forKey:"MyDataViewImage"];
|
||||
[aCoder encodeObject:text forKey:"MyDataViewText"];
|
||||
}
|
||||
@endcode
|
||||
|
||||
@section Themeing
|
||||
|
||||
When you set a dataview and it is added to the tableview the theme state will be set to \c CPThemeStateTableDataView
|
||||
When the dataview becomes selected the theme state will be set to \c CPThemeStateSelectedDataView.
|
||||
|
||||
If the dataview shows up in a group row of the tableview the theme state will be set to \c CPThemeStateGroupRow.
|
||||
|
||||
You should overide \c setThemeState: and \c unsetThemeState: to handle these theme state changes in your dataview.
|
||||
*/
|
||||
- (void)setDataView:(CPView)aView
|
||||
{
|
||||
if (_dataView)
|
||||
_dataViewData[[_dataView UID]] = nil;
|
||||
|
||||
[aView setThemeState:CPThemeStateTableDataView];
|
||||
|
||||
_dataView = aView;
|
||||
_dataViewData[[aView UID]] = [CPKeyedArchiver archivedDataWithRootObject:aView];
|
||||
}
|
||||
|
||||
- (CPView)dataView
|
||||
{
|
||||
return _dataView;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the CPView object used by the CPTableView to draw values for the receiver.
|
||||
|
||||
By default, this method just calls dataView. Subclassers can override if they need to
|
||||
potentially use different "cells" or dataViews for different rows. Subclasses should expect this method
|
||||
to be invoked with row equal to -1 in cases where no actual row is involved but the table
|
||||
view needs to get some generic cell info.
|
||||
*/
|
||||
- (id)dataViewForRow:(int)aRowIndex
|
||||
{
|
||||
return [self dataView];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (id)_newDataViewForRow:(int)aRowIndex
|
||||
{
|
||||
var dataView = [self dataViewForRow:aRowIndex],
|
||||
dataViewUID = [dataView UID];
|
||||
|
||||
var x = [self tableView]._cachedDataViews[dataViewUID];
|
||||
if (x && x.length)
|
||||
return x.pop();
|
||||
|
||||
// if we haven't cached an archive of the data view, do it now
|
||||
if (!_dataViewData[dataViewUID])
|
||||
_dataViewData[dataViewUID] = [CPKeyedArchiver archivedDataWithRootObject:dataView];
|
||||
|
||||
// unarchive the data view cache
|
||||
var newDataView = [CPKeyedUnarchiver unarchiveObjectWithData:_dataViewData[dataViewUID]];
|
||||
newDataView.identifier = dataViewUID;
|
||||
|
||||
// make sure only we have control over the size and placement
|
||||
[newDataView setAutoresizingMask:CPViewNotSizable];
|
||||
|
||||
return newDataView;
|
||||
}
|
||||
|
||||
//Setting the Identifier
|
||||
|
||||
/*!
|
||||
Sets the receiver identifier to anIdentifier.
|
||||
*/
|
||||
- (void)setIdentifier:(id)anIdentifier
|
||||
{
|
||||
_identifier = anIdentifier;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the object used by the data source to identify the attribute corresponding to the receiver.
|
||||
*/
|
||||
- (id)identifier
|
||||
{
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
//Controlling Editability
|
||||
|
||||
/*!
|
||||
Controls whether the user can edit cells in the receiver by double-clicking them.
|
||||
*/
|
||||
- (void)setEditable:(BOOL)shouldBeEditable
|
||||
{
|
||||
_isEditable = shouldBeEditable;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns YES if the user can edit cells associated with the receiver by double-clicking the
|
||||
column in the CPTableView, NO otherwise.
|
||||
*/
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return _isEditable;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the sort descriptor prototype for the column.
|
||||
*/
|
||||
- (void)setSortDescriptorPrototype:(CPSortDescriptor)aSortDescriptor
|
||||
{
|
||||
_sortDescriptorPrototype = aSortDescriptor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the sort descriptor prototype for the column.
|
||||
*/
|
||||
- (CPSortDescriptor)sortDescriptorPrototype
|
||||
{
|
||||
if (_sortDescriptorPrototype)
|
||||
return _sortDescriptorPrototype;
|
||||
|
||||
var binderClass = [[self class] _binderClassForBinding:CPValueBinding],
|
||||
binding = [binderClass getBinding:CPValueBinding forObject:self];
|
||||
|
||||
return [binding _defaultSortDescriptorPrototype];
|
||||
}
|
||||
|
||||
/*!
|
||||
If YES the tablecolumn will no longer be visible in the tableview.
|
||||
If NO the tablecolumn will be visible in the tableview.
|
||||
*/
|
||||
- (void)setHidden:(BOOL)shouldBeHidden
|
||||
{
|
||||
shouldBeHidden = !!shouldBeHidden
|
||||
|
||||
if (_isHidden === shouldBeHidden)
|
||||
return;
|
||||
|
||||
_isHidden = shouldBeHidden;
|
||||
|
||||
[[self headerView] setHidden:shouldBeHidden];
|
||||
[[self tableView] _tableColumnVisibilityDidChange:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the visibility status of the column.
|
||||
*/
|
||||
- (BOOL)isHidden
|
||||
{
|
||||
return _isHidden;
|
||||
}
|
||||
|
||||
//Setting Tool Tips
|
||||
|
||||
/*!
|
||||
Sets the tooltip string that is displayed when the cursor pauses over the
|
||||
header cell of the receiver.
|
||||
*/
|
||||
- (void)setHeaderToolTip:(CPString)aToolTip
|
||||
{
|
||||
_headerToolTip = aToolTip;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tooltip for the column header
|
||||
*/
|
||||
- (CPString)headerToolTip
|
||||
{
|
||||
return _headerToolTip;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_postDidResizeNotificationWithOldWidth:(float)oldWidth
|
||||
{
|
||||
[[self tableView] _didResizeTableColumn:self];
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPTableViewColumnDidResizeNotification
|
||||
object:[self tableView]
|
||||
userInfo:[CPDictionary dictionaryWithObjects:[self, oldWidth] forKeys:[@"CPTableColumn", "CPOldWidth"]]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTableColumnValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)aBinding
|
||||
{
|
||||
var tableView = [_source tableView],
|
||||
column = [[tableView tableColumns] indexOfObjectIdenticalTo:_source],
|
||||
rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [tableView numberOfRows])],
|
||||
columnIndexes = [CPIndexSet indexSetWithIndex:column];
|
||||
|
||||
[tableView reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes];
|
||||
}
|
||||
|
||||
- (CPSortDescriptor)_defaultSortDescriptorPrototype
|
||||
{
|
||||
if (![self createsSortDescriptor])
|
||||
return nil;
|
||||
|
||||
var keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
dotIndex = keyPath.indexOf(".");
|
||||
|
||||
if (dotIndex === CPNotFound)
|
||||
return nil;
|
||||
|
||||
var firstPart = keyPath.substring(0, dotIndex),
|
||||
key = keyPath.substring(dotIndex + 1);
|
||||
|
||||
return [CPSortDescriptor sortDescriptorWithKey:key ascending:YES];
|
||||
}
|
||||
|
||||
- (BOOL)createsSortDescriptor
|
||||
{
|
||||
var options = [_info objectForKey:CPOptionsKey],
|
||||
optionValue = [options objectForKey:CPCreatesSortDescriptorBindingOption];
|
||||
return optionValue === nil ? YES : [optionValue boolValue];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTableColumn (Bindings)
|
||||
|
||||
+ (id)_binderClassForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding == CPValueBinding)
|
||||
return [CPTableColumnValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
}
|
||||
|
||||
/*!
|
||||
Binds the receiver to an object.
|
||||
|
||||
@param CPString aBinding - The binding you wish to make. Typically CPValueBinding.
|
||||
@param id anObject - The object to bind the receiver to.
|
||||
@param CPString aKeyPath - The key path you wish to bind the receiver to.
|
||||
@param CPDictionary options - A dictionary of options for the binding. This parameter is optional, pass nil if you do not wish to use it.
|
||||
*/
|
||||
- (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
|
||||
{
|
||||
[super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
|
||||
|
||||
if (![aBinding isEqual:@"someListOfExceptedBindings(notAcceptedBindings)"])
|
||||
{
|
||||
// Bind the table to the array controller this column is bound to.
|
||||
// Note that anObject might not be the array controller. E.g. the keypath could be something like
|
||||
// somePathTo.anArrayController.arrangedObjects.aKey. Cocoa doesn't support this but it is consistent
|
||||
// and it makes sense.
|
||||
var acIndex = aKeyPath.lastIndexOf("arrangedObjects."),
|
||||
arrayController = anObject;
|
||||
|
||||
if (acIndex > 1)
|
||||
{
|
||||
var firstPart = aKeyPath.substring(0, acIndex - 1);
|
||||
arrayController = [anObject valueForKeyPath:firstPart];
|
||||
}
|
||||
|
||||
[[self tableView] _establishBindingsIfUnbound:arrayController];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_prepareDataView:(CPView)aDataView forRow:(unsigned)aRow
|
||||
{
|
||||
var bindingsDictionary = [CPBinder allBindingsForObject:self],
|
||||
keys = [bindingsDictionary allKeys];
|
||||
|
||||
for (var i = 0, count = [keys count]; i < count; i++)
|
||||
{
|
||||
var bindingName = keys[i],
|
||||
bindingPath = [aDataView _replacementKeyPathForBinding:bindingName],
|
||||
binding = [bindingsDictionary objectForKey:bindingName],
|
||||
bindingInfo = binding._info,
|
||||
destination = [bindingInfo objectForKey:CPObservedObjectKey],
|
||||
keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
|
||||
dotIndex = keyPath.lastIndexOf("."),
|
||||
value;
|
||||
|
||||
if (dotIndex === CPNotFound)
|
||||
value = [[destination valueForKeyPath:keyPath] objectAtIndex:aRow];
|
||||
else
|
||||
{
|
||||
/*
|
||||
Optimize the prototypical use case where the key path describes a value
|
||||
in an array. Without this optimization, we call CPArray's valueForKey
|
||||
which generates as many values as objects in the array, of which we then
|
||||
pick one and throw away the rest.
|
||||
|
||||
The optimization is to get the array and access the value directly. This
|
||||
turns the operation into a single access regardless of how long the model
|
||||
array is.
|
||||
*/
|
||||
|
||||
var firstPart = keyPath.substring(0, dotIndex),
|
||||
secondPart = keyPath.substring(dotIndex + 1),
|
||||
firstValue = [destination valueForKeyPath:firstPart];
|
||||
|
||||
if ([firstValue isKindOfClass:CPArray])
|
||||
value = [[firstValue objectAtIndex:aRow] valueForKeyPath:secondPart];
|
||||
else
|
||||
value = [[firstValue valueForKeyPath:secondPart] objectAtIndex:aRow];
|
||||
}
|
||||
|
||||
value = [binding transformValue:value withOptions:[bindingInfo objectForKey:CPOptionsKey]];
|
||||
[aDataView setValue:value forKey:@"objectValue"];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_reverseSetDataView:(CPView)aDataView forRow:(unsigned)aRow
|
||||
{
|
||||
var bindingsDictionary = [CPBinder allBindingsForObject:self],
|
||||
keys = [bindingsDictionary allKeys],
|
||||
newValue = [aDataView valueForKey:@"objectValue"];
|
||||
|
||||
for (var i = 0, count = [keys count]; i < count; i++)
|
||||
{
|
||||
var bindingName = keys[i],
|
||||
bindingPath = [aDataView _replacementKeyPathForBinding:bindingName],
|
||||
binding = [bindingsDictionary objectForKey:bindingName],
|
||||
bindingInfo = binding._info,
|
||||
destination = [bindingInfo objectForKey:CPObservedObjectKey],
|
||||
keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
|
||||
options = [bindingInfo objectForKey:CPOptionsKey],
|
||||
dotIndex = keyPath.lastIndexOf(".");
|
||||
|
||||
newValue = [binding reverseTransformValue:newValue withOptions:options];
|
||||
|
||||
if (dotIndex === CPNotFound)
|
||||
[[destination valueForKeyPath:keyPath] replaceObjectAtIndex:aRow withObject:newValue];
|
||||
else
|
||||
{
|
||||
var firstPart = keyPath.substring(0, dotIndex),
|
||||
secondPart = keyPath.substring(dotIndex + 1),
|
||||
firstValue = [destination valueForKeyPath:firstPart];
|
||||
|
||||
if ([firstValue isKindOfClass:CPArray])
|
||||
[[firstValue objectAtIndex:aRow] setValue:newValue forKeyPath:secondPart];
|
||||
else
|
||||
[[firstValue valueForKeyPath:secondPart] replaceObjectAtIndex:aRow withObject:newValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPTableColumnIdentifierKey = @"CPTableColumnIdentifierKey",
|
||||
CPTableColumnHeaderViewKey = @"CPTableColumnHeaderViewKey",
|
||||
CPTableColumnDataViewKey = @"CPTableColumnDataViewKey",
|
||||
CPTableColumnWidthKey = @"CPTableColumnWidthKey",
|
||||
CPTableColumnMinWidthKey = @"CPTableColumnMinWidthKey",
|
||||
CPTableColumnMaxWidthKey = @"CPTableColumnMaxWidthKey",
|
||||
CPTableColumnResizingMaskKey = @"CPTableColumnResizingMaskKey",
|
||||
CPTableColumnIsHiddenKey = @"CPTableColumnIsHiddenKey",
|
||||
CPSortDescriptorPrototypeKey = @"CPSortDescriptorPrototypeKey",
|
||||
CPTableColumnIsEditableKey = @"CPTableColumnIsEditableKey";
|
||||
|
||||
@implementation CPTableColumn (CPCoding)
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_dataViewData = { };
|
||||
|
||||
_width = [aCoder decodeFloatForKey:CPTableColumnWidthKey];
|
||||
_minWidth = [aCoder decodeFloatForKey:CPTableColumnMinWidthKey];
|
||||
_maxWidth = [aCoder decodeFloatForKey:CPTableColumnMaxWidthKey];
|
||||
|
||||
[self setIdentifier:[aCoder decodeObjectForKey:CPTableColumnIdentifierKey]];
|
||||
[self setHeaderView:[aCoder decodeObjectForKey:CPTableColumnHeaderViewKey]];
|
||||
[self setDataView:[aCoder decodeObjectForKey:CPTableColumnDataViewKey]];
|
||||
|
||||
_resizingMask = [aCoder decodeIntForKey:CPTableColumnResizingMaskKey];
|
||||
_isHidden = [aCoder decodeBoolForKey:CPTableColumnIsHiddenKey];
|
||||
_isEditable = [aCoder decodeBoolForKey:CPTableColumnIsEditableKey];
|
||||
|
||||
_sortDescriptorPrototype = [aCoder decodeObjectForKey:CPSortDescriptorPrototypeKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_identifier forKey:CPTableColumnIdentifierKey];
|
||||
|
||||
[aCoder encodeFloat:_width forKey:CPTableColumnWidthKey];
|
||||
[aCoder encodeFloat:_minWidth forKey:CPTableColumnMinWidthKey];
|
||||
[aCoder encodeFloat:_maxWidth forKey:CPTableColumnMaxWidthKey];
|
||||
|
||||
[aCoder encodeObject:_headerView forKey:CPTableColumnHeaderViewKey];
|
||||
[aCoder encodeObject:_dataView forKey:CPTableColumnDataViewKey];
|
||||
|
||||
[aCoder encodeObject:_resizingMask forKey:CPTableColumnResizingMaskKey];
|
||||
[aCoder encodeBool:_isHidden forKey:CPTableColumnIsHiddenKey];
|
||||
[aCoder encodeBool:_isEditable forKey:CPTableColumnIsEditableKey];
|
||||
|
||||
[aCoder encodeObject:_sortDescriptorPrototype forKey:CPSortDescriptorPrototypeKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTableColumn (NSInCompatibility)
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)setHeaderCell:(CPView)aView
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:@"setHeaderCell: is not supported. Use -setHeaderView:aView instead."];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (CPView)headerCell
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:@"headCell is not supported. Use -headerView instead."];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)setDataCell:(CPView)aView
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:@"setDataCell: is not supported. Use -setDataView:aView instead."];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (CPView)dataCell
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:@"dataCell is not supported. Use -dataView instead."];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (id)dataCellForRow:(int)row
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:@"dataCellForRow: is not supported. Use -dataViewForRow:row instead."];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,750 +0,0 @@
|
||||
/*
|
||||
* CPTableHeaderView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2009 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/CPIndexSet.j>
|
||||
|
||||
@import "CPTableColumn.j"
|
||||
@import "CPTableView.j"
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
@implementation _CPTableColumnHeaderView : CPView
|
||||
{
|
||||
_CPImageAndTextView _textField;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"columnHeader";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[[CPNull null], CPLeftTextAlignment, CPLineBreakByTruncatingTail, CGInsetMakeZero(), [CPNull null], [CPNull null], [CPNull null], CGSizeMakeZero()]
|
||||
forKeys:[@"background-color", @"text-alignment", @"line-break-mode", @"text-inset", @"text-color", @"font", @"text-shadow-color", @"text-shadow-offset"]];
|
||||
}
|
||||
|
||||
- (void)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self)
|
||||
[self _init];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
_textField = [[_CPImageAndTextView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[_textField setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
[_textField setLineBreakMode:CPLineBreakByTruncatingTail];
|
||||
[_textField setAlignment:CPLeftTextAlignment];
|
||||
[_textField setVerticalAlignment:CPCenterVerticalTextAlignment];
|
||||
|
||||
[self addSubview:_textField];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
|
||||
|
||||
var inset = [self currentValueForThemeAttribute:@"text-inset"],
|
||||
bounds = [self bounds];
|
||||
|
||||
[_textField setFrame:_CGRectMake(inset.right, inset.top, bounds.size.width - inset.right - inset.left, bounds.size.height - inset.top - inset.bottom)];
|
||||
[_textField setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
|
||||
[_textField setFont:[self currentValueForThemeAttribute:@"font"]];
|
||||
[_textField setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
|
||||
[_textField setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
|
||||
[_textField setAlignment:[self currentValueForThemeAttribute:@"text-alignment"]];
|
||||
[_textField setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
|
||||
}
|
||||
|
||||
- (void)setStringValue:(CPString)string
|
||||
{
|
||||
[_textField setText:string];
|
||||
}
|
||||
|
||||
- (CPString)stringValue
|
||||
{
|
||||
return [_textField text];
|
||||
}
|
||||
|
||||
- (void)textField
|
||||
{
|
||||
return _textField;
|
||||
}
|
||||
|
||||
- (void)sizeToFit
|
||||
{
|
||||
[_textField sizeToFit];
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
[self setValue:aFont forThemeAttribute:@"font"];
|
||||
}
|
||||
|
||||
- (CPFont)font
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"font"]
|
||||
}
|
||||
|
||||
- (void)setAlignment:(CPTextAlignment)alignment
|
||||
{
|
||||
[self setValue:alignment forThemeAttribute:@"text-alignment"];
|
||||
}
|
||||
|
||||
- (CPTextAlignment)alignment
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"text-alignment"]
|
||||
}
|
||||
|
||||
- (void)setLineBreakMode:(CPLineBreakMode)mode
|
||||
{
|
||||
[self setValue:mode forThemeAttribute:@"line-break-mode"];
|
||||
}
|
||||
|
||||
- (CPLineBreakMode)lineBreakMode
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"line-break-mode"]
|
||||
}
|
||||
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
{
|
||||
[self setValue:aColor forThemeAttribute:@"text-color"];
|
||||
}
|
||||
|
||||
- (CPColor)textColor
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"text-color"]
|
||||
}
|
||||
|
||||
- (void)setTextShadowColor:(CPColor)aColor
|
||||
{
|
||||
[self setValue:aColor forThemeAttribute:@"text-shadow-color"];
|
||||
}
|
||||
|
||||
- (CPColor)textShadowColor
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"text-shadow-color"]
|
||||
}
|
||||
|
||||
- (void)_setIndicatorImage:(CPImage)anImage
|
||||
{
|
||||
if (anImage)
|
||||
{
|
||||
[_textField setImage:anImage];
|
||||
[_textField setImagePosition:CPImageRight];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_textField setImagePosition:CPNoImage];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPTableColumnHeaderViewStringValueKey = @"_CPTableColumnHeaderViewStringValueKey",
|
||||
_CPTableColumnHeaderViewFontKey = @"_CPTableColumnHeaderViewFontKey",
|
||||
_CPTableColumnHeaderViewTextColorKey = @"_CPTableColumnHeaderViewTextColorKey",
|
||||
_CPTableColumnHeaderViewTextShadowColorKey = @"_CPTableColumnHeaderViewTextShadowColorKey",
|
||||
_CPTableColumnHeaderViewAlignmentKey = @"_CPTableColumnHeaderViewAlignmentKey",
|
||||
_CPTableColumnHeaderViewLineBreakModeKey = @"_CPTableColumnHeaderViewLineBreakModeKey",
|
||||
_CPTableColumnHeaderViewImageKey = @"_CPTableColumnHeaderViewImageKey";
|
||||
|
||||
@implementation _CPTableColumnHeaderView (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
[self _init];
|
||||
[self _setIndicatorImage:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewImageKey]];
|
||||
[self setStringValue:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewStringValueKey]];
|
||||
[self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
|
||||
[self setTextColor:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewTextColorKey]];
|
||||
[self setTextShadowColor:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewTextShadowColorKey]];
|
||||
[self setAlignment:[aCoder decodeIntForKey:_CPTableColumnHeaderViewAlignmentKey]];
|
||||
[self setLineBreakMode:[aCoder decodeIntForKey:_CPTableColumnHeaderViewLineBreakModeKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:[_textField text] forKey:_CPTableColumnHeaderViewStringValueKey];
|
||||
[aCoder encodeObject:[_textField image] forKey:_CPTableColumnHeaderViewImageKey];
|
||||
[aCoder encodeObject:[self font] forKey:_CPTableColumnHeaderViewFontKey];
|
||||
[aCoder encodeObject:[self textColor] forKey:_CPTableColumnHeaderViewTextColorKey];
|
||||
[aCoder encodeObject:[self textShadowColor] forKey:_CPTableColumnHeaderViewTextShadowColorKey];
|
||||
[aCoder encodeInt:[self alignment] forKey:_CPTableColumnHeaderViewAlignmentKey];
|
||||
[aCoder encodeInt:[self lineBreakMode] forKey:_CPTableColumnHeaderViewLineBreakModeKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTableHeaderView : CPView
|
||||
{
|
||||
CGPoint _mouseDownLocation;
|
||||
CGPoint _previousTrackingLocation;
|
||||
int _activeColumn;
|
||||
int _pressedColumn;
|
||||
|
||||
BOOL _isResizing;
|
||||
BOOL _isDragging;
|
||||
BOOL _isTrackingColumn;
|
||||
BOOL _drawsColumnLines;
|
||||
|
||||
float _columnOldWidth;
|
||||
|
||||
CPTableView _tableView @accessors(property=tableView);
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"tableHeaderRow";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[[CPNull null], [CPColor grayColor]]
|
||||
forKeys:[@"background-color", @"divider-color"]];
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
_mouseDownLocation = _CGPointMakeZero();
|
||||
_previousTrackingLocation = _CGPointMakeZero();
|
||||
_activeColumn = -1;
|
||||
_pressedColumn = -1;
|
||||
|
||||
_isResizing = NO;
|
||||
_isDragging = NO;
|
||||
_isTrackingColumn = NO;
|
||||
_drawsColumnLines = YES;
|
||||
|
||||
_columnOldWidth = 0.0;
|
||||
|
||||
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
[self _init];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (int)columnAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
return [_tableView columnAtPoint:CGPointMake(aPoint.x, aPoint.y)];
|
||||
}
|
||||
|
||||
- (CGRect)headerRectOfColumn:(int)aColumnIndex
|
||||
{
|
||||
var headerRect = CGRectMakeCopy([self bounds]),
|
||||
columnRect = [_tableView rectOfColumn:aColumnIndex];
|
||||
|
||||
headerRect.origin.x = _CGRectGetMinX(columnRect);
|
||||
headerRect.size.width = _CGRectGetWidth(columnRect);
|
||||
|
||||
return headerRect;
|
||||
}
|
||||
|
||||
- (void)setDrawsColumnLines:(BOOL)aFlag
|
||||
{
|
||||
_drawsColumnLines = aFlag;
|
||||
}
|
||||
|
||||
- (BOOL)drawsColumnLines
|
||||
{
|
||||
return _drawsColumnLines;
|
||||
}
|
||||
|
||||
- (CGRect)_cursorRectForColumn:(int)column
|
||||
{
|
||||
if (column == -1 || !([_tableView._tableColumns[column] resizingMask] & CPTableColumnUserResizingMask))
|
||||
return _CGRectMakeZero();
|
||||
|
||||
var rect = [self headerRectOfColumn:column];
|
||||
|
||||
rect.origin.x = _CGRectGetMaxX(rect) - 5;
|
||||
rect.size.width = 20;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (void)_setPressedColumn:(CPInteger)column
|
||||
{
|
||||
if (_pressedColumn != -1)
|
||||
{
|
||||
var headerView = [_tableView._tableColumns[_pressedColumn] headerView];
|
||||
[headerView unsetThemeState:CPThemeStateHighlighted];
|
||||
}
|
||||
|
||||
if (column != -1)
|
||||
{
|
||||
var headerView = [_tableView._tableColumns[column] headerView];
|
||||
[headerView setThemeState:CPThemeStateHighlighted];
|
||||
}
|
||||
|
||||
_pressedColumn = column;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)theEvent
|
||||
{
|
||||
[self trackMouse:theEvent];
|
||||
}
|
||||
|
||||
- (void)trackMouse:(CPEvent)theEvent
|
||||
{
|
||||
var type = [theEvent type],
|
||||
currentLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil];
|
||||
|
||||
// Take the right columns resize tracking area into account
|
||||
currentLocation.x -= 5.0;
|
||||
|
||||
var columnIndex = [self columnAtPoint:currentLocation],
|
||||
shouldResize = [self shouldResizeTableColumn:columnIndex at:_CGPointMake(currentLocation.x + 5.0, currentLocation.y)];
|
||||
|
||||
if (type === CPLeftMouseUp)
|
||||
{
|
||||
if (shouldResize)
|
||||
[self stopResizingTableColumn:_activeColumn at:currentLocation];
|
||||
else if ([self _shouldStopTrackingTableColumn:columnIndex at:currentLocation])
|
||||
{
|
||||
[_tableView _didClickTableColumn:columnIndex modifierFlags:[theEvent modifierFlags]];
|
||||
[self stopTrackingTableColumn:columnIndex at:currentLocation];
|
||||
|
||||
_isTrackingColumn = NO;
|
||||
}
|
||||
|
||||
[self _updateResizeCursor:[CPApp currentEvent]];
|
||||
|
||||
_activeColumn = CPNotFound;
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === CPLeftMouseDown)
|
||||
{
|
||||
if (columnIndex === -1)
|
||||
return;
|
||||
|
||||
_mouseDownLocation = currentLocation;
|
||||
_activeColumn = columnIndex;
|
||||
|
||||
[_tableView _sendDelegateDidMouseDownInHeader:columnIndex];
|
||||
|
||||
if (shouldResize)
|
||||
[self startResizingTableColumn:columnIndex at:currentLocation];
|
||||
else
|
||||
{
|
||||
[self startTrackingTableColumn:columnIndex at:currentLocation];
|
||||
_isTrackingColumn = YES;
|
||||
}
|
||||
}
|
||||
else if (type === CPLeftMouseDragged)
|
||||
{
|
||||
if (shouldResize)
|
||||
[self continueResizingTableColumn:_activeColumn at:currentLocation];
|
||||
else
|
||||
{
|
||||
if (_activeColumn === columnIndex && _CGRectContainsPoint([self headerRectOfColumn:columnIndex], currentLocation))
|
||||
{
|
||||
if (_isTrackingColumn && _pressedColumn !== -1)
|
||||
{
|
||||
if (![self continueTrackingTableColumn:columnIndex at:currentLocation])
|
||||
return; // Stop tracking the column, because it's being dragged
|
||||
} else
|
||||
[self startTrackingTableColumn:columnIndex at:currentLocation];
|
||||
|
||||
} else if (_isTrackingColumn && _pressedColumn !== -1)
|
||||
[self stopTrackingTableColumn:_activeColumn at:currentLocation];
|
||||
}
|
||||
}
|
||||
|
||||
_previousTrackingLocation = currentLocation;
|
||||
[CPApp setTarget:self selector:@selector(trackMouse:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
}
|
||||
|
||||
- (void)startTrackingTableColumn:(int)aColumnIndex at:(CGPoint)aPoint
|
||||
{
|
||||
[self _setPressedColumn:aColumnIndex];
|
||||
}
|
||||
|
||||
- (BOOL)continueTrackingTableColumn:(int)aColumnIndex at:(CGPoint)aPoint
|
||||
{
|
||||
if ([self _shouldDragTableColumn:aColumnIndex at:aPoint])
|
||||
{
|
||||
var columnRect = [self headerRectOfColumn:aColumnIndex],
|
||||
offset = _CGPointMakeZero(),
|
||||
view = [_tableView _dragViewForColumn:aColumnIndex event:[CPApp currentEvent] offset:offset],
|
||||
viewLocation = _CGPointMakeZero();
|
||||
|
||||
viewLocation.x = ( _CGRectGetMinX(columnRect) + offset.x ) + ( aPoint.x - _mouseDownLocation.x );
|
||||
viewLocation.y = _CGRectGetMinY(columnRect) + offset.y;
|
||||
|
||||
[self dragView:view at:viewLocation offset:_CGSizeMakeZero() event:[CPApp currentEvent]
|
||||
pasteboard:[CPPasteboard pasteboardWithName:CPDragPboard] source:self slideBack:YES];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)_shouldStopTrackingTableColumn:(int)aColumnIndex at:(CGPoint)aPoint
|
||||
{
|
||||
return _isTrackingColumn && _activeColumn === aColumnIndex &&
|
||||
_CGRectContainsPoint([self headerRectOfColumn:aColumnIndex], aPoint);
|
||||
}
|
||||
|
||||
- (void)stopTrackingTableColumn:(int)aColumnIndex at:(CGPoint)aPoint
|
||||
{
|
||||
[self _setPressedColumn:CPNotFound];
|
||||
[self _updateResizeCursor:[CPApp currentEvent]];
|
||||
}
|
||||
|
||||
- (BOOL)_shouldDragTableColumn:(int)aColumnIndex at:(CGPoint)aPoint
|
||||
{
|
||||
return [_tableView allowsColumnReordering] && ABS(aPoint.x - _mouseDownLocation.x) >= 10.0;
|
||||
}
|
||||
|
||||
- (CGRect)_headerRectOfLastVisibleColumn
|
||||
{
|
||||
var tableColumns = [_tableView tableColumns],
|
||||
columnIndex = [tableColumns count];
|
||||
|
||||
while (columnIndex--)
|
||||
{
|
||||
var tableColumn = [tableColumns objectAtIndex:columnIndex];
|
||||
|
||||
if (![tableColumn isHidden])
|
||||
return [self headerRectOfColumn:columnIndex];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)_constrainDragView:(CPView)theDragView at:(CGPoint)aPoint
|
||||
{
|
||||
var tableColumns = [_tableView tableColumns],
|
||||
lastColumnRect = [self _headerRectOfLastVisibleColumn],
|
||||
activeColumnRect = [self headerRectOfColumn:_activeColumn],
|
||||
dragWindow = [theDragView window],
|
||||
frame = [dragWindow frame];
|
||||
|
||||
// Convert the frame origin from the global coordinate system to the windows' coordinate system
|
||||
frame.origin = [[self window] convertGlobalToBase:frame.origin];
|
||||
// the from the window to the view
|
||||
frame.origin = [self convertPoint:frame.origin fromView:nil];
|
||||
|
||||
// This effectively clamps the value between the minimum and maximum
|
||||
frame.origin.x = MAX(0.0, MIN(_CGRectGetMinX(frame), _CGRectGetMaxX(lastColumnRect) - _CGRectGetWidth(activeColumnRect)));
|
||||
|
||||
// Make sure the column cannot move vertically
|
||||
frame.origin.y = _CGRectGetMinY(lastColumnRect);
|
||||
|
||||
// Convert the calculated origin back to the window coordinate system
|
||||
frame.origin = [self convertPoint:frame.origin toView:nil];
|
||||
// Then back to the global coordinate system
|
||||
frame.origin = [[self window] convertBaseToGlobal:frame.origin];
|
||||
|
||||
[dragWindow setFrame:frame];
|
||||
}
|
||||
|
||||
- (void)_moveColumn:(int)aFromIndex toColumn:(int)aToIndex
|
||||
{
|
||||
[_tableView moveColumn:aFromIndex toColumn:aToIndex];
|
||||
_activeColumn = aToIndex;
|
||||
_pressedColumn = _activeColumn;
|
||||
}
|
||||
|
||||
- (void)draggedView:(CPView)aView beganAt:(CGPoint)aPoint
|
||||
{
|
||||
_isDragging = YES;
|
||||
|
||||
var column = [[_tableView tableColumns] objectAtIndex:_activeColumn];
|
||||
|
||||
[[column headerView] setHidden:YES];
|
||||
[_tableView _setDraggedColumn:column];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)draggedView:(CPView)aView movedTo:(CGPoint)aPoint
|
||||
{
|
||||
[self _constrainDragView:aView at:aPoint];
|
||||
|
||||
var dragWindow = [aView window],
|
||||
dragWindowFrame = [dragWindow frame];
|
||||
|
||||
var hoverPoint = CGPointCreateCopy(aPoint);
|
||||
|
||||
if (aPoint.x < _previousTrackingLocation.x)
|
||||
hoverPoint = _CGPointMake(_CGRectGetMinX(dragWindowFrame), _CGRectGetMinY(dragWindowFrame));
|
||||
else if (aPoint.x > _previousTrackingLocation.x)
|
||||
hoverPoint = _CGPointMake(_CGRectGetMaxX(dragWindowFrame), _CGRectGetMinY(dragWindowFrame));
|
||||
|
||||
// Convert the hover point from the global coordinate system to windows' coordinate system
|
||||
hoverPoint = [[self window] convertGlobalToBase:hoverPoint];
|
||||
// then to the view
|
||||
hoverPoint = [self convertPoint:hoverPoint fromView:nil];
|
||||
|
||||
var hoveredColumn = [self columnAtPoint:hoverPoint];
|
||||
|
||||
if (hoveredColumn !== -1)
|
||||
{
|
||||
var columnRect = [self headerRectOfColumn:hoveredColumn],
|
||||
columnCenterPoint = [self convertPoint:CGPointMake(_CGRectGetMidX(columnRect), _CGRectGetMidY(columnRect)) fromView:self];
|
||||
if (hoveredColumn < _activeColumn && hoverPoint.x < columnCenterPoint.x)
|
||||
[self _moveColumn:_activeColumn toColumn:hoveredColumn];
|
||||
else if (hoveredColumn > _activeColumn && hoverPoint.x > columnCenterPoint.x)
|
||||
[self _moveColumn:_activeColumn toColumn:hoveredColumn];
|
||||
}
|
||||
|
||||
_previousTrackingLocation = aPoint;
|
||||
}
|
||||
|
||||
- (void)draggedView:(CPImage)aView endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
|
||||
{
|
||||
_isDragging = NO;
|
||||
_isTrackingColumn = NO; // We need to do this explicitly because the mouse up section of trackMouse is never reached
|
||||
|
||||
[_tableView _setDraggedColumn:nil];
|
||||
[[[[_tableView tableColumns] objectAtIndex:_activeColumn] headerView] setHidden:NO];
|
||||
[self stopTrackingTableColumn:_activeColumn at:aLocation];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (BOOL)shouldResizeTableColumn:(int)aColumnIndex at:(CGPoint)aPoint
|
||||
{
|
||||
if (_isResizing)
|
||||
return YES;
|
||||
|
||||
if (_isTrackingColumn)
|
||||
return NO;
|
||||
|
||||
return [_tableView allowsColumnResizing] && _CGRectContainsPoint([self _cursorRectForColumn:aColumnIndex], aPoint);
|
||||
}
|
||||
|
||||
- (void)startResizingTableColumn:(int)aColumnIndex at:(CGPoint)aPoint
|
||||
{
|
||||
_isResizing = YES;
|
||||
|
||||
var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex];
|
||||
|
||||
[tableColumn setDisableResizingPosting:YES];
|
||||
[_tableView setDisableAutomaticResizing:YES];
|
||||
}
|
||||
|
||||
- (void)continueResizingTableColumn:(int)aColumnIndex at:(CGPoint)aPoint
|
||||
{
|
||||
var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
|
||||
newWidth = [tableColumn width] + aPoint.x - _previousTrackingLocation.x;
|
||||
|
||||
if (newWidth < [tableColumn minWidth])
|
||||
[[CPCursor resizeRightCursor] set];
|
||||
else if (newWidth > [tableColumn maxWidth])
|
||||
[[CPCursor resizeLeftCursor] set];
|
||||
else
|
||||
{
|
||||
_tableView._lastColumnShouldSnap = NO;
|
||||
[tableColumn setWidth:newWidth];
|
||||
|
||||
[[CPCursor resizeLeftRightCursor] set];
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopResizingTableColumn:(int)aColumnIndex at:(CGPoint)aPoint
|
||||
{
|
||||
var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex];
|
||||
[tableColumn _postDidResizeNotificationWithOldWidth:_columnOldWidth];
|
||||
[tableColumn setDisableResizingPosting:NO];
|
||||
[_tableView setDisableAutomaticResizing:NO];
|
||||
|
||||
_isResizing = NO;
|
||||
}
|
||||
|
||||
- (void)_updateResizeCursor:(CPEvent)theEvent
|
||||
{
|
||||
// never get stuck in resize cursor mode (FIXME take out when we turn on tracking rects)
|
||||
if (![_tableView allowsColumnResizing] || ([theEvent type] === CPLeftMouseUp && ![[self window] acceptsMouseMovedEvents]))
|
||||
{
|
||||
[[CPCursor arrowCursor] set];
|
||||
return;
|
||||
}
|
||||
|
||||
var mouseLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil],
|
||||
mouseOverLocation = CGPointMake(mouseLocation.x - 5, mouseLocation.y),
|
||||
overColumn = [self columnAtPoint:mouseOverLocation];
|
||||
|
||||
if (overColumn >= 0 && _CGRectContainsPoint([self _cursorRectForColumn:overColumn], mouseLocation))
|
||||
{
|
||||
var tableColumn = [[_tableView tableColumns] objectAtIndex:overColumn],
|
||||
width = [tableColumn width];
|
||||
|
||||
if (width == [tableColumn minWidth])
|
||||
[[CPCursor resizeRightCursor] set];
|
||||
else if (width == [tableColumn maxWidth])
|
||||
[[CPCursor resizeLeftCursor] set];
|
||||
else
|
||||
[[CPCursor resizeLeftRightCursor] set];
|
||||
}
|
||||
else
|
||||
[[CPCursor arrowCursor] set];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(CPEvent)theEvent
|
||||
{
|
||||
[self _updateResizeCursor:theEvent];
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(CPEvent)theEvent
|
||||
{
|
||||
[self _updateResizeCursor:theEvent];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(CPEvent)theEvent
|
||||
{
|
||||
// FIXME: we should use CPCursor push/pop (if previous currentCursor != arrow).
|
||||
[[CPCursor arrowCursor] set];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var tableColumns = [_tableView tableColumns],
|
||||
count = [tableColumns count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var column = [tableColumns objectAtIndex:i],
|
||||
headerView = [column headerView],
|
||||
frame = [self headerRectOfColumn:i];
|
||||
|
||||
// Make space for the gridline on the right.
|
||||
frame.origin.x -= 0.5;
|
||||
frame.size.width -= 1.0;
|
||||
frame.size.height -= 0.5;
|
||||
// Note: we're not adding in intercell spacing here. This setting only affects the regular
|
||||
// table cell data views, not the header. Verified in Cocoa on March 29th, 2011.
|
||||
|
||||
[headerView setFrame:frame];
|
||||
|
||||
if ([headerView superview] != self)
|
||||
[self addSubview:headerView];
|
||||
}
|
||||
|
||||
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
if (!_tableView || ![self drawsColumnLines])
|
||||
return;
|
||||
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
exposedColumnIndexes = [_tableView columnIndexesInRect:aRect],
|
||||
columnsArray = [],
|
||||
tableColumns = [_tableView tableColumns],
|
||||
exposedTableColumns = _tableView._exposedColumns,
|
||||
firstIndex = [exposedTableColumns firstIndex],
|
||||
exposedRange = CPMakeRange(firstIndex, [exposedTableColumns lastIndex] - firstIndex + 1);
|
||||
|
||||
CGContextSetLineWidth(context, 1);
|
||||
CGContextSetStrokeColor(context, [self currentValueForThemeAttribute:@"divider-color"]);
|
||||
|
||||
[exposedColumnIndexes getIndexes:columnsArray maxCount:-1 inIndexRange:exposedRange];
|
||||
|
||||
var columnArrayIndex = 0,
|
||||
columnArrayCount = columnsArray.length,
|
||||
columnMaxX;
|
||||
|
||||
CGContextBeginPath(context);
|
||||
for (; columnArrayIndex < columnArrayCount; columnArrayIndex++)
|
||||
{
|
||||
// grab each column rect and add vertical lines
|
||||
var columnIndex = columnsArray[columnArrayIndex],
|
||||
columnToStroke = [self headerRectOfColumn:columnIndex];
|
||||
|
||||
columnMaxX = _CGRectGetMaxX(columnToStroke);
|
||||
|
||||
CGContextMoveToPoint(context, FLOOR(columnMaxX) - 0.5, ROUND(_CGRectGetMinY(columnToStroke)));
|
||||
CGContextAddLineToPoint(context, FLOOR(columnMaxX) - 0.5, ROUND(_CGRectGetMaxY(columnToStroke)) - 1.0);
|
||||
}
|
||||
CGContextClosePath(context);
|
||||
CGContextStrokePath(context);
|
||||
|
||||
/*if (_isDragging)
|
||||
{
|
||||
CGContextSetFillColor(context, [CPColor grayColor]);
|
||||
CGContextFillRect(context, [self headerRectOfColumn:_activeColumn])
|
||||
}*/
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPTableHeaderViewTableViewKey = @"CPTableHeaderViewTableViewKey",
|
||||
CPTableHeaderViewDrawsColumnLines = @"CPTableHeaderViewDrawsColumnLines";
|
||||
|
||||
@implementation CPTableHeaderView (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
[self _init];
|
||||
_tableView = [aCoder decodeObjectForKey:CPTableHeaderViewTableViewKey];
|
||||
|
||||
// FIX ME: Take this out before 1.0
|
||||
if ([aCoder containsValueForKey:CPTableHeaderViewDrawsColumnLines])
|
||||
_drawsColumnLines = [aCoder decodeBoolForKey:CPTableHeaderViewDrawsColumnLines];
|
||||
else
|
||||
{
|
||||
_drawsColumnLines = YES;
|
||||
CPLog.warn("The tableview header being decoded is using an old cib. Please run Nib2Cib.");
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
[aCoder encodeObject:_tableView forKey:CPTableHeaderViewTableViewKey];
|
||||
[aCoder encodeBool:_drawsColumnLines forKey:CPTableHeaderViewDrawsColumnLines];
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* CPText.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Alexander Ljungberg.
|
||||
* Copyright 2010, WireLoad, LLC.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
CPEnterCharacter = "\u0003";
|
||||
CPBackspaceCharacter = "\u0008";
|
||||
CPTabCharacter = "\u0009";
|
||||
CPNewlineCharacter = "\u000a";
|
||||
CPFormFeedCharacter = "\u000c";
|
||||
CPCarriageReturnCharacter = "\u000d";
|
||||
CPBackTabCharacter = "\u0019";
|
||||
CPDeleteCharacter = "\u007f";
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,762 +0,0 @@
|
||||
/*
|
||||
* CPTheme.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 <Foundation/CPString.j>
|
||||
@import <Foundation/CPKeyedUnarchiver.j>
|
||||
|
||||
|
||||
var CPThemesByName = { },
|
||||
CPThemeDefaultTheme = nil,
|
||||
CPThemeDefaultHudTheme = nil;
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
*/
|
||||
|
||||
@implementation CPTheme : CPObject
|
||||
{
|
||||
CPString _name;
|
||||
CPDictionary _attributes;
|
||||
}
|
||||
|
||||
+ (void)setDefaultTheme:(CPTheme)aTheme
|
||||
{
|
||||
CPThemeDefaultTheme = aTheme;
|
||||
}
|
||||
|
||||
+ (CPTheme)defaultTheme
|
||||
{
|
||||
return CPThemeDefaultTheme;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the default HUD theme. If set to nil, the default described in defaultHudTheme
|
||||
will be used.
|
||||
*/
|
||||
+ (void)setDefaultHudTheme:(CPTheme)aTheme
|
||||
{
|
||||
CPThemeDefaultHudTheme = aTheme;
|
||||
}
|
||||
|
||||
/*!
|
||||
The default HUD theme is (sometimes) used for windows with the CPHUDBackgroundWindowMask
|
||||
style mask. The default is theme with the name of the default theme with -HUD appended
|
||||
at the end.
|
||||
*/
|
||||
+ (CPTheme)defaultHudTheme
|
||||
{
|
||||
if (!CPThemeDefaultHudTheme)
|
||||
CPThemeDefaultHudTheme = [CPTheme themeNamed:[[self defaultTheme] name] + "-HUD"];
|
||||
return CPThemeDefaultHudTheme;
|
||||
}
|
||||
|
||||
+ (CPTheme)themeNamed:(CPString)aName
|
||||
{
|
||||
return CPThemesByName[aName];
|
||||
}
|
||||
|
||||
- (id)initWithName:(CPString)aName
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_name = aName;
|
||||
_attributes = [CPDictionary dictionary];
|
||||
|
||||
CPThemesByName[_name] = self;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)name
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of names of themed classes defined in this theme, as found in its
|
||||
ThemeDescriptors.j file.
|
||||
|
||||
NOTE: The names are not class names (such as "CPButton"), but the names returned
|
||||
by the class' +defaultThemeClass method. For example, the name for CPCheckBox is "check-box",
|
||||
as defined in CPCheckBox::themeClass.
|
||||
*/
|
||||
- (CPArray)classNames
|
||||
{
|
||||
return [_attributes allKeys];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a dictionary of all theme attributes defined for the given class, as found in the
|
||||
theme's ThemeDescriptors.j file. The keys of the dictionary are attribute names, and the values
|
||||
are instances of _CPThemeAttribute.
|
||||
|
||||
For a description of valid values for \c aClass, see \ref attributeNamesForClass:.
|
||||
|
||||
@param aClass The themed class whose attributes you want to retrieve
|
||||
@return A dictionary of attributes or nil
|
||||
*/
|
||||
- (CPDictionary)attributesForClass:(id)aClass
|
||||
{
|
||||
if (!aClass)
|
||||
return nil;
|
||||
|
||||
var className = nil;
|
||||
|
||||
if ([aClass isKindOfClass:[CPString class]])
|
||||
{
|
||||
// See if it is a class name
|
||||
var theClass = CPClassFromString(aClass);
|
||||
|
||||
if (theClass)
|
||||
aClass = theClass;
|
||||
else
|
||||
className = aClass;
|
||||
}
|
||||
|
||||
if (!className)
|
||||
{
|
||||
if ([aClass isKindOfClass:[CPView class]])
|
||||
{
|
||||
if ([aClass respondsToSelector:@selector(defaultThemeClass)])
|
||||
className = [aClass defaultThemeClass];
|
||||
else if ([aClass respondsToSelector:@selector(themeClass)])
|
||||
{
|
||||
CPLog.warn(@"%@ themeClass is deprecated in favor of defaultThemeClass",CPStringFromClass([anObject class]));
|
||||
className = [aClass themeClass];
|
||||
}
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
else
|
||||
[CPException raise:CPInvalidArgumentException reason:@"aClass must be a class object or a string."];
|
||||
}
|
||||
|
||||
return [_attributes objectForKey:className];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of names of all theme attributes defined for the given class, as found in the
|
||||
theme's ThemeDescriptors.j file.
|
||||
|
||||
The \c aClass parameter can be one of the following:
|
||||
|
||||
- A class instance, for example the result of [CPCheckBox class]. The class must be a subclass
|
||||
of CPView.
|
||||
- A class name, for example "CPCheckBox".
|
||||
- A themed class name, for example "check-box".
|
||||
|
||||
If \c aClass does not refer to a themed class in this theme, nil is returned.
|
||||
|
||||
@param aClass The themed class whose attributes you want to retrieve
|
||||
@return An array of attribute names or nil
|
||||
*/
|
||||
- (CPDictionary)attributeNamesForClass:(id)aClass
|
||||
{
|
||||
var attributes = [self attributesForClass:aClass];
|
||||
|
||||
if (attributes)
|
||||
return [attributes allKeys];
|
||||
else
|
||||
return [CPArray array];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a theme attribute defined for the given class, as found in the
|
||||
theme's ThemeDescriptors.j file.
|
||||
|
||||
\c aName should be the attribute name as you would pass to the method
|
||||
CPView::valueForThemeAttribute:.
|
||||
|
||||
For a description of valid values for \c aClass, see \ref attributeNamesForClass:.
|
||||
|
||||
@param aName The name of the attribute you want to retrieve
|
||||
@param aClass The themed class in which to look for the attribute
|
||||
@return An instance of _CPThemeAttribute or nil
|
||||
*/
|
||||
- (_CPThemeAttribute)attributeWithName:(CPString)aName forClass:(id)aClass
|
||||
{
|
||||
var attributes = [self attributesForClass:aClass];
|
||||
|
||||
if (!attributes)
|
||||
return nil;
|
||||
|
||||
return [attributes objectForKey:aName];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the value for a theme attribute in its normal state, as defined for the given class
|
||||
in the theme's ThemeDescriptors.j file.
|
||||
|
||||
\c aName should be the attribute name as you would pass to the method
|
||||
CPView::valueForThemeAttribute:.
|
||||
|
||||
For a description of valid values for \c aClass, see \ref attributeNamesForClass:.
|
||||
|
||||
@param aName The name of the attribute whose value you want to retrieve
|
||||
@param aClass The themed class in which to look for the attribute
|
||||
@return A value or nil
|
||||
*/
|
||||
- (id)valueForAttributeWithName:(CPString)aName forClass:(id)aClass
|
||||
{
|
||||
return [self valueForAttributeWithName:aName inState:CPThemeStateNormal forClass:aClass];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the value for a theme attribute in a given state, as defined for the given class
|
||||
in the theme's ThemeDescriptors.j file. This is the equivalent of the method
|
||||
CPView::valueForThemeAttribute:inState:, but retrieves the value from the theme definition as
|
||||
opposed to a single view's current theme state.
|
||||
|
||||
For a description of valid values for \c aClass, see \ref attributeNamesForClass:.
|
||||
|
||||
@param aName The name of the attribute whose value you want to retrieve
|
||||
@param aState The state qualifier for the attribute
|
||||
@param aClass The themed class in which to look for the attribute
|
||||
@return A value or nil
|
||||
*/
|
||||
- (id)valueForAttributeWithName:(CPString)aName inState:(CPThemeState)aState forClass:(id)aClass
|
||||
{
|
||||
var attribute = [self attributeWithName:aName forClass:aClass];
|
||||
|
||||
if (!attribute)
|
||||
return nil;
|
||||
|
||||
return [attribute valueForState:aState];
|
||||
}
|
||||
|
||||
- (void)takeThemeFromObject:(id)anObject
|
||||
{
|
||||
var attributes = [anObject _themeAttributeDictionary],
|
||||
attributeName = nil,
|
||||
attributeNames = [attributes keyEnumerator],
|
||||
objectThemeClass = [anObject themeClass];
|
||||
|
||||
while ((attributeName = [attributeNames nextObject]) !== nil)
|
||||
[self _recordAttribute:[attributes objectForKey:attributeName] forClass:objectThemeClass];
|
||||
}
|
||||
|
||||
- (void)_recordAttribute:(_CPThemeAttribute)anAttribute forClass:(CPString)aClass
|
||||
{
|
||||
if (![anAttribute hasValues])
|
||||
return;
|
||||
|
||||
var attributes = [_attributes objectForKey:aClass];
|
||||
|
||||
if (!attributes)
|
||||
{
|
||||
attributes = [CPDictionary dictionary];
|
||||
|
||||
[_attributes setObject:attributes forKey:aClass];
|
||||
}
|
||||
|
||||
var name = [anAttribute name],
|
||||
existingAttribute = [attributes objectForKey:name];
|
||||
|
||||
if (existingAttribute)
|
||||
[attributes setObject:[existingAttribute attributeMergedWithAttribute:anAttribute] forKey:name];
|
||||
else
|
||||
[attributes setObject:anAttribute forKey:name];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPThemeNameKey = @"CPThemeNameKey",
|
||||
CPThemeAttributesKey = @"CPThemeAttributesKey";
|
||||
|
||||
@implementation CPTheme (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_name = [aCoder decodeObjectForKey:CPThemeNameKey];
|
||||
_attributes = [aCoder decodeObjectForKey:CPThemeAttributesKey];
|
||||
|
||||
CPThemesByName[_name] = self;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_name forKey:CPThemeNameKey];
|
||||
[aCoder encodeObject:_attributes forKey:CPThemeAttributesKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPThemeKeyedUnarchiver : CPKeyedUnarchiver
|
||||
{
|
||||
CPBundle _bundle;
|
||||
}
|
||||
|
||||
- (id)initForReadingWithData:(CPData)data bundle:(CPBundle)aBundle
|
||||
{
|
||||
self = [super initForReadingWithData:data];
|
||||
|
||||
if (self)
|
||||
_bundle = aBundle;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPBundle)bundle
|
||||
{
|
||||
return _bundle;
|
||||
}
|
||||
|
||||
- (BOOL)awakenCustomResources
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPThemeStates = {},
|
||||
CPThemeStateNames = {},
|
||||
CPThemeStateCount = 0;
|
||||
|
||||
function CPThemeState(aStateName)
|
||||
{
|
||||
var state = CPThemeStates[aStateName];
|
||||
|
||||
if (state === undefined)
|
||||
{
|
||||
if (aStateName.indexOf('+') === -1)
|
||||
state = 1 << CPThemeStateCount++;
|
||||
else
|
||||
{
|
||||
var state = 0,
|
||||
states = aStateName.split('+'),
|
||||
count = states.length;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var stateName = states[count],
|
||||
individualState = CPThemeStates[stateName];
|
||||
|
||||
if (individualState === undefined)
|
||||
{
|
||||
individualState = 1 << CPThemeStateCount++;
|
||||
CPThemeStates[stateName] = individualState;
|
||||
CPThemeStateNames[individualState] = stateName;
|
||||
}
|
||||
|
||||
state |= individualState;
|
||||
}
|
||||
}
|
||||
|
||||
CPThemeStates[aStateName] = state;
|
||||
CPThemeStateNames[state] = aStateName;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function CPThemeStateName(aState)
|
||||
{
|
||||
var name = CPThemeStateNames[aState];
|
||||
|
||||
if (name !== undefined)
|
||||
return name;
|
||||
|
||||
if (!(aState & (aState - 1)))
|
||||
return "";
|
||||
|
||||
var state = 1,
|
||||
name = "";
|
||||
|
||||
for (; state < aState; state <<= 1)
|
||||
if (aState & state)
|
||||
name += (name.length === 0 ? '' : '+') + CPThemeStateNames[state];
|
||||
|
||||
CPThemeStateNames[aState] = name;
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
CPThemeStateNames[0] = "normal";
|
||||
CPThemeStateNormal = CPThemeStates["normal"] = 0;
|
||||
CPThemeStateDisabled = CPThemeState("disabled");
|
||||
CPThemeStateHovered = CPThemeState("hovered");
|
||||
CPThemeStateHighlighted = CPThemeState("highlighted");
|
||||
CPThemeStateSelected = CPThemeState("selected");
|
||||
CPThemeStateTableDataView = CPThemeState("tableDataView");
|
||||
CPThemeStateSelectedDataView = CPThemeStateSelectedTableDataView = CPThemeState("selectedTableDataView");
|
||||
CPThemeStateGroupRow = CPThemeState("CPThemeStateGroupRow");
|
||||
CPThemeStateBezeled = CPThemeState("bezeled");
|
||||
CPThemeStateBordered = CPThemeState("bordered");
|
||||
CPThemeStateEditable = CPThemeState("editable");
|
||||
CPThemeStateEditing = CPThemeState("editing");
|
||||
CPThemeStateVertical = CPThemeState("vertical");
|
||||
CPThemeStateDefault = CPThemeState("default");
|
||||
CPThemeStateCircular = CPThemeState("circular");
|
||||
CPThemeStateAutocompleting = CPThemeState("autocompleting");
|
||||
CPThemeStateMainWindow = CPThemeState("mainWindow");
|
||||
CPThemeStateKeyWindow = CPThemeState("keyWindow");
|
||||
|
||||
@implementation _CPThemeAttribute : CPObject
|
||||
{
|
||||
CPString _name;
|
||||
id _defaultValue;
|
||||
CPDictionary _values @accessors(readonly, getter=values);
|
||||
|
||||
JSObject _cache;
|
||||
_CPThemeAttribute _parentAttribute;
|
||||
}
|
||||
|
||||
- (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_cache = { };
|
||||
_name = aName;
|
||||
_defaultValue = aDefaultValue;
|
||||
_values = [CPDictionary dictionary];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)name
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
- (id)defaultValue
|
||||
{
|
||||
return _defaultValue;
|
||||
}
|
||||
|
||||
- (BOOL)hasValues
|
||||
{
|
||||
return [_values count] > 0;
|
||||
}
|
||||
|
||||
- (BOOL)isTrivial
|
||||
{
|
||||
return ([_values count] === 1) && (Number([_values allKeys][0]) === CPThemeStateNormal);
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue
|
||||
{
|
||||
_cache = {};
|
||||
|
||||
if (aValue === undefined || aValue === nil)
|
||||
_values = [CPDictionary dictionary];
|
||||
else
|
||||
_values = [CPDictionary dictionaryWithObject:aValue forKey:String(CPThemeStateNormal)];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forState:(CPThemeState)aState
|
||||
{
|
||||
_cache = { };
|
||||
|
||||
if ((aValue === undefined) || (aValue === nil))
|
||||
[_values removeObjectForKey:String(aState)];
|
||||
else
|
||||
[_values setObject:aValue forKey:String(aState)];
|
||||
}
|
||||
|
||||
- (id)value
|
||||
{
|
||||
return [self valueForState:CPThemeStateNormal];
|
||||
}
|
||||
|
||||
- (id)valueForState:(CPThemeState)aState
|
||||
{
|
||||
var value = _cache[aState];
|
||||
|
||||
// This can be nil.
|
||||
if (value !== undefined)
|
||||
return value;
|
||||
|
||||
value = [_values objectForKey:String(aState)];
|
||||
|
||||
// If we don't have a value, and we have a non-normal state...
|
||||
if ((value === undefined || value === nil) && aState !== CPThemeStateNormal)
|
||||
{
|
||||
// If this is a composite state (not a power of 2), find the closest partial subset match.
|
||||
if (aState & (aState - 1))
|
||||
{
|
||||
var highestOneCount = 0,
|
||||
states = [_values allKeys],
|
||||
count = states.length;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
// states[count] is a string!
|
||||
var state = Number(states[count]);
|
||||
|
||||
// A & B = A iff A < B
|
||||
if ((state & aState) === state)
|
||||
{
|
||||
var oneCount = cachedNumberOfOnes[state];
|
||||
|
||||
if (oneCount === undefined)
|
||||
oneCount = numberOfOnes(state);
|
||||
|
||||
if (oneCount > highestOneCount)
|
||||
{
|
||||
highestOneCount = oneCount;
|
||||
value = [_values objectForKey:String(state)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Still don't have a value? OK, let's use the normal value.
|
||||
if (value === undefined || value === nil)
|
||||
value = [_values objectForKey:String(CPThemeStateNormal)];
|
||||
}
|
||||
|
||||
if (value === undefined || value === nil)
|
||||
value = [_parentAttribute valueForState:aState];
|
||||
|
||||
if (value === undefined || value === nil)
|
||||
value = _defaultValue;
|
||||
|
||||
_cache[aState] = value;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)setParentAttribute:(_CPThemeAttribute)anAttribute
|
||||
{
|
||||
if (_parentAttribute === anAttribute)
|
||||
return;
|
||||
|
||||
_cache = { };
|
||||
_parentAttribute = anAttribute;
|
||||
}
|
||||
|
||||
- (_CPThemeAttribute)attributeMergedWithAttribute:(_CPThemeAttribute)anAttribute
|
||||
{
|
||||
var mergedAttribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue];
|
||||
|
||||
mergedAttribute._values = [_values copy];
|
||||
[mergedAttribute._values addEntriesFromDictionary:anAttribute._values];
|
||||
|
||||
return mergedAttribute;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPThemeAttribute (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_cache = {};
|
||||
|
||||
_name = [aCoder decodeObjectForKey:@"name"];
|
||||
_defaultValue = [aCoder decodeObjectForKey:@"defaultValue"];
|
||||
_values = [CPDictionary dictionary];
|
||||
|
||||
if ([aCoder containsValueForKey:@"value"])
|
||||
{
|
||||
var state = CPThemeStateNormal;
|
||||
|
||||
if ([aCoder containsValueForKey:@"state"])
|
||||
state = CPThemeState([aCoder decodeObjectForKey:@"state"]);
|
||||
|
||||
[_values setObject:[aCoder decodeObjectForKey:"value"] forKey:state];
|
||||
}
|
||||
else
|
||||
{
|
||||
var encodedValues = [aCoder decodeObjectForKey:@"values"],
|
||||
keys = [encodedValues allKeys],
|
||||
count = keys.length;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var key = keys[count];
|
||||
|
||||
[_values setObject:[encodedValues objectForKey:key] forKey:CPThemeState(key)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_name forKey:@"name"];
|
||||
[aCoder encodeObject:_defaultValue forKey:@"defaultValue"];
|
||||
|
||||
var keys = [_values allKeys],
|
||||
count = keys.length;
|
||||
|
||||
if (count === 1)
|
||||
{
|
||||
var onlyKey = keys[0];
|
||||
|
||||
if (Number(onlyKey) !== CPThemeStateNormal)
|
||||
[aCoder encodeObject:CPThemeStateName(Number(onlyKey)) forKey:@"state"];
|
||||
|
||||
[aCoder encodeObject:[_values objectForKey:onlyKey] forKey:@"value"];
|
||||
}
|
||||
else
|
||||
{
|
||||
var encodedValues = [CPDictionary dictionary];
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var key = keys[count];
|
||||
|
||||
[encodedValues setObject:[_values objectForKey:key] forKey:CPThemeStateName(Number(key))];
|
||||
}
|
||||
|
||||
[aCoder encodeObject:encodedValues forKey:@"values"];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var cachedNumberOfOnes = [ 0 /*000000*/, 1 /*000001*/, 1 /*000010*/, 2 /*000011*/, 1 /*000100*/, 2 /*000101*/, 2 /*000110*/,
|
||||
3 /*000111*/, 1 /*001000*/, 2 /*001001*/, 2 /*001010*/, 3 /*001011*/, 2 /*001100*/, 3 /*001101*/,
|
||||
3 /*001110*/, 4 /*001111*/, 1 /*010000*/, 2 /*010001*/, 2 /*010010*/, 3 /*010011*/, 2 /*010100*/,
|
||||
3 /*010101*/, 3 /*010110*/, 4 /*010111*/, 2 /*011000*/, 3 /*011001*/, 3 /*011010*/, 4 /*011011*/,
|
||||
3 /*011100*/, 4 /*011101*/, 4 /*011110*/, 5 /*011111*/, 1 /*100000*/, 2 /*100001*/, 2 /*100010*/,
|
||||
3 /*100011*/, 2 /*100100*/, 3 /*100101*/, 3 /*100110*/, 4 /*100111*/, 2 /*101000*/, 3 /*101001*/,
|
||||
3 /*101010*/, 4 /*101011*/, 3 /*101100*/, 4 /*101101*/, 4 /*101110*/, 5 /*101111*/, 2 /*110000*/,
|
||||
3 /*110001*/, 3 /*110010*/, 4 /*110011*/, 3 /*110100*/, 4 /*110101*/, 4 /*110110*/, 5 /*110111*/,
|
||||
3 /*111000*/, 4 /*111001*/, 4 /*111010*/, 5 /*111011*/, 4 /*111100*/, 5 /*111101*/, 5 /*111110*/,
|
||||
6 /*111111*/ ];
|
||||
|
||||
var numberOfOnes = function(aNumber)
|
||||
{
|
||||
var count = 0,
|
||||
slot = aNumber;
|
||||
|
||||
while (aNumber)
|
||||
{
|
||||
++count;
|
||||
aNumber &= (aNumber - 1);
|
||||
}
|
||||
|
||||
cachedNumberOfOnes[slot] = count;
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
numberOfOnes.displayName = "numberOfOnes";
|
||||
|
||||
function CPThemeAttributeEncode(aCoder, aThemeAttribute)
|
||||
{
|
||||
var values = aThemeAttribute._values,
|
||||
count = [values count],
|
||||
key = "$a" + [aThemeAttribute name];
|
||||
|
||||
if (count === 1)
|
||||
{
|
||||
var state = [values allKeys][0];
|
||||
|
||||
if (Number(state) === 0)
|
||||
{
|
||||
[aCoder encodeObject:[values objectForKey:state] forKey:key];
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (count >= 1)
|
||||
{
|
||||
[aCoder encodeObject:aThemeAttribute forKey:key];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
function CPThemeAttributeDecode(aCoder, anAttributeName, aDefaultValue, aTheme, aClass)
|
||||
{
|
||||
var key = "$a" + anAttributeName;
|
||||
|
||||
if (![aCoder containsValueForKey:key])
|
||||
var attribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
|
||||
|
||||
else
|
||||
{
|
||||
var attribute = [aCoder decodeObjectForKey:key];
|
||||
|
||||
if (!attribute.isa || ![attribute isKindOfClass:[_CPThemeAttribute class]])
|
||||
{
|
||||
var themeAttribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
|
||||
|
||||
[themeAttribute setValue:attribute];
|
||||
|
||||
attribute = themeAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
if (aTheme && aClass)
|
||||
[attribute setParentAttribute:[aTheme attributeWithName:anAttributeName forClass:aClass]];
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
/* TO AUTO CREATE THESE:
|
||||
function bit_count(bits)
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
while (bits)
|
||||
{
|
||||
++count;
|
||||
bits &= (bits - 1);
|
||||
}
|
||||
|
||||
return count ;
|
||||
}
|
||||
|
||||
zeros = "000000000";
|
||||
|
||||
function pad(string, digits)
|
||||
{
|
||||
return zeros.substr(0, digits - string.length) + string;
|
||||
}
|
||||
|
||||
var str = ""
|
||||
str += '[';
|
||||
for (i = 0;i < Math.pow(2,6);++i)
|
||||
{
|
||||
str += bit_count(i) + " /*" + pad(i.toString(2),6) + "*" + "/, ";
|
||||
}
|
||||
print(str+']');
|
||||
|
||||
*/
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* CPThemeBlend.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPTheme.j"
|
||||
@import "_CPCibCustomResource.j"
|
||||
@import "_CPCibKeyedUnarchiver.j"
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
*/
|
||||
|
||||
@implementation CPThemeBlend : CPObject
|
||||
{
|
||||
CPBundle _bundle;
|
||||
CPArray _themes;
|
||||
id _loadDelegate;
|
||||
}
|
||||
|
||||
- (id)initWithContentsOfURL:(CPURL)aURL
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_bundle = [[CPBundle alloc] initWithPath:aURL];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of names of the keyed theme archives that make up this blend.
|
||||
Each item in the array will have the extension ".keyedtheme".
|
||||
*/
|
||||
- (CPArray)themes
|
||||
{
|
||||
return _themes;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of names of the themes that make up this blend.
|
||||
*/
|
||||
- (CPArray)themeNames
|
||||
{
|
||||
var names = [];
|
||||
|
||||
for (var i = 0; i < _themes.length; ++i)
|
||||
names.push(_themes[i].substring(0, _themes[i].indexOf(".keyedtheme")));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
- (void)loadWithDelegate:(id)aDelegate
|
||||
{
|
||||
_loadDelegate = aDelegate;
|
||||
|
||||
[_bundle loadWithDelegate:self];
|
||||
}
|
||||
|
||||
- (void)bundleDidFinishLoading:(CPBundle)aBundle
|
||||
{
|
||||
_themes = [_bundle objectForInfoDictionaryKey:@"CPKeyedThemes"];
|
||||
|
||||
var count = _themes.length;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var path = [aBundle pathForResource:_themes[count]],
|
||||
unarchiver = [[_CPThemeKeyedUnarchiver alloc]
|
||||
initForReadingWithData:[[CPURL URLWithString:path] staticResourceData]
|
||||
bundle:_bundle];
|
||||
|
||||
[unarchiver decodeObjectForKey:@"root"];
|
||||
|
||||
[unarchiver finishDecoding];
|
||||
}
|
||||
|
||||
[_loadDelegate blendDidFinishLoading:self];
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
-1263
File diff suppressed because it is too large
Load Diff
@@ -1,662 +0,0 @@
|
||||
/*
|
||||
* CPToolbarItem.j
|
||||
* AppKit
|
||||
*
|
||||
* 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 <Foundation/CPString.j>
|
||||
|
||||
@import "CPImage.j"
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
CPToolbarItemVisibilityPriorityStandard = 0;
|
||||
CPToolbarItemVisibilityPriorityLow = -1000;
|
||||
CPToolbarItemVisibilityPriorityHigh = 1000;
|
||||
CPToolbarItemVisibilityPriorityUser = 2000;
|
||||
|
||||
CPToolbarSeparatorItemIdentifier = @"CPToolbarSeparatorItem";
|
||||
CPToolbarSpaceItemIdentifier = @"CPToolbarSpaceItem";
|
||||
CPToolbarFlexibleSpaceItemIdentifier = @"CPToolbarFlexibleSpaceItem";
|
||||
CPToolbarShowColorsItemIdentifier = @"CPToolbarShowColorsItem";
|
||||
CPToolbarShowFontsItemIdentifier = @"CPToolbarShowFontsItem";
|
||||
CPToolbarCustomizeToolbarItemIdentifier = @"CPToolbarCustomizeToolbarItem";
|
||||
CPToolbarPrintItemIdentifier = @"CPToolbarPrintItem";
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPToolbarItem
|
||||
|
||||
A representation of an item in a CPToolbar.
|
||||
*/
|
||||
@implementation CPToolbarItem : CPObject
|
||||
{
|
||||
CPString _itemIdentifier;
|
||||
|
||||
CPToolbar _toolbar;
|
||||
|
||||
CPString _label;
|
||||
CPString _paletteLabel;
|
||||
CPString _toolTip;
|
||||
int _tag;
|
||||
id _target;
|
||||
SEL _action;
|
||||
BOOL _isEnabled;
|
||||
CPImage _image;
|
||||
CPImage _alternateImage;
|
||||
|
||||
CPView _view;
|
||||
|
||||
CGSize _minSize;
|
||||
CGSize _maxSize;
|
||||
|
||||
int _visibilityPriority;
|
||||
|
||||
BOOL _autovalidates;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithItemIdentifier:@""];
|
||||
}
|
||||
|
||||
// Creating a Toolbar Item
|
||||
/*!
|
||||
Initializes the toolbar item with a specified identifier.
|
||||
@param anItemIdentifier the item's identifier
|
||||
@return the initialized toolbar item
|
||||
*/
|
||||
- (id)initWithItemIdentifier:(CPString)anItemIdentifier
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_itemIdentifier = anItemIdentifier;
|
||||
|
||||
_tag = 0;
|
||||
_isEnabled = YES;
|
||||
|
||||
_minSize = CGSizeMakeZero();
|
||||
_maxSize = CGSizeMakeZero();
|
||||
|
||||
_visibilityPriority = CPToolbarItemVisibilityPriorityStandard;
|
||||
_autovalidates = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Managing Attributes
|
||||
/*!
|
||||
Returns the item's identifier.
|
||||
*/
|
||||
- (CPString)itemIdentifier
|
||||
{
|
||||
return _itemIdentifier;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the toolbar of which this item is a part.
|
||||
*/
|
||||
- (CPToolbar)toolbar
|
||||
{
|
||||
return _toolbar;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_setToolbar:(CPToolbar)aToolbar
|
||||
{
|
||||
_toolbar = aToolbar;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's label
|
||||
*/
|
||||
- (CPString)label
|
||||
{
|
||||
return _label;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's label.
|
||||
@param aLabel the new label for the item
|
||||
*/
|
||||
- (void)setLabel:(CPString)aLabel
|
||||
{
|
||||
_label = aLabel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the palette label.
|
||||
*/
|
||||
- (CPString)paletteLabel
|
||||
{
|
||||
return _paletteLabel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the palette label
|
||||
@param aPaletteLabel the new palette label
|
||||
*/
|
||||
- (void)setPaletteLabel:(CPString)aPaletteLabel
|
||||
{
|
||||
_paletteLabel = aPaletteLabel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's tooltip. A tooltip pops up
|
||||
next to the cursor when the user hovers over
|
||||
the item with the mouse.
|
||||
*/
|
||||
- (CPString)toolTip
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(toolTip)])
|
||||
return [_view toolTip];
|
||||
|
||||
return _toolTip;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's tooltip. A tooltip pops up next to the cursor when the user hovers over the item with the mouse.
|
||||
@param aToolTip the new item tool tip
|
||||
*/
|
||||
- (void)setToolTip:(CPString)aToolTip
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setToolTip:)])
|
||||
[_view setToolTip:aToolTip];
|
||||
|
||||
_toolTip = aToolTip;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's tag.
|
||||
*/
|
||||
- (int)tag
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(tag)])
|
||||
return [_view tag];
|
||||
|
||||
return _tag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's tag.
|
||||
@param aTag the new tag for the item
|
||||
*/
|
||||
- (void)setTag:(int)aTag
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setTag:)])
|
||||
[_view setTag:aTag];
|
||||
|
||||
_tag = aTag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's action target.
|
||||
*/
|
||||
- (id)target
|
||||
{
|
||||
if (_view)
|
||||
return [_view respondsToSelector:@selector(target)] ? [_view target] : nil;
|
||||
|
||||
return _target;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the target of the action that is triggered when the user clicks this item. \c nil will cause
|
||||
the action to be passed on to the first responder.
|
||||
@param aTarget the new target
|
||||
*/
|
||||
- (void)setTarget:(id)aTarget
|
||||
{
|
||||
if (!_view)
|
||||
_target = aTarget;
|
||||
|
||||
else if ([_view respondsToSelector:@selector(setTarget:)])
|
||||
[_view setTarget:aTarget];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the action that is triggered when the user clicks this item.
|
||||
*/
|
||||
- (SEL)action
|
||||
{
|
||||
if (_view)
|
||||
return [_view respondsToSelector:@selector(action)] ? [_view action] : nil;
|
||||
|
||||
return _action;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the action that is triggered when the user clicks this item.
|
||||
@param anAction the new action
|
||||
*/
|
||||
- (void)setAction:(SEL)anAction
|
||||
{
|
||||
if (!_view)
|
||||
_action = anAction;
|
||||
|
||||
else if ([_view respondsToSelector:@selector(setAction:)])
|
||||
[_view setAction:anAction];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the item is enabled.
|
||||
*/
|
||||
- (BOOL)isEnabled
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(isEnabled)])
|
||||
return [_view isEnabled];
|
||||
|
||||
return _isEnabled;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the item is enabled.
|
||||
@param aFlag \c YES enables the item
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)shouldBeEnabled
|
||||
{
|
||||
if (_isEnabled === shouldBeEnabled)
|
||||
return;
|
||||
|
||||
if ([_view respondsToSelector:@selector(setEnabled:)])
|
||||
[_view setEnabled:shouldBeEnabled];
|
||||
|
||||
_isEnabled = shouldBeEnabled;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's image
|
||||
*/
|
||||
- (CPImage)image
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(image)])
|
||||
return [_view image];
|
||||
|
||||
return _image;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's image.
|
||||
@param anImage the new item image
|
||||
*/
|
||||
- (void)setImage:(CPImage)anImage
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setImage:)])
|
||||
[_view setImage:anImage];
|
||||
|
||||
_image = anImage;
|
||||
|
||||
if (!_image)
|
||||
return;
|
||||
|
||||
if (_minSize.width === 0 && _minSize.height === 0 &&
|
||||
_maxSize.width === 0 && _maxSize.height === 0)
|
||||
{
|
||||
var imageSize = [_image size];
|
||||
|
||||
if (imageSize.width > 0 || imageSize.height > 0)
|
||||
{
|
||||
[self setMinSize:imageSize];
|
||||
[self setMaxSize:imageSize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the alternate image. This image is displayed on the item when the user is clicking it.
|
||||
@param anImage the new alternate image
|
||||
*/
|
||||
- (void)setAlternateImage:(CPImage)anImage
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setAlternateImage:)])
|
||||
[_view setAlternateImage:anImage];
|
||||
|
||||
_alternateImage = anImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the alternate image. This image is displayed on the item when the user is clicking it.
|
||||
*/
|
||||
- (CPImage)alternateImage
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(alternateIamge)])
|
||||
return [_view alternateImage];
|
||||
|
||||
return _alternateImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's view.
|
||||
*/
|
||||
- (CPView)view
|
||||
{
|
||||
return _view;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's view
|
||||
@param aView the item's new view
|
||||
*/
|
||||
- (void)setView:(CPView)aView
|
||||
{
|
||||
if (_view == aView)
|
||||
return;
|
||||
|
||||
_view = aView;
|
||||
|
||||
if (_view)
|
||||
{
|
||||
// Tags get forwarded.
|
||||
if (_tag !== 0 && [_view respondsToSelector:@selector(setTag:)])
|
||||
[_view setTag:_tag];
|
||||
|
||||
_target = nil;
|
||||
_action = nil;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's minimum size.
|
||||
*/
|
||||
- (CGSize)minSize
|
||||
{
|
||||
return _minSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's minimum size.
|
||||
@param aMinSize the new minimum size
|
||||
*/
|
||||
- (void)setMinSize:(CGSize)aMinSize
|
||||
{
|
||||
if (!aMinSize.height || !aMinSize.width)
|
||||
return;
|
||||
|
||||
_minSize = CGSizeMakeCopy(aMinSize);
|
||||
|
||||
// Try to provide some sanity: Make maxSize >= minSize
|
||||
_maxSize = CGSizeMake(MAX(_minSize.width, _maxSize.width), MAX(_minSize.height, _maxSize.height));
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's maximum size.
|
||||
*/
|
||||
- (CGSize)maxSize
|
||||
{
|
||||
return _maxSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's new maximum size.
|
||||
@param aMaxSize the new maximum size
|
||||
*/
|
||||
- (void)setMaxSize:(CGSize)aMaxSize
|
||||
{
|
||||
if (!aMaxSize.height || !aMaxSize.width)
|
||||
return;
|
||||
|
||||
_maxSize = CGSizeMakeCopy(aMaxSize);
|
||||
|
||||
// Try to provide some sanity: Make minSize <= maxSize
|
||||
_minSize = CGSizeMake(MIN(_minSize.width, _maxSize.width), MIN(_minSize.height, _maxSize.height));
|
||||
}
|
||||
|
||||
// Visibility Priority
|
||||
/*!
|
||||
Returns the item's visibility priority. The value will be one of:
|
||||
<pre>
|
||||
CPToolbarItemVisibilityPriorityStandard
|
||||
CPToolbarItemVisibilityPriorityLow
|
||||
CPToolbarItemVisibilityPriorityHigh
|
||||
CPToolbarItemVisibilityPriorityUser
|
||||
</pre>
|
||||
*/
|
||||
- (int)visibilityPriority
|
||||
{
|
||||
return _visibilityPriority;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's visibility priority. The value must be one of:
|
||||
<pre>
|
||||
CPToolbarItemVisibilityPriorityStandard
|
||||
CPToolbarItemVisibilityPriorityLow
|
||||
CPToolbarItemVisibilityPriorityHigh
|
||||
CPToolbarItemVisibilityPriorityUser
|
||||
</pre>
|
||||
@param aVisiblityPriority the priority
|
||||
*/
|
||||
- (void)setVisibilityPriority:(int)aVisibilityPriority
|
||||
{
|
||||
_visibilityPriority = aVisibilityPriority;
|
||||
}
|
||||
|
||||
- (void)validate
|
||||
{
|
||||
var action = [self action],
|
||||
target = [self target];
|
||||
|
||||
// View items do not do any target-action analysis.
|
||||
if (_view)
|
||||
{
|
||||
if ([target respondsToSelector:@selector(validateToolbarItem:)])
|
||||
{
|
||||
var shouldBeEnabled = [target validateToolbarItem:self];
|
||||
if (_isEnabled !== shouldBeEnabled)
|
||||
[self setEnabled:shouldBeEnabled];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!action)
|
||||
{
|
||||
if (_isEnabled)
|
||||
return [self setEnabled:NO];
|
||||
return;
|
||||
}
|
||||
|
||||
if (target && ![target respondsToSelector:action])
|
||||
{
|
||||
if (_isEnabled)
|
||||
return [self setEnabled:NO];
|
||||
return;
|
||||
}
|
||||
|
||||
target = [CPApp targetForAction:action to:target from:self];
|
||||
|
||||
if (!target)
|
||||
{
|
||||
if (_isEnabled)
|
||||
return [self setEnabled:NO];
|
||||
return;
|
||||
}
|
||||
|
||||
if ([target respondsToSelector:@selector(validateToolbarItem:)])
|
||||
{
|
||||
var shouldBeEnabled = [target validateToolbarItem:self];
|
||||
if (_isEnabled !== shouldBeEnabled)
|
||||
[self setEnabled:shouldBeEnabled];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_isEnabled)
|
||||
[self setEnabled:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)autovalidates
|
||||
{
|
||||
return _autovalidates;
|
||||
}
|
||||
|
||||
- (void)setAutovalidates:(BOOL)shouldAutovalidate
|
||||
{
|
||||
_autovalidates = !!shouldAutovalidate;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPToolbarItemItemIdentifierKey = @"CPToolbarItemItemIdentifierKey",
|
||||
CPToolbarItemLabelKey = @"CPToolbarItemLabelKey",
|
||||
CPToolbarItemPaletteLabelKey = @"CPToolbarItemPaletteLabelKey",
|
||||
CPToolbarItemToolTipKey = @"CPToolbarItemToolTipKey",
|
||||
CPToolbarItemTagKey = @"CPToolbarItemTagKey",
|
||||
CPToolbarItemTargetKey = @"CPToolbarItemTargetKey",
|
||||
CPToolbarItemActionKey = @"CPToolbarItemActionKey",
|
||||
CPToolbarItemEnabledKey = @"CPToolbarItemEnabledKey",
|
||||
CPToolbarItemImageKey = @"CPToolbarItemImageKey",
|
||||
CPToolbarItemAlternateImageKey = @"CPToolbarItemAlternateImageKey",
|
||||
CPToolbarItemViewKey = @"CPToolbarItemViewKey",
|
||||
CPToolbarItemMinSizeKey = @"CPToolbarItemMinSizeKey",
|
||||
CPToolbarItemMaxSizeKey = @"CPToolbarItemMaxSizeKey",
|
||||
CPToolbarItemVisibilityPriorityKey = @"CPToolbarItemVisibilityPriorityKey",
|
||||
CPToolbarItemAutovalidatesKey = @"CPToolbarItemAutovalidatesKey";
|
||||
|
||||
@implementation CPToolbarItem (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_itemIdentifier = [aCoder decodeObjectForKey:CPToolbarItemItemIdentifierKey];
|
||||
|
||||
_minSize = [aCoder decodeSizeForKey:CPToolbarItemMinSizeKey];
|
||||
_maxSize = [aCoder decodeSizeForKey:CPToolbarItemMaxSizeKey];
|
||||
|
||||
[self setLabel:[aCoder decodeObjectForKey:CPToolbarItemLabelKey]];
|
||||
[self setPaletteLabel:[aCoder decodeObjectForKey:CPToolbarItemPaletteLabelKey]];
|
||||
[self setToolTip:[aCoder decodeObjectForKey:CPToolbarItemToolTipKey]];
|
||||
|
||||
[self setTag:[aCoder decodeObjectForKey:CPToolbarItemTagKey]];
|
||||
[self setTarget:[aCoder decodeObjectForKey:CPToolbarItemTargetKey]];
|
||||
[self setAction:CPSelectorFromString([aCoder decodeObjectForKey:CPToolbarItemActionKey])];
|
||||
|
||||
[self setEnabled:[aCoder decodeBoolForKey:CPToolbarItemEnabledKey]];
|
||||
|
||||
[self setImage:[aCoder decodeObjectForKey:CPToolbarItemImageKey]];
|
||||
[self setAlternateImage:[aCoder decodeObjectForKey:CPToolbarItemAlternateImageKey]];
|
||||
|
||||
[self setView:[aCoder decodeObjectForKey:CPToolbarItemViewKey]];
|
||||
|
||||
[self setVisibilityPriority:[aCoder decodeIntForKey:CPToolbarItemVisibilityPriorityKey]];
|
||||
[self setAutovalidates:[aCoder decodeBoolForKey:CPToolbarItemAutovalidatesKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_itemIdentifier forKey:CPToolbarItemItemIdentifierKey];
|
||||
|
||||
[aCoder encodeObject:[self label] forKey:CPToolbarItemLabelKey];
|
||||
[aCoder encodeObject:[self paletteLabel] forKey:CPToolbarItemPaletteLabelKey];
|
||||
|
||||
[aCoder encodeObject:[self toolTip] forKey:CPToolbarItemToolTipKey];
|
||||
|
||||
[aCoder encodeObject:[self tag] forKey:CPToolbarItemTagKey];
|
||||
[aCoder encodeObject:[self target] forKey:CPToolbarItemTargetKey];
|
||||
[aCoder encodeObject:[self action] forKey:CPToolbarItemActionKey];
|
||||
|
||||
[aCoder encodeObject:[self isEnabled] forKey:CPToolbarItemEnabledKey];
|
||||
|
||||
[aCoder encodeObject:[self image] forKey:CPToolbarItemImageKey];
|
||||
[aCoder encodeObject:[self alternateImage] forKey:CPToolbarItemAlternateImageKey];
|
||||
|
||||
[aCoder encodeObject:[self view] forKey:CPToolbarItemViewKey];
|
||||
|
||||
[aCoder encodeSize:[self minSize] forKey:CPToolbarItemMinSizeKey];
|
||||
[aCoder encodeSize:[self maxSize] forKey:CPToolbarItemMaxSizeKey];
|
||||
|
||||
[aCoder encodeObject:[self visibilityPriority] forKey:CPToolbarItemVisibilityPriorityKey];
|
||||
[aCoder encodeBool:[self autovalidates] forKey:CPToolbarItemAutovalidatesKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPToolbarItem (CPCopying)
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var copy = [[[self class] alloc] initWithItemIdentifier:_itemIdentifier];
|
||||
|
||||
if (_view)
|
||||
[copy setView:[CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:_view]]];
|
||||
|
||||
[copy _setToolbar:_toolbar];
|
||||
|
||||
[copy setLabel:_label];
|
||||
[copy setPaletteLabel:_paletteLabel];
|
||||
[copy setToolTip:[self toolTip]];
|
||||
|
||||
[copy setTag:[self tag]];
|
||||
[copy setTarget:[self target]];
|
||||
[copy setAction:[self action]];
|
||||
|
||||
[copy setEnabled:[self isEnabled]];
|
||||
|
||||
[copy setImage:[self image]];
|
||||
[copy setAlternateImage:[self alternateImage]];
|
||||
|
||||
[copy setMinSize:_minSize];
|
||||
[copy setMaxSize:_maxSize];
|
||||
|
||||
[copy setVisibilityPriority:[self visibilityPriority]];
|
||||
[copy setAutovalidates:[self autovalidates]];
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Standard toolbar identifiers
|
||||
|
||||
@implementation CPToolbarItem (Standard)
|
||||
|
||||
/* @ignore */
|
||||
+ (CPToolbarItem)_standardItemWithItemIdentifier:(CPString)anItemIdentifier
|
||||
{
|
||||
switch (anItemIdentifier)
|
||||
{
|
||||
case CPToolbarSeparatorItemIdentifier: return [_CPToolbarSeparatorItem new];
|
||||
case CPToolbarSpaceItemIdentifier: return [_CPToolbarSpaceItem new];
|
||||
case CPToolbarFlexibleSpaceItemIdentifier: return [_CPToolbarFlexibleSpaceItem new];
|
||||
case CPToolbarShowColorsItemIdentifier: return [_CPToolbarShowColorsItem new];
|
||||
case CPToolbarShowFontsItemIdentifier: return nil;
|
||||
case CPToolbarCustomizeToolbarItemIdentifier: return nil;
|
||||
case CPToolbarPrintItemIdentifier: return nil;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@import "_CPToolbarFlexibleSpaceItem.j"
|
||||
@import "_CPToolbarShowColorsItem.j"
|
||||
@import "_CPToolbarSeparatorItem.j"
|
||||
@import "_CPToolbarSpaceItem.j"
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPIndexPath.j>
|
||||
|
||||
|
||||
@implementation CPTreeNode : CPObject
|
||||
{
|
||||
id _representedObject @accessors(readonly, property=representedObject);
|
||||
|
||||
CPTreeNode _parentNode @accessors(readonly, property=parentNode);
|
||||
CPMutableArray _childNodes;
|
||||
}
|
||||
|
||||
+ (id)treeNodeWithRepresentedObject:(id)anObject
|
||||
{
|
||||
return [[self alloc] initWithRepresentedObject:anObject];
|
||||
}
|
||||
|
||||
- (id)initWithRepresentedObject:(id)anObject
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_representedObject = anObject;
|
||||
_childNodes = [];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isLeaf
|
||||
{
|
||||
return [_childNodes count] <= 0;
|
||||
}
|
||||
|
||||
- (CPArray)childNodes
|
||||
{
|
||||
return [_childNodes copy];
|
||||
}
|
||||
|
||||
- (CPMutableArray)mutableChildNodes
|
||||
{
|
||||
return [self mutableArrayValueForKey:@"childNodes"];
|
||||
}
|
||||
|
||||
- (void)insertObject:(id)aTreeNode inChildNodesAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
[[aTreeNode._parentNode mutableChildNodes] removeObjectIdenticalTo:aTreeNode];
|
||||
|
||||
aTreeNode._parentNode = self;
|
||||
|
||||
[_childNodes insertObject:aTreeNode atIndex:anIndex];
|
||||
}
|
||||
|
||||
- (void)removeObjectFromChildNodesAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
[_childNodes objectAtIndex:anIndex]._parentNode = nil;
|
||||
|
||||
[_childNodes removeObjectAtIndex:anIndex];
|
||||
}
|
||||
|
||||
- (void)replaceObjectFromChildNodesAtIndex:(CPInteger)anIndex withObject:(id)aTreeNode
|
||||
{
|
||||
var oldTreeNode = [_childNodes objectAtIndex:anIndex];
|
||||
|
||||
oldTreeNode._parentNode = nil;
|
||||
aTreeNode._parentNode = self;
|
||||
|
||||
[_childNodes replaceObjectAtIndex:anIndex withObject:aTreeNode];
|
||||
}
|
||||
|
||||
- (id)objectInChildNodesAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
return _childNodes[anIndex];
|
||||
}
|
||||
|
||||
- (void)sortWithSortDescriptors:(CPArray)sortDescriptors recursively:(BOOL)shouldSortRecursively
|
||||
{
|
||||
[_childNodes sortUsingDescriptors:sortDescriptors];
|
||||
|
||||
if (!shouldSortRecursively)
|
||||
return;
|
||||
|
||||
var count = [_childNodes count];
|
||||
|
||||
while (count--)
|
||||
[_childNodes[count] sortWithSortDescriptors:sortDescriptors recursively:YES];
|
||||
}
|
||||
|
||||
- (CPTreeNode)descendantNodeAtIndexPath:(CPIndexPath)indexPath
|
||||
{
|
||||
var index = 0,
|
||||
count = [indexPath length],
|
||||
node = self;
|
||||
|
||||
for (; index < count; ++index)
|
||||
node = [node objectInChildNodesAtIndex:[indexPath indexAtPosition:index]];
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
|
||||
CPTreeNodeParentNodeKey = @"CPTreeNodeParentNodeKey",
|
||||
CPTreeNodeChildNodesKey = @"CPTreeNodeChildNodesKey";
|
||||
|
||||
@implementation CPTreeNode (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_representedObject = [aCoder decodeObjectForKey:CPTreeNodeRepresentedObjectKey];
|
||||
_parentNode = [aCoder decodeObjectForKey:CPTreeNodeParentNodeKey];
|
||||
_childNodes = [aCoder decodeObjectForKey:CPTreeNodeChildNodesKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_representedObject forKey:CPTreeNodeRepresentedObjectKey];
|
||||
[aCoder encodeConditionalObject:_parentNode forKey:CPTreeNodeParentNodeKey];
|
||||
[aCoder encodeObject:_childNodes forKey:CPTreeNodeChildNodesKey];
|
||||
}
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user