Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
535a38dec0 | ||
|
|
46c78c3fe0 | ||
|
|
9b0a0b8bec | ||
|
|
db32b10551 | ||
|
|
d8d2670bf4 | ||
|
|
9bc56bed86 | ||
|
|
66629d8e32 | ||
|
|
c3c4a09c8c | ||
|
|
be7ac48c2b | ||
|
|
be0b97cb56 | ||
|
|
6f7cbe21ef | ||
|
|
eff3dfc74a | ||
|
|
2ad1f4e4e5 | ||
|
|
4aec3f7466 | ||
|
|
c8991e07b9 | ||
|
|
48157db973 | ||
|
|
d070e5fce5 | ||
|
|
a8fae51ea8 | ||
|
|
2815d462be | ||
|
|
ed002d7d3e | ||
|
|
a7994c1480 | ||
|
|
85f8ca9241 | ||
|
|
b5fb9e8c23 | ||
|
|
c605ca4531 | ||
|
|
c288930a3c | ||
|
|
596676a562 | ||
|
|
cc3d9f337c | ||
|
|
1addc002d3 | ||
|
|
e99e2660e9 | ||
|
|
03d1bb8bce | ||
|
|
8d210100a5 | ||
|
|
d25e96902f | ||
|
|
ad792d9772 | ||
|
|
5b00f7f2e8 | ||
|
|
97b7f0901e | ||
|
|
67b2671dad | ||
|
|
9e7b737960 | ||
|
|
9bc6a69b76 | ||
|
|
1cb28e4206 | ||
|
|
5311689265 | ||
|
|
4228a2a146 | ||
|
|
7886d49e65 | ||
|
|
e5284e9b43 | ||
|
|
c6403baa04 | ||
|
|
662b1ec696 | ||
|
|
043df71185 | ||
|
|
8dab47f4da | ||
|
|
90d069df2b | ||
|
|
dcbe3ee650 | ||
|
|
e32489aba1 | ||
|
|
b619c0b793 | ||
|
|
fab61d14fa | ||
|
|
b4edc084f8 | ||
|
|
c7518d0c49 | ||
|
|
a40345c14d | ||
|
|
bacc50c271 | ||
|
|
909471a5d7 | ||
|
|
38f7ea00c2 | ||
|
|
9f459f053e | ||
|
|
4195f3b8c6 | ||
|
|
4926824482 | ||
|
|
cb2f539cf7 | ||
|
|
7f31be7cb1 | ||
|
|
78b9c5b576 | ||
|
|
01e35efdbb | ||
|
|
bef1687b1f | ||
|
|
da18a9eb24 | ||
|
|
d975e90407 | ||
|
|
abc8e14275 | ||
|
|
b032cd5b7d | ||
|
|
bb589285e3 | ||
|
|
1d52c3e957 | ||
|
|
df5ce9e0f3 | ||
|
|
16b446ae08 | ||
|
|
afa691376e | ||
|
|
be10bcf27a | ||
|
|
d0a5b6e1c7 | ||
|
|
271a1ef3b4 | ||
|
|
b91042eb08 | ||
|
|
608cb86a4a | ||
|
|
88780ff0a9 | ||
|
|
a5e241afd7 | ||
|
|
28772d3254 | ||
|
|
d1f5c1eebc | ||
|
|
d40ebaf369 | ||
|
|
06f74e5ef0 | ||
|
|
5eaeb5f05e | ||
|
|
961cb2f6ed | ||
|
|
4dca2bbb85 | ||
|
|
892d3d7120 | ||
|
|
c0fe499937 | ||
|
|
01e8e9e597 | ||
|
|
08b740c36f | ||
|
|
31918bc0cd | ||
|
|
d45aeed224 | ||
|
|
7d2835c522 | ||
|
|
c77a282aaf | ||
|
|
7a1cd5c545 | ||
|
|
cff26bd4e6 | ||
|
|
936ae1d563 | ||
|
|
3d52935be0 | ||
|
|
777b167a2a | ||
|
|
c5d602bd6d | ||
|
|
8545195cd7 | ||
|
|
74fd73c18d | ||
|
|
2479f15b6b | ||
|
|
75875f4032 | ||
|
|
0752b63b03 | ||
|
|
bde2f20fa3 | ||
|
|
29ff54054e | ||
|
|
8e1bbe0132 | ||
|
|
c12305f40a | ||
|
|
2be149c486 | ||
|
|
a2b6585c0d | ||
|
|
c7a16deb6f | ||
|
|
aa452952a8 | ||
|
|
1fb8c95b5d | ||
|
|
6ea4aa0a66 | ||
|
|
ad630c840b | ||
|
|
16b52035c1 | ||
|
|
e98cd9e069 | ||
|
|
e99f7261f3 | ||
|
|
85b370e6cf | ||
|
|
2462cd7b1d | ||
|
|
331e338c06 | ||
|
|
2b68f40872 | ||
|
|
2f7b3c306d | ||
|
|
eba5dbe654 | ||
|
|
9ae1f548bf | ||
|
|
c5ab90ae52 | ||
|
|
776cdf93fe | ||
|
|
2bbba19aba | ||
|
|
635a57b036 | ||
|
|
5b231867dc | ||
|
|
d191255a84 | ||
|
|
9bf8824109 | ||
|
|
f773bd217c | ||
|
|
a5f0f0e3db | ||
|
|
6f6bfc667b | ||
|
|
0789f49b94 | ||
|
|
92c94eab8f | ||
|
|
9f06ed725e | ||
|
|
6eee5543f9 | ||
|
|
c45039fd70 | ||
|
|
8e203ec78e | ||
|
|
1918528710 | ||
|
|
e32515e5cc | ||
|
|
2b77da821c | ||
|
|
f8404c7a97 | ||
|
|
b007f9899d | ||
|
|
156610ee07 | ||
|
|
8a460b65ed | ||
|
|
fcaf672bf8 | ||
|
|
dd5d8fcdce | ||
|
|
e0c4f6455d | ||
|
|
dd5d7e9979 | ||
|
|
408baac91e | ||
|
|
f241142216 | ||
|
|
f0561844d8 | ||
|
|
bd6294b806 | ||
|
|
70f8e3a832 | ||
|
|
59b302acde | ||
|
|
f54f8dfec9 | ||
|
|
93135e7e81 | ||
|
|
b8026e19ba | ||
|
|
147d9282fb | ||
|
|
7c18b8bc5f | ||
|
|
2ccbf5d4e5 | ||
|
|
da6d8c86d1 | ||
|
|
78d997b404 | ||
|
|
c6e5219f2e | ||
|
|
3bb450adbf | ||
|
|
a0f5257258 | ||
|
|
6520615bb0 | ||
|
|
4593212698 | ||
|
|
5363deb122 | ||
|
|
d5a651fc43 | ||
|
|
4740acbcc6 | ||
|
|
bfb2134329 | ||
|
|
ec83977fe1 | ||
|
|
98d08ff05d | ||
|
|
1eb6319b49 | ||
|
|
8f7e06f9d6 | ||
|
|
81e3abe10c | ||
|
|
edcee8934d | ||
|
|
d115d2ef47 | ||
|
|
d1dd725848 | ||
|
|
ebdba33082 | ||
|
|
918f6fbb8f | ||
|
|
28733ec592 | ||
|
|
916b9a399b | ||
|
|
3d62791477 | ||
|
|
64e6764015 | ||
|
|
cbed5c9cb1 | ||
|
|
0246d501e2 | ||
|
|
3d5b30ab9e | ||
|
|
1f479c3f20 | ||
|
|
b5bb9c8ec3 | ||
|
|
1ffc7ed6fb | ||
|
|
af0a479f88 | ||
|
|
86a28d8cc1 | ||
|
|
3dd178a69d | ||
|
|
417334419a | ||
|
|
c03b3c2d34 | ||
|
|
e97d4a8e72 | ||
|
|
24d0af3104 | ||
|
|
5790e99359 | ||
|
|
0d6c2c9890 | ||
|
|
c5df9c973f | ||
|
|
b89aa9af0d | ||
|
|
5d26066e02 | ||
|
|
de73d86684 | ||
|
|
d6244f090e | ||
|
|
1c44dc1052 | ||
|
|
4f7fbdc344 | ||
|
|
c5f37e665b | ||
|
|
99180ba001 | ||
|
|
9377cf9128 | ||
|
|
e17d8ca302 | ||
|
|
acbaa1eec4 | ||
|
|
65b6ff6ecc | ||
|
|
df7aa30267 | ||
|
|
0f4aab926e | ||
|
|
7f3512d22c | ||
|
|
a6b5ed22b0 | ||
|
|
e843b31287 | ||
|
|
06a5aa45ba | ||
|
|
45a5eed62f | ||
|
|
188e27e931 | ||
|
|
ed560ed489 | ||
|
|
186eb66f52 | ||
|
|
8b566f06f0 | ||
|
|
28d174d9d8 | ||
|
|
dd892f746d | ||
|
|
95b1b8b19d | ||
|
|
298b73ddfc | ||
|
|
4b9695ff50 | ||
|
|
a02b721944 | ||
|
|
f60a92d2c4 |
@@ -44,6 +44,7 @@
|
||||
@import "CPColor.j"
|
||||
@import "CPColorPanel.j"
|
||||
@import "CPColorWell.j"
|
||||
@import "CPComboBox.j"
|
||||
@import "CPCompatibility.j"
|
||||
@import "CPControl.j"
|
||||
@import "CPCookie.j"
|
||||
@@ -67,6 +68,7 @@
|
||||
@import "CPOutlineView.j"
|
||||
@import "CPPanel.j"
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPPopover.j"
|
||||
@import "CPPopUpButton.j"
|
||||
@import "CPPredicateEditor.j"
|
||||
@import "CPPredicateEditorRowTemplate.j"
|
||||
|
||||
@@ -117,7 +117,7 @@ CPCriticalAlertStyle = 2;
|
||||
*/
|
||||
+ (CPAlert)alertWithMessageText:(CPString)aMessage defaultButton:(CPString)defaultButtonTitle alternateButton:(CPString)alternateButtonTitle otherButton:(CPString)otherButtonTitle informativeTextWithFormat:(CPString)informativeText
|
||||
{
|
||||
var alert = [[CPAlert alloc] init];
|
||||
var alert = [[self alloc] init];
|
||||
|
||||
[alert setMessageText:aMessage];
|
||||
[alert addButtonWithTitle:defaultButtonTitle];
|
||||
@@ -142,7 +142,7 @@ CPCriticalAlertStyle = 2;
|
||||
*/
|
||||
+ (CPAlert)alertWithError:(CPString)anErrorMessage
|
||||
{
|
||||
var alert = [[CPAlert alloc] init];
|
||||
var alert = [[self alloc] init];
|
||||
|
||||
[alert setMessageText:anErrorMessage];
|
||||
[alert setAlertStyle:CPCriticalAlertStyle];
|
||||
|
||||
@@ -306,6 +306,9 @@ ACTUAL_FRAME_RATE = 0;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animation:valueForProgress:)])
|
||||
return [_delegate animation:self valueForProgress:t];
|
||||
|
||||
if (_animationCurve == CPAnimationLinear)
|
||||
return t;
|
||||
|
||||
var c1 = [],
|
||||
c2 = [];
|
||||
|
||||
@@ -564,8 +564,9 @@
|
||||
- (BOOL)setSelectionIndexes:(CPIndexSet)indexes
|
||||
{
|
||||
[self _selectionWillChange]
|
||||
[self __setSelectionIndexes:indexes];
|
||||
var r = [self __setSelectionIndexes:indexes];
|
||||
[self _selectionDidChange];
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -574,7 +575,7 @@
|
||||
*/
|
||||
- (BOOL)__setSelectionIndex:(int)theIndex
|
||||
{
|
||||
[self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:theIndex]];
|
||||
return [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:theIndex]];
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -583,28 +584,38 @@
|
||||
*/
|
||||
- (BOOL)__setSelectionIndexes:(CPIndexSet)indexes
|
||||
{
|
||||
if (!indexes)
|
||||
indexes = [CPIndexSet indexSet];
|
||||
var newIndexes = indexes;
|
||||
|
||||
if (![indexes count])
|
||||
if (!newIndexes)
|
||||
newIndexes = [CPIndexSet indexSet];
|
||||
|
||||
if (![newIndexes count])
|
||||
{
|
||||
if (_avoidsEmptySelection && [[self arrangedObjects] count])
|
||||
indexes = [CPIndexSet indexSetWithIndex:0];
|
||||
newIndexes = [CPIndexSet indexSetWithIndex:0];
|
||||
}
|
||||
else
|
||||
{
|
||||
var objectsCount = [[self arrangedObjects] count];
|
||||
|
||||
// Don't trash the input - the caller might depend on it or we might have been
|
||||
// given _selectionIndexes as the input in which case the equality test below
|
||||
// would always succeed despite our change below.
|
||||
newIndexes = [newIndexes copy];
|
||||
|
||||
// Remove out of bounds indexes.
|
||||
[indexes removeIndexesInRange:CPMakeRange(objectsCount, [indexes lastIndex] + 1)];
|
||||
[newIndexes removeIndexesInRange:CPMakeRange(objectsCount, [newIndexes lastIndex] + 1)];
|
||||
// When avoiding empty selection and the deleted selection was at the bottom, select the last item.
|
||||
if (![indexes count] && _avoidsEmptySelection && objectsCount)
|
||||
indexes = [CPIndexSet indexSetWithIndex:objectsCount - 1];
|
||||
if (![newIndexes count] && _avoidsEmptySelection && objectsCount)
|
||||
newIndexes = [CPIndexSet indexSetWithIndex:objectsCount - 1];
|
||||
}
|
||||
|
||||
if ([_selectionIndexes isEqualToIndexSet:indexes])
|
||||
if ([_selectionIndexes isEqualToIndexSet:newIndexes])
|
||||
return NO;
|
||||
|
||||
_selectionIndexes = [indexes copy];
|
||||
// If we haven't already created our own index instance, make sure to copy it here so that
|
||||
// the copy the user sent in is decoupled from our internal copy.
|
||||
_selectionIndexes = indexes === newIndexes ? [indexes copy] : newIndexes;
|
||||
|
||||
// Push back the new selection to the model for selectionIndexes if we have one.
|
||||
// There won't be an infinite loop because of the equality check above.
|
||||
@@ -636,10 +647,11 @@
|
||||
[self willChangeValueForKey:@"selectionIndexes"];
|
||||
[self _selectionWillChange];
|
||||
|
||||
[self __setSelectedObjects:objects];
|
||||
var r = [self __setSelectedObjects:objects];
|
||||
|
||||
[self didChangeValueForKey:@"selectionIndexes"];
|
||||
[self _selectionDidChange];
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -61,6 +61,7 @@ CPGrooveBorder = 3;
|
||||
var box = [[self alloc] initWithFrame:CGRectMakeZero()],
|
||||
enclosingView = [aView superview];
|
||||
|
||||
[box setAutoresizingMask:[aView autoresizingMask]];
|
||||
[box setFrameFromContentFrame:[aView frame]];
|
||||
|
||||
[enclosingView replaceSubview:aView with:box];
|
||||
|
||||
@@ -526,7 +526,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
// Cocoa raises an invalid parameter assertion and returns if you pass nil.
|
||||
if (aString === nil || aString === undefined)
|
||||
{
|
||||
CPLog.warn("nil sent to CPControl -setStringValue");
|
||||
CPLog.warn("nil or undefined sent to CPControl -setStringValue");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -536,10 +536,10 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
{
|
||||
value = nil;
|
||||
|
||||
if ([_formatter getObjectValue:AT_REF(value) forString:aString errorDescription:nil] === NO)
|
||||
if (![_formatter getObjectValue:AT_REF(value) forString:aString errorDescription:nil])
|
||||
{
|
||||
// If the given string is non-empty and doesn't work, Cocoa tries an empty string.
|
||||
if (!aString || [_formatter getObjectValue:AT_REF(value) forString:@"" errorDescription:nil] === NO)
|
||||
if (!aString || ![_formatter getObjectValue:AT_REF(value) forString:@"" errorDescription:nil])
|
||||
value = undefined; // Means the value is invalid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,6 @@ var CPBindingOperationAnd = 0,
|
||||
if (valueTransformer)
|
||||
aValue = [valueTransformer transformedValue:aValue];
|
||||
|
||||
|
||||
if (aValue === undefined || aValue === nil || aValue === [CPNull null])
|
||||
aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
|
||||
|
||||
@@ -311,7 +310,7 @@ var CPBindingOperationAnd = 0,
|
||||
- (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);
|
||||
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);
|
||||
@@ -488,6 +487,8 @@ CPSelectedIndexBinding = @"selectedIndex";
|
||||
CPTextColorBinding = @"textColor";
|
||||
CPToolTipBinding = @"toolTip";
|
||||
CPValueBinding = @"value";
|
||||
CPContentBinding = @"content";
|
||||
CPContentValuesBinding = @"contentValues";
|
||||
|
||||
//Binding options constants
|
||||
CPAllowsEditingMultipleValuesSelectionBindingOption = @"CPAllowsEditingMultipleValuesSelection";
|
||||
@@ -518,4 +519,4 @@ CPValueTransformerBindingOption = @"CPValueTransformer";
|
||||
CPIsControllerMarker = function(/*id*/anObject)
|
||||
{
|
||||
return anObject === CPMultipleValuesMarker || anObject === CPNoSelectionMarker || anObject === CPNotApplicableMarker || anObject === CPNullMarker;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,11 +622,11 @@ var _CPMenuBarVisible = NO,
|
||||
var validator = [CPApp targetForAction:[item action] to:[item target] from:item];
|
||||
|
||||
if (!validator || ![validator respondsToSelector:[item action]])
|
||||
[item _setEnabled:NO];
|
||||
[item setEnabled:NO];
|
||||
else if ([validator respondsToSelector:@selector(validateMenuItem:)])
|
||||
[item _setEnabled:[validator validateMenuItem:item]];
|
||||
[item setEnabled:[validator validateMenuItem:item]];
|
||||
else if ([validator respondsToSelector:@selector(validateUserInterfaceItem:)])
|
||||
[item _setEnabled:[validator validateUserInterfaceItem:item]];
|
||||
[item setEnabled:[validator validateUserInterfaceItem:item]];
|
||||
}
|
||||
|
||||
[[_menuWindow _menuView] tile];
|
||||
|
||||
@@ -61,6 +61,9 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
{
|
||||
var menu = [aMenuContainer menu];
|
||||
|
||||
if ([menu numberOfItems] <= 0)
|
||||
return;
|
||||
|
||||
CPApp._activeMenu = menu;
|
||||
|
||||
_startTime = [anEvent timestamp];//new Date();
|
||||
@@ -102,7 +105,7 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
if (type === CPAppKitDefined)
|
||||
return [self completeTracking];
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackEvent:) forNextEventMatchingMask:CPKeyDownMask | CPPeriodicMask | CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPAppKitDefinedMask | CPScrollWheelMask untilDate:nil inMode:nil dequeue:YES];
|
||||
[CPApp setTarget:self selector:@selector(trackEvent:) forNextEventMatchingMask:CPKeyDownMask | CPPeriodicMask | CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPRightMouseUpMask | CPAppKitDefinedMask | CPScrollWheelMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
if (type === CPKeyDown)
|
||||
{
|
||||
@@ -224,10 +227,17 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
[CPEvent startPeriodicEventsAfterDelay:0.0 withPeriod:0.04];
|
||||
}
|
||||
}
|
||||
else if (type === CPLeftMouseUp)
|
||||
else if (type === CPLeftMouseUp || type === CPRightMouseUp)
|
||||
{
|
||||
if (_hasMouseGoneUpAfterStartedTracking)
|
||||
{
|
||||
// Don't close the menu if the current item has a submenu
|
||||
// and did not override it's default action
|
||||
if ([activeItem action] === @selector(submenuAction:))
|
||||
return;
|
||||
|
||||
[trackingMenu cancelTracking];
|
||||
}
|
||||
else
|
||||
_hasMouseGoneUpAfterStartedTracking = YES;
|
||||
}
|
||||
|
||||
@@ -148,14 +148,6 @@ var CPMenuItemStringRepresentationDictionary = [CPDictionary dictionary];
|
||||
@param isEnabled \c YES enables the item. \c NO disables it.
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)isEnabled
|
||||
{
|
||||
if ([_menu autoenablesItems])
|
||||
return;
|
||||
|
||||
[self _setEnabled:isEnabled];
|
||||
}
|
||||
|
||||
- (void)_setEnabled:(BOOL)isEnabled
|
||||
{
|
||||
if (_isEnabled === isEnabled)
|
||||
return;
|
||||
|
||||
@@ -131,8 +131,8 @@
|
||||
|
||||
_contentObject = aContent;
|
||||
|
||||
[self didChangeValueForKey:@"contentObject"];
|
||||
[self _selectionDidChange];
|
||||
[self didChangeValueForKey:@"contentObject"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -323,12 +323,22 @@
|
||||
@ignore
|
||||
*/
|
||||
- (void)_selectionDidChange
|
||||
{
|
||||
[self _selectionDidChangeNotify:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_selectionDidChangeNotify:(BOOL)notify
|
||||
{
|
||||
if (_selection === undefined || _selection === nil)
|
||||
_selection = [[CPControllerSelectionProxy alloc] initWithController:self];
|
||||
|
||||
[_selection controllerDidChange];
|
||||
[self didChangeValueForKey:@"selection"];
|
||||
|
||||
if (notify)
|
||||
[self didChangeValueForKey:@"selection"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -666,21 +676,21 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
var count = [theValues count];
|
||||
|
||||
if (!count)
|
||||
value = CPNoSelectionMarker;
|
||||
return CPNoSelectionMarker;
|
||||
else if (count === 1)
|
||||
value = [theValues objectAtIndex:0];
|
||||
else
|
||||
{
|
||||
if ([_controller alwaysUsesMultipleValuesMarker])
|
||||
value = CPMultipleValuesMarker;
|
||||
return CPMultipleValuesMarker;
|
||||
else
|
||||
{
|
||||
value = [theValues objectAtIndex:0];
|
||||
|
||||
for (var i = 0, count= [theValues count]; i < count && value != CPMultipleValuesMarker; i++)
|
||||
for (var i = 0, count = [theValues count]; i < count; i++)
|
||||
{
|
||||
if (![value isEqual:[theValues objectAtIndex:i]])
|
||||
value = CPMultipleValuesMarker;
|
||||
return CPMultipleValuesMarker;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -693,8 +703,21 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
- (id)valueForKeyPath:(CPString)theKeyPath
|
||||
{
|
||||
var values = [[_controller selectedObjects] valueForKeyPath:theKeyPath];
|
||||
value = [self _controllerMarkerForValues:values];
|
||||
// If valueForKeyPath fails because of an undefined key, return CPNotApplicableMarker
|
||||
var value;
|
||||
|
||||
try
|
||||
{
|
||||
var values = [[_controller selectedObjects] valueForKeyPath:theKeyPath];
|
||||
value = [self _controllerMarkerForValues:values];
|
||||
}
|
||||
catch (ex)
|
||||
{
|
||||
if ([ex name] === CPUndefinedKeyException)
|
||||
value = CPNotApplicableMarker;
|
||||
else
|
||||
throw ex;
|
||||
}
|
||||
|
||||
[_cachedValues setObject:value forKey:theKeyPath];
|
||||
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 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:
|
||||
– popoverShouldClose:(CPPopover)aPopOver
|
||||
– popoverWillShow:(CPPopover)aPopOver
|
||||
– popoverDidShow:(CPPopover)aPopOver
|
||||
– popoverWillClose:(CPPopover)aPopOver
|
||||
– popoverDidClose:(CPPopover)aPopOver
|
||||
*/
|
||||
@implementation CPPopover : CPResponder
|
||||
{
|
||||
@outlet CPViewController _contentViewController @accessors(property=contentViewController);
|
||||
@outlet id _delegate @accessors(getter=delegate);
|
||||
|
||||
BOOL _animates @accessors(property=animates);
|
||||
BOOL _shown @accessors(getter=shown);
|
||||
int _appearance @accessors(property=appearance);
|
||||
int _behavior @accessors(getter=behavior);
|
||||
|
||||
BOOL _needsCompute;
|
||||
_CPAttachedWindow _attachedWindow;
|
||||
int _implementedDelegateMethods;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark INitialization
|
||||
|
||||
/*!
|
||||
Initialize the CPPopover witn default values
|
||||
|
||||
@returns anInitialized CPPopover
|
||||
*/
|
||||
- (CPPopover)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_animates = YES;
|
||||
_appearance = CPPopoverAppearanceMinimal;
|
||||
_behavior = CPPopoverBehaviorApplicationDefined;
|
||||
_needsCompute = YES;
|
||||
_shown = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Getters / Setters
|
||||
|
||||
/*!
|
||||
Returns the current rect of the popover
|
||||
|
||||
@return CPRect represeting the frame of the popover
|
||||
*/
|
||||
- (CPRect)positionningRect
|
||||
{
|
||||
if (!_attachedWindow || ![_attachedWindow isVisible])
|
||||
return nil;
|
||||
return [_attachedWindow frame];
|
||||
}
|
||||
|
||||
/*! Sets the frame of the popover
|
||||
@param aRect the desired frame
|
||||
*/
|
||||
- (void)setPositionningRect:(CPRect)aRect
|
||||
{
|
||||
if (!_attachedWindow || ![_attachedWindow isVisible])
|
||||
return;
|
||||
[_attachedWindow setFrame:aRect];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the size of the popover's view
|
||||
|
||||
@return CPSize represeting the size of the popover's view
|
||||
*/
|
||||
- (CPRect)contentSize
|
||||
{
|
||||
if (!_attachedWindow || ![_attachedWindow isVisible])
|
||||
return nil;
|
||||
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 YES if visible
|
||||
*/
|
||||
- (BOOL)shown
|
||||
{
|
||||
if (!_attachedWindow)
|
||||
return NO;
|
||||
return [_attachedWindow isVisible];
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the behaviour of the CPPopover. It can be
|
||||
- CPPopoverBehaviorTransient: the popover will be close if another control outside the popover become the responder
|
||||
- CPPopoverBehaviorApplicationDefined: (DEFAULT) the application is responsible for closing the popover
|
||||
|
||||
@param aBehaviour the desired behaviour
|
||||
*/
|
||||
- (void)setBehaviour:(int)aBehaviour
|
||||
{
|
||||
if (_behavior == aBehaviour)
|
||||
return;
|
||||
|
||||
_behavior = aBehaviour;
|
||||
_needsCompute = YES;
|
||||
}
|
||||
|
||||
|
||||
- (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 Positionning
|
||||
|
||||
/*!
|
||||
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: CPRectEdge representing the prefered positionning.
|
||||
*/
|
||||
- (void)showRelativeToRect:(CPRect)positioningRect ofView:(CPView)positioningView preferredEdge:(CPRectEdge)preferredEdge
|
||||
{
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_willShow_)
|
||||
[_delegate popoverWillShow:self];
|
||||
|
||||
if (!_contentViewController)
|
||||
[CPException raise:CPInternalInconsistencyException reason:@"contentViewController must not be nil"];
|
||||
|
||||
if (_needsCompute)
|
||||
{
|
||||
var styleMask = (_behavior == CPPopoverBehaviorTransient) ? CPClosableOnBlurWindowMask : nil;
|
||||
_attachedWindow = [[_CPAttachedWindow alloc] initWithContentRect:CPRectMakeZero() styleMask:styleMask];
|
||||
}
|
||||
|
||||
[_attachedWindow setAppearance:_appearance];
|
||||
[_attachedWindow setAnimates:_animates];
|
||||
[_attachedWindow setMovableByWindowBackground:NO];
|
||||
[_attachedWindow setFrame:[_attachedWindow frameRectForContentRect:[[_contentViewController view] frame]]];
|
||||
[_attachedWindow setContentView:[_contentViewController view]];
|
||||
|
||||
if (positioningRect)
|
||||
[_attachedWindow positionRelativeToRect:positioningRect preferredEdge:preferredEdge];
|
||||
else if (positioningView)
|
||||
[_attachedWindow positionRelativeToView:positioningView preferredEdge:preferredEdge];
|
||||
else
|
||||
[CPException raise:CPInvalidArgumentException reason:@"you must set positioningRect or positioningRect"];
|
||||
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_didShow_)
|
||||
[_delegate popoverDidShow:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Closes the popover
|
||||
*/
|
||||
- (void)close
|
||||
{
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_shouldClose_)
|
||||
if (![_delegate popoverShouldClose:self])
|
||||
return;
|
||||
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_willClose_)
|
||||
[_delegate popoverWillClose:self];
|
||||
|
||||
[_attachedWindow close];
|
||||
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_didClose_)
|
||||
[_delegate popoverDidClose:self];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Action
|
||||
|
||||
/*!
|
||||
Close the popover
|
||||
|
||||
@param aSender the sender of the action
|
||||
*/
|
||||
- (IBAction)performClose:(id)aSender
|
||||
{
|
||||
[self close];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPPopover (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_needsCompute = [aCoder decodeIntForKey:@"_needsCompute"];
|
||||
_appearance = [aCoder decodeIntForKey:@"_appearance"];
|
||||
_animates = [aCoder decodeBoolForKey:@"_animates"];
|
||||
_contentViewController = [aCoder decodeObjectForKey:@"_contentViewController"];
|
||||
[self setDelegate:[aCoder decodeObjectForKey:@"_delegate"]];
|
||||
[self setBehaviour:[aCoder decodeIntForKey:@"_behavior"]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeInt:_behavior forKey:@"_behavior"];
|
||||
[aCoder encodeInt:_appearance forKey:@"_appearance"];
|
||||
[aCoder encodeBool:_needsCompute forKey:@"_needsCompute"];
|
||||
[aCoder encodeObject:_contentViewController forKey:@"_contentViewController"];
|
||||
[aCoder encodeObject:_delegate forKey:@"_delegate"];
|
||||
[aCoder encodeObject:_animates forKey:@"_animates"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -187,7 +187,7 @@ var itemsContext = "items",
|
||||
[_slicesHolder addSubview:_dropLineView];
|
||||
|
||||
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPRuleEditorItemPBoardType,nil]];
|
||||
[_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:boundArrayContext];
|
||||
[_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
|
||||
}
|
||||
|
||||
/*! @endcond */
|
||||
@@ -408,7 +408,7 @@ var itemsContext = "items",
|
||||
if (![stringsFilename hasSuffix:@".strings"])
|
||||
stringsFilename = stringsFilename + @".strings";
|
||||
var path = [[CPBundle mainBundle] pathForResource:stringsFilename];
|
||||
if (path !=nil)
|
||||
if (path != nil)
|
||||
[_standardLocalizer loadContentOfURL:[CPURL URLWithString:path]];
|
||||
}
|
||||
}
|
||||
@@ -626,7 +626,7 @@ TODO: implement
|
||||
{
|
||||
var slice = _slices[count],
|
||||
rowIndex = [slice rowIndex],
|
||||
contains = [indexes containsIndex:rowIndex];
|
||||
contains = [indexes containsIndex:rowIndex],
|
||||
shouldSelect = (contains && !(extend && [slice _isSelected]));
|
||||
|
||||
if (contains)
|
||||
@@ -1153,8 +1153,8 @@ TODO: implement
|
||||
if (!_nestingModeDidChange)
|
||||
return _nestingMode;
|
||||
|
||||
var a = (_nestingMode == CPRuleEditorNestingModeCompound || _nestingMode == CPRuleEditorNestingModeSimple);
|
||||
var b = ([self rowTypeForRow:0] == CPRuleEditorRowTypeCompound);
|
||||
var a = (_nestingMode == CPRuleEditorNestingModeCompound || _nestingMode == CPRuleEditorNestingModeSimple),
|
||||
b = ([self rowTypeForRow:0] == CPRuleEditorRowTypeCompound);
|
||||
|
||||
if (a == b)
|
||||
return _nestingMode;
|
||||
@@ -1241,10 +1241,10 @@ TODO: implement
|
||||
|
||||
while (current_index != CPNotFound)
|
||||
{
|
||||
var parentIndex = [self parentRowForRow:current_index];
|
||||
var parentIndex = [self parentRowForRow:current_index],
|
||||
subrowsIndexes = [self subrowIndexesForRow:parentIndex];
|
||||
|
||||
var subrowsIndexes = [self subrowIndexesForRow:parentIndex];
|
||||
if ([subrowsIndexes count]==1)
|
||||
if ([subrowsIndexes count] == 1)
|
||||
{
|
||||
if (parentIndex != -1)
|
||||
return [CPIndexSet indexSetWithIndex:0];
|
||||
@@ -1432,7 +1432,7 @@ TODO: implement
|
||||
|
||||
- (void)_startObservingRowObjectsRecursively:(CPArray)rowObjects
|
||||
{
|
||||
[_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:boundArrayContext];
|
||||
[_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
|
||||
|
||||
var count = [rowObjects count];
|
||||
|
||||
@@ -1440,9 +1440,9 @@ TODO: implement
|
||||
{
|
||||
var rowObject = [rowObjects objectAtIndex:i];
|
||||
|
||||
[rowObject addObserver:self forKeyPath:_itemsKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:itemsContext];
|
||||
[rowObject addObserver:self forKeyPath:_valuesKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:valuesContext];
|
||||
[rowObject addObserver:self forKeyPath:_subrowsArrayKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:subrowsContext];
|
||||
[rowObject addObserver:self forKeyPath:_itemsKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:itemsContext];
|
||||
[rowObject addObserver:self forKeyPath:_valuesKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:valuesContext];
|
||||
[rowObject addObserver:self forKeyPath:_subrowsArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:subrowsContext];
|
||||
|
||||
var subrows = [self _subrowObjectsOfObject:rowObject];
|
||||
if ([subrows count] > 0)
|
||||
@@ -1604,13 +1604,14 @@ TODO: implement
|
||||
break;
|
||||
}
|
||||
|
||||
var replaceCount = (deltaCount == 0) ? maxCount : maxCount - minusCount;
|
||||
var startIndex = parentCacheIndex + changeStartIndex + 1;
|
||||
var replaceCount = (deltaCount == 0) ? maxCount : maxCount - minusCount,
|
||||
startIndex = parentCacheIndex + changeStartIndex + 1;
|
||||
|
||||
if (deltaCount <= 0)
|
||||
{
|
||||
var removeIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startIndex, replaceCount)];
|
||||
var removeSlices = [_slices objectsAtIndexes:removeIndexes];
|
||||
var removeIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startIndex, replaceCount)],
|
||||
removeSlices = [_slices objectsAtIndexes:removeIndexes];
|
||||
|
||||
[removeSlices makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
[_slices removeObjectsAtIndexes:removeIndexes];
|
||||
}
|
||||
@@ -1694,7 +1695,7 @@ TODO: implement
|
||||
|
||||
var boundRows = [_boundArrayOwner valueForKey:_boundArrayKeyPath];
|
||||
|
||||
[_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:boundArrayContext];
|
||||
[_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
|
||||
|
||||
if ([boundRows isKindOfClass:[CPArray class]] && [boundRows count] > 0)
|
||||
[_boundArrayOwner setValue:boundRows forKey:_boundArrayKeyPath];
|
||||
@@ -1890,7 +1891,7 @@ TODO: implement
|
||||
{
|
||||
var current_index = [indexes firstIndex];
|
||||
|
||||
while (current_index !=CPNotFound)
|
||||
while (current_index != CPNotFound)
|
||||
{
|
||||
var subindexes = [self subrowIndexesForRow:index];
|
||||
[self _updateSliceIndentationAtIndex:current_index toIndentation:indentation + 1 withIndexSet:subindexes];
|
||||
@@ -2152,7 +2153,7 @@ TODO: implement
|
||||
- (CPDragOperation)draggingUpdated:(id <CPDraggingInfo>)sender
|
||||
{
|
||||
var point = [self convertPoint:[sender draggingLocation] fromView:nil],
|
||||
y = point.y + _sliceHeight /2,
|
||||
y = point.y + _sliceHeight / 2,
|
||||
indexOfDropLine = FLOOR(y / _sliceHeight),
|
||||
numberOfRows = [self numberOfRows];
|
||||
|
||||
@@ -2198,11 +2199,10 @@ TODO: implement
|
||||
removeIndex;
|
||||
|
||||
var rowObjects = [_rowCache valueForKey:@"rowObject"],
|
||||
index = [_draggingRows lastIndex];
|
||||
|
||||
var parentRowIndex = [self parentRowForRow:index]; // first index of draggingrows
|
||||
var parentRowObject = (parentRowIndex == -1) ? _boundArrayOwner : [[self _rowCacheForIndex:parentRowIndex] rowObject];
|
||||
var insertIndex = _subviewIndexOfDropLine;
|
||||
index = [_draggingRows lastIndex],
|
||||
parentRowIndex = [self parentRowForRow:index], // first index of draggingrows
|
||||
parentRowObject = (parentRowIndex == -1) ? _boundArrayOwner : [[self _rowCacheForIndex:parentRowIndex] rowObject],
|
||||
insertIndex = _subviewIndexOfDropLine;
|
||||
|
||||
while (index != CPNotFound)
|
||||
{
|
||||
@@ -2570,9 +2570,9 @@ var dropSeparatorColor = [CPColor colorWithHexString:@"4886ca"];
|
||||
|
||||
- (int)valueType
|
||||
{
|
||||
var result = 0;
|
||||
var result = 0,
|
||||
isString = [self isKindOfClass:[CPString class]];
|
||||
|
||||
var isString = [self isKindOfClass:[CPString class]];
|
||||
if ( !isString )
|
||||
{
|
||||
var isView = [self isKindOfClass:[CPView class]];
|
||||
@@ -2593,4 +2593,4 @@ var dropSeparatorColor = [CPColor colorWithHexString:@"4886ca"];
|
||||
}
|
||||
|
||||
@end
|
||||
/*! @endcond */
|
||||
/*! @endcond */
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
places scroll bars on the side of the view to allow the user to scroll and see the entire
|
||||
contents of the view.
|
||||
*/
|
||||
|
||||
var TIMER_INTERVAL = 0.2,
|
||||
CPScrollViewDelegate_scrollViewWillScroll_ = 1 << 0,
|
||||
CPScrollViewDelegate_scrollViewDidScroll_ = 1 << 1;
|
||||
|
||||
|
||||
@implementation CPScrollView : CPView
|
||||
{
|
||||
CPClipView _contentView;
|
||||
@@ -41,6 +47,9 @@
|
||||
CPView _cornerView;
|
||||
CPView _bottomCornerView;
|
||||
|
||||
id _delegate;
|
||||
CPTimer _scrollTimer;
|
||||
|
||||
BOOL _hasVerticalScroller;
|
||||
BOOL _hasHorizontalScroller;
|
||||
BOOL _autohidesScrollers;
|
||||
@@ -48,7 +57,8 @@
|
||||
CPScroller _verticalScroller;
|
||||
CPScroller _horizontalScroller;
|
||||
|
||||
int _recursionCount;
|
||||
CPInteger _recursionCount;
|
||||
CPInteger _implementedDelegateMethods;
|
||||
|
||||
float _verticalLineScroll;
|
||||
float _verticalPageScroll;
|
||||
@@ -86,7 +96,6 @@
|
||||
_borderType = CPNoBorder;
|
||||
|
||||
_contentView = [[CPClipView alloc] initWithFrame:[self _insetBounds]];
|
||||
|
||||
[self addSubview:_contentView];
|
||||
|
||||
_headerClipView = [[CPClipView alloc] init];
|
||||
@@ -97,11 +106,37 @@
|
||||
|
||||
[self setHasVerticalScroller:YES];
|
||||
[self setHasHorizontalScroller:YES];
|
||||
|
||||
_delegate = nil;
|
||||
_scrollTimer = nil;
|
||||
_implementedDelegateMethods = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (aDelegate === _delegate)
|
||||
return;
|
||||
|
||||
_delegate = aDelegate;
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
if (_delegate === nil)
|
||||
return;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(scrollViewWillScroll:)])
|
||||
_implementedDelegateMethods |= CPScrollViewDelegate_scrollViewWillScroll_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(scrollViewDidScroll:)])
|
||||
_implementedDelegateMethods |= CPScrollViewDelegate_scrollViewDidScroll_;
|
||||
}
|
||||
// Calculating Layout
|
||||
|
||||
+ (CGSize)contentSizeForFrameSize:(CGSize)frameSize hasHorizontalScroller:(BOOL)hFlag hasVerticalScroller:(BOOL)vFlag borderType:(CPBorderType)borderType
|
||||
@@ -655,6 +690,8 @@
|
||||
default: contentBounds.origin.y = ROUND(value * (_CGRectGetHeight(documentFrame) - _CGRectGetHeight(contentBounds)));
|
||||
}
|
||||
|
||||
[self _sendDelegateMessages];
|
||||
|
||||
[_contentView scrollToPoint:contentBounds.origin];
|
||||
}
|
||||
|
||||
@@ -685,6 +722,8 @@
|
||||
default: contentBounds.origin.x = ROUND(value * (_CGRectGetWidth(documentFrame) - _CGRectGetWidth(contentBounds)));
|
||||
}
|
||||
|
||||
[self _sendDelegateMessages];
|
||||
|
||||
[_contentView scrollToPoint:contentBounds.origin];
|
||||
[_headerClipView scrollToPoint:CGPointMake(contentBounds.origin.x, 0.0)];
|
||||
}
|
||||
@@ -962,6 +1001,8 @@
|
||||
extraX = contentBounds.origin.x - constrainedOrigin.x,
|
||||
extraY = contentBounds.origin.y - constrainedOrigin.y;
|
||||
|
||||
[self _sendDelegateMessages];
|
||||
|
||||
[_contentView scrollToPoint:constrainedOrigin];
|
||||
[_headerClipView scrollToPoint:CGPointMake(constrainedOrigin.x, 0.0)];
|
||||
|
||||
@@ -1029,6 +1070,35 @@
|
||||
[_headerClipView scrollToPoint:CGPointMake(contentBounds.origin.x, 0)];
|
||||
}
|
||||
|
||||
- (void)_sendDelegateMessages
|
||||
{
|
||||
if (_implementedDelegateMethods == 0)
|
||||
return;
|
||||
|
||||
if (!_scrollTimer)
|
||||
{
|
||||
[self _scrollViewWillScroll];
|
||||
_scrollTimer = [CPTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(_scrollViewDidScroll) userInfo:nil repeats:YES];
|
||||
}
|
||||
else
|
||||
[_scrollTimer setFireDate:[CPDate dateWithTimeIntervalSinceNow:TIMER_INTERVAL]];
|
||||
}
|
||||
|
||||
- (void)_scrollViewWillScroll
|
||||
{
|
||||
if (_implementedDelegateMethods & CPScrollViewDelegate_scrollViewWillScroll_)
|
||||
[_delegate scrollViewWillScroll:self];
|
||||
}
|
||||
|
||||
- (void)_scrollViewDidScroll
|
||||
{
|
||||
[_scrollTimer invalidate];
|
||||
_scrollTimer = nil;
|
||||
|
||||
if (_implementedDelegateMethods & CPScrollViewDelegate_scrollViewDidScroll_)
|
||||
[_delegate scrollViewDidScroll:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPScrollViewContentViewKey = @"CPScrollViewContentView",
|
||||
@@ -1079,6 +1149,9 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
|
||||
_cornerView = [aCoder decodeObjectForKey:CPScrollViewCornerViewKey];
|
||||
_bottomCornerView = [aCoder decodeObjectForKey:CPScrollViewBottomCornerViewKey];
|
||||
|
||||
_delegate = nil;
|
||||
_scrollTimer = nil;
|
||||
_implementedDelegateMethods = 0;
|
||||
// Due to the anything goes nature of decoding, our subviews may not exist yet, so layout at the end of the run loop when we're sure everything is in a correct state.
|
||||
[[CPRunLoop currentRunLoop] performSelector:@selector(_updateCornerAndHeaderView) target:self argument:_contentView order:0 modes:[CPDefaultRunLoopMode]];
|
||||
}
|
||||
|
||||
@@ -457,8 +457,9 @@ NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
|
||||
}
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackKnob:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
if (type === CPLeftMouseDragged)
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -45,6 +45,7 @@ CPSoundPlayBackStatePause = 2;
|
||||
CPString _name @accessors(property=name);
|
||||
id _delegate @accessors(property=delegate);
|
||||
|
||||
BOOL _playRequestBeforeLoad;
|
||||
HTMLAudioElement _audioTag;
|
||||
int _loadStatus;
|
||||
int _playBackStatus;
|
||||
@@ -61,6 +62,7 @@ CPSoundPlayBackStatePause = 2;
|
||||
_loops = NO;
|
||||
_audioTag = document.createElement("audio");
|
||||
_audioTag.preload = YES;
|
||||
_playRequestBeforeLoad = NO;
|
||||
|
||||
_audioTag.addEventListener("canplay", function()
|
||||
{
|
||||
@@ -138,6 +140,12 @@ CPSoundPlayBackStatePause = 2;
|
||||
- (void)_soundDidload
|
||||
{
|
||||
_loadStatus = CPSoundLoadStateCanBePlayed;
|
||||
|
||||
if (_playRequestBeforeLoad)
|
||||
{
|
||||
_playRequestBeforeLoad = NO;
|
||||
[self play];
|
||||
}
|
||||
}
|
||||
|
||||
/*! @ignore
|
||||
@@ -167,6 +175,12 @@ CPSoundPlayBackStatePause = 2;
|
||||
*/
|
||||
- (BOOL)play
|
||||
{
|
||||
if (_loadStatus === CPSoundLoadStateLoading)
|
||||
{
|
||||
_playRequestBeforeLoad = YES;
|
||||
return YES;
|
||||
}
|
||||
|
||||
if ((_loadStatus !== CPSoundLoadStateCanBePlayed)
|
||||
|| (_playBackStatus === CPSoundPlayBackStatePlay))
|
||||
return NO;
|
||||
|
||||
@@ -3545,16 +3545,16 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
}
|
||||
|
||||
var exposedRows = [self _unboundedRowsInRect:aRect],
|
||||
firstRow = FLOOR(exposedRows.location / colorCount) * colorCount,
|
||||
lastRow = CPMaxRange(exposedRows),
|
||||
colorIndex = 0,
|
||||
groupRowRects = [],
|
||||
row = exposedRows.location;
|
||||
groupRowRects = [];
|
||||
|
||||
//loop through each color so we only draw once for each color
|
||||
while (colorIndex < colorCount)
|
||||
{
|
||||
CGContextBeginPath(context);
|
||||
for (var row = colorIndex; row <= lastRow; row += colorCount)
|
||||
for (var row = firstRow + colorIndex; row <= lastRow; row += colorCount)
|
||||
{
|
||||
// if it's not a group row draw it otherwise we draw it later
|
||||
if (![_groupRows containsIndex:row])
|
||||
|
||||
@@ -169,8 +169,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[_CGInsetMakeZero(), _CGInsetMake(2.0, 2.0, 2.0, 2.0), [CPNull null]]
|
||||
forKeys:[@"bezel-inset", @"content-inset", @"bezel-color"]];
|
||||
return [CPDictionary dictionaryWithObjects:[_CGInsetMakeZero(), _CGInsetMake(2.0, 2.0, 2.0, 2.0), _CGInsetMake(3.0, 3.0, 3.0, 3.0), [CPNull null]]
|
||||
forKeys:[@"bezel-inset", @"content-inset", @"border-inset", @"bezel-color"]];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
@@ -507,31 +507,32 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
}
|
||||
|
||||
var contentRect = [self contentRectForBounds:[self bounds]],
|
||||
verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"];
|
||||
verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"],
|
||||
lineHeight = [font defaultLineHeightForFont];
|
||||
|
||||
switch (verticalAlign)
|
||||
{
|
||||
case CPTopVerticalTextAlignment:
|
||||
var topPoint = (_CGRectGetMinY(contentRect) + 1) + "px"; // for the same reason we have a -1 for the left, we also have a + 1 here
|
||||
var topPoint = _CGRectGetMinY(contentRect) + "px"; // for the same reason we have a -1 for the left, we also have a + 1 here
|
||||
break;
|
||||
|
||||
case CPCenterVerticalTextAlignment:
|
||||
var topPoint = (_CGRectGetMidY(contentRect) - (font._lineHeight / 2) + 1) + "px";
|
||||
var topPoint = (_CGRectGetMidY(contentRect) - (lineHeight / 2)) + "px";
|
||||
break;
|
||||
|
||||
case CPBottomVerticalTextAlignment:
|
||||
var topPoint = (_CGRectGetMaxY(contentRect) - font._lineHeight) + "px";
|
||||
var topPoint = (_CGRectGetMaxY(contentRect) - lineHeight) + "px";
|
||||
break;
|
||||
|
||||
default:
|
||||
var topPoint = (_CGRectGetMinY(contentRect) + 1) + "px";
|
||||
var topPoint = _CGRectGetMinY(contentRect) + "px";
|
||||
break;
|
||||
}
|
||||
|
||||
element.style.top = topPoint;
|
||||
element.style.left = (_CGRectGetMinX(contentRect) - 1) + "px"; // why -1?
|
||||
element.style.width = _CGRectGetWidth(contentRect) + "px";
|
||||
element.style.height = font._lineHeight + "px"; // private ivar for the line height of the DOM text at this particular size
|
||||
element.style.height = lineHeight + "px";
|
||||
|
||||
_DOMElement.appendChild(element);
|
||||
|
||||
@@ -625,15 +626,11 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
#endif
|
||||
|
||||
// post CPControlTextDidEndEditingNotification
|
||||
if (_isEditing)
|
||||
{
|
||||
_isEditing = NO;
|
||||
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
|
||||
_isEditing = NO;
|
||||
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
|
||||
|
||||
if ([self sendsActionOnEndEditing])
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
if ([self sendsActionOnEndEditing])
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
[self textDidBlur:[CPNotification notificationWithName:CPTextFieldDidBlurNotification object:self userInfo:nil]];
|
||||
|
||||
@@ -776,14 +773,21 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
{
|
||||
if ([self _valueIsValid:_stringValue])
|
||||
{
|
||||
// If _isEditing == YES then the target action can also be called via
|
||||
// resignFirstResponder, and it is possible that the target action
|
||||
// itself will change this textfield's responder status, so start by
|
||||
// setting the _isEditing flag to NO to prevent the target action being
|
||||
// called twice (once below and once from resignFirstResponder).
|
||||
if (_isEditing)
|
||||
{
|
||||
_isEditing = NO;
|
||||
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
// If there is no target action, or the sendAction call returns
|
||||
// success.
|
||||
if (![self action] || [self sendAction:[self action] to:[self target]])
|
||||
{
|
||||
if (_isEditing)
|
||||
{
|
||||
_isEditing = NO;
|
||||
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
[self selectAll:nil];
|
||||
}
|
||||
}
|
||||
@@ -1017,7 +1021,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
maxSize = [self currentValueForThemeAttribute:@"max-size"],
|
||||
lineBreakMode = [self lineBreakMode],
|
||||
text = (_stringValue || @" "),
|
||||
textSize = _CGSizeMakeCopy(frameSize),
|
||||
textSize = CGSizeMakeCopy(frameSize),
|
||||
font = [self currentValueForThemeAttribute:@"font"];
|
||||
|
||||
textSize.width -= contentInset.left + contentInset.right;
|
||||
@@ -1287,10 +1291,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
- (CGRect)contentRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
|
||||
|
||||
if (!contentInset)
|
||||
return bounds;
|
||||
var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"],
|
||||
contentInset = [self currentValueForThemeAttribute:@"content-inset"];
|
||||
|
||||
bounds.origin.x += contentInset.left;
|
||||
bounds.origin.y += contentInset.top;
|
||||
@@ -1315,6 +1317,14 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
return bounds;
|
||||
}
|
||||
|
||||
- (CGInset)borderInset
|
||||
{
|
||||
var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"],
|
||||
borderInset = [self currentValueForThemeAttribute:@"border-inset"];
|
||||
|
||||
return CGInsetUnion(bezelInset, borderInset);
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
if (aName === "bezel-view")
|
||||
|
||||
@@ -93,65 +93,69 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
return "tokenfield";
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)frame
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame])
|
||||
{
|
||||
_selectedRange = CPMakeRange(0, 0);
|
||||
|
||||
_tokenScrollView = [[CPScrollView alloc] initWithFrame:CGRectMakeZero()];
|
||||
[_tokenScrollView setHasHorizontalScroller:NO];
|
||||
[_tokenScrollView setHasVerticalScroller:NO];
|
||||
[_tokenScrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
var contentView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
[contentView setAutoresizingMask:CPViewWidthSizable];
|
||||
[_tokenScrollView setDocumentView:contentView];
|
||||
|
||||
[self addSubview:_tokenScrollView];
|
||||
|
||||
_tokenIndex = 0;
|
||||
|
||||
_cachedCompletions = [];
|
||||
_completionDelay = [CPTokenField defaultCompletionDelay];
|
||||
|
||||
_tokenizingCharacterSet = [[self class] defaultTokenizingCharacterSet];
|
||||
|
||||
_autocompleteContainer = [[CPView alloc] initWithFrame:CPRectMake(0.0, 0.0, frame.size.width, 92.0)];
|
||||
[_autocompleteContainer setBackgroundColor:[_CPMenuWindow backgroundColorForBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle]];
|
||||
|
||||
_autocompleteScrollView = [[CPScrollView alloc] initWithFrame:CPRectMake(1.0, 1.0, frame.size.width - 2.0, 90.0)];
|
||||
[_autocompleteScrollView setAutohidesScrollers:YES];
|
||||
[_autocompleteScrollView setHasHorizontalScroller:NO];
|
||||
[_autocompleteContainer addSubview:_autocompleteScrollView];
|
||||
|
||||
_autocompleteView = [[CPTableView alloc] initWithFrame:CPRectMakeZero()];
|
||||
|
||||
var tableColumn = [[CPTableColumn alloc] initWithIdentifier:CPTokenFieldTableColumnIdentifier];
|
||||
[tableColumn setResizingMask:CPTableColumnAutoresizingMask];
|
||||
[_autocompleteView addTableColumn:tableColumn];
|
||||
|
||||
[_autocompleteView setDataSource:self];
|
||||
[_autocompleteView setDelegate:self];
|
||||
[_autocompleteView setAllowsMultipleSelection:NO];
|
||||
[_autocompleteView setHeaderView:nil];
|
||||
[_autocompleteView setCornerView:nil];
|
||||
[_autocompleteView setRowHeight:30.0];
|
||||
[_autocompleteView setGridStyleMask:CPTableViewSolidHorizontalGridLineMask];
|
||||
[_autocompleteView setBackgroundColor:[CPColor clearColor]];
|
||||
[_autocompleteView setGridColor:[CPColor colorWithRed:242.0 / 255.0 green:243.0 / 255.0 blue:245.0 / 255.0 alpha:1.0]];
|
||||
|
||||
[_autocompleteScrollView setDocumentView:_autocompleteView];
|
||||
|
||||
[self setBezeled:YES];
|
||||
|
||||
[self _init];
|
||||
|
||||
[self setObjectValue:[]];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
var frame = [self frame];
|
||||
|
||||
_tokenScrollView = [[CPScrollView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
[_tokenScrollView setHasHorizontalScroller:NO];
|
||||
[_tokenScrollView setHasVerticalScroller:NO];
|
||||
[_tokenScrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
var contentView = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
[contentView setAutoresizingMask:CPViewWidthSizable];
|
||||
[_tokenScrollView setDocumentView:contentView];
|
||||
|
||||
[self addSubview:_tokenScrollView];
|
||||
|
||||
_cachedCompletions = [];
|
||||
|
||||
_autocompleteContainer = [[CPView alloc] initWithFrame:_CGRectMake(0.0, 0.0, frame.size.width, 92.0)];
|
||||
[_autocompleteContainer setBackgroundColor:[_CPMenuWindow backgroundColorForBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle]];
|
||||
|
||||
_autocompleteScrollView = [[CPScrollView alloc] initWithFrame:_CGRectMake(1.0, 1.0, frame.size.width - 2.0, 90.0)];
|
||||
[_autocompleteScrollView setAutohidesScrollers:YES];
|
||||
[_autocompleteScrollView setHasHorizontalScroller:NO];
|
||||
[_autocompleteContainer addSubview:_autocompleteScrollView];
|
||||
|
||||
_autocompleteView = [[CPTableView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
var tableColumn = [[CPTableColumn alloc] initWithIdentifier:CPTokenFieldTableColumnIdentifier];
|
||||
[tableColumn setResizingMask:CPTableColumnAutoresizingMask];
|
||||
[_autocompleteView addTableColumn:tableColumn];
|
||||
|
||||
[_autocompleteView setDataSource:self];
|
||||
[_autocompleteView setDelegate:self];
|
||||
[_autocompleteView setAllowsMultipleSelection:NO];
|
||||
[_autocompleteView setHeaderView:nil];
|
||||
[_autocompleteView setCornerView:nil];
|
||||
[_autocompleteView setRowHeight:30.0];
|
||||
[_autocompleteView setGridStyleMask:CPTableViewSolidHorizontalGridLineMask];
|
||||
[_autocompleteView setBackgroundColor:[CPColor clearColor]];
|
||||
[_autocompleteView setGridColor:[CPColor colorWithRed:242.0 / 255.0 green:243.0 / 255.0 blue:245.0 / 255.0 alpha:1.0]];
|
||||
|
||||
[_autocompleteScrollView setDocumentView:_autocompleteView];
|
||||
}
|
||||
|
||||
// ===============
|
||||
// = CONVENIENCE =
|
||||
// ===============
|
||||
@@ -159,12 +163,16 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
{
|
||||
var indexOfSelectedItem = 0;
|
||||
|
||||
_cachedCompletions = [self tokenField:self completionsForSubstring:[self _inputElement].value indexOfToken:_tokenIndex indexOfSelectedItem:indexOfSelectedItem];
|
||||
#if PLATFORM(DOM)
|
||||
_cachedCompletions = [self tokenField:self completionsForSubstring:[self _inputElement].value indexOfToken:0 indexOfSelectedItem:indexOfSelectedItem];
|
||||
#endif
|
||||
|
||||
[_autocompleteView selectRowIndexes:[CPIndexSet indexSetWithIndex:indexOfSelectedItem] byExtendingSelection:NO];
|
||||
[_autocompleteView reloadData];
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
- (void)_autocompleteWithDOMEvent:(JSObject)DOMEvent
|
||||
{
|
||||
if (!_cachedCompletions || ![self hasThemeState:CPThemeStateAutoCompleting])
|
||||
@@ -202,10 +210,25 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
if (shouldRemoveLastObject)
|
||||
[objectValue removeObjectAtIndex:_selectedRange.location];
|
||||
|
||||
[objectValue insertObject:token atIndex:_selectedRange.location];
|
||||
// Give the delegate a chance to confirm, replace or add to the list of tokens being added.
|
||||
var delegateApprovedObjects = [self tokenField:self shouldAddObjects:[CPArray arrayWithObject:token] atIndex:_selectedRange.location],
|
||||
delegateApprovedObjectsCount = [delegateApprovedObjects count];
|
||||
if (delegateApprovedObjects)
|
||||
{
|
||||
for (var i = 0; i < delegateApprovedObjectsCount; i++)
|
||||
{
|
||||
[objectValue insertObject:[delegateApprovedObjects objectAtIndex:i] atIndex:_selectedRange.location + i];
|
||||
}
|
||||
}
|
||||
|
||||
// Put the cursor after the last inserted token.
|
||||
var location = _selectedRange.location;
|
||||
|
||||
[self setObjectValue:objectValue];
|
||||
_selectedRange = CPMakeRange(location + 1, 0);
|
||||
|
||||
if (delegateApprovedObjectsCount)
|
||||
location += delegateApprovedObjectsCount;
|
||||
_selectedRange = CPMakeRange(location, 0);
|
||||
|
||||
[self _inputElement].value = @"";
|
||||
[self setNeedsLayout];
|
||||
@@ -218,6 +241,8 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
[self _autocompleteWithDOMEvent:nil];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (void)_selectToken:(_CPTokenFieldToken)token byExtendingSelection:(BOOL)extend
|
||||
{
|
||||
var indexOfToken = [[self _tokens] indexOfObject:token];
|
||||
@@ -368,10 +393,10 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
|
||||
[self unsetThemeState:CPThemeStateEditing];
|
||||
|
||||
[self _autocomplete];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
[self _autocomplete];
|
||||
|
||||
var element = [self _inputElement];
|
||||
|
||||
CPTokenFieldInputResigning = YES;
|
||||
@@ -654,7 +679,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
rowRect = [autocompleteView rectOfRow:index],
|
||||
owner = CPTokenFieldInputOwner;
|
||||
|
||||
if (rowRect && !CPRectContainsRect([clipView bounds], rowRect))
|
||||
if (rowRect && !CGRectContainsRect([clipView bounds], rowRect))
|
||||
[clipView scrollToPoint:[autocompleteView rectOfRow:index].origin];
|
||||
|
||||
if (aDOMEvent.keyCode === CPReturnKeyCode || aDOMEvent.keyCode === CPTabKeyCode)
|
||||
@@ -744,7 +769,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
else if (aDOMEvent.keyCode === CPDeleteKeyCode)
|
||||
{
|
||||
// Highlight the previous token if backspace was pressed in an empty input element or re-show the completions view
|
||||
if (CPTokenFieldDOMInputElement.value == @"")
|
||||
if (CPTokenFieldDOMInputElement.value.length === 0)
|
||||
{
|
||||
[self _hideCompletions];
|
||||
|
||||
@@ -864,6 +889,15 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
}
|
||||
#endif
|
||||
|
||||
// In the context of a token field, it is empty when there are no tokens
|
||||
- (void)_updatePlaceholderState
|
||||
{
|
||||
if (([[self _tokens] count] === 0) && ![self hasThemeState:CPThemeStateEditing])
|
||||
[self setThemeState:CPTextFieldStatePlaceholder];
|
||||
else
|
||||
[self unsetThemeState:CPTextFieldStatePlaceholder];
|
||||
}
|
||||
|
||||
// - (void)setTokenStyle: (NSTokenStyle) style;
|
||||
// - (NSTokenStyle)tokenStyle;
|
||||
//
|
||||
@@ -946,9 +980,9 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
// Manually sizeToFit because CPTableView's sizeToFit doesn't work properly
|
||||
[_autocompleteContainer setHidden:NO];
|
||||
var frameOrigin = [self convertPoint:[self bounds].origin toView:[_autocompleteContainer superview]];
|
||||
[_autocompleteContainer setFrameOrigin:CPPointMake(frameOrigin.x, frameOrigin.y + frame.size.height)];
|
||||
[_autocompleteContainer setFrameSize:CPSizeMake(CPRectGetWidth([self bounds]), 92.0)];
|
||||
[_autocompleteScrollView setFrameSize:CPSizeMake([_autocompleteContainer frame].size.width - 2.0, 90.0)];
|
||||
[_autocompleteContainer setFrameOrigin:_CGPointMake(frameOrigin.x, frameOrigin.y + frame.size.height)];
|
||||
[_autocompleteContainer setFrameSize:_CGSizeMake(_CGRectGetWidth([self bounds]), 92.0)];
|
||||
[_autocompleteScrollView setFrameSize:_CGSizeMake([_autocompleteContainer frame].size.width - 2.0, 90.0)];
|
||||
}
|
||||
else
|
||||
[_autocompleteContainer setHidden:YES];
|
||||
@@ -958,11 +992,11 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
return;
|
||||
|
||||
// Move each token into the right position.
|
||||
var contentRect = CGRectMakeCopy([contentView bounds]),
|
||||
var contentRect = _CGRectMakeCopy([contentView bounds]),
|
||||
contentOrigin = contentRect.origin,
|
||||
contentSize = contentRect.size,
|
||||
offset = CPPointMake(contentOrigin.x, contentOrigin.y),
|
||||
spaceBetweenTokens = CPSizeMake(2.0, 2.0),
|
||||
offset = _CGPointMake(contentOrigin.x, contentOrigin.y),
|
||||
spaceBetweenTokens = _CGSizeMake(2.0, 2.0),
|
||||
isEditing = [[self window] firstResponder] == self,
|
||||
tokenToken = [_CPTokenFieldToken new];
|
||||
|
||||
@@ -973,7 +1007,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
|
||||
var fitAndFrame = function(width, height)
|
||||
{
|
||||
var r = CGRectMake(0, 0, width, height);
|
||||
var r = _CGRectMake(0, 0, width, height);
|
||||
|
||||
if (offset.x + width >= contentSize.width && offset.x > contentOrigin.x)
|
||||
{
|
||||
@@ -986,7 +1020,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
|
||||
// Make sure the frame fits.
|
||||
if (CGRectGetHeight([contentView bounds]) < offset.y + height)
|
||||
[contentView setFrame:CGRectMake(0, 0, CGRectGetWidth([_tokenScrollView bounds]), offset.y + height)];
|
||||
[contentView setFrame:_CGRectMake(0, 0, CGRectGetWidth([_tokenScrollView bounds]), offset.y + height)];
|
||||
|
||||
offset.x += width + spaceBetweenTokens.width;
|
||||
|
||||
@@ -995,6 +1029,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
|
||||
var placeEditor = function(useRemainingWidth)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var element = [self _inputElement],
|
||||
textWidth = 1;
|
||||
|
||||
@@ -1004,6 +1039,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
// without clipping. Since different fonts might have different sizes of "X" this
|
||||
// solution is not ideal, but it works.
|
||||
textWidth = [(element.value || @"") + "X" sizeWithFont:[self font]].width;
|
||||
|
||||
if (useRemainingWidth)
|
||||
textWidth = MAX(contentSize.width - offset.x - 1, textWidth);
|
||||
}
|
||||
@@ -1018,6 +1054,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
// When editing, always scroll to the cursor.
|
||||
if (_selectedRange.length == 0)
|
||||
[[_tokenScrollView documentView] scrollRectToVisible:inputFrame];
|
||||
#endif
|
||||
}
|
||||
|
||||
for (var i = 0, count = [tokens count]; i < count; i++)
|
||||
@@ -1044,6 +1081,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
if (isEditing && CPMaxRange(_selectedRange) >= [tokens count])
|
||||
placeEditor(true);
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
// Hide the editor if there are selected tokens, but still keep it active
|
||||
// so we can continue using our standard keyboard handling events.
|
||||
if (isEditing && _selectedRange.length)
|
||||
@@ -1051,24 +1089,27 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
[self _inputElement].style.left = "-10000px";
|
||||
[self _inputElement].focus();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Trim off any excess height downwards.
|
||||
if (CGRectGetHeight([contentView bounds]) > offset.y + tokenHeight)
|
||||
[contentView setFrame:CGRectMake(0, 0, CGRectGetWidth([_tokenScrollView bounds]), offset.y + tokenHeight)];
|
||||
[contentView setFrame:_CGRectMake(0, 0, CGRectGetWidth([_tokenScrollView bounds]), offset.y + tokenHeight)];
|
||||
|
||||
if (_shouldScrollTo !== CPScrollDestinationNone)
|
||||
{
|
||||
// Only carry out the scroll if the cursor isn't visible.
|
||||
if (!(isEditing && _selectedRange.length == 0))
|
||||
{
|
||||
|
||||
var scrollToToken = _shouldScrollTo;
|
||||
|
||||
if (scrollToToken === CPScrollDestinationLeft)
|
||||
scrollToToken = tokens[_selectedRange.location]
|
||||
else if (scrollToToken === CPScrollDestinationRight)
|
||||
scrollToToken = tokens[MAX(0, CPMaxRange(_selectedRange) - 1)];
|
||||
|
||||
[self _scrollTokenViewToVisible:scrollToToken];
|
||||
}
|
||||
|
||||
_shouldScrollTo = CPScrollDestinationNone;
|
||||
}
|
||||
}
|
||||
@@ -1123,7 +1164,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
{
|
||||
if ([[self delegate] respondsToSelector:@selector(tokenField:completionsForSubstring:indexOfToken:indexOfSelectedItem:)])
|
||||
{
|
||||
return [[self delegate] tokenField:tokenField completionsForSubstring:substring indexOfToken:_tokenIndex indexOfSelectedItem:selectedIndex];
|
||||
return [[self delegate] tokenField:tokenField completionsForSubstring:substring indexOfToken:tokenIndex indexOfSelectedItem:selectedIndex];
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -1149,7 +1190,19 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
// // return an array of represented objects you want to add.
|
||||
// // If you want to reject the add, return an empty array.
|
||||
// // returning nil will cause an error.
|
||||
// - (NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index;
|
||||
- (CPArray)tokenField:(CPTokenField)tokenField shouldAddObjects:(CPArray)tokens atIndex:(int)index
|
||||
{
|
||||
var delegate = [self delegate];
|
||||
if ([delegate respondsToSelector:@selector(tokenField:shouldAddObjects:atIndex:)])
|
||||
{
|
||||
var approvedObjects = [delegate tokenField:tokenField shouldAddObjects:tokens atIndex:index];
|
||||
if (approvedObjects !== nil)
|
||||
return approvedObjects;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
//
|
||||
// // If you return nil or don't implement these delegate methods, we will assume
|
||||
// // editing string = display string = represented object
|
||||
@@ -1184,11 +1237,11 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
return "tokenfield-token";
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)frame
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame])
|
||||
{
|
||||
_deleteButton = [[_CPTokenFieldTokenCloseButton alloc] initWithFrame:CPRectMakeZero()];
|
||||
_deleteButton = [[_CPTokenFieldTokenCloseButton alloc] initWithFrame:_CGRectMakeZero()];
|
||||
[self addSubview:_deleteButton];
|
||||
|
||||
[self setEditable:NO];
|
||||
@@ -1249,7 +1302,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
buttonOffset = [_deleteButton currentValueForThemeAttribute:@"offset"],
|
||||
buttonSize = [_deleteButton currentValueForThemeAttribute:@"min-size"];
|
||||
|
||||
[_deleteButton setFrame:CPRectMake(CPRectGetMaxX(frame) - buttonOffset.x, CPRectGetMinY(frame) + buttonOffset.y, buttonSize.width, buttonSize.height)];
|
||||
[_deleteButton setFrame:_CGRectMake(_CGRectGetMaxX(frame) - buttonOffset.x, _CGRectGetMinY(frame) + buttonOffset.y, buttonSize.width, buttonSize.height)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1292,3 +1345,37 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPTokenFieldTokenizingCharacterSetKey = "CPTokenFieldTokenizingCharacterSetKey",
|
||||
CPTokenFieldCompletionDelayKey = "CPTokenFieldCompletionDelay";
|
||||
|
||||
@implementation CPTokenField (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_tokenizingCharacterSet = [aCoder decodeObjectForKey:CPTokenFieldTokenizingCharacterSetKey] || [[self class] defaultTokenizingCharacterSet];
|
||||
_completionDelay = [aCoder decodeDoubleForKey:CPTokenFieldCompletionDelayKey] || [[self class] defaultCompletionDelay];
|
||||
|
||||
[self _init];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeInt:_tokenizingCharacterSet forKey:CPTokenFieldTokenizingCharacterSetKey];
|
||||
[aCoder encodeDouble:_completionDelay forKey:CPTokenFieldCompletionDelayKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1015,7 +1015,7 @@ var TOP_MARGIN = 5.0,
|
||||
_labelSize = [_labelField frame].size;
|
||||
|
||||
_minSize = CGSizeMake(MAX(_labelSize.width, minSize.width), _labelSize.height + minSize.height + LABEL_MARGIN + TOP_MARGIN);
|
||||
_maxSize = CGSizeMake(MIN(_labelSize.width, maxSize.width), 100000000.0);
|
||||
_maxSize = CGSizeMake(MAX(_labelSize.width, maxSize.width), 100000000.0);
|
||||
|
||||
[_toolbar tile];
|
||||
}
|
||||
|
||||
@@ -111,7 +111,8 @@ CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
|
||||
view = [self _targetView:dictionary],
|
||||
startFrame = [self _startFrame:dictionary],
|
||||
endFrame = [self _endFrame:dictionary],
|
||||
differenceFrame = _CGRectMakeZero();
|
||||
differenceFrame = _CGRectMakeZero(),
|
||||
value = [super currentValue];
|
||||
|
||||
differenceFrame.origin.x = endFrame.origin.x - startFrame.origin.x;
|
||||
differenceFrame.origin.y = endFrame.origin.y - startFrame.origin.y;
|
||||
@@ -119,19 +120,19 @@ CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
|
||||
differenceFrame.size.height = endFrame.size.height - startFrame.size.height;
|
||||
|
||||
var intermediateFrame = _CGRectMakeZero();
|
||||
intermediateFrame.origin.x = startFrame.origin.x + differenceFrame.origin.x * progress;
|
||||
intermediateFrame.origin.y = startFrame.origin.y + differenceFrame.origin.y * progress;
|
||||
intermediateFrame.size.width = startFrame.size.width + differenceFrame.size.width * progress;
|
||||
intermediateFrame.size.height = startFrame.size.height + differenceFrame.size.height * progress;
|
||||
intermediateFrame.origin.x = startFrame.origin.x + differenceFrame.origin.x * value;
|
||||
intermediateFrame.origin.y = startFrame.origin.y + differenceFrame.origin.y * value;
|
||||
intermediateFrame.size.width = startFrame.size.width + differenceFrame.size.width * value;
|
||||
intermediateFrame.size.height = startFrame.size.height + differenceFrame.size.height * value;
|
||||
|
||||
[view setFrame:intermediateFrame];
|
||||
|
||||
// Update the view's alpha value
|
||||
var effect = [self _effect:dictionary];
|
||||
if (effect === CPViewAnimationFadeInEffect)
|
||||
[view setAlphaValue:1.0 * progress];
|
||||
[view setAlphaValue:1.0 * value];
|
||||
else if (effect === CPViewAnimationFadeOutEffect)
|
||||
[view setAlphaValue:1.0 + ( 0.0 - 1.0 ) * progress];
|
||||
[view setAlphaValue:1.0 + ( 0.0 - 1.0 ) * value];
|
||||
|
||||
if (progress === 1.0)
|
||||
[self _targetView:view setHidden:_CGRectIsNull(endFrame) || [view alphaValue] === 0.0];
|
||||
|
||||
@@ -426,6 +426,7 @@ CPTexturedBackgroundWindowMask
|
||||
|
||||
// Create a generic content view.
|
||||
[self setContentView:[[CPView alloc] initWithFrame:CGRectMakeZero()]];
|
||||
[self setInitialFirstResponder:[self contentView]];
|
||||
|
||||
_firstResponder = self;
|
||||
|
||||
@@ -480,6 +481,7 @@ CPTexturedBackgroundWindowMask
|
||||
[self close];
|
||||
|
||||
_platformWindow = aPlatformWindow;
|
||||
[_platformWindow _setTitle:_title window:self];
|
||||
|
||||
if (wasVisible)
|
||||
[self orderFront:self];
|
||||
@@ -949,6 +951,12 @@ CPTexturedBackgroundWindowMask
|
||||
|
||||
var bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(_frame), CGRectGetHeight(_frame));
|
||||
|
||||
// During init the initial first responder is set to the contentView
|
||||
// if it hasn't changed in the mean time we need to update that reference
|
||||
// to the new contentView
|
||||
if ([self initialFirstResponder] === _contentView)
|
||||
[self setInitialFirstResponder:aView];
|
||||
|
||||
_contentView = aView;
|
||||
[_contentView setFrame:[self contentRectForFrameRect:bounds]];
|
||||
|
||||
@@ -1273,17 +1281,17 @@ CPTexturedBackgroundWindowMask
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (id)initialFirstResponder
|
||||
- (CPView)initialFirstResponder
|
||||
{
|
||||
return _initialFirstResponder;
|
||||
}
|
||||
|
||||
- (void)setInitialFirstResponder:(id)aResponder
|
||||
- (void)setInitialFirstResponder:(CPView)aView
|
||||
{
|
||||
_initialFirstResponder = aResponder;
|
||||
_initialFirstResponder = aView;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1359,6 +1367,7 @@ CPTexturedBackgroundWindowMask
|
||||
_title = aTitle;
|
||||
|
||||
[_windowView setTitle:aTitle];
|
||||
[_platformWindow _setTitle:_title window:self];
|
||||
|
||||
[self _synchronizeMenuBarTitleWithWindowTitle];
|
||||
}
|
||||
@@ -2485,30 +2494,70 @@ CPTexturedBackgroundWindowMask
|
||||
|
||||
- (void)selectNextKeyView:(id)sender
|
||||
{
|
||||
if (_keyViewLoopIsDirty && [self autorecalculatesKeyViewLoop])
|
||||
[self recalculateKeyViewLoop];
|
||||
|
||||
var nextValidKeyView = nil;
|
||||
|
||||
if ([_firstResponder isKindOfClass:[CPView class]])
|
||||
[self selectKeyViewFollowingView:_firstResponder];
|
||||
nextValidKeyView = [_firstResponder nextValidKeyView];
|
||||
|
||||
if (!nextValidKeyView)
|
||||
{
|
||||
var initialFirstResponder = [self initialFirstResponder];
|
||||
|
||||
if ([initialFirstResponder acceptsFirstResponder])
|
||||
nextValidKeyView = initialFirstResponder;
|
||||
else
|
||||
nextValidKeyView = [initialFirstResponder nextValidKeyView];
|
||||
}
|
||||
|
||||
[self makeFirstResponder:nextValidKeyView];
|
||||
}
|
||||
|
||||
- (void)selectPreviousKeyView:(id)sender
|
||||
{
|
||||
if (_keyViewLoopIsDirty && [self autorecalculatesKeyViewLoop])
|
||||
[self recalculateKeyViewLoop];
|
||||
|
||||
var previousValidKeyView = nil;
|
||||
|
||||
if ([_firstResponder isKindOfClass:[CPView class]])
|
||||
[self selectKeyViewPrecedingView:_firstResponder];
|
||||
previousValidKeyView = [_firstResponder previousValidKeyView];
|
||||
|
||||
if (!previousValidKeyView)
|
||||
{
|
||||
var initialFirstResponder = [self initialFirstResponder];
|
||||
|
||||
if ([initialFirstResponder acceptsFirstResponder])
|
||||
previousValidKeyView = initialFirstResponder;
|
||||
else
|
||||
previousValidKeyView = [initialFirstResponder previousValidKeyView];
|
||||
}
|
||||
|
||||
[self makeFirstResponder:previousValidKeyView];
|
||||
}
|
||||
|
||||
- (void)selectKeyViewFollowingView:(CPView)aView
|
||||
{
|
||||
if (_keyViewLoopIsDirty)
|
||||
if (_keyViewLoopIsDirty && [self autorecalculatesKeyViewLoop])
|
||||
[self recalculateKeyViewLoop];
|
||||
|
||||
[self makeFirstResponder:[aView nextValidKeyView]];
|
||||
var nextValidKeyView = [aView nextValidKeyView];
|
||||
|
||||
if ([nextValidKeyView isKindOfClass:[CPView class]])
|
||||
[self makeFirstResponder:nextValidKeyView];
|
||||
}
|
||||
|
||||
- (void)selectKeyViewPrecedingView:(CPView)aView
|
||||
{
|
||||
if (_keyViewLoopIsDirty)
|
||||
if (_keyViewLoopIsDirty && [self autorecalculatesKeyViewLoop])
|
||||
[self recalculateKeyViewLoop];
|
||||
|
||||
[self makeFirstResponder:[aView previousValidKeyView]];
|
||||
var previousValidKeyView = [aView previousValidKeyView];
|
||||
|
||||
if ([previousValidKeyView isKindOfClass:[CPView class]])
|
||||
[self makeFirstResponder:previousValidKeyView];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -2596,9 +2645,11 @@ CPTexturedBackgroundWindowMask
|
||||
|
||||
var allViews = function(aWindow)
|
||||
{
|
||||
var views = [[aWindow contentView] subviews],
|
||||
index = 0;
|
||||
var views = [CPArray arrayWithObject:[aWindow contentView]];
|
||||
|
||||
[views addObjectsFromArray:[[aWindow contentView] subviews]];
|
||||
|
||||
var index = 0;
|
||||
for (; index < views.length; ++index)
|
||||
views = views.concat([views[index] subviews]);
|
||||
|
||||
@@ -2928,5 +2979,6 @@ CPCustomWindowShadowStyle = 3;
|
||||
@import "_CPHUDWindowView.j"
|
||||
@import "_CPBorderlessWindowView.j"
|
||||
@import "_CPBorderlessBridgeWindowView.j"
|
||||
@import "_CPAttachedWindowView.j"
|
||||
@import "CPDragServer.j"
|
||||
@import "CPView.j"
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* _CPAttachedWindowView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Antoine Mercadal
|
||||
* Copyright 2011 <primalmotion@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
|
||||
*/
|
||||
|
||||
@import "_CPWindowView.j"
|
||||
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
|
||||
A custom CPWindowView that manage border and cursor
|
||||
*/
|
||||
@implementation _CPAttachedWindowView : _CPWindowView
|
||||
{
|
||||
BOOL _mouseDownPressed @accessors(getter=isMouseDownPressed, setter=setMouseDownPressed:);
|
||||
float _arrowOffsetX @accessors(property=arrowOffsetX);
|
||||
float _arrowOffsetY @accessors(property=arrowOffsetY);
|
||||
int _appearance @accessors(property=appearance);
|
||||
unsigned _preferredEdge @accessors(property=preferredEdge);
|
||||
|
||||
CPSize _cursorSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Compute the contentView frame from a given window frame
|
||||
|
||||
@param aFrameRect the window frame
|
||||
*/
|
||||
- (CGRect)contentRectForFrameRect:(CGRect)aFrameRect
|
||||
{
|
||||
var contentRect = CGRectMakeCopy(aFrameRect);
|
||||
|
||||
// @todo change border art and remove this pixel perfect adaptation
|
||||
// return CGRectInset(contentRect, 20, 20);
|
||||
|
||||
contentRect.origin.x += 18;
|
||||
contentRect.origin.y += 17;
|
||||
contentRect.size.width -= 35;
|
||||
contentRect.size.height -= 37;
|
||||
|
||||
return contentRect;
|
||||
}
|
||||
|
||||
/*!
|
||||
Compute the window frame from a given contentView frame
|
||||
|
||||
@param aContentRect the contentView frame
|
||||
*/
|
||||
+ (CGRect)frameRectForContentRect:(CGRect)aContentRect
|
||||
{
|
||||
var frameRect = CGRectMakeCopy(aContentRect);
|
||||
|
||||
// @todo change border art and remove this pixel perfect adaptation
|
||||
//return CGRectOffset(frameRect, 20, 20);
|
||||
|
||||
frameRect.origin.x -= 18;
|
||||
frameRect.origin.y -= 17;
|
||||
frameRect.size.width += 35;
|
||||
frameRect.size.height += 37;
|
||||
return frameRect;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initialize the _CPWindowView
|
||||
*/
|
||||
- (id)initWithFrame:(CPRect)aFrame styleMask:(unsigned)aStyleMask
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame styleMask:aStyleMask])
|
||||
{
|
||||
var bundle = [CPBundle bundleForClass:[self class]];
|
||||
_arrowOffsetX = 0.0;
|
||||
_arrowOffsetY = 0.0;
|
||||
|
||||
// @TODO: make this themable
|
||||
_useGlowingEffect = YES;
|
||||
_appearance = CPPopoverAppearanceMinimal;
|
||||
_cursorSize = CPSizeMake(15, 10);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Hide the cursor
|
||||
*/
|
||||
- (void)hideCursor
|
||||
{
|
||||
_cursorSize = CPSizeMakeZero();
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Show the cursor
|
||||
*/
|
||||
- (void)showCursor
|
||||
{
|
||||
_cursorSize = CPSizeMake(15, 10);
|
||||
[self setNeedsDisplay:YES];
|
||||
_mouseDownPressed = NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Draw the view
|
||||
*/
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
[super drawRect:aRect];
|
||||
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
radius = 5,
|
||||
arrowWidth = _cursorSize.width,
|
||||
arrowHeight = _cursorSize.height,
|
||||
strokeWidth = 1,
|
||||
strokeColor,
|
||||
shadowColor = [[CPColor blackColor] colorWithAlphaComponent:.2],
|
||||
shadowSize = CGSizeMake(0, 7),
|
||||
shadowBlur = 15,
|
||||
gradient;
|
||||
|
||||
if (_appearance == CPPopoverAppearanceMinimal)
|
||||
{
|
||||
gradient = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), [(254.0 / 255), (254.0 / 255), (254.0 / 255), 0.93,
|
||||
(231.0 / 255), (231.0 / 255), (231.0 / 255), 0.93], [0,1], 2);
|
||||
strokeColor = [CPColor colorWithHexString:@"B8B8B8"];
|
||||
}
|
||||
else
|
||||
{
|
||||
gradient = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), [(38.0 / 255), (38.0 / 255), (38.0 / 255), 0.93,
|
||||
(18.0 / 255), (18.0 / 255), (18.0 / 255), 0.93], [0,1], 2);
|
||||
strokeColor = [CPColor colorWithHexString:@"222222"];
|
||||
}
|
||||
|
||||
// fix rect to take care of stroke and shadow
|
||||
aRect.origin.x += strokeWidth + shadowBlur;
|
||||
aRect.origin.y += strokeWidth + (shadowBlur + shadowSize.height / 2);
|
||||
aRect.size.width -= (strokeWidth * 2) + (shadowBlur * 2);
|
||||
aRect.size.height -= (strokeWidth * 2) + (shadowBlur * 2 + shadowSize.height);
|
||||
|
||||
CGContextSetStrokeColor(context, strokeColor);
|
||||
CGContextSetLineWidth(context, strokeWidth);
|
||||
CGContextBeginPath(context);
|
||||
CGContextSetShadowWithColor(context, shadowSize, shadowBlur, shadowColor);
|
||||
CGContextDrawLinearGradient(context, gradient, CGPointMake(CPRectGetMidX(aRect), 0.0), CGPointMake(CPRectGetMidX(aRect), aRect.size.height), 0);
|
||||
|
||||
|
||||
var xMin = _CGRectGetMinX(aRect),
|
||||
xMax = _CGRectGetMaxX(aRect),
|
||||
yMin = _CGRectGetMinY(aRect),
|
||||
yMax = _CGRectGetMaxY(aRect);
|
||||
|
||||
// draw!
|
||||
switch (_preferredEdge)
|
||||
{
|
||||
case CPMinXEdge:
|
||||
// origin ne
|
||||
CGContextMoveToPoint(context, xMin + radius, yMin);
|
||||
|
||||
// ne
|
||||
CGContextAddLineToPoint(context, xMax - radius, yMin);
|
||||
CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius);
|
||||
|
||||
// arrow CPMinXEdge
|
||||
CGContextAddLineToPoint(context, xMax, (aRect.size.height / 2) + aRect.origin.y + _arrowOffsetY - (arrowHeight - 2));
|
||||
CGContextAddLineToPoint(context, aRect.size.width + arrowHeight + aRect.origin.x + _arrowOffsetX, (aRect.size.height / 2) + aRect.origin.y + _arrowOffsetY);
|
||||
CGContextAddLineToPoint(context, aRect.size.width + aRect.origin.x + _arrowOffsetX, (aRect.size.height / 2 + (arrowWidth / 2)) + aRect.origin.y + _arrowOffsetY);
|
||||
|
||||
// se
|
||||
CGContextAddLineToPoint(context, xMax, yMax - radius);
|
||||
CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax);
|
||||
|
||||
// sw
|
||||
CGContextAddLineToPoint(context, xMin + radius, yMax);
|
||||
CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius);
|
||||
|
||||
// nw
|
||||
CGContextAddLineToPoint(context, xMin, yMin + radius);
|
||||
CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin);
|
||||
break;
|
||||
|
||||
case CPMaxXEdge:
|
||||
// origin ne
|
||||
CGContextMoveToPoint(context, xMin + radius, yMin);
|
||||
|
||||
// ne
|
||||
CGContextAddLineToPoint(context, xMax - radius, yMin);
|
||||
CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius);
|
||||
|
||||
// se
|
||||
CGContextAddLineToPoint(context, xMax, yMax - radius);
|
||||
CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax);
|
||||
|
||||
// sw
|
||||
CGContextAddLineToPoint(context, xMin + radius, yMax);
|
||||
CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius);
|
||||
|
||||
// arrow CPMaxXEdge
|
||||
CGContextAddLineToPoint(context, xMin, (aRect.size.height / 2 + (arrowWidth / 2) + aRect.origin.y + _arrowOffsetY));
|
||||
CGContextAddLineToPoint(context, aRect.origin.x - arrowHeight + _arrowOffsetX, (aRect.size.height / 2) + aRect.origin.y + _arrowOffsetY);
|
||||
CGContextAddLineToPoint(context, aRect.origin.x + _arrowOffsetX, (aRect.size.height / 2 - (arrowWidth / 2) + aRect.origin.y + _arrowOffsetY));
|
||||
|
||||
// nw
|
||||
CGContextAddLineToPoint(context, xMin, yMin + radius);
|
||||
CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin);
|
||||
break;
|
||||
|
||||
case CPMaxYEdge:
|
||||
// origin nw
|
||||
CGContextMoveToPoint(context, xMin, yMin + yMin);
|
||||
|
||||
// nw
|
||||
CGContextAddLineToPoint(context, xMin, yMin + radius);
|
||||
CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin);
|
||||
|
||||
// arrow CPMaxYEdge
|
||||
CGContextAddLineToPoint(context, (aRect.size.width / 2) + aRect.origin.x + _arrowOffsetX - (arrowWidth / 2), yMin);
|
||||
CGContextAddLineToPoint(context, (aRect.size.width / 2) + aRect.origin.x + _arrowOffsetX, aRect.origin.y - arrowHeight + _arrowOffsetY);
|
||||
CGContextAddLineToPoint(context, (aRect.size.width / 2) + (arrowWidth / 2) + aRect.origin.x + _arrowOffsetX, aRect.origin.y + _arrowOffsetY);
|
||||
|
||||
// ne
|
||||
CGContextAddLineToPoint(context, xMax - radius, yMin);
|
||||
CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius);
|
||||
|
||||
// se
|
||||
CGContextAddLineToPoint(context, xMax, yMax - radius);
|
||||
CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax);
|
||||
|
||||
// sw
|
||||
CGContextAddLineToPoint(context, xMin + radius, yMax);
|
||||
CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius);
|
||||
break;
|
||||
|
||||
case CPMinYEdge:
|
||||
// origin nw
|
||||
CGContextMoveToPoint(context, xMin, yMin + yMin);
|
||||
|
||||
// nw
|
||||
CGContextAddLineToPoint(context, xMin, yMin + radius);
|
||||
CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin);
|
||||
|
||||
// ne
|
||||
CGContextAddLineToPoint(context, xMax - radius, yMin);
|
||||
CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius);
|
||||
|
||||
// se
|
||||
CGContextAddLineToPoint(context, xMax, yMax - radius);
|
||||
CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax);
|
||||
|
||||
// arrow CPMinYEdge
|
||||
CGContextAddLineToPoint(context, (aRect.size.width / 2) + (arrowWidth / 2) + aRect.origin.x + _arrowOffsetX , yMax);
|
||||
CGContextAddLineToPoint(context, (aRect.size.width / 2) + aRect.origin.x + _arrowOffsetX, aRect.size.height + aRect.origin.y + arrowHeight + _arrowOffsetY);
|
||||
CGContextAddLineToPoint(context, (aRect.size.width / 2) - (arrowWidth / 2) + aRect.origin.x + _arrowOffsetX, aRect.size.height + aRect.origin.y + _arrowOffsetY);
|
||||
|
||||
// sw
|
||||
CGContextAddLineToPoint(context, xMin + radius, yMax);
|
||||
CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius);
|
||||
break;
|
||||
|
||||
default:
|
||||
// no computed edge means standard rounded rect
|
||||
CGContextAddPath(context, CGPathWithRoundedRectangleInRect(aRect, radius, radius, YES, YES, YES, YES));
|
||||
}
|
||||
|
||||
CGContextClosePath(context);
|
||||
|
||||
//Draw it
|
||||
CGContextStrokePath(context);
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
_mouseDownPressed = YES;
|
||||
[super mouseDown:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
_mouseDownPressed = NO;
|
||||
[super mouseUp:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -267,6 +267,32 @@ function CGPointFromEvent(anEvent)
|
||||
return _CGPointMake(anEvent.clientX, anEvent.clientY);
|
||||
}
|
||||
|
||||
/*!
|
||||
Combines two insets by adding their individual elements and returns the result.
|
||||
|
||||
@group CGInset
|
||||
*/
|
||||
function CGInsetUnion(lhsInset, rhsInset)
|
||||
{
|
||||
return _CGInsetMake(lhsInset.top + rhsInset.top,
|
||||
lhsInset.right + rhsInset.right,
|
||||
lhsInset.bottom + rhsInset.bottom,
|
||||
lhsInset.left + rhsInset.left);
|
||||
}
|
||||
|
||||
/*!
|
||||
Subtract one inset from another by subtracting their individual elements and returns the result.
|
||||
|
||||
@group CGInset
|
||||
*/
|
||||
function CGInsetDifference(lhsInset, rhsInset)
|
||||
{
|
||||
return _CGInsetMake(lhsInset.top - rhsInset.top,
|
||||
lhsInset.right - rhsInset.right,
|
||||
lhsInset.bottom - rhsInset.bottom,
|
||||
lhsInset.left - rhsInset.left);
|
||||
}
|
||||
|
||||
function CGInsetFromString(aString)
|
||||
{
|
||||
var numbers = aString.substr(1, aString.length - 2).split(',');
|
||||
|
||||
@@ -32,6 +32,7 @@ var PrimaryPlatformWindow = NULL;
|
||||
CPInteger _level;
|
||||
BOOL _hasShadow;
|
||||
unsigned _shadowStyle;
|
||||
CPString _title;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
DOMWindow _DOMWindow;
|
||||
@@ -258,6 +259,21 @@ var PrimaryPlatformWindow = NULL;
|
||||
return [CPPlatform isBrowser];
|
||||
}
|
||||
|
||||
- (void)_setTitle:(CPString)aTitle window:(CPWindow)aWindow
|
||||
{
|
||||
_title = aTitle;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if (_DOMWindow && _DOMWindow.document && (aWindow === [CPApp mainWindow]))
|
||||
_DOMWindow.document.title = _title;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (CPString)title
|
||||
{
|
||||
return _title;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#if PLATFORM(BROWSER)
|
||||
|
||||
@@ -534,13 +534,15 @@ var ModifierKeyCodes = [
|
||||
if (_DOMWindow)
|
||||
return _DOMWindow.focus();
|
||||
|
||||
_DOMWindow = window.open("", "_blank", "menubar=no,location=no,resizable=yes,scrollbars=no,status=no,left=" + _CGRectGetMinX(_contentRect) + ",top=" + _CGRectGetMinY(_contentRect) + ",width=" + _CGRectGetWidth(_contentRect) + ",height=" + _CGRectGetHeight(_contentRect));
|
||||
_DOMWindow = window.open("about:blank", "_blank", "menubar=no,location=no,resizable=yes,scrollbars=no,status=no,left=" + _CGRectGetMinX(_contentRect) + ",top=" + _CGRectGetMinY(_contentRect) + ",width=" + _CGRectGetWidth(_contentRect) + ",height=" + _CGRectGetHeight(_contentRect));
|
||||
|
||||
[PlatformWindows addObject:self];
|
||||
|
||||
// FIXME: cpSetFrame?
|
||||
_DOMWindow.document.write("<!DOCTYPE html><html lang='en'><head></head><body style='background-color:transparent;'></body></html>");
|
||||
_DOMWindow.document.close();
|
||||
if (self != [CPPlatformWindow primaryPlatformWindow])
|
||||
_DOMWindow.document.title = _title;
|
||||
|
||||
if (![CPPlatform isBrowser])
|
||||
{
|
||||
|
||||
|
After Width: | Height: | Size: 145 B |
|
After Width: | Height: | Size: 139 B |
|
After Width: | Height: | Size: 196 B |
|
After Width: | Height: | Size: 487 B |
|
After Width: | Height: | Size: 160 B |
|
After Width: | Height: | Size: 352 B |
|
After Width: | Height: | Size: 690 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 145 B |
|
After Width: | Height: | Size: 139 B |
|
After Width: | Height: | Size: 199 B |
|
After Width: | Height: | Size: 383 B |
|
After Width: | Height: | Size: 160 B |
|
After Width: | Height: | Size: 335 B |
|
After Width: | Height: | Size: 581 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 527 B |
|
Before Width: | Height: | Size: 123 B After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 436 B |
|
After Width: | Height: | Size: 473 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 163 B |
|
Before Width: | Height: | Size: 757 B After Width: | Height: | Size: 803 B |
|
Before Width: | Height: | Size: 735 B After Width: | Height: | Size: 823 B |
|
Before Width: | Height: | Size: 559 B After Width: | Height: | Size: 451 B |
|
Before Width: | Height: | Size: 531 B After Width: | Height: | Size: 501 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 155 B |
|
Before Width: | Height: | Size: 1004 B After Width: | Height: | Size: 130 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 160 B |
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 117 B |
|
Before Width: | Height: | Size: 991 B After Width: | Height: | Size: 109 B |
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 115 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 158 B |
|
Before Width: | Height: | Size: 997 B After Width: | Height: | Size: 124 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 159 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 155 B |
|
Before Width: | Height: | Size: 1004 B After Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 163 B |
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 117 B |
|
Before Width: | Height: | Size: 991 B After Width: | Height: | Size: 109 B |
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 115 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 157 B |
|
Before Width: | Height: | Size: 997 B After Width: | Height: | Size: 124 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 156 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 208 B |
|
Before Width: | Height: | Size: 1012 B After Width: | Height: | Size: 133 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 231 B |
|
Before Width: | Height: | Size: 1012 B After Width: | Height: | Size: 130 B |
|
Before Width: | Height: | Size: 991 B After Width: | Height: | Size: 109 B |
|
Before Width: | Height: | Size: 1009 B After Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 223 B |
|
Before Width: | Height: | Size: 1007 B After Width: | Height: | Size: 134 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 203 B |
@@ -762,46 +762,47 @@ var themedButtonValues = nil,
|
||||
|
||||
bezelColor = PatternColor(
|
||||
[
|
||||
["textfield-bezel-square-0.png", 3.0, 4.0],
|
||||
["textfield-bezel-square-1.png", 1.0, 4.0],
|
||||
["textfield-bezel-square-2.png", 3.0, 4.0],
|
||||
["textfield-bezel-square-3.png", 3.0, 1.0],
|
||||
["textfield-bezel-square-0.png", 6.0, 6.0],
|
||||
["textfield-bezel-square-1.png", 1.0, 6.0],
|
||||
["textfield-bezel-square-2.png", 6.0, 6.0],
|
||||
["textfield-bezel-square-3.png", 6.0, 1.0],
|
||||
["textfield-bezel-square-4.png", 1.0, 1.0],
|
||||
["textfield-bezel-square-5.png", 3.0, 1.0],
|
||||
["textfield-bezel-square-6.png", 3.0, 4.0],
|
||||
["textfield-bezel-square-7.png", 1.0, 4.0],
|
||||
["textfield-bezel-square-8.png", 3.0, 4.0]
|
||||
["textfield-bezel-square-5.png", 6.0, 1.0],
|
||||
["textfield-bezel-square-6.png", 6.0, 6.0],
|
||||
["textfield-bezel-square-7.png", 1.0, 6.0],
|
||||
["textfield-bezel-square-8.png", 6.0, 6.0]
|
||||
]),
|
||||
|
||||
bezelFocusedColor = PatternColor(
|
||||
[
|
||||
["textfield-bezel-square-focused-0.png", 7.0, 7.0],
|
||||
["textfield-bezel-square-focused-1.png", 1.0, 7.0],
|
||||
["textfield-bezel-square-focused-2.png", 7.0, 7.0],
|
||||
["textfield-bezel-square-focused-3.png", 7.0, 1.0],
|
||||
["textfield-bezel-square-focused-0.png", 6.0, 6.0],
|
||||
["textfield-bezel-square-focused-1.png", 1.0, 6.0],
|
||||
["textfield-bezel-square-focused-2.png", 6.0, 6.0],
|
||||
["textfield-bezel-square-focused-3.png", 6.0, 1.0],
|
||||
["textfield-bezel-square-focused-4.png", 1.0, 1.0],
|
||||
["textfield-bezel-square-focused-5.png", 7.0, 1.0],
|
||||
["textfield-bezel-square-focused-6.png", 7.0, 7.0],
|
||||
["textfield-bezel-square-focused-7.png", 1.0, 7.0],
|
||||
["textfield-bezel-square-focused-8.png", 7.0, 7.0]
|
||||
["textfield-bezel-square-focused-5.png", 6.0, 1.0],
|
||||
["textfield-bezel-square-focused-6.png", 6.0, 6.0],
|
||||
["textfield-bezel-square-focused-7.png", 1.0, 6.0],
|
||||
["textfield-bezel-square-focused-8.png", 6.0, 6.0]
|
||||
]),
|
||||
|
||||
bezelDisabledColor = PatternColor(
|
||||
[
|
||||
["textfield-bezel-square-disabled-0.png", 3.0, 4.0],
|
||||
["textfield-bezel-square-disabled-1.png", 1.0, 4.0],
|
||||
["textfield-bezel-square-disabled-2.png", 3.0, 4.0],
|
||||
["textfield-bezel-square-disabled-3.png", 3.0, 1.0],
|
||||
["textfield-bezel-square-disabled-0.png", 6.0, 6.0],
|
||||
["textfield-bezel-square-disabled-1.png", 1.0, 6.0],
|
||||
["textfield-bezel-square-disabled-2.png", 6.0, 6.0],
|
||||
["textfield-bezel-square-disabled-3.png", 6.0, 1.0],
|
||||
["textfield-bezel-square-disabled-4.png", 1.0, 1.0],
|
||||
["textfield-bezel-square-disabled-5.png", 3.0, 1.0],
|
||||
["textfield-bezel-square-disabled-6.png", 3.0, 4.0],
|
||||
["textfield-bezel-square-disabled-7.png", 1.0, 4.0],
|
||||
["textfield-bezel-square-disabled-8.png", 3.0, 4.0]
|
||||
["textfield-bezel-square-disabled-5.png", 6.0, 1.0],
|
||||
["textfield-bezel-square-disabled-6.png", 6.0, 6.0],
|
||||
["textfield-bezel-square-disabled-7.png", 1.0, 6.0],
|
||||
["textfield-bezel-square-disabled-8.png", 6.0, 6.0]
|
||||
]),
|
||||
|
||||
textDisabledColor = [CPColor colorWithCalibratedWhite:0.60 alpha:1.0],
|
||||
placeholderColor = [CPColor colorWithCalibratedRed:189.0 / 255.0 green:199.0 / 255.0 blue:211.0 / 255.0 alpha:1.0];
|
||||
|
||||
// Global for reuse by CPTokenField.
|
||||
// Global for reuse by subclasses
|
||||
themedTextFieldValues =
|
||||
[
|
||||
[@"vertical-alignment", CPTopVerticalTextAlignment, CPThemeStateBezeled],
|
||||
@@ -810,30 +811,38 @@ var themedButtonValues = nil,
|
||||
[@"bezel-color", bezelDisabledColor, CPThemeStateBezeled | CPThemeStateDisabled],
|
||||
[@"font", [CPFont systemFontOfSize:12.0], CPThemeStateBezeled],
|
||||
|
||||
[@"content-inset", CGInsetMake(7.0, 7.0, 6.0, 8.0), CPThemeStateBezeled],
|
||||
[@"content-inset", CGInsetMake(6.0, 7.0, 6.0, 8.0), CPThemeStateBezeled | CPThemeStateEditing],
|
||||
[@"bezel-inset", CGInsetMake(3.0, 4.0, 3.0, 4.0), CPThemeStateBezeled],
|
||||
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPThemeStateBezeled | CPThemeStateEditing],
|
||||
// no border
|
||||
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0)],
|
||||
[@"content-inset", CGInsetMake(2.0, 2.0, 2.0, 2.0)], // as defined in [CPTextField +themeAttributes]
|
||||
|
||||
// with border
|
||||
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPThemeStateBezeled],
|
||||
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPThemeStateBezeled | CPThemeStateEditing],
|
||||
[@"border-inset", CGInsetMake(3.0, 3.0, 3.0, 3.0), CPThemeStateBezeled],
|
||||
[@"content-inset", CGInsetMake(8.0, 8.0, 7.0, 8.0), CPThemeStateBezeled],
|
||||
[@"content-inset", CGInsetMake(8.0, 8.0, 7.0, 8.0), CPThemeStateBezeled | CPThemeStateEditing],
|
||||
|
||||
[@"text-color", textDisabledColor, CPThemeStateBezeled | CPThemeStateDisabled],
|
||||
[@"text-color", placeholderColor, CPTextFieldStatePlaceholder],
|
||||
[@"text-color", placeholderColor, CPTextFieldStatePlaceholder | CPThemeStateDisabled],
|
||||
|
||||
[@"line-break-mode", CPLineBreakByTruncatingTail, CPThemeStateTableDataView],
|
||||
[@"vertical-alignment", CPCenterVerticalTextAlignment, CPThemeStateTableDataView],
|
||||
[@"content-inset", CGInsetMake(0.0, 0.0, 0.0, 5.0), CPThemeStateTableDataView],
|
||||
[@"content-inset", CGInsetMake(0.0, 0.0, 0.0, 4.0), CPThemeStateTableDataView],
|
||||
|
||||
[@"text-color", [CPColor colorWithCalibratedWhite:51.0 / 255.0 alpha:1.0], CPThemeStateTableDataView],
|
||||
[@"text-color", [CPColor whiteColor], CPThemeStateTableDataView | CPThemeStateSelectedTableDataView],
|
||||
[@"font", [CPFont boldSystemFontOfSize:12.0], CPThemeStateTableDataView | CPThemeStateSelectedTableDataView],
|
||||
[@"text-color", [CPColor blackColor], CPThemeStateTableDataView | CPThemeStateEditing],
|
||||
[@"content-inset", CGInsetMake(7.0, 7.0, 5.0, 8.0), CPThemeStateTableDataView | CPThemeStateEditing],
|
||||
[@"content-inset", CGInsetMake(8.0, 8.0, 7.0, 4.0), CPThemeStateTableDataView | CPThemeStateEditing],
|
||||
[@"font", [CPFont systemFontOfSize:12.0], CPThemeStateTableDataView | CPThemeStateEditing],
|
||||
[@"bezel-inset", CGInsetMake(-2.0, -2.0, -2.0, -2.0), CPThemeStateTableDataView | CPThemeStateEditing],
|
||||
[@"bezel-inset", CGInsetMake(-1.0, -1.0, -1.0, -1.0), CPThemeStateTableDataView | CPThemeStateEditing],
|
||||
|
||||
[@"text-color", [CPColor colorWithCalibratedWhite:125.0 / 255.0 alpha:1.0], CPThemeStateTableDataView | CPThemeStateGroupRow],
|
||||
[@"text-color", [CPColor colorWithCalibratedWhite:1.0 alpha:1.0], CPThemeStateTableDataView | CPThemeStateGroupRow | CPThemeStateSelectedTableDataView],
|
||||
[@"text-shadow-color", [CPColor whiteColor], CPThemeStateTableDataView | CPThemeStateGroupRow],
|
||||
[@"text-shadow-offset", CGSizeMake(0,1), CPThemeStateTableDataView | CPThemeStateGroupRow],
|
||||
[@"text-shadow-color", [CPColor colorWithCalibratedWhite:0.0 alpha:0.6], CPThemeStateTableDataView | CPThemeStateGroupRow | CPThemeStateSelectedTableDataView],
|
||||
[@"text-shadow-color", [CPColor colorWithCalibratedWhite:0.0 alpha:0.6], CPThemeStateTableDataView | CPThemeStateGroupRow | CPThemeStateSelectedTableDataView],
|
||||
[@"font", [CPFont boldSystemFontOfSize:12.0], CPThemeStateTableDataView | CPThemeStateGroupRow]
|
||||
];
|
||||
|
||||
@@ -853,35 +862,45 @@ var themedButtonValues = nil,
|
||||
var textfield = [[CPTextField alloc] initWithFrame:CGRectMake(0.0, 0.0, 60.0, 30.0)],
|
||||
bezelColor = PatternColor(
|
||||
[
|
||||
["textfield-bezel-rounded-left.png", 13.0, 22.0],
|
||||
["textfield-bezel-rounded-center.png", 1.0, 22.0],
|
||||
["textfield-bezel-rounded-right.png", 13.0, 22.0]
|
||||
["textfield-bezel-rounded-left.png", 15.0, 30.0],
|
||||
["textfield-bezel-rounded-center.png", 1.0, 30.0],
|
||||
["textfield-bezel-rounded-right.png", 15.0, 30.0]
|
||||
],
|
||||
PatternIsHorizontal),
|
||||
|
||||
bezelFocusedColor = PatternColor(
|
||||
[
|
||||
["textfield-bezel-rounded-focused-left.png", 17.0, 30.0],
|
||||
["textfield-bezel-rounded-focused-left.png", 15.0, 30.0],
|
||||
["textfield-bezel-rounded-focused-center.png", 1.0, 30.0],
|
||||
["textfield-bezel-rounded-focused-right.png", 17.0, 30.0]
|
||||
["textfield-bezel-rounded-focused-right.png", 15.0, 30.0]
|
||||
],
|
||||
PatternIsHorizontal),
|
||||
|
||||
bezelDisabledColor = PatternColor(
|
||||
[
|
||||
["textfield-bezel-rounded-disabled-left.png", 15.0, 30.0],
|
||||
["textfield-bezel-rounded-disabled-center.png", 1.0, 30.0],
|
||||
["textfield-bezel-rounded-disabled-right.png", 15.0, 30.0]
|
||||
],
|
||||
PatternIsHorizontal),
|
||||
|
||||
placeholderColor = [CPColor colorWithCalibratedRed:189.0 / 255.0 green:199.0 / 255.0 blue:211.0 / 255.0 alpha:1.0];
|
||||
|
||||
// Global for reuse by CPSearchField
|
||||
// Global for reuse by subclasses
|
||||
themedRoundedTextFieldValues =
|
||||
[
|
||||
[@"bezel-color", bezelColor, CPTextFieldStateRounded | CPThemeStateBezeled],
|
||||
[@"bezel-color", bezelFocusedColor, CPTextFieldStateRounded | CPThemeStateBezeled | CPThemeStateEditing],
|
||||
[@"bezel-color", bezelDisabledColor, CPTextFieldStateRounded | CPThemeStateBezeled | CPThemeStateDisabled],
|
||||
[@"font", [CPFont systemFontOfSize:12.0]],
|
||||
|
||||
[@"content-inset", CGInsetMake(8.0, 14.0, 6.0, 14.0), CPTextFieldStateRounded | CPThemeStateBezeled],
|
||||
[@"content-inset", CGInsetMake(7.0, 14.0, 6.0, 14.0), CPTextFieldStateRounded | CPThemeStateBezeled | CPThemeStateEditing],
|
||||
|
||||
[@"bezel-inset", CGInsetMake(4.0, 4.0, 4.0, 4.0), CPTextFieldStateRounded | CPThemeStateBezeled],
|
||||
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPTextFieldStateRounded | CPThemeStateBezeled],
|
||||
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPTextFieldStateRounded | CPThemeStateBezeled | CPThemeStateEditing],
|
||||
|
||||
// There is one extra empty pixel at the bottom for size compatibility with an earlier version.
|
||||
[@"content-inset", CGInsetMake(8.0, 15.0, 8.0, 15.0), CPTextFieldStateRounded | CPThemeStateBezeled],
|
||||
[@"content-inset", CGInsetMake(8.0, 15.0, 8.0, 15.0), CPTextFieldStateRounded | CPThemeStateBezeled | CPThemeStateEditing],
|
||||
|
||||
[@"text-color", placeholderColor, CPTextFieldStateRounded | CPTextFieldStatePlaceholder],
|
||||
|
||||
[@"min-size", CGSizeMake(0.0, 30.0), CPTextFieldStateRounded | CPThemeStateBezeled],
|
||||
@@ -914,11 +933,17 @@ var themedButtonValues = nil,
|
||||
|
||||
overrides =
|
||||
[
|
||||
[@"content-inset", CGInsetMake(8.0, 0.0, 4.0, 0.0)],
|
||||
// Placeholder is displayed as regular text, not tokens; requires a different inset.
|
||||
[@"content-inset", CGInsetMake(9.0, 0.0, 5.0, 2.0), CPTextFieldStatePlaceholder],
|
||||
[@"content-inset", CGInsetMake(6.0, 5.0, 5.0, 6.0), CPThemeStateBezeled],
|
||||
[@"content-inset", CGInsetMake(9.0, 7.0, 6.0, 8.0), CPThemeStateBezeled | CPTextFieldStatePlaceholder],
|
||||
// Non-bezeled token field with tokens
|
||||
[@"content-inset", CGInsetMake(5.0, 8.0, 4.0, 8.0)],
|
||||
|
||||
// Non-bezeled token field with no tokens
|
||||
[@"content-inset", CGInsetMake(7.0, 8.0, 6.0, 8.0), CPTextFieldStatePlaceholder],
|
||||
|
||||
// Bezeled token field with tokens
|
||||
[@"content-inset", CGInsetMake(6.0, 8.0, 5.0, 8.0), CPThemeStateBezeled],
|
||||
|
||||
// Bezeled token field with no tokens
|
||||
[@"content-inset", CGInsetMake(8.0, 8.0, 7.0, 8.0), CPThemeStateBezeled | CPTextFieldStatePlaceholder],
|
||||
];
|
||||
|
||||
[self registerThemeValues:overrides forView:tokenfield inherit:themedTextFieldValues];
|
||||
@@ -958,13 +983,13 @@ var themedButtonValues = nil,
|
||||
[@"text-color", textHighlightedColor, CPThemeStateHighlighted],
|
||||
|
||||
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPThemeStateBezeled],
|
||||
[@"content-inset", CGInsetMake(1.0, 24.0, 2.0, 16.0), CPThemeStateBezeled],
|
||||
[@"content-inset", CGInsetMake(1.0, 23.0, 3.0, 15.0), CPThemeStateBezeled],
|
||||
|
||||
// Minimum height == maximum height since tokens are fixed height.
|
||||
[@"min-size", CGSizeMake(0.0, 19.0)],
|
||||
[@"max-size", CGSizeMake(-1.0, 19.0)],
|
||||
|
||||
[@"vertical-alignment", CPCenterTextAlignment],
|
||||
[@"vertical-alignment", CPCenterTextAlignment]
|
||||
];
|
||||
|
||||
[self registerThemeValues:themeValues forView:token];
|
||||
@@ -990,7 +1015,7 @@ var themedButtonValues = nil,
|
||||
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPThemeStateBordered],
|
||||
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPThemeStateBordered | CPThemeStateHighlighted],
|
||||
|
||||
[@"offset", CGPointMake(18, 6), CPThemeStateBordered]
|
||||
[@"offset", CGPointMake(17, 6), CPThemeStateBordered]
|
||||
];
|
||||
|
||||
[self registerThemeValues:themeValues forView:button];
|
||||
@@ -998,6 +1023,86 @@ var themedButtonValues = nil,
|
||||
return button;
|
||||
}
|
||||
|
||||
+ (CPComboBox)themedComboBox
|
||||
{
|
||||
var combo = [[CPComboBox alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 29.0)],
|
||||
|
||||
bezelColor = PatternColor(
|
||||
[
|
||||
["combobox-bezel-left.png", 6.0, 29.0],
|
||||
["combobox-bezel-center.png", 1.0, 29.0],
|
||||
["combobox-bezel-right.png", 24.0, 29.0]
|
||||
],
|
||||
PatternIsHorizontal),
|
||||
|
||||
bezelFocusedColor = PatternColor(
|
||||
[
|
||||
["combobox-bezel-focused-left.png", 6.0, 29.0],
|
||||
["combobox-bezel-focused-center.png", 1.0, 29.0],
|
||||
["combobox-bezel-focused-right.png", 24.0, 29.0]
|
||||
],
|
||||
PatternIsHorizontal),
|
||||
|
||||
bezelDisabledColor = PatternColor(
|
||||
[
|
||||
["combobox-bezel-disabled-left.png", 6.0, 29.0],
|
||||
["combobox-bezel-disabled-center.png", 1.0, 29.0],
|
||||
["combobox-bezel-disabled-right.png", 24.0, 29.0]
|
||||
],
|
||||
PatternIsHorizontal),
|
||||
|
||||
bezelNoBorderColor = PatternColor(
|
||||
[
|
||||
["combobox-bezel-no-border-left.png", 6.0, 29.0],
|
||||
["combobox-bezel-no-border-center.png", 1.0, 29.0],
|
||||
["combobox-bezel-no-border-right.png", 24.0, 29.0]
|
||||
],
|
||||
PatternIsHorizontal),
|
||||
|
||||
bezelNoBorderFocusedColor = PatternColor(
|
||||
[
|
||||
["combobox-bezel-no-border-focused-left.png", 6.0, 29.0],
|
||||
["combobox-bezel-no-border-focused-center.png", 1.0, 29.0],
|
||||
["combobox-bezel-no-border-focused-right.png", 24.0, 29.0]
|
||||
],
|
||||
PatternIsHorizontal),
|
||||
|
||||
bezelNoBorderDisabledColor = PatternColor(
|
||||
[
|
||||
["combobox-bezel-no-border-disabled-left.png", 6.0, 29.0],
|
||||
["combobox-bezel-no-border-disabled-center.png", 1.0, 29.0],
|
||||
["combobox-bezel-no-border-disabled-right.png", 24.0, 29.0]
|
||||
],
|
||||
PatternIsHorizontal),
|
||||
|
||||
overrides =
|
||||
[
|
||||
[@"bezel-color", bezelColor, CPThemeStateBezeled | CPComboBoxStateButtonBordered],
|
||||
[@"bezel-color", bezelFocusedColor, CPThemeStateBezeled | CPComboBoxStateButtonBordered | CPThemeStateEditing],
|
||||
[@"bezel-color", bezelDisabledColor, CPThemeStateBezeled | CPComboBoxStateButtonBordered | CPThemeStateDisabled],
|
||||
|
||||
[@"bezel-color", bezelNoBorderColor, CPThemeStateBezeled],
|
||||
[@"bezel-color", bezelNoBorderFocusedColor, CPThemeStateBezeled | CPThemeStateEditing],
|
||||
[@"bezel-color", bezelNoBorderDisabledColor, CPThemeStateBezeled | CPThemeStateDisabled],
|
||||
|
||||
// The right border inset has to make room for the focus ring and popup button
|
||||
[@"content-inset", CGInsetMake(8.0, 27.0, 7.0, 8.0), CPThemeStateBezeled | CPComboBoxStateButtonBordered],
|
||||
[@"content-inset", CGInsetMake(8.0, 24.0, 7.0, 8.0), CPThemeStateBezeled],
|
||||
[@"content-inset", CGInsetMake(8.0, 24.0, 7.0, 8.0), CPThemeStateBezeled | CPThemeStateEditing],
|
||||
|
||||
[@"popup-button-size", CGSizeMake(21.0, 23.0), CPThemeStateBezeled | CPComboBoxStateButtonBordered],
|
||||
[@"popup-button-size", CGSizeMake(17.0, 23.0), CPThemeStateBezeled],
|
||||
|
||||
// Because combo box uses a three-part bezel, the height is fixed
|
||||
[@"min-size", CGSizeMake(0, 29.0)],
|
||||
[@"max-size", CGSizeMake(-1, 29.0)]
|
||||
];
|
||||
|
||||
[self registerThemeValues:overrides forView:combo inherit:themedTextFieldValues];
|
||||
|
||||
return combo;
|
||||
}
|
||||
|
||||
+ (CPRadioButton)themedRadioButton
|
||||
{
|
||||
var button = [CPRadio radioWithTitle:@"Hello Friend!"],
|
||||
|
||||
@@ -0,0 +1,501 @@
|
||||
/*
|
||||
* _CPToolTipWindowView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Antoine Mercadal
|
||||
* Copyright 2011 <primalmotion@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
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CPButton.j"
|
||||
@import "CPWindow.j"
|
||||
|
||||
|
||||
CPClosableOnBlurWindowMask = 1 << 4;
|
||||
CPPopoverAppearanceMinimal = 0;
|
||||
CPPopoverAppearanceHUD = 1;
|
||||
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
|
||||
This is a simple attached window like the one that pops up
|
||||
when you double click on a meeting in iCal
|
||||
*/
|
||||
@implementation _CPAttachedWindow : CPWindow
|
||||
{
|
||||
BOOL _animates @accessors(property=animates);
|
||||
id _targetView @accessors(property=targetView);
|
||||
int _appearance @accessors(getter=appearance);
|
||||
|
||||
BOOL _closeOnBlur;
|
||||
BOOL _isClosed;
|
||||
BOOL _shouldPerformAnimation;
|
||||
CPButton _closeButton;
|
||||
float _animationDuration;
|
||||
}
|
||||
|
||||
/*!
|
||||
override default windowView class loader
|
||||
|
||||
@param aStyleMask the window mask
|
||||
@return the windowView class
|
||||
*/
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Class methods
|
||||
|
||||
+ (Class)_windowViewClassForStyleMask:(unsigned)aStyleMask
|
||||
{
|
||||
return _CPAttachedWindowView;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
/*!
|
||||
Create and init a _CPAttachedWindow with given size of and view
|
||||
|
||||
@param aSize the size of the attached window
|
||||
@param aView the target view
|
||||
@return ready to use _CPAttachedWindow
|
||||
*/
|
||||
+ (id)attachedWindowWithSize:(CGSize)aSize forView:(CPView)aView
|
||||
{
|
||||
return [_CPAttachedWindow attachedWindowWithSize:aSize forView:aView styleMask:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Create and init a _CPAttachedWindow with given size of and view
|
||||
|
||||
@param aSize the size of the attached window
|
||||
@param aView the target view
|
||||
@return ready to use _CPAttachedWindow
|
||||
@param styleMask the window style mask (combine CPClosableWindowMask and CPClosableOnBlurWindowMask)
|
||||
*/
|
||||
+ (id)attachedWindowWithSize:(CGSize)aSize forView:(CPView)aView styleMask:(int)aMask
|
||||
{
|
||||
var attachedWindow = [[_CPAttachedWindow alloc] initWithContentRect:CPRectMake(0.0, 0.0, aSize.width, aSize.height) styleMask:aMask];
|
||||
|
||||
[attachedWindow attachToView:aView];
|
||||
|
||||
return attachedWindow;
|
||||
}
|
||||
|
||||
/*!
|
||||
Create and init a _CPAttachedWindow with given frame
|
||||
|
||||
@param aFrame the frame of the attached window
|
||||
@return ready to use _CPAttachedWindow
|
||||
*/
|
||||
- (id)initWithContentRect:(CGRect)aFrame
|
||||
{
|
||||
self = [self initWithContentRect:aFrame styleMask:nil]
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Create and init a _CPAttachedWindow with given frame
|
||||
|
||||
@param aFrame the frame of the attached window
|
||||
@param styleMask the window style mask (combine CPClosableWindowMask and CPClosableOnBlurWindowMask)
|
||||
@return ready to use _CPAttachedWindow
|
||||
*/
|
||||
- (id)initWithContentRect:(CGRect)aFrame styleMask:(unsigned)aStyleMask
|
||||
{
|
||||
if (self = [super initWithContentRect:aFrame styleMask:aStyleMask])
|
||||
{
|
||||
_animates = YES;
|
||||
_animates = YES;
|
||||
_animationDuration = 150;
|
||||
_closeOnBlur = (aStyleMask & CPClosableOnBlurWindowMask);
|
||||
_isClosed = NO;
|
||||
_shouldPerformAnimation = _animates;
|
||||
|
||||
[self setLevel:CPStatusWindowLevel];
|
||||
[self setMovableByWindowBackground:YES];
|
||||
[self setHasShadow:NO];
|
||||
|
||||
_DOMElement.style.WebkitBackfaceVisibility = "hidden";
|
||||
_DOMElement.style.WebkitTransitionProperty = "-webkit-transform, opacity";
|
||||
_DOMElement.style.WebkitTransitionDuration = _animationDuration + "ms";
|
||||
|
||||
[_windowView setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Getters / Setters
|
||||
|
||||
- (void)setAppearance:(int)anAppearance
|
||||
{
|
||||
if (_appearance == anAppearance)
|
||||
return;
|
||||
|
||||
[_windowView setAppearance:anAppearance];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Observer
|
||||
|
||||
/*!
|
||||
Update the _CPAttachedWindow frame if a resize event is observed
|
||||
|
||||
*/
|
||||
- (void)observeValueForKeyPath:(CPString)aPath ofObject:(id)anObject change:(CPDictionary)theChange context:(void)aContext
|
||||
{
|
||||
if ([aPath isEqual:@"frame"])
|
||||
{
|
||||
// @TODO: not recompute everything, just compute the move offset
|
||||
var g = [_windowView preferredEdge];
|
||||
|
||||
[self positionRelativeToView:_targetView preferredEdge:g];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Notification handlers
|
||||
|
||||
- (void)_attachedWindowDidMove:(CPNotification)aNotification
|
||||
{
|
||||
if ([_windowView isMouseDownPressed])
|
||||
{
|
||||
[_targetView removeObserver:self forKeyPath:@"frame"];
|
||||
[_windowView hideCursor];
|
||||
[self setLevel:CPNormalWindowLevel];
|
||||
[_closeButton setFrameOrigin:CPPointMake(1.0, 1.0)];
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidMoveNotification object:self];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Utilities
|
||||
|
||||
- (CPPoint)computeOrigin:(CPView)aView preferredEdge:(int)anEdge
|
||||
{
|
||||
var frameView = [aView frame],
|
||||
currentView = aView,
|
||||
origin = [aView frameOrigin],
|
||||
lastView;
|
||||
|
||||
// if somebody succeed to use the conversion function of CPView
|
||||
// to get this working, please do.
|
||||
while (currentView = [currentView superview])
|
||||
{
|
||||
origin.x += [currentView frameOrigin].x;
|
||||
origin.y += [currentView frameOrigin].y;
|
||||
lastView = currentView;
|
||||
}
|
||||
|
||||
origin.x += [[lastView window] frame].origin.x;
|
||||
origin.y += [[lastView window] frame].origin.y;
|
||||
|
||||
// take care of the scrolling point
|
||||
if ([aView enclosingScrollView])
|
||||
{
|
||||
var offsetPoint = [[[aView enclosingScrollView] contentView] boundsOrigin];
|
||||
origin.x -= offsetPoint.x;
|
||||
origin.y -= offsetPoint.y;
|
||||
}
|
||||
|
||||
return [self computeOriginFromRect:CPRectMake(origin.x, origin.y, CPRectGetWidth(frameView), CPRectGetHeight(frameView)) preferredEdge:anEdge];
|
||||
}
|
||||
|
||||
- (CPPoint)computeOriginFromRect:(CPRect)aRect preferredEdge:(int)anEdge
|
||||
{
|
||||
var nativeRect = [[[CPApp mainWindow] platformWindow] nativeContentRect],
|
||||
originLeft = CPPointCreateCopy(aRect.origin),
|
||||
originRight = CPPointCreateCopy(aRect.origin),
|
||||
originTop = CPPointCreateCopy(aRect.origin),
|
||||
originBottom = CPPointCreateCopy(aRect.origin);
|
||||
|
||||
// CPMaxXEdge
|
||||
originRight.x += aRect.size.width;
|
||||
originRight.y += (aRect.size.height / 2.0) - (CPRectGetHeight([self frame]) / 2.0)
|
||||
|
||||
// CPMinXEdge
|
||||
originLeft.x -= CPRectGetWidth([self frame]);
|
||||
originLeft.y += (aRect.size.height / 2.0) - (CPRectGetHeight([self frame]) / 2.0)
|
||||
|
||||
// CPMaxYEdge
|
||||
originBottom.x += aRect.size.width / 2.0 - CPRectGetWidth([self frame]) / 2.0;
|
||||
originBottom.y += aRect.size.height;
|
||||
|
||||
// CPMinYEdge
|
||||
originTop.x += aRect.size.width / 2.0 - CPRectGetWidth([self frame]) / 2.0;
|
||||
originTop.y -= CPRectGetHeight([self frame]);
|
||||
|
||||
var requestedEdge = (anEdge !== nil) ? anEdge : CPMaxXEdge,
|
||||
requestedOrigin;
|
||||
|
||||
switch (requestedEdge)
|
||||
{
|
||||
case CPMaxXEdge:
|
||||
requestedOrigin = originRight;
|
||||
break;
|
||||
case CPMinXEdge:
|
||||
requestedOrigin = originLeft;
|
||||
break;
|
||||
case CPMinYEdge:
|
||||
requestedOrigin = originTop;
|
||||
break;
|
||||
case CPMaxYEdge:
|
||||
requestedOrigin = originBottom;
|
||||
break;
|
||||
}
|
||||
|
||||
var origins = [requestedOrigin, originRight, originLeft, originTop, originBottom],
|
||||
edges = [requestedEdge, CPMaxXEdge, CPMinXEdge, CPMinYEdge, CPMaxYEdge];
|
||||
|
||||
for (var i = 0; i < origins.length; i++)
|
||||
{
|
||||
var o = origins[i],
|
||||
g = edges[i];
|
||||
|
||||
[_windowView setArrowOffsetX:0];
|
||||
[_windowView setArrowOffsetY:0];
|
||||
[_windowView setPreferredEdge:g];
|
||||
|
||||
if (o.x < 0)
|
||||
{
|
||||
[_windowView setArrowOffsetX:o.x];
|
||||
o.x = 0;
|
||||
}
|
||||
if (o.x + CPRectGetWidth([self frame]) > nativeRect.size.width)
|
||||
{
|
||||
[_windowView setArrowOffsetX:(o.x + CPRectGetWidth([self frame]) - nativeRect.size.width)];
|
||||
o.x = nativeRect.size.width - CPRectGetWidth([self frame]);
|
||||
}
|
||||
if (o.y < 0)
|
||||
{
|
||||
[_windowView setArrowOffsetY:o.y];
|
||||
o.y = 0;
|
||||
}
|
||||
if (o.y + CPRectGetHeight([self frame]) > nativeRect.size.height)
|
||||
{
|
||||
[_windowView setArrowOffsetY:(CPRectGetHeight([self frame]) + o.y - nativeRect.size.height)];
|
||||
o.y = nativeRect.size.height - CPRectGetHeight([self frame]);
|
||||
}
|
||||
|
||||
switch (g)
|
||||
{
|
||||
case CPMaxXEdge:
|
||||
if (o.x >= (aRect.origin.x + aRect.size.width))
|
||||
return o;
|
||||
break;
|
||||
case CPMinXEdge:
|
||||
if ((o.x + _frame.size.width) <= aRect.origin.x)
|
||||
return o;
|
||||
break;
|
||||
case CPMaxYEdge:
|
||||
if (o.y >= (aRect.origin.y + aRect.size.height))
|
||||
return o;
|
||||
break;
|
||||
case CPMinYEdge:
|
||||
if ((o.y + _frame.size.height) <= aRect.origin.y)
|
||||
return o;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[_windowView setPreferredEdge:nil];
|
||||
return requestedOrigin;
|
||||
}
|
||||
|
||||
/*!
|
||||
Compute the frame needed to be placed to the given view
|
||||
and position the attached window according to this view (edge will be automatic)
|
||||
|
||||
@param aView the view where _CPAttachedWindow must be attached
|
||||
*/
|
||||
- (void)positionRelativeToView:(CPView)aView
|
||||
{
|
||||
[self positionRelativeToView:aView preferredEdge:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Compute the frame needed to be placed to the given view
|
||||
and position the attached window according to this view
|
||||
|
||||
@param aView the view where _CPAttachedWindow must be attached
|
||||
@param anEdge the preferd edge to use
|
||||
*/
|
||||
- (void)positionRelativeToView:(CPView)aView preferredEdge:(int)anEdge
|
||||
{
|
||||
var point = [self computeOrigin:aView preferredEdge:anEdge];
|
||||
|
||||
[self setFrameOrigin:point];
|
||||
[_windowView showCursor];
|
||||
[self setLevel:CPStatusWindowLevel];
|
||||
[_closeButton setFrameOrigin:CPPointMake(1.0, 1.0)];
|
||||
[_windowView setNeedsDisplay:YES];
|
||||
[self makeKeyAndOrderFront:nil];
|
||||
|
||||
_targetView = aView;
|
||||
[_targetView addObserver:self forKeyPath:@"frame" options:nil context:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Position the _CPAttachedWindow to a random point
|
||||
|
||||
@param aPoint the point where the _CPAttachedWindow will be attached
|
||||
*/
|
||||
- (void)positionRelativeToRect:(CPRect)aRect
|
||||
{
|
||||
[self positionRelativeToRect:aRect preferredEdge:nil]
|
||||
}
|
||||
|
||||
/*!
|
||||
Position the _CPAttachedWindow to a random point
|
||||
|
||||
@param aPoint the point where the _CPAttachedWindow will be attached
|
||||
@param anEdge the prefered edge
|
||||
*/
|
||||
- (void)positionRelativeToRect:(CPRect)aRect preferredEdge:(int)anEdge
|
||||
{
|
||||
var point = [self computeOriginFromRect:aRect preferredEdge:anEdge];
|
||||
|
||||
[self setFrameOrigin:point];
|
||||
[_windowView showCursor];
|
||||
[self setLevel:CPStatusWindowLevel];
|
||||
[_closeButton setFrameOrigin:CPPointMake(1.0, 1.0)];
|
||||
[_windowView setNeedsDisplay:YES];
|
||||
[self makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Actions
|
||||
|
||||
/*!
|
||||
Closes the _CPAttachedWindow
|
||||
|
||||
@param sender the sender of the action
|
||||
*/
|
||||
- (IBAction)close:(id)aSender
|
||||
{
|
||||
[self close];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Overrides
|
||||
|
||||
/*!
|
||||
Called when the window is loowing focus and close the window if CPClosableOnBlurWindowMask is setted
|
||||
*/
|
||||
- (void)resignMainWindow
|
||||
{
|
||||
if (_closeOnBlur && !_isClosed)
|
||||
{
|
||||
// set a close flag to avoid infinite loop
|
||||
_isClosed = YES;
|
||||
[self close];
|
||||
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(didAttachedWindowClose:)])
|
||||
[_delegate didAttachedWindowClose:self];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Order front the window as usual and add listener for CPWindowDidMoveNotification
|
||||
|
||||
@param sender the sender of the action
|
||||
*/
|
||||
- (IBAction)orderFront:(is)aSender
|
||||
{
|
||||
[super orderFront:aSender];
|
||||
|
||||
var tranformOrigin = "50% 100%";
|
||||
|
||||
switch ([_windowView preferredEdge])
|
||||
{
|
||||
case CPMaxYEdge:
|
||||
var posX = 50 + (([_windowView arrowOffsetX] * 100) / _frame.size.width);
|
||||
tranformOrigin = posX + "% 0%"; // 50 0
|
||||
break;
|
||||
case CPMinYEdge:
|
||||
var posX = 50 + (([_windowView arrowOffsetX] * 100) / _frame.size.width);
|
||||
tranformOrigin = posX + "% 100%"; // 50 100
|
||||
break;
|
||||
case CPMinXEdge:
|
||||
var posY = 50 + (([_windowView arrowOffsetY] * 100) / _frame.size.height);
|
||||
tranformOrigin = "100% " + posY + "%"; // 100 50
|
||||
break;
|
||||
case CPMaxXEdge:
|
||||
var posY = 50 + (([_windowView arrowOffsetY] * 100) / _frame.size.height);
|
||||
tranformOrigin = "0% "+ posY + "%"; // 0 50
|
||||
break;
|
||||
}
|
||||
|
||||
// @TODO: implement for FF
|
||||
if (_animates && _shouldPerformAnimation && typeof(_DOMElement.style.WebkitTransform) != "undefined")
|
||||
{
|
||||
_DOMElement.style.opacity = 0;
|
||||
_DOMElement.style.WebkitTransform = "scale(0)";
|
||||
_DOMElement.style.WebkitTransformOrigin = tranformOrigin;
|
||||
window.setTimeout(function(){
|
||||
_DOMElement.style.height = _frame.size.height + @"px";
|
||||
_DOMElement.style.width = _frame.size.width + @"px";
|
||||
_DOMElement.style.opacity = 1;
|
||||
_DOMElement.style.WebkitTransform = "scale(1.1)";
|
||||
var transitionEndFunction = function(){
|
||||
_DOMElement.style.WebkitTransform = "scale(1)";
|
||||
_DOMElement.removeEventListener("webkitTransitionEnd", transitionEndFunction, YES);
|
||||
};
|
||||
_DOMElement.addEventListener("webkitTransitionEnd", transitionEndFunction, YES)
|
||||
},0);
|
||||
}
|
||||
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_attachedWindowDidMove:) name:CPWindowDidMoveNotification object:self];
|
||||
|
||||
_shouldPerformAnimation = NO;
|
||||
_isClosed = NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Close the windo with animation
|
||||
*/
|
||||
- (void)close
|
||||
{
|
||||
if (_animates && typeof(_DOMElement.style.WebkitTransform) != "undefined")
|
||||
{
|
||||
_DOMElement.style.opacity = 0;
|
||||
var transitionEndFunction = function(){
|
||||
[super close];
|
||||
_DOMElement.removeEventListener("webkitTransitionEnd", transitionEndFunction, YES);
|
||||
};
|
||||
_DOMElement.addEventListener("webkitTransitionEnd", transitionEndFunction, YES);
|
||||
}
|
||||
else
|
||||
[super close];
|
||||
|
||||
[_targetView removeObserver:self forKeyPath:@"frame"];
|
||||
|
||||
_shouldPerformAnimation = _animates;
|
||||
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(didAttachedWindowClose:)])
|
||||
[_delegate didAttachedWindowClose:self];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -294,6 +294,7 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
|
||||
|
||||
var textFrame = CGRectMakeZero();
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if (_DOMTextElement)
|
||||
{
|
||||
var textStyle = _DOMTextElement.style;
|
||||
@@ -306,6 +307,7 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
|
||||
textFrame.size.width += _textShadowOffset.width;
|
||||
textFrame.size.height += _textShadowOffset.height;
|
||||
}
|
||||
#endif
|
||||
|
||||
return textFrame;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,838 @@
|
||||
/*
|
||||
* _CPPopUpList.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Aparajita Fishman.
|
||||
* Copyright (c) 2011, Intalio, 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 "CPTableView.j"
|
||||
@import "_CPPopUpListDataSource.j"
|
||||
|
||||
|
||||
/*!
|
||||
Notification sent when the list is about to pop up. \c object is the _CPPopUpList.
|
||||
*/
|
||||
_CPPopUpListWillPopUpNotification = @"_CPPopUpListWillPopUpNotification";
|
||||
|
||||
/*!
|
||||
Notification sent when the list is about to be dismissed. \c object is the _CPPopUpList.
|
||||
*/
|
||||
_CPPopUpListWillDismissNotification = @"_CPPopUpListWillDismissNotification";
|
||||
|
||||
/*!
|
||||
Notification sent when the list is dismissed. \c object is the _CPPopUpList.
|
||||
*/
|
||||
_CPPopUpListDidDismissNotification = @"_CPPopUpListDidDismissNotification";
|
||||
|
||||
/*!
|
||||
Notification sent when the selection of the list changes. \c object is the _CPPopUpList.
|
||||
*/
|
||||
_CPPopUpListDidDismissNotification = @"_CPPopUpListDidDismissNotification";
|
||||
|
||||
/*!
|
||||
Notification sent when the list is dismissed. \c object is the _CPPopUpList.
|
||||
*/
|
||||
_CPPopUpListDidDismissNotification = @"_CPPopUpListDidDismissNotification";
|
||||
|
||||
/*!
|
||||
Notification sent by when an item is selected. \c object is the _CPPopUpList.
|
||||
When this is received the list has already been dismissed and the dismiss notification has been sent.
|
||||
*/
|
||||
_CPPopUpListItemWasClickedNotification = @"_CPPopUpListItemWasClickedNotification";
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
|
||||
The minimum number of items that must be visible below the related field.
|
||||
If less than this number would be completely visible, and there is room for this many complete items
|
||||
above the field, the list is displayed above.
|
||||
*/
|
||||
var ListMinimumItems = 3;
|
||||
|
||||
/*! @ignore */
|
||||
var ListColumnIdentifier = @"1";
|
||||
|
||||
|
||||
/*!
|
||||
This class is a controller for a panel that can pop up and display a scrollable list of items in a CPTableView.
|
||||
It is used by CPComboBox to display the list of choices.
|
||||
|
||||
This class requires a data source which must conform to the interface of _CPPopUpListDataSource.
|
||||
|
||||
Objects of this class send the following notifications:
|
||||
|
||||
_CPPopUpListWillPopUpNotification
|
||||
_CPPopUpListWillDismissNotification
|
||||
_CPPopUpListDidDismissNotification
|
||||
_CPPopUpListItemWasClickedNotification
|
||||
*/
|
||||
@implementation _CPPopUpList : CPObject
|
||||
{
|
||||
_CPPopUpListDataSource _dataSource;
|
||||
BOOL _itemWasClicked;
|
||||
BOOL _listWasClicked;
|
||||
int _listWidth;
|
||||
_CPPopUpPanel _panel;
|
||||
CPScrollView _scrollView;
|
||||
_CPPopUpTableView _tableView;
|
||||
CPTableColumn _tableColumn;
|
||||
}
|
||||
|
||||
#pragma mark Creating and Displaying a List
|
||||
|
||||
/*!
|
||||
Creates a pop up list of choices that will display in a scrollable CPTableView.
|
||||
|
||||
@param aDataSource A subclass of _CPPopUpListDataSource
|
||||
*/
|
||||
- (id)initWithDataSource:(_CPPopUpListDataSource)aDataSource
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setDataSource:aDataSource];
|
||||
_itemWasClicked = NO;
|
||||
_listWasClicked = NO;
|
||||
_listWidth = 0;
|
||||
|
||||
_tableView = [self makeTableView];
|
||||
|
||||
// Start with a default size, we will resize it later
|
||||
frame = CGRectMake(0, 0, 200, 200);
|
||||
|
||||
_tableColumn = [[CPTableColumn alloc] initWithIdentifier:ListColumnIdentifier];
|
||||
[_tableColumn setWidth:CGRectGetWidth(frame) - [CPScroller scrollerWidth]];
|
||||
[_tableColumn setResizingMask:CPTableColumnAutoresizingMask];
|
||||
[_tableView addTableColumn:_tableColumn];
|
||||
|
||||
_scrollView = [self makeScrollViewWithFrame:CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame))];
|
||||
[_scrollView setDocumentView:_tableView];
|
||||
|
||||
// This has to be done after setDocumentView so that the table knows which scroll view to update
|
||||
[_tableView setHeaderView:nil];
|
||||
|
||||
_panel = [self makeListPanelWithFrame:frame];
|
||||
[[_panel contentView] addSubview:_scrollView];
|
||||
[_panel setInitialFirstResponder:_tableView];
|
||||
|
||||
if ([_dataSource numberOfItemsInList:self] > 0)
|
||||
[_tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
|
||||
else
|
||||
[_tableView setEnabled:NO];
|
||||
|
||||
[_scrollView scrollToBeginningOfDocument:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (CPPanel)makeListPanelWithFrame:(CGRect)aFrame
|
||||
{
|
||||
var panel = [[_CPPopUpPanel alloc] initWithContentRect:aFrame styleMask:CPBorderlessWindowMask];
|
||||
|
||||
[panel setTitle:@""];
|
||||
[panel setFloatingPanel:YES];
|
||||
[panel setBecomesKeyOnlyIfNeeded:YES];
|
||||
[panel setHasShadow:YES];
|
||||
[panel setShadowStyle:CPMenuWindowShadowStyle];
|
||||
[panel setDelegate:self];
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (_CPPopUpTableView)makeTableView
|
||||
{
|
||||
[self removeTableViewObservers];
|
||||
|
||||
var table = [[_CPPopUpTableView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[table setDelegate:self];
|
||||
[table setDataSource:self];
|
||||
[table setColumnAutoresizingStyle:CPTableViewLastColumnOnlyAutoresizingStyle];
|
||||
[table setUsesAlternatingRowBackgroundColors:NO];
|
||||
[table setAllowsMultipleSelection:NO];
|
||||
[table setIntercellSpacing:CGSizeMake(3, 2)];
|
||||
[table setTarget:self];
|
||||
[table setDoubleAction:@selector(tableViewClickAction:)];
|
||||
[table setAction:@selector(tableViewClickAction:)];
|
||||
[table setRowHeight:[self rowHeightForTableView:table]];
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)removeTableViewObservers
|
||||
{
|
||||
if (_tableView)
|
||||
{
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
[defaultCenter removeObserver:self name:CPTableViewSelectionIsChangingNotification object:_tableView];
|
||||
[defaultCenter removeObserver:self name:CPTableViewSelectionDidChangeNotification object:_tableView];
|
||||
}
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (CPScrollView)makeScrollViewWithFrame:(CGRect)aFrame
|
||||
{
|
||||
var scroll = [[CPScrollView alloc] initWithFrame:aFrame];
|
||||
|
||||
[scroll setBorderType:CPLineBorder];
|
||||
[scroll setAutohidesScrollers:NO];
|
||||
[scroll setHasVerticalScroller:YES];
|
||||
[scroll setHasHorizontalScroller:NO];
|
||||
[scroll setLineScroll:[_tableView rowHeight]];
|
||||
[scroll setVerticalPageScroll:0.0];
|
||||
|
||||
return scroll;
|
||||
}
|
||||
|
||||
/*!
|
||||
Pop up the list if it is not already visible.
|
||||
If it is not visible, a _CPPopUpListWillPopUpNotification will be sent.
|
||||
|
||||
@param aRect A rect (in \c aView coordinates) to display relative to
|
||||
@param aView The view whose coordinate system \c aRect is in
|
||||
@param offset How far to offset the list from \c aRect
|
||||
*/
|
||||
- (void)popUpRelativeToRect:(CGRect)aRect view:(CPView)aView offset:(int)offset
|
||||
{
|
||||
if ([_panel isVisible])
|
||||
return;
|
||||
|
||||
var rowRect = [_tableView rectOfRow:[self numberOfRowsInTableView:_tableView] - 1],
|
||||
frame = CGRectMake(0, 0, MAX(_listWidth, CGRectGetWidth(aRect)), CGRectGetMaxY(rowRect));
|
||||
|
||||
// Place the frame relative to aRect and constrain it to the screen bounds
|
||||
frame = [self constrain:frame relativeToRect:aRect view:aView offset:offset];
|
||||
|
||||
[_panel setFrame:frame];
|
||||
[_scrollView setFrameSize:CGSizeMakeCopy(frame.size)];
|
||||
[_tableView setEnabled:[_dataSource numberOfItemsInList:self] > 0];
|
||||
[self scrollItemAtIndexToTop:[_tableView selectedRow]];
|
||||
|
||||
[self listWillPopUp];
|
||||
|
||||
[_panel orderFront:nil];
|
||||
}
|
||||
|
||||
#pragma mark Setting Display Attributes
|
||||
|
||||
/*!
|
||||
Returns the desired width of the list.
|
||||
*/
|
||||
- (int)listWidth
|
||||
{
|
||||
return _listWidth;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the desired width of the list for the next call to \ref showListForfield:relativeTo:.
|
||||
Note that the actual display width may be larger if the given width is less than the width of the associated
|
||||
field.
|
||||
*/
|
||||
- (void)setListWidth:(int)width
|
||||
{
|
||||
_listWidth = width;
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
var oldDataView = [_tableColumn dataView],
|
||||
newDataView = [CPTextField new];
|
||||
|
||||
[newDataView setFont:aFont];
|
||||
[newDataView setAlignment:[oldDataView alignment]];
|
||||
[_tableColumn setDataView:newDataView];
|
||||
|
||||
// Force the data view cache to flush
|
||||
[_tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)setAlignment:(CPTextAlignment)alignment
|
||||
{
|
||||
var oldDataView = [_tableColumn dataView],
|
||||
newDataView = [CPTextField new];
|
||||
|
||||
[newDataView setAlignment:alignment];
|
||||
[newDataView setFont:[oldDataView font]];
|
||||
[_tableColumn setDataView:newDataView];
|
||||
|
||||
// Force the data view cache to flush
|
||||
[_tableView reloadData];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the list is currently visible.
|
||||
*/
|
||||
- (BOOL)isVisible
|
||||
{
|
||||
return [_panel isVisible];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the desired row height for the table view.
|
||||
Subclasses should override this if they want something other than the default.
|
||||
*/
|
||||
- (int)rowHeightForTableView:(CPTableView)aTableView
|
||||
{
|
||||
return [aTableView rowHeight];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the table view used by the list.
|
||||
*/
|
||||
- (CPTableView)tableView
|
||||
{
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the single table column used by the list.
|
||||
*/
|
||||
- (CPTableColumn)tableColumn
|
||||
{
|
||||
return _tableColumn;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the scroll view used by the list.
|
||||
*/
|
||||
- (CPScrollView)scrollView
|
||||
{
|
||||
return _scrollView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the panel in which the list appears.
|
||||
*/
|
||||
- (CPPanel)panel
|
||||
{
|
||||
return _panel;
|
||||
}
|
||||
|
||||
#pragma mark Setting a Data Source
|
||||
|
||||
- (void)setDataSource:(_CPPopUpListDataSource)aDataSource
|
||||
{
|
||||
if (_dataSource === aDataSource)
|
||||
return;
|
||||
|
||||
if (![_CPPopUpListDataSource protocolIsImplementedByObject:aDataSource])
|
||||
{
|
||||
CPLog.warn("Illegal %s data source (%s). Must implement the methods in _CPPopUpListDataSource.", [self className], [aDataSource description]);
|
||||
}
|
||||
else
|
||||
_dataSource = aDataSource;
|
||||
}
|
||||
|
||||
- (_CPPopUpListDataSource)dataSource
|
||||
{
|
||||
return _dataSource;
|
||||
}
|
||||
|
||||
#pragma mark Manipulating the Selection
|
||||
|
||||
/*!
|
||||
Select the next item in the list if there one. If there is currently no selected item,
|
||||
the first item is selected. Returns YES if the selection changed.
|
||||
*/
|
||||
- (BOOL)selectNextItem
|
||||
{
|
||||
if (![_tableView isEnabled])
|
||||
return NO;
|
||||
|
||||
var row = [_tableView selectedRow];
|
||||
|
||||
if (row < ([_dataSource numberOfItemsInList:self] - 1))
|
||||
return [self selectRow:++row];
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Select the previous item in the list. If there is currently no selected item,
|
||||
nothing happens. Returns YES if the selection changed.
|
||||
*/
|
||||
- (BOOL)selectPreviousItem
|
||||
{
|
||||
if (![_tableView isEnabled])
|
||||
return NO;
|
||||
|
||||
var row = [_tableView selectedRow];
|
||||
|
||||
if (row > 0)
|
||||
return [self selectRow:--row];
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the selected object value. If no value is selected,
|
||||
returns nil.
|
||||
*/
|
||||
- (id)selectedObjectValue
|
||||
{
|
||||
var row = [_tableView selectedRow];
|
||||
|
||||
return (row >= 0) ? [_dataSource list:self objectValueForItemAtIndex:row] : nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the selected value as a single-line string. If no value is selected,
|
||||
returns nil.
|
||||
*/
|
||||
- (CPString)selectedStringValue
|
||||
{
|
||||
var value = [self selectedObjectValue];
|
||||
|
||||
return value !== nil ? [_dataSource list:self stringValueForObjectValue:value] : nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the last selected row in the list. If no row has been selected, returns -1.
|
||||
*/
|
||||
- (int)selectedRow
|
||||
{
|
||||
return [_tableView selectedRow];
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects a row and scrolls it to be visible. Returns YES if the selection actually changed.
|
||||
*/
|
||||
- (BOOL)selectRow:(int)row
|
||||
{
|
||||
if (row === [_tableView selectedRow])
|
||||
return NO;
|
||||
|
||||
var validRow = (row >= 0 && row < [self numberOfRowsInTableView:_tableView]),
|
||||
indexes = validRow ? [CPIndexSet indexSetWithIndex:row] : [CPIndexSet indexSet];
|
||||
|
||||
[_tableView selectRowIndexes:indexes byExtendingSelection:NO];
|
||||
|
||||
if (validRow)
|
||||
{
|
||||
[_tableView scrollRowToVisible:row];
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark Manipulating the Displayed List
|
||||
|
||||
/*!
|
||||
Scroll the list down one page.
|
||||
*/
|
||||
- (void)scrollPageDown
|
||||
{
|
||||
[_scrollView scrollPageDown:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Scroll the list up one page.
|
||||
*/
|
||||
- (void)scrollPageUp
|
||||
{
|
||||
[_scrollView scrollPageUp:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Scroll to the top of the list.
|
||||
*/
|
||||
- (void)scrollToTop
|
||||
{
|
||||
[_scrollView scrollToBeginningOfDocument:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Scroll to the bottom of the list.
|
||||
*/
|
||||
- (void)scrollToBottom
|
||||
{
|
||||
[_scrollView scrollToEndOfDocument:nil];
|
||||
}
|
||||
|
||||
- (void)scrollItemAtIndexToTop:(int)row
|
||||
{
|
||||
var rect = [_tableView rectOfRow:row];
|
||||
|
||||
[[_tableView superview] scrollToPoint:rect.origin];
|
||||
}
|
||||
|
||||
/*!
|
||||
Close the list if it is currently visible. If it is visible,
|
||||
a CPComboBoxWillDismissNotification will be sent. If the
|
||||
list is being closed after an item was clicked, the close
|
||||
is delayed slightly so the user can briefly see the clicked row
|
||||
get highlighted.
|
||||
*/
|
||||
- (void)close
|
||||
{
|
||||
if (![_panel isVisible])
|
||||
return;
|
||||
|
||||
if ([self listWasClicked])
|
||||
{
|
||||
[self setListWasClicked:NO];
|
||||
|
||||
// Wait until we get through the run loop and delay a little
|
||||
// so the user can briefly see the clicked row get highlighted.
|
||||
if ([self itemWasClicked])
|
||||
{
|
||||
[self setItemWasClicked:NO];
|
||||
[CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(closeListAfterItemClick) userInfo:nil repeats:NO];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:_CPPopUpListWillDismissNotification object:self];
|
||||
[_panel close];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:_CPPopUpListDidDismissNotification object:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Close the list after an item was clicked.
|
||||
*/
|
||||
- (void)closeListAfterItemClick
|
||||
{
|
||||
[self close];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:_CPPopUpListItemWasClickedNotification object:self];
|
||||
}
|
||||
|
||||
#pragma mark Handling Events
|
||||
|
||||
/*!
|
||||
Handles standard key equivalents for moving the selection
|
||||
and selecting an item. This method should be called by
|
||||
the -performKeyEquivalent method of the field that is
|
||||
controlling the list.
|
||||
*/
|
||||
- (BOOL)performKeyEquivalent:(CPEvent)anEvent
|
||||
{
|
||||
var key = [anEvent charactersIgnoringModifiers];
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case CPDownArrowFunctionKey:
|
||||
if ([self isVisible])
|
||||
{
|
||||
[self selectNextItem];
|
||||
return YES;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPUpArrowFunctionKey:
|
||||
if ([self isVisible])
|
||||
{
|
||||
[self selectPreviousItem];
|
||||
return YES;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPEscapeFunctionKey:
|
||||
if ([self isVisible])
|
||||
{
|
||||
[self close];
|
||||
return YES;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPPageUpFunctionKey:
|
||||
if ([self isVisible])
|
||||
{
|
||||
[self scrollPageUp];
|
||||
return YES;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPPageDownFunctionKey:
|
||||
if ([self isVisible])
|
||||
{
|
||||
[self scrollPageDown];
|
||||
return YES;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPHomeFunctionKey:
|
||||
if ([self isVisible])
|
||||
{
|
||||
[self scrollToTop];
|
||||
return YES;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPEndFunctionKey:
|
||||
if ([self isVisible])
|
||||
{
|
||||
[self scrollToBottom];
|
||||
return YES;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether an item in the list was clicked since it was opened.
|
||||
If there are no items, \ref itemWasClicked will always return NO.
|
||||
*/
|
||||
- (BOOL)itemWasClicked
|
||||
{
|
||||
return _itemWasClicked && ([_dataSource numberOfItemsInList:self] > 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether an item in the list was clicked since it was opened.
|
||||
If there are no items, \ref itemWasClicked will always return NO.
|
||||
|
||||
Subclasses will usually want to set this in the mouseDown:
|
||||
of the control.
|
||||
*/
|
||||
- (void)setItemWasClicked:(BOOL)flag
|
||||
{
|
||||
_itemWasClicked = ([_dataSource numberOfItemsInList:self] > 0) && flag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether any view in the list was clicked since it was opened.
|
||||
If there are no items, \ref listWasClicked will always return NO.
|
||||
*/
|
||||
- (BOOL)listWasClicked
|
||||
{
|
||||
return _listWasClicked && ([_dataSource numberOfItemsInList:self] > 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether any view in the list was clicked since it was opened.
|
||||
If there are no items, \ref listWasClicked will always return NO.
|
||||
|
||||
Subclasses will usually want to use a subclass of CPPanel and override
|
||||
sendEvent: to set this flag when the event type is CPLeftMouseDown
|
||||
or CPRightMouseDown. This is distinct from \ref itemWasClicked because,
|
||||
for example, a scroller in the list may be clicked without clicking an
|
||||
item in the list.
|
||||
*/
|
||||
- (void)setListWasClicked:(BOOL)flag
|
||||
{
|
||||
_listWasClicked = ([_dataSource numberOfItemsInList:self] > 0) && flag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether a controlling view should resign. This should be called
|
||||
from the controlling view's resignFirstResponder method.
|
||||
*/
|
||||
- (BOOL)controllingViewShouldResign
|
||||
{
|
||||
if ([self listWasClicked])
|
||||
{
|
||||
/*
|
||||
If an item was not clicked (probably the scrollbar), clear the click flag so that future
|
||||
clicks outside the list will allow it to close.
|
||||
*/
|
||||
if ([self listWasClicked] && ![self itemWasClicked])
|
||||
[self setListWasClicked:NO];
|
||||
|
||||
return NO;
|
||||
}
|
||||
else
|
||||
return YES;
|
||||
}
|
||||
#pragma mark Internal Helpers
|
||||
|
||||
/*! @ignore */
|
||||
- (void)listWillPopUp
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:_CPPopUpListWillPopUpNotification object:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return a frame in platform window base coordinates such that the list, when displayed, will show at least ListMinimumItems
|
||||
items completely on screen. Normally the list should be displayed below \c aRect, but if there is not room
|
||||
for at least ListMinimumItems items, an attempt should be made to display that many
|
||||
items above \c aRect. If the minimum cannot be displayed on top, whichever direction can display more items
|
||||
is chosen.
|
||||
@ignore
|
||||
*/
|
||||
- (CGRect)constrain:(CGRect)aFrame relativeToRect:(CGRect)aRect view:(CPView)aView offset:(int)offset
|
||||
{
|
||||
// Convert from the view's coordinate system to the coordinate system of the primary platform window
|
||||
var baseOrigin = [aView convertPointToBase:aRect.origin],
|
||||
windowOrigin = [[aView window] convertBaseToPlatformWindow:baseOrigin],
|
||||
rowHeight = [self rowHeightForTableView:_tableView] + [_tableView intercellSpacing].height,
|
||||
|
||||
// Be sure to clip the number of displayed rows to what the field wants
|
||||
numberOfRows = MIN([self numberOfRowsInTableView:_tableView], [_dataSource numberOfVisibleItemsInList:self]),
|
||||
|
||||
// Add 2 to height for border
|
||||
frame = CGRectMake(windowOrigin.x, windowOrigin.y + CGRectGetHeight(aRect) + offset, MAX(_listWidth, CGRectGetWidth(aFrame)), (rowHeight * numberOfRows) + 2),
|
||||
|
||||
// Get the bottom coordinate of the frame and the platform window
|
||||
bottomFrame = CGRectMakeCopy(frame),
|
||||
bottom = CGRectGetMaxY(bottomFrame),
|
||||
viewRect = [[CPPlatformWindow primaryPlatformWindow] visibleFrame],
|
||||
visibleBottom = CGRectGetMaxY(viewRect),
|
||||
bottomVisibleRows = numberOfRows;
|
||||
|
||||
// Make sure it will fit in the screen. If not, reduce the number of items till we reach the minimum.
|
||||
while (bottom > visibleBottom && bottomVisibleRows >= ListMinimumItems)
|
||||
{
|
||||
bottom -= rowHeight;
|
||||
bottomFrame.size.height -= rowHeight;
|
||||
--bottomVisibleRows;
|
||||
}
|
||||
|
||||
if (bottom >= visibleBottom || bottomVisibleRows < ListMinimumItems)
|
||||
{
|
||||
// The minimum number of items will not fit, try above
|
||||
var topFrame = CGRectMakeCopy(frame);
|
||||
|
||||
topFrame.origin.y = windowOrigin.y - offset - CGRectGetHeight(topFrame);
|
||||
|
||||
var visibleTop = CGRectGetMinY(viewRect),
|
||||
topVisibleRows = numberOfRows;
|
||||
|
||||
while (topFrame.origin.y <= visibleTop && topVisibleRows >= ListMinimumItems)
|
||||
{
|
||||
topFrame.origin.y += rowHeight;
|
||||
topFrame.size.height -= rowHeight;
|
||||
--topVisibleRows;
|
||||
}
|
||||
|
||||
// If there is room on the top or it can display more than at the bottom, show it there
|
||||
if ((topFrame.origin.y > visibleTop && topVisibleRows >= ListMinimumItems) || topVisibleRows > bottomVisibleRows)
|
||||
frame = topFrame;
|
||||
else
|
||||
frame = bottomFrame;
|
||||
}
|
||||
else
|
||||
frame = bottomFrame;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
- (void)tableViewClickAction:(id)sender
|
||||
{
|
||||
[self close];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var _CPPopUpListDataSourceKey = @"_CPPopUpListDataSourceKey",
|
||||
_CPPopUpListListWidthKey = @"_CPPopUpListListWidthKey",
|
||||
_CPPopUpListListPanelKey = @"_CPPopUpListListPanelKey",
|
||||
_CPPopUpListScrollViewKey = @"_CPPopUpListScrollViewKey",
|
||||
_CPPopUpListTableViewKey = @"_CPPopUpListTableViewKey";
|
||||
|
||||
@implementation _CPPopUpList (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_listWasClicked = NO;
|
||||
_itemWasClicked = NO;
|
||||
|
||||
_dataSource = [aCoder decodeObjectForKey:_CPPopUpListDataSourceKey];
|
||||
_listWidth = [aCoder decodeIntForKey:_CPPopUpListListWidthKey];
|
||||
_panel = [aCoder decodeObjectForKey:_CPPopUpListListPanelKey];
|
||||
_scrollView = [aCoder decodeObjectForKey:_CPPopUpListScrollViewKey];
|
||||
_tableView = [aCoder decodeObjectForKey:_CPPopUpListTableViewKey];
|
||||
_tableColumn = [_tableView tableColumnWithIdentifier:ListColumnIdentifier];
|
||||
[_scrollView setDocumentView:_tableView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_dataSource forKey:_CPPopUpListDataSourceKey];
|
||||
[aCoder encodeObject:_listWidth forKey:_CPPopUpListListWidthKey];
|
||||
[aCoder encodeObject:_panel forKey:_CPPopUpListListPanelKey];
|
||||
[aCoder encodeObject:_scrollView forKey:_CPPopUpListScrollViewKey];
|
||||
[aCoder encodeObject:_tableView forKey:_CPPopUpListTableViewKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPPopUpList (CPTableViewDataSource)
|
||||
|
||||
- (int)numberOfRowsInTableView:(id)aTableView
|
||||
{
|
||||
return MAX([_dataSource numberOfItemsInList:self], 1);
|
||||
}
|
||||
|
||||
- (id)tableView:(id)aTableView objectValueForTableColumn:(CPTableColumn)aColumn row:(int)aRow
|
||||
{
|
||||
return [_dataSource list:self displayValueForObjectValue:[_dataSource list:self objectValueForItemAtIndex:aRow]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPPopUpTableView : CPTableView
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
[[self delegate] setItemWasClicked:YES];
|
||||
[super mouseDown:anEvent];
|
||||
}
|
||||
|
||||
/*
|
||||
We always want the autocomplete to have focus
|
||||
*/
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the column used for the list.
|
||||
*/
|
||||
- (CPTableColumn)listColumn
|
||||
{
|
||||
return _tableColumn;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPPopUpPanel : CPPanel
|
||||
|
||||
- (void)sendEvent:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type];
|
||||
|
||||
if (type === CPLeftMouseDown || type === CPRightMouseDown)
|
||||
[[self delegate] setListWasClicked:YES];
|
||||
|
||||
return [super sendEvent:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* _CPPopUpListDataSource.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Aparajita Fishman.
|
||||
* Copyright (c) 2011, Intalio, 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
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
This abstract base class defines the methods that delegates of _CPPopUpList must implement.
|
||||
You may either subclass this class to use the default implementation of
|
||||
list:objectValueForItemAtIndex: and list:displayValueForObjectValue: or use your
|
||||
own class and define these methods yourself.
|
||||
*/
|
||||
@implementation _CPPopUpListDataSource : CPObject
|
||||
|
||||
/*!
|
||||
Returns whether the given object conforms to the minimum protocol defined by this class.
|
||||
*/
|
||||
+ (BOOL)protocolIsImplementedByObject:(id)anObject
|
||||
{
|
||||
return (anObject &&
|
||||
[anObject respondsToSelector:@selector(numberOfItemsInList:)] &&
|
||||
[anObject respondsToSelector:@selector(numberOfVisibleItemsInList:)] &&
|
||||
[anObject respondsToSelector:@selector(list:objectValueForItemAtIndex:)] &&
|
||||
[anObject respondsToSelector:@selector(list:displayValueForObjectValue:)] &&
|
||||
[anObject respondsToSelector:@selector(list:stringValueForObjectValue:)]);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of items managed by the list.
|
||||
*/
|
||||
- (int)numberOfItemsInList:(_CPPopUpList)aList
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of items to display at one time.
|
||||
*/
|
||||
- (int)numberOfVisibleItemsInList:(_CPPopUpList)aList
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the data for a given row index.
|
||||
*/
|
||||
- (id)list:(_CPPopUpList)aList objectValueForItemAtIndex:(int)index
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a value to display for a single row in the list. Subclasses should override
|
||||
this if the table data needs to be converted or formatted in some way to be displayed.
|
||||
If your data source use a data representation other than CPStrings, you must override
|
||||
this method and return the appropriate data when there are no search results.
|
||||
|
||||
If the _CPPopUpList's table uses a custom data view, this method should return a value suitable
|
||||
for sending to the setObjectValue: method of the data view.
|
||||
|
||||
@param aValue Data for the given row
|
||||
@return A value to be displayed in the list
|
||||
*/
|
||||
- (id)list:(_CPPopUpList)aList displayValueForObjectValue:(id)aValue
|
||||
{
|
||||
return aValue || @"";
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a single-line string representation for an object value. Subclasses should override
|
||||
this if the object data is not convertible to a simple single-line string.
|
||||
|
||||
@param aValue Table data to be converted to a string
|
||||
@return A value to be displayed in the autocomplete field
|
||||
*/
|
||||
- (CPString)list:(_CPPopUpList)aList stringValueForObjectValue:(id)aValue
|
||||
{
|
||||
return String(aValue);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), "repositories.rb")
|
||||
require File.join(File.dirname(__FILE__), "dependencies.rb")
|
||||
|
||||
# Keep this structure to allow the build system to update version numbers.
|
||||
VERSION_NUMBER = "1.0.0.006"
|
||||
VERSION_NUMBER = "1.0.0.021"
|
||||
|
||||
|
||||
# Shorten expressions
|
||||
@@ -14,7 +14,7 @@ desc "Cappuccino Distribution"
|
||||
define "cappuccino" do
|
||||
project.version = VERSION_NUMBER
|
||||
project.group = "com.intalio.cloud.cappuccino"
|
||||
|
||||
|
||||
file(_("target/patch")).enhance do
|
||||
mkdir_p "target/patch"
|
||||
end
|
||||
@@ -44,7 +44,7 @@ BASH
|
||||
package(:jar).with :manifest => read_m
|
||||
#package(:jar).include _("target/patch"), :as => "patch"
|
||||
package(:jar).include _("target/Frameworks"), :as => "META-INF/resources/Frameworks"
|
||||
|
||||
|
||||
#do not include those: they are at best not read harmful.
|
||||
#package(:bundle).include _("build.properties"), :as => "build.properties"
|
||||
#package(:bundle).meta_inf << file('META-INF/web-fragment.xml')
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
|
||||
_replaceManySEL = sel_getName(@"replace" + capitalizedKey + "AtIndexes:with" + capitalizedKey + ":");
|
||||
if ([_proxyObject respondsToSelector:_replaceManySEL])
|
||||
_replace = [_proxyObject methodForSelector:_replaceManySEL];
|
||||
_replaceMany = [_proxyObject methodForSelector:_replaceManySEL];
|
||||
|
||||
_objectAtIndexSEL = sel_getName(@"objectIn" + capitalizedKey + "AtIndex:");
|
||||
if ([_proxyObject respondsToSelector:_objectAtIndexSEL])
|
||||
@@ -233,10 +233,25 @@
|
||||
|
||||
- (id)objectAtIndex:(unsigned)anIndex
|
||||
{
|
||||
if (_objectAtIndex)
|
||||
return _objectAtIndex(_proxyObject, _objectAtIndexSEL, anIndex);
|
||||
return [[self objectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]] firstObject];
|
||||
}
|
||||
|
||||
return [[self _representedObject] objectAtIndex:anIndex];
|
||||
- (CPArray)objectsAtIndexes:(CPIndexSet)theIndexes
|
||||
{
|
||||
if (_objectsAtIndexes)
|
||||
return _objectsAtIndexes(_proxyObject, _objectsAtIndexesSEL, theIndexes);
|
||||
if (_objectAtIndex)
|
||||
{
|
||||
var index = CPNotFound,
|
||||
objects = [];
|
||||
|
||||
while ((index = [theIndexes indexGreaterThanIndex:index]) !== CPNotFound)
|
||||
objects.push(_objectAtIndex(_proxyObject, _objectAtIndexSEL, index));
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
return [[self _representedObject] objectsAtIndexes:theIndexes];
|
||||
}
|
||||
|
||||
- (void)addObject:(id)anObject
|
||||
@@ -288,37 +303,111 @@
|
||||
[self removeObject:anObject inRange:CPMakeRange(0, [self count])];
|
||||
}
|
||||
|
||||
- (void)removeLastObject
|
||||
- (void)removeObjectsInArray:(CPArray)theObjects
|
||||
{
|
||||
if (_removeMany)
|
||||
{
|
||||
var indexes = [CPIndexSet indexSet],
|
||||
index = [theObjects count];
|
||||
|
||||
while (index--)
|
||||
[indexes addIndex:[self indexOfObject:[theObjects objectAtIndex:index]]];
|
||||
|
||||
_removeMany(_proxyObject, _removeManySEL, indexes);
|
||||
}
|
||||
else if (_remove)
|
||||
{
|
||||
var index = [theObjects count];
|
||||
while (index--)
|
||||
_remove(_proxyObject, _removeSEL, [self indexOfObject:[theObjects objectAtIndex:index]]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var target = [[self _representedObject] copy];
|
||||
[target removeObjectsInArray:theObjects];
|
||||
[self _setRepresentedObject:target];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeObject:(id)theObject inRange:(CPRange)theRange
|
||||
{
|
||||
if (_remove)
|
||||
return _remove(_proxyObject, _removeSEL, [self count] - 1);
|
||||
_remove(_proxyObject, _removeSEL, [self indexOfObject:theObject inRange:theRange]);
|
||||
else if (_removeMany)
|
||||
{
|
||||
var index = [self indexOfObject:theObject inRange:theRange];
|
||||
_removeMany(_proxyObject, _removeManySEL, [CPIndexSet indexSetWithIndex:index]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var index;
|
||||
|
||||
var target = [[self _representedObject] copy];
|
||||
while ((index = [self indexOfObject:theObject inRange:theRange]) !== CPNotFound)
|
||||
{
|
||||
[self removeObjectAtIndex:index];
|
||||
theRange = CPIntersectionRange(CPMakeRange(index, length - index), theRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[target removeLastObject];
|
||||
[self _setRepresentedObject:target];
|
||||
- (void)removeLastObject
|
||||
{
|
||||
[self removeObjectsAtIndexes:[CPIndexSet indexSetWithIndex:[self count] - 1]];
|
||||
}
|
||||
|
||||
- (void)removeObjectAtIndex:(unsigned)anIndex
|
||||
{
|
||||
if (_remove)
|
||||
return _remove(_proxyObject, _removeSEL, anIndex);
|
||||
[self removeObjectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex]];
|
||||
}
|
||||
|
||||
var target = [[self _representedObject] copy];
|
||||
- (void)removeObjectsAtIndexes:(CPIndexSet)theIndexes
|
||||
{
|
||||
if (_removeMany)
|
||||
_removeMany(_proxyObject, _removeManySEL, theIndexes);
|
||||
else if (_remove)
|
||||
{
|
||||
var index = [theIndexes lastIndex];
|
||||
|
||||
[target removeObjectAtIndex:anIndex];
|
||||
[self _setRepresentedObject:target];
|
||||
while (index !== CPNotFound)
|
||||
{
|
||||
_remove(_proxyObject, _removeSEL, index)
|
||||
index = [theIndexes indexLessThanIndex:index];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var target = [[self _representedObject] copy];
|
||||
[target removeObjectsAtIndexes:theIndexes];
|
||||
[self _setRepresentedObject:target];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)replaceObjectAtIndex:(unsigned)anIndex withObject:(id)anObject
|
||||
{
|
||||
if (_replace)
|
||||
return _replace(_proxyObject, _replaceSEL, anIndex, anObject);
|
||||
[self replaceObjectsAtIndexes:[CPIndexSet indexSetWithIndex:anIndex] withObjects:[anObject]]
|
||||
}
|
||||
|
||||
var target = [[self _representedObject] copy];
|
||||
- (void)replaceObjectsAtIndexes:(CPIndexSet)theIndexes withObjects:(CPArray)theObjects
|
||||
{
|
||||
if (_replaceMany)
|
||||
return _replaceMany(_proxyObject, _replaceManySEL, theIndexes, theObjects);
|
||||
else if (_replace)
|
||||
{
|
||||
var i = 0,
|
||||
index = [theIndexes firstIndex];
|
||||
|
||||
[target replaceObjectAtIndex:anIndex withObject:anObject];
|
||||
[self _setRepresentedObject:target];
|
||||
while (index !== CPNotFound)
|
||||
{
|
||||
_replace(_proxyObject, _replaceSEL, index, [theObjects objectAtIndex:i++]);
|
||||
index = [theIndexes indexGreaterThanIndex:index];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var target = [[self _representedObject] copy];
|
||||
[target replaceObjectsAtIndexes:theIndexes withObjects:theObjects];
|
||||
[self _setRepresentedObject:target];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -362,7 +451,7 @@
|
||||
|
||||
- (id)valueForKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
if (aKeyPath.indexOf("@") === 0)
|
||||
if (aKeyPath.charAt(0) === "@")
|
||||
{
|
||||
var dotIndex = aKeyPath.indexOf("."),
|
||||
operator,
|
||||
|
||||
@@ -156,8 +156,7 @@ var concat = Array.prototype.concat,
|
||||
- (void)makeObjectsPerformSelector:(SEL)aSelector withObjects:(CPArray)objects
|
||||
{
|
||||
if (!aSelector)
|
||||
[CPException raise:CPInvalidArgumentException
|
||||
reason:"makeObjectsPerformSelector:withObjects: 'aSelector' can't be nil"];
|
||||
_CPRaiseInvalidArgumentException(self, _cmd, 'attempt to pass a nil selector');
|
||||
|
||||
var index = 0,
|
||||
count = self.length;
|
||||
@@ -198,7 +197,7 @@ var concat = Array.prototype.concat,
|
||||
- (CPArray)subarrayWithRange:(CPRange)aRange
|
||||
{
|
||||
if (aRange.location < 0 || CPMaxRange(aRange) > self.length)
|
||||
[CPException raise:CPRangeException reason:"subarrayWithRange: aRange out of bounds"];
|
||||
[CPException raise:CPRangeException reason:_cmd + " aRange out of bounds"];
|
||||
|
||||
return slice.call(self, aRange.location, CPMaxRange(aRange));
|
||||
}
|
||||
@@ -208,13 +207,19 @@ var concat = Array.prototype.concat,
|
||||
return join.call(self, aString);
|
||||
}
|
||||
|
||||
- (void)insertObject:(id)anObject atIndex:(CPUInteger)anIndex
|
||||
- (void)insertObject:(id)anObject atIndex:(int)anIndex
|
||||
{
|
||||
if (anIndex > self.length || anIndex < 0)
|
||||
_CPRaiseRangeException(self, _cmd, anIndex, self.length);
|
||||
|
||||
splice.call(self, anIndex, 0, anObject);
|
||||
}
|
||||
|
||||
- (void)removeObjectAtIndex:(CPUInteger)anIndex
|
||||
- (void)removeObjectAtIndex:(int)anIndex
|
||||
{
|
||||
if (anIndex >= self.length || anIndex < 0)
|
||||
_CPRaiseRangeException(self, _cmd, anIndex, self.length);
|
||||
|
||||
splice.call(self, anIndex, 1);
|
||||
}
|
||||
|
||||
@@ -235,16 +240,28 @@ var concat = Array.prototype.concat,
|
||||
|
||||
- (void)removeObjectsInRange:(CPRange)aRange
|
||||
{
|
||||
if (aRange.location < 0 || CPMaxRange(aRange) > self.length)
|
||||
[CPException raise:CPRangeException reason:_cmd + " aRange out of bounds"];
|
||||
|
||||
splice.call(self, aRange.location, aRange.length);
|
||||
}
|
||||
|
||||
- (void)replaceObjectAtIndex:(int)anIndex withObject:(id)anObject
|
||||
{
|
||||
if (anIndex >= self.length || anIndex < 0)
|
||||
_CPRaiseRangeException(self, _cmd, anIndex, self.length);
|
||||
|
||||
self[anIndex] = anObject;
|
||||
}
|
||||
|
||||
- (void)replaceObjectsInRange:(CPRange)aRange withObjectsFromArray:(CPArray)anArray range:(CPRange)otherRange
|
||||
{
|
||||
if (aRange.location < 0 || CPMaxRange(aRange) > self.length)
|
||||
[CPException raise:CPRangeException reason:_cmd + " aRange out of bounds"];
|
||||
|
||||
if (otherRange && (otherRange.location < 0 || CPMaxRange(otherRange) > anArray.length))
|
||||
[CPException raise:CPRangeException reason:_cmd + " otherRange out of bounds"];
|
||||
|
||||
if (otherRange && (otherRange.location !== 0 || otherRange.length !== [anArray count]))
|
||||
anArray = [anArray subarrayWithRange:otherRange];
|
||||
|
||||
|
||||
@@ -174,6 +174,27 @@ var _builtInCharacterSets = {};
|
||||
|
||||
@end
|
||||
|
||||
var CPCharacterSetInvertedKey = @"CPCharacterSetInvertedKey";
|
||||
|
||||
@implementation CPCharacterSet (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_inverted = [aCoder decodeBoolForKey:CPCharacterSetInvertedKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeBool:_inverted forKey:CPCharacterSetInvertedKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// A character set that stores a list of ranges of
|
||||
// acceptable characters.
|
||||
@implementation _CPRangeCharacterSet : CPCharacterSet
|
||||
@@ -342,6 +363,48 @@ var _builtInCharacterSets = {};
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(CPCharacterSet)aCharacterSet
|
||||
{
|
||||
if (self === aCharacterSet)
|
||||
return YES;
|
||||
|
||||
if (!aCharacterSet || ![aCharacterSet isKindOfClass:[self class]])
|
||||
return NO;
|
||||
|
||||
return [self _isEqualToStringContentCharacterSet:aCharacterSet];
|
||||
}
|
||||
|
||||
- (BOOL)_isEqualToStringContentCharacterSet:(_CPStringContentCharacterSet)aCharacterSet
|
||||
{
|
||||
if (!aCharacterSet)
|
||||
return NO;
|
||||
|
||||
return _string == aCharacterSet._string && _inverted == aCharacterSet._inverted;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPStringContentCharacterSetStringKey = @"_CPStringContentCharacterSetStringKey";
|
||||
|
||||
@implementation _CPStringContentCharacterSet (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_string = [aCoder decodeObjectForKey:_CPStringContentCharacterSetStringKey]
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_string forKey:_CPStringContentCharacterSetStringKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
_CPCharacterSetTrimAtBeginning = 1 << 1;
|
||||
|
||||
@@ -176,11 +176,18 @@
|
||||
for (; i < count; i++)
|
||||
{
|
||||
var thisValue = value[i];
|
||||
|
||||
if (thisValue.constructor === Object)
|
||||
newValue.push([CPDictionary dictionaryWithJSObject:thisValue recursively:YES]);
|
||||
|
||||
if (thisValue === null)
|
||||
{
|
||||
newValue.push([CPNull null]);
|
||||
}
|
||||
else
|
||||
newValue.push(thisValue);
|
||||
{
|
||||
if (thisValue.constructor === Object)
|
||||
newValue.push([CPDictionary dictionaryWithJSObject:thisValue recursively:YES]);
|
||||
else
|
||||
newValue.push(thisValue);
|
||||
}
|
||||
}
|
||||
|
||||
value = newValue;
|
||||
|
||||
@@ -228,6 +228,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
id _targetObject;
|
||||
Class _nativeClass;
|
||||
CPDictionary _changesForKey;
|
||||
CPDictionary _nestingForKey;
|
||||
Object _observersForKey;
|
||||
int _observersForKeyLength;
|
||||
CPSet _replacedKeys;
|
||||
@@ -251,6 +252,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
_nativeClass = [aTarget class];
|
||||
_observersForKey = {};
|
||||
_changesForKey = {};
|
||||
_nestingForKey = {};
|
||||
_observersForKeyLength = 0;
|
||||
|
||||
[self _replaceClass];
|
||||
@@ -715,6 +717,18 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
|
||||
if (isBefore)
|
||||
{
|
||||
if (changes)
|
||||
{
|
||||
// "willChange:X" nesting.
|
||||
var level = _nestingForKey[aKey];
|
||||
if (!level)
|
||||
[CPException raise:CPInternalInconsistencyException reason:@"_changesForKey without _nestingForKey"];
|
||||
_nestingForKey[aKey] = level + 1;
|
||||
// Only notify on the first willChange..., silently note any following nested calls.
|
||||
return;
|
||||
}
|
||||
_nestingForKey[aKey] = 1;
|
||||
|
||||
changes = changeOptions;
|
||||
|
||||
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
|
||||
@@ -772,9 +786,19 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!changes)
|
||||
var level = _nestingForKey[aKey];
|
||||
if (!changes || !level)
|
||||
[CPException raise:@"CPKeyValueObservingException" reason:@"'didChange...' message called without prior call of 'willChange...'"];
|
||||
|
||||
_nestingForKey[aKey] = level - 1;
|
||||
if (level - 1 > 0)
|
||||
{
|
||||
// willChange... was called multiple times. Only fire observation notifications when
|
||||
// didChange... has been called an equal number of times.
|
||||
return;
|
||||
}
|
||||
delete _nestingForKey[aKey];
|
||||
|
||||
[changes removeObjectForKey:CPKeyValueChangeNotificationIsPriorKey];
|
||||
|
||||
var indexes = [changes objectForKey:CPKeyValueChangeIndexesKey],
|
||||
@@ -812,6 +836,8 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
|
||||
[changes setObject:newValue forKey:CPKeyValueChangeNewKey];
|
||||
}
|
||||
|
||||
delete _changesForKey[aKey];
|
||||
}
|
||||
|
||||
var observers = [_observersForKey[aKey] allValues],
|
||||
|
||||
@@ -306,12 +306,17 @@ var CPStringRegexSpecialCharacters = [
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a substring from the beginning of the receiver to the specified index.
|
||||
@param anIndex the last index of the receiver to use for the substring (inclusive)
|
||||
Creates a substring of characters from the receiver, starting at the beginning and up to
|
||||
the given index.
|
||||
|
||||
@param anIndex the index of the receiver where the substring should end (non inclusive)
|
||||
@return the substring
|
||||
*/
|
||||
- (CPString)substringToIndex:(unsigned)anIndex
|
||||
{
|
||||
if (anIndex > length)
|
||||
[CPException raise:CPRangeException reason:"index out of bounds"];
|
||||
|
||||
return substring(0, anIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ task ("sudo-install", ["CommonJS"], function()
|
||||
if (OS.system(["sudo", "tusk", "install", "--force", $BUILD_CJS_OBJECTIVE_J, $BUILD_CJS_CAPPUCCINO]))
|
||||
{
|
||||
// Attempt a hackish work-around for sudo compiled with the --with-secure-path option
|
||||
sudo("tusk install --force " + $BUILD_CJS_OBJECTIVE_J + " " + $BUILD_CJS_CAPPUCCINO)
|
||||
sudo("tusk install --force " + $BUILD_CJS_OBJECTIVE_J + " " + $BUILD_CJS_CAPPUCCINO);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -71,10 +71,38 @@ task ("install-symlinks", function()
|
||||
|
||||
task ("install-debug-symlinks", function()
|
||||
{
|
||||
SYSTEM.env["CONFIG"] = "Debug";
|
||||
SYSTEM.env.CONFIG = "Debug";
|
||||
spawnJake("install-symlinks");
|
||||
});
|
||||
|
||||
task ("clean-sprites", function()
|
||||
{
|
||||
var f = new FileList(FILE.join(SYSTEM.env.CAPP_BUILD, "**/dataURLs.txt")),
|
||||
paths = f.items();
|
||||
|
||||
f = new FileList(FILE.join(SYSTEM.env.CAPP_BUILD, "**/MHTML*.txt"));
|
||||
paths = paths.concat(f.items());
|
||||
|
||||
paths.forEach(function(path)
|
||||
{
|
||||
FILE.remove(path);
|
||||
});
|
||||
});
|
||||
|
||||
task ("clobber-theme", function()
|
||||
{
|
||||
var f = new FileList(FILE.join(SYSTEM.env.CAPP_BUILD, "**/Aristo.blend")),
|
||||
paths = f.items();
|
||||
|
||||
f = new FileList(FILE.join(SYSTEM.env.CAPP_BUILD, "Aristo.build"));
|
||||
paths = paths.concat(f.items());
|
||||
|
||||
paths.forEach(function(path)
|
||||
{
|
||||
rm_rf(path);
|
||||
});
|
||||
});
|
||||
|
||||
// Documentation
|
||||
|
||||
$DOCUMENTATION_BUILD = FILE.join($BUILD_DIR, "Documentation");
|
||||
@@ -160,6 +188,10 @@ function generateDocs(/* boolean */ noFrame)
|
||||
rm_rf($DOCUMENTATION_BUILD);
|
||||
mv("debug.txt", FILE.join("Documentation", "debug.txt"));
|
||||
mv("Documentation", $DOCUMENTATION_BUILD);
|
||||
|
||||
// There is a bug in doxygen 1.7.x preventing loading correctly the custom CSS
|
||||
// So let's do it manually
|
||||
cp(FILE.join(documentationDir, "doxygen.css"), FILE.join($DOCUMENTATION_BUILD, "html", "doxygen.css"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,30 +279,30 @@ task ("demos", function()
|
||||
if (key)
|
||||
return this._plist.valueForKey(key);
|
||||
return this._plist;
|
||||
}
|
||||
};
|
||||
|
||||
Demo.prototype.name = function()
|
||||
{
|
||||
return this.plist("CPBundleName");
|
||||
}
|
||||
};
|
||||
|
||||
Demo.prototype.path = function()
|
||||
{
|
||||
return this._path;
|
||||
}
|
||||
};
|
||||
|
||||
Demo.prototype.excluded = function()
|
||||
{
|
||||
return !!this.plist("CPDemoExcluded");
|
||||
}
|
||||
};
|
||||
|
||||
Demo.prototype.toString = function()
|
||||
{
|
||||
return this.name();
|
||||
}
|
||||
};
|
||||
|
||||
FILE.glob(FILE.join(demosDir, "demos", "**/Info.plist")).map(function(demoPath){
|
||||
return new Demo(FILE.dirname(demoPath))
|
||||
return new Demo(FILE.dirname(demoPath));
|
||||
}).filter(function(demo){
|
||||
return !demo.excluded();
|
||||
}).forEach(function(demo)
|
||||
@@ -315,7 +347,7 @@ task("push-cappuccino", function() {
|
||||
pushPackage(
|
||||
$BUILD_CJS_CAPPUCCINO,
|
||||
"git@github.com:280north/cappuccino-package.git",
|
||||
SYSTEM.env["PACKAGE_BRANCH"]
|
||||
SYSTEM.env.PACKAGE_BRANCH
|
||||
);
|
||||
});
|
||||
|
||||
@@ -323,7 +355,7 @@ task("push-objective-j", function() {
|
||||
pushPackage(
|
||||
$BUILD_CJS_OBJECTIVE_J,
|
||||
"git@github.com:280north/objective-j-package.git",
|
||||
SYSTEM.env["PACKAGE_BRANCH"]
|
||||
SYSTEM.env.PACKAGE_BRANCH
|
||||
);
|
||||
});
|
||||
|
||||
@@ -361,7 +393,7 @@ function pushPackage(path, remote, branch)
|
||||
|
||||
var pkg = JSON.parse(packagePath.join("package.json").read({ charset : "UTF-8" }));
|
||||
|
||||
stream.print(" Version: " + colorize(pkg["version"], "purple"));
|
||||
stream.print(" Version: " + colorize(pkg.version, "purple"));
|
||||
stream.print(" Revision: " + colorize(pkg["cappuccino-revision"], "purple"));
|
||||
stream.print(" Timestamp: " + colorize(pkg["cappuccino-timestamp"], "purple"));
|
||||
|
||||
|
||||
@@ -464,6 +464,45 @@
|
||||
[self assert:1 equals:[observations count] message:@"exactly 1 notification for insertObject x 2 (clearsFilterPredicate YES)"];
|
||||
}
|
||||
|
||||
/**
|
||||
Replicate the situation found in the Bindings manual test where one array controller's
|
||||
contents depend on the selection of another.
|
||||
*/
|
||||
- (void)testObservationBetweenBoundControllers
|
||||
{
|
||||
var companies = [Companies new],
|
||||
companiesController = [CPArrayController new],
|
||||
employeesController = [CPArrayController new];
|
||||
|
||||
[companiesController bind:@"contentArray" toObject:companies withKeyPath:@"items" options:nil];
|
||||
[employeesController bind:@"contentArray" toObject:companiesController withKeyPath:@"selection.employees" options:nil];
|
||||
|
||||
[companiesController addObserver:self forKeyPath:@"selection" options:0 context:@"companies.selection"];
|
||||
[companiesController addObserver:self forKeyPath:@"selectionIndexes" options:0 context:@"companies.selectionIndexes"];
|
||||
[companiesController addObserver:self forKeyPath:@"arrangedObjects" options:0 context:@"companies.arrangedObjects"];
|
||||
[employeesController addObserver:self forKeyPath:@"selectionIndexes" options:0 context:@"employees.selectionIndexes"];
|
||||
[employeesController addObserver:self forKeyPath:@"selection" options:0 context:@"employees.selection"];
|
||||
[employeesController addObserver:self forKeyPath:@"arrangedObjects" options:0 context:@"employees.arrangedObjects"];
|
||||
|
||||
observations = [];
|
||||
|
||||
[companiesController setSelectionIndexes:[CPIndexSet indexSetWithIndex:1]];
|
||||
|
||||
//for (var i = 0; i < [observations count]; i++)
|
||||
// CPLog.error(observations[i].context);
|
||||
|
||||
// There should be exactly one observation of each kind, and they should be in the right
|
||||
// order.
|
||||
[self assert:5 equals:[observations count]];
|
||||
[self assert:@"companies.selectionIndexes" equals:observations[0].context];
|
||||
[self assert:@"employees.arrangedObjects" equals:observations[1].context];
|
||||
[self assert:@"employees.selectionIndexes" equals:observations[2].context];
|
||||
[self assert:@"employees.selection" equals:observations[3].context];
|
||||
// This observation registers after the employees update because we are later in the
|
||||
// observation queue than the binding.
|
||||
[self assert:@"companies.selection" equals:observations[4].context];
|
||||
}
|
||||
|
||||
- (void)testCompoundKeyPaths
|
||||
{
|
||||
var departmentNameField = [[CPTextField alloc] init];
|
||||
@@ -524,6 +563,18 @@
|
||||
[self assert:[1, 2, 1] equals:[ac arrangedObjects] message:"only one copy of 1 removed + the right copy should be removed"];
|
||||
}
|
||||
|
||||
- (void)testSetOutOfBoundsSelectedIndexes
|
||||
{
|
||||
var ac = [CPArrayController new],
|
||||
contentArray = [1, 2, 3],
|
||||
indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 4)];
|
||||
|
||||
[ac setContent:contentArray];
|
||||
[ac setSelectionIndexes:indexes];
|
||||
[self assert:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 3)] equals:[ac selectionIndexes]];
|
||||
[self assert:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 4)] equals:indexes];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:keyPath
|
||||
ofObject:anActivity
|
||||
change:change
|
||||
@@ -534,6 +585,7 @@
|
||||
|
||||
[observations addObject:{
|
||||
keyPath: keyPath,
|
||||
context: context,
|
||||
oldValue: [change valueForKey:CPKeyValueChangeOldKey],
|
||||
newValue: [change valueForKey:CPKeyValueChangeNewKey]
|
||||
}];
|
||||
@@ -620,4 +672,25 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
@implementation Companies : CPObject
|
||||
{
|
||||
CPMutableArray items @accessors;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
items = [CPMutableArray array];
|
||||
[items addObject:[CPDictionary dictionaryWithObjectsAndKeys:@"Spacely Sprockets", @"name", [CPArray arrayWithObjects:@"Tom", @"Dick", @"Harry"], @"employees"]];
|
||||
[items addObject:[CPDictionary dictionaryWithObjectsAndKeys:@"Cogswell Cogs", @"name", [CPArray arrayWithObjects:@"Jane", @"Mary", @"Vic"], @"employees"]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
@import <AppKit/CPTokenField.j>
|
||||
|
||||
@implementation CPTokenFieldTest : OJTestCase
|
||||
{
|
||||
}
|
||||
|
||||
- (void)testArchiving
|
||||
{
|
||||
var tokenField = [CPTokenField new];
|
||||
[tokenField setCompletionDelay:5.0];
|
||||
[tokenField setTokenizingCharacterSet:[CPCharacterSet characterSetWithCharactersInString:@","]];
|
||||
|
||||
var archived = [CPKeyedArchiver archivedDataWithRootObject:tokenField],
|
||||
unarchived = [CPKeyedUnarchiver unarchiveObjectWithData:archived];
|
||||
|
||||
[self assert:[unarchived completionDelay] equals:5.0];
|
||||
[self assert:[unarchived tokenizingCharacterSet] equals:[CPCharacterSet characterSetWithCharactersInString:@","]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -5,15 +5,108 @@
|
||||
|
||||
@implementation CPWindowTest : OJTestCase
|
||||
{
|
||||
CPWindow theWindow;
|
||||
CPWindow _window @accessors(property=window);
|
||||
}
|
||||
|
||||
-(void)testCanAllocWindow
|
||||
- (void)setUp
|
||||
{
|
||||
theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0,0,200,150)
|
||||
styleMask:CPWindowNotSizable];
|
||||
_window = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0)
|
||||
styleMask:CPWindowNotSizable];
|
||||
}
|
||||
|
||||
[self assertTrue:!!theWindow];
|
||||
- (void)testCanAllocWindow
|
||||
{
|
||||
[self assertTrue:!![self window]];
|
||||
}
|
||||
|
||||
- (void)testThatContentViewIsInitialFirstResponder
|
||||
{
|
||||
var contentView = [[self window] contentView];
|
||||
[self assert:contentView
|
||||
equals:[[self window] initialFirstResponder]
|
||||
message:@"The window's content view must be the initial first responder"];
|
||||
}
|
||||
|
||||
- (void)testKeyViewLoop
|
||||
{
|
||||
var contentView = [[self window] contentView],
|
||||
textField = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:100.0];
|
||||
|
||||
[textField setFrame:CGRectMake(100.0, 100.0, 100.0, 26.0)];
|
||||
[contentView addSubview:textField];
|
||||
[[self window] recalculateKeyViewLoop];
|
||||
|
||||
var nextTextField = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:100];
|
||||
[nextTextField setFrame:CGRectMake(100.0, 200.0, 100.0, 26.0)];
|
||||
[contentView addSubview:nextTextField];
|
||||
[[self window] recalculateKeyViewLoop];
|
||||
|
||||
// Test nextKeyView
|
||||
[self assert:textField
|
||||
equals:[contentView nextKeyView]
|
||||
message:@"textField should be the next key view of contentView"];
|
||||
|
||||
[self assert:nextTextField
|
||||
equals:[textField nextKeyView]
|
||||
message:@"nextTextField should be the next key view of textField"];
|
||||
|
||||
[self assert:contentView
|
||||
equals:[nextTextField nextKeyView]
|
||||
message:@"contentView should be the next key view of nextTextField"];
|
||||
|
||||
// Test previousKeyView
|
||||
[self assert:contentView
|
||||
equals:[textField previousKeyView]
|
||||
message:@"contentView should be the previous key view of textField"];
|
||||
|
||||
[self assert:textField
|
||||
equals:[nextTextField previousKeyView]
|
||||
message:@"textField should be the previous key view of nextTextField"];
|
||||
|
||||
[self assert:nextTextField
|
||||
equals:[contentView previousKeyView]
|
||||
message:@"nextTextField should be the previous key view of contentView"];
|
||||
}
|
||||
|
||||
- (void)testValiKeyViewLoop
|
||||
{
|
||||
var contentView = [[self window] contentView],
|
||||
textField = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:100.0];
|
||||
|
||||
[textField setFrame:CGRectMake(100.0, 100.0, 100.0, 26.0)];
|
||||
[contentView addSubview:textField];
|
||||
[[self window] recalculateKeyViewLoop];
|
||||
|
||||
var nextTextField = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:100];
|
||||
[nextTextField setFrame:CGRectMake(100.0, 200.0, 100.0, 26.0)];
|
||||
[contentView addSubview:nextTextField];
|
||||
[[self window] recalculateKeyViewLoop];
|
||||
|
||||
// Test nextValidKeyView
|
||||
[self assert:textField
|
||||
equals:[contentView nextValidKeyView]
|
||||
message:@"textField should be the next valid key view of contentView"];
|
||||
|
||||
[self assert:nextTextField
|
||||
equals:[textField nextValidKeyView]
|
||||
message:@"nextTextField should be the next valid key view of textField"];
|
||||
|
||||
[self assert:textField
|
||||
equals:[nextTextField nextValidKeyView]
|
||||
message:@"textField should be the next key valid view of nextTextField"];
|
||||
|
||||
// Test previousValidKeyView
|
||||
[self assert:nextTextField
|
||||
equals:[textField previousValidKeyView]
|
||||
message:@"nextTextField should be the previous valid key view of textField"];
|
||||
|
||||
[self assert:textField
|
||||
equals:[nextTextField previousValidKeyView]
|
||||
message:@"textField should be the previous key valid view of nextTextField"];
|
||||
|
||||
[self assert:nextTextField
|
||||
equals:[contentView previousValidKeyView]
|
||||
message:@"nextTextField should be the previous valid key view of contentView"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,39 @@
|
||||
@import <Foundation/CPCharacterSet.j>
|
||||
|
||||
@implementation CPCharacterSetTest : OJTestCase
|
||||
|
||||
- (void)testEquals
|
||||
{
|
||||
var charSetA = [CPCharacterSet characterSetWithCharactersInString:"abc"],
|
||||
charSetB = [CPCharacterSet characterSetWithCharactersInString:"abc"],
|
||||
charSetC = [CPCharacterSet characterSetWithCharactersInString:""];
|
||||
|
||||
[self assertFalse:[charSetA isEqual:[CPNull null]]];
|
||||
[self assertFalse:[charSetA isEqual:charSetC]];
|
||||
[self assertTrue:[charSetA isEqual:charSetA]];
|
||||
[self assertTrue:[charSetA isEqual:charSetB]];
|
||||
}
|
||||
|
||||
- (void)testArchiving
|
||||
{
|
||||
var charSet = [CPCharacterSet characterSetWithCharactersInString:"abc"],
|
||||
archived = [CPKeyedArchiver archivedDataWithRootObject:charSet],
|
||||
unarchived = [CPKeyedUnarchiver unarchiveObjectWithData:archived];
|
||||
|
||||
[self assertTrue:[unarchived characterIsMember:'a']];
|
||||
[self assertTrue:[unarchived characterIsMember:'b']];
|
||||
[self assertTrue:[unarchived characterIsMember:'c']];
|
||||
[self assertFalse:[unarchived characterIsMember:'d']];
|
||||
|
||||
var invertedSet = [charSet invertedSet];
|
||||
|
||||
archived = [CPKeyedArchiver archivedDataWithRootObject:invertedSet];
|
||||
unarchived = [CPKeyedUnarchiver unarchiveObjectWithData:archived];
|
||||
|
||||
[self assertFalse:[unarchived characterIsMember:'a'] message:"in unarchived inverted set 'a' is not a member"];
|
||||
[self assertFalse:[unarchived characterIsMember:'b'] message:"in unarchived inverted set 'b' is not a member"];
|
||||
[self assertFalse:[unarchived characterIsMember:'c'] message:"in unarchived inverted set 'c' is not a member"];
|
||||
[self assertTrue:[unarchived characterIsMember:'d'] message:"in unarchived inverted set 'd' is a member"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,396 @@
|
||||
var COUNTER;
|
||||
|
||||
@implementation CPKVCArrayTest : OJTestCase
|
||||
{
|
||||
TestObject _object @accessors(property=object);
|
||||
ImplementedTestObject _implementedObject @accessors(property=implementedObject);
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
_object = [[TestObject alloc] init];
|
||||
_implementedObject = [[ImplementedTestObject alloc] init];
|
||||
|
||||
COUNTER = 0;
|
||||
}
|
||||
|
||||
- (void)_patchSelector:(SEL)theSelector
|
||||
{
|
||||
var method_dtable = [[self object] class].method_dtable,
|
||||
method_list = [[self object] class].method_list,
|
||||
selectorsToRemove = [@selector(countOfValues), @selector(objectInValuesAtIndex),
|
||||
@selector(objectInValuesAtIndex:), @selector(valuesAtIndexes:),
|
||||
@selector(insertObject:inValuesAtIndex:), @selector(insertValues:atIndexes:),
|
||||
@selector(removeObjectFromValuesAtIndex:), @selector(removeValuesAtIndexes:),
|
||||
@selector(removeObjectFromValuesAtIndex:),
|
||||
@selector(replaceObjectInValuesAtIndex:withObject:),
|
||||
@selector(replaceValuesAtIndexes:withValues:)],
|
||||
selectorIndex = [selectorsToRemove count];
|
||||
|
||||
while (selectorIndex--)
|
||||
{
|
||||
var selector = [selectorsToRemove objectAtIndex:selectorIndex],
|
||||
implementation = method_dtable[selector];
|
||||
|
||||
if (!implementation)
|
||||
continue;
|
||||
|
||||
delete method_dtable[selector]
|
||||
[method_list removeObject:implementation];
|
||||
}
|
||||
|
||||
var method = class_getInstanceMethod([[self implementedObject] class], theSelector),
|
||||
implementation = method_getImplementation(method);
|
||||
|
||||
class_addMethod([[self object] class], theSelector, implementation);
|
||||
}
|
||||
|
||||
- (void)testUsesCountOfKey
|
||||
{
|
||||
[self _patchSelector:@selector(countOfValues)];
|
||||
|
||||
var count = [[[self object] mutableArrayValueForKey:@"values"] count];
|
||||
|
||||
[self assert:10 equals:count message:@"countOfValues should return 10"];
|
||||
[self assert:1 equals:COUNTER message:@"countOfValues should have been called once"]
|
||||
}
|
||||
|
||||
- (void)testObjectAtIndexUsesObjectInKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(objectInValuesAtIndex:)];
|
||||
|
||||
var values = [[self object] mutableArrayValueForKey:@"values"],
|
||||
object = [values objectAtIndex:0];
|
||||
|
||||
[self assert:0 equals:object]
|
||||
[self assert:1 equals:COUNTER message:@"objectInValuesAtIndex: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testObjectsAtIndexesUsesObjectInKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(objectInValuesAtIndex:)];
|
||||
|
||||
var values = [[self object] mutableArrayValueForKey:@"values"],
|
||||
objects = [values objectsAtIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 2)]];
|
||||
|
||||
[self assert:[0, 1] equals:objects]
|
||||
[self assert:2 equals:COUNTER message:@"objectInValuesAtIndex: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testObjectAtIndexUsesKeyAtIndexes
|
||||
{
|
||||
[self _patchSelector:@selector(valuesAtIndexes:)];
|
||||
|
||||
var values = [[self object] mutableArrayValueForKey:@"values"],
|
||||
object = [values objectAtIndex:0];
|
||||
|
||||
[self assert:0 equals:object]
|
||||
[self assert:1 equals:COUNTER message:@"valuesAtIndexes: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testObjectsAtIndexesUsesKeyAtIndexes
|
||||
{
|
||||
[self _patchSelector:@selector(valuesAtIndexes:)];
|
||||
|
||||
var values = [[self object] mutableArrayValueForKey:@"values"],
|
||||
objects = [values objectsAtIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 2)]];
|
||||
|
||||
[self assert:[0, 1] equals:objects]
|
||||
[self assert:1 equals:COUNTER message:@"valuesAtIndexes: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testInsertObjectAtIndexUsesInsertKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(insertObject:inValuesAtIndex:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] insertObject:11 atIndex:10];
|
||||
|
||||
[self assert:11 equals:[[[self object] values] objectAtIndex:10]];
|
||||
[self assert:1 equals:COUNTER message:@"insertObject:inValuesAtIndex: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testInsertObjectsAtIndexesUsesInsertKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(insertObject:inValuesAtIndex:)];
|
||||
|
||||
var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(10, 2)];
|
||||
[[[self object] mutableArrayValueForKey:@"values"] insertObjects:[11, 12] atIndexes:indexes];
|
||||
|
||||
[self assert:[11, 12] equals:[[[self object] values] objectsAtIndexes:indexes]];
|
||||
[self assert:2
|
||||
equals:COUNTER
|
||||
message:@"insertValues:atIndexes: should have been called once for each object"];
|
||||
}
|
||||
|
||||
- (void)testInsertObjectAtIndexUsesInsertKeyAtIndexes
|
||||
{
|
||||
[self _patchSelector:@selector(insertValues:atIndexes:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] insertObject:11 atIndex:10];
|
||||
|
||||
[self assert:11 equals:[[[self object] values] objectAtIndex:10]];
|
||||
[self assert:1 equals:COUNTER message:@"insertValues:atIndexes: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testInsertObjectsAtIndexesUsesInsertKeyAtIndexes
|
||||
{
|
||||
[self _patchSelector:@selector(insertValues:atIndexes:)];
|
||||
|
||||
var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(10, 2)];
|
||||
[[[self object] mutableArrayValueForKey:@"values"] insertObjects:[11, 12] atIndexes:indexes];
|
||||
|
||||
[self assert:[11, 12] equals:[[[self object] values] objectsAtIndexes:indexes]];
|
||||
[self assert:1
|
||||
equals:COUNTER
|
||||
message:@"insertValues:atIndexes: should have been called once for each object"];
|
||||
}
|
||||
|
||||
- (void)testRemoveObjectAtIndexUsesRemoveObjectFromKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(removeObjectFromValuesAtIndex:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeObjectAtIndex:0];
|
||||
|
||||
[self assert:[1, 2, 3, 4, 5, 6, 7, 8, 9] equals:[[self object] values]];
|
||||
[self assert:1 equals:COUNTER message:@"removeObjectFromValuesAtIndex: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testRemoveObjectAtIndexesUsesRemoveObjectFromKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(removeObjectFromValuesAtIndex:)];
|
||||
|
||||
var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 2)];
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeObjectsAtIndexes:indexes];
|
||||
|
||||
[self assert:[2, 3, 4, 5, 6, 7, 8, 9] equals:[[self object] values]];
|
||||
[self assert:2 equals:COUNTER message:@"removeValuesAtIndex: should have been called once for each object"];
|
||||
}
|
||||
- (void)testRemoveObjectsAtIndexesUsesRemoveKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(removeValuesAtIndexes:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeObjectAtIndex:0];
|
||||
|
||||
[self assert:[1, 2, 3, 4, 5, 6, 7, 8, 9] equals:[[self object] values]];
|
||||
[self assert:1
|
||||
equals:COUNTER
|
||||
message:@"removeValuesAtIndexes: should have been once for each object"];
|
||||
}
|
||||
|
||||
- (void)testRemoveObjectsAtIndexesUsesRemoveKeyAtIndexes
|
||||
{
|
||||
[self _patchSelector:@selector(removeValuesAtIndexes:)];
|
||||
|
||||
var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(3, 2)];
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeObjectsAtIndexes:indexes];
|
||||
|
||||
[self assert:[0, 1, 2, 5, 6, 7, 8, 9] equals:[[self object] values]];
|
||||
[self assert:1 equals:COUNTER message:@"removeValuesAtIndexes: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testRemoveObjectUsesRemoveKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(removeObjectFromValuesAtIndex:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeObject:1];
|
||||
|
||||
[self assert:[0, 2, 3, 4, 5, 6, 7, 8, 9] equals:[[self object] values]];
|
||||
[self assert:1 equals:COUNTER message:"removeObjectFromValuesAtIndex: should have been called once"]
|
||||
}
|
||||
|
||||
- (void)testeRemoveObjectUsesRemoveKeyAtIndexes
|
||||
{
|
||||
[self _patchSelector:@selector(removeValuesAtIndexes:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeObject:5];
|
||||
|
||||
[self assert:[0, 1, 2, 3, 4, 6, 7, 8, 9] equals:[[self object] values]];
|
||||
[self assert:1 equals:COUNTER message:@"removeValuesAtIndexes: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testRemoveObjectsInArrayUsesRemoveKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(removeObjectFromValuesAtIndex:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeObjectsInArray:[3, 6]];
|
||||
|
||||
[self assert:[0, 1, 2, 4, 5, 7, 8, 9] equals:[[self object] values]];
|
||||
[self assert:2 equals:COUNTER message:"removeObjectFromValuesAtIndex: should have been called once"]
|
||||
}
|
||||
|
||||
- (void)testRemoveObjectsInArrayUsesRemoveKeyAtIndexes
|
||||
{
|
||||
[self _patchSelector:@selector(removeValuesAtIndexes:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeObjectsInArray:[0, 9]];
|
||||
|
||||
[self assert:[1, 2, 3, 4, 5, 6, 7, 8] equals:[[self object] values]];
|
||||
[self assert:1 equals:COUNTER message:@"removeValuesAtIndexes: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testRemoveLastObjectUsesRemoveKeyAtIndex
|
||||
{
|
||||
[self _patchSelector:@selector(removeObjectFromValuesAtIndex:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeLastObject];
|
||||
|
||||
[self assert:[0, 1, 2, 3, 4, 5, 6, 7, 8] equals:[[self object] values]];
|
||||
[self assert:1 equals:COUNTER message:"removeObjectFromValuesAtIndex: should have been called once"]
|
||||
}
|
||||
|
||||
- (void)testRemoveLastObjectUsesRemoveKeyAtIndexes
|
||||
{
|
||||
[self _patchSelector:@selector(removeValuesAtIndexes:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] removeLastObject];
|
||||
|
||||
[self assert:[0, 1, 2, 3, 4, 5, 6, 7, 8] equals:[[self object] values]];
|
||||
[self assert:1 equals:COUNTER message:@"removeValuesAtIndexes: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testReplaceObjectAtIndexWithObjectUsesReplaceObjectInKeyAtIndexWithObject
|
||||
{
|
||||
[self _patchSelector:@selector(replaceObjectInValuesAtIndex:withObject:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] replaceObjectAtIndex:0 withObject:1];
|
||||
|
||||
[self assert:[1, 1, 2, 3, 4, 5, 6, 7, 8, 9] equals:[[self object] values]];
|
||||
[self assert:1
|
||||
equals:COUNTER
|
||||
message:@"replaceObjectInValuesAtIndex:withObject: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testReplaceObjectsAtIndexesWithObjectsUsesReplaceObjectInKeyAtIndexWithObject
|
||||
{
|
||||
[self _patchSelector:@selector(replaceObjectInValuesAtIndex:withObject:)];
|
||||
|
||||
var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(5, 3)];
|
||||
[[[self object] mutableArrayValueForKey:@"values"] replaceObjectsAtIndexes:indexes withObjects:[1, 2, 3]];
|
||||
|
||||
[self assert:[0, 1, 2, 3, 4, 1, 2, 3, 8, 9] equals:[[self object] values]];
|
||||
[self assert:3
|
||||
equals:COUNTER
|
||||
message:@"replaceObjectInValuesAtIndex:withObject: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testReplaceObjectAtIndexWithObjectUsesReplaceKeyAtIndexesWithKeys
|
||||
{
|
||||
[self _patchSelector:@selector(replaceValuesAtIndexes:withValues:)];
|
||||
|
||||
[[[self object] mutableArrayValueForKey:@"values"] replaceObjectAtIndex:5 withObject:6];
|
||||
|
||||
[self assert:[0, 1, 2, 3, 4, 6, 6, 7, 8, 9] equals:[[self object] values]];
|
||||
[self assert:1
|
||||
equals:COUNTER
|
||||
message:@"replaceObjectInValuesAtIndex:withObject: should have been called once"];
|
||||
}
|
||||
|
||||
- (void)testReplaceObjectsAtIndexesWithObjectsUsesReplaceKeyAtIndexesWithKeys
|
||||
{
|
||||
[self _patchSelector:@selector(replaceValuesAtIndexes:withValues:)];
|
||||
|
||||
var indexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(6, 2)];
|
||||
[[[self object] mutableArrayValueForKey:@"values"] replaceObjectsAtIndexes:indexes withObjects:[7, 8]];
|
||||
|
||||
[self assert:[0, 1, 2, 3, 4, 5, 7, 8, 8, 9] equals:[[self object] values]];
|
||||
[self assert:1
|
||||
equals:COUNTER
|
||||
message:@"replaceObjectInValuesAtIndexes:withValues: should have been called once"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TestObject : CPObject
|
||||
{
|
||||
CPArray _values @accessors(property=values);
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ImplementedTestObject : TestObject
|
||||
{
|
||||
}
|
||||
|
||||
- (int)countOfValues
|
||||
{
|
||||
// CPLog.warn(@"countOfValues");
|
||||
|
||||
COUNTER += 1;
|
||||
return [[self values] count];
|
||||
}
|
||||
|
||||
- (id)objectInValuesAtIndex:(int)theIndex
|
||||
{
|
||||
// CPLog.warn(@"objectInValuesAtIndex: %@", theIndex);
|
||||
|
||||
COUNTER += 1;
|
||||
return [[self values] objectAtIndex:theIndex];
|
||||
}
|
||||
|
||||
- (CPArray)valuesAtIndexes:(CPIndexSet)theIndexes
|
||||
{
|
||||
// CPLog.warn(@"valuesAtIndexes: %@", theIndexes);
|
||||
|
||||
COUNTER += 1;
|
||||
return [[self values] objectsAtIndexes:theIndexes];
|
||||
}
|
||||
|
||||
- (void)insertObject:(id)theObject inValuesAtIndex:(int)theIndex
|
||||
{
|
||||
// CPLog.warn(@"insertObject: %@ inValuesAtIndex: %@", theObject, theIndex);
|
||||
|
||||
COUNTER += 1;
|
||||
[[self values] insertObject:theObject atIndex:theIndex];
|
||||
}
|
||||
|
||||
- (void)insertValues:(CPArray)theObjects atIndexes:(CPIndexSet)theIndexes
|
||||
{
|
||||
// CPLog.warn(@"insertValues: %@ atIndexes: %@", theObjects, theIndexes);
|
||||
|
||||
COUNTER += 1;
|
||||
[[self values] insertObjects:theObjects atIndexes:theIndexes];
|
||||
}
|
||||
|
||||
- (void)removeObjectFromValuesAtIndex:(int)theIndex
|
||||
{
|
||||
// CPLog.warn(@"removeObjectFromValuesAtIndex: %@", theIndex);
|
||||
|
||||
COUNTER += 1;
|
||||
[[self values] removeObjectAtIndex:theIndex];
|
||||
}
|
||||
|
||||
- (void)removeValuesAtIndexes:(CPIndexSet)theIndexes
|
||||
{
|
||||
// CPLog.warn(@"removeValuesAtIndexes: %@", theIndexes);
|
||||
|
||||
COUNTER += 1;
|
||||
[[self values] removeObjectsAtIndexes:theIndexes];
|
||||
}
|
||||
|
||||
- (void)replaceObjectInValuesAtIndex:(int)theIndex withObject:(id)theObject
|
||||
{
|
||||
// CPLog.warn(@"replaceObjectInValuesAtIndex: %@ withObject: %@", theIndex, theObject);
|
||||
|
||||
COUNTER += 1;
|
||||
[[self values] replaceObjectAtIndex:theIndex withObject:theObject];
|
||||
}
|
||||
|
||||
- (void)replaceValuesAtIndexes:(CPIndexSet)theIndexes withValues:(id)theObjects
|
||||
{
|
||||
// CPLog.warn(@"replaceValuesAtIndexes: %@ withValues: %@", theIndexes, theObjects);
|
||||
|
||||
COUNTER += 1;
|
||||
[[self values] replaceObjectsAtIndexes:theIndexes withObjects:theObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -128,6 +128,18 @@
|
||||
[self assert:"abcd" equals:["abcd" substringToIndex:4]];
|
||||
[self assert:"abc" equals:["abcd" substringToIndex:3]];
|
||||
[self assert:"" equals:["abcd" substringToIndex:0]];
|
||||
|
||||
var sawException = false;
|
||||
try
|
||||
{
|
||||
[@"abcd" substringToIndex:5];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
sawException = true;
|
||||
[self assert:CPRangeException equals:[anException name]];
|
||||
}
|
||||
[self assertTrue:sawException message:"expected CPRangeException"];
|
||||
}
|
||||
|
||||
- (void)testBoolValue
|
||||
|
||||