mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-09 20:27:13 +00:00
Compare commits
+132
-22
@@ -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)
|
||||
@@ -737,6 +739,10 @@
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
[_contentObject addObject:object];
|
||||
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
_disableSetContent = NO;
|
||||
|
||||
if (willClearPredicate)
|
||||
@@ -771,7 +777,8 @@
|
||||
}
|
||||
|
||||
/*!
|
||||
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,8 +788,12 @@
|
||||
if (![self canAdd])
|
||||
return;
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion)
|
||||
var willClearPredicate = NO;
|
||||
if (_clearsFilterPredicateOnInsertion && _filterPredicate)
|
||||
{
|
||||
[self willChangeValueForKey:@"filterPredicate"];
|
||||
willClearPredicate = YES;
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"content"];
|
||||
|
||||
@@ -790,10 +801,17 @@
|
||||
See _disableSetContent explanation in addObject:.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
[_contentObject insertObject:anObject atIndex:anIndex];
|
||||
|
||||
// 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];
|
||||
|
||||
_disableSetContent = NO;
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion)
|
||||
if (willClearPredicate)
|
||||
[self __setFilterPredicate:nil];
|
||||
|
||||
[[self arrangedObjects] insertObject:anObject atIndex:anIndex];
|
||||
@@ -809,7 +827,7 @@
|
||||
[self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]];
|
||||
|
||||
[self didChangeValueForKey:@"content"];
|
||||
if (_clearsFilterPredicateOnInsertion)
|
||||
if (willClearPredicate)
|
||||
[self didChangeValueForKey:@"filterPredicate"];
|
||||
}
|
||||
|
||||
@@ -820,26 +838,28 @@
|
||||
*/
|
||||
- (void)removeObject:(id)object
|
||||
{
|
||||
[self willChangeValueForKey:@"content"];
|
||||
[self willChangeValueForKey:@"content"];
|
||||
|
||||
/*
|
||||
See _disableSetContent explanation in addObject:.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
[_contentObject removeObject:object];
|
||||
_disableSetContent = NO;
|
||||
// See _disableSetContent explanation in addObject:.
|
||||
_disableSetContent = YES;
|
||||
|
||||
if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
|
||||
{
|
||||
[_contentObject removeObject:object];
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
_disableSetContent = NO;
|
||||
|
||||
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 +895,61 @@
|
||||
*/
|
||||
- (void)remove:(id)sender
|
||||
{
|
||||
[self removeObjects:[[self arrangedObjects] objectsAtIndexes:[self selectionIndexes]]];
|
||||
[self removeObjectsAtArrangedObjectIndexes:_selectionIndexes];
|
||||
}
|
||||
|
||||
/*!
|
||||
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"];
|
||||
|
||||
/*
|
||||
See _disableSetContent explanation in addObject:.
|
||||
*/
|
||||
_disableSetContent = YES;
|
||||
|
||||
var arrangedObjects = [self arrangedObjects],
|
||||
index = [anIndexSet lastIndex],
|
||||
position = CPNotFound,
|
||||
newSelectionIndexes = [_selectionIndexes copy];
|
||||
|
||||
while (index !== CPNotFound)
|
||||
{
|
||||
var object = [arrangedObjects objectAtIndex:index];
|
||||
|
||||
// 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];
|
||||
}
|
||||
[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 +969,8 @@
|
||||
[contentArray addObject:[objects objectAtIndex:i]];
|
||||
|
||||
[self setContent:contentArray];
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -922,11 +989,13 @@
|
||||
{
|
||||
[self willChangeValueForKey:@"content"];
|
||||
|
||||
/*
|
||||
See _disableSetContent explanation in addObject:.
|
||||
*/
|
||||
// See _disableSetContent explanation in addObject:.
|
||||
_disableSetContent = YES;
|
||||
|
||||
[_contentObject removeObjectsInArray:objects];
|
||||
// Allow handlesContentAsCompoundValue reverse sets to trigger.
|
||||
[[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
|
||||
|
||||
_disableSetContent = NO;
|
||||
|
||||
var arrangedObjects = [self arrangedObjects],
|
||||
@@ -985,12 +1054,53 @@
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
isCompound = [self handlesContentAsCompoundValue];
|
||||
|
||||
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",
|
||||
|
||||
+41
-8
@@ -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];
|
||||
}
|
||||
|
||||
@@ -226,6 +233,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 +280,6 @@ var CPBindingOperationAnd = 0,
|
||||
[CPBinder exposeBinding:aBinding forClass:[self class]];
|
||||
}
|
||||
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
return [CPBinder class];
|
||||
|
||||
@@ -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])
|
||||
|
||||
+27
-9
@@ -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;
|
||||
|
||||
@@ -87,6 +88,8 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
return [self trackMenuBarButtonEvent:anEvent];
|
||||
}
|
||||
|
||||
_hasMouseGoneUpAfterStartedTracking = NO;
|
||||
|
||||
[self trackEvent:anEvent];
|
||||
}
|
||||
|
||||
@@ -221,8 +224,13 @@ 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)
|
||||
{
|
||||
if (_hasMouseGoneUpAfterStartedTracking)
|
||||
[trackingMenu cancelTracking];
|
||||
else
|
||||
_hasMouseGoneUpAfterStartedTracking = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent previous selected menu items from opening by stopping the timer if a
|
||||
@@ -306,11 +314,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 +383,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 +404,8 @@ var STICKY_TIME_INTERVAL = 500,
|
||||
|
||||
[_CPMenuWindow poolMenuWindow:menuContainer];
|
||||
[_menuContainerStack removeObjectAtIndex:index];
|
||||
|
||||
[menu _menuDidClose];
|
||||
}
|
||||
|
||||
if (!newMenu)
|
||||
|
||||
@@ -496,6 +496,7 @@ CPOffState
|
||||
if (_submenu)
|
||||
{
|
||||
[_submenu setSupermenu:_menu];
|
||||
[_submenu setTitle:[self title]]
|
||||
|
||||
[self setTarget:_menu];
|
||||
[self setAction:@selector(submenuAction:)];
|
||||
|
||||
@@ -498,7 +498,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return "<_CPObservableArray: "+[super description]+" >";
|
||||
return "<_CPObservableArray: " + [super description] + " >";
|
||||
}
|
||||
|
||||
- (id)initWithArray:(CPArray)anArray
|
||||
@@ -710,6 +710,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...
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -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;
|
||||
|
||||
+169
-68
@@ -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;
|
||||
@@ -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;
|
||||
@@ -548,8 +549,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
CPTextFieldInputOwner = self;
|
||||
}, 0.0);
|
||||
|
||||
element.value = [self stringValue];
|
||||
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
|
||||
CPTextFieldInputIsActive = YES;
|
||||
@@ -572,17 +571,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 +625,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
#endif
|
||||
|
||||
//post CPControlTextDidEndEditingNotification
|
||||
// post CPControlTextDidEndEditingNotification
|
||||
if (_isEditing)
|
||||
{
|
||||
_isEditing = NO;
|
||||
@@ -625,6 +640,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 +722,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 +739,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
@@ -732,45 +774,46 @@ 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 (![self action] || [self sendAction:[self action] to:[self target]])
|
||||
{
|
||||
if (_isEditing)
|
||||
{
|
||||
_isEditing = NO;
|
||||
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
[self selectText:nil];
|
||||
[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 +823,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)textDidBlur:(CPNotification)note
|
||||
@@ -810,15 +855,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 +865,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 +950,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,7 +1016,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
minSize = [self currentValueForThemeAttribute:@"min-size"],
|
||||
maxSize = [self currentValueForThemeAttribute:@"max-size"],
|
||||
lineBreakMode = [self lineBreakMode],
|
||||
text = ([self stringValue] || @" "),
|
||||
text = (_stringValue || @" "),
|
||||
textSize = _CGSizeMakeCopy(frameSize),
|
||||
font = [self currentValueForThemeAttribute:@"font"];
|
||||
|
||||
@@ -991,8 +1087,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 +1117,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 +1132,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 +1160,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
// fall through to the return
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return CPMakeRange(0, 0);
|
||||
}
|
||||
|
||||
@@ -1072,6 +1170,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
if (![[self window] firstResponder] === self)
|
||||
return;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
var inputElement = [self _inputElement];
|
||||
|
||||
try
|
||||
@@ -1100,6 +1200,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
catch (e)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)selectAll:(id)sender
|
||||
@@ -1117,8 +1219,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)];
|
||||
@@ -1271,7 +1372,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
string = [self placeholderString];
|
||||
else
|
||||
{
|
||||
string = [self stringValue];
|
||||
string = _stringValue;
|
||||
|
||||
if ([self isSecure])
|
||||
string = secureStringForString(string);
|
||||
|
||||
+6
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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(MIN(_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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.003"
|
||||
VERSION_NUMBER = "1.0.0.006"
|
||||
|
||||
|
||||
# Shorten expressions
|
||||
|
||||
@@ -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)
|
||||
|
||||
+41
-25
@@ -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
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
|
||||
@@ -33,6 +33,15 @@ var CLS_CLASS = 0x1,
|
||||
#define GETMETA(aClass) (ISMETA(aClass) ? aClass : aClass.isa)
|
||||
#define ISINITIALIZED(aClass) GETINFO(GETMETA(aClass), CLS_INITIALIZED)
|
||||
|
||||
|
||||
// MAXIMUM_RECURSION_CHECKS
|
||||
// If defined, objj_msgSend will check for recursion deeper than MAXIMUM_RECURSION_DEPTH and
|
||||
// throw an error if found. While crude, this can be helpful when your JavaScript debugger
|
||||
// crashes on recursion errors (e.g. Safari) or ignores them (e.g. Chrome).
|
||||
|
||||
// #define MAXIMUM_RECURSION_CHECKS
|
||||
#define MAXIMUM_RECURSION_DEPTH 80
|
||||
|
||||
GLOBAL(objj_ivar) = function(/*String*/ aName, /*String*/ aType)
|
||||
{
|
||||
this.name = aName;
|
||||
@@ -604,6 +613,10 @@ DISPLAY_NAME(ivar_getTypeEncoding);
|
||||
|
||||
// Sending Messages
|
||||
|
||||
#ifdef MAXIMUM_RECURSION_CHECKS
|
||||
var __objj_msgSend__StackDepth = 0;
|
||||
#endif
|
||||
|
||||
GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
|
||||
{
|
||||
if (aReceiver == nil)
|
||||
@@ -613,6 +626,13 @@ GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
|
||||
|
||||
CLASS_GET_METHOD_IMPLEMENTATION(var implementation, isa, aSelector);
|
||||
|
||||
#ifdef MAXIMUM_RECURSION_CHECKS
|
||||
if (__objj_msgSend__StackDepth++ > MAXIMUM_RECURSION_DEPTH)
|
||||
throw new Error("Maximum call stack depth exceeded.");
|
||||
|
||||
try {
|
||||
#endif
|
||||
|
||||
switch(arguments.length)
|
||||
{
|
||||
case 2: return implementation(aReceiver, aSelector);
|
||||
@@ -621,6 +641,12 @@ GLOBAL(objj_msgSend) = function(/*id*/ aReceiver, /*SEL*/ aSelector)
|
||||
}
|
||||
|
||||
return implementation.apply(aReceiver, arguments);
|
||||
|
||||
#ifdef MAXIMUM_RECURSION_CHECKS
|
||||
} finally {
|
||||
__objj_msgSend__StackDepth--;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
DISPLAY_NAME(objj_msgSend);
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
[arrayController insertObject:object atArrangedObjectIndex:1];
|
||||
|
||||
[self assert:object equals:[[arrayController arrangedObjects] objectAtIndex:1]];
|
||||
[self assertTrue:[[arrayController content] containsObject:object] message:@"object should be inserted into content"];
|
||||
}
|
||||
|
||||
- (void)testAddObjectUpdatesArrangedObjectsWithoutSortDescriptors
|
||||
@@ -438,6 +439,31 @@
|
||||
[self assert:1 equals:[observations count] message:@"exactly 1 notification for addObject (clearsFilterPredicate YES)"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Test that if there is no filter predicate to clear, insertObject:atArrangedObjectIndex: with
|
||||
clearsFilterPredicate YES does not send a false filterPredicate notification.
|
||||
*/
|
||||
- (void)testObservationDuringInsertObject_atArrangedIndex_
|
||||
{
|
||||
var arrayController = [self arrayController];
|
||||
|
||||
[arrayController addObserver:self forKeyPath:@"filterPredicate" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:nil];
|
||||
|
||||
// Add something to clear.
|
||||
[arrayController setFilterPredicate:[CPPredicate predicateWithFormat:@"(name != %@)", "Francisco"]];
|
||||
observations = [];
|
||||
var aPerson = [Employee employeeWithName:@"Alexander" department:[Department departmentWithName:@"Cosmic Path Finding"]];
|
||||
|
||||
[arrayController setClearsFilterPredicateOnInsertion:YES];
|
||||
[self assert:0 equals:[observations count] message:@"no observations before insertObject test"];
|
||||
[arrayController insertObject:aPerson atArrangedObjectIndex:1];
|
||||
[self assert:1 equals:[observations count] message:@"exactly 1 notification for insertObject (clearsFilterPredicate YES)"];
|
||||
|
||||
// Now that the filter is already cleared, we should not get notified that it clears again on the second insert.
|
||||
[arrayController insertObject:aPerson atArrangedObjectIndex:1];
|
||||
[self assert:1 equals:[observations count] message:@"exactly 1 notification for insertObject x 2 (clearsFilterPredicate YES)"];
|
||||
}
|
||||
|
||||
- (void)testCompoundKeyPaths
|
||||
{
|
||||
var departmentNameField = [[CPTextField alloc] init];
|
||||
@@ -482,6 +508,22 @@
|
||||
[self assertTrue:[[arrayController arrangedObjects] count] > 0];
|
||||
}
|
||||
|
||||
/**
|
||||
In a table with arranged contents like [1, 1, 2, 1], selecting the second '1' and removing it
|
||||
should result in [1, 2, 1] - not [2]. E.g. we don't use removeObject:1 but only remove the
|
||||
actually selected instance.
|
||||
*/
|
||||
- (void)testRemove_OneOfMultipleEqualObjects
|
||||
{
|
||||
var ac = [CPArrayController new],
|
||||
contentArray = [1, 1, 2, 1];
|
||||
[ac setContent:contentArray];
|
||||
[self assert:[1, 1, 2, 1] equals:[ac arrangedObjects]];
|
||||
[ac setSelectionIndexes:[CPIndexSet indexSetWithIndex:1]];
|
||||
[ac remove:nil];
|
||||
[self assert:[1, 2, 1] equals:[ac arrangedObjects] message:"only one copy of 1 removed + the right copy should be removed"];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:keyPath
|
||||
ofObject:anActivity
|
||||
change:change
|
||||
|
||||
@@ -249,6 +249,29 @@
|
||||
CPLog(@"here: "+aKeyPath+" value: "+[anObject valueForKey:aKeyPath]);
|
||||
}
|
||||
|
||||
- (void)testSuppressNotification
|
||||
{
|
||||
var control = [[CPTextField alloc] init],
|
||||
anotherControl = [[CPTextField alloc] init];
|
||||
[control setStringValue:@"brown"];
|
||||
[control bind:CPValueBinding toObject:self withKeyPath:@"FOO" options:nil];
|
||||
[self setValue:@"green" forKeyPath:@"FOO"];
|
||||
[self assert:@"green" equals:[control stringValue] message:@"normal binding action"];
|
||||
|
||||
var binding = [CPBinder getBinding:CPValueBinding forObject:control];
|
||||
[binding suppressSpecificNotificationFromObject:self keyPath:@"FOO"];
|
||||
[self setValue:@"orange" forKeyPath:@"FOO"];
|
||||
[self assert:@"green" equals:[control stringValue] message:@"binding update suppressed"];
|
||||
|
||||
[binding unsuppressSpecificNotificationFromObject:anotherControl keyPath:@"FOO"];
|
||||
[self setValue:@"blue" forKeyPath:@"FOO"];
|
||||
[self assert:@"green" equals:[control stringValue] message:@"binding update still suppressed"];
|
||||
|
||||
[binding unsuppressSpecificNotificationFromObject:self keyPath:@"FOO"];
|
||||
[self setValue:@"octarine" forKeyPath:@"FOO"];
|
||||
[self assert:@"octarine" equals:[control stringValue] message:@"binding update no longer suppressed"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation BindingTester : CPObject
|
||||
|
||||
@@ -22,6 +22,31 @@
|
||||
[self assert:[view valueForThemeAttribute:@"content-inset"].top equals:[decoded valueForThemeAttribute:@"content-inset"].top message:@"content-inset should unarchive correctly"];
|
||||
}
|
||||
|
||||
- (void)testFormatters
|
||||
{
|
||||
var control = [[CPTextField alloc] initWithFrame:CGRectMakeZero()],
|
||||
numberFormatter = [[CPNumberFormatter alloc] init];
|
||||
|
||||
[numberFormatter setNumberStyle:CPNumberFormatterDecimalStyle];
|
||||
[numberFormatter setMaximumFractionDigits:3];
|
||||
|
||||
[control setFormatter:numberFormatter];
|
||||
[control setStringValue:@"12.3456"];
|
||||
[self assert:[CPNumber class] equals:[[control objectValue] class] message:@"object should be a number"];
|
||||
// Note that the control stores the value with a different precision than the maximum fraction
|
||||
// digits of the number formatter. It's a little surprising but this makes the implementation easier
|
||||
// and Cocoa does it too.
|
||||
[self assert:[CPNumber numberWithFloat:12.3456] equals:[control objectValue]];
|
||||
[self assert:"12.346" equals:[control stringValue]];
|
||||
|
||||
[control setFloatValue:45.3456];
|
||||
[self assertTrue:"45.346" === [control stringValue]];
|
||||
|
||||
[control setFormatter:nil];
|
||||
[control setStringValue:@"12"];
|
||||
[self assert:@"12" equals:[control objectValue]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTextFieldSubclass : CPTextField
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
@implementation CPNumberFormatterTest : OJTestCase
|
||||
{
|
||||
}
|
||||
|
||||
- (void)testDecimalStyle
|
||||
{
|
||||
var numberFormatter = [[CPNumberFormatter alloc] init];
|
||||
[numberFormatter setNumberStyle:CPNumberFormatterDecimalStyle];
|
||||
var formattedNumberString = [numberFormatter stringFromNumber:[CPNumber numberWithInt:123]];
|
||||
[self assert:@"123" equals:formattedNumberString];
|
||||
|
||||
formattedNumberString = [numberFormatter stringFromNumber:[CPNumber numberWithFloat:122344.4563]];
|
||||
// TODO Locale support. This test is sensitive to float precision.
|
||||
[self assert:@"122,344.456" equals:formattedNumberString];
|
||||
}
|
||||
|
||||
- (void)testRoundingMode
|
||||
{
|
||||
var numberFormatter = [[CPNumberFormatter alloc] init],
|
||||
formattedNumberString;
|
||||
[numberFormatter setNumberStyle:CPNumberFormatterDecimalStyle];
|
||||
|
||||
[numberFormatter setRoundingMode:CPNumberFormatterRoundUp];
|
||||
formattedNumberString = [numberFormatter stringFromNumber:[CPNumber numberWithFloat:123.5672]];
|
||||
[self assert:@"123.568" equals:formattedNumberString];
|
||||
|
||||
[numberFormatter setRoundingMode:CPNumberFormatterRoundDown];
|
||||
formattedNumberString = [numberFormatter stringFromNumber:[CPNumber numberWithFloat:123.5679]];
|
||||
[self assert:@"123.567" equals:formattedNumberString];
|
||||
|
||||
[numberFormatter setRoundingMode:CPNumberFormatterRoundHalfEven];
|
||||
formattedNumberString = [numberFormatter stringFromNumber:[CPNumber numberWithFloat:123.5675]];
|
||||
[self assert:@"123.568" equals:formattedNumberString];
|
||||
formattedNumberString = [numberFormatter stringFromNumber:[CPNumber numberWithFloat:123.5665]];
|
||||
[self assert:@"123.566" equals:formattedNumberString];
|
||||
|
||||
[numberFormatter setRoundingMode:CPNumberFormatterRoundHalfDown];
|
||||
formattedNumberString = [numberFormatter stringFromNumber:[CPNumber numberWithFloat:123.5675]];
|
||||
[self assert:@"123.567" equals:formattedNumberString];
|
||||
|
||||
[numberFormatter setRoundingMode:CPNumberFormatterRoundHalfUp];
|
||||
formattedNumberString = [numberFormatter stringFromNumber:[CPNumber numberWithFloat:123.5675]];
|
||||
[self assert:@"123.568" equals:formattedNumberString];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -18,6 +18,7 @@ CPUserDefaultsTestKey2 = @"KEY2";
|
||||
@implementation CPUserDefaultsTest : OJTestCase
|
||||
{
|
||||
CPUserDefaults target;
|
||||
id lastObservedCPUserDefaultsTestKey1;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
@@ -40,7 +41,6 @@ CPUserDefaultsTestKey2 = @"KEY2";
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (void)testSetObjectForKey
|
||||
{
|
||||
[target setObject:[CPArray arrayWithObjects:@"cell1", @"cell2"] forKey:CPUserDefaultsTestKey1];
|
||||
@@ -133,7 +133,23 @@ CPUserDefaultsTestKey2 = @"KEY2";
|
||||
|
||||
[target removeObjectForKey:CPUserDefaultsTestKey1];
|
||||
[self assert:[target dataForKey:CPUserDefaultsTestKey1] equals:nil];
|
||||
}
|
||||
|
||||
- (void)testNotification
|
||||
{
|
||||
[target setDouble:5.0 forKey:CPUserDefaultsTestKey1];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:CPUserDefaultsDidChangeNotification object:target];
|
||||
|
||||
// Prod the class to resolve any outstanding _searchListNeedsReload's.
|
||||
[self assert:5.0 equals:[target objectForKey:CPUserDefaultsTestKey1] message:"normal read"];
|
||||
[target setDouble:10.0 forKey:CPUserDefaultsTestKey1];
|
||||
[self assert:[target objectForKey:CPUserDefaultsTestKey1] equals:lastObservedCPUserDefaultsTestKey1 message:"should observe new value"];
|
||||
}
|
||||
|
||||
- (void)userDefaultsDidChange:(CPNotification)aNotification
|
||||
{
|
||||
lastObservedCPUserDefaultsTestKey1 = [target objectForKey:CPUserDefaultsTestKey1];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* AppController.j
|
||||
* CPFormatterTest
|
||||
*
|
||||
* Created by aparajita on June 30, 2011.
|
||||
* Copyright 2011, Victory-Heart Productions All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import "DateFormatter.j"
|
||||
|
||||
var RecordData = [
|
||||
{name:"Tom", age:34},
|
||||
{name:"Dick", age:27},
|
||||
{name:"Harry", age:50}
|
||||
];
|
||||
|
||||
var randomFromTo = function(from, to)
|
||||
{
|
||||
return Math.floor(Math.random() * (to - from + 1) + from);
|
||||
};
|
||||
|
||||
@implementation AppController : CPObject
|
||||
{
|
||||
CPWindow theWindow;
|
||||
CPTextField dateField1;
|
||||
CPTextField dateField2;
|
||||
CPTextField error1;
|
||||
CPTextField textField;
|
||||
CPTextField error2;
|
||||
CPTextField recordField;
|
||||
CPPopUpButton recordMenu;
|
||||
}
|
||||
|
||||
- (void)awakeFromCib
|
||||
{
|
||||
[theWindow setInitialFirstResponder:dateField1];
|
||||
[dateField1 setFormatter:[DateFormatter formatterWithDisplayFormat:@"D M jS, Y" editingFormat:@"m/j/Y" emptyIsValid:NO]];
|
||||
[dateField2 setFormatter:[DateFormatter formatterWithDisplayFormat:@"m-d-Y" editingFormat:@"m-d-Y" emptyIsValid:YES]];
|
||||
[dateField2 setDelegate:self];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(resetErrorMessage:)
|
||||
name:CPTextFieldDidFocusNotification
|
||||
object:nil];
|
||||
|
||||
[textField setFormatter:[TextFormatter new]];
|
||||
[textField setDelegate:self];
|
||||
|
||||
[error1 setStringValue:@""];
|
||||
[error2 setStringValue:@""];
|
||||
|
||||
[recordField setFormatter:[ContactFormatter new]];
|
||||
[recordField setObjectValue:RecordData[0]];
|
||||
}
|
||||
|
||||
- (void)selectRecord:(id)sender
|
||||
{
|
||||
var record = RecordData[[sender selectedIndex]];
|
||||
|
||||
[recordField setObjectValue:record];
|
||||
}
|
||||
|
||||
- (void)setDate1:(id)sender
|
||||
{
|
||||
[self setDate:dateField1];
|
||||
}
|
||||
|
||||
- (void)setDate2:(id)sender
|
||||
{
|
||||
[self setDate:dateField2];
|
||||
}
|
||||
|
||||
- (void)setDate:(CPTextField)field
|
||||
{
|
||||
var date = new Date(randomFromTo(1931, 2012), randomFromTo(0, 11), randomFromTo(1, 31));
|
||||
|
||||
[field setObjectValue:date];
|
||||
}
|
||||
|
||||
- (void)objectValue1:(id)sender
|
||||
{
|
||||
CPLog.info("Date 1: %s", [[dateField1 objectValue] description]);
|
||||
}
|
||||
|
||||
- (void)objectValue2:(id)sender
|
||||
{
|
||||
CPLog.info("Date 2: %s", [[dateField2 objectValue] description]);
|
||||
}
|
||||
|
||||
- (void)setNil:(id)sender
|
||||
{
|
||||
[dateField1 setStringValue:nil]; // should log a warning and do nothing
|
||||
}
|
||||
|
||||
- (void)setEmptyOK:(id)sender
|
||||
{
|
||||
[[dateField2 formatter] setEmptyIsValid:[sender state] === CPOnState];
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(CPNotification)aNotification
|
||||
{
|
||||
var field = [aNotification object],
|
||||
error;
|
||||
|
||||
if (field === dateField2)
|
||||
error = error1;
|
||||
else if (field === textField)
|
||||
error = error2;
|
||||
|
||||
[error setStringValue:@""];
|
||||
}
|
||||
|
||||
- (BOOL)control:(CPControl)aControl didFailToFormatString:(CPString)aString errorDescription:(CPString)anError
|
||||
{
|
||||
CPLog.info("control:didFailToFormatString:%s errorDescription:%s", aString, anError);
|
||||
|
||||
if (anError)
|
||||
{
|
||||
var error;
|
||||
|
||||
if (aControl === dateField2)
|
||||
error = error1;
|
||||
else if (aControl === textField)
|
||||
error = error2;
|
||||
|
||||
[error setStringValue:anError];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)resetErrorMessage:(CPNotification)aNotification
|
||||
{
|
||||
[error2 setStringValue:@""];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TextFormatter : CPFormatter
|
||||
|
||||
- (CPString)stringForObjectValue:(id)anObject
|
||||
{
|
||||
var result;
|
||||
|
||||
if ([anObject isKindOfClass:[CPString class]])
|
||||
result = [self errorDescriptionForString:anObject] === nil ? anObject : nil;
|
||||
else
|
||||
result = nil;
|
||||
|
||||
console.log("stringForObjectValue:%s ==> %s", [anObject description], result);
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)getObjectValue:(CPStringRef)anObject forString:(CPString)aString errorDescription:(CPStringRef)anError
|
||||
{
|
||||
var error = [self errorDescriptionForString:aString];
|
||||
|
||||
if (error)
|
||||
{
|
||||
anObject(nil);
|
||||
|
||||
if (anError)
|
||||
anError(error);
|
||||
}
|
||||
else
|
||||
anObject(aString);
|
||||
|
||||
result = error === nil;
|
||||
|
||||
console.log("getObjectValue:forString:%s ==> %s", aString, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CPString)errorDescriptionForString:(CPString)aString
|
||||
{
|
||||
if (aString.length > 7)
|
||||
return @"Maximum length is 7 characters.";
|
||||
else if (!/^[a-zA-Z0-9]*$/.test(aString))
|
||||
return @"Invalid characters in string.";
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ContactFormatter : CPFormatter
|
||||
|
||||
- (CPString)stringForObjectValue:(id)anObject
|
||||
{
|
||||
if (anObject && typeof(anObject) === "object" && anObject.hasOwnProperty("name"))
|
||||
return [CPString stringWithFormat:@"%s (age %d)", anObject.name, anObject.age];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)getObjectValue:(CPStringRef)anObject forString:(CPString)aString errorDescription:(CPStringRef)anError
|
||||
{
|
||||
// We don't support reverse conversion
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* AppController.j
|
||||
* CPFormatterTest
|
||||
*
|
||||
* Created by aparajita on July 6, 2011.
|
||||
* Copyright 2011, Victory-Heart Productions All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/CPFormatter.j>
|
||||
|
||||
|
||||
@implementation DateFormatter : CPFormatter
|
||||
{
|
||||
CPString displayFormat;
|
||||
CPString editingFormat;
|
||||
BOOL emptyIsValid @accessors(getter=isEmptyValid);
|
||||
}
|
||||
|
||||
+ (DateFormatter)formatterWithDisplayFormat:(CPString)aDisplayFormat editingFormat:(CPString)anEditingFormat emptyIsValid:(BOOL)emptyValid
|
||||
{
|
||||
return [[self alloc] initWithDisplayFormat:aDisplayFormat editingFormat:anEditingFormat emptyIsValid:emptyValid];
|
||||
}
|
||||
|
||||
- (id)initWithDisplayFormat:(CPString)aDisplayFormat editingFormat:(CPString)anEditingFormat emptyIsValid:(BOOL)emptyValid
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
displayFormat = aDisplayFormat;
|
||||
editingFormat = anEditingFormat;
|
||||
emptyIsValid = emptyValid;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)stringForObjectValue:(id)anObject
|
||||
{
|
||||
var result;
|
||||
|
||||
if ([anObject isKindOfClass:[CPDate class]])
|
||||
result = anObject.dateFormat(displayFormat);
|
||||
else
|
||||
result = nil;
|
||||
|
||||
console.log("stringForObjectValue:%s ==> %s", [anObject description], result);
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CPString)editingStringForObjectValue:(id)anObject
|
||||
{
|
||||
var result;
|
||||
|
||||
if ([anObject isKindOfClass:[CPDate class]])
|
||||
result = anObject.dateFormat(editingFormat);
|
||||
else
|
||||
result = nil;
|
||||
|
||||
console.log("editingStringForObjectValue:%s ==> %s", [anObject description], result);
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)getObjectValue:(CPStringRef)anObject forString:(CPString)aString errorDescription:(CPStringRef)anError
|
||||
{
|
||||
var result;
|
||||
|
||||
if (anError)
|
||||
anError(nil);
|
||||
|
||||
if (aString.length === 0)
|
||||
{
|
||||
anObject(nil);
|
||||
result = emptyIsValid;
|
||||
|
||||
if (anError)
|
||||
anError(@"Please enter a date.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var date = Date.parseDate(aString, editingFormat);
|
||||
|
||||
anObject(date);
|
||||
|
||||
if (date === nil && anError)
|
||||
anError(@"Invalid date format.");
|
||||
|
||||
result = date !== nil;
|
||||
}
|
||||
|
||||
console.log("getObjectValue:forString:%s ==> %s", aString, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var DateFormatterDisplayFormatKey = @"DateFormatterDisplayFormatKey",
|
||||
DateFormatterEditingFormatKey = @"DateFormatterEditingFormatKey",
|
||||
DateFormatterEmptyIsValidKey = @"DateFormatterEmptyIsValidKey";
|
||||
|
||||
@implementation DateFormatter (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
displayFormat = [aCoder decodeObjectForKey:DateFormatterDisplayFormatKey];
|
||||
editingFormat = [aCoder decodeObjectForKey:DateFormatterEditingFormatKey];
|
||||
emptyIsValid = [aCoder decodeObjectForKey:DateFormatterEmptyIsValidKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
[aCoder encodeObject:displayFormat forKey:DateFormatterDisplayFormatKey];
|
||||
[aCoder encodeObject:editingFormat forKey:DateFormatterEditingFormatKey];
|
||||
[aCoder encodeObject:emptyIsValid forKey:DateFormatterEmptyIsValidKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Main cib file base name</key>
|
||||
<string>MainMenu.cib</string>
|
||||
<key>CPBundleName</key>
|
||||
<string>CPFormatterTest</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Jakefile
|
||||
* CPFormatterTest
|
||||
*
|
||||
* Created by aparajita on June 30, 2011.
|
||||
* Copyright 2011, Victory-Heart Productions All rights reserved.
|
||||
*/
|
||||
|
||||
var ENV = require("system").env,
|
||||
FILE = require("file"),
|
||||
JAKE = require("jake"),
|
||||
task = JAKE.task,
|
||||
FileList = JAKE.FileList,
|
||||
app = require("cappuccino/jake").app,
|
||||
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug",
|
||||
OS = require("os");
|
||||
|
||||
app ("CPFormatterTest", function(task)
|
||||
{
|
||||
task.setBuildIntermediatesPath(FILE.join("Build", "CPFormatterTest.build", configuration));
|
||||
task.setBuildPath(FILE.join("Build", configuration));
|
||||
|
||||
task.setProductName("CPFormatterTest");
|
||||
task.setIdentifier("com.aparajita.CPFormatterTest");
|
||||
task.setVersion("1.0");
|
||||
task.setAuthor("Victory-Heart Productions");
|
||||
task.setEmail("feedback @nospam@ yourcompany.com");
|
||||
task.setSummary("CPFormatterTest");
|
||||
task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**")));
|
||||
task.setResources(new FileList("Resources/**"));
|
||||
task.setIndexFilePath("index.html");
|
||||
task.setInfoPlistPath("Info.plist");
|
||||
task.setNib2CibFlags("-R Resources/");
|
||||
|
||||
if (configuration === "Debug")
|
||||
task.setCompilerFlags("-DDEBUG -g");
|
||||
else
|
||||
task.setCompilerFlags("-O");
|
||||
});
|
||||
|
||||
task ("default", ["CPFormatterTest"], function()
|
||||
{
|
||||
printResults(configuration);
|
||||
});
|
||||
|
||||
task ("build", ["default"]);
|
||||
|
||||
task ("debug", function()
|
||||
{
|
||||
ENV["CONFIGURATION"] = "Debug";
|
||||
JAKE.subjake(["."], "build", ENV);
|
||||
});
|
||||
|
||||
task ("release", function()
|
||||
{
|
||||
ENV["CONFIGURATION"] = "Release";
|
||||
JAKE.subjake(["."], "build", ENV);
|
||||
});
|
||||
|
||||
task ("run", ["debug"], function()
|
||||
{
|
||||
OS.system(["open", FILE.join("Build", "Debug", "CPFormatterTest", "index.html")]);
|
||||
});
|
||||
|
||||
task ("run-release", ["release"], function()
|
||||
{
|
||||
OS.system(["open", FILE.join("Build", "Release", "CPFormatterTest", "index.html")]);
|
||||
});
|
||||
|
||||
task ("deploy", ["release"], function()
|
||||
{
|
||||
FILE.mkdirs(FILE.join("Build", "Deployment", "CPFormatterTest"));
|
||||
OS.system(["press", "-f", FILE.join("Build", "Release", "CPFormatterTest"), FILE.join("Build", "Deployment", "CPFormatterTest")]);
|
||||
printResults("Deployment")
|
||||
});
|
||||
|
||||
task ("desktop", ["release"], function()
|
||||
{
|
||||
FILE.mkdirs(FILE.join("Build", "Desktop", "CPFormatterTest"));
|
||||
require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "CPFormatterTest"), FILE.join("Build", "Desktop", "CPFormatterTest", "CPFormatterTest.app"));
|
||||
printResults("Desktop")
|
||||
});
|
||||
|
||||
task ("run-desktop", ["desktop"], function()
|
||||
{
|
||||
OS.system([FILE.join("Build", "Desktop", "CPFormatterTest", "CPFormatterTest.app", "Contents", "MacOS", "NativeHost"), "-i"]);
|
||||
});
|
||||
|
||||
function printResults(configuration)
|
||||
{
|
||||
print("----------------------------");
|
||||
print(configuration+" app built at path: "+FILE.join("Build", configuration, "CPFormatterTest"));
|
||||
print("----------------------------");
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
* http://code.google.com/p/flexible-js-formatting/
|
||||
*
|
||||
* Copyright (C) 2004 Baron Schwartz <baron at sequent dot org>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
Date.parseFunctions = {count:0};
|
||||
Date.parseRegexes = [];
|
||||
Date.formatFunctions = {count:0};
|
||||
|
||||
Date.prototype.dateFormat = function(format, ignore_offset) {
|
||||
if (Date.formatFunctions[format] == null) {
|
||||
Date.createNewFormat(format);
|
||||
}
|
||||
var func = Date.formatFunctions[format];
|
||||
if (ignore_offset || ! this.offset) {
|
||||
return this[func]();
|
||||
} else {
|
||||
return (new Date(this.valueOf() - this.offset))[func]();
|
||||
}
|
||||
};
|
||||
|
||||
Date.createNewFormat = function(format) {
|
||||
var funcName = "format" + Date.formatFunctions.count++;
|
||||
Date.formatFunctions[format] = funcName;
|
||||
var code = "Date.prototype." + funcName + " = function(){return ";
|
||||
var special = false;
|
||||
var ch = '';
|
||||
for (var i = 0; i < format.length; ++i) {
|
||||
ch = format.charAt(i);
|
||||
// escape character start
|
||||
if (!special && ch == "\\") {
|
||||
special = true;
|
||||
}
|
||||
// escaped string
|
||||
else if (!special && ch == '"') {
|
||||
var end = format.indexOf('"', i+1);
|
||||
if (end==-1)
|
||||
{
|
||||
end = format.length;
|
||||
}
|
||||
code += "'" + String.escape(format.substring(i+1, end)) + "' + ";
|
||||
i = end;
|
||||
}
|
||||
// escaped character
|
||||
else if (special) {
|
||||
special = false;
|
||||
code += "'" + String.escape(ch) + "' + ";
|
||||
}
|
||||
else {
|
||||
code += Date.getFormatCode(ch);
|
||||
}
|
||||
}
|
||||
eval(code.substring(0, code.length - 3) + ";}");
|
||||
};
|
||||
|
||||
Date.getFormatCode = function(character) {
|
||||
switch (character) {
|
||||
case "d":
|
||||
return "String.leftPad(this.getDate(), 2, '0') + ";
|
||||
case "D":
|
||||
return "Date.dayNames[this.getDay()].substring(0, 3) + ";
|
||||
case "j":
|
||||
return "this.getDate() + ";
|
||||
case "l":
|
||||
return "Date.dayNames[this.getDay()] + ";
|
||||
case "S":
|
||||
return "this.getSuffix() + ";
|
||||
case "w":
|
||||
return "this.getDay() + ";
|
||||
case "z":
|
||||
return "this.getDayOfYear() + ";
|
||||
case "W":
|
||||
return "this.getWeekOfYear() + ";
|
||||
case "F":
|
||||
return "Date.monthNames[this.getMonth()] + ";
|
||||
case "m":
|
||||
return "String.leftPad(this.getMonth() + 1, 2, '0') + ";
|
||||
case "M":
|
||||
return "Date.monthNames[this.getMonth()].substring(0, 3) + ";
|
||||
case "n":
|
||||
return "(this.getMonth() + 1) + ";
|
||||
case "t":
|
||||
return "this.getDaysInMonth() + ";
|
||||
case "L":
|
||||
return "(this.isLeapYear() ? 1 : 0) + ";
|
||||
case "Y":
|
||||
return "this.getFullYear() + ";
|
||||
case "y":
|
||||
return "('' + this.getFullYear()).substring(2, 4) + ";
|
||||
case "a":
|
||||
return "(this.getHours() < 12 ? 'am' : 'pm') + ";
|
||||
case "A":
|
||||
return "(this.getHours() < 12 ? 'AM' : 'PM') + ";
|
||||
case "g":
|
||||
return "((this.getHours() %12) ? this.getHours() % 12 : 12) + ";
|
||||
case "G":
|
||||
return "this.getHours() + ";
|
||||
case "h":
|
||||
return "String.leftPad((this.getHours() %12) ? this.getHours() % 12 : 12, 2, '0') + ";
|
||||
case "H":
|
||||
return "String.leftPad(this.getHours(), 2, '0') + ";
|
||||
case "i":
|
||||
return "String.leftPad(this.getMinutes(), 2, '0') + ";
|
||||
case "s":
|
||||
return "String.leftPad(this.getSeconds(), 2, '0') + ";
|
||||
case "X":
|
||||
return "String.leftPad(this.getMilliseconds(), 3, '0') + ";
|
||||
case "O":
|
||||
return "this.getGMTOffset() + ";
|
||||
case "T":
|
||||
return "this.getTimezone() + ";
|
||||
case "Z":
|
||||
return "(this.getTimezoneOffset() * -60) + ";
|
||||
case "q": // quarter num, Q for name?
|
||||
return "this.getQuarter() + ";
|
||||
default:
|
||||
return "'" + String.escape(character) + "' + ";
|
||||
}
|
||||
};
|
||||
|
||||
Date.parseDate = function(input, format) {
|
||||
if (Date.parseFunctions[format] == null) {
|
||||
Date.createParser(format);
|
||||
}
|
||||
var func = Date.parseFunctions[format];
|
||||
return Date[func](input);
|
||||
};
|
||||
|
||||
Date.createParser = function(format) {
|
||||
var funcName = "parse" + Date.parseFunctions.count++;
|
||||
var regexNum = Date.parseRegexes.length;
|
||||
var currentGroup = 1;
|
||||
Date.parseFunctions[format] = funcName;
|
||||
|
||||
var code = "Date." + funcName + " = function(input){\n"
|
||||
+ "var y = -1, m = -1, d = -1, h = -1, i = -1, s = -1, ms = -1, z = 0;\n"
|
||||
+ "var d = new Date();\n"
|
||||
+ "y = d.getFullYear();\n"
|
||||
+ "m = d.getMonth();\n"
|
||||
+ "d = d.getDate();\n"
|
||||
+ "var results = input.match(Date.parseRegexes[" + regexNum + "]);\n"
|
||||
+ "if (results && results.length > 0) {" ;
|
||||
var regex = "";
|
||||
|
||||
var special = false;
|
||||
var ch = '';
|
||||
for (var i = 0; i < format.length; ++i) {
|
||||
ch = format.charAt(i);
|
||||
if (!special && ch == "\\") {
|
||||
special = true;
|
||||
}
|
||||
else if (special) {
|
||||
special = false;
|
||||
regex += String.escape(ch);
|
||||
}
|
||||
else {
|
||||
obj = Date.formatCodeToRegex(ch, currentGroup);
|
||||
currentGroup += obj.g;
|
||||
regex += obj.s;
|
||||
if (obj.g && obj.c) {
|
||||
code += obj.c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code += "if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0 && ms >= 0)\n"
|
||||
+ "{return new Date(y, m, d, h, i, s, ms).applyOffset(z);}\n"
|
||||
+ "if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n"
|
||||
+ "{return new Date(y, m, d, h, i, s).applyOffset(z);}\n"
|
||||
+ "else if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n"
|
||||
+ "{return new Date(y, m, d, h, i).applyOffset(z);}\n"
|
||||
+ "else if (y > 0 && m >= 0 && d > 0 && h >= 0)\n"
|
||||
+ "{return new Date(y, m, d, h).applyOffset(z);}\n"
|
||||
+ "else if (y > 0 && m >= 0 && d > 0)\n"
|
||||
+ "{return new Date(y, m, d).applyOffset(z);}\n"
|
||||
+ "else if (y > 0 && m >= 0)\n"
|
||||
+ "{return new Date(y, m).applyOffset(z);}\n"
|
||||
+ "else if (y > 0)\n"
|
||||
+ "{return new Date(y).applyOffset(z);}\n"
|
||||
+ "}return null;}";
|
||||
|
||||
Date.parseRegexes[regexNum] = new RegExp("^" + regex + "$");
|
||||
eval(code);
|
||||
};
|
||||
|
||||
Date.formatCodeToRegex = function(character, currentGroup) {
|
||||
switch (character) {
|
||||
case "D":
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:"(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)"};
|
||||
case "j":
|
||||
case "d":
|
||||
return {g:1,
|
||||
c:"d = parseInt(results[" + currentGroup + "], 10);\n",
|
||||
s:"(\\d{1,2})"};
|
||||
case "l":
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:"(?:" + Date.dayNames.join("|") + ")"};
|
||||
case "S":
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:"(?:st|nd|rd|th)"};
|
||||
case "w":
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:"\\d"};
|
||||
case "z":
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:"(?:\\d{1,3})"};
|
||||
case "W":
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:"(?:\\d{2})"};
|
||||
case "F":
|
||||
return {g:1,
|
||||
c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "].substring(0, 3)], 10);\n",
|
||||
s:"(" + Date.monthNames.join("|") + ")"};
|
||||
case "M":
|
||||
return {g:1,
|
||||
c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "]], 10);\n",
|
||||
s:"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"};
|
||||
case "n":
|
||||
case "m":
|
||||
return {g:1,
|
||||
c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
|
||||
s:"(\\d{1,2})"};
|
||||
case "t":
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:"\\d{1,2}"};
|
||||
case "L":
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:"(?:1|0)"};
|
||||
case "Y":
|
||||
return {g:1,
|
||||
c:"y = parseInt(results[" + currentGroup + "], 10);\n",
|
||||
s:"(\\d{4})"};
|
||||
case "y":
|
||||
return {g:1,
|
||||
c:"var ty = parseInt(results[" + currentGroup + "], 10);\n"
|
||||
+ "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n",
|
||||
s:"(\\d{1,2})"};
|
||||
case "a":
|
||||
return {g:1,
|
||||
c:"if (results[" + currentGroup + "] == 'am') {\n"
|
||||
+ "if (h == 12) { h = 0; }\n"
|
||||
+ "} else { if (h < 12) { h += 12; }}",
|
||||
s:"(am|pm)"};
|
||||
case "A":
|
||||
return {g:1,
|
||||
c:"if (results[" + currentGroup + "] == 'AM') {\n"
|
||||
+ "if (h == 12) { h = 0; }\n"
|
||||
+ "} else { if (h < 12) { h += 12; }}",
|
||||
s:"(AM|PM)"};
|
||||
case "g":
|
||||
case "G":
|
||||
case "h":
|
||||
case "H":
|
||||
return {g:1,
|
||||
c:"h = parseInt(results[" + currentGroup + "], 10);\n",
|
||||
s:"(\\d{1,2})"};
|
||||
case "i":
|
||||
return {g:1,
|
||||
c:"i = parseInt(results[" + currentGroup + "], 10);\n",
|
||||
s:"(\\d{2})"};
|
||||
case "s":
|
||||
return {g:1,
|
||||
c:"s = parseInt(results[" + currentGroup + "], 10);\n",
|
||||
s:"(\\d{2})"};
|
||||
case "X":
|
||||
return {g:1,
|
||||
c:"ms = parseInt(results[" + currentGroup + "], 10);\n",
|
||||
s:"(\\d{3})"};
|
||||
case "O":
|
||||
case "P":
|
||||
return {g:1,
|
||||
c:"z = Date.parseOffset(results[" + currentGroup + "], 10);\n",
|
||||
s:"(Z|[+-]\\d{2}:?\\d{2})"}; // "Z", "+05:00", "+0500" all acceptable.
|
||||
case "T":
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:"[A-Z]{3}"};
|
||||
case "Z":
|
||||
return {g:1,
|
||||
c:"s = parseInt(results[" + currentGroup + "], 10);\n",
|
||||
s:"([+-]\\d{1,5})"};
|
||||
default:
|
||||
return {g:0,
|
||||
c:null,
|
||||
s:String.escape(character)};
|
||||
}
|
||||
};
|
||||
|
||||
Date.parseOffset = function(str) {
|
||||
if (str == "Z") { return 0 ; } // UTC, no offset.
|
||||
var seconds ;
|
||||
seconds = parseInt(str[0] + str[1] + str[2]) * 3600 ; // e.g., "+05" or "-08"
|
||||
if (str[3] == ":") { // "+HH:MM" is preferred iso8601 format ("O")
|
||||
seconds += parseInt(str[4] + str[5]) * 60;
|
||||
} else { // "+HHMM" is frequently used, though. ("P")
|
||||
seconds += parseInt(str[3] + str[4]) * 60;
|
||||
}
|
||||
return seconds ;
|
||||
};
|
||||
|
||||
// convert the parsed date into UTC, but store the offset so we can optionally use it in dateFormat()
|
||||
Date.prototype.applyOffset = function(offset_seconds) {
|
||||
this.offset = offset_seconds * 1000 ;
|
||||
this.setTime(this.valueOf() + this.offset);
|
||||
return this ;
|
||||
};
|
||||
|
||||
Date.prototype.getTimezone = function() {
|
||||
return this.toString().replace(
|
||||
/^.*? ([A-Z]{3}) [0-9]{4}.*$/, "$1").replace(
|
||||
/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, "$1$2$3").replace(
|
||||
/^.*?[0-9]{4} \(([A-Z]{3})\)/, "$1");
|
||||
};
|
||||
|
||||
Date.prototype.getGMTOffset = function() {
|
||||
return (this.getTimezoneOffset() > 0 ? "-" : "+")
|
||||
+ String.leftPad(Math.floor(this.getTimezoneOffset() / 60), 2, "0")
|
||||
+ String.leftPad(this.getTimezoneOffset() % 60, 2, "0");
|
||||
};
|
||||
|
||||
Date.prototype.getDayOfYear = function() {
|
||||
var num = 0;
|
||||
Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
|
||||
for (var i = 0; i < this.getMonth(); ++i) {
|
||||
num += Date.daysInMonth[i];
|
||||
}
|
||||
return num + this.getDate() - 1;
|
||||
};
|
||||
|
||||
Date.prototype.getWeekOfYear = function() {
|
||||
// Skip to Thursday of this week
|
||||
var now = this.getDayOfYear() + (4 - this.getDay());
|
||||
// Find the first Thursday of the year
|
||||
var jan1 = new Date(this.getFullYear(), 0, 1);
|
||||
var then = (7 - jan1.getDay() + 4);
|
||||
document.write(then);
|
||||
return String.leftPad(((now - then) / 7) + 1, 2, "0");
|
||||
};
|
||||
|
||||
Date.prototype.isLeapYear = function() {
|
||||
var year = this.getFullYear();
|
||||
return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
|
||||
};
|
||||
|
||||
Date.prototype.getFirstDayOfMonth = function() {
|
||||
var day = (this.getDay() - (this.getDate() - 1)) % 7;
|
||||
return (day < 0) ? (day + 7) : day;
|
||||
};
|
||||
|
||||
Date.prototype.getLastDayOfMonth = function() {
|
||||
var day = (this.getDay() + (Date.daysInMonth[this.getMonth()] - this.getDate())) % 7;
|
||||
return (day < 0) ? (day + 7) : day;
|
||||
};
|
||||
|
||||
Date.prototype.getDaysInMonth = function() {
|
||||
Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
|
||||
return Date.daysInMonth[this.getMonth()];
|
||||
};
|
||||
Date.prototype.getQuarter = function() {
|
||||
return Date.quarterFromMonthNum[this.getMonth()];
|
||||
};
|
||||
|
||||
Date.prototype.getSuffix = function() {
|
||||
switch (this.getDate()) {
|
||||
case 1:
|
||||
case 21:
|
||||
case 31:
|
||||
return "st";
|
||||
case 2:
|
||||
case 22:
|
||||
return "nd";
|
||||
case 3:
|
||||
case 23:
|
||||
return "rd";
|
||||
default:
|
||||
return "th";
|
||||
}
|
||||
};
|
||||
|
||||
String.escape = function(string) {
|
||||
return string.replace(/('|\\)/g, "\\$1");
|
||||
};
|
||||
|
||||
String.leftPad = function (val, size, ch) {
|
||||
var result = new String(val);
|
||||
if (ch == null) {
|
||||
ch = " ";
|
||||
}
|
||||
while (result.length < size) {
|
||||
result = ch + result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
Date.quarterFromMonthNum = [1,1,1,2,2,2,3,3,3,4,4,4];
|
||||
Date.daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31];
|
||||
Date.monthNames =
|
||||
["January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"];
|
||||
Date.dayNames =
|
||||
["Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday"];
|
||||
Date.y2kYear = 50;
|
||||
Date.monthNumbers = {
|
||||
Jan:0,
|
||||
Feb:1,
|
||||
Mar:2,
|
||||
Apr:3,
|
||||
May:4,
|
||||
Jun:5,
|
||||
Jul:6,
|
||||
Aug:7,
|
||||
Sep:8,
|
||||
Oct:9,
|
||||
Nov:10,
|
||||
Dec:11};
|
||||
Date.patterns = {
|
||||
ISO8601LongPattern: "Y\\-m\\-d\\TH\\:i\\:sO",
|
||||
ISO8601ShortPattern: "Y\\-m\\-d",
|
||||
ShortDatePattern: "n/j/Y",
|
||||
LongDatePattern: "l, F d, Y",
|
||||
FullDateTimePattern: "l, F d, Y g:i:s A",
|
||||
MonthDayPattern: "F d",
|
||||
ShortTimePattern: "g:i A",
|
||||
LongTimePattern: "g:i:s A",
|
||||
SortableDateTimePattern: "Y-m-d\\TH:i:s",
|
||||
UniversalSortableDateTimePattern: "Y-m-d H:i:sO",
|
||||
YearMonthPattern: "F, Y"};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,105 @@
|
||||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<!--
|
||||
index-debug.html
|
||||
CPFormatterTest
|
||||
|
||||
Created by aparajita on June 30, 2011.
|
||||
Copyright 2011, Victory-Heart Productions All rights reserved.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1" />
|
||||
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
|
||||
<link rel="apple-touch-icon" href="Resources/icon.png" />
|
||||
<link rel="apple-touch-startup-image" href="Resources/default.png" />
|
||||
|
||||
<title>CPFormatterTest</title>
|
||||
|
||||
<script src="Resources/date-functions.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
OBJJ_MAIN_FILE = "main.j";
|
||||
OBJJ_INCLUDE_PATHS = ["Frameworks/Debug", "Frameworks"];
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="Frameworks/Debug/Objective-J/Objective-J.js" charset="UTF-8"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
objj_msgSend_reset();
|
||||
|
||||
// DEBUG OPTIONS:
|
||||
|
||||
// Uncomment to enable printing of backtraces on exceptions:
|
||||
//objj_msgSend_decorate(objj_backtrace_decorator);
|
||||
|
||||
// Uncomment to supress exceptions that take place inside a message
|
||||
//objj_msgSend_decorate(objj_supress_exceptions_decorator)
|
||||
|
||||
// Uncomment to enable runtime type checking:
|
||||
//objj_msgSend_decorate(objj_typecheck_decorator);
|
||||
|
||||
// Uncomment (along with both above) to print backtraces on type check errors:
|
||||
//objj_typecheck_prints_backtrace = true;
|
||||
|
||||
// Uncomment to disable the default logger (CPLogConsole if window.console exists, CPLogPopup otherwise):
|
||||
//CPLogUnregister(CPLogDefault);
|
||||
|
||||
// Uncomment to enable a specific logger:
|
||||
//CPLogRegister(CPLogConsole);
|
||||
//CPLogRegister(CPLogPopup);
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
body{margin:0; padding:0;}
|
||||
#container {position: absolute; top:50%; left:50%;}
|
||||
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
|
||||
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
|
||||
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
|
||||
</style>
|
||||
|
||||
<!--[if lt IE 7]>
|
||||
<STYLE type="text/css">
|
||||
#container { position: relative; top: 50%; }
|
||||
#content { position: relative;}
|
||||
</STYLE>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body style="">
|
||||
<div id="cappuccino-body">
|
||||
<div id="loadingcontainer" style="background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
|
||||
<script type="text/javascript">
|
||||
document.write("<div id='container'><p id='content'>" +
|
||||
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
|
||||
"Loading CPFormatterTest...</p></div>");
|
||||
</script>
|
||||
|
||||
<noscript>
|
||||
<div id="container">
|
||||
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
|
||||
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
|
||||
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino.org/noscript">Show me how to enable JavaScript</a></p>
|
||||
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
|
||||
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
|
||||
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
|
||||
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
|
||||
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
|
||||
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
|
||||
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<!--
|
||||
index.html
|
||||
CPFormatterTest
|
||||
|
||||
Created by aparajita on June 30, 2011.
|
||||
Copyright 2011, Victory-Heart Productions All rights reserved.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1" />
|
||||
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
|
||||
<link rel="apple-touch-icon" href="Resources/icon.png" />
|
||||
<link rel="apple-touch-startup-image" href="Resources/default.png" />
|
||||
|
||||
<title>CPFormatterTest</title>
|
||||
|
||||
<script src="Resources/date-functions.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
OBJJ_MAIN_FILE = "main.j";
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="Frameworks/Objective-J/Objective-J.js" charset="UTF-8"></script>
|
||||
|
||||
<style type="text/css">
|
||||
body{margin:0; padding:0;}
|
||||
#container {position: absolute; top:50%; left:50%;}
|
||||
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
|
||||
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
|
||||
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
|
||||
</style>
|
||||
|
||||
<!--[if lt IE 7]>
|
||||
<STYLE type="text/css">
|
||||
#container { position: relative; top: 50%; }
|
||||
#content { position: relative;}
|
||||
</STYLE>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body style="">
|
||||
<div id="cappuccino-body">
|
||||
<div id="loadingcontainer" style="background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
|
||||
<script type="text/javascript">
|
||||
document.write("<div id='container'><p id='content'>" +
|
||||
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
|
||||
"Loading CPFormatterTest...</p></div>");
|
||||
</script>
|
||||
|
||||
<noscript>
|
||||
<div id="container">
|
||||
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
|
||||
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
|
||||
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino.org/noscript">Show me how to enable JavaScript</a></p>
|
||||
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
|
||||
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
|
||||
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
|
||||
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
|
||||
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
|
||||
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
|
||||
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* AppController.j
|
||||
* CPFormatterTest
|
||||
*
|
||||
* Created by aparajita on June 30, 2011.
|
||||
* Copyright 2011, Victory-Heart Productions All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
@import <AppKit/AppKit.j>
|
||||
|
||||
@import "AppController.j"
|
||||
|
||||
function formatter(aString, aLevel, aTitle)
|
||||
{
|
||||
return aString;
|
||||
}
|
||||
|
||||
function main(args, namedArgs)
|
||||
{
|
||||
CPLogRegister(CPLogConsole, null, formatter);
|
||||
CPApplicationMain(args, namedArgs);
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
|
||||
@implementation RootClass
|
||||
{
|
||||
}
|
||||
|
||||
+ (id)alloc
|
||||
{
|
||||
return class_createInstance(self);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RootClassWithDoesNotRecognizeSelector
|
||||
{
|
||||
}
|
||||
|
||||
+ (id)alloc
|
||||
{
|
||||
return class_createInstance(self);
|
||||
}
|
||||
|
||||
- (void)doesNotRecognizeSelector:(SEL)aSelector
|
||||
{
|
||||
throw "ERROR";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation Subclass : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RootClassWithForwardingTarget
|
||||
{
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
}
|
||||
|
||||
+ (id)alloc
|
||||
{
|
||||
return class_createInstance(self);
|
||||
}
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)aSelector
|
||||
{
|
||||
if (aSelector !== @selector(doesNotExist))
|
||||
return nil;
|
||||
|
||||
if (class_isMetaClass(isa))
|
||||
return GlobalMethodDispatchTest;
|
||||
|
||||
return GlobalMethodDispatchTest;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SubclassWithForwardingTarget : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
+ (id)forwardingTargetForSelector:(SEL)aSelector
|
||||
{
|
||||
if (aSelector !== @selector(doesNotExist))
|
||||
return [super forwardingTargetForSelector:aSelector];
|
||||
|
||||
return GlobalMethodDispatchTest;
|
||||
}
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)aSelector
|
||||
{
|
||||
if (aSelector !== @selector(doesNotExist))
|
||||
return [super forwardingTargetForSelector:aSelector];
|
||||
|
||||
return GlobalMethodDispatchTest;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RootClassWithForwardInvocation
|
||||
{
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
}
|
||||
|
||||
+ (id)alloc
|
||||
{
|
||||
return class_createInstance(self);
|
||||
}
|
||||
|
||||
- (CPMethodSignature)methodSignatureForSelector:(SEL)aSelector
|
||||
{
|
||||
if (aSelector === @selector(doesNotExist))
|
||||
return 1;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)forwardInvocation:(CPInvocation)anInvocation
|
||||
{
|
||||
[anInvocation setTarget:GlobalMethodDispatchTest];
|
||||
[anInvocation invoke];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SubclassWithForwardInvocation : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
+ (CPMethodSignature)methodSignatureForSelector:(SEL)aSelector
|
||||
{
|
||||
if (aSelector === @selector(doesNotExist))
|
||||
return 1;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (void)forwardInvocation:(CPInvocation)anInvocation
|
||||
{
|
||||
[anInvocation setTarget:GlobalMethodDispatchTest];
|
||||
[anInvocation invoke];
|
||||
}
|
||||
|
||||
- (CPMethodSignature)methodSignatureForSelector:(SEL)aSelector
|
||||
{
|
||||
if (aSelector === @selector(doesNotExist))
|
||||
return 1;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)forwardInvocation:(CPInvocation)anInvocation
|
||||
{
|
||||
[anInvocation setTarget:GlobalMethodDispatchTest];
|
||||
[anInvocation invoke];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var GlobalMethodDispatchTest;
|
||||
|
||||
@implementation MethodDispatchTest : OJTestCase
|
||||
{
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
GlobalMethodDispatchTest = self;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)doesNotExist
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)test_RootClass_class_doesNotRecognizeSelector_
|
||||
{
|
||||
try
|
||||
{
|
||||
[RootClass doesNotExist];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
[self assert:anException equals:"RootClass does not implement doesNotRecognizeSelector:. Did you forget a superclass for RootClass?"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_RootClass_instance_doesNotRecognizeSelector_
|
||||
{
|
||||
var object = [RootClass alloc];
|
||||
|
||||
try
|
||||
{
|
||||
[object doesNotExist];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
[self assert:anException equals:"RootClass does not implement doesNotRecognizeSelector:. Did you forget a superclass for RootClass?"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_RootClassWithDoesNotRecognizeSelector_class_doesNotRecognizeSelector_
|
||||
{
|
||||
try
|
||||
{
|
||||
[RootClassWithDoesNotRecognizeSelector doesNotExist];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
[self assert:anException equals:"ERROR"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_RootClassWithDoesNotRecognizeSelector_instance_doesNotRecognizeSelector_
|
||||
{
|
||||
var object = [RootClassWithDoesNotRecognizeSelector alloc];
|
||||
|
||||
try
|
||||
{
|
||||
[object doesNotExist];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
[self assert:anException equals:"ERROR"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_CPObject_class_doesNotRecognizeSelector_
|
||||
{
|
||||
try
|
||||
{
|
||||
[CPObject doesNotExist];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
[self assert:[anException name] equals:CPInvalidArgumentException];
|
||||
[self assert:[anException reason] equals:@"+ [CPObject doesNotExist] unrecognized selector sent to class CPObject"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_CPObject_instance_doesNotRecognizeSelector_
|
||||
{
|
||||
var object = [CPObject alloc];
|
||||
|
||||
try
|
||||
{
|
||||
[object doesNotExist];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
[self assert:[anException name] equals:CPInvalidArgumentException];
|
||||
[self assert:[anException reason] equals:@"- [CPObject doesNotExist] unrecognized selector sent to instance 0x" + [CPString stringWithHash:[object UID]]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_Subclass_class_doesNotRecognizeSelector_
|
||||
{
|
||||
try
|
||||
{
|
||||
[Subclass doesNotExist];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
[self assert:[anException name] equals:CPInvalidArgumentException];
|
||||
[self assert:[anException reason] equals:@"+ [Subclass doesNotExist] unrecognized selector sent to class Subclass"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_Subclass_instance_doesNotRecognizeSelector_
|
||||
{
|
||||
var object = [Subclass alloc];
|
||||
|
||||
try
|
||||
{
|
||||
[object doesNotExist];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
[self assert:[anException name] equals:CPInvalidArgumentException];
|
||||
[self assert:[anException reason] equals:@"- [Subclass doesNotExist] unrecognized selector sent to instance 0x" + [CPString stringWithHash:[object UID]]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_RootClassWithForwardingTarget_class_forwardingTargetForSelector_
|
||||
{
|
||||
[self assert:YES equals:[RootClassWithForwardingTarget doesNotExist]];
|
||||
}
|
||||
|
||||
- (void)test_RootClassWithForwardingTarget_instance_forwardingTargetForSelector_
|
||||
{
|
||||
var object = [RootClassWithForwardingTarget alloc];
|
||||
|
||||
[self assert:YES equals:[object doesNotExist]];
|
||||
}
|
||||
|
||||
- (void)test_SubclassWithForwardingTarget_class_forwardingTargetForSelector_
|
||||
{
|
||||
[self assert:YES equals:[SubclassWithForwardingTarget doesNotExist]];
|
||||
}
|
||||
|
||||
- (void)test_SubclassWithForwardingTarget_instance_forwardingTargetForSelector_
|
||||
{
|
||||
var object = [[SubclassWithForwardingTarget alloc] init];
|
||||
|
||||
[self assert:YES equals:[object doesNotExist]];
|
||||
}
|
||||
|
||||
- (void)test_RootClassWithForwardInvocation_class_forwardInvocation_
|
||||
{
|
||||
[self assert:YES equals:[RootClassWithForwardInvocation doesNotExist]];
|
||||
}
|
||||
|
||||
- (void)test_RootClassWithForwardInvocation_instance_forwardInvocation_
|
||||
{
|
||||
var object = [RootClassWithForwardInvocation alloc];
|
||||
|
||||
[self assert:YES equals:[object doesNotExist]];
|
||||
}
|
||||
|
||||
- (void)test_SubclassWithForwardInvocation_class_forwardInvocation_
|
||||
{
|
||||
[self assert:YES equals:[SubclassWithForwardInvocation doesNotExist]];
|
||||
}
|
||||
|
||||
- (void)test_SubclassWithForwardInvocation_instance_forwardInvocation_
|
||||
{
|
||||
var object = [[SubclassWithForwardInvocation alloc] init];
|
||||
|
||||
[self assert:YES equals:[object doesNotExist]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -189,6 +189,8 @@ function gen(/*va_args*/)
|
||||
{
|
||||
fail("The directory " + FILE.absolute(destinationProject) + " already exists.");
|
||||
}
|
||||
|
||||
executePostInstallScript(destinationProject);
|
||||
}
|
||||
|
||||
function createFrameworksInFile(/*Array*/ frameworks, /*String*/ aFile, /*Boolean*/ symlink, /*Boolean*/ build, /*Boolean*/ force)
|
||||
@@ -365,6 +367,19 @@ function listFrameworks()
|
||||
});
|
||||
}
|
||||
|
||||
function executePostInstallScript(/*String*/ destinationProject)
|
||||
{
|
||||
var path = FILE.join(destinationProject, "postinstall");
|
||||
|
||||
if (FILE.exists(path))
|
||||
{
|
||||
stream.print(colorize("Executing postinstall script...", "cyan"));
|
||||
|
||||
OS.system(["/bin/sh", path, destinationProject]); // Use sh in case it isn't marked executable
|
||||
FILE.remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
function colorize(message, color)
|
||||
{
|
||||
return "\0" + color + "(" + message + "\0)";
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* __filename__
|
||||
* __project.name__
|
||||
*
|
||||
* Created by __user.name__ on __project.date__.
|
||||
*
|
||||
* Copyright __project.year__, __organization.name__. All rights reserved.
|
||||
*
|
||||
* 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 "__project.nameasidentifier__Class.j"
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* __filename__
|
||||
* __project.name__
|
||||
*
|
||||
* Created by __user.name__ on __project.date__.
|
||||
*
|
||||
* Copyright __project.year__, __organization.name__. All rights reserved.
|
||||
*
|
||||
* 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>
|
||||
|
||||
|
||||
/*!
|
||||
This class is defined to make it easier to find the bundle,
|
||||
for example to get an image from the framework like this:
|
||||
|
||||
@code
|
||||
var path = [[CPBundle bundleForClass:__project.nameasidentifier__] pathForResource:@"email-action.png"];
|
||||
@endcode
|
||||
|
||||
You can also use [__project.nameasidentifier__ version] to get the current version.
|
||||
*/
|
||||
@implementation __project.nameasidentifier__ : CPObject
|
||||
|
||||
+ (CPString)version
|
||||
{
|
||||
var bundle = [CPBundle bundleForClass:[self class]];
|
||||
|
||||
return [bundle objectForInfoDictionaryKey:@"CPBundleVersion"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CPBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CPBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Jakefile
|
||||
* __project.name__
|
||||
*
|
||||
* Created by __user.name__ on __project.date__.
|
||||
*
|
||||
* Copyright __project.year__, __organization.name__. All rights reserved.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
//===========================================================
|
||||
// DO NOT REMOVE
|
||||
//===========================================================
|
||||
|
||||
var SYS = require("system"),
|
||||
ENV = SYS.env,
|
||||
FILE = require("file"),
|
||||
OS = require("os");
|
||||
|
||||
|
||||
//===========================================================
|
||||
// USER CONFIGURABLE VARIABLES
|
||||
//===========================================================
|
||||
|
||||
/*
|
||||
The directory in which the project will be built. By default
|
||||
it is built in $CAPP_BUILD if that is defined, otherwise
|
||||
in a "Build" directory within the project directory.
|
||||
*/
|
||||
var buildDir = ENV["BUILD_PATH"] || ENV["CAPP_BUILD"] || "Build";
|
||||
|
||||
|
||||
//===========================================================
|
||||
// AUTOMATICALLY GENERATED
|
||||
//
|
||||
// Do not edit! (unless you know what you are doing)
|
||||
//===========================================================
|
||||
|
||||
var stream = require("narwhal/term").stream,
|
||||
JAKE = require("jake"),
|
||||
task = JAKE.task,
|
||||
CLEAN = require("jake/clean").CLEAN,
|
||||
CLOBBER = require("jake/clean").CLOBBER,
|
||||
FileList = JAKE.FileList,
|
||||
filedir = JAKE.filedir,
|
||||
framework = require("cappuccino/jake").framework,
|
||||
browserEnvironment = require("objective-j/jake/environment").Browser,
|
||||
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug",
|
||||
productName = "__project.nameasidentifier__",
|
||||
buildPath = FILE.canonical(FILE.join(buildDir, productName + ".build")),
|
||||
packageFrameworksPath = FILE.join(SYS.prefix, "packages", "cappuccino", "Frameworks"),
|
||||
debugPackagePath = FILE.join(packageFrameworksPath, "Debug", productName);
|
||||
releasePackagePath = FILE.join(packageFrameworksPath, productName);
|
||||
|
||||
var frameworkTask = framework (productName, function(frameworkTask)
|
||||
{
|
||||
frameworkTask.setBuildIntermediatesPath(FILE.join(buildPath, configuration));
|
||||
frameworkTask.setBuildPath(FILE.join(buildDir, configuration));
|
||||
|
||||
frameworkTask.setProductName(productName);
|
||||
frameworkTask.setIdentifier("__project.identifier__");
|
||||
frameworkTask.setVersion("1.0");
|
||||
frameworkTask.setAuthor("__organization.name__");
|
||||
frameworkTask.setEmail("__organization.email__");
|
||||
frameworkTask.setSummary("__project.name__");
|
||||
frameworkTask.setSources(new FileList("*.j"));
|
||||
frameworkTask.setResources(new FileList("Resources/**/*"));
|
||||
frameworkTask.setFlattensSources(true);
|
||||
frameworkTask.setInfoPlistPath("Info.plist");
|
||||
frameworkTask.setLicense(BundleTask.License.LGPL_v2_1);
|
||||
//frameworkTask.setEnvironments([browserEnvironment]);
|
||||
|
||||
if (configuration === "Debug")
|
||||
frameworkTask.setCompilerFlags("-DDEBUG -g");
|
||||
else
|
||||
frameworkTask.setCompilerFlags("-O");
|
||||
});
|
||||
|
||||
task ("debug", function()
|
||||
{
|
||||
ENV["CONFIGURATION"] = "Debug";
|
||||
JAKE.subjake(["."], "build", ENV);
|
||||
});
|
||||
|
||||
task ("release", function()
|
||||
{
|
||||
ENV["CONFIGURATION"] = "Release";
|
||||
JAKE.subjake(["."], "build", ENV);
|
||||
});
|
||||
|
||||
task ("default", ["release"]);
|
||||
|
||||
var frameworkCJS = FILE.join(buildDir, configuration, "CommonJS", "cappuccino", "Frameworks", productName);
|
||||
|
||||
filedir (frameworkCJS, [productName], function()
|
||||
{
|
||||
if (FILE.exists(frameworkCJS))
|
||||
FILE.rmtree(frameworkCJS);
|
||||
|
||||
FILE.copyTree(frameworkTask.buildProductPath(), frameworkCJS);
|
||||
});
|
||||
|
||||
task ("build", [productName, frameworkCJS]);
|
||||
|
||||
task ("all", ["debug", "release"]);
|
||||
|
||||
task ("install", ["debug", "release"], function()
|
||||
{
|
||||
install("copy");
|
||||
});
|
||||
|
||||
task ("install-symlinks", ["debug", "release"], function()
|
||||
{
|
||||
install("symlink");
|
||||
});
|
||||
|
||||
task ("help", function()
|
||||
{
|
||||
var app = JAKE.application().name();
|
||||
|
||||
colorPrint("--------------------------------------------------------------------------", "bold+green");
|
||||
colorPrint("__project.name__ - Framework", "bold+green");
|
||||
colorPrint("--------------------------------------------------------------------------", "bold+green");
|
||||
|
||||
describeTask(app, "debug", "Builds a debug version at " + FILE.join(buildDir, "Debug", productName));
|
||||
describeTask(app, "release", "Builds a release version at " + FILE.join(buildDir, "Release", productName));
|
||||
describeTask(app, "all", "Builds a debug and release version");
|
||||
describeTask(app, "install", "Builds a debug and release version, then installs in " + packageFrameworksPath);
|
||||
describeTask(app, "install-symlinks", "Builds a debug and release version, then symlinks the built versions into " + packageFrameworksPath);
|
||||
describeTask(app, "clean", "Removes the intermediate build files");
|
||||
describeTask(app, "clobber", "Removes the intermediate build files and the installed frameworks");
|
||||
|
||||
colorPrint("--------------------------------------------------------------------------", "bold+green");
|
||||
});
|
||||
|
||||
CLEAN.include(buildPath);
|
||||
CLOBBER.include(FILE.join(buildDir, "Debug", productName))
|
||||
.include(FILE.join(buildDir, "Release", productName))
|
||||
.include(debugPackagePath)
|
||||
.include(releasePackagePath);
|
||||
|
||||
var install = function(action)
|
||||
{
|
||||
var packageFrameworksPath = FILE.join(SYS.prefix, "packages", "cappuccino", "Frameworks");
|
||||
|
||||
["Release", "Debug"].forEach(function(aConfig)
|
||||
{
|
||||
colorPrint((action === "symlink" ? "Symlinking " : "Copying ") + aConfig + "...", "cyan");
|
||||
|
||||
if (aConfig === "Debug")
|
||||
packageFrameworksPath = FILE.join(packageFrameworksPath, aConfig);
|
||||
|
||||
if (!FILE.isDirectory(packageFrameworksPath))
|
||||
sudo(["mkdir", "-p", packageFrameworksPath]);
|
||||
|
||||
var buildPath = FILE.absolute(FILE.join(buildDir, aConfig, productName)),
|
||||
targetPath = FILE.join(packageFrameworksPath, productName);
|
||||
|
||||
if (action === "symlink")
|
||||
directoryOp(["ln", "-sf", buildPath, targetPath]);
|
||||
else
|
||||
directoryOp(["cp", "-rf", buildPath, targetPath]);
|
||||
});
|
||||
};
|
||||
|
||||
var directoryOp = function(cmd)
|
||||
{
|
||||
var targetPath = cmd[cmd.length - 1];
|
||||
|
||||
if (FILE.isDirectory(targetPath))
|
||||
sudo(["rm", "-rf", targetPath])
|
||||
|
||||
sudo(cmd);
|
||||
};
|
||||
|
||||
var sudo = function(cmd)
|
||||
{
|
||||
if (OS.system(cmd))
|
||||
OS.system(["sudo"].concat(cmd));
|
||||
};
|
||||
|
||||
var describeTask = function(application, task, description)
|
||||
{
|
||||
colorPrint("\n" + application + " " + task, "violet");
|
||||
description.split("\n").forEach(function(line)
|
||||
{
|
||||
stream.print(" " + line);
|
||||
});
|
||||
}
|
||||
|
||||
var colorPrint = function(message, color)
|
||||
{
|
||||
var matches = color.match(/(bold(?: |\+))?(.+)/);
|
||||
|
||||
if (!matches)
|
||||
return;
|
||||
|
||||
message = "\0" + matches[2] + "(" + message + "\0)";
|
||||
|
||||
if (matches[1])
|
||||
message = "\0bold(" + message + "\0)";
|
||||
|
||||
stream.print(message);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
mv "$1"/Framework.j "$1/__project.name__.j"
|
||||
mv "$1"/FrameworkClass.j "$1/__project.name__Class.j"
|
||||
sed -e 's/__filename__/__project.name__.j/' -i '' "$1/__project.name__.j"
|
||||
sed -e 's/__filename__/__project.name__Class.j/' -i '' "$1/__project.name__Class.j"
|
||||
@@ -88,6 +88,20 @@ or
|
||||
This is a handy shortcut for
|
||||
.Sy --symlink --build.
|
||||
.El
|
||||
.Ss Postinstall scripts
|
||||
After copying the chosen template, replacing placeholders, and installing the frameworks,
|
||||
if
|
||||
.Nm
|
||||
finds a file named "postinstall", it will be executed using
|
||||
.Sy sh.
|
||||
The script receives a single parameter which is the directory of the generated project.
|
||||
Scripts should reference files in the project relative to that directory.
|
||||
.Pp
|
||||
After executing the
|
||||
.Sy postinstall
|
||||
script,
|
||||
.Nm
|
||||
deletes it from the generated project.
|
||||
.\"-----------------------------------------------------------------------------------------
|
||||
.Sh CONFIGURING
|
||||
.\"-----------------------------------------------------------------------------------------
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
@import "NSMenu.j"
|
||||
@import "NSMenuItem.j"
|
||||
@import "NSNibConnector.j"
|
||||
@import "NSNumberFormatter.j"
|
||||
@import "NSObjectController.j"
|
||||
@import "NSOutlineView.j"
|
||||
@import "NSPopUpButton.j"
|
||||
|
||||
@@ -99,6 +99,8 @@ var NSButtonIsBorderedMask = 0x00800000,
|
||||
self.isa = [CPRadio class];
|
||||
self._radioGroup = [CPRadioGroup new];
|
||||
}
|
||||
|
||||
_themeClass = [[self class] defaultThemeClass];
|
||||
}
|
||||
|
||||
NIB_CONNECTION_EQUIVALENCY_TABLE[[cell UID]] = self;
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
id _objectValue @accessors(readonly, getter=objectValue);
|
||||
CPFont _font @accessors(readonly, getter=font);
|
||||
int _lineBreakMode @accessors(readonly, getter=lineBreakMode);
|
||||
CPFormatter _formatter @accessors(readonly, getter=formatter);
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
@@ -78,6 +79,8 @@
|
||||
|
||||
_objectValue = [aCoder decodeObjectForKey:@"NSContents"];
|
||||
_font = [aCoder decodeObjectForKey:@"NSSupport"];
|
||||
|
||||
_formatter = [aCoder decodeObjectForKey:@"NSFormatter"];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
[self setAction:[aCoder decodeObjectForKey:@"NSAction"]];
|
||||
|
||||
[self setLineBreakMode:[cell lineBreakMode]];
|
||||
|
||||
[self setFormatter:[cell formatter]];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
@@ -3,9 +3,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSNumberFormatter : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -58,6 +58,7 @@
|
||||
_numberOfMajorTickMarks = [cell numberOfMajorTickMarks];
|
||||
|
||||
[self setEditable:[cell isEditable]];
|
||||
[self setEnabled:[cell isEnabled]];
|
||||
[self setContinuous:[cell isContinuous]];
|
||||
|
||||
return self;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* NSNumberFormatter.j
|
||||
* nib2cib
|
||||
*
|
||||
* 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 <Foundation/CPNumberFormatter.j>
|
||||
|
||||
@implementation CPNumberFormatter (CPCoding)
|
||||
|
||||
- (id)NS_initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSNumberFormatter : CPNumberFormatter
|
||||
{
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
return [self NS_initWithCoder:aCoder];
|
||||
}
|
||||
|
||||
- (Class)classForKeyedArchiver
|
||||
{
|
||||
return [CPNumberFormatter class];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -75,6 +75,8 @@ var FILE = require("file"),
|
||||
|
||||
- (void)run
|
||||
{
|
||||
var exitValue = 0;
|
||||
|
||||
try
|
||||
{
|
||||
var options = [self parseOptionsFromArgs:commandLineArgs];
|
||||
@@ -84,13 +86,16 @@ var FILE = require("file"),
|
||||
if (options.watch)
|
||||
[self watchWithOptions:options];
|
||||
else
|
||||
[self convertWithOptions:options inputFile:nil];
|
||||
if (![self convertWithOptions:options inputFile:nil])
|
||||
exitValue = 2;
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
CPLog.fatal([self exceptionReason:anException]);
|
||||
OS.exit(1);
|
||||
exitValue = 1;
|
||||
}
|
||||
|
||||
OS.exit(exitValue);
|
||||
}
|
||||
|
||||
- (BOOL)convertWithOptions:(JSObject)options inputFile:(CPString)inputFile
|
||||
|
||||
Regular → Executable
Reference in New Issue
Block a user