Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06f74e5ef0 | ||
|
|
5eaeb5f05e | ||
|
|
961cb2f6ed | ||
|
|
4dca2bbb85 | ||
|
|
892d3d7120 | ||
|
|
c0fe499937 | ||
|
|
01e8e9e597 | ||
|
|
08b740c36f | ||
|
|
31918bc0cd | ||
|
|
d45aeed224 | ||
|
|
7d2835c522 | ||
|
|
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 | ||
|
|
8fa12b874e | ||
|
|
a9edc868f1 | ||
|
|
c6e5219f2e | ||
|
|
3bb450adbf | ||
|
|
a0f5257258 | ||
|
|
6520615bb0 | ||
|
|
4593212698 | ||
|
|
5363deb122 | ||
|
|
d5a651fc43 | ||
|
|
4740acbcc6 | ||
|
|
bfb2134329 | ||
|
|
ec83977fe1 | ||
|
|
98d08ff05d | ||
|
|
1eb6319b49 | ||
|
|
8f7e06f9d6 | ||
|
|
81e3abe10c | ||
|
|
edcee8934d | ||
|
|
d115d2ef47 | ||
|
|
d1dd725848 | ||
|
|
ebdba33082 | ||
|
|
7e465689e4 | ||
|
|
918f6fbb8f | ||
|
|
1cab9463ad | ||
|
|
3974a483d1 | ||
|
|
28733ec592 | ||
|
|
916b9a399b | ||
|
|
3d62791477 | ||
|
|
64e6764015 | ||
|
|
ecf8f7b143 | ||
|
|
cbed5c9cb1 | ||
|
|
0246d501e2 | ||
|
|
3d5b30ab9e | ||
|
|
4719578f10 | ||
|
|
fe370c7066 | ||
|
|
63d208bf20 | ||
|
|
5158540208 | ||
|
|
a69f7dc81a | ||
|
|
e848e161d4 | ||
|
|
3db61216e1 | ||
|
|
63b00186f4 | ||
|
|
110d4c478d | ||
|
|
49c29ded92 | ||
|
|
06670f2214 | ||
|
|
134bfec615 | ||
|
|
279326ffe1 | ||
|
|
ecb04e983e | ||
|
|
73b4c33f05 | ||
|
|
ad30f4dcbb | ||
|
|
0318587f80 | ||
|
|
2b6fddf26c | ||
|
|
f57755ce46 | ||
|
|
da61da392b | ||
|
|
984b46ba07 | ||
|
|
1abce77b3a | ||
|
|
eaa51835ef | ||
|
|
9038b7cfa9 | ||
|
|
420606d8e8 | ||
|
|
80c8fd9794 | ||
|
|
1f479c3f20 | ||
|
|
0af89853d4 | ||
|
|
b5bb9c8ec3 | ||
|
|
ff13de9f75 | ||
|
|
47f40eb04b | ||
|
|
8dd174d1e3 | ||
|
|
1ffc7ed6fb | ||
|
|
b9b0eb79d8 | ||
|
|
a5832aa66b | ||
|
|
63710d5fae | ||
|
|
9472f76ad5 | ||
|
|
a2ef701d18 | ||
|
|
55e07536d9 | ||
|
|
fc81483371 | ||
|
|
4da5669ecb | ||
|
|
c232680600 | ||
|
|
e552b022ba | ||
|
|
bd25b6fb48 | ||
|
|
af0a479f88 | ||
|
|
86a28d8cc1 | ||
|
|
3dd178a69d | ||
|
|
417334419a | ||
|
|
6c34bec51e | ||
|
|
c03b3c2d34 | ||
|
|
e97d4a8e72 | ||
|
|
24d0af3104 | ||
|
|
5790e99359 | ||
|
|
0d6c2c9890 | ||
|
|
cbabb544ab | ||
|
|
42220e057b | ||
|
|
c5df9c973f | ||
|
|
91509e7d36 | ||
|
|
b89aa9af0d | ||
|
|
4421b55613 | ||
|
|
75d9929483 | ||
|
|
7c78c2ef5d | ||
|
|
3758c86d20 | ||
|
|
ada547a79d | ||
|
|
445adcb47f | ||
|
|
4e9b975ca8 | ||
|
|
5d26066e02 | ||
|
|
de73d86684 | ||
|
|
d6244f090e | ||
|
|
aac09d8208 | ||
|
|
a127fc9ec3 | ||
|
|
551aa300ea | ||
|
|
485e59b5eb | ||
|
|
fdfcbf1da2 | ||
|
|
0f5eee0258 | ||
|
|
870492eb96 | ||
|
|
b50005049e | ||
|
|
0e890e19ea | ||
|
|
59aabc150c | ||
|
|
b159815349 | ||
|
|
1c44dc1052 | ||
|
|
bfe5afaddd | ||
|
|
64e907156c | ||
|
|
5d01ca428c | ||
|
|
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 = [];
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
@import "CPObjectController.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
|
||||
|
||||
/*!
|
||||
|
||||
@class CPArrayController
|
||||
@@ -284,8 +285,7 @@
|
||||
|
||||
if (value === nil)
|
||||
value = [];
|
||||
|
||||
if (![value isKindOfClass:[CPArray class]])
|
||||
else if (![value isKindOfClass:[CPArray class]])
|
||||
value = [value];
|
||||
|
||||
var oldSelectedObjects = nil,
|
||||
@@ -313,6 +313,8 @@
|
||||
|
||||
// Don't use [super setContent:] as that would fire the contentObject change.
|
||||
// We need to be in control of when notifications fire.
|
||||
// Note that if we have a contentArray binding, setting the content does /not/
|
||||
// cause a reverse binding set.
|
||||
_contentObject = value;
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion && _filterPredicate != nil)
|
||||
@@ -337,14 +339,6 @@
|
||||
[self setContent:anArray];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_setContentSet:(id)aSet
|
||||
{
|
||||
[self setContent:[aSet allObjects]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the content array of the controller.
|
||||
@return id the content array of the receiver
|
||||
@@ -354,6 +348,14 @@
|
||||
return [self content];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_setContentSet:(id)aSet
|
||||
{
|
||||
[self setContent:[aSet allObjects]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the content of the receiver as a CPSet.
|
||||
|
||||
@@ -562,8 +564,13 @@
|
||||
- (BOOL)setSelectionIndexes:(CPIndexSet)indexes
|
||||
{
|
||||
[self _selectionWillChange]
|
||||
[self __setSelectionIndexes:indexes];
|
||||
[self _selectionDidChange];
|
||||
|
||||
// When explicitly setting the selection, ignore avoidsEmptySelection
|
||||
var changed = [self __setSelectionIndexes:indexes obeyAvoidsEmptySelection:NO];
|
||||
|
||||
[self _selectionDidChangeNotify:NO];
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -572,7 +579,7 @@
|
||||
*/
|
||||
- (BOOL)__setSelectionIndex:(int)theIndex
|
||||
{
|
||||
[self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:theIndex]];
|
||||
return [self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:theIndex] obeyAvoidsEmptySelection:YES];
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -580,13 +587,22 @@
|
||||
@ignore
|
||||
*/
|
||||
- (BOOL)__setSelectionIndexes:(CPIndexSet)indexes
|
||||
{
|
||||
[self __setSelectionIndexes:indexes obeyAvoidsEmptySelection:YES];
|
||||
}
|
||||
|
||||
/*
|
||||
Like setSelectionIndexes but don't fire any change notifications.
|
||||
@ignore
|
||||
*/
|
||||
- (BOOL)__setSelectionIndexes:(CPIndexSet)indexes obeyAvoidsEmptySelection:(BOOL)obeyAvoidsEmptySelection
|
||||
{
|
||||
if (!indexes)
|
||||
indexes = [CPIndexSet indexSet];
|
||||
|
||||
if (![indexes count])
|
||||
{
|
||||
if (_avoidsEmptySelection && [[self arrangedObjects] count])
|
||||
if (obeyAvoidsEmptySelection && _avoidsEmptySelection && [[self arrangedObjects] count])
|
||||
indexes = [CPIndexSet indexSetWithIndex:0];
|
||||
}
|
||||
else
|
||||
@@ -634,10 +650,10 @@
|
||||
[self willChangeValueForKey:@"selectionIndexes"];
|
||||
[self _selectionWillChange];
|
||||
|
||||
[self __setSelectedObjects:objects];
|
||||
[self __setSelectedObjects:objects obeyAvoidsEmptySelection:NO];
|
||||
|
||||
[self didChangeValueForKey:@"selectionIndexes"];
|
||||
[self _selectionDidChange];
|
||||
[self _selectionDidChangeNotify:NO];
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -645,6 +661,15 @@
|
||||
@ignore
|
||||
*/
|
||||
- (BOOL)__setSelectedObjects:(CPArray)objects
|
||||
{
|
||||
[self __setSelectedObjects:objects obeyAvoidsEmptySelection:YES];
|
||||
}
|
||||
|
||||
/*
|
||||
Like setSelectedObjects but don't fire any change notifications.
|
||||
@ignore
|
||||
*/
|
||||
- (BOOL)__setSelectedObjects:(CPArray)objects obeyAvoidsEmptySelection:(BOOL)obeyAvoidsEmptySelection
|
||||
{
|
||||
var set = [CPIndexSet indexSet],
|
||||
count = [objects count],
|
||||
@@ -658,7 +683,7 @@
|
||||
[set addIndex:index];
|
||||
}
|
||||
|
||||
[self __setSelectionIndexes:set];
|
||||
[self __setSelectionIndexes:set obeyAvoidsEmptySelection:obeyAvoidsEmptySelection];
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -727,17 +752,13 @@
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"content"];
|
||||
|
||||
/*
|
||||
If the content array is bound then our addObject: message below will cause the observed
|
||||
array to change. The binding will call setContent:_contentObject on this array
|
||||
controller to let it know about the change. We want to ignore that message since we
|
||||
A) already have the right _contentObject and B) properly update _arrangedObjects
|
||||
by hand below.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
[self _willModifyContent];
|
||||
[_contentObject addObject:object];
|
||||
_disableSetContent = NO;
|
||||
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
[self _didModifyContent];
|
||||
|
||||
if (willClearPredicate)
|
||||
{
|
||||
@@ -758,20 +779,22 @@
|
||||
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:1];
|
||||
}
|
||||
/*
|
||||
else if (_filterPredicate !== nil)
|
||||
...
|
||||
// Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does
|
||||
// not appear in arrangedObjects and we do not have to update at all.
|
||||
else if (_filterPredicate !== nil)
|
||||
...
|
||||
Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does
|
||||
not appear in arrangedObjects and we do not have to update at all.
|
||||
*/
|
||||
|
||||
// This will also send notificaitons for arrangedObjects.
|
||||
[self didChangeValueForKey:@"content"];
|
||||
|
||||
if (willClearPredicate)
|
||||
[self didChangeValueForKey:@"filterPredicate"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds an object at a given index to the receiver's collection.
|
||||
Adds an object at a given index in the receiver's arrangedObjects. Also add the object
|
||||
to the content collection (although at the end rather than the given index).
|
||||
|
||||
@param id anObject - The object to add to the collection.
|
||||
@param int anIndex - The index to insert the object at.
|
||||
@@ -781,19 +804,27 @@
|
||||
if (![self canAdd])
|
||||
return;
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion)
|
||||
var willClearPredicate = NO;
|
||||
if (_clearsFilterPredicateOnInsertion && _filterPredicate)
|
||||
{
|
||||
[self willChangeValueForKey:@"filterPredicate"];
|
||||
willClearPredicate = YES;
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"content"];
|
||||
|
||||
/*
|
||||
See _disableSetContent explanation in addObject:.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
[_contentObject insertObject:anObject atIndex:anIndex];
|
||||
_disableSetContent = NO;
|
||||
[self _willModifyContent];
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion)
|
||||
// The atArrangedObjectIndex: part of this method's name only refers to where the
|
||||
// object goes in arrangedObjects, not in the content array. So use addObject:,
|
||||
// not insertObject:atIndex: here for speed.
|
||||
[_contentObject addObject:anObject];
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
[self _didModifyContent];
|
||||
|
||||
if (willClearPredicate)
|
||||
[self __setFilterPredicate:nil];
|
||||
|
||||
[[self arrangedObjects] insertObject:anObject atIndex:anIndex];
|
||||
@@ -809,7 +840,8 @@
|
||||
[self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]];
|
||||
|
||||
[self didChangeValueForKey:@"content"];
|
||||
if (_clearsFilterPredicateOnInsertion)
|
||||
|
||||
if (willClearPredicate)
|
||||
[self didChangeValueForKey:@"filterPredicate"];
|
||||
}
|
||||
|
||||
@@ -820,26 +852,26 @@
|
||||
*/
|
||||
- (void)removeObject:(id)object
|
||||
{
|
||||
[self willChangeValueForKey:@"content"];
|
||||
[self willChangeValueForKey:@"content"];
|
||||
[self _willModifyContent];
|
||||
|
||||
/*
|
||||
See _disableSetContent explanation in addObject:.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
[_contentObject removeObject:object];
|
||||
_disableSetContent = NO;
|
||||
[_contentObject removeObject:object];
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
|
||||
{
|
||||
[self _didModifyContent];
|
||||
|
||||
if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
|
||||
{
|
||||
// selectionIndexes change notification will be fired as a result of the
|
||||
// content change. Don't fire manually.
|
||||
var pos = [_arrangedObjects indexOfObject:object];
|
||||
|
||||
[_arrangedObjects removeObjectAtIndex:pos];
|
||||
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:-1];
|
||||
}
|
||||
}
|
||||
|
||||
[self didChangeValueForKey:@"content"];
|
||||
[self didChangeValueForKey:@"content"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -875,16 +907,75 @@
|
||||
*/
|
||||
- (void)remove:(id)sender
|
||||
{
|
||||
[self removeObjects:[[self arrangedObjects] objectsAtIndexes:[self selectionIndexes]]];
|
||||
[self removeObjectsAtArrangedObjectIndexes:_selectionIndexes];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes the object at the specified index in the controller's arranged objects from the content array.
|
||||
@param int index - index of the object to remove.
|
||||
*/
|
||||
- (void)removeObjectAtArrangedObjectIndex:(int)index
|
||||
{
|
||||
[self removeObjectsAtArrangedObjectIndexes:[CPIndexSet indexSetWithIndex:index]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes the objects at the specified indexes in the controller's arranged objects from the content array.
|
||||
@param CPIndexSet indexes - indexes of the objects to remove.
|
||||
*/
|
||||
- (void)removeObjectsAtArrangedObjectIndexes:(CPIndexSet)indexes
|
||||
- (void)removeObjectsAtArrangedObjectIndexes:(CPIndexSet)anIndexSet
|
||||
{
|
||||
[self _removeObjects:[[self arrangedObjects] objectsAtIndexes:indexes]];
|
||||
[self willChangeValueForKey:@"content"];
|
||||
|
||||
_disableSetContent = YES;
|
||||
|
||||
var arrangedObjects = [self arrangedObjects],
|
||||
index = [anIndexSet lastIndex],
|
||||
position = CPNotFound,
|
||||
newSelectionIndexes = [_selectionIndexes copy],
|
||||
proxy = [_CPKVOProxy proxyForObject:self];
|
||||
|
||||
while (index !== CPNotFound)
|
||||
{
|
||||
var object = [arrangedObjects objectAtIndex:index];
|
||||
|
||||
// Make sure arrangedObjects does not notify because of content change
|
||||
[proxy suppressNotificationsForKeyPath:@"arrangedObjects"];
|
||||
|
||||
// First try the simple case which should work if there are no sort descriptors.
|
||||
if ([_contentObject objectAtIndex:index] === object)
|
||||
[_contentObject removeObjectAtIndex:index];
|
||||
else
|
||||
{
|
||||
// Since we don't have a reverse mapping between the sorted order and the
|
||||
// unsorted one, we'll just simply have to remove an arbitrary pointer. It might
|
||||
// be the 'wrong' one - as in not the one the user selected - but the wrong
|
||||
// one is still just another pointer to the same object, so the user will not
|
||||
// be able to see any difference.
|
||||
contentIndex = [_contentObject indexOfObjectIdenticalTo:object];
|
||||
[_contentObject removeObjectAtIndex:contentIndex];
|
||||
}
|
||||
|
||||
// Now arrangedObjects can notify
|
||||
[proxy unsuppressNotificationsForKeyPath:@"arrangedObjects"];
|
||||
[arrangedObjects removeObjectAtIndex:index];
|
||||
|
||||
// Deselect this row if it was selected, and either way shift all selection indexes
|
||||
// following it up by 1.
|
||||
[newSelectionIndexes removeIndex:index];
|
||||
[newSelectionIndexes shiftIndexesStartingAtIndex:index by:-1];
|
||||
|
||||
index = [anIndexSet indexLessThanIndex:index];
|
||||
}
|
||||
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
_disableSetContent = NO;
|
||||
|
||||
// This will automatically handle the avoidsEmptySelection case.
|
||||
[self __setSelectionIndexes:newSelectionIndexes];
|
||||
|
||||
[self didChangeValueForKey:@"content"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -904,6 +995,8 @@
|
||||
[contentArray addObject:[objects objectAtIndex:i]];
|
||||
|
||||
[self setContent:contentArray];
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -921,13 +1014,13 @@
|
||||
- (void)_removeObjects:(CPArray)objects
|
||||
{
|
||||
[self willChangeValueForKey:@"content"];
|
||||
[self _willModifyContent];
|
||||
|
||||
/*
|
||||
See _disableSetContent explanation in addObject:.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
[_contentObject removeObjectsInArray:objects];
|
||||
_disableSetContent = NO;
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
[self _didModifyContent];
|
||||
|
||||
var arrangedObjects = [self arrangedObjects],
|
||||
position = [arrangedObjects indexOfObject:[objects objectAtIndex:0]];
|
||||
@@ -964,13 +1057,39 @@
|
||||
return [self isEditable];
|
||||
}
|
||||
|
||||
// Internal
|
||||
- (void)_willModifyContent
|
||||
{
|
||||
/*
|
||||
If the content array is bound then our addObject: message below will cause the observed
|
||||
array to change. The binding will call setContent:_contentObject on this array
|
||||
controller to let it know about the change. We want to ignore that message since we
|
||||
A) already have the right _contentObject and B) properly update _arrangedObjects
|
||||
by hand below.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
|
||||
/*
|
||||
When mutating arrangedObjects, we mutate the content first. But mutating the content
|
||||
will trigger a notification that the arrangedObjects have changed, which we want to
|
||||
suppress, since the arrangedObjects mutation will do the correct notification.
|
||||
*/
|
||||
[[_CPKVOProxy proxyForObject:self] suppressNotificationsForKeyPath:@"arrangedObjects"];
|
||||
}
|
||||
|
||||
- (void)_didModifyContent
|
||||
{
|
||||
_disableSetContent = NO;
|
||||
[[_CPKVOProxy proxyForObject:self] unsuppressNotificationsForKeyPath:@"arrangedObjects"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPArrayController (CPBinder)
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (theBinding == @"contentArray")
|
||||
if (theBinding === @"content" || theBinding === @"contentArray")
|
||||
return [_CPArrayControllerContentBinder class];
|
||||
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
@@ -985,12 +1104,53 @@
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
isCompound = [self handlesContentAsCompoundValue] || keyPath.indexOf("@") !== -1;
|
||||
|
||||
if (!isCompound)
|
||||
{
|
||||
newValue = [destination mutableArrayValueForKeyPath:keyPath];
|
||||
}
|
||||
else
|
||||
{
|
||||
// handlesContentAsCompoundValue == YES so we cannot just set up a proxy.
|
||||
// Every read and every write must go through transformValue and
|
||||
// reverseTransformValue, and the resulting object cannot be described by
|
||||
// a key path.
|
||||
newValue = [destination valueForKeyPath:keyPath];
|
||||
}
|
||||
|
||||
newValue = [self transformValue:newValue withOptions:options];
|
||||
|
||||
if (isCompound)
|
||||
{
|
||||
// Make sure we can edit our copy of the content. TODO In Cocoa, this copy
|
||||
// appears to be deferred until the array actually needs to be edited.
|
||||
newValue = [newValue mutableCopy];
|
||||
}
|
||||
|
||||
[_source setValue:newValue forKey:aBinding];
|
||||
}
|
||||
|
||||
- (void)_contentArrayDidChange
|
||||
{
|
||||
// When handlesContentAsCompoundValue == YES, it is not sufficient to modify the content object
|
||||
// in place because what we are holding is an array 'unwrapped' from a compound value by
|
||||
// a value transformer. So when we modify it we need a reverse set and transform to create
|
||||
// a new compound value.
|
||||
//
|
||||
// (The Cocoa documentation on the subject is not very clear but after substantial
|
||||
// experimentation this seems both reasonable and compliant.)
|
||||
if ([self handlesContentAsCompoundValue])
|
||||
{
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey];
|
||||
|
||||
[self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
|
||||
[self reverseSetValueFor:@"contentArray"];
|
||||
[self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPArrayControllerAvoidsEmptySelection = @"CPArrayControllerAvoidsEmptySelection",
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#import "../Foundation/Ref.h"
|
||||
|
||||
@import "../Foundation/CPFormatter.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPShadow.j"
|
||||
@import "CPView.j"
|
||||
@@ -82,6 +85,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
@implementation CPControl : CPView
|
||||
{
|
||||
id _value;
|
||||
CPFormatter _formatter @accessors(property=formatter);
|
||||
|
||||
// Target-Action Support
|
||||
id _target;
|
||||
@@ -221,10 +225,11 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
@param anAction the action to send
|
||||
@param anObject the object to which the action will be sent
|
||||
*/
|
||||
- (void)sendAction:(SEL)anAction to:(id)anObject
|
||||
- (BOOL)sendAction:(SEL)anAction to:(id)anObject
|
||||
{
|
||||
[self _reverseSetBinding];
|
||||
[CPApp sendAction:anAction to:anObject from:self];
|
||||
|
||||
return [CPApp sendAction:anAction to:anObject from:self];
|
||||
}
|
||||
|
||||
- (int)sendActionOn:(int)mask
|
||||
@@ -502,15 +507,46 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
*/
|
||||
- (CPString)stringValue
|
||||
{
|
||||
if (_formatter && _value !== undefined && _value !== nil)
|
||||
{
|
||||
var formattedValue = [self hasThemeState:CPThemeStateEditing] ? [_formatter editingStringForObjectValue:_value] : [_formatter stringForObjectValue:_value];
|
||||
|
||||
if (formattedValue !== nil && formattedValue !== undefined)
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
return (_value === undefined || _value === nil) ? "" : String(_value);
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's string value.
|
||||
*/
|
||||
- (void)setStringValue:(CPString)anObject
|
||||
- (void)setStringValue:(CPString)aString
|
||||
{
|
||||
[self setObjectValue:anObject];
|
||||
// Cocoa raises an invalid parameter assertion and returns if you pass nil.
|
||||
if (aString === nil || aString === undefined)
|
||||
{
|
||||
CPLog.warn("nil sent to CPControl -setStringValue");
|
||||
return;
|
||||
}
|
||||
|
||||
var value;
|
||||
|
||||
if (_formatter)
|
||||
{
|
||||
value = nil;
|
||||
|
||||
if ([_formatter getObjectValue:AT_REF(value) forString:aString errorDescription:nil] === NO)
|
||||
{
|
||||
// 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)
|
||||
value = undefined; // Means the value is invalid
|
||||
}
|
||||
}
|
||||
else
|
||||
value = aString;
|
||||
|
||||
[self setObjectValue:value];
|
||||
}
|
||||
|
||||
- (void)takeDoubleValueFrom:(id)sender
|
||||
@@ -526,21 +562,18 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
[self setFloatValue:[sender floatValue]];
|
||||
}
|
||||
|
||||
|
||||
- (void)takeIntegerValueFrom:(id)sender
|
||||
{
|
||||
if ([sender respondsToSelector:@selector(integerValue)])
|
||||
[self setIntegerValue:[sender integerValue]];
|
||||
}
|
||||
|
||||
|
||||
- (void)takeIntValueFrom:(id)sender
|
||||
{
|
||||
if ([sender respondsToSelector:@selector(intValue)])
|
||||
[self setIntValue:[sender intValue]];
|
||||
}
|
||||
|
||||
|
||||
- (void)takeObjectValueFrom:(id)sender
|
||||
{
|
||||
if ([sender respondsToSelector:@selector(objectValue)])
|
||||
@@ -882,7 +915,7 @@ var __Deprecated__CPImageViewImageKey = @"CPImageViewImageKey";
|
||||
if (_target !== nil)
|
||||
[aCoder encodeConditionalObject:_target forKey:CPControlTargetKey];
|
||||
|
||||
if (_action !== NULL)
|
||||
if (_action !== nil)
|
||||
[aCoder encodeObject:_action forKey:CPControlActionKey];
|
||||
|
||||
[aCoder encodeInt:_sendActionOn forKey:CPControlSendActionOnKey];
|
||||
|
||||
@@ -39,6 +39,8 @@ var CPBindingOperationAnd = 0,
|
||||
{
|
||||
CPDictionary _info;
|
||||
id _source;
|
||||
|
||||
JSObject _suppressedNotifications;
|
||||
}
|
||||
|
||||
+ (void)exposeBinding:(CPString)aBinding forClass:(Class)aClass
|
||||
@@ -122,6 +124,7 @@ var CPBindingOperationAnd = 0,
|
||||
{
|
||||
_source = aSource;
|
||||
_info = [CPDictionary dictionaryWithObjects:[aDestination, aKeyPath] forKeys:[CPObservedObjectKey, CPObservedKeyPathKey]];
|
||||
_suppressedNotifications = {};
|
||||
|
||||
if (options)
|
||||
[_info setObject:options forKey:CPOptionsKey];
|
||||
@@ -169,6 +172,10 @@ var CPBindingOperationAnd = 0,
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
var objectSuppressions = _suppressedNotifications[[anObject UID]];
|
||||
if (objectSuppressions && objectSuppressions[aKeyPath])
|
||||
return;
|
||||
|
||||
[self setValueFor:context];
|
||||
}
|
||||
|
||||
@@ -197,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;
|
||||
|
||||
@@ -226,6 +232,44 @@ var CPBindingOperationAnd = 0,
|
||||
return [[options objectForKey:CPContinuouslyUpdatesValueBindingOption] boolValue];
|
||||
}
|
||||
|
||||
- (BOOL)handlesContentAsCompoundValue
|
||||
{
|
||||
var options = [_info objectForKey:CPOptionsKey];
|
||||
return [[options objectForKey:CPHandlesContentAsCompoundValueBindingOption] boolValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Use this to avoid reacting to the notifications coming out of a reverseTransformedValue:.
|
||||
*/
|
||||
- (void)suppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
|
||||
{
|
||||
if (!anObject)
|
||||
return;
|
||||
|
||||
var uid = [anObject UID],
|
||||
objectSuppressions = _suppressedNotifications[uid];
|
||||
if (!objectSuppressions)
|
||||
_suppressedNotifications[uid] = objectSuppressions = {};
|
||||
|
||||
objectSuppressions[aKeyPath] = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Use this to cancel suppressSpecificNotificationFromObject:keyPath:.
|
||||
*/
|
||||
- (void)unsuppressSpecificNotificationFromObject:(id)anObject keyPath:(CPString)aKeyPath
|
||||
{
|
||||
if (!anObject)
|
||||
return;
|
||||
|
||||
var uid = [anObject UID],
|
||||
objectSuppressions = _suppressedNotifications[uid];
|
||||
if (!objectSuppressions)
|
||||
return;
|
||||
|
||||
delete objectSuppressions[aKeyPath];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPObject (KeyValueBindingCreation)
|
||||
@@ -235,7 +279,6 @@ var CPBindingOperationAnd = 0,
|
||||
[CPBinder exposeBinding:aBinding forClass:[self class]];
|
||||
}
|
||||
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
return [CPBinder class];
|
||||
@@ -267,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);
|
||||
@@ -444,6 +487,8 @@ CPSelectedIndexBinding = @"selectedIndex";
|
||||
CPTextColorBinding = @"textColor";
|
||||
CPToolTipBinding = @"toolTip";
|
||||
CPValueBinding = @"value";
|
||||
CPContentBinding = @"content";
|
||||
CPContentValuesBinding = @"contentValues";
|
||||
|
||||
//Binding options constants
|
||||
CPAllowsEditingMultipleValuesSelectionBindingOption = @"CPAllowsEditingMultipleValuesSelection";
|
||||
@@ -474,4 +519,4 @@ CPValueTransformerBindingOption = @"CPValueTransformer";
|
||||
CPIsControllerMarker = function(/*id*/anObject)
|
||||
{
|
||||
return anObject === CPMultipleValuesMarker || anObject === CPNoSelectionMarker || anObject === CPNotApplicableMarker || anObject === CPNullMarker;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,9 +149,9 @@ var _CPLevelIndicatorBezelColor = nil,
|
||||
var filledColor = _CPLevelIndicatorSegmentNormalColor,
|
||||
value = [self doubleValue];
|
||||
|
||||
if (value < _criticalValue)
|
||||
if (value <= _criticalValue)
|
||||
filledColor = _CPLevelIndicatorSegmentCriticalColor;
|
||||
else if (value < _warningValue)
|
||||
else if (value <= _warningValue)
|
||||
filledColor = _CPLevelIndicatorSegmentWarningColor;
|
||||
|
||||
for (var i = 0; i < segmentCount; i++)
|
||||
@@ -223,6 +223,15 @@ var _CPLevelIndicatorBezelColor = nil,
|
||||
return _isEditable;
|
||||
}
|
||||
|
||||
- (CPView)hitTest:(CPPoint)aPoint
|
||||
{
|
||||
// Don't swallow clicks when displayed in a table.
|
||||
if (![self isEditable])
|
||||
return nil;
|
||||
|
||||
return [super hitTest:aPoint];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEditable] || ![self isEnabled])
|
||||
|
||||
@@ -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];
|
||||
@@ -711,10 +711,7 @@ var _CPMenuBarVisible = NO,
|
||||
if (aView && !theWindow)
|
||||
throw "In call to popUpMenuPositioningItem:atLocation:inView:callback:, view is not in any window.";
|
||||
|
||||
var delegate = [self delegate];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(menuWillOpen:)])
|
||||
[delegate menuWillOpen:self];
|
||||
[self _menuWillOpen];
|
||||
|
||||
// Convert location to global coordinates if not already in them.
|
||||
if (aView)
|
||||
@@ -802,10 +799,7 @@ var _CPMenuBarVisible = NO,
|
||||
|
||||
+ (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView withFont:(CPFont)aFont
|
||||
{
|
||||
var delegate = [aMenu delegate];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(menuWillOpen:)])
|
||||
[delegate menuWillOpen:aMenu];
|
||||
[aMenu _menuWillOpen];
|
||||
|
||||
if (!aFont)
|
||||
aFont = [CPFont systemFontOfSize:12.0];
|
||||
@@ -875,7 +869,15 @@ var _CPMenuBarVisible = NO,
|
||||
*/
|
||||
- (CPMenuItem)highlightedItem
|
||||
{
|
||||
return _highlightedIndex >= 0 ? _items[_highlightedIndex] : nil;
|
||||
if (_highlightedIndex < 0)
|
||||
return nil;
|
||||
|
||||
var highlightedItem = _items[_highlightedIndex];
|
||||
|
||||
if ([highlightedItem isSeparatorItem])
|
||||
return nil;
|
||||
|
||||
return highlightedItem;
|
||||
}
|
||||
|
||||
// Managing the Delegate
|
||||
@@ -890,6 +892,22 @@ var _CPMenuBarVisible = NO,
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
- (void)_menuWillOpen
|
||||
{
|
||||
var delegate = [self delegate];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(menuWillOpen:)])
|
||||
[delegate menuWillOpen:self];
|
||||
}
|
||||
|
||||
- (void)_menuDidClose
|
||||
{
|
||||
var delegate = [self delegate];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(menuDidClose:)])
|
||||
[delegate menuDidClose:self];
|
||||
}
|
||||
|
||||
// Handling Tracking
|
||||
/*!
|
||||
Cancels tracking.
|
||||
|
||||
@@ -11,6 +11,7 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
@implementation _CPMenuManager: CPObject
|
||||
{
|
||||
CPTimeInterval _startTime;
|
||||
BOOL _hasMouseGoneUpAfterStartedTracking;
|
||||
int _scrollingState;
|
||||
CGPoint _lastGlobalLocation;
|
||||
|
||||
@@ -60,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();
|
||||
@@ -87,6 +91,8 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
return [self trackMenuBarButtonEvent:anEvent];
|
||||
}
|
||||
|
||||
_hasMouseGoneUpAfterStartedTracking = NO;
|
||||
|
||||
[self trackEvent:anEvent];
|
||||
}
|
||||
|
||||
@@ -99,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)
|
||||
{
|
||||
@@ -221,8 +227,20 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
[CPEvent startPeriodicEventsAfterDelay:0.0 withPeriod:0.04];
|
||||
}
|
||||
}
|
||||
else if (type === CPLeftMouseUp && ([anEvent timestamp] - _startTime > (STICKY_TIME_INTERVAL + [activeMenu numberOfItems] * 5)))
|
||||
[trackingMenu cancelTracking];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent previous selected menu items from opening by stopping the timer if a
|
||||
@@ -306,11 +324,7 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
|
||||
// Hide all submenus.
|
||||
[self showMenu:nil fromMenu:trackingMenu atPoint:nil];
|
||||
|
||||
var delegate = [trackingMenu delegate];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(menuDidClose:)])
|
||||
[delegate menuDidClose:trackingMenu];
|
||||
[trackingMenu _menuDidClose];
|
||||
|
||||
if (_trackingCallback)
|
||||
_trackingCallback([self trackingMenuContainer], trackingMenu);
|
||||
@@ -379,6 +393,8 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
var count = _menuContainerStack.length,
|
||||
index = count;
|
||||
|
||||
[newMenu _menuWillOpen];
|
||||
|
||||
// Hide all menus up to the base menu...
|
||||
while (index--)
|
||||
{
|
||||
@@ -398,6 +414,8 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
|
||||
[_CPMenuWindow poolMenuWindow:menuContainer];
|
||||
[_menuContainerStack removeObjectAtIndex:index];
|
||||
|
||||
[menu _menuDidClose];
|
||||
}
|
||||
|
||||
if (!newMenu)
|
||||
|
||||
@@ -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;
|
||||
@@ -496,6 +488,7 @@ CPOffState
|
||||
if (_submenu)
|
||||
{
|
||||
[_submenu setSupermenu:_menu];
|
||||
[_submenu setTitle:[self title]]
|
||||
|
||||
[self setTarget:_menu];
|
||||
[self setAction:@selector(submenuAction:)];
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -498,7 +508,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return "<_CPObservableArray: "+[super description]+" >";
|
||||
return "<_CPObservableArray: " + [super description] + " >";
|
||||
}
|
||||
|
||||
- (id)initWithArray:(CPArray)anArray
|
||||
@@ -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];
|
||||
|
||||
@@ -710,6 +733,17 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
{
|
||||
[[_controller selectedObjects] setValue:theValue forKeyPath:theKeyPath];
|
||||
[_cachedValues removeObjectForKey:theKeyPath];
|
||||
|
||||
// Allow handlesContentAsCompoundValue to work, based on observation of Cocoa's
|
||||
// NSArrayController - when handlesContentAsCompoundValue and setValue:forKey:@"selection.X"
|
||||
// is called, the array controller causes the compound value to be rewritten if
|
||||
// handlesContentAsCompoundValue == YES. Note that
|
||||
// A) this doesn't use observation (observe: X is not visible in backtraces)
|
||||
// B) it only happens through the selection proxy and not on arrangedObject.X, content.X
|
||||
// or even selectedObjects.X.
|
||||
// FIXME The main code for this should somehow be in CPArrayController and also work
|
||||
// for table based row edits.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:_controller] _contentArrayDidChange];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)theValue forKey:(CPString)theKeyPath
|
||||
|
||||
@@ -1458,6 +1458,44 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]];
|
||||
}
|
||||
|
||||
|
||||
- (void)keyDown:(CPEvent)anEvent
|
||||
{
|
||||
var character = [anEvent charactersIgnoringModifiers],
|
||||
modifierFlags = [anEvent modifierFlags];
|
||||
|
||||
// Check for the key events manually, as opposed to waiting for CPWindow to sent the actual action message
|
||||
// in _processKeyboardUIKey:, because we might not want to handle the arrow events.
|
||||
|
||||
if (character !== CPRightArrowFunctionKey && character !== CPLeftArrowFunctionKey)
|
||||
return [super keyDown:anEvent];
|
||||
|
||||
var rows = [self selectedRowIndexes],
|
||||
indexes = [],
|
||||
items = [];
|
||||
|
||||
[rows getIndexes:indexes maxCount:-1 inIndexRange:nil];
|
||||
|
||||
var i = 0,
|
||||
c = [indexes count];
|
||||
|
||||
for (; i < c; i++)
|
||||
items.push([self itemAtRow:indexes[i]]);
|
||||
|
||||
|
||||
if (character === CPRightArrowFunctionKey)
|
||||
{
|
||||
for (var i = 0; i < c; i++)
|
||||
[self expandItem:items[i]];
|
||||
}
|
||||
else if (character === CPLeftArrowFunctionKey)
|
||||
{
|
||||
for (var i = 0; i < c; i++)
|
||||
[self collapseItem:items[i]];
|
||||
}
|
||||
|
||||
[super keyDown:anEvent];
|
||||
}
|
||||
@end
|
||||
|
||||
// FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever...
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -2771,7 +2771,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
*/
|
||||
- (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes atPoint:(CGPoint)mouseDownPoint
|
||||
{
|
||||
return YES;
|
||||
return [rowIndexes count] > 0 && [self numberOfRows] > 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -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])
|
||||
@@ -4610,7 +4610,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
}
|
||||
_wasSelectionBroken = true;
|
||||
}
|
||||
else if (_wasSelectionBroken && ((shouldGoUpward && i !== [selectedIndexes firstIndex]) || (!shouldGoUpward && i !== [selectedIndexes lastindex])))
|
||||
else if (_wasSelectionBroken && ((shouldGoUpward && i !== [selectedIndexes firstIndex]) || (!shouldGoUpward && i !== [selectedIndexes lastIndex])))
|
||||
{
|
||||
shouldGoUpward ? i = [selectedIndexes firstIndex] - 1 : i = [selectedIndexes lastIndex];
|
||||
_wasSelectionBroken = false;
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#import "../Foundation/Ref.h"
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPStringDrawing.j"
|
||||
@import "CPCompatibility.j"
|
||||
@@ -83,12 +85,11 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
CPColor _textFieldBackgroundColor;
|
||||
|
||||
id _placeholderString;
|
||||
CPString _placeholderString;
|
||||
CPString _stringValue;
|
||||
|
||||
id _delegate;
|
||||
|
||||
CPString _textDidChangeValue;
|
||||
|
||||
// NS-style Display Properties
|
||||
CPTextFieldBezelStyle _bezelStyle;
|
||||
BOOL _isBordered;
|
||||
@@ -168,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 */
|
||||
@@ -481,17 +482,17 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[self setNeedsLayout];
|
||||
|
||||
_isEditing = NO;
|
||||
_stringValue = [self stringValue];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
var string = [self stringValue],
|
||||
element = [self _inputElement],
|
||||
var element = [self _inputElement],
|
||||
font = [self currentValueForThemeAttribute:@"font"];
|
||||
|
||||
// generate the font metric
|
||||
[font _getMetrics];
|
||||
|
||||
element.value = string;
|
||||
element.value = _stringValue;
|
||||
element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
|
||||
element.style.font = [font cssString];
|
||||
element.style.zIndex = 1000;
|
||||
@@ -506,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);
|
||||
|
||||
@@ -548,8 +550,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
CPTextFieldInputOwner = self;
|
||||
}, 0.0);
|
||||
|
||||
element.value = [self stringValue];
|
||||
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
|
||||
CPTextFieldInputIsActive = YES;
|
||||
@@ -572,17 +572,33 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
{
|
||||
[self unsetThemeState:CPThemeStateEditing];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
var element = [self _inputElement],
|
||||
error = @"";
|
||||
|
||||
// If there is a formatter, always give it a chance to reject the resignation,
|
||||
// even if the value has not changed.
|
||||
if ([self _valueIsValid:element.value] === NO)
|
||||
{
|
||||
[self setThemeState:CPThemeStateEditing];
|
||||
element.focus();
|
||||
return NO;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Cache the formatted string
|
||||
_stringValue = [self stringValue];
|
||||
|
||||
_willBecomeFirstResponderByClick = NO;
|
||||
|
||||
[self _updatePlaceholderState];
|
||||
|
||||
[self setNeedsLayout];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
var element = [self _inputElement];
|
||||
|
||||
if ([self stringValue] !== element.value)
|
||||
[self _setStringValue:element.value];
|
||||
|
||||
CPTextFieldInputResigning = YES;
|
||||
|
||||
if (CPTextFieldInputIsActive)
|
||||
@@ -610,7 +626,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
#endif
|
||||
|
||||
//post CPControlTextDidEndEditingNotification
|
||||
// post CPControlTextDidEndEditingNotification
|
||||
if (_isEditing)
|
||||
{
|
||||
_isEditing = NO;
|
||||
@@ -625,6 +641,28 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)_valueIsValid:(CPString)aValue
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
var error = @"";
|
||||
|
||||
if ([self _setStringValue:aValue isNewValue:NO errorDescription:AT_REF(error)] === NO)
|
||||
{
|
||||
var acceptInvalidValue = NO;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(control:didFailToFormatString:errorDescription:)])
|
||||
acceptInvalidValue = [_delegate control:self didFailToFormatString:[self _inputElement] errorDescription:error];
|
||||
|
||||
if (acceptInvalidValue === NO)
|
||||
return NO;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Text fields require panels to become key window, so this returns \c YES.
|
||||
*/
|
||||
@@ -685,11 +723,14 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
- (void)keyUp:(CPEvent)anEvent
|
||||
{
|
||||
var oldValue = [self stringValue];
|
||||
[self _setStringValue:[self _inputElement].value];
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
if (oldValue !== [self stringValue])
|
||||
var newValue = [self _inputElement].value;
|
||||
|
||||
if (newValue !== _stringValue)
|
||||
{
|
||||
[self _setStringValue:newValue];
|
||||
|
||||
if (!_isEditing)
|
||||
{
|
||||
_isEditing = YES;
|
||||
@@ -699,6 +740,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
@@ -732,45 +775,53 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
- (void)insertNewline:(id)sender
|
||||
{
|
||||
if (_isEditing)
|
||||
if ([self _valueIsValid:_stringValue])
|
||||
{
|
||||
_isEditing = NO;
|
||||
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
|
||||
}
|
||||
// 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]];
|
||||
}
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
[self selectText:nil];
|
||||
// If there is no target action, or the sendAction call returns
|
||||
// success.
|
||||
if (![self action] || [self sendAction:[self action] to:[self target]])
|
||||
{
|
||||
[self selectAll:nil];
|
||||
}
|
||||
}
|
||||
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:NO];
|
||||
}
|
||||
|
||||
- (void)insertNewlineIgnoringFieldEditor:(id)sender
|
||||
{
|
||||
var oldValue = [self stringValue];
|
||||
|
||||
[self _inputElement].value += CPNewlineCharacter;
|
||||
[self _setStringValue:[self _inputElement].value];
|
||||
|
||||
if (oldValue !== [self stringValue])
|
||||
{
|
||||
if (!_isEditing)
|
||||
{
|
||||
_isEditing = YES;
|
||||
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
}
|
||||
[self _insertCharacterIgnoringFieldEditor:CPNewlineCharacter];
|
||||
}
|
||||
|
||||
- (void)insertTabIgnoringFieldEditor:(id)sender
|
||||
{
|
||||
var oldValue = [self stringValue];
|
||||
[self _insertCharacterIgnoringFieldEditor:CPTabCharacter];
|
||||
}
|
||||
|
||||
[self _inputElement].value += CPTabCharacter;
|
||||
[self _setStringValue:[self _inputElement].value];
|
||||
- (void)_insertCharacterIgnoringFieldEditor:(CPString)aCharacter
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
if (oldValue !== [self stringValue])
|
||||
var oldValue = _stringValue,
|
||||
range = [self selectedRange],
|
||||
element = [self _inputElement];
|
||||
|
||||
element.value = [element.value stringByReplacingCharactersInRange:[self selectedRange] withString:aCharacter];
|
||||
[self _setStringValue:element.value];
|
||||
|
||||
// NOTE: _stringValue is now the current input element value
|
||||
if (oldValue !== _stringValue)
|
||||
{
|
||||
if (!_isEditing)
|
||||
{
|
||||
@@ -780,6 +831,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)textDidBlur:(CPNotification)note
|
||||
@@ -810,15 +863,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[super textDidChange:note];
|
||||
}
|
||||
|
||||
- (void)sendAction:(SEL)anAction to:(id)anObject
|
||||
{
|
||||
[self _reverseSetBinding];
|
||||
|
||||
[CPApp sendAction:anAction to:anObject from:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the string the text field.
|
||||
Returns the string in the text field.
|
||||
*/
|
||||
- (id)objectValue
|
||||
{
|
||||
@@ -827,24 +873,84 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
/*
|
||||
@ignore
|
||||
Sets the internal string value without updating the value in the input element
|
||||
Sets the internal string value without updating the value in the input element.
|
||||
This should only be invoked when the underlying text element's value has changed.
|
||||
*/
|
||||
- (void)_setStringValue:(id)aValue
|
||||
- (BOOL)_setStringValue:(CPString)aValue
|
||||
{
|
||||
[self willChangeValueForKey:@"objectValue"];
|
||||
[super setObjectValue:String(aValue)];
|
||||
[self _updatePlaceholderState];
|
||||
[self didChangeValueForKey:@"objectValue"];
|
||||
return [self _setStringValue:aValue isNewValue:YES errorDescription:nil];
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
Sets the internal string value without updating the value in the input element.
|
||||
If there is a formatter and formatting fails, returns NO. Otherwise returns YES.
|
||||
*/
|
||||
- (BOOL)_setStringValue:(CPString)aValue isNewValue:(BOOL)isNewValue errorDescription:(CPStringRef)anError
|
||||
{
|
||||
_stringValue = aValue;
|
||||
|
||||
var objectValue = aValue,
|
||||
formatter = [self formatter],
|
||||
result = YES;
|
||||
|
||||
if (formatter)
|
||||
{
|
||||
var object = nil;
|
||||
|
||||
if ([formatter getObjectValue:AT_REF(object) forString:aValue errorDescription:anError])
|
||||
objectValue = object;
|
||||
else
|
||||
{
|
||||
objectValue = undefined; // Mark the value as invalid
|
||||
result = NO;
|
||||
}
|
||||
|
||||
isNewValue |= objectValue !== [super objectValue];
|
||||
}
|
||||
|
||||
if (isNewValue)
|
||||
{
|
||||
[self willChangeValueForKey:@"objectValue"];
|
||||
[super setObjectValue:objectValue];
|
||||
[self _updatePlaceholderState];
|
||||
[self didChangeValueForKey:@"objectValue"];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setObjectValue:(id)aValue
|
||||
{
|
||||
[super setObjectValue:aValue];
|
||||
|
||||
var formatter = [self formatter];
|
||||
|
||||
if (formatter)
|
||||
{
|
||||
// If there is a formatter, make sure the object value can be formatted successfully
|
||||
var formattedString = [self hasThemeState:CPThemeStateEditing] ? [formatter editingStringForObjectValue:aValue] : [formatter stringForObjectValue:aValue];
|
||||
|
||||
if (formattedString === nil)
|
||||
{
|
||||
var value = nil;
|
||||
|
||||
// Formatting failed, get an "empty" object by formatting an empty string.
|
||||
// If that fails, the value is undefined.
|
||||
if ([formatter getObjectValue:AT_REF(value) forString:@"" errorDescription:nil] === NO)
|
||||
value = undefined;
|
||||
|
||||
[super setObjectValue:value];
|
||||
}
|
||||
}
|
||||
|
||||
_stringValue = [self stringValue];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
if (CPTextFieldInputOwner === self || [[self window] firstResponder] === self)
|
||||
[self _inputElement].value = aValue;
|
||||
[self _inputElement].value = _stringValue;
|
||||
|
||||
#endif
|
||||
|
||||
[self _updatePlaceholderState];
|
||||
@@ -852,9 +958,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
- (void)_updatePlaceholderState
|
||||
{
|
||||
var string = [self stringValue];
|
||||
|
||||
if ((!string || string.length === 0) && ![self hasThemeState:CPThemeStateEditing])
|
||||
if ((!_stringValue || _stringValue.length === 0) && ![self hasThemeState:CPThemeStateEditing])
|
||||
[self setThemeState:CPTextFieldStatePlaceholder];
|
||||
else
|
||||
[self unsetThemeState:CPTextFieldStatePlaceholder];
|
||||
@@ -920,8 +1024,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
minSize = [self currentValueForThemeAttribute:@"min-size"],
|
||||
maxSize = [self currentValueForThemeAttribute:@"max-size"],
|
||||
lineBreakMode = [self lineBreakMode],
|
||||
text = ([self stringValue] || @" "),
|
||||
textSize = _CGSizeMakeCopy(frameSize),
|
||||
text = (_stringValue || @" "),
|
||||
textSize = CGSizeMakeCopy(frameSize),
|
||||
font = [self currentValueForThemeAttribute:@"font"];
|
||||
|
||||
textSize.width -= contentInset.left + contentInset.right;
|
||||
@@ -991,8 +1095,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
return;
|
||||
|
||||
var pasteboard = [CPPasteboard generalPasteboard],
|
||||
stringValue = [self stringValue],
|
||||
stringForPasting = [stringValue substringWithRange:selectedRange];
|
||||
stringForPasting = [_stringValue substringWithRange:selectedRange];
|
||||
|
||||
[pasteboard declareTypes:[CPStringPboardType] owner:nil];
|
||||
[pasteboard setString:stringForPasting forType:CPStringPboardType];
|
||||
@@ -1022,9 +1125,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[self deleteBackward:sender];
|
||||
|
||||
var selectedRange = [self selectedRange],
|
||||
stringValue = [self stringValue],
|
||||
pasteString = [pasteboard stringForType:CPStringPboardType],
|
||||
newValue = [stringValue stringByReplacingCharactersInRange:selectedRange withString:pasteString];
|
||||
newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:pasteString];
|
||||
|
||||
[self setStringValue:newValue];
|
||||
[self setSelectedRange:CPMakeRange(selectedRange.location + pasteString.length, 0)];
|
||||
@@ -1038,6 +1140,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
if ([[self window] firstResponder] !== self)
|
||||
return CPMakeRange(0, 0);
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
// we wrap this in try catch because firefox will throw an exception in certain instances
|
||||
try
|
||||
{
|
||||
@@ -1064,6 +1168,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
// fall through to the return
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return CPMakeRange(0, 0);
|
||||
}
|
||||
|
||||
@@ -1072,6 +1178,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
if (![[self window] firstResponder] === self)
|
||||
return;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
var inputElement = [self _inputElement];
|
||||
|
||||
try
|
||||
@@ -1100,6 +1208,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
catch (e)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)selectAll:(id)sender
|
||||
@@ -1117,8 +1227,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
selectedRange.location += 1;
|
||||
selectedRange.length -= 1;
|
||||
|
||||
var stringValue = [self stringValue],
|
||||
newValue = [stringValue stringByReplacingCharactersInRange:selectedRange withString:""];
|
||||
var newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:""];
|
||||
|
||||
[self setStringValue:newValue];
|
||||
[self setSelectedRange:CPMakeRange(selectedRange.location, 0)];
|
||||
@@ -1186,10 +1295,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;
|
||||
@@ -1214,6 +1321,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")
|
||||
@@ -1271,7 +1386,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
string = [self placeholderString];
|
||||
else
|
||||
{
|
||||
string = [self stringValue];
|
||||
string = _stringValue;
|
||||
|
||||
if ([self isSecure])
|
||||
string = secureStringForString(string);
|
||||
|
||||
@@ -519,7 +519,12 @@ CPThemeStateCircular = CPThemeState("circular");
|
||||
|
||||
- (void)setValue:(id)aValue
|
||||
{
|
||||
[self setValue:aValue forState:CPThemeStateNormal];
|
||||
_cache = {};
|
||||
|
||||
if (aValue === undefined || aValue === nil)
|
||||
_values = [CPDictionary dictionary];
|
||||
else
|
||||
_values = [CPDictionary dictionaryWithObject:aValue forKey:String(CPThemeStateNormal)];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forState:(CPThemeState)aState
|
||||
|
||||
@@ -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])
|
||||
@@ -218,6 +226,8 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
[self _autocompleteWithDOMEvent:nil];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (void)_selectToken:(_CPTokenFieldToken)token byExtendingSelection:(BOOL)extend
|
||||
{
|
||||
var indexOfToken = [[self _tokens] indexOfObject:token];
|
||||
@@ -368,10 +378,10 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
|
||||
[self unsetThemeState:CPThemeStateEditing];
|
||||
|
||||
[self _autocomplete];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
[self _autocomplete];
|
||||
|
||||
var element = [self _inputElement];
|
||||
|
||||
CPTokenFieldInputResigning = YES;
|
||||
@@ -654,7 +664,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 +754,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 +874,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 +965,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 +977,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 +992,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 +1005,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 +1014,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
|
||||
|
||||
var placeEditor = function(useRemainingWidth)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var element = [self _inputElement],
|
||||
textWidth = 1;
|
||||
|
||||
@@ -1004,6 +1024,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 +1039,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 +1066,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 +1074,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 +1149,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 [];
|
||||
@@ -1184,11 +1210,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 +1275,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 +1318,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(MAX(_labelSize.width, minSize.width), 100000000.0);
|
||||
_maxSize = CGSizeMake(MAX(_labelSize.width, maxSize.width), 100000000.0);
|
||||
|
||||
[_toolbar tile];
|
||||
}
|
||||
|
||||
@@ -2543,6 +2543,32 @@ setBoundsOrigin:
|
||||
return (_themeAttributes && _themeAttributes[aName] !== undefined);
|
||||
}
|
||||
|
||||
- (void)registerThemeValues:(CPArray)themeValues
|
||||
{
|
||||
for (var i = 0; i < themeValues.length; ++i)
|
||||
{
|
||||
var attributeValueState = themeValues[i],
|
||||
attribute = attributeValueState[0],
|
||||
value = attributeValueState[1],
|
||||
state = attributeValueState[2];
|
||||
|
||||
if (state)
|
||||
[self setValue:value forThemeAttribute:attribute inState:state];
|
||||
else
|
||||
[self setValue:value forThemeAttribute:attribute];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)registerThemeValues:(CPArray)themeValues inherit:(CPArray)inheritedValues
|
||||
{
|
||||
// Register inherited values first, then override those with the subtheme values.
|
||||
if (inheritedValues)
|
||||
[self registerThemeValues:inheritedValues];
|
||||
|
||||
if (themeValues)
|
||||
[self registerThemeValues:themeValues];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aViewName
|
||||
{
|
||||
return nil;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -2928,5 +2928,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(',');
|
||||
|
||||
@@ -42,7 +42,16 @@
|
||||
#include "DOM/CPPlatformString.j"
|
||||
#else
|
||||
@implementation CPPlatformString : CPBasePlatformString
|
||||
|
||||
+ (CGSize)sizeOfString:(CPString)aString withFont:(CPFont)aFont forWidth:(float)aWidth
|
||||
{
|
||||
return _CGSizeMakeZero();
|
||||
}
|
||||
|
||||
+ (CPDictionary)metricsOfFont:(CPFont)aFont
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjectsAndKeys:0, @"ascender", 0, @"descender", 0, @"lineHeight"];
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
After Width: | Height: | Size: 146 B |
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 219 B |
|
After Width: | Height: | Size: 490 B |
|
After Width: | Height: | Size: 160 B |
|
After Width: | Height: | Size: 361 B |
|
After Width: | Height: | Size: 694 B |
|
After Width: | Height: | Size: 221 B |
|
After Width: | Height: | Size: 146 B |
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 219 B |
|
After Width: | Height: | Size: 407 B |
|
After Width: | Height: | Size: 160 B |
|
After Width: | Height: | Size: 348 B |
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 422 B |
|
After Width: | Height: | Size: 527 B |
|
Before Width: | Height: | Size: 123 B After Width: | Height: | Size: 142 B |
|
After Width: | Height: | Size: 142 B |
|
After Width: | Height: | Size: 447 B |
|
After Width: | Height: | Size: 471 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 164 B |
|
Before Width: | Height: | Size: 757 B After Width: | Height: | Size: 844 B |
|
Before Width: | Height: | Size: 735 B After Width: | Height: | Size: 908 B |
|
Before Width: | Height: | Size: 559 B After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 531 B After Width: | Height: | Size: 515 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: 160 B |
|
Before Width: | Height: | Size: 1004 B After Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 164 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.1 KiB After Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 1012 B After Width: | Height: | Size: 133 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 234 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: 225 B |
|
Before Width: | Height: | Size: 1007 B After Width: | Height: | Size: 134 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 212 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,37 @@ 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],
|
||||
|
||||
[@"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 +861,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 +932,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 +982,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 +1014,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 +1022,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!"],
|
||||
|
||||
@@ -225,7 +225,7 @@ var ItemSizes = { },
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)registerThemeValues:(CPArray)themeValues forView:aView inherit:(CPArray)inheritedValues
|
||||
+ (void)registerThemeValues:(CPArray)themeValues forView:(CPView)aView inherit:(CPArray)inheritedValues
|
||||
{
|
||||
// Register inherited values first, then override those with the subtheme values.
|
||||
if (inheritedValues)
|
||||
|
||||
@@ -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,715 @@
|
||||
/*
|
||||
* _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"
|
||||
|
||||
|
||||
/*!
|
||||
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.
|
||||
For tables that display single-line rows of scalar data, no subclassing should be necessary.
|
||||
|
||||
The delegate of this class MUST implement the following methods:
|
||||
|
||||
numberOfItems The number of items the list should display
|
||||
numberOfVisibleItems The maximum number of items to display at once
|
||||
objectValueForItemAtIndex: Retrieves an object value for an item in the range 0..(numberOfItems - 1)
|
||||
|
||||
Objects of this class send the following notifications:
|
||||
|
||||
_CPPopUpListWillPopUpNotification
|
||||
_CPPopUpListWillDismissNotification
|
||||
_CPPopUpListDidDismissNotification
|
||||
_CPPopUpListItemWasClickedNotification
|
||||
*/
|
||||
@implementation _CPPopUpList : CPObject
|
||||
{
|
||||
_CPPopUpListDelegate _delegate;
|
||||
BOOL _itemWasClicked;
|
||||
BOOL _listWasClicked;
|
||||
int _listWidth;
|
||||
_CPPopUpPanel _panel @accessors(readonly, property=panel);
|
||||
CPScrollView _scrollView @accessors(readonly, property=scrollView);
|
||||
_CPPopUpTableView _tableView @accessors(readonly, property=tableView);
|
||||
CPTableColumn _tableColumn;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a pop up list of choices which will display relative to the given rect in base coordinates.
|
||||
|
||||
@param aDelegate The object that usually is controlling this list.
|
||||
*/
|
||||
- (id)initWithDelegate:(_CPPopUpListDelegate)aDelegate
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
_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 ([_delegate numberOfItems] > 0)
|
||||
[_tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
|
||||
else
|
||||
[_tableView setEnabled:NO];
|
||||
|
||||
[_scrollView scrollToBeginningOfDocument:nil];
|
||||
|
||||
[self finalizeTableView:_tableView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Create and configure the panel containing the table view.
|
||||
Subclasses should override this (and usually call super first)
|
||||
if they want to customize the list panel.
|
||||
*/
|
||||
- (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;
|
||||
}
|
||||
|
||||
/*!
|
||||
Create and configure the table view. Subclasses should use \ref finalizeTableView:.
|
||||
*/
|
||||
- (_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;
|
||||
}
|
||||
|
||||
- (void)removeTableViewObservers
|
||||
{
|
||||
if (_tableView)
|
||||
{
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
[defaultCenter removeObserver:self name:CPTableViewSelectionIsChangingNotification object:_tableView];
|
||||
[defaultCenter removeObserver:self name:CPTableViewSelectionDidChangeNotification object:_tableView];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
If subclasses want to configure the table view after it has been
|
||||
completely configured, for example to set a custom data view,
|
||||
this is the place. Such customization can also be done by an
|
||||
observer of _CPPopUpListListWillDisplayNotification.
|
||||
*/
|
||||
- (void)finalizeTableView:(CPTableView)aTableView
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and configures the scroll view containing the table view.
|
||||
Subclasses should override this (and usually call super first)
|
||||
if they want to customize the scroll view.
|
||||
*/
|
||||
- (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;
|
||||
}
|
||||
|
||||
/*!
|
||||
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];
|
||||
}
|
||||
|
||||
/*!
|
||||
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)];
|
||||
[self scrollItemAtIndexToTop:[_tableView selectedRow]];
|
||||
|
||||
[self listWillPopUp];
|
||||
|
||||
[_panel orderFront:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
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.
|
||||
*/
|
||||
- (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], [_delegate numberOfVisibleItems]),
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the list is currently visible.
|
||||
*/
|
||||
- (BOOL)isVisible
|
||||
{
|
||||
return [_panel isVisible];
|
||||
}
|
||||
|
||||
/*!
|
||||
Subclasses MUST call this method just before the list is about to display.
|
||||
*/
|
||||
- (void)listWillPopUp
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:_CPPopUpListWillPopUpNotification object:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
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 < ([_delegate numberOfItems] - 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;
|
||||
}
|
||||
|
||||
/*!
|
||||
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];
|
||||
}
|
||||
|
||||
- (BOOL)selectRow:(int)row
|
||||
{
|
||||
if (row === [_tableView selectedRow])
|
||||
return NO;
|
||||
|
||||
var indexes = row >= 0 ? [CPIndexSet indexSetWithIndex:row] : [CPIndexSet indexSet];
|
||||
|
||||
[_tableView selectRowIndexes:indexes byExtendingSelection:NO];
|
||||
|
||||
if (row >= 0)
|
||||
{
|
||||
[_tableView scrollRowToVisible:row];
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)scrollItemAtIndexToTop:(int)row
|
||||
{
|
||||
var rect = [_tableView rectOfRow:row];
|
||||
|
||||
[[_tableView superview] scrollToPoint:rect.origin];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the selected value as a single-line string. If no value is selected,
|
||||
returns nil.
|
||||
*/
|
||||
- (CPString)selectedStringValue
|
||||
{
|
||||
var row = [_tableView selectedRow];
|
||||
|
||||
return (row >= 0) ? [self stringValueForObjectValue:[_delegate objectValueForItemAtIndex:row] row:row] : nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
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 && ([_delegate numberOfItems] > 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 = ([_delegate numberOfItems] > 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 && ([_delegate numberOfItems] > 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 = ([_delegate numberOfItems] > 0) && flag;
|
||||
}
|
||||
|
||||
/*!
|
||||
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];
|
||||
}
|
||||
|
||||
/*!
|
||||
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 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 there are no search results, an empty string is returned.
|
||||
If subclasses use a data representation other than CPStrings, they must override
|
||||
this method and return the appropriate data when there are no search results.
|
||||
|
||||
If the 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 Table data for the given row
|
||||
@param aRow The row being displayed
|
||||
@return A value to be displayed in the list
|
||||
*/
|
||||
- (id)displayValueForObjectValue:(id)aValue row:(int)aRow
|
||||
{
|
||||
return aValue || @"";
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a single-line string for use in an autocomplete field. Subclasses should override
|
||||
this if the row data is not convertable to a simple string, and return the data as a single-line string.
|
||||
|
||||
@param aValue Table data to be converted to a string
|
||||
@param aRow The row whose data is being converted
|
||||
@return A value to be displayed in the autocomplete field
|
||||
*/
|
||||
- (CPString)stringValueForObjectValue:(id)aValue row:(int)aRow
|
||||
{
|
||||
return String(aValue);
|
||||
}
|
||||
|
||||
- (void)tableViewClickAction:(id)sender
|
||||
{
|
||||
[self close];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var _CPPopUpListFieldKey = @"_CPPopUpListDelegateKey",
|
||||
_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;
|
||||
|
||||
_delegate = [aCoder decodeObjectForKey:_CPPopUpListDelegateKey];
|
||||
_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:_delegate forKey:_CPPopUpListDelegateKey];
|
||||
[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([_delegate numberOfItems], 1);
|
||||
}
|
||||
|
||||
- (id)tableView:(id)aTableView objectValueForTableColumn:(CPTableColumn)aColumn row:(int)aRow
|
||||
{
|
||||
return [self displayValueForObjectValue:[_delegate objectValueForItemAtIndex:aRow] row: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
|
||||
@@ -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.004"
|
||||
VERSION_NUMBER = "1.0.0.009"
|
||||
|
||||
|
||||
# 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')
|
||||
|
||||
@@ -362,7 +362,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.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;
|
||||
|
||||
@@ -72,6 +72,7 @@ CPRoundPlain = 1;
|
||||
CPRoundDown = 2;
|
||||
CPRoundUp = 3;
|
||||
CPRoundBankers = 4;
|
||||
_CPRoundHalfDown = 5; // Private API rounding mode used by CPNumberFormatter.
|
||||
|
||||
//Exceptions
|
||||
CPDecimalNumberOverflowException = @"CPDecimalNumberOverflowException";
|
||||
@@ -1330,6 +1331,10 @@ function CPDecimalRound(result, dcm, scale ,roundingMode)
|
||||
n = result._mantissa[l];
|
||||
up = (n >= 5);
|
||||
break;
|
||||
case _CPRoundHalfDown:
|
||||
n = result._mantissa[l];
|
||||
up = (n > 5);
|
||||
break;
|
||||
case CPRoundBankers:
|
||||
n = result._mantissa[l];
|
||||
if (n > 5)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
make sure that you cannot configure the public subclasses CPDateFormatter and CPNumberFormatter to satisfy your requirements.
|
||||
*/
|
||||
|
||||
#import "Ref.h"
|
||||
|
||||
@import "CPException.j"
|
||||
@import "CPObject.j"
|
||||
|
||||
@@ -80,19 +82,19 @@
|
||||
/*!
|
||||
The default implementation of this method raises an exception.
|
||||
|
||||
When implementing a subclass, return by reference the object anObject after creating it from string.
|
||||
Return YES if the conversion is successful. If you return NO, also return by indirection (in error)
|
||||
When implementing a subclass, return by reference the object anObject after creating it from aString.
|
||||
Return \c YES if the conversion is successful. If you return \c NO, also return by reference (in anError)
|
||||
a localized user-presentable CPString object that explains the reason why the conversion failed; the delegate
|
||||
(if any) of the CPControl object managing the cell can then respond to the failure in
|
||||
control:didFailToFormatString:errorDescription:. However, if error is nil, the sender is not interested in
|
||||
(if any) of the CPControl object can then respond to the failure in
|
||||
control:didFailToFormatString:errorDescription:. However, if anError is nil, the sender is not interested in
|
||||
the error description, and you should not attempt to assign one.
|
||||
|
||||
@param anObject if conversion is successful, upon return contains the object created from the string
|
||||
@param aString the string to parse.
|
||||
@param anError if non-nil, if there is an error during the conversion, upon return contains an CPString object that describes the problem.
|
||||
@return BOOL YES if the conversion from the string to a view content object was successful, otherwise NO.
|
||||
@return BOOL \c YES if the conversion from the string to a view content object was successful, otherwise \c NO.
|
||||
*/
|
||||
- (BOOL)getObjectValue:(id)anObject forString:(CPString)aString errorDescription:(CPString)anError
|
||||
- (BOOL)getObjectValue:(idRef)anObject forString:(CPString)aString errorDescription:(CPStringRef)anError
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
return NO;
|
||||
@@ -104,11 +106,11 @@
|
||||
This method is invoked each time the user presses a key while the cell has the keyboard focus it lets you verify and
|
||||
edit the cell text as the user types it.
|
||||
|
||||
In a subclass implementation, evaluate partialString according to the context, edit the text if necessary, and return
|
||||
by reference any edited string in newString. Return YES if partialString is acceptable and NO if partialString is unacceptable.
|
||||
If you return NO and newString is nil, the cell displays partialString minus the last character typed. If you return NO, you can
|
||||
also return by indirection an CPString object (in error) that explains the reason why the validation failed; the delegate (if any)
|
||||
of the CPControl object managing the cell can then respond to the failure in control:didFailToValidatePartialString:errorDescription:.
|
||||
In a subclass implementation, evaluate aPartialString according to the context, edit the text if necessary, and return
|
||||
by reference any edited string in aNewString. Return \c YES if aPartialString is acceptable and \c NO if aPartialString is unacceptable.
|
||||
If you return \c NO and aNewString is nil, the control displays aPartialString minus the last character typed. If you return \c NO, you can
|
||||
also return by reference a CPString object (in anError) that explains the reason why the validation failed; the delegate (if any)
|
||||
of the CPControl can then respond to the failure in control:didFailToValidatePartialString:errorDescription:.
|
||||
The selection range will always be set to the end of the text if replacement occurs.
|
||||
|
||||
This method is a compatibility method. If a subclass overrides this method and does not override
|
||||
@@ -118,12 +120,16 @@
|
||||
@param aPartialString the text currently in the view.
|
||||
@param aNewString if aPartialString needs to be modified, upon return contains the replacement string.
|
||||
@param anError if non-nil, if validation fails contains a CPString object that describes the problem.
|
||||
@return YES if aPartialString is an acceptable value, otherwise NO.
|
||||
@return \c YES if aPartialString is an acceptable value, otherwise \c NO.
|
||||
*/
|
||||
- (BOOL)isPartialStringValid:(CPString)aPartialString newEditingString:(CPString)aNewString errorDescription:(CPString)anError
|
||||
- (BOOL)isPartialStringValid:(CPString)aPartialString newEditingString:(CPStringRef)aNewString errorDescription:(CPStringRef)anError
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
return NO;
|
||||
AT_DEREF(aPartialString, nil);
|
||||
|
||||
if (anError)
|
||||
AT_DEREF(anError, nil);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -131,24 +137,35 @@
|
||||
not necessarily at the end of the string, and preserve the selection (or set a different one, such as selecting the erroneous part of
|
||||
the string the user has typed).
|
||||
|
||||
In a subclass implementation, evaluate partialString according to the context. Return YES if partialStringPtr is acceptable and NO if partialStringPtr
|
||||
is unacceptable. Assign a new string to partialStringPtr and a new range to proposedSelRangePtr and return NO if you want to replace the string and
|
||||
change the selection range. If you return NO, you can also return by indirection an CPString object (in error) that explains the reason why the
|
||||
validation failed; the delegate (if any) of the CPControl object managing the cell can then respond to the failure in
|
||||
In a subclass implementation, evaluate aPartialString according to the context. Return \c YES if aPartialString is acceptable and \c NO if aPartialString
|
||||
is unacceptable. Assign a new string by reference to aPartialString and a new range by reference to aProposedSelectedRange and return \c NO if you want to replace the string and
|
||||
change the selection range. If you return \c NO, you can also return by reference a CPString object (in anError) that explains the reason why the
|
||||
validation failed; the delegate (if any) of the CPControl can then respond to the failure in
|
||||
control:didFailToValidatePartialString:errorDescription:.
|
||||
|
||||
@param aPartialString The new string to validate.
|
||||
@param aProposedSelectedRange The selection range that will be used if the string is accepted or replaced.
|
||||
@param originalString The original string, before the proposed change.
|
||||
@param originalSelectedRange The selection range over which the change is to take place.
|
||||
@param error If non-nil, if validation fails contains an CPString object that describes the problem.
|
||||
@return YES if aPartialString is acceptable, otherwise NO.
|
||||
@param anError If non-nil, if validation fails contains an CPString object that describes the problem.
|
||||
@return \c YES if aPartialString is acceptable, otherwise \c NO.
|
||||
|
||||
*/
|
||||
- (BOOL)isPartialStringValue:(CPString)aPartialString proposedSelectedRange:(CPRange)aProposedSelectedRange originalString:(CPString)originalString originalSelectedRange:(CPRange)originalSelectedRange errorDescription:(CPString)anError
|
||||
- (BOOL)isPartialStringValid:(CPStringRef)aPartialString proposedSelectedRange:(CPRangeRef)aProposedSelectedRange originalString:(CPString)originalString originalSelectedRange:(CPRange)originalSelectedRange errorDescription:(CPStringRef)anError
|
||||
{
|
||||
_CPRaiseInvalidAbstractInvocation(self, _cmd);
|
||||
return NO;
|
||||
var newString = nil,
|
||||
valid = [self isPartialStringValid:aPartialString newEditingString:AT_REF(newString) errorDescription:anError];
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
AT_DEREF(aPartialString, newString);
|
||||
|
||||
// If a new string is passed back, the selection is always put at the end
|
||||
if (newString !== nil)
|
||||
AT_DEREF(aProposedSelectedRange, CPMakeRange(newString.length, 0));
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
@@ -158,7 +175,6 @@
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -231,6 +231,7 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
Object _observersForKey;
|
||||
int _observersForKeyLength;
|
||||
CPSet _replacedKeys;
|
||||
CPSet _suppressedNotifications;
|
||||
}
|
||||
|
||||
+ (id)proxyForObject:(CPObject)anObject
|
||||
@@ -247,11 +248,12 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_targetObject = aTarget;
|
||||
_nativeClass = [aTarget class];
|
||||
_observersForKey = {};
|
||||
_changesForKey = {};
|
||||
_observersForKeyLength = 0;
|
||||
_targetObject = aTarget;
|
||||
_nativeClass = [aTarget class];
|
||||
_observersForKey = {};
|
||||
_changesForKey = {};
|
||||
_observersForKeyLength = 0;
|
||||
_suppressedNotifications = [CPSet set];
|
||||
|
||||
[self _replaceClass];
|
||||
aTarget[KVOProxyKey] = self;
|
||||
@@ -629,12 +631,12 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
{
|
||||
// Fire change events for the dependent keys
|
||||
var dependentKeysForClass = _nativeClass[DependentKeysKey],
|
||||
dependantKeys = [dependentKeysForClass[theKeyPath] allObjects];
|
||||
dependentKeys = [dependentKeysForClass[theKeyPath] allObjects],
|
||||
isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey];
|
||||
|
||||
var isBeforeFlag = !![theChanges objectForKey:CPKeyValueChangeNotificationIsPriorKey];
|
||||
for (var i = 0; i < [dependantKeys count]; i++)
|
||||
for (var i = 0; i < [dependentKeys count]; i++)
|
||||
{
|
||||
var dependantKey = [dependantKeys objectAtIndex:i];
|
||||
var dependantKey = [dependentKeys objectAtIndex:i];
|
||||
[self _sendNotificationsForKey:dependantKey changeOptions:theChanges isBefore:isBeforeFlag];
|
||||
}
|
||||
}
|
||||
@@ -842,12 +844,23 @@ var kvoNewAndOld = CPKeyValueObservingOptionNew | CPKeyValueObservingOpti
|
||||
{
|
||||
var keyPath = dependentKeyPaths[index];
|
||||
|
||||
[self _sendNotificationsForKey:keyPath
|
||||
changeOptions:isBefore ? [changeOptions copy] : _changesForKey[keyPath]
|
||||
isBefore:isBefore];
|
||||
if (![_suppressedNotifications containsObject:keyPath])
|
||||
[self _sendNotificationsForKey:keyPath
|
||||
changeOptions:isBefore ? [changeOptions copy] : _changesForKey[keyPath]
|
||||
isBefore:isBefore];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)suppressNotificationsForKeyPath:(CPString)keyPath
|
||||
{
|
||||
[_suppressedNotifications addObject:keyPath];
|
||||
}
|
||||
|
||||
- (void)unsuppressNotificationsForKeyPath:(CPString)keyPath
|
||||
{
|
||||
[_suppressedNotifications removeObject:keyPath];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPKVOModelSubclass
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* CPNumberFormatter.j
|
||||
* Foundation
|
||||
*
|
||||
* Created by Alexander Ljungberg.
|
||||
* Copyright 2011, WireLoad Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#import "Ref.h"
|
||||
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPFormatter.j>
|
||||
@import <Foundation/CPDecimalNumber.j>
|
||||
|
||||
#define UPDATE_NUMBER_HANDLER_IF_NECESSARY() if (!_numberHandler) \
|
||||
_numberHandler = [CPDecimalNumberHandler decimalNumberHandlerWithRoundingMode:_roundingMode scale:_maximumFractionalDigits raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:YES];
|
||||
#define SET_NEEDS_NUMBER_HANDLER_UPDATE() _numberHandler = nil;
|
||||
|
||||
CPNumberFormatterNoStyle = 0;
|
||||
CPNumberFormatterDecimalStyle = 1;
|
||||
CPNumberFormatterCurrencyStyle = 2;
|
||||
CPNumberFormatterPercentStyle = 3;
|
||||
CPNumberFormatterScientificStyle = 4;
|
||||
CPNumberFormatterSpellOutStyle = 5;
|
||||
|
||||
CPNumberFormatterRoundCeiling = CPRoundUp;
|
||||
CPNumberFormatterRoundFloor = CPRoundDown;
|
||||
CPNumberFormatterRoundDown = CPRoundDown;
|
||||
CPNumberFormatterRoundUp = CPRoundUp;
|
||||
CPNumberFormatterRoundHalfEven = CPRoundBankers;
|
||||
CPNumberFormatterRoundHalfDown = _CPRoundHalfDown;
|
||||
CPNumberFormatterRoundHalfUp = CPRoundPlain;
|
||||
|
||||
/*!
|
||||
@ingroup foundation
|
||||
@class CPNumberFormatter
|
||||
|
||||
CPNumberFormatter takes a numeric NSNumber value and formats it as text for
|
||||
display. It also supports the converse, taking text and interpreting it as a
|
||||
CPNumber by configurable formatting rules.
|
||||
*/
|
||||
@implementation CPNumberFormatter : CPFormatter
|
||||
{
|
||||
CPNumberFormatterStyle _numberStyle @accessors(property=numberStyle);
|
||||
CPString _perMillSymbol @accessors(property=perMillSymbol);
|
||||
CPNumberFormatterRoundingMode _roundingMode @accessors(property=roundingMode);
|
||||
CPUInteger _maximumFractionalDigits @accessors(property=maximalFractionalDigits);
|
||||
|
||||
CPDecimalNumberHandler _numberHandler;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_roundingMode = CPNumberFormatterRoundHalfUp;
|
||||
_maximumFractionalDigits = 3;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)stringFromNumber:(CPNumber)number
|
||||
{
|
||||
// TODO Add locale support.
|
||||
switch(_numberStyle)
|
||||
{
|
||||
case CPNumberFormatterDecimalStyle:
|
||||
UPDATE_NUMBER_HANDLER_IF_NECESSARY();
|
||||
|
||||
var dcmn = [CPDecimalNumber numberWithFloat:number];
|
||||
dcmn = [dcmn decimalNumberByRoundingAccordingToBehavior:_numberHandler];
|
||||
|
||||
var output = [dcmn descriptionWithLocale:nil],
|
||||
parts = [output componentsSeparatedByString:"."], // FIXME Locale specific.
|
||||
preFraction = parts[0],
|
||||
fraction = parts.length > 1 ? parts[1] : "",
|
||||
preFractionLength = [preFraction length],
|
||||
commaPosition = 3,
|
||||
perMillSymbol = [self _effectivePerMillSymbol];
|
||||
|
||||
// TODO This is just a temporary solution. Should be generalised.
|
||||
// Add in thousands separators.
|
||||
if (perMillSymbol)
|
||||
while(commaPosition < [preFraction length])
|
||||
{
|
||||
preFraction = [preFraction stringByReplacingCharactersInRange:CPMakeRange(commaPosition, 0) withString:perMillSymbol];
|
||||
commaPosition += 4;
|
||||
}
|
||||
|
||||
if (fraction)
|
||||
return preFraction + "." + fraction;
|
||||
else
|
||||
return preFraction;
|
||||
default:
|
||||
return [number description];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPNumber)numberFromString:(CPString)string
|
||||
{
|
||||
// TODO
|
||||
return parseFloat(string);
|
||||
}
|
||||
|
||||
- (CPString)stringForObjectValue:(id)anObject
|
||||
{
|
||||
if ([anObject isKindOfClass:[CPNumber class]])
|
||||
return [self stringFromNumber:anObject];
|
||||
else
|
||||
return [anObject description];
|
||||
}
|
||||
|
||||
- (CPString)editingStringForObjectValue:(id)anObject
|
||||
{
|
||||
return [self stringForObjectValue:anObject];
|
||||
}
|
||||
|
||||
- (BOOL)getObjectValue:(id)anObject forString:(CPString)aString errorDescription:(CPString)anError
|
||||
{
|
||||
// TODO Error handling.
|
||||
var value = [self numberFromString:aString];
|
||||
AT_DEREF(anObject, value);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Return the perMillSymbol if set, otherwise the locale default.
|
||||
*/
|
||||
- (CPString)_effectivePerMillSymbol
|
||||
{
|
||||
if (_perMillSymbol === nil || _perMillSymbol === undefined)
|
||||
return ","; // (FIXME US Locale specific.)
|
||||
return _perMillSymbol;
|
||||
}
|
||||
|
||||
- (void)setRoundingMode:(CPNumberFormatterRoundingMode)aRoundingMode
|
||||
{
|
||||
_roundingMode = aRoundingMode;
|
||||
SET_NEEDS_NUMBER_HANDLER_UPDATE();
|
||||
}
|
||||
|
||||
- (void)setMaximumFractionDigits:(CPUInteger)aNumber
|
||||
{
|
||||
_maximumFractionalDigits = aNumber;
|
||||
SET_NEEDS_NUMBER_HANDLER_UPDATE();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPNumberFormatterStyleKey = "CPNumberFormatterStyleKey";
|
||||
|
||||
@implementation CPNumberFormatter (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_numberStyle = [aCoder decodeIntForKey:CPNumberFormatterStyleKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeInt:_numberStyle forKey:CPNumberFormatterStyleKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -184,9 +184,8 @@ var StandardUserDefaults;
|
||||
}
|
||||
|
||||
[domain setObject:anObject forKey:aKey];
|
||||
[self domainDidChange:aDomain];
|
||||
|
||||
_searchListNeedsReload = YES;
|
||||
[self domainDidChange:aDomain];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -211,9 +210,8 @@ var StandardUserDefaults;
|
||||
return;
|
||||
|
||||
[domain removeObjectForKey:aKey];
|
||||
[self domainDidChange:aDomain];
|
||||
|
||||
_searchListNeedsReload = YES;
|
||||
[self domainDidChange:aDomain];
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
@import "CPNotificationCenter.j"
|
||||
@import "CPNull.j"
|
||||
@import "CPNumber.j"
|
||||
@import "CPNumberFormatter.j"
|
||||
@import "CPObject.j"
|
||||
@import "CPObjJRuntime.j"
|
||||
@import "CPOperation.j"
|
||||
|
||||
@@ -836,8 +836,7 @@ BundleTask.prototype.defineSourceTasks = function()
|
||||
|
||||
environmentSources.forEach(function(/*String*/ aFilename)
|
||||
{
|
||||
// if this file doesn't exist or isn't a .j file, don't preprocess it.
|
||||
if (!FILE.exists(aFilename) || FILE.extension(aFilename) !== '.j')
|
||||
if (!FILE.exists(aFilename))
|
||||
return;
|
||||
|
||||
var relativePath = aFilename.substring(basePathLength ? basePathLength + 1 : basePathLength),
|
||||
@@ -845,8 +844,19 @@ BundleTask.prototype.defineSourceTasks = function()
|
||||
|
||||
filedir (compiledEnvironmentSource, [aFilename], function()
|
||||
{
|
||||
TERM.stream.write("Compiling [\0blue(" + anEnvironment + "\0)] \0purple(" + aFilename + "\0)").flush();
|
||||
var compiled = require("objective-j/compiler").compile(aFilename, environmentCompilerFlags);
|
||||
var compile
|
||||
// if this file doesn't exist or isn't a .j file, don't preprocess it.
|
||||
if (FILE.extension(aFilename) !== ".j")
|
||||
{
|
||||
TERM.stream.write("Including [\0blue(" + anEnvironment + "\0)] \0purple(" + aFilename + "\0)").flush();
|
||||
var compiled = FILE.read(aFilename, { charset:"UTF-8" });
|
||||
}
|
||||
else
|
||||
{
|
||||
TERM.stream.write("Compiling [\0blue(" + anEnvironment + "\0)] \0purple(" + aFilename + "\0)").flush();
|
||||
var compiled = require("objective-j/compiler").compile(aFilename, environmentCompilerFlags);
|
||||
}
|
||||
|
||||
TERM.stream.print(Array(Math.round(compiled.length / 1024) + 3).join("."));
|
||||
FILE.write(compiledEnvironmentSource, compiled, { charset:"UTF-8" });
|
||||
});
|
||||
|
||||