diff --git a/AppKit/CPAccordionView.j b/AppKit/CPAccordionView.j
index c4a6e6e49..3f28cfa3c 100644
--- a/AppKit/CPAccordionView.j
+++ b/AppKit/CPAccordionView.j
@@ -227,7 +227,7 @@ var secondItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"secondSection
{
var indexSet = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, _items.length)];
- [indexSet removeIndexes:_expandedIndexes];
+ [indexSet removeIndexes:_expandedItemIndexes];
return indexSet;
}
diff --git a/AppKit/CPAlert.j b/AppKit/CPAlert.j
index 896789cdd..372240fd6 100644
--- a/AppKit/CPAlert.j
+++ b/AppKit/CPAlert.j
@@ -37,6 +37,9 @@
@import "CPPanel.j"
@import "CPTextField.j"
+@class CPCheckBox
+
+
/*
@global
@group CPAlertStyle
diff --git a/AppKit/CPApplication.j b/AppKit/CPApplication.j
index 46d2347bd..5b520a121 100644
--- a/AppKit/CPApplication.j
+++ b/AppKit/CPApplication.j
@@ -33,6 +33,9 @@
@import "CPCibLoading.j"
@import "CPPlatform.j"
+@class CPCursor
+@class CPColorPanel
+
var CPMainCibFile = @"CPMainCibFile",
CPMainCibFileHumanFriendly = @"Main cib file base name",
@@ -407,7 +410,7 @@ CPRunContinuesResponse = -1002;
}
-- (void)_documentController:(NSDocumentController *)docController didCloseAll:(BOOL)didCloseAll context:(Object)info
+- (void)_documentController:(CPDocumentController)docController didCloseAll:(BOOL)didCloseAll context:(Object)info
{
// callback method for terminate:
if (didCloseAll)
diff --git a/AppKit/CPArrayController.j b/AppKit/CPArrayController.j
index dc13ae452..f907ee032 100644
--- a/AppKit/CPArrayController.j
+++ b/AppKit/CPArrayController.j
@@ -964,7 +964,7 @@
// 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];
+ var contentIndex = [_contentObject indexOfObjectIdenticalTo:object];
[_contentObject removeObjectAtIndex:contentIndex];
}
[arrangedObjects removeObjectAtIndex:anIndex];
@@ -1093,7 +1093,8 @@
isCompound = [self handlesContentAsCompoundValue],
dotIndex = keyPath.lastIndexOf("."),
firstPart = dotIndex !== CPNotFound ? keyPath.substring(0, dotIndex) : nil,
- isSelectionProxy = firstPart && [[destination valueForKeyPath:firstPart] isKindOfClass:CPControllerSelectionProxy];
+ isSelectionProxy = firstPart && [[destination valueForKeyPath:firstPart] isKindOfClass:CPControllerSelectionProxy],
+ newValue;
if (!isCompound && !isSelectionProxy)
{
diff --git a/AppKit/CPBox.j b/AppKit/CPBox.j
index 5c2d906e3..10ba4ac00 100644
--- a/AppKit/CPBox.j
+++ b/AppKit/CPBox.j
@@ -492,16 +492,17 @@ CPBelowBottom = 6;
- (void)_drawLineBorderInRect:(CGRect)aRect
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
- sides = [CPMinYEdge, CPMaxXEdge, CPMaxYEdge, CPMinXEdge],
- sideGray = 190.0 / 255.0,
- grays = [142.0 / 255.0, sideGray, sideGray, sideGray],
+ cornerRadius = [self cornerRadius],
borderWidth = [self borderWidth];
- while (borderWidth--)
- aRect = CPDrawTiledRects(aRect, aRect, sides, grays);
+ aRect = CGRectInset(aRect, borderWidth / 2.0, borderWidth / 2.0);
CGContextSetFillColor(context, [self fillColor]);
- CGContextFillRect(context, aRect);
+ CGContextSetStrokeColor(context, [self borderColor]);
+
+ CGContextSetLineWidth(context, borderWidth);
+ CGContextFillRoundedRectangleInRect(context, aRect, cornerRadius, YES, YES, YES, YES);
+ CGContextStrokeRoundedRectangleInRect(context, aRect, cornerRadius, YES, YES, YES, YES);
}
- (void)_drawBezelBorderInRect:(CGRect)aRect
diff --git a/AppKit/CPButton.j b/AppKit/CPButton.j
index a4826b72a..8b8148808 100644
--- a/AppKit/CPButton.j
+++ b/AppKit/CPButton.j
@@ -26,6 +26,8 @@
@import "CPControl.j"
@import "CPStringDrawing.j"
+@global CPWindowAbove
+@global CPWindowBelow
/* @group CPBezelStyle */
@@ -989,5 +991,5 @@ var CPButtonImageKey = @"CPButtonImageKey",
@end
-@import "CPCheckBox.j"
-@import "CPRadio.j"
+//@import "CPCheckBox.j"
+//@import "CPRadio.j"
diff --git a/AppKit/CPButtonBar.j b/AppKit/CPButtonBar.j
index ab0b1a212..4e0b4dd4a 100644
--- a/AppKit/CPButtonBar.j
+++ b/AppKit/CPButtonBar.j
@@ -1,6 +1,10 @@
@import "CPView.j"
+@class CPSplitView
+
+@global CPPopUpButtonStatePullsDown
+
@implementation CPButtonBar : CPView
{
diff --git a/AppKit/CPClipView.j b/AppKit/CPClipView.j
index 437a210d9..72d4311a7 100644
--- a/AppKit/CPClipView.j
+++ b/AppKit/CPClipView.j
@@ -22,6 +22,7 @@
@import "CPView.j"
+@class CPScrollView
/*!
@ingroup appkit
diff --git a/AppKit/CPColor.j b/AppKit/CPColor.j
index 8a0ed9e02..36e852291 100644
--- a/AppKit/CPColor.j
+++ b/AppKit/CPColor.j
@@ -21,6 +21,7 @@
*/
@import
-CPToolbarItemVisibilityPriorityStandard
-CPToolbarItemVisibilityPriorityLow
-CPToolbarItemVisibilityPriorityHigh
-CPToolbarItemVisibilityPriorityUser
-
-*/
-- (int)visibilityPriority
-{
- return _visibilityPriority;
-}
-
-/*!
- Sets the item's visibility priority. The value must be one of:
-
-CPToolbarItemVisibilityPriorityStandard
-CPToolbarItemVisibilityPriorityLow
-CPToolbarItemVisibilityPriorityHigh
-CPToolbarItemVisibilityPriorityUser
-
- @param aVisiblityPriority the priority
-*/
-- (void)setVisibilityPriority:(int)aVisibilityPriority
-{
- _visibilityPriority = aVisibilityPriority;
-}
-
-- (void)validate
-{
- var action = [self action],
- target = [self target];
-
- // View items do not do any target-action analysis.
- if (_view)
- {
- if ([target respondsToSelector:@selector(validateToolbarItem:)])
- {
- var shouldBeEnabled = [target validateToolbarItem:self];
- if (_isEnabled !== shouldBeEnabled)
- [self setEnabled:shouldBeEnabled];
- }
-
- return;
- }
-
- if (!action)
- {
- if (_isEnabled)
- return [self setEnabled:NO];
- return;
- }
-
- if (target && ![target respondsToSelector:action])
- {
- if (_isEnabled)
- return [self setEnabled:NO];
- return;
- }
-
- target = [CPApp targetForAction:action to:target from:self];
-
- if (!target)
- {
- if (_isEnabled)
- return [self setEnabled:NO];
- return;
- }
-
- if ([target respondsToSelector:@selector(validateToolbarItem:)])
- {
- var shouldBeEnabled = [target validateToolbarItem:self];
- if (_isEnabled !== shouldBeEnabled)
- [self setEnabled:shouldBeEnabled];
- }
- else
- {
- if (!_isEnabled)
- [self setEnabled:YES];
- }
-}
-
-- (BOOL)autovalidates
-{
- return _autovalidates;
-}
-
-- (void)setAutovalidates:(BOOL)shouldAutovalidate
-{
- _autovalidates = !!shouldAutovalidate;
-}
-
-@end
-
-var CPToolbarItemItemIdentifierKey = @"CPToolbarItemItemIdentifierKey",
- CPToolbarItemLabelKey = @"CPToolbarItemLabelKey",
- CPToolbarItemPaletteLabelKey = @"CPToolbarItemPaletteLabelKey",
- CPToolbarItemToolTipKey = @"CPToolbarItemToolTipKey",
- CPToolbarItemTagKey = @"CPToolbarItemTagKey",
- CPToolbarItemTargetKey = @"CPToolbarItemTargetKey",
- CPToolbarItemActionKey = @"CPToolbarItemActionKey",
- CPToolbarItemEnabledKey = @"CPToolbarItemEnabledKey",
- CPToolbarItemImageKey = @"CPToolbarItemImageKey",
- CPToolbarItemAlternateImageKey = @"CPToolbarItemAlternateImageKey",
- CPToolbarItemViewKey = @"CPToolbarItemViewKey",
- CPToolbarItemMinSizeKey = @"CPToolbarItemMinSizeKey",
- CPToolbarItemMaxSizeKey = @"CPToolbarItemMaxSizeKey",
- CPToolbarItemVisibilityPriorityKey = @"CPToolbarItemVisibilityPriorityKey",
- CPToolbarItemAutovalidatesKey = @"CPToolbarItemAutovalidatesKey";
-
-@implementation CPToolbarItem (CPCoding)
-
-- (id)initWithCoder:(CPCoder)aCoder
-{
- self = [super init];
-
- if (self)
- {
- _itemIdentifier = [aCoder decodeObjectForKey:CPToolbarItemItemIdentifierKey];
-
- _minSize = [aCoder decodeSizeForKey:CPToolbarItemMinSizeKey];
- _maxSize = [aCoder decodeSizeForKey:CPToolbarItemMaxSizeKey];
-
- [self setLabel:[aCoder decodeObjectForKey:CPToolbarItemLabelKey]];
- [self setPaletteLabel:[aCoder decodeObjectForKey:CPToolbarItemPaletteLabelKey]];
- [self setToolTip:[aCoder decodeObjectForKey:CPToolbarItemToolTipKey]];
-
- [self setTag:[aCoder decodeObjectForKey:CPToolbarItemTagKey]];
- [self setTarget:[aCoder decodeObjectForKey:CPToolbarItemTargetKey]];
- [self setAction:CPSelectorFromString([aCoder decodeObjectForKey:CPToolbarItemActionKey])];
-
- [self setEnabled:[aCoder decodeBoolForKey:CPToolbarItemEnabledKey]];
-
- [self setImage:[aCoder decodeObjectForKey:CPToolbarItemImageKey]];
- [self setAlternateImage:[aCoder decodeObjectForKey:CPToolbarItemAlternateImageKey]];
-
- [self setView:[aCoder decodeObjectForKey:CPToolbarItemViewKey]];
-
- [self setVisibilityPriority:[aCoder decodeIntForKey:CPToolbarItemVisibilityPriorityKey]];
- [self setAutovalidates:[aCoder decodeBoolForKey:CPToolbarItemAutovalidatesKey]];
- }
-
- return self;
-}
-
-- (void)encodeWithCoder:(CPCoder)aCoder
-{
- [aCoder encodeObject:_itemIdentifier forKey:CPToolbarItemItemIdentifierKey];
-
- [aCoder encodeObject:[self label] forKey:CPToolbarItemLabelKey];
- [aCoder encodeObject:[self paletteLabel] forKey:CPToolbarItemPaletteLabelKey];
-
- [aCoder encodeObject:[self toolTip] forKey:CPToolbarItemToolTipKey];
-
- [aCoder encodeObject:[self tag] forKey:CPToolbarItemTagKey];
- [aCoder encodeObject:[self target] forKey:CPToolbarItemTargetKey];
- [aCoder encodeObject:[self action] forKey:CPToolbarItemActionKey];
-
- [aCoder encodeObject:[self isEnabled] forKey:CPToolbarItemEnabledKey];
-
- [aCoder encodeObject:[self image] forKey:CPToolbarItemImageKey];
- [aCoder encodeObject:[self alternateImage] forKey:CPToolbarItemAlternateImageKey];
-
- [aCoder encodeObject:[self view] forKey:CPToolbarItemViewKey];
-
- [aCoder encodeSize:[self minSize] forKey:CPToolbarItemMinSizeKey];
- [aCoder encodeSize:[self maxSize] forKey:CPToolbarItemMaxSizeKey];
-
- [aCoder encodeObject:[self visibilityPriority] forKey:CPToolbarItemVisibilityPriorityKey];
- [aCoder encodeBool:[self autovalidates] forKey:CPToolbarItemAutovalidatesKey];
-}
-
-@end
-
-@implementation CPToolbarItem (CPCopying)
-
-- (id)copy
-{
- var copy = [[[self class] alloc] initWithItemIdentifier:_itemIdentifier];
-
- if (_view)
- [copy setView:[CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:_view]]];
-
- [copy _setToolbar:_toolbar];
-
- [copy setLabel:_label];
- [copy setPaletteLabel:_paletteLabel];
- [copy setToolTip:[self toolTip]];
-
- [copy setTag:[self tag]];
- [copy setTarget:[self target]];
- [copy setAction:[self action]];
-
- [copy setEnabled:[self isEnabled]];
-
- [copy setImage:[self image]];
- [copy setAlternateImage:[self alternateImage]];
-
- [copy setMinSize:_minSize];
- [copy setMaxSize:_maxSize];
-
- [copy setVisibilityPriority:[self visibilityPriority]];
- [copy setAutovalidates:[self autovalidates]];
-
- return copy;
-}
-
-@end
-
-// Standard toolbar identifiers
-
-@implementation CPToolbarItem (Standard)
-
-/* @ignore */
-+ (CPToolbarItem)_standardItemWithItemIdentifier:(CPString)anItemIdentifier
-{
- switch (anItemIdentifier)
- {
- case CPToolbarSeparatorItemIdentifier: return [_CPToolbarSeparatorItem new];
- case CPToolbarSpaceItemIdentifier: return [_CPToolbarSpaceItem new];
- case CPToolbarFlexibleSpaceItemIdentifier: return [_CPToolbarFlexibleSpaceItem new];
- case CPToolbarShowColorsItemIdentifier: return [_CPToolbarShowColorsItem new];
- case CPToolbarShowFontsItemIdentifier: return nil;
- case CPToolbarCustomizeToolbarItemIdentifier: return nil;
- case CPToolbarPrintItemIdentifier: return nil;
- }
-
- return nil;
-}
-
-@end
+@import "_CPToolbarItem.j"
@import "_CPToolbarFlexibleSpaceItem.j"
@import "_CPToolbarShowColorsItem.j"
@import "_CPToolbarSeparatorItem.j"
diff --git a/AppKit/CPUserDefaultsController.j b/AppKit/CPUserDefaultsController.j
index 6aa98fe7f..0cf110da0 100644
--- a/AppKit/CPUserDefaultsController.j
+++ b/AppKit/CPUserDefaultsController.j
@@ -25,7 +25,12 @@
@import
Each window has a style, which determines how the window is decorated; whether it has a border, a title bar, a resize bar, minimise and close buttons.
- -A window has a frame. This is the frame of the entire window on the screen, including all decorations and borders. The origin of the frame represents its bottom left corner and the frame is expressed in screen coordinates.
- -A window always contains a content view which is the highest level view available for public (application) use. This view fills the area of the window inside any decoration/border. This is the only part of the window that application programmers are allowed to draw in directly.
- -You can convert between view coordinates and window base coordinates using the [CPView -convertPoint:fromView:], [CPView -convertPoint:toView:], [CPView -convertRect:fromView:], and [CPView -convertRect:toView:] methods with a nil view argument. - - @par Delegate Methods - - @delegate -(void)windowDidResize:(CPNotification)notification; - Sent from the notification center when the window has been resized. - @param notification contains information about the resize event - - @delegate -(CPUndoManager)windowWillReturnUndoManager:(CPWindow)window; - Called to obtain the undo manager for a window - @param window the window for which to return the undo manager - @return the window's undo manager - - @delegate -(void)windowDidBecomeMain:(CPNotification)notification; - Sent from the notification center when the delegate's window becomes - the main window. - @param notification contains information about the event - - @delegate -(void)windowDidResignMain:(CPNotification)notification; - Sent from the notification center when the delegate's window has - resigned main window status. - @param notification contains information about the event - - @delegate -(void)windowDidResignKey:(CPNotification)notification; - Sent from the notification center when the delegate's window has - resigned key window status. - @param notification contains information about the event - - @delegate -(BOOL)windowShouldClose:(id)window; - Called when the user tries to close the window. - @param window the window to close - @return \c YES allows the window to close. \c NO - vetoes the close operation and leaves the window open. - - @delegate -(BOOL)windowWillBeginSheet:(CPNotification)notification; - Sent from the notification center before sheet is visible on - the delegate's window. - @param notification contains information about the event - - @delegate -(BOOL)windowDidEndSheet:(CPNotification)notification; - Sent from the notification center when an attached sheet on the - delegate's window has been animated out and is no longer visible. - @param notification contains information about the event -*/ -@implementation CPWindow : CPResponder -{ - CPPlatformWindow _platformWindow; - - int _windowNumber; - unsigned _styleMask; - CGRect _frame; - int _level; - BOOL _isVisible; - BOOL _isMiniaturized; - BOOL _isAnimating; - BOOL _hasShadow; - BOOL _isMovableByWindowBackground; - BOOL _isMovable; - unsigned _shadowStyle; - BOOL _showsResizeIndicator; - - int _positioningMask; - CGRect _positioningScreenRect; - - BOOL _isDocumentEdited; - BOOL _isDocumentSaving; - - CPImageView _shadowView; - - CPView _windowView; - CPView _contentView; - CPView _toolbarView; - - CPArray _mouseEnteredStack; - CPView _leftMouseDownView; - CPView _rightMouseDownView; - - CPToolbar _toolbar; - CPResponder _firstResponder; - CPResponder _initialFirstResponder; - BOOL _hasBecomeKeyWindow; - id _delegate; - - CPString _title; - - BOOL _acceptsMouseMovedEvents; - BOOL _ignoresMouseEvents; - - CPWindowController _windowController; - - CGSize _minSize; - CGSize _maxSize; - - CPUndoManager _undoManager; - CPURL _representedURL; - - CPSet _registeredDraggedTypes; - CPArray _registeredDraggedTypesArray; - CPCountedSet _inclusiveRegisteredDraggedTypes; - - CPButton _defaultButton; - BOOL _defaultButtonEnabled; - - BOOL _autorecalculatesKeyViewLoop; - BOOL _keyViewLoopIsDirty; - - BOOL _sharesChromeWithPlatformWindow; - - // Bridge Support -#if PLATFORM(DOM) - DOMElement _DOMElement; -#endif - - unsigned _autoresizingMask; - - BOOL _delegateRespondsToWindowWillReturnUndoManagerSelector; - - BOOL _isFullPlatformWindow; - _CPWindowFullPlatformWindowSession _fullPlatformWindowSession; - - CPDictionary _sheetContext; - CPWindow _parentView; - BOOL _isSheet; - _CPWindowFrameAnimation _frameAnimation; -} - -- (id)init -{ - return [self initWithContentRect:_CGRectMakeZero() styleMask:CPTitledWindowMask]; -} - -/*! - Initializes the window. The method also takes a style bit mask made up - of any of the following values: -
-CPBorderlessWindowMask -CPTitledWindowMask -CPClosableWindowMask -CPMiniaturizableWindowMask -CPResizableWindowMask -CPTexturedBackgroundWindowMask -- @param aContentRect the size and location of the window in screen space - @param aStyleMask a style mask - @return the initialized window -*/ -- (id)initWithContentRect:(CGRect)aContentRect styleMask:(unsigned int)aStyleMask -{ - self = [super init]; - - if (self) - { - var windowViewClass = [[self class] _windowViewClassForStyleMask:aStyleMask]; - - _frame = [windowViewClass frameRectForContentRect:aContentRect]; - - [self _setSharesChromeWithPlatformWindow:![CPPlatform isBrowser]]; - - if ([CPPlatform isBrowser]) - [self setPlatformWindow:[CPPlatformWindow primaryPlatformWindow]]; - else - { - // give zero sized borderless bridge windows a default size if we're not in the browser so they show up in NativeHost. - if ((aStyleMask & CPBorderlessBridgeWindowMask) && aContentRect.size.width === 0 && aContentRect.size.height === 0) - { - var visibleFrame = [[[CPScreen alloc] init] visibleFrame]; - _frame.size.height = MIN(768.0, visibleFrame.size.height); - _frame.size.width = MIN(1024.0, visibleFrame.size.width); - _frame.origin.x = (visibleFrame.size.width - _frame.size.width) / 2; - _frame.origin.y = (visibleFrame.size.height - _frame.size.height) / 2; - } - [self setPlatformWindow:[[CPPlatformWindow alloc] initWithContentRect:_frame]]; - [self platformWindow]._only = self; - } - - _isFullPlatformWindow = NO; - _registeredDraggedTypes = [CPSet set]; - _registeredDraggedTypesArray = []; - _acceptsMouseMovedEvents = YES; - _isMovable = YES; - - _isSheet = NO; - _sheetContext = nil; - _parentView = nil; - - // Set up our window number. - _windowNumber = [CPApp._windows count]; - CPApp._windows[_windowNumber] = self; - - _styleMask = aStyleMask; - - [self setLevel:CPNormalWindowLevel]; - - _minSize = _CGSizeMake(0.0, 0.0); - _maxSize = _CGSizeMake(1000000.0, 1000000.0); - - // Create our border view which is the actual root of our view hierarchy. - _windowView = [[windowViewClass alloc] initWithFrame:_CGRectMake(0.0, 0.0, _CGRectGetWidth(_frame), _CGRectGetHeight(_frame)) styleMask:aStyleMask]; - - [_windowView _setWindow:self]; - [_windowView setNextResponder:self]; - - [self setMovableByWindowBackground:aStyleMask & CPHUDBackgroundWindowMask]; - - // Create a generic content view. - [self setContentView:[[CPView alloc] initWithFrame:_CGRectMakeZero()]]; - - _firstResponder = self; - -#if PLATFORM(DOM) - _DOMElement = document.createElement("div"); - - _DOMElement.style.position = "absolute"; - _DOMElement.style.visibility = "visible"; - _DOMElement.style.zIndex = 0; - - if (![self _sharesChromeWithPlatformWindow]) - { - CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, _CGRectGetMinX(_frame), _CGRectGetMinY(_frame)); - } - - CPDOMDisplayServerSetStyleSize(_DOMElement, 1, 1); - CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement); -#endif - - [self setNextResponder:CPApp]; - - [self setHasShadow:aStyleMask !== CPBorderlessWindowMask]; - - if (aStyleMask & CPBorderlessBridgeWindowMask) - [self setFullPlatformWindow:YES]; - - _autorecalculatesKeyViewLoop = NO; - _defaultButtonEnabled = YES; - _keyViewLoopIsDirty = NO; - _hasBecomeKeyWindow = NO; - - [self setShowsResizeIndicator:_styleMask & CPResizableWindowMask]; - - [[CPNotificationCenter defaultCenter] addObserver:self - selector:@selector(_didReceiveResizeStyleChange:) - name:CPWindowResizeStyleGlobalChangeNotification - object:nil]; - } - - return self; -} - -- (CPPlatformWindow)platformWindow -{ - return _platformWindow; -} - -/*! - Sets the platform window of the reciver. - This method will first close the reciever, - change the platform window, then reopen the window (if it was originally open). -*/ -- (void)setPlatformWindow:(CPPlatformWindow)aPlatformWindow -{ - var wasVisible = [self isVisible]; - - // we have to close it first, otherwise we get a DOM exception. - if (wasVisible) - [self close]; - - _platformWindow = aPlatformWindow; - [_platformWindow _setTitle:_title window:self]; - - if (wasVisible) - [self orderFront:self]; -} - - -/*! - @ignore -*/ -+ (Class)_windowViewClassForStyleMask:(unsigned)aStyleMask -{ - if (aStyleMask & CPHUDBackgroundWindowMask) - return _CPHUDWindowView; - - else if (aStyleMask === CPBorderlessWindowMask) - return _CPBorderlessWindowView; - - else if (aStyleMask & CPDocModalWindowMask) - return _CPDocModalWindowView; - - else if (aStyleMask & _CPModalWindowMask) - return _CPModalWindowView - - return _CPStandardWindowView; -} - -+ (Class)_windowViewClassForFullPlatformWindowStyleMask:(unsigned)aStyleMask -{ - return _CPBorderlessBridgeWindowView; -} - -- (void)awakeFromCib -{ - // At this time we know the final screen (or browser) size - // and can apply the positioning mask, if any, from the nib. - if (_positioningScreenRect) - { - var actualScreenRect = [CPPlatform isBrowser] ? [_platformWindow contentBounds] : [[self screen] visibleFrame], - frame = [self frame], - origin = frame.origin; - - if (actualScreenRect) - { - if ((_positioningMask & CPWindowPositionFlexibleLeft) && (_positioningMask & CPWindowPositionFlexibleRight)) - { - // Proportional Horizontal. - origin.x *= (actualScreenRect.size.width / _positioningScreenRect.size.width); - } - else if (_positioningMask & CPWindowPositionFlexibleLeft) - { - // Fixed from Right - origin.x += actualScreenRect.size.width - _positioningScreenRect.size.width; - } - else if (_positioningMask & CPWindowPositionFlexibleRight) - { - // Fixed from Left - } - - if ((_positioningMask & CPWindowPositionFlexibleTop) && (_positioningMask & CPWindowPositionFlexibleBottom)) - { - // Proportional Vertical. - origin.y *= (actualScreenRect.size.height / _positioningScreenRect.size.height); - } - else if (_positioningMask & CPWindowPositionFlexibleTop) - { - // Fixed from Bottom - origin.y += actualScreenRect.size.height - _positioningScreenRect.size.height; - } - else if (_positioningMask & CPWindowPositionFlexibleBottom) - { - // Fixed from Top - } - - [self setFrameOrigin:origin]; - } - } - - /* - Calculate the key view loop if necessary. Note that Cocoa does not call recalculateKeyViewLoop when awaking a nib. If a key view loop was set in the cib, we have to chain it to the content view. - */ - if ([self _hasKeyViewLoop:[_contentView subviews]]) - { - var views = [self _viewsSortedByPosition], - count = [views count]; - - // The first view is the content view. - // Find the first subview that has a next key view. - for (var i = 1; i < count; ++i) - { - var view = views[i]; - - if ([view nextKeyView]) - { - [_contentView setNextKeyView:view]; - break; - } - } - } - else - { - // Cooca does NOT call the public method recalculateKeyViewLoop for nibs, - // but it does calculate the loop. - [self _doRecalculateKeyViewLoop]; - } -} - -- (void)_setWindowView:(CPView)aWindowView -{ - if (_windowView === aWindowView) - return; - - var oldWindowView = _windowView; - - _windowView = aWindowView; - - if (oldWindowView) - { - [oldWindowView _setWindow:nil]; - [oldWindowView noteToolbarChanged]; - -#if PLATFORM(DOM) - CPDOMDisplayServerRemoveChild(_DOMElement, oldWindowView._DOMElement); -#endif - } - - if (_windowView) - { -#if PLATFORM(DOM) - CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement); -#endif - - var contentRect = [_contentView convertRect:[_contentView bounds] toView:nil]; - - contentRect.origin = [self convertBaseToGlobal:contentRect.origin]; - - [_windowView _setWindow:self]; - [_windowView setNextResponder:self]; - [_windowView addSubview:_contentView]; - [_windowView setTitle:_title]; - [_windowView noteToolbarChanged]; - [_windowView setShowsResizeIndicator:[self showsResizeIndicator]]; - - [self setFrame:[self frameRectForContentRect:contentRect]]; - } -} - -/*! - Sets the receiver as a full platform window. If you pass YES the CPWindow instance will fill the entire browser content area, - otherwise the CPWindow will be a window inside of your browser window which the user can drag around, and resize (if you allow). - - @param BOOL - YES if the window should fill the browser window, otherwise NO. -*/ -- (void)setFullPlatformWindow:(BOOL)shouldBeFullPlatformWindow -{ - if (![_platformWindow supportsFullPlatformWindows]) - return; - - shouldBeFullPlatformWindow = !!shouldBeFullPlatformWindow; - - if (_isFullPlatformWindow === shouldBeFullPlatformWindow) - return; - - _isFullPlatformWindow = shouldBeFullPlatformWindow; - - if (_isFullPlatformWindow) - { - _fullPlatformWindowSession = _CPWindowFullPlatformWindowSessionMake(_windowView, [self contentRectForFrameRect:[self frame]], [self hasShadow], [self level]); - - var fullPlatformWindowViewClass = [[self class] _windowViewClassForFullPlatformWindowStyleMask:_styleMask], - windowView = [[fullPlatformWindowViewClass alloc] initWithFrame:_CGRectMakeZero() styleMask:_styleMask]; - - [self _setWindowView:windowView]; - - [self setLevel:CPBackgroundWindowLevel]; - [self setHasShadow:NO]; - [self setAutoresizingMask:CPWindowWidthSizable | CPWindowHeightSizable]; - [self setFrame:[_platformWindow visibleFrame]]; - } - else - { - var windowView = _fullPlatformWindowSession.windowView; - - [self _setWindowView:windowView]; - - [self setLevel:_fullPlatformWindowSession.level]; - [self setHasShadow:_fullPlatformWindowSession.hasShadow]; - [self setAutoresizingMask:CPWindowNotSizable]; - - [self setFrame:[windowView frameRectForContentRect:_fullPlatformWindowSession.contentRect]]; - } -} - -/*! - @return BOOL - YES if the CPWindow fills the browser window, otherwise NO. -*/ -- (BOOL)isFullPlatformWindow -{ - return _isFullPlatformWindow; -} - -/*! - Returns the window's style mask. -*/ -- (unsigned)styleMask -{ - return _styleMask; -} - -/*! - Returns the frame rectangle used by a window. - Style masks include: -
- CPBorderlessWindowMask - CPTitledWindowMask - CPClosableWindowMask - CPMiniaturizableWindowMask (NOTE: only available in NativeHost) - CPResizableWindowMask - CPTexturedBackgroundWindowMask - CPBorderlessBridgeWindowMask - CPHUDBackgroundWindowMask -- - @param aContentRect the content rectangle of the window - @param aStyleMask the style mask of the window - @return the matching window's frame rectangle -*/ -+ (CGRect)frameRectForContentRect:(CGRect)aContentRect styleMask:(unsigned)aStyleMask -{ - return [[[self class] _windowViewClassForStyleMask:aStyleMask] frameRectForContentRect:aContentRect]; -} - -/*! - Returns the receiver's content rectangle. A content rectangle does not include toolbars. - @param aFrame the window's frame rectangle -*/ -- (CGRect)contentRectForFrameRect:(CGRect)aFrame -{ - return [_windowView contentRectForFrameRect:aFrame]; -} - -/*! - Retrieves the frame rectangle for this window. - @param aContentRect the window's content rectangle - @return the window's frame rectangle -*/ -- (CGRect)frameRectForContentRect:(CGRect)aContentRect -{ - return [_windowView frameRectForContentRect:aContentRect]; -} - -/*! - Returns the window's frame rectangle -*/ -- (CGRect)frame -{ - return _CGRectMakeCopy(_frame); -} - -/*! - Sets the window's frame rectangle. Also tells the window whether it should animate - the resize operation, and redraw itself if necessary. - @param aFrame the new size and location for the window - @param shouldDisplay whether the window should redraw its views - @param shouldAnimate whether the window resize should be animated. -*/ -- (void)_setClippedFrame:(CGRect)aFrame display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate -{ - aFrame.size.width = MIN(MAX(aFrame.size.width, _minSize.width), _maxSize.width) - aFrame.size.height = MIN(MAX(aFrame.size.height, _minSize.height), _maxSize.height); - [self setFrame:aFrame display:shouldDisplay animate:shouldAnimate]; -} - -/*! - Sets the frame of the window. - - @param aFrame - A CGRect of the new frame for the receiver. - @param shouldDisplay - YES if the window should call setNeedsDisplay otherwise NO. - @param shouldAnimate - YES if the window should animate to it's new size and position, otherwise NO. -*/ -- (void)setFrame:(CGRect)aFrame display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate -{ - aFrame = _CGRectMakeCopy(aFrame); - - var value = aFrame.origin.x, - delta = value - FLOOR(value); - - if (delta) - aFrame.origin.x = value > 0.879 ? CEIL(value) : FLOOR(value); - - value = aFrame.origin.y; - delta = value - FLOOR(value); - - if (delta) - aFrame.origin.y = value > 0.879 ? CEIL(value) : FLOOR(value); - - value = aFrame.size.width; - delta = value - FLOOR(value); - - if (delta) - aFrame.size.width = value > 0.15 ? CEIL(value) : FLOOR(value); - - value = aFrame.size.height; - delta = value - FLOOR(value); - - if (delta) - aFrame.size.height = value > 0.15 ? CEIL(value) : FLOOR(value); - - if (shouldAnimate) - { - [_frameAnimation stopAnimation]; - _frameAnimation = [[_CPWindowFrameAnimation alloc] initWithWindow:self targetFrame:aFrame]; - - [_frameAnimation startAnimation]; - } - else - { - var origin = _frame.origin, - newOrigin = aFrame.origin; - - if (!_CGPointEqualToPoint(origin, newOrigin)) - { - origin.x = newOrigin.x; - origin.y = newOrigin.y; - -#if PLATFORM(DOM) - if (![self _sharesChromeWithPlatformWindow]) - { - CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, origin.x, origin.y); - } -#endif - - [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidMoveNotification object:self]; - } - - var size = _frame.size, - newSize = aFrame.size; - - if (!_CGSizeEqualToSize(size, newSize)) - { - size.width = newSize.width; - size.height = newSize.height; - - [_windowView setFrameSize:size]; - - if (_hasShadow) - [_shadowView setNeedsLayout]; - - if (!_isAnimating) - [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidResizeNotification object:self]; - } - - if ([self _sharesChromeWithPlatformWindow]) - [_platformWindow setContentRect:_frame]; - } -} - -/*! - Sets the window's frame rect. - @param aFrame - The new CGRect of the window. - @param shouldDisplay - YES if the window should call setNeedsDisplay: otherwise NO. -*/ -- (void)setFrame:(CGRect)aFrame display:(BOOL)shouldDisplay -{ - [self _setClippedFrame:aFrame display:shouldDisplay animate:NO]; -} - -/*! - Sets the window's frame rectangle - @param aFrame - The CGRect of the windows new frame -*/ -- (void)setFrame:(CGRect)aFrame -{ - [self _setClippedFrame:aFrame display:YES animate:NO]; -} - -/*! - Sets the window's location. - @param anOrigin the new location for the window -*/ -- (void)setFrameOrigin:(CGPoint)anOrigin -{ - [self _setClippedFrame:_CGRectMake(anOrigin.x, anOrigin.y, _CGRectGetWidth(_frame), _CGRectGetHeight(_frame)) display:YES animate:NO]; - - // reposition sheet - if ([self attachedSheet]) - [self _setAttachedSheetFrameOrigin]; -} - -/*! - Sets the window's size. - @param aSize the new size for the window -*/ -- (void)setFrameSize:(CGSize)aSize -{ - [self _setClippedFrame:_CGRectMake(_CGRectGetMinX(_frame), _CGRectGetMinY(_frame), aSize.width, aSize.height) display:YES animate:NO]; -} - -/*! - Makes the receiver the front most window in the screen ordering. - @param aSender the object that requested this -*/ -- (void)orderFront:(id)aSender -{ -#if PLATFORM(DOM) - // -dw- if a sheet is clicked, the parent window should come up too - if ([self isSheet]) - [_parentView orderFront:self]; - - [_platformWindow orderFront:self]; - [_platformWindow order:CPWindowAbove window:self relativeTo:nil]; -#endif - - if (!CPApp._keyWindow) - [self makeKeyWindow]; - - if ([self isKeyWindow] && (_firstResponder === self || !_firstResponder)) - [self makeFirstResponder:_initialFirstResponder]; - - if (!CPApp._mainWindow) - [self makeMainWindow]; -} - -/* - Makes the receiver the last window in the screen ordering. - @param aSender the object that requested this - @ignore -*/ -- (void)orderBack:(id)aSender -{ - //[_platformWindow order:CPWindowBelow -} - -/*! - Hides the window. - @param the object that requested this -*/ -- (void)orderOut:(id)aSender -{ - if ([self isSheet]) - { - // -dw- as in Cocoa, orderOut: detaches the sheet and animates out - [self._parentView _detachSheetWindow]; - return; - } - -#if PLATFORM(DOM) - if ([self _sharesChromeWithPlatformWindow]) - [_platformWindow orderOut:self]; -#endif - - if ([_delegate respondsToSelector:@selector(windowWillClose:)]) - [_delegate windowWillClose:self]; - -#if PLATFORM(DOM) - [_platformWindow order:CPWindowOut window:self relativeTo:nil]; -#endif - - [self _updateMainAndKeyWindows]; -} - -/*! - Relocates the window in the screen list. - @param aPlace the positioning relative to \c otherWindowNumber - @param otherWindowNumber the window relative to which the receiver should be placed -*/ -- (void)orderWindow:(CPWindowOrderingMode)aPlace relativeTo:(int)otherWindowNumber -{ -#if PLATFORM(DOM) - [_platformWindow order:aPlace window:self relativeTo:CPApp._windows[otherWindowNumber]]; -#endif -} - -/*! - Sets the window's level - @param the window's new level -*/ -- (void)setLevel:(int)aLevel -{ - if (aLevel === _level) - return; - - [_platformWindow moveWindow:self fromLevel:_level toLevel:aLevel]; - - _level = aLevel; - - if ([self _sharesChromeWithPlatformWindow]) - [_platformWindow setLevel:aLevel]; -} - -/*! - Returns the window's current level -*/ -- (int)level -{ - return _level; -} - -/*! - Returns \c YES if the window is visible. It does not mean that the window is not obscured by other windows. -*/ -- (BOOL)isVisible -{ - return _isVisible; -} - -/*! - Globally sets whether windows resize following the legacy style (using a resize thumb - in the bottom right corner), or the modern style (no resize thumb, resizing - can be done on all edges). -*/ -+ (void)setGlobalResizeStyle:(int)aStyle -{ - if (CPWindowResizeStyle === aStyle) - return; - - CPWindowResizeStyle = aStyle; - [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowResizeStyleGlobalChangeNotification object:nil]; -} - -- (void)_didReceiveResizeStyleChange:(CPNotification)aNotification -{ - [_windowView setShowsResizeIndicator:_styleMask & CPResizableWindowMask]; -} - -/*! - Returns the global window resizing style. -*/ -+ (int)globalResizeStyle -{ - return CPWindowResizeStyle; -} - -/*! - Returns \c YES if the window's resize indicator is showing. \c NO otherwise. -*/ -- (BOOL)showsResizeIndicator -{ - return _showsResizeIndicator; -} - -/*! - Sets the window's resize indicator. - @param shouldShowResizeIndicator \c YES sets the window to show its resize indicator. -*/ -- (void)setShowsResizeIndicator:(BOOL)shouldShowResizeIndicator -{ - shouldShowResizeIndicator = !!shouldShowResizeIndicator; - - if (_showsResizeIndicator === shouldShowResizeIndicator) - return; - - _showsResizeIndicator = shouldShowResizeIndicator; - [_windowView setShowsResizeIndicator:[self showsResizeIndicator]]; -} - -/*! - Returns the offset of the window's resize indicator. -*/ -- (CGSize)resizeIndicatorOffset -{ - return [_windowView resizeIndicatorOffset]; -} - -/*! - Sets the offset of the window's resize indicator. - @param aSize the offset for the resize indicator -*/ -- (void)setResizeIndicatorOffset:(CGSize)anOffset -{ - [_windowView setResizeIndicatorOffset:anOffset]; -} - -/*! - Sets the window's content view. The new view will be resized to fit - inside the content rectangle of the window. - @param aView the new content view for the receiver -*/ -- (void)setContentView:(CPView)aView -{ - if (_contentView) - [_contentView removeFromSuperview]; - - var bounds = _CGRectMake(0.0, 0.0, _CGRectGetWidth(_frame), _CGRectGetHeight(_frame)); - - _contentView = aView; - [_contentView setFrame:[self contentRectForFrameRect:bounds]]; - - [_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; - [_windowView addSubview:_contentView]; - - /* - If the initial first responder has been set to something other than - the window, set it to the window because it will no longer be valid. - */ - if (_initialFirstResponder && _initialFirstResponder !== self) - _initialFirstResponder = self; -} - -/*! - Returns the window's current content view. -*/ -- (CPView)contentView -{ - return _contentView; -} - -/*! - Applies an alpha value to the window. - @param aValue the alpha value to apply -*/ -- (void)setAlphaValue:(float)aValue -{ - [_windowView setAlphaValue:aValue]; -} - -/*! - Returns the alpha value of the window. -*/ -- (float)alphaValue -{ - return [_windowView alphaValue]; -} - -/*! - Sets the window's background color. - @param aColor the new color for the background -*/ -- (void)setBackgroundColor:(CPColor)aColor -{ - [_windowView setBackgroundColor:aColor]; -} - -/*! - Returns the window's background color. -*/ -- (CPColor)backgroundColor -{ - return [_windowView backgroundColor]; -} - -/*! - Sets the window's minimum size. If the provided - size is the same as the current minimum size, the method simply returns. - @param aSize the new minimum size for the window -*/ -- (void)setMinSize:(CGSize)aSize -{ - if (_CGSizeEqualToSize(_minSize, aSize)) - return; - - _minSize = _CGSizeMakeCopy(aSize); - - var size = _CGSizeMakeCopy([self frame].size), - needsFrameChange = NO; - - if (size.width < _minSize.width) - { - size.width = _minSize.width; - needsFrameChange = YES; - } - - if (size.height < _minSize.height) - { - size.height = _minSize.height; - needsFrameChange = YES; - } - - if (needsFrameChange) - [self setFrameSize:size]; -} - -/*! - Returns the windows minimum size. -*/ -- (CGSize)minSize -{ - return _minSize; -} - -/*! - Sets the window's maximum size. If the provided - size is the same as the current maximum size, - the method simply returns. - @param aSize the new maximum size -*/ -- (void)setMaxSize:(CGSize)aSize -{ - if (_CGSizeEqualToSize(_maxSize, aSize)) - return; - - _maxSize = _CGSizeMakeCopy(aSize); - - var size = _CGSizeMakeCopy([self frame].size), - needsFrameChange = NO; - - if (size.width > _maxSize.width) - { - size.width = _maxSize.width; - needsFrameChange = YES; - } - - if (size.height > _maxSize.height) - { - size.height = _maxSize.height; - needsFrameChange = YES; - } - - if (needsFrameChange) - [self setFrameSize:size]; -} - -/*! - Returns the window's maximum size. -*/ -- (CGSize)maxSize -{ - return _maxSize; -} - -/*! - Returns \c YES if the window has a drop shadow. \c NO otherwise. -*/ -- (BOOL)hasShadow -{ - return _hasShadow; -} - -- (void)_updateShadow -{ - if ([self _sharesChromeWithPlatformWindow]) - { - if (_shadowView) - { -#if PLATFORM(DOM) - CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement); -#endif - _shadowView = nil; - } - - [_platformWindow setHasShadow:_hasShadow]; - - return; - } - - if (_hasShadow && !_shadowView) - { - _shadowView = [[_CPShadowWindowView alloc] initWithFrame:CGRectMakeZero()]; - - [_shadowView setWindowView:_windowView]; - [_shadowView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; - [_shadowView setNeedsLayout]; - -#if PLATFORM(DOM) - CPDOMDisplayServerInsertBefore(_DOMElement, _shadowView._DOMElement, _windowView._DOMElement); -#endif - } - else if (!_hasShadow && _shadowView) - { -#if PLATFORM(DOM) - CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement); -#endif - _shadowView = nil; - } -} - -/*! - Sets whether the window should have a drop shadow. - @param shouldHaveShadow \c YES to have a drop shadow. -*/ -- (void)setHasShadow:(BOOL)shouldHaveShadow -{ - if (_hasShadow === shouldHaveShadow) - return; - - _hasShadow = shouldHaveShadow; - - [self _updateShadow]; -} - -/*! - Sets the shadow style of the receiver. - Values are: -
- CPWindowShadowStyleStandard - CPWindowShadowStyleMenu - CPWindowShadowStylePanel -- - @param aStyle - The new shadow style of the receiver. -*/ -- (void)setShadowStyle:(unsigned)aStyle -{ - _shadowStyle = aStyle; - - [[self platformWindow] setShadowStyle:_shadowStyle]; -} - -/*! - Sets the delegate for the window. Passing \c nil will just remove the window's current delegate. - @param aDelegate an object to respond to the various delegate methods of CPWindow -*/ -- (void)setDelegate:(id)aDelegate -{ - var defaultCenter = [CPNotificationCenter defaultCenter]; - - [defaultCenter removeObserver:_delegate name:CPWindowDidResignKeyNotification object:self]; - [defaultCenter removeObserver:_delegate name:CPWindowDidBecomeKeyNotification object:self]; - [defaultCenter removeObserver:_delegate name:CPWindowDidBecomeMainNotification object:self]; - [defaultCenter removeObserver:_delegate name:CPWindowDidResignMainNotification object:self]; - [defaultCenter removeObserver:_delegate name:CPWindowDidMoveNotification object:self]; - [defaultCenter removeObserver:_delegate name:CPWindowDidResizeNotification object:self]; - [defaultCenter removeObserver:_delegate name:CPWindowWillBeginSheetNotification object:self]; - [defaultCenter removeObserver:_delegate name:CPWindowDidEndSheetNotification object:self]; - - _delegate = aDelegate; - _delegateRespondsToWindowWillReturnUndoManagerSelector = [_delegate respondsToSelector:@selector(windowWillReturnUndoManager:)]; - - if ([_delegate respondsToSelector:@selector(windowDidResignKey:)]) - [defaultCenter - addObserver:_delegate - selector:@selector(windowDidResignKey:) - name:CPWindowDidResignKeyNotification - object:self]; - - if ([_delegate respondsToSelector:@selector(windowDidBecomeKey:)]) - [defaultCenter - addObserver:_delegate - selector:@selector(windowDidBecomeKey:) - name:CPWindowDidBecomeKeyNotification - object:self]; - - if ([_delegate respondsToSelector:@selector(windowDidBecomeMain:)]) - [defaultCenter - addObserver:_delegate - selector:@selector(windowDidBecomeMain:) - name:CPWindowDidBecomeMainNotification - object:self]; - - if ([_delegate respondsToSelector:@selector(windowDidResignMain:)]) - [defaultCenter - addObserver:_delegate - selector:@selector(windowDidResignMain:) - name:CPWindowDidResignMainNotification - object:self]; - - if ([_delegate respondsToSelector:@selector(windowDidMove:)]) - [defaultCenter - addObserver:_delegate - selector:@selector(windowDidMove:) - name:CPWindowDidMoveNotification - object:self]; - - if ([_delegate respondsToSelector:@selector(windowDidResize:)]) - [defaultCenter - addObserver:_delegate - selector:@selector(windowDidResize:) - name:CPWindowDidResizeNotification - object:self]; - - if ([_delegate respondsToSelector:@selector(windowWillBeginSheet:)]) - [defaultCenter - addObserver:_delegate - selector:@selector(windowWillBeginSheet:) - name:CPWindowWillBeginSheetNotification - object:self]; - - if ([_delegate respondsToSelector:@selector(windowDidEndSheet:)]) - [defaultCenter - addObserver:_delegate - selector:@selector(windowDidEndSheet:) - name:CPWindowDidEndSheetNotification - object:self]; -} - -/*! - Returns window's delegate -*/ -- (id)delegate -{ - return _delegate; -} - -/*! - Sets the window's controller - @param aWindowController a window controller -*/ -- (void)setWindowController:(CPWindowController)aWindowController -{ - _windowController = aWindowController; -} - -/*! - Returns the window's controller. -*/ -- (CPWindowController)windowController -{ - return _windowController; -} - -- (void)doCommandBySelector:(SEL)aSelector -{ - if ([_delegate respondsToSelector:aSelector]) - [_delegate performSelector:aSelector]; - else - [super doCommandBySelector:aSelector]; -} - -- (BOOL)acceptsFirstResponder -{ - return NO; -} - -- (CPView)initialFirstResponder -{ - return _initialFirstResponder; -} - -- (void)setInitialFirstResponder:(CPView)aView -{ - _initialFirstResponder = aView; -} - -- (void)_setupFirstResponder -{ - /* - When the window is first made the key window, if the first responder is the window, use the initial first responder if there is one. If there is a first responder and it is not the window, ignore the initial first responder. - */ - if (!_hasBecomeKeyWindow) - { - if (_firstResponder === self) - { - if (_initialFirstResponder) - [self makeFirstResponder:_initialFirstResponder]; - else - { - // Make the first valid key view the first responder - var view = [_contentView nextValidKeyView]; - - if (view) - [self makeFirstResponder:view]; - } - - return; - } - } - - if (_firstResponder) - [self makeFirstResponder:_firstResponder]; -} - -/*! - Attempts to make the \c aResponder the first responder. Before trying - to make it the first responder, the receiver will ask the current first responder - to resign its first responder status. If it resigns, it will ask - \c aResponder accept first responder, then finally tell it to become first responder. - @return \c YES if the attempt was successful. \c NO otherwise. -*/ -- (BOOL)makeFirstResponder:(CPResponder)aResponder -{ - if (_firstResponder === aResponder) - return YES; - - if (![_firstResponder resignFirstResponder]) - return NO; - - if (!aResponder || ![aResponder acceptsFirstResponder] || ![aResponder becomeFirstResponder]) - { - _firstResponder = self; - - return NO; - } - - _firstResponder = aResponder; - - [[CPNotificationCenter defaultCenter] postNotificationName:_CPWindowDidChangeFirstResponderNotification object:self]; - - return YES; -} - -/*! - Returns the window's current first responder. -*/ -- (CPResponder)firstResponder -{ - return _firstResponder; -} - -- (BOOL)acceptsMouseMovedEvents -{ - return _acceptsMouseMovedEvents; -} - -- (void)setAcceptsMouseMovedEvents:(BOOL)shouldAcceptMouseMovedEvents -{ - _acceptsMouseMovedEvents = shouldAcceptMouseMovedEvents; -} - -- (BOOL)ignoresMouseEvents -{ - return _ignoresMouseEvents; -} - -- (void)setIgnoresMouseEvents:(BOOL)shouldIgnoreMouseEvents -{ - _ignoresMouseEvents = shouldIgnoreMouseEvents; -} - -- (void)_mouseExitedResizeRect -{ - [[CPCursor arrowCursor] set]; -} - -// Managing Titles - -/*! - Returns the window's title bar string -*/ -- (CPString)title -{ - return _title; -} - -/*! - Sets the window's title bar string -*/ -- (void)setTitle:(CPString)aTitle -{ - _title = aTitle; - - [_windowView setTitle:aTitle]; - [_platformWindow _setTitle:_title window:self]; - - [self _synchronizeMenuBarTitleWithWindowTitle]; -} - -/*! - Sets the title bar to represent a file path -*/ -- (void)setTitleWithRepresentedFilename:(CPString)aFilePath -{ - [self setRepresentedFilename:aFilePath]; - [self setTitle:[aFilePath lastPathComponent]]; -} - -/*! - Sets the path to the file the receiver represents -*/ -- (void)setRepresentedFilename:(CPString)aFilePath -{ - // FIXME: urls vs filepaths and all. - [self setRepresentedURL:aFilePath]; -} - -/*! - Returns the path to the file the receiver represents -*/ -- (CPString)representedFilename -{ - return _representedURL; -} - -/*! - Sets the URL that the receiver represents -*/ -- (void)setRepresentedURL:(CPURL)aURL -{ - _representedURL = aURL; -} - -/*! - Returns the URL that the receiver represents -*/ -- (CPURL)representedURL -{ - return _representedURL; -} - -- (CPScreen)screen -{ - return [[CPScreen alloc] init]; -} - -// Moving - -/*! - Sets whether the window can be moved by dragging its background. The default is based on the window style. - @param shouldBeMovableByWindowBackground \c YES makes the window move from a background drag. -*/ -- (void)setMovableByWindowBackground:(BOOL)shouldBeMovableByWindowBackground -{ - _isMovableByWindowBackground = shouldBeMovableByWindowBackground; -} - -/*! - Returns \c YES if the window can be moved by dragging its background. -*/ -- (BOOL)isMovableByWindowBackground -{ - return _isMovableByWindowBackground; -} - -/*! - Sets whether the window can be moved. - @param shouldBeMovable \c YES makes the window movable. -*/ -- (void)setMovable:(BOOL)shouldBeMovable -{ - _isMovable = shouldBeMovable; -} - -/*! - Returns \c YES if the window can be moved. -*/ -- (void)isMovable -{ - return _isMovable; -} - -/*! - Sets the window location to be the center of the screen -*/ -- (void)center -{ - if (_isFullPlatformWindow) - return; - - var size = [self frame].size, - containerSize = [CPPlatform isBrowser] ? [_platformWindow contentBounds].size : [[self screen] visibleFrame].size; - - var origin = _CGPointMake((containerSize.width - size.width) / 2.0, (containerSize.height - size.height) / 2.0); - - if (origin.x < 0.0) - origin.x = 0.0; - - if (origin.y < 0.0) - origin.y = 0.0; - - [self setFrameOrigin:origin]; -} - -/*! - Dispatches events that are sent to it from CPApplication. - @param anEvent the event to be dispatched -*/ -- (void)sendEvent:(CPEvent)anEvent -{ - var type = [anEvent type], - point = [anEvent locationInWindow]; - - // If a sheet is attached events get filtered here. - // It is not clear what events should be passed to the view, perhaps all? - // CPLeftMouseDown is needed for window moving and resizing to work. - // CPMouseMoved is needed for rollover effects on title bar buttons. - var sheet = [self attachedSheet]; - - if (sheet) - { - switch (type) - { - case CPLeftMouseDown: - [_windowView mouseDown:anEvent]; - - // -dw- if the window is clicked, the sheet should come to front, and become key, - // and the window should be immediately behind - [sheet makeKeyAndOrderFront:self]; - break; - case CPMouseMoved: - [_windowView mouseMoved:anEvent]; - break; - } - - return; - } - - switch (type) - { - case CPFlagsChanged: - return [[self firstResponder] flagsChanged:anEvent]; - - case CPKeyUp: - return [[self firstResponder] keyUp:anEvent]; - - case CPKeyDown: - if ([anEvent charactersIgnoringModifiers] === CPTabCharacter) - { - if ([anEvent modifierFlags] & CPShiftKeyMask) - [self selectPreviousKeyView:self]; - else - [self selectNextKeyView:self]; -#if PLATFORM(DOM) - // Make sure the browser doesn't try to do its own tab handling. - // This is important or the browser might blur the shared text field or token field input field, - // even that we just moved it to a new first responder. - [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO] -#endif - return; - } - else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter) - { - var didTabBack = [self selectPreviousKeyView:self]; - - if (didTabBack) - { -#if PLATFORM(DOM) - // Make sure the browser doesn't try to do its own tab handling. - // This is important or the browser might blur the shared text field or token field input field, - // even that we just moved it to a new first responder. - [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO] -#endif - } - - return didTabBack; - } - - [[self firstResponder] keyDown:anEvent]; - - // Trigger the default button if needed - // FIXME: Is this only applicable in a sheet? See isse: #722. - if (![self disableKeyEquivalentForDefaultButton]) - { - var defaultButton = [self defaultButton], - keyEquivalent = [defaultButton keyEquivalent], - modifierMask = [defaultButton keyEquivalentModifierMask]; - - if ([anEvent _triggersKeyEquivalent:keyEquivalent withModifierMask:modifierMask]) - [[self defaultButton] performClick:self]; - } - - return; - - case CPScrollWheel: - return [[_windowView hitTest:point] scrollWheel:anEvent]; - - case CPLeftMouseUp: - case CPRightMouseUp: - var hitTestedView = _leftMouseDownView, - selector = type == CPRightMouseUp ? @selector(rightMouseUp:) : @selector(mouseUp:); - - if (!hitTestedView) - hitTestedView = [_windowView hitTest:point]; - - [hitTestedView performSelector:selector withObject:anEvent]; - - _leftMouseDownView = nil; - - return; - - case CPLeftMouseDown: - case CPRightMouseDown: - _leftMouseDownView = [_windowView hitTest:point]; - - if (_leftMouseDownView != _firstResponder && [_leftMouseDownView acceptsFirstResponder]) - [self makeFirstResponder:_leftMouseDownView]; - - [CPApp activateIgnoringOtherApps:YES]; - - var theWindow = [anEvent window], - selector = type == CPRightMouseDown ? @selector(rightMouseDown:) : @selector(mouseDown:); - - if ([theWindow isKeyWindow] || [theWindow becomesKeyOnlyIfNeeded] && ![_leftMouseDownView needsPanelToBecomeKey]) - return [_leftMouseDownView performSelector:selector withObject:anEvent]; - else - { - // FIXME: delayed ordering? - [self makeKeyAndOrderFront:self]; - - if ([_leftMouseDownView acceptsFirstMouse:anEvent]) - return [_leftMouseDownView performSelector:selector withObject:anEvent]; - } - break; - - case CPLeftMouseDragged: - case CPRightMouseDragged: - if (!_leftMouseDownView) - return [[_windowView hitTest:point] mouseDragged:anEvent]; - - var selector; - - if (type == CPRightMouseDragged) - { - selector = @selector(rightMouseDragged:) - if (![_leftMouseDownView respondsToSelector:selector]) - selector = nil; - } - - if (!selector) - selector = @selector(mouseDragged:) - - return [_leftMouseDownView performSelector:selector withObject:anEvent]; - - case CPMouseMoved: - [_windowView setCursorForLocation:point resizing:NO]; - - if (!_acceptsMouseMovedEvents) - return; - - if (!_mouseEnteredStack) - _mouseEnteredStack = []; - - var hitTestView = [_windowView hitTest:point]; - - if ([_mouseEnteredStack count] && [_mouseEnteredStack lastObject] === hitTestView) - return [hitTestView mouseMoved:anEvent]; - - var view = hitTestView, - mouseEnteredStack = []; - - while (view) - { - mouseEnteredStack.unshift(view); - - view = [view superview]; - } - - var deviation = MIN(_mouseEnteredStack.length, mouseEnteredStack.length); - - while (deviation--) - if (_mouseEnteredStack[deviation] === mouseEnteredStack[deviation]) - break; - - var index = deviation + 1, - count = _mouseEnteredStack.length; - - if (index < count) - { - var event = [CPEvent mouseEventWithType:CPMouseExited location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0]; - - for (; index < count; ++index) - [_mouseEnteredStack[index] mouseExited:event]; - } - - index = deviation + 1; - count = mouseEnteredStack.length; - - if (index < count) - { - var event = [CPEvent mouseEventWithType:CPMouseEntered location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0]; - - for (; index < count; ++index) - [mouseEnteredStack[index] mouseEntered:event]; - } - - _mouseEnteredStack = mouseEnteredStack; - - [hitTestView mouseMoved:anEvent]; - } -} - -/*! - Returns the window's number in the desktop's screen list -*/ -- (int)windowNumber -{ - return _windowNumber; -} - -/*! - Called when the receiver should become the key window. It sends - the \c -becomeKeyWindow message to the first responder if it responds, - and posts \c CPWindowDidBecomeKeyNotification. -*/ -- (void)becomeKeyWindow -{ - CPApp._keyWindow = self; - - if (_firstResponder !== self && [_firstResponder respondsToSelector:@selector(becomeKeyWindow)]) - [_firstResponder becomeKeyWindow]; - - if (!_hasBecomeKeyWindow) - { - // The first time a window is loaded, if it does not have a key view loop - // established, calculate it now. - if (![self _hasKeyViewLoop:[_contentView subviews]]) - [self recalculateKeyViewLoop]; - } - - [self _setupFirstResponder]; - _hasBecomeKeyWindow = YES; - - [_windowView noteKeyWindowStateChanged]; - - [[CPNotificationCenter defaultCenter] - postNotificationName:CPWindowDidBecomeKeyNotification - object:self]; -} - -/*! - Determines if the window can become the key window. - @return \c YES means the window can become the key window. -*/ -- (BOOL)canBecomeKeyWindow -{ - /* - In Cocoa only titled windows return YES here by default. But the main browser - window in Cappuccino doesn't have a title bar even that it's both titled and - resizable, so we return YES when isFullPlatformWindow too. - - Note that Cocoa will return NO for a non-titled, resizable window. The Cocoa documention - says it will return YES if there is a "resize bar", but in practice - that is not the same as the resizable mask. - */ - return (_styleMask & CPTitledWindowMask) || [self isFullPlatformWindow] || _isSheet; -} - -/*! - Returns \c YES if the window is the key window. -*/ -- (BOOL)isKeyWindow -{ - return [CPApp keyWindow] == self; -} - -/*! - Makes the window the key window and brings it to the front of the screen list. - @param aSender the object requesting this -*/ -- (void)makeKeyAndOrderFront:(id)aSender -{ - [self orderFront:self]; - - [self makeKeyWindow]; - [self makeMainWindow]; -} - -/*! - Makes this window the key window. -*/ -- (void)makeKeyWindow -{ - if ([CPApp keyWindow] === self || ![self canBecomeKeyWindow]) - return; - - [[CPApp keyWindow] resignKeyWindow]; - [self becomeKeyWindow]; -} - -/*! - Causes the window to resign it's key window status. -*/ -- (void)resignKeyWindow -{ - if (_firstResponder !== self && [_firstResponder respondsToSelector:@selector(resignKeyWindow)]) - [_firstResponder resignKeyWindow]; - - if (CPApp._keyWindow === self) - CPApp._keyWindow = nil; - - [_windowView noteKeyWindowStateChanged]; - - [[CPNotificationCenter defaultCenter] - postNotificationName:CPWindowDidResignKeyNotification - object:self]; -} - -/*! - Initiates a drag operation from the receiver to another view that accepts dragged data. - @param anImage the image to be dragged - @param aLocation the lower-left corner coordinate of \c anImage - @param mouseOffset the distance from the \c -mouseDown: location and the current location - @param anEvent the \c -mouseDown: that triggered the drag - @param aPasteboard the pasteboard that holds the drag data - @param aSourceObject the drag operation controller - @param slideBack Whether the image should 'slide back' if the drag is rejected -*/ -- (void)dragImage:(CPImage)anImage at:(CGPoint)imageLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack -{ - [[CPDragServer sharedDragServer] dragImage:anImage fromWindow:self at:[self convertBaseToGlobal:imageLocation] offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack]; -} - -- (void)_noteRegisteredDraggedTypes:(CPSet)pasteboardTypes -{ - if (!pasteboardTypes) - return; - - if (!_inclusiveRegisteredDraggedTypes) - _inclusiveRegisteredDraggedTypes = [CPCountedSet set]; - - [_inclusiveRegisteredDraggedTypes unionSet:pasteboardTypes]; -} - -- (void)_noteUnregisteredDraggedTypes:(CPSet)pasteboardTypes -{ - if (!pasteboardTypes) - return; - - [_inclusiveRegisteredDraggedTypes minusSet:pasteboardTypes]; - - if ([_inclusiveRegisteredDraggedTypes count] === 0) - _inclusiveRegisteredDraggedTypes = nil; -} - -/*! - Initiates a drag operation from the receiver to another view that accepts dragged data. - @param aView the view to be dragged - @param aLocation the lower-left corner coordinate of \c aView - @param mouseOffset the distance from the \c -mouseDown: location and the current location - @param anEvent the \c -mouseDown: that triggered the drag - @param aPasteboard the pasteboard that holds the drag data - @param aSourceObject the drag operation controller - @param slideBack Whether the view should 'slide back' if the drag is rejected -*/ -- (void)dragView:(CPView)aView at:(CGPoint)viewLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack -{ - [[CPDragServer sharedDragServer] dragView:aView fromWindow:self at:[self convertBaseToGlobal:viewLocation] offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack]; -} - -/*! - Sets the receiver's list of acceptable data types for a dragging operation. - @param pasteboardTypes an array of CPPasteboards -*/ -- (void)registerForDraggedTypes:(CPArray)pasteboardTypes -{ - if (!pasteboardTypes) - return; - - [self _noteUnregisteredDraggedTypes:_registeredDraggedTypes]; - [_registeredDraggedTypes addObjectsFromArray:pasteboardTypes]; - [self _noteRegisteredDraggedTypes:_registeredDraggedTypes]; - - _registeredDraggedTypesArray = nil; -} - -/*! - Returns an array of all types the receiver accepts for dragging operations. - @return an array of CPPasteBoards -*/ -- (CPArray)registeredDraggedTypes -{ - if (!_registeredDraggedTypesArray) - _registeredDraggedTypesArray = [_registeredDraggedTypes allObjects]; - - return _registeredDraggedTypesArray; -} - -/*! - Resets the array of acceptable data types for a dragging operation. -*/ -- (void)unregisterDraggedTypes -{ - [self _noteUnregisteredDraggedTypes:_registeredDraggedTypes]; - - _registeredDraggedTypes = [CPSet set]; - _registeredDraggedTypesArray = []; -} - -// Accessing Editing Status - -/*! - Sets whether the document has been edited. - @param isDocumentEdited \c YES if the document has been edited. -*/ -- (void)setDocumentEdited:(BOOL)isDocumentEdited -{ - if (_isDocumentEdited == isDocumentEdited) - return; - - _isDocumentEdited = isDocumentEdited; - - [CPMenu _setMenuBarIconImageAlphaValue:_isDocumentEdited ? 0.5 : 1.0]; - - [_windowView setDocumentEdited:isDocumentEdited]; -} - -/*! - Returns \c YES if the document has been edited. -*/ -- (BOOL)isDocumentEdited -{ - return _isDocumentEdited; -} - -- (void)setDocumentSaving:(BOOL)isDocumentSaving -{ - if (_isDocumentSaving == isDocumentSaving) - return; - - _isDocumentSaving = isDocumentSaving; - - [self _synchronizeSaveMenuWithDocumentSaving]; - - [_windowView windowDidChangeDocumentSaving]; -} - -- (BOOL)isDocumentSaving -{ - return _isDocumentSaving; -} - -/* @ignore */ -- (void)_synchronizeSaveMenuWithDocumentSaving -{ - if (![self isMainWindow]) - return; - - var mainMenu = [CPApp mainMenu], - index = [mainMenu indexOfItemWithTitle:_isDocumentSaving ? @"Save" : @"Saving..."]; - - if (index == CPNotFound) - return; - - var item = [mainMenu itemAtIndex:index]; - - if (_isDocumentSaving) - { - CPWindowSaveImage = [item image]; - - [item setTitle:@"Saving..."]; - [item setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"spinning-regular-gif" forClass:CPProgressIndicator]]; - [item setEnabled:NO]; - } - else - { - [item setTitle:@"Save"]; - [item setImage:CPWindowSaveImage]; - [item setEnabled:YES]; - } -} - -// Minimizing Windows - -/*! - Simulates the user minimizing the window, then minimizes the window. - @param aSender the object making this request -*/ -- (void)performMiniaturize:(id)aSender -{ - //FIXME show stuff - [self miniaturize:aSender]; -} - -/*! - Minimizes the window. Posts a \c CPWindowWillMiniaturizeNotification to the - notification center before minimizing the window. -*/ -- (void)miniaturize:(id)sender -{ - [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillMiniaturizeNotification object:self]; - - [[self platformWindow] miniaturize:sender]; - - [self _updateMainAndKeyWindows]; - - [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidMiniaturizeNotification object:self]; - - _isMiniaturized = YES; -} - -/*! - Restores a minimized window to it's original size. -*/ -- (void)deminiaturize:(id)sender -{ - [[self platformWindow] deminiaturize:sender]; - - [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidDeminiaturizeNotification object:self]; - - _isMiniaturized = NO; -} - -/*! - Returns YES if the window is minimized. -*/ -- (void)isMiniaturized -{ - return _isMiniaturized; -} - -// Closing Windows - -/*! - Simulates the user closing the window, then closes the window. - @param aSender the object making this request -*/ -- (void)performClose:(id)aSender -{ - if (!(_styleMask & CPClosableWindowMask)) - return; - - if ([self isFullBridge]) - { - var event = [CPApp currentEvent]; - - if ([event type] === CPKeyDown && [event characters] === "w" && ([event modifierFlags] & CPPlatformActionKeyMask)) - { - [[self platformWindow] _propagateCurrentDOMEvent:YES]; - return; - } - } - - // Only send ONE windowShouldClose: message. - if ([_delegate respondsToSelector:@selector(windowShouldClose:)]) - { - if (![_delegate windowShouldClose:self]) - return; - } - - // Only check self is delegate does NOT implement this. This also ensures this when delegate == self (returns true). - else if ([self respondsToSelector:@selector(windowShouldClose:)] && ![self windowShouldClose:self]) - return; - - var documents = [_windowController documents]; - - if ([documents count]) - { - var index = [documents indexOfObject:[_windowController document]]; - - [documents[index] shouldCloseWindowController:_windowController - delegate:self - shouldCloseSelector:@selector(_windowControllerContainingDocument:shouldClose:contextInfo:) - contextInfo:{documents:[documents copy], visited:0, index:index}]; - } - else - [self close]; -} - -- (void)_windowControllerContainingDocument:(CPDocument)document shouldClose:(BOOL)shouldClose contextInfo:(Object)context -{ - if (shouldClose) - { - var windowController = [self windowController], - documents = context.documents, - count = [documents count], - visited = ++context.visited, - index = ++context.index % count; - - [document removeWindowController:windowController]; - - if (visited < count) - { - [windowController setDocument:documents[index]]; - - [documents[index] shouldCloseWindowController:_windowController - delegate:self - shouldCloseSelector:@selector(_windowControllerContainingDocument:shouldClose:contextInfo:) - contextInfo:context]; - } - else - [self close]; - } -} - -/*! - Closes the window. Posts a \c CPWindowWillCloseNotification to the - notification center before closing the window. -*/ -- (void)close -{ - [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillCloseNotification object:self]; - - [self orderOut:nil]; -} - -// Managing Main Status -/*! - Returns \c YES if this the main window. -*/ -- (BOOL)isMainWindow -{ - return [CPApp mainWindow] === self; -} - -/*! - Returns \c YES if the window can become the main window. -*/ -- (BOOL)canBecomeMainWindow -{ - // Note that the Cocoa documentation says that this method returns YES if - // the window is visible and has a title bar or a "resize mechanism". It turns - // out a "resize mechanism" is not the same as having the resize mask set. - // In practice a window must have a title bar to become main, but we make - // an exception for a full platform window. - return ([self isVisible] && ((_styleMask & CPTitledWindowMask) || _isFullPlatformWindow)); -} - -/*! - Makes the receiver the main window. -*/ -- (void)makeMainWindow -{ - if ([CPApp mainWindow] === self || ![self canBecomeMainWindow]) - return; - - [[CPApp mainWindow] resignMainWindow]; - [self becomeMainWindow]; -} - -/*! - Called to tell the receiver that it has become the main window. -*/ -- (void)becomeMainWindow -{ - CPApp._mainWindow = self; - - [self _synchronizeMenuBarTitleWithWindowTitle]; - [self _synchronizeSaveMenuWithDocumentSaving]; - - [_windowView noteMainWindowStateChanged]; - - [[CPNotificationCenter defaultCenter] - postNotificationName:CPWindowDidBecomeMainNotification - object:self]; -} - -/*! - Called when the window resigns main window status. -*/ -- (void)resignMainWindow -{ - [[CPNotificationCenter defaultCenter] - postNotificationName:CPWindowDidResignMainNotification - object:self]; - - if (CPApp._mainWindow === self) - CPApp._mainWindow = nil; - - [_windowView noteMainWindowStateChanged]; -} - -- (void)_updateMainAndKeyWindows -{ - var allWindows = [CPApp orderedWindows], - windowCount = [allWindows count]; - - if ([self isKeyWindow]) - { - var keyWindow = [CPApp keyWindow]; - [self resignKeyWindow]; - - if (keyWindow && keyWindow !== self && [keyWindow canBecomeKeyWindow]) - [keyWindow makeKeyWindow]; - else - { - var mainMenu = [CPApp mainMenu], - menuBarClass = objj_getClass("_CPMenuBarWindow"), - menuWindow; - - for (var i = 0; i < windowCount; i++) - { - var currentWindow = allWindows[i]; - - if ([currentWindow isKindOfClass:menuBarClass]) - menuWindow = currentWindow; - - if (currentWindow === self || currentWindow === menuWindow) - continue; - - if ([currentWindow isVisible] && [currentWindow canBecomeKeyWindow]) - { - [currentWindow makeKeyWindow]; - break; - } - } - - if (![CPApp keyWindow]) - [menuWindow makeKeyWindow]; - } - } - - if ([self isMainWindow]) - { - var mainWindow = [CPApp mainWindow]; - [self resignMainWindow]; - - if (mainWindow && mainWindow !== self && [mainWindow canBecomeMainWindow]) - [mainWindow makeMainWindow]; - else - { - var mainMenu = [CPApp mainMenu], - menuBarClass = objj_getClass("_CPMenuBarWindow"), - menuWindow; - - for (var i = 0; i < windowCount; i++) - { - var currentWindow = allWindows[i]; - - if ([currentWindow isKindOfClass:menuBarClass]) - menuWindow = currentWindow; - - if (currentWindow === self || currentWindow === menuWindow) - continue; - - if ([currentWindow isVisible] && [currentWindow canBecomeMainWindow]) - { - [currentWindow makeMainWindow]; - break; - } - } - } - } -} - -// Managing Toolbars -/*! - Return's the window's toolbar -*/ -- (CPToolbar)toolbar -{ - return _toolbar; -} - -/*! - Sets the window's toolbar. - @param aToolbar the window's new toolbar -*/ -- (void)setToolbar:(CPToolbar)aToolbar -{ - if (_toolbar === aToolbar) - return; - - // If this has an owner, dump it! - [[aToolbar _window] setToolbar:nil]; - - // This is no longer out toolbar. - [_toolbar _setWindow:nil]; - - _toolbar = aToolbar; - - // THIS is our toolbar. - [_toolbar _setWindow:self]; - - [self _noteToolbarChanged]; -} - -- (void)toggleToolbarShown:(id)aSender -{ - var toolbar = [self toolbar]; - - [toolbar setVisible:![toolbar isVisible]]; -} - -- (void)_noteToolbarChanged -{ - var frame = _CGRectMakeCopy([self frame]), - newFrame; - - [_windowView noteToolbarChanged]; - - if (_isFullPlatformWindow) - newFrame = [_platformWindow visibleFrame]; - else - { - newFrame = _CGRectMakeCopy([self frame]); - - newFrame.origin = frame.origin; - } - - [self setFrame:newFrame]; - /* - [_windowView setAnimatingToolbar:YES]; - [self setFrame:frame]; - [self setFrame:newFrame display:YES animate:YES]; - [_windowView setAnimatingToolbar:NO]; - */ -} - -- (void)_setFrame:(CGRect)aFrame delegate:(id)delegate duration:(int)duration curve:(CPAnimationCurve)curve -{ - [_frameAnimation stopAnimation]; - _frameAnimation = [[_CPWindowFrameAnimation alloc] initWithWindow:self targetFrame:aFrame]; - [_frameAnimation setDelegate:delegate]; - [_frameAnimation setAnimationCurve:curve]; - [_frameAnimation setDuration:duration]; - [_frameAnimation startAnimation]; -} - -- (CPTimeInterval)animationResizeTime:(CGRect)newWindowFrame -{ - return CPWindowResizeTime; -} - -/* @ignore */ -- (void)_setAttachedSheetFrameOrigin -{ - // Position the sheet above the contentRect. - var attachedSheet = [self attachedSheet], - contentRect = [_contentView frame], - sheetFrame = _CGRectMakeCopy([attachedSheet frame]); - - sheetFrame.origin.y = _CGRectGetMinY(_frame) + _CGRectGetMinY(contentRect); - sheetFrame.origin.x = _CGRectGetMinX(_frame) + FLOOR((_CGRectGetWidth(_frame) - _CGRectGetWidth(sheetFrame)) / 2.0); - - [attachedSheet setFrame:sheetFrame display:YES animate:NO]; -} - -/* @ignore - Starting point for sheet session, called from CPApplication beginSheet: -*/ -- (void)_attachSheet:(CPWindow)aSheet modalDelegate:(id)aModalDelegate - didEndSelector:(SEL)aDidEndSelector contextInfo:(id)aContextInfo -{ - if (_sheetContext) - { - [CPException raise:CPInternalInconsistencyException - reason:@"The target window of beginSheet: already has a sheet, did you forget orderOut: ?"]; - return; - } - - var sheetFrame = [aSheet frame]; - - _sheetContext = {"sheet": aSheet, "modalDelegate": aModalDelegate, "endSelector": aDidEndSelector, - "contextInfo": aContextInfo, "frame": _CGRectMakeCopy(sheetFrame), "returnCode": -1, - "opened": NO}; - - [self _attachSheetWindow]; -} - -/* @ignore - Called to animate the sheet in. The timer seems to solve a bug where sheets would - be partially animated under certain conditions. -*/ -- (void)_attachSheetWindow -{ - _sheetContext["isAttached"] = YES; - - // it would be ideal to block here and spin an event loop, until attach is complete - [CPTimer scheduledTimerWithTimeInterval:0.0 - target:self - selector:@selector(_sheetShouldAnimateIn:) - userInfo:nil - repeats:NO]; -} - -/* @ignore - Called to end the sheet. Note that orderOut: is needed to animate the sheet out, as in Cocoa. - The sheet isn't completely gone until _cleanupSheetWindow gets called. -*/ -- (void)_endSheet -{ - var delegate = _sheetContext["modalDelegate"], - endSelector = _sheetContext["endSelector"]; - - // If the sheet has been ordered out, defer didEndSelector until after sheet animates out. - // This must be done since we cannot block and wait for the animation to complete. - if (delegate && endSelector) - { - if (_sheetContext["isAttached"]) - objj_msgSend(delegate, endSelector, _sheetContext["sheet"], _sheetContext["returnCode"], - _sheetContext["contextInfo"]); - else - _sheetContext["deferDidEndSelector"] = YES; - } -} - -/* @ignore - Called to animate the sheet out. If called while animating in, schedules an animate - out at completion -*/ -- (void)_detachSheetWindow -{ - _sheetContext["isAttached"] = NO; - - // it would be ideal to block here and spin the event loop, until attach is complete - [CPTimer scheduledTimerWithTimeInterval:0.0 - target:self - selector:@selector(_sheetShouldAnimateOut:) - userInfo:nil - repeats:NO]; -} - -/* @ignore - Called to cleanup sheet, when we are definitely done with it -*/ -- (void)_cleanupSheetWindow -{ - var sheet = _sheetContext["sheet"], - lastFrame = _sheetContext["frame"], - deferDidEnd = _sheetContext["deferDidEndSelector"]; - - [sheet setFrame:lastFrame]; - [self _restoreMasksForView:[sheet contentView]]; - - // if the parent window is modal, the sheet started its own modal session - if (sheet._isModal) - [CPApp stopModal]; - - // restore the state of window before it was sheetified - [sheet._windowView _enableSheet:NO]; - - // close it - sheet._isSheet = NO; - [sheet orderOut:self]; - - [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidEndSheetNotification object:self]; - - if (deferDidEnd) - { - var delegate = _sheetContext["modalDelegate"], - selector = _sheetContext["endSelector"], - returnCode = _sheetContext["returnCode"], - contextInfo = _sheetContext["contextInfo"]; - - // context must be destroyed, since didEnd might want to attach another sheet - _sheetContext = nil; - sheet._parentView = nil; - - objj_msgSend(delegate, selector, sheet, returnCode, contextInfo); - } - else - { - _sheetContext = nil; - sheet._parentView = nil; - } -} - -/* @ignore */ -- (void)animationDidEnd:(id)anim -{ - var sheet = _sheetContext["sheet"]; - - if (anim._window != sheet) - return; - - [CPTimer scheduledTimerWithTimeInterval:0.0 - target:self - selector:@selector(_sheetAnimationDidEnd:) - userInfo:nil - repeats:NO]; -} - -/* @ignore */ -- (void)_sheetShouldAnimateIn:(CPTimer)timer -{ - // can't open sheet while opening or closing animation is going on - if (_sheetContext["isOpening"] || - _sheetContext["isClosing"]) - return; - - var sheet = _sheetContext["sheet"], - sheetFrame = [sheet frame], - frame = [self frame]; - - [self _setUpMasksForView:[sheet contentView]]; - - sheet._isSheet = YES; - sheet._parentView = self; - - var originx = frame.origin.x + FLOOR((frame.size.width - sheetFrame.size.width) / 2), - originy = frame.origin.y + [_contentView frame].origin.y, - startFrame = _CGRectMake(originx, originy, sheetFrame.size.width, 0), - endFrame = _CGRectMake(originx, originy, sheetFrame.size.width, sheetFrame.size.height); - - [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillBeginSheetNotification object:self]; - - // if sheet is attached to a modal window, the sheet runs - // as if itself and the parent window are modal - sheet._isModal = NO; - - if ([CPApp modalWindow] === self) - { - [CPApp runModalForWindow:sheet]; - sheet._isModal = YES; - } - - [sheet orderFront:self]; - [sheet setFrame:startFrame display:YES animate:NO]; - - _sheetContext["opened"] = YES; - _sheetContext["shouldClose"] = NO; - _sheetContext["isOpening"] = YES; - - [sheet _setFrame:endFrame delegate:self duration:[self animationResizeTime:endFrame] curve:CPAnimationEaseOut]; - - // NOTE: cocoa doesn't make window key until animation is done, but a - // keypress while animating eventually gets to the window. Therefore, - // there must be a runloop specifically designed for sheets? - [sheet makeKeyWindow]; -} - -/* @ignore */ -- (void)_sheetShouldAnimateOut:(CPTimer)timer -{ - var sheet = _sheetContext["sheet"], - startFrame = [sheet frame], - endFrame = _CGRectMakeCopy(startFrame); - - if (_sheetContext["isOpening"]) - { - // allow sheet to be closed while opening, it will close when animate in completes - _sheetContext["shouldClose"] = YES; - return; - } - - if (_sheetContext["isClosing"]) - return; - - _sheetContext["opened"] = NO; - _sheetContext["frame"] = startFrame; - _sheetContext["isClosing"] = YES; - - // the parent window can be orderedOut to disable the sheet animate out, as in Cocoa - if ([self isVisible]) - { - endFrame.size.height = 0; - [self _setUpMasksForView:[sheet contentView]]; - [sheet _setFrame:endFrame delegate:self duration:[self animationResizeTime:endFrame] curve:CPAnimationEaseIn]; - } - else - { - [self _sheetAnimationDidEnd:nil]; - } -} - -/* @ignore */ -- (void)_sheetAnimationDidEnd:(CPTimer)timer -{ - var sheet = _sheetContext["sheet"]; - - _sheetContext["isOpening"] = NO; - _sheetContext["isClosing"] = NO; - - if (_sheetContext["opened"] === YES) - { - // sheet is open and completely visible - [self _restoreMasksForView:[sheet contentView]]; - - // we wanted to close the sheet while it animated in, do that now - if (_sheetContext["shouldClose"] === YES) - [self _detachSheetWindow]; - } - else - { - // sheet is closed and not visible - [self _cleanupSheetWindow]; - } -} - -- (void)_setUpMasksForView:(CPView)aView -{ - var views = [aView subviews]; - - [views addObject:aView]; - - for (var i = 0, count = [views count]; i < count; i++) - { - var view = [views objectAtIndex:i], - mask = [view autoresizingMask], - maskToAdd = (mask & CPViewMinYMargin) ? 128 : CPViewMinYMargin; - - [view setAutoresizingMask:(mask | maskToAdd)]; - } -} - -- (void)_restoreMasksForView:(CPView)aView -{ - var views = [aView subviews]; - - [views addObject:aView]; - - for (var i = 0, count = [views count]; i < count; i++) - { - var view = [views objectAtIndex:i], - mask = [view autoresizingMask], - maskToRemove = (mask & 128) ? 128 : CPViewMinYMargin; - - [view setAutoresizingMask:(mask & (~ maskToRemove))]; - } -} - -/*! - Returns the window's attached sheet. -*/ -- (CPWindow)attachedSheet -{ - if (_sheetContext === nil) - return nil; - - return _sheetContext["sheet"]; -} - -/*! - Returns \c YES if the window has ever run as a sheet. -*/ -- (BOOL)isSheet -{ - return _isSheet; -} - -// -/* - Used privately. - @ignore -*/ -- (BOOL)becomesKeyOnlyIfNeeded -{ - return NO; -} - -/*! - Returns \c YES if the receiver is able to receive input events - even when a modal session is active. -*/ -- (BOOL)worksWhenModal -{ - return NO; -} - -- (BOOL)performKeyEquivalent:(CPEvent)anEvent -{ - // FIXME: should we be starting at the root, in other words _windowView? - // The evidence seems to point to no... - return [_contentView performKeyEquivalent:anEvent]; -} - -- (void)keyDown:(CPEvent)anEvent -{ - // It's not clear why we do performKeyEquivalent again here... - // Perhaps to allow something to happen between sendEvent: and keyDown:? - if ([anEvent _couldBeKeyEquivalent] && [self performKeyEquivalent:anEvent]) - return; - - // Apple's documentation is inconsistent with their behavior here. According to the docs - // an event going of the responder chain is passed to the input system as a last resort. - // However, the only methods I could get Cocoa to call automatically are - // moveUp: moveDown: moveLeft: moveRight: pageUp: pageDown: and complete: - // Unhandled events just travel further up the responder chain _past_ the window. - if (![self _processKeyboardUIKey:anEvent]) - [super keyDown:anEvent]; -} - -/* - @ignore - Interprets the key event for action messages and sends the action message down the responder chain - Cocoa only sends moveDown:, moveUp:, moveLeft:, moveRight:, pageUp:, pageDown: and complete: messages. - We deviate from this by sending (the default) scrollPageUp:, scrollPageDown:, scrollToBeginningOfDocument: and scrollToEndOfDocument: for pageUp, pageDown, home and end keys. - @param anEvent the event to handle. - @return YES if the key event was handled, NO if no responder handled the key event -*/ -- (BOOL)_processKeyboardUIKey:(CPEvent)anEvent -{ - var character = [anEvent charactersIgnoringModifiers]; - - if (![CPWindowActionMessageKeys containsObject:character]) - return NO; - - var selectors = [CPKeyBinding selectorsForKey:character modifierFlags:0]; - - if ([selectors count] <= 0) - return NO; - - if (character !== CPEscapeFunctionKey) - { - var selector = [selectors objectAtIndex:0]; - return [[self firstResponder] tryToPerform:selector with:self]; - } - else - { - /* - Cocoa sends complete: for the escape key (instead of the default cancelOperation:). This is also the only action that is not sent directly to the first responder, but through doCommandBySelector. The difference is that doCommandBySelector: will also send the action to the window and application delegates. - */ - [[self firstResponder] doCommandBySelector:@selector(complete:)]; - } - - return NO; -} - -- (void)_dirtyKeyViewLoop -{ - if (_autorecalculatesKeyViewLoop) - _keyViewLoopIsDirty = YES; -} - -/* - Recursively traverse an array of views (depth last) until we find one that has a next or previous key view set. Return nil if none can be found. - - We don't use _viewsSortedByPosition here because it is wasteful to enumerate the entire view hierarchy when we will probably find a key view at the top level. -*/ -- (BOOL)_hasKeyViewLoop:(CPArray)theViews -{ - var i, - count = [theViews count]; - - for (i = 0; i < count; ++i) - { - var view = theViews[i]; - - if ([view nextKeyView] || [view previousKeyView]) - return YES; - } - - for (i = 0; i < count; ++i) - { - var subviews = [theViews[i] subviews]; - - if ([subviews count] && [self _hasKeyViewLoop:subviews]) - return YES; - } - - return NO; -} - -/*! - Recalculates the key view loop, based on geometric position. - Note that the Cocoa documentation says that this method only marks the loop - as dirty, the recalculation is not done until the next or previous key view - of the window is requested. In reality, Cocoa does recalculate the loop - when this method is called. -*/ -- (void)recalculateKeyViewLoop -{ - [self _doRecalculateKeyViewLoop]; -} - -- (CPArray)_viewsSortedByPosition -{ - var views = [CPArray arrayWithObject:_contentView]; - - views = views.concat([self _subviewsSortedByPosition:[_contentView subviews]]); - - return views; -} - -- (CPArray)_subviewsSortedByPosition:(CPArray)theSubviews -{ - /* - We first sort the subviews according to geometric order. - Then we go through each subview, and if it has subviews, - they are sorted and inserted after the superview. This - is done recursively. - */ - theSubviews = [theSubviews copy]; - [theSubviews sortUsingFunction:keyViewComparator context:nil]; - - var sortedViews = []; - - for (var i = 0, count = [theSubviews count]; i < count; ++i) - { - var view = theSubviews[i], - subviews = [view subviews]; - - sortedViews.push(view); - - if ([subviews count]) - sortedViews = sortedViews.concat([self _subviewsSortedByPosition:subviews]); - } - - return sortedViews; -} - -- (void)_doRecalculateKeyViewLoop -{ - var views = [self _viewsSortedByPosition]; - - for (var index = 0, count = [views count]; index < count; ++index) - [views[index] setNextKeyView:views[(index + 1) % count]]; - - _keyViewLoopIsDirty = NO; -} - -- (void)setAutorecalculatesKeyViewLoop:(BOOL)shouldRecalculate -{ - if (_autorecalculatesKeyViewLoop === shouldRecalculate) - return; - - _autorecalculatesKeyViewLoop = shouldRecalculate; -} - -- (BOOL)autorecalculatesKeyViewLoop -{ - return _autorecalculatesKeyViewLoop; -} - -- (void)selectNextKeyView:(id)sender -{ - if (_keyViewLoopIsDirty) - [self _doRecalculateKeyViewLoop]; - - var nextValidKeyView = nil; - - if ([_firstResponder isKindOfClass:[CPView class]]) - nextValidKeyView = [_firstResponder nextValidKeyView]; - - if (!nextValidKeyView) - { - if ([_initialFirstResponder acceptsFirstResponder]) - nextValidKeyView = _initialFirstResponder; - else - nextValidKeyView = [_initialFirstResponder nextValidKeyView]; - } - - if (nextValidKeyView) - [self makeFirstResponder:nextValidKeyView]; -} - -- (void)selectPreviousKeyView:(id)sender -{ - if (_keyViewLoopIsDirty) - [self _doRecalculateKeyViewLoop]; - - var previousValidKeyView = nil; - - if ([_firstResponder isKindOfClass:[CPView class]]) - previousValidKeyView = [_firstResponder previousValidKeyView]; - - if (!previousValidKeyView) - { - if ([_initialFirstResponder acceptsFirstResponder]) - previousValidKeyView = _initialFirstResponder; - else - previousValidKeyView = [_initialFirstResponder previousValidKeyView]; - } - - if (previousValidKeyView) - [self makeFirstResponder:previousValidKeyView]; -} - -- (void)selectKeyViewFollowingView:(CPView)aView -{ - if (_keyViewLoopIsDirty) - [self _doRecalculateKeyViewLoop]; - - var nextValidKeyView = [aView nextValidKeyView]; - - if ([nextValidKeyView isKindOfClass:[CPView class]]) - [self makeFirstResponder:nextValidKeyView]; -} - -- (void)selectKeyViewPrecedingView:(CPView)aView -{ - if (_keyViewLoopIsDirty) - [self _doRecalculateKeyViewLoop]; - - var previousValidKeyView = [aView previousValidKeyView]; - - if ([previousValidKeyView isKindOfClass:[CPView class]]) - [self makeFirstResponder:previousValidKeyView]; -} - -/*! - Sets the default button for the window. - Note: this method is deprecated use setDefaultButton: instead. - @param aButton - The button that should become default. -*/ -- (void)setDefaultButtonCell:(CPButton)aButton -{ - [self setDefaultButton:aButton]; -} - -/*! - Returns the default button of the receiver. - NOTE: This method is deprecated. Use defaultButton instead. -*/ -- (CPButton)defaultButtonCell -{ - return [self defaultButton]; -} - -/*! - Sets the default button for the window. - This is equivalent to setting the the key equivalent of the button to "return". - Additionally this will turn your button blue (with the Aristo theme). - @param aButton - The button that should become default. -*/ -- (void)setDefaultButton:(CPButton)aButton -{ - if (_defaultButton === aButton) - return; - - if ([_defaultButton keyEquivalent] === CPCarriageReturnCharacter) - [_defaultButton setKeyEquivalent:nil]; - - _defaultButton = aButton; - - if ([_defaultButton keyEquivalent] !== CPCarriageReturnCharacter) - [_defaultButton setKeyEquivalent:CPCarriageReturnCharacter]; -} - -/*! - Returns the default button of the receiver. -*/ -- (CPButton)defaultButton -{ - return _defaultButton; -} - -/*! - Sets the default button key equivalent to "return". -*/ -- (void)enableKeyEquivalentForDefaultButton -{ - _defaultButtonEnabled = YES; -} - -/*! - Sets the default button key equivalent to "return". - NOTE: this method is deprecated. Use enableKeyEquivalentForDefaultButton instead. -*/ -- (void)enableKeyEquivalentForDefaultButtonCell -{ - [self enableKeyEquivalentForDefaultButton]; -} - -/*! - Removes the key equivalent for the default button. -*/ -- (void)disableKeyEquivalentForDefaultButton -{ - _defaultButtonEnabled = NO; -} - -/*! - Removes the key equivalent for the default button. - Note: this method is deprecated. Use disableKeyEquivalentForDefaultButton instead. -*/ -- (void)disableKeyEquivalentForDefaultButtonCell -{ - [self disableKeyEquivalentForDefaultButton]; -} - -@end - -var keyViewComparator = function(lhs, rhs, context) -{ - var lhsBounds = [lhs convertRect:[lhs bounds] toView:nil], - rhsBounds = [rhs convertRect:[rhs bounds] toView:nil], - lhsY = _CGRectGetMinY(lhsBounds), - rhsY = _CGRectGetMinY(rhsBounds), - lhsX = _CGRectGetMinX(lhsBounds), - rhsX = _CGRectGetMinX(rhsBounds), - intersectsVertically = MIN(_CGRectGetMaxY(lhsBounds), _CGRectGetMaxY(rhsBounds)) - MAX(lhsY, rhsY); - - // If two views are "on the same line" (intersect vertically), then rely on the x comparison. - if (intersectsVertically > 0) - { - if (lhsX < rhsX) - return CPOrderedAscending; - - if (lhsX === rhsX) - return CPOrderedSame; - - return CPOrderedDescending; - } - - if (lhsY < rhsY) - return CPOrderedAscending; - - if (lhsY === rhsY) - return CPOrderedSame; - - return CPOrderedDescending; -}; - -@implementation CPWindow (MenuBar) - -- (void)_synchronizeMenuBarTitleWithWindowTitle -{ - // Windows with Documents automatically update the native window title and the menu bar title. - if (![_windowController document] || ![self isMainWindow]) - return; - - [CPMenu setMenuBarTitle:_title]; -} - -@end - -@implementation CPWindow (BridgeSupport) - -/* - @ignore -*/ -- (void)resizeWithOldPlatformWindowSize:(CGSize)aSize -{ - if ([self isFullPlatformWindow]) - return [self setFrame:[_platformWindow visibleFrame]]; - - if (_autoresizingMask == CPWindowNotSizable) - return; - - var frame = [_platformWindow contentBounds], - newFrame = _CGRectMakeCopy(_frame), - dX = (_CGRectGetWidth(frame) - aSize.width) / - (((_autoresizingMask & CPWindowMinXMargin) ? 1 : 0) + (_autoresizingMask & CPWindowWidthSizable ? 1 : 0) + (_autoresizingMask & CPWindowMaxXMargin ? 1 : 0)), - dY = (_CGRectGetHeight(frame) - aSize.height) / - ((_autoresizingMask & CPWindowMinYMargin ? 1 : 0) + (_autoresizingMask & CPWindowHeightSizable ? 1 : 0) + (_autoresizingMask & CPWindowMaxYMargin ? 1 : 0)); - - if (_autoresizingMask & CPWindowMinXMargin) - newFrame.origin.x += dX; - if (_autoresizingMask & CPWindowWidthSizable) - newFrame.size.width += dX; - - if (_autoresizingMask & CPWindowMinYMargin) - newFrame.origin.y += dY; - if (_autoresizingMask & CPWindowHeightSizable) - newFrame.size.height += dY; - - [self setFrame:newFrame]; -} - -/* - @ignore -*/ -- (void)setAutoresizingMask:(unsigned)anAutoresizingMask -{ - _autoresizingMask = anAutoresizingMask; -} - -/* - @ignore -*/ -- (unsigned)autoresizingMask -{ - return _autoresizingMask; -} - -/*! - Converts aPoint from the window coordinate system to the global coordinate system. -*/ -- (CGPoint)convertBaseToGlobal:(CGPoint)aPoint -{ - return [CPPlatform isBrowser] ? [self convertBaseToPlatformWindow:aPoint] : [self convertBaseToScreen:aPoint]; -} - -/*! - Converts aPoint from the global coordinate system to the window coordinate system. -*/ -- (CGPoint)convertGlobalToBase:(CGPoint)aPoint -{ - return [CPPlatform isBrowser] ? [self convertPlatformWindowToBase:aPoint] : [self convertScreenToBase:aPoint]; -} - -/*! - Converts aPoint from the window coordinate system to the coordinate system of the parent platform window. -*/ -- (CGPoint)convertBaseToPlatformWindow:(CGPoint)aPoint -{ - if ([self _sharesChromeWithPlatformWindow]) - return _CGPointMakeCopy(aPoint); - - var origin = [self frame].origin; - - return _CGPointMake(aPoint.x + origin.x, aPoint.y + origin.y); -} - -/*! - Converts aPoint from the parent platform window coordinate system to the window's coordinate system. -*/ -- (CGPoint)convertPlatformWindowToBase:(CGPoint)aPoint -{ - if ([self _sharesChromeWithPlatformWindow]) - return _CGPointMakeCopy(aPoint); - - var origin = [self frame].origin; - - return _CGPointMake(aPoint.x - origin.x, aPoint.y - origin.y); -} - -- (CGPoint)convertScreenToBase:(CGPoint)aPoint -{ - return [self convertPlatformWindowToBase:[_platformWindow convertScreenToBase:aPoint]]; -} - -- (CGPoint)convertBaseToScreen:(CGPoint)aPoint -{ - return [_platformWindow convertBaseToScreen:[self convertBaseToPlatformWindow:aPoint]]; -} - -- (void)_setSharesChromeWithPlatformWindow:(BOOL)shouldShareFrameWithPlatformWindow -{ - // We canna' do it captain! We just don't have the power! - if (shouldShareFrameWithPlatformWindow && [CPPlatform isBrowser]) - return; - - _sharesChromeWithPlatformWindow = shouldShareFrameWithPlatformWindow; - - [self _updateShadow]; -} - -- (BOOL)_sharesChromeWithPlatformWindow -{ - return _sharesChromeWithPlatformWindow; -} - -// Undo and Redo Support -/*! - Returns the window's undo manager. -*/ -- (CPUndoManager)undoManager -{ - // If we've ever created an undo manager, return it. - if (_undoManager) - return _undoManager; - - // If not, check to see if the document has one. - var documentUndoManager = [[_windowController document] undoManager]; - - if (documentUndoManager) - return documentUndoManager; - - // If not, check to see if the delegate has one. - if (_delegateRespondsToWindowWillReturnUndoManagerSelector) - return [_delegate windowWillReturnUndoManager:self]; - - // If not, create one. - if (!_undoManager) - _undoManager = [[CPUndoManager alloc] init]; - - return _undoManager; -} - -/*! - Sends the undo manager an \c -undo: message. - @param aSender the object requesting this -*/ -- (void)undo:(id)aSender -{ - [[self undoManager] undo]; -} - -/*! - Sends the undo manager a \c -redo: message. - @param aSender the object requesting this -*/ -- (void)redo:(id)aSender -{ - [[self undoManager] redo]; -} - -- (BOOL)containsPoint:(CGPoint)aPoint -{ - return _CGRectContainsPoint(_frame, aPoint); -} - -- (BOOL)_isValidMousePoint:(CGPoint)aPoint -{ - // If we are using the new resizing mode, mouse events are valid - // outside the window's frame for non-full platform windows. - var mouseFrame = (!_isFullPlatformWindow && (_styleMask & CPResizableWindowMask) && (CPWindowResizeStyle === CPWindowResizeStyleModern)) ? _CGRectInset(_frame, -CPWindowResizeSlop, -CPWindowResizeSlop) : _frame; - - return _CGRectContainsPoint(mouseFrame, aPoint); -} - -@end - -@implementation CPWindow (Deprecated) -/*! - Sets the CPWindow to fill the whole browser window. - NOTE: this method has been deprecated in favor of setFullPlatformWindow: -*/ -- (void)setFullBridge:(BOOL)shouldBeFullBridge -{ - [self setFullPlatformWindow:shouldBeFullBridge]; -} - -/*! - Returns YES if the window fills the full browser window, otherwise NO. - NOTE: this method has been deprecated in favor of isFullPlatformWindow. -*/ -- (BOOL)isFullBridge -{ - return [self isFullPlatformWindow]; -} - -/* - @ignore -*/ -- (CGPoint)convertBaseToBridge:(CGPoint)aPoint -{ - return [self convertBaseToPlatformWindow:aPoint]; -} - -/* - @ignore -*/ -- (CGPoint)convertBridgeToBase:(CGPoint)aPoint -{ - return [self convertPlatformWindowToBase:aPoint]; -} - -@end - -var interpolate = function(fromValue, toValue, progress) -{ - return fromValue + (toValue - fromValue) * progress; -}; - -/* @ignore */ -@implementation _CPWindowFrameAnimation : CPAnimation -{ - CPWindow _window; - - CGRect _startFrame; - CGRect _targetFrame; -} - -- (id)initWithWindow:(CPWindow)aWindow targetFrame:(CGRect)aTargetFrame -{ - self = [super initWithDuration:[aWindow animationResizeTime:aTargetFrame] animationCurve:CPAnimationLinear]; - - if (self) - { - _window = aWindow; - - _targetFrame = _CGRectMakeCopy(aTargetFrame); - _startFrame = _CGRectMakeCopy([_window frame]); - } - - return self; -} - -- (void)startAnimation -{ - [super startAnimation]; - - _window._isAnimating = YES; -} - -- (void)setCurrentProgress:(float)aProgress -{ - [super setCurrentProgress:aProgress]; - - var value = [self currentValue]; - - if (value == 1.0) - _window._isAnimating = NO; - - var newFrame = _CGRectMake( - interpolate(_CGRectGetMinX(_startFrame), _CGRectGetMinX(_targetFrame), value), - interpolate(_CGRectGetMinY(_startFrame), _CGRectGetMinY(_targetFrame), value), - interpolate(_CGRectGetWidth(_startFrame), _CGRectGetWidth(_targetFrame), value), - interpolate(_CGRectGetHeight(_startFrame), _CGRectGetHeight(_targetFrame), value)); - - [_window setFrame:newFrame display:YES animate:NO]; -} - -@end - -function _CPWindowFullPlatformWindowSessionMake(aWindowView, aContentRect, hasShadow, aLevel) -{ - return { windowView:aWindowView, contentRect:aContentRect, hasShadow:hasShadow, level:aLevel }; -} - -CPStandardWindowShadowStyle = 0; -CPMenuWindowShadowStyle = 1; -CPPanelWindowShadowStyle = 2; -CPCustomWindowShadowStyle = 3; - - +@import "_CPWindow.j" @import "_CPWindowView.j" @import "_CPStandardWindowView.j" @import "_CPDocModalWindowView.j" @@ -3444,4 +32,4 @@ CPCustomWindowShadowStyle = 3; @import "_CPModalWindowView.j" @import "_CPShadowWindowView.j" @import "CPDragServer.j" -@import "CPView.j" +//@import "CPView.j" diff --git a/AppKit/CPWindow/_CPAttachedWindowView.j b/AppKit/CPWindow/_CPAttachedWindowView.j index fb3b2c7b4..c92ca0b4d 100644 --- a/AppKit/CPWindow/_CPAttachedWindowView.j +++ b/AppKit/CPWindow/_CPAttachedWindowView.j @@ -21,6 +21,9 @@ */ @import "_CPWindowView.j" +@import "CGGradient.j" + +@global CPPopoverAppearanceMinimal #define ALIGN_STROKE(point) (FLOOR(point) === (point) ? (point) + halfStrokeWidth : (point)) #define ALIGN_COORD(point) (FLOOR(point)) diff --git a/AppKit/CPWindow/_CPStandardWindowView.j b/AppKit/CPWindow/_CPStandardWindowView.j index bba45d233..af35dd92b 100644 --- a/AppKit/CPWindow/_CPStandardWindowView.j +++ b/AppKit/CPWindow/_CPStandardWindowView.j @@ -22,6 +22,9 @@ @import "CPTextField.j" @import "_CPTitleableWindowView.j" +@import "CPButton.j" + +@class _CPDocModalWindowView @implementation _CPTexturedWindowHeadView : CPView { @@ -206,6 +209,7 @@ var dividerMaxY = 0, dividerMinY = 0; + if (![_dividerView isHidden]) { dividerMinY = _CGRectGetMinY([_dividerView frame]); @@ -319,6 +323,7 @@ [self setFrameSize:_CGSizeMake(newWidth, newHeight)]; [self tile]; [theWindow setFrame:frame display:NO animate:NO]; + } - (void)layoutSubviews @@ -331,9 +336,16 @@ [_minimizeButton setImage:[self valueForThemeAttribute:@"minimize-image-button"]]; [_minimizeButton setAlternateImage:[self valueForThemeAttribute:@"minimize-image-highlighted-button"]]; - [_headView setFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), [self toolbarMaxY])]; + if(![_headView isHidden]) + [_headView setFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), [self toolbarMaxY])]; + else + [_headView setFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), 0)]; + + if(![_dividerView isHidden]) + [_dividerView setFrame:CGRectMake(0.0, CGRectGetMaxY([_headView frame]), CGRectGetWidth(bounds), 1.0)]; + else + [_dividerView setFrame:CGRectMake(0.0, CGRectGetMaxY([_headView frame]), CGRectGetWidth(bounds), 0.0)]; - [_dividerView setFrame:CGRectMake(0.0, CGRectGetMaxY([_headView frame]), CGRectGetWidth(bounds), 1.0)]; [_dividerView setBackgroundColor:[self valueForThemeAttribute:@"divider-color"]]; var y = CGRectGetMaxY([_dividerView frame]); diff --git a/AppKit/CPWindow/_CPTitleableWindowView.j b/AppKit/CPWindow/_CPTitleableWindowView.j index bb9c2ece4..263f928b9 100644 --- a/AppKit/CPWindow/_CPTitleableWindowView.j +++ b/AppKit/CPWindow/_CPTitleableWindowView.j @@ -22,6 +22,9 @@ @import "_CPWindowView.j" +@class CPTextField + + @implementation _CPTitleableWindowView : _CPWindowView { CPTextField _titleField; diff --git a/AppKit/CPWindow/_CPWindow.j b/AppKit/CPWindow/_CPWindow.j new file mode 100644 index 000000000..fddccea40 --- /dev/null +++ b/AppKit/CPWindow/_CPWindow.j @@ -0,0 +1,3449 @@ +/* + * CPWindow.j + * AppKit + * + * Created by Francisco Tolmasky. + * Copyright 2008, 280 North, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import
Each window has a style, which determines how the window is decorated; whether it has a border, a title bar, a resize bar, minimise and close buttons.
+ +A window has a frame. This is the frame of the entire window on the screen, including all decorations and borders. The origin of the frame represents its bottom left corner and the frame is expressed in screen coordinates.
+ +A window always contains a content view which is the highest level view available for public (application) use. This view fills the area of the window inside any decoration/border. This is the only part of the window that application programmers are allowed to draw in directly.
+ +You can convert between view coordinates and window base coordinates using the [CPView -convertPoint:fromView:], [CPView -convertPoint:toView:], [CPView -convertRect:fromView:], and [CPView -convertRect:toView:] methods with a nil view argument. + + @par Delegate Methods + + @delegate -(void)windowDidResize:(CPNotification)notification; + Sent from the notification center when the window has been resized. + @param notification contains information about the resize event + + @delegate -(CPUndoManager)windowWillReturnUndoManager:(CPWindow)window; + Called to obtain the undo manager for a window + @param window the window for which to return the undo manager + @return the window's undo manager + + @delegate -(void)windowDidBecomeMain:(CPNotification)notification; + Sent from the notification center when the delegate's window becomes + the main window. + @param notification contains information about the event + + @delegate -(void)windowDidResignMain:(CPNotification)notification; + Sent from the notification center when the delegate's window has + resigned main window status. + @param notification contains information about the event + + @delegate -(void)windowDidResignKey:(CPNotification)notification; + Sent from the notification center when the delegate's window has + resigned key window status. + @param notification contains information about the event + + @delegate -(BOOL)windowShouldClose:(id)window; + Called when the user tries to close the window. + @param window the window to close + @return \c YES allows the window to close. \c NO + vetoes the close operation and leaves the window open. + + @delegate -(BOOL)windowWillBeginSheet:(CPNotification)notification; + Sent from the notification center before sheet is visible on + the delegate's window. + @param notification contains information about the event + + @delegate -(BOOL)windowDidEndSheet:(CPNotification)notification; + Sent from the notification center when an attached sheet on the + delegate's window has been animated out and is no longer visible. + @param notification contains information about the event +*/ +@implementation CPWindow : CPResponder +{ + CPPlatformWindow _platformWindow; + + int _windowNumber; + unsigned _styleMask; + CGRect _frame; + int _level; + BOOL _isVisible; + BOOL _isMiniaturized; + BOOL _isAnimating; + BOOL _hasShadow; + BOOL _isMovableByWindowBackground; + BOOL _isMovable; + unsigned _shadowStyle; + BOOL _showsResizeIndicator; + + int _positioningMask; + CGRect _positioningScreenRect; + + BOOL _isDocumentEdited; + BOOL _isDocumentSaving; + + CPImageView _shadowView; + + CPView _windowView; + CPView _contentView; + CPView _toolbarView; + + CPArray _mouseEnteredStack; + CPView _leftMouseDownView; + CPView _rightMouseDownView; + + CPToolbar _toolbar; + CPResponder _firstResponder; + CPResponder _initialFirstResponder; + BOOL _hasBecomeKeyWindow; + id _delegate; + + CPString _title; + + BOOL _acceptsMouseMovedEvents; + BOOL _ignoresMouseEvents; + + CPWindowController _windowController; + + CGSize _minSize; + CGSize _maxSize; + + CPUndoManager _undoManager; + CPURL _representedURL; + + CPSet _registeredDraggedTypes; + CPArray _registeredDraggedTypesArray; + CPCountedSet _inclusiveRegisteredDraggedTypes; + + CPButton _defaultButton; + BOOL _defaultButtonEnabled; + + BOOL _autorecalculatesKeyViewLoop; + BOOL _keyViewLoopIsDirty; + + BOOL _sharesChromeWithPlatformWindow; + + // Bridge Support +#if PLATFORM(DOM) + DOMElement _DOMElement; +#endif + + unsigned _autoresizingMask; + + BOOL _delegateRespondsToWindowWillReturnUndoManagerSelector; + + BOOL _isFullPlatformWindow; + _CPWindowFullPlatformWindowSession _fullPlatformWindowSession; + + CPDictionary _sheetContext; + CPWindow _parentView; + BOOL _isSheet; + _CPWindowFrameAnimation _frameAnimation; +} + +- (id)init +{ + return [self initWithContentRect:_CGRectMakeZero() styleMask:CPTitledWindowMask]; +} + +/*! + Initializes the window. The method also takes a style bit mask made up + of any of the following values: +
+CPBorderlessWindowMask +CPTitledWindowMask +CPClosableWindowMask +CPMiniaturizableWindowMask +CPResizableWindowMask +CPTexturedBackgroundWindowMask ++ @param aContentRect the size and location of the window in screen space + @param aStyleMask a style mask + @return the initialized window +*/ +- (id)initWithContentRect:(CGRect)aContentRect styleMask:(unsigned int)aStyleMask +{ + self = [super init]; + + if (self) + { + var windowViewClass = [[self class] _windowViewClassForStyleMask:aStyleMask]; + + _frame = [windowViewClass frameRectForContentRect:aContentRect]; + + [self _setSharesChromeWithPlatformWindow:![CPPlatform isBrowser]]; + + if ([CPPlatform isBrowser]) + [self setPlatformWindow:[CPPlatformWindow primaryPlatformWindow]]; + else + { + // give zero sized borderless bridge windows a default size if we're not in the browser so they show up in NativeHost. + if ((aStyleMask & CPBorderlessBridgeWindowMask) && aContentRect.size.width === 0 && aContentRect.size.height === 0) + { + var visibleFrame = [[[CPScreen alloc] init] visibleFrame]; + _frame.size.height = MIN(768.0, visibleFrame.size.height); + _frame.size.width = MIN(1024.0, visibleFrame.size.width); + _frame.origin.x = (visibleFrame.size.width - _frame.size.width) / 2; + _frame.origin.y = (visibleFrame.size.height - _frame.size.height) / 2; + } + [self setPlatformWindow:[[CPPlatformWindow alloc] initWithContentRect:_frame]]; + [self platformWindow]._only = self; + } + + _isFullPlatformWindow = NO; + _registeredDraggedTypes = [CPSet set]; + _registeredDraggedTypesArray = []; + _acceptsMouseMovedEvents = YES; + _isMovable = YES; + + _isSheet = NO; + _sheetContext = nil; + _parentView = nil; + + // Set up our window number. + _windowNumber = [CPApp._windows count]; + CPApp._windows[_windowNumber] = self; + + _styleMask = aStyleMask; + + [self setLevel:CPNormalWindowLevel]; + + _minSize = _CGSizeMake(0.0, 0.0); + _maxSize = _CGSizeMake(1000000.0, 1000000.0); + + // Create our border view which is the actual root of our view hierarchy. + _windowView = [[windowViewClass alloc] initWithFrame:_CGRectMake(0.0, 0.0, _CGRectGetWidth(_frame), _CGRectGetHeight(_frame)) styleMask:aStyleMask]; + + [_windowView _setWindow:self]; + [_windowView setNextResponder:self]; + + [self setMovableByWindowBackground:aStyleMask & CPHUDBackgroundWindowMask]; + + // Create a generic content view. + [self setContentView:[[CPView alloc] initWithFrame:_CGRectMakeZero()]]; + + _firstResponder = self; + +#if PLATFORM(DOM) + _DOMElement = document.createElement("div"); + + _DOMElement.style.position = "absolute"; + _DOMElement.style.visibility = "visible"; + _DOMElement.style.zIndex = 0; + + if (![self _sharesChromeWithPlatformWindow]) + { + CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, _CGRectGetMinX(_frame), _CGRectGetMinY(_frame)); + } + + CPDOMDisplayServerSetStyleSize(_DOMElement, 1, 1); + CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement); +#endif + + [self setNextResponder:CPApp]; + + [self setHasShadow:aStyleMask !== CPBorderlessWindowMask]; + + if (aStyleMask & CPBorderlessBridgeWindowMask) + [self setFullPlatformWindow:YES]; + + _autorecalculatesKeyViewLoop = NO; + _defaultButtonEnabled = YES; + _keyViewLoopIsDirty = NO; + _hasBecomeKeyWindow = NO; + + [self setShowsResizeIndicator:_styleMask & CPResizableWindowMask]; + + [[CPNotificationCenter defaultCenter] addObserver:self + selector:@selector(_didReceiveResizeStyleChange:) + name:CPWindowResizeStyleGlobalChangeNotification + object:nil]; + } + + return self; +} + +- (CPPlatformWindow)platformWindow +{ + return _platformWindow; +} + +/*! + Sets the platform window of the reciver. + This method will first close the reciever, + change the platform window, then reopen the window (if it was originally open). +*/ +- (void)setPlatformWindow:(CPPlatformWindow)aPlatformWindow +{ + var wasVisible = [self isVisible]; + + // we have to close it first, otherwise we get a DOM exception. + if (wasVisible) + [self close]; + + _platformWindow = aPlatformWindow; + [_platformWindow _setTitle:_title window:self]; + + if (wasVisible) + [self orderFront:self]; +} + + +/*! + @ignore +*/ ++ (Class)_windowViewClassForStyleMask:(unsigned)aStyleMask +{ + if (aStyleMask & CPHUDBackgroundWindowMask) + return _CPHUDWindowView; + + else if (aStyleMask === CPBorderlessWindowMask) + return _CPBorderlessWindowView; + + else if (aStyleMask & CPDocModalWindowMask) + return _CPDocModalWindowView; + + else if (aStyleMask & _CPModalWindowMask) + return _CPModalWindowView + + return _CPStandardWindowView; +} + ++ (Class)_windowViewClassForFullPlatformWindowStyleMask:(unsigned)aStyleMask +{ + return _CPBorderlessBridgeWindowView; +} + +- (void)awakeFromCib +{ + // At this time we know the final screen (or browser) size + // and can apply the positioning mask, if any, from the nib. + if (_positioningScreenRect) + { + var actualScreenRect = [CPPlatform isBrowser] ? [_platformWindow contentBounds] : [[self screen] visibleFrame], + frame = [self frame], + origin = frame.origin; + + if (actualScreenRect) + { + if ((_positioningMask & CPWindowPositionFlexibleLeft) && (_positioningMask & CPWindowPositionFlexibleRight)) + { + // Proportional Horizontal. + origin.x *= (actualScreenRect.size.width / _positioningScreenRect.size.width); + } + else if (_positioningMask & CPWindowPositionFlexibleLeft) + { + // Fixed from Right + origin.x += actualScreenRect.size.width - _positioningScreenRect.size.width; + } + else if (_positioningMask & CPWindowPositionFlexibleRight) + { + // Fixed from Left + } + + if ((_positioningMask & CPWindowPositionFlexibleTop) && (_positioningMask & CPWindowPositionFlexibleBottom)) + { + // Proportional Vertical. + origin.y *= (actualScreenRect.size.height / _positioningScreenRect.size.height); + } + else if (_positioningMask & CPWindowPositionFlexibleTop) + { + // Fixed from Bottom + origin.y += actualScreenRect.size.height - _positioningScreenRect.size.height; + } + else if (_positioningMask & CPWindowPositionFlexibleBottom) + { + // Fixed from Top + } + + [self setFrameOrigin:origin]; + } + } + + /* + Calculate the key view loop if necessary. Note that Cocoa does not call recalculateKeyViewLoop when awaking a nib. If a key view loop was set in the cib, we have to chain it to the content view. + */ + if ([self _hasKeyViewLoop:[_contentView subviews]]) + { + var views = [self _viewsSortedByPosition], + count = [views count]; + + // The first view is the content view. + // Find the first subview that has a next key view. + for (var i = 1; i < count; ++i) + { + var view = views[i]; + + if ([view nextKeyView]) + { + [_contentView setNextKeyView:view]; + break; + } + } + } + else + { + // Cooca does NOT call the public method recalculateKeyViewLoop for nibs, + // but it does calculate the loop. + [self _doRecalculateKeyViewLoop]; + } +} + +- (void)_setWindowView:(CPView)aWindowView +{ + if (_windowView === aWindowView) + return; + + var oldWindowView = _windowView; + + _windowView = aWindowView; + + if (oldWindowView) + { + [oldWindowView _setWindow:nil]; + [oldWindowView noteToolbarChanged]; + +#if PLATFORM(DOM) + CPDOMDisplayServerRemoveChild(_DOMElement, oldWindowView._DOMElement); +#endif + } + + if (_windowView) + { +#if PLATFORM(DOM) + CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement); +#endif + + var contentRect = [_contentView convertRect:[_contentView bounds] toView:nil]; + + contentRect.origin = [self convertBaseToGlobal:contentRect.origin]; + + [_windowView _setWindow:self]; + [_windowView setNextResponder:self]; + [_windowView addSubview:_contentView]; + [_windowView setTitle:_title]; + [_windowView noteToolbarChanged]; + [_windowView setShowsResizeIndicator:[self showsResizeIndicator]]; + + [self setFrame:[self frameRectForContentRect:contentRect]]; + } +} + +/*! + Sets the receiver as a full platform window. If you pass YES the CPWindow instance will fill the entire browser content area, + otherwise the CPWindow will be a window inside of your browser window which the user can drag around, and resize (if you allow). + + @param BOOL - YES if the window should fill the browser window, otherwise NO. +*/ +- (void)setFullPlatformWindow:(BOOL)shouldBeFullPlatformWindow +{ + if (![_platformWindow supportsFullPlatformWindows]) + return; + + shouldBeFullPlatformWindow = !!shouldBeFullPlatformWindow; + + if (_isFullPlatformWindow === shouldBeFullPlatformWindow) + return; + + _isFullPlatformWindow = shouldBeFullPlatformWindow; + + if (_isFullPlatformWindow) + { + _fullPlatformWindowSession = _CPWindowFullPlatformWindowSessionMake(_windowView, [self contentRectForFrameRect:[self frame]], [self hasShadow], [self level]); + + var fullPlatformWindowViewClass = [[self class] _windowViewClassForFullPlatformWindowStyleMask:_styleMask], + windowView = [[fullPlatformWindowViewClass alloc] initWithFrame:_CGRectMakeZero() styleMask:_styleMask]; + + [self _setWindowView:windowView]; + + [self setLevel:CPBackgroundWindowLevel]; + [self setHasShadow:NO]; + [self setAutoresizingMask:CPWindowWidthSizable | CPWindowHeightSizable]; + [self setFrame:[_platformWindow visibleFrame]]; + } + else + { + var windowView = _fullPlatformWindowSession.windowView; + + [self _setWindowView:windowView]; + + [self setLevel:_fullPlatformWindowSession.level]; + [self setHasShadow:_fullPlatformWindowSession.hasShadow]; + [self setAutoresizingMask:CPWindowNotSizable]; + + [self setFrame:[windowView frameRectForContentRect:_fullPlatformWindowSession.contentRect]]; + } +} + +/*! + @return BOOL - YES if the CPWindow fills the browser window, otherwise NO. +*/ +- (BOOL)isFullPlatformWindow +{ + return _isFullPlatformWindow; +} + +/*! + Returns the window's style mask. +*/ +- (unsigned)styleMask +{ + return _styleMask; +} + +/*! + Returns the frame rectangle used by a window. + Style masks include: +
+ CPBorderlessWindowMask + CPTitledWindowMask + CPClosableWindowMask + CPMiniaturizableWindowMask (NOTE: only available in NativeHost) + CPResizableWindowMask + CPTexturedBackgroundWindowMask + CPBorderlessBridgeWindowMask + CPHUDBackgroundWindowMask ++ + @param aContentRect the content rectangle of the window + @param aStyleMask the style mask of the window + @return the matching window's frame rectangle +*/ ++ (CGRect)frameRectForContentRect:(CGRect)aContentRect styleMask:(unsigned)aStyleMask +{ + return [[[self class] _windowViewClassForStyleMask:aStyleMask] frameRectForContentRect:aContentRect]; +} + +/*! + Returns the receiver's content rectangle. A content rectangle does not include toolbars. + @param aFrame the window's frame rectangle +*/ +- (CGRect)contentRectForFrameRect:(CGRect)aFrame +{ + return [_windowView contentRectForFrameRect:aFrame]; +} + +/*! + Retrieves the frame rectangle for this window. + @param aContentRect the window's content rectangle + @return the window's frame rectangle +*/ +- (CGRect)frameRectForContentRect:(CGRect)aContentRect +{ + return [_windowView frameRectForContentRect:aContentRect]; +} + +/*! + Returns the window's frame rectangle +*/ +- (CGRect)frame +{ + return _CGRectMakeCopy(_frame); +} + +/*! + Sets the window's frame rectangle. Also tells the window whether it should animate + the resize operation, and redraw itself if necessary. + @param aFrame the new size and location for the window + @param shouldDisplay whether the window should redraw its views + @param shouldAnimate whether the window resize should be animated. +*/ +- (void)_setClippedFrame:(CGRect)aFrame display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate +{ + aFrame.size.width = MIN(MAX(aFrame.size.width, _minSize.width), _maxSize.width) + aFrame.size.height = MIN(MAX(aFrame.size.height, _minSize.height), _maxSize.height); + [self setFrame:aFrame display:shouldDisplay animate:shouldAnimate]; +} + +/*! + Sets the frame of the window. + + @param aFrame - A CGRect of the new frame for the receiver. + @param shouldDisplay - YES if the window should call setNeedsDisplay otherwise NO. + @param shouldAnimate - YES if the window should animate to it's new size and position, otherwise NO. +*/ +- (void)setFrame:(CGRect)aFrame display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate +{ + aFrame = _CGRectMakeCopy(aFrame); + + var value = aFrame.origin.x, + delta = value - FLOOR(value); + + if (delta) + aFrame.origin.x = value > 0.879 ? CEIL(value) : FLOOR(value); + + value = aFrame.origin.y; + delta = value - FLOOR(value); + + if (delta) + aFrame.origin.y = value > 0.879 ? CEIL(value) : FLOOR(value); + + value = aFrame.size.width; + delta = value - FLOOR(value); + + if (delta) + aFrame.size.width = value > 0.15 ? CEIL(value) : FLOOR(value); + + value = aFrame.size.height; + delta = value - FLOOR(value); + + if (delta) + aFrame.size.height = value > 0.15 ? CEIL(value) : FLOOR(value); + + if (shouldAnimate) + { + [_frameAnimation stopAnimation]; + _frameAnimation = [[_CPWindowFrameAnimation alloc] initWithWindow:self targetFrame:aFrame]; + + [_frameAnimation startAnimation]; + } + else + { + var origin = _frame.origin, + newOrigin = aFrame.origin; + + if (!_CGPointEqualToPoint(origin, newOrigin)) + { + origin.x = newOrigin.x; + origin.y = newOrigin.y; + +#if PLATFORM(DOM) + if (![self _sharesChromeWithPlatformWindow]) + { + CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, origin.x, origin.y); + } +#endif + + [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidMoveNotification object:self]; + } + + var size = _frame.size, + newSize = aFrame.size; + + if (!_CGSizeEqualToSize(size, newSize)) + { + size.width = newSize.width; + size.height = newSize.height; + + [_windowView setFrameSize:size]; + + if (_hasShadow) + [_shadowView setNeedsLayout]; + + if (!_isAnimating) + [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidResizeNotification object:self]; + } + + if ([self _sharesChromeWithPlatformWindow]) + [_platformWindow setContentRect:_frame]; + } +} + +/*! + Sets the window's frame rect. + @param aFrame - The new CGRect of the window. + @param shouldDisplay - YES if the window should call setNeedsDisplay: otherwise NO. +*/ +- (void)setFrame:(CGRect)aFrame display:(BOOL)shouldDisplay +{ + [self _setClippedFrame:aFrame display:shouldDisplay animate:NO]; +} + +/*! + Sets the window's frame rectangle + @param aFrame - The CGRect of the windows new frame +*/ +- (void)setFrame:(CGRect)aFrame +{ + [self _setClippedFrame:aFrame display:YES animate:NO]; +} + +/*! + Sets the window's location. + @param anOrigin the new location for the window +*/ +- (void)setFrameOrigin:(CGPoint)anOrigin +{ + [self _setClippedFrame:_CGRectMake(anOrigin.x, anOrigin.y, _CGRectGetWidth(_frame), _CGRectGetHeight(_frame)) display:YES animate:NO]; + + // reposition sheet + if ([self attachedSheet]) + [self _setAttachedSheetFrameOrigin]; +} + +/*! + Sets the window's size. + @param aSize the new size for the window +*/ +- (void)setFrameSize:(CGSize)aSize +{ + [self _setClippedFrame:_CGRectMake(_CGRectGetMinX(_frame), _CGRectGetMinY(_frame), aSize.width, aSize.height) display:YES animate:NO]; +} + +/*! + Makes the receiver the front most window in the screen ordering. + @param aSender the object that requested this +*/ +- (void)orderFront:(id)aSender +{ +#if PLATFORM(DOM) + // -dw- if a sheet is clicked, the parent window should come up too + if ([self isSheet]) + [_parentView orderFront:self]; + + [_platformWindow orderFront:self]; + [_platformWindow order:CPWindowAbove window:self relativeTo:nil]; +#endif + + if (!CPApp._keyWindow) + [self makeKeyWindow]; + + if ([self isKeyWindow] && (_firstResponder === self || !_firstResponder)) + [self makeFirstResponder:_initialFirstResponder]; + + if (!CPApp._mainWindow) + [self makeMainWindow]; +} + +/* + Makes the receiver the last window in the screen ordering. + @param aSender the object that requested this + @ignore +*/ +- (void)orderBack:(id)aSender +{ + //[_platformWindow order:CPWindowBelow +} + +/*! + Hides the window. + @param the object that requested this +*/ +- (void)orderOut:(id)aSender +{ + if ([self isSheet]) + { + // -dw- as in Cocoa, orderOut: detaches the sheet and animates out + [self._parentView _detachSheetWindow]; + return; + } + +#if PLATFORM(DOM) + if ([self _sharesChromeWithPlatformWindow]) + [_platformWindow orderOut:self]; +#endif + + if ([_delegate respondsToSelector:@selector(windowWillClose:)]) + [_delegate windowWillClose:self]; + +#if PLATFORM(DOM) + [_platformWindow order:CPWindowOut window:self relativeTo:nil]; +#endif + + [self _updateMainAndKeyWindows]; +} + +/*! + Relocates the window in the screen list. + @param aPlace the positioning relative to \c otherWindowNumber + @param otherWindowNumber the window relative to which the receiver should be placed +*/ +- (void)orderWindow:(CPWindowOrderingMode)aPlace relativeTo:(int)otherWindowNumber +{ +#if PLATFORM(DOM) + [_platformWindow order:aPlace window:self relativeTo:CPApp._windows[otherWindowNumber]]; +#endif +} + +/*! + Sets the window's level + @param the window's new level +*/ +- (void)setLevel:(int)aLevel +{ + if (aLevel === _level) + return; + + [_platformWindow moveWindow:self fromLevel:_level toLevel:aLevel]; + + _level = aLevel; + + if ([self _sharesChromeWithPlatformWindow]) + [_platformWindow setLevel:aLevel]; +} + +/*! + Returns the window's current level +*/ +- (int)level +{ + return _level; +} + +/*! + Returns \c YES if the window is visible. It does not mean that the window is not obscured by other windows. +*/ +- (BOOL)isVisible +{ + return _isVisible; +} + +/*! + Globally sets whether windows resize following the legacy style (using a resize thumb + in the bottom right corner), or the modern style (no resize thumb, resizing + can be done on all edges). +*/ ++ (void)setGlobalResizeStyle:(int)aStyle +{ + if (CPWindowResizeStyle === aStyle) + return; + + CPWindowResizeStyle = aStyle; + [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowResizeStyleGlobalChangeNotification object:nil]; +} + +- (void)_didReceiveResizeStyleChange:(CPNotification)aNotification +{ + [_windowView setShowsResizeIndicator:_styleMask & CPResizableWindowMask]; +} + +/*! + Returns the global window resizing style. +*/ ++ (int)globalResizeStyle +{ + return CPWindowResizeStyle; +} + +/*! + Returns \c YES if the window's resize indicator is showing. \c NO otherwise. +*/ +- (BOOL)showsResizeIndicator +{ + return _showsResizeIndicator; +} + +/*! + Sets the window's resize indicator. + @param shouldShowResizeIndicator \c YES sets the window to show its resize indicator. +*/ +- (void)setShowsResizeIndicator:(BOOL)shouldShowResizeIndicator +{ + shouldShowResizeIndicator = !!shouldShowResizeIndicator; + + if (_showsResizeIndicator === shouldShowResizeIndicator) + return; + + _showsResizeIndicator = shouldShowResizeIndicator; + [_windowView setShowsResizeIndicator:[self showsResizeIndicator]]; +} + +/*! + Returns the offset of the window's resize indicator. +*/ +- (CGSize)resizeIndicatorOffset +{ + return [_windowView resizeIndicatorOffset]; +} + +/*! + Sets the offset of the window's resize indicator. + @param aSize the offset for the resize indicator +*/ +- (void)setResizeIndicatorOffset:(CGSize)anOffset +{ + [_windowView setResizeIndicatorOffset:anOffset]; +} + +/*! + Sets the window's content view. The new view will be resized to fit + inside the content rectangle of the window. + @param aView the new content view for the receiver +*/ +- (void)setContentView:(CPView)aView +{ + if (_contentView) + [_contentView removeFromSuperview]; + + var bounds = _CGRectMake(0.0, 0.0, _CGRectGetWidth(_frame), _CGRectGetHeight(_frame)); + + _contentView = aView; + [_contentView setFrame:[self contentRectForFrameRect:bounds]]; + + [_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [_windowView addSubview:_contentView]; + + /* + If the initial first responder has been set to something other than + the window, set it to the window because it will no longer be valid. + */ + if (_initialFirstResponder && _initialFirstResponder !== self) + _initialFirstResponder = self; +} + +/*! + Returns the window's current content view. +*/ +- (CPView)contentView +{ + return _contentView; +} + +/*! + Applies an alpha value to the window. + @param aValue the alpha value to apply +*/ +- (void)setAlphaValue:(float)aValue +{ + [_windowView setAlphaValue:aValue]; +} + +/*! + Returns the alpha value of the window. +*/ +- (float)alphaValue +{ + return [_windowView alphaValue]; +} + +/*! + Sets the window's background color. + @param aColor the new color for the background +*/ +- (void)setBackgroundColor:(CPColor)aColor +{ + [_windowView setBackgroundColor:aColor]; +} + +/*! + Returns the window's background color. +*/ +- (CPColor)backgroundColor +{ + return [_windowView backgroundColor]; +} + +/*! + Sets the window's minimum size. If the provided + size is the same as the current minimum size, the method simply returns. + @param aSize the new minimum size for the window +*/ +- (void)setMinSize:(CGSize)aSize +{ + if (_CGSizeEqualToSize(_minSize, aSize)) + return; + + _minSize = _CGSizeMakeCopy(aSize); + + var size = _CGSizeMakeCopy([self frame].size), + needsFrameChange = NO; + + if (size.width < _minSize.width) + { + size.width = _minSize.width; + needsFrameChange = YES; + } + + if (size.height < _minSize.height) + { + size.height = _minSize.height; + needsFrameChange = YES; + } + + if (needsFrameChange) + [self setFrameSize:size]; +} + +/*! + Returns the windows minimum size. +*/ +- (CGSize)minSize +{ + return _minSize; +} + +/*! + Sets the window's maximum size. If the provided + size is the same as the current maximum size, + the method simply returns. + @param aSize the new maximum size +*/ +- (void)setMaxSize:(CGSize)aSize +{ + if (_CGSizeEqualToSize(_maxSize, aSize)) + return; + + _maxSize = _CGSizeMakeCopy(aSize); + + var size = _CGSizeMakeCopy([self frame].size), + needsFrameChange = NO; + + if (size.width > _maxSize.width) + { + size.width = _maxSize.width; + needsFrameChange = YES; + } + + if (size.height > _maxSize.height) + { + size.height = _maxSize.height; + needsFrameChange = YES; + } + + if (needsFrameChange) + [self setFrameSize:size]; +} + +/*! + Returns the window's maximum size. +*/ +- (CGSize)maxSize +{ + return _maxSize; +} + +/*! + Returns \c YES if the window has a drop shadow. \c NO otherwise. +*/ +- (BOOL)hasShadow +{ + return _hasShadow; +} + +- (void)_updateShadow +{ + if ([self _sharesChromeWithPlatformWindow]) + { + if (_shadowView) + { +#if PLATFORM(DOM) + CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement); +#endif + _shadowView = nil; + } + + [_platformWindow setHasShadow:_hasShadow]; + + return; + } + + if (_hasShadow && !_shadowView) + { + _shadowView = [[_CPShadowWindowView alloc] initWithFrame:CGRectMakeZero()]; + + [_shadowView setWindowView:_windowView]; + [_shadowView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [_shadowView setNeedsLayout]; + +#if PLATFORM(DOM) + CPDOMDisplayServerInsertBefore(_DOMElement, _shadowView._DOMElement, _windowView._DOMElement); +#endif + } + else if (!_hasShadow && _shadowView) + { +#if PLATFORM(DOM) + CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement); +#endif + _shadowView = nil; + } +} + +/*! + Sets whether the window should have a drop shadow. + @param shouldHaveShadow \c YES to have a drop shadow. +*/ +- (void)setHasShadow:(BOOL)shouldHaveShadow +{ + if (_hasShadow === shouldHaveShadow) + return; + + _hasShadow = shouldHaveShadow; + + [self _updateShadow]; +} + +/*! + Sets the shadow style of the receiver. + Values are: +
+ CPWindowShadowStyleStandard + CPWindowShadowStyleMenu + CPWindowShadowStylePanel ++ + @param aStyle - The new shadow style of the receiver. +*/ +- (void)setShadowStyle:(unsigned)aStyle +{ + _shadowStyle = aStyle; + + [[self platformWindow] setShadowStyle:_shadowStyle]; +} + +/*! + Sets the delegate for the window. Passing \c nil will just remove the window's current delegate. + @param aDelegate an object to respond to the various delegate methods of CPWindow +*/ +- (void)setDelegate:(id)aDelegate +{ + var defaultCenter = [CPNotificationCenter defaultCenter]; + + [defaultCenter removeObserver:_delegate name:CPWindowDidResignKeyNotification object:self]; + [defaultCenter removeObserver:_delegate name:CPWindowDidBecomeKeyNotification object:self]; + [defaultCenter removeObserver:_delegate name:CPWindowDidBecomeMainNotification object:self]; + [defaultCenter removeObserver:_delegate name:CPWindowDidResignMainNotification object:self]; + [defaultCenter removeObserver:_delegate name:CPWindowDidMoveNotification object:self]; + [defaultCenter removeObserver:_delegate name:CPWindowDidResizeNotification object:self]; + [defaultCenter removeObserver:_delegate name:CPWindowWillBeginSheetNotification object:self]; + [defaultCenter removeObserver:_delegate name:CPWindowDidEndSheetNotification object:self]; + + _delegate = aDelegate; + _delegateRespondsToWindowWillReturnUndoManagerSelector = [_delegate respondsToSelector:@selector(windowWillReturnUndoManager:)]; + + if ([_delegate respondsToSelector:@selector(windowDidResignKey:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(windowDidResignKey:) + name:CPWindowDidResignKeyNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(windowDidBecomeKey:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(windowDidBecomeKey:) + name:CPWindowDidBecomeKeyNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(windowDidBecomeMain:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(windowDidBecomeMain:) + name:CPWindowDidBecomeMainNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(windowDidResignMain:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(windowDidResignMain:) + name:CPWindowDidResignMainNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(windowDidMove:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(windowDidMove:) + name:CPWindowDidMoveNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(windowDidResize:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(windowDidResize:) + name:CPWindowDidResizeNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(windowWillBeginSheet:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(windowWillBeginSheet:) + name:CPWindowWillBeginSheetNotification + object:self]; + + if ([_delegate respondsToSelector:@selector(windowDidEndSheet:)]) + [defaultCenter + addObserver:_delegate + selector:@selector(windowDidEndSheet:) + name:CPWindowDidEndSheetNotification + object:self]; +} + +/*! + Returns window's delegate +*/ +- (id)delegate +{ + return _delegate; +} + +/*! + Sets the window's controller + @param aWindowController a window controller +*/ +- (void)setWindowController:(CPWindowController)aWindowController +{ + _windowController = aWindowController; +} + +/*! + Returns the window's controller. +*/ +- (CPWindowController)windowController +{ + return _windowController; +} + +- (void)doCommandBySelector:(SEL)aSelector +{ + if ([_delegate respondsToSelector:aSelector]) + [_delegate performSelector:aSelector]; + else + [super doCommandBySelector:aSelector]; +} + +- (BOOL)acceptsFirstResponder +{ + return NO; +} + +- (CPView)initialFirstResponder +{ + return _initialFirstResponder; +} + +- (void)setInitialFirstResponder:(CPView)aView +{ + _initialFirstResponder = aView; +} + +- (void)_setupFirstResponder +{ + /* + When the window is first made the key window, if the first responder is the window, use the initial first responder if there is one. If there is a first responder and it is not the window, ignore the initial first responder. + */ + if (!_hasBecomeKeyWindow) + { + if (_firstResponder === self) + { + if (_initialFirstResponder) + [self makeFirstResponder:_initialFirstResponder]; + else + { + // Make the first valid key view the first responder + var view = [_contentView nextValidKeyView]; + + if (view) + [self makeFirstResponder:view]; + } + + return; + } + } + + if (_firstResponder) + [self makeFirstResponder:_firstResponder]; +} + +/*! + Attempts to make the \c aResponder the first responder. Before trying + to make it the first responder, the receiver will ask the current first responder + to resign its first responder status. If it resigns, it will ask + \c aResponder accept first responder, then finally tell it to become first responder. + @return \c YES if the attempt was successful. \c NO otherwise. +*/ +- (BOOL)makeFirstResponder:(CPResponder)aResponder +{ + if (_firstResponder === aResponder) + return YES; + + if (![_firstResponder resignFirstResponder]) + return NO; + + if (!aResponder || ![aResponder acceptsFirstResponder] || ![aResponder becomeFirstResponder]) + { + _firstResponder = self; + + return NO; + } + + _firstResponder = aResponder; + + [[CPNotificationCenter defaultCenter] postNotificationName:_CPWindowDidChangeFirstResponderNotification object:self]; + + return YES; +} + +/*! + Returns the window's current first responder. +*/ +- (CPResponder)firstResponder +{ + return _firstResponder; +} + +- (BOOL)acceptsMouseMovedEvents +{ + return _acceptsMouseMovedEvents; +} + +- (void)setAcceptsMouseMovedEvents:(BOOL)shouldAcceptMouseMovedEvents +{ + _acceptsMouseMovedEvents = shouldAcceptMouseMovedEvents; +} + +- (BOOL)ignoresMouseEvents +{ + return _ignoresMouseEvents; +} + +- (void)setIgnoresMouseEvents:(BOOL)shouldIgnoreMouseEvents +{ + _ignoresMouseEvents = shouldIgnoreMouseEvents; +} + +- (void)_mouseExitedResizeRect +{ + [[CPCursor arrowCursor] set]; +} + +// Managing Titles + +/*! + Returns the window's title bar string +*/ +- (CPString)title +{ + return _title; +} + +/*! + Sets the window's title bar string +*/ +- (void)setTitle:(CPString)aTitle +{ + _title = aTitle; + + [_windowView setTitle:aTitle]; + [_platformWindow _setTitle:_title window:self]; + + [self _synchronizeMenuBarTitleWithWindowTitle]; +} + +/*! + Sets the title bar to represent a file path +*/ +- (void)setTitleWithRepresentedFilename:(CPString)aFilePath +{ + [self setRepresentedFilename:aFilePath]; + [self setTitle:[aFilePath lastPathComponent]]; +} + +/*! + Sets the path to the file the receiver represents +*/ +- (void)setRepresentedFilename:(CPString)aFilePath +{ + // FIXME: urls vs filepaths and all. + [self setRepresentedURL:aFilePath]; +} + +/*! + Returns the path to the file the receiver represents +*/ +- (CPString)representedFilename +{ + return _representedURL; +} + +/*! + Sets the URL that the receiver represents +*/ +- (void)setRepresentedURL:(CPURL)aURL +{ + _representedURL = aURL; +} + +/*! + Returns the URL that the receiver represents +*/ +- (CPURL)representedURL +{ + return _representedURL; +} + +- (CPScreen)screen +{ + return [[CPScreen alloc] init]; +} + +// Moving + +/*! + Sets whether the window can be moved by dragging its background. The default is based on the window style. + @param shouldBeMovableByWindowBackground \c YES makes the window move from a background drag. +*/ +- (void)setMovableByWindowBackground:(BOOL)shouldBeMovableByWindowBackground +{ + _isMovableByWindowBackground = shouldBeMovableByWindowBackground; +} + +/*! + Returns \c YES if the window can be moved by dragging its background. +*/ +- (BOOL)isMovableByWindowBackground +{ + return _isMovableByWindowBackground; +} + +/*! + Sets whether the window can be moved. + @param shouldBeMovable \c YES makes the window movable. +*/ +- (void)setMovable:(BOOL)shouldBeMovable +{ + _isMovable = shouldBeMovable; +} + +/*! + Returns \c YES if the window can be moved. +*/ +- (void)isMovable +{ + return _isMovable; +} + +/*! + Sets the window location to be the center of the screen +*/ +- (void)center +{ + if (_isFullPlatformWindow) + return; + + var size = [self frame].size, + containerSize = [CPPlatform isBrowser] ? [_platformWindow contentBounds].size : [[self screen] visibleFrame].size; + + var origin = _CGPointMake((containerSize.width - size.width) / 2.0, (containerSize.height - size.height) / 2.0); + + if (origin.x < 0.0) + origin.x = 0.0; + + if (origin.y < 0.0) + origin.y = 0.0; + + [self setFrameOrigin:origin]; +} + +/*! + Dispatches events that are sent to it from CPApplication. + @param anEvent the event to be dispatched +*/ +- (void)sendEvent:(CPEvent)anEvent +{ + var type = [anEvent type], + point = [anEvent locationInWindow]; + + // If a sheet is attached events get filtered here. + // It is not clear what events should be passed to the view, perhaps all? + // CPLeftMouseDown is needed for window moving and resizing to work. + // CPMouseMoved is needed for rollover effects on title bar buttons. + var sheet = [self attachedSheet]; + + if (sheet) + { + switch (type) + { + case CPLeftMouseDown: + [_windowView mouseDown:anEvent]; + + // -dw- if the window is clicked, the sheet should come to front, and become key, + // and the window should be immediately behind + [sheet makeKeyAndOrderFront:self]; + break; + case CPMouseMoved: + [_windowView mouseMoved:anEvent]; + break; + } + + return; + } + + switch (type) + { + case CPFlagsChanged: + return [[self firstResponder] flagsChanged:anEvent]; + + case CPKeyUp: + return [[self firstResponder] keyUp:anEvent]; + + case CPKeyDown: + if ([anEvent charactersIgnoringModifiers] === CPTabCharacter) + { + if ([anEvent modifierFlags] & CPShiftKeyMask) + [self selectPreviousKeyView:self]; + else + [self selectNextKeyView:self]; +#if PLATFORM(DOM) + // Make sure the browser doesn't try to do its own tab handling. + // This is important or the browser might blur the shared text field or token field input field, + // even that we just moved it to a new first responder. + [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO] +#endif + return; + } + else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter) + { + var didTabBack = [self selectPreviousKeyView:self]; + + if (didTabBack) + { +#if PLATFORM(DOM) + // Make sure the browser doesn't try to do its own tab handling. + // This is important or the browser might blur the shared text field or token field input field, + // even that we just moved it to a new first responder. + [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO] +#endif + } + + return didTabBack; + } + + [[self firstResponder] keyDown:anEvent]; + + // Trigger the default button if needed + // FIXME: Is this only applicable in a sheet? See isse: #722. + if (![self disableKeyEquivalentForDefaultButton]) + { + var defaultButton = [self defaultButton], + keyEquivalent = [defaultButton keyEquivalent], + modifierMask = [defaultButton keyEquivalentModifierMask]; + + if ([anEvent _triggersKeyEquivalent:keyEquivalent withModifierMask:modifierMask]) + [[self defaultButton] performClick:self]; + } + + return; + + case CPScrollWheel: + return [[_windowView hitTest:point] scrollWheel:anEvent]; + + case CPLeftMouseUp: + case CPRightMouseUp: + var hitTestedView = _leftMouseDownView, + selector = type == CPRightMouseUp ? @selector(rightMouseUp:) : @selector(mouseUp:); + + if (!hitTestedView) + hitTestedView = [_windowView hitTest:point]; + + [hitTestedView performSelector:selector withObject:anEvent]; + + _leftMouseDownView = nil; + + return; + + case CPLeftMouseDown: + case CPRightMouseDown: + _leftMouseDownView = [_windowView hitTest:point]; + + if (_leftMouseDownView != _firstResponder && [_leftMouseDownView acceptsFirstResponder]) + [self makeFirstResponder:_leftMouseDownView]; + + [CPApp activateIgnoringOtherApps:YES]; + + var theWindow = [anEvent window], + selector = type == CPRightMouseDown ? @selector(rightMouseDown:) : @selector(mouseDown:); + + if ([theWindow isKeyWindow] || [theWindow becomesKeyOnlyIfNeeded] && ![_leftMouseDownView needsPanelToBecomeKey]) + return [_leftMouseDownView performSelector:selector withObject:anEvent]; + else + { + // FIXME: delayed ordering? + [self makeKeyAndOrderFront:self]; + + if ([_leftMouseDownView acceptsFirstMouse:anEvent]) + return [_leftMouseDownView performSelector:selector withObject:anEvent]; + } + break; + + case CPLeftMouseDragged: + case CPRightMouseDragged: + if (!_leftMouseDownView) + return [[_windowView hitTest:point] mouseDragged:anEvent]; + + var selector; + + if (type == CPRightMouseDragged) + { + selector = @selector(rightMouseDragged:) + if (![_leftMouseDownView respondsToSelector:selector]) + selector = nil; + } + + if (!selector) + selector = @selector(mouseDragged:) + + return [_leftMouseDownView performSelector:selector withObject:anEvent]; + + case CPMouseMoved: + [_windowView setCursorForLocation:point resizing:NO]; + + if (!_acceptsMouseMovedEvents) + return; + + if (!_mouseEnteredStack) + _mouseEnteredStack = []; + + var hitTestView = [_windowView hitTest:point]; + + if ([_mouseEnteredStack count] && [_mouseEnteredStack lastObject] === hitTestView) + return [hitTestView mouseMoved:anEvent]; + + var view = hitTestView, + mouseEnteredStack = []; + + while (view) + { + mouseEnteredStack.unshift(view); + + view = [view superview]; + } + + var deviation = MIN(_mouseEnteredStack.length, mouseEnteredStack.length); + + while (deviation--) + if (_mouseEnteredStack[deviation] === mouseEnteredStack[deviation]) + break; + + var index = deviation + 1, + count = _mouseEnteredStack.length; + + if (index < count) + { + var event = [CPEvent mouseEventWithType:CPMouseExited location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0]; + + for (; index < count; ++index) + [_mouseEnteredStack[index] mouseExited:event]; + } + + index = deviation + 1; + count = mouseEnteredStack.length; + + if (index < count) + { + var event = [CPEvent mouseEventWithType:CPMouseEntered location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0]; + + for (; index < count; ++index) + [mouseEnteredStack[index] mouseEntered:event]; + } + + _mouseEnteredStack = mouseEnteredStack; + + [hitTestView mouseMoved:anEvent]; + } +} + +/*! + Returns the window's number in the desktop's screen list +*/ +- (int)windowNumber +{ + return _windowNumber; +} + +/*! + Called when the receiver should become the key window. It sends + the \c -becomeKeyWindow message to the first responder if it responds, + and posts \c CPWindowDidBecomeKeyNotification. +*/ +- (void)becomeKeyWindow +{ + CPApp._keyWindow = self; + + if (_firstResponder !== self && [_firstResponder respondsToSelector:@selector(becomeKeyWindow)]) + [_firstResponder becomeKeyWindow]; + + if (!_hasBecomeKeyWindow) + { + // The first time a window is loaded, if it does not have a key view loop + // established, calculate it now. + if (![self _hasKeyViewLoop:[_contentView subviews]]) + [self recalculateKeyViewLoop]; + } + + [self _setupFirstResponder]; + _hasBecomeKeyWindow = YES; + + [_windowView noteKeyWindowStateChanged]; + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPWindowDidBecomeKeyNotification + object:self]; +} + +/*! + Determines if the window can become the key window. + @return \c YES means the window can become the key window. +*/ +- (BOOL)canBecomeKeyWindow +{ + /* + In Cocoa only titled windows return YES here by default. But the main browser + window in Cappuccino doesn't have a title bar even that it's both titled and + resizable, so we return YES when isFullPlatformWindow too. + + Note that Cocoa will return NO for a non-titled, resizable window. The Cocoa documention + says it will return YES if there is a "resize bar", but in practice + that is not the same as the resizable mask. + */ + return (_styleMask & CPTitledWindowMask) || [self isFullPlatformWindow] || _isSheet; +} + +/*! + Returns \c YES if the window is the key window. +*/ +- (BOOL)isKeyWindow +{ + return [CPApp keyWindow] == self; +} + +/*! + Makes the window the key window and brings it to the front of the screen list. + @param aSender the object requesting this +*/ +- (void)makeKeyAndOrderFront:(id)aSender +{ + [self orderFront:self]; + + [self makeKeyWindow]; + [self makeMainWindow]; +} + +/*! + Makes this window the key window. +*/ +- (void)makeKeyWindow +{ + if ([CPApp keyWindow] === self || ![self canBecomeKeyWindow]) + return; + + [[CPApp keyWindow] resignKeyWindow]; + [self becomeKeyWindow]; +} + +/*! + Causes the window to resign it's key window status. +*/ +- (void)resignKeyWindow +{ + if (_firstResponder !== self && [_firstResponder respondsToSelector:@selector(resignKeyWindow)]) + [_firstResponder resignKeyWindow]; + + if (CPApp._keyWindow === self) + CPApp._keyWindow = nil; + + [_windowView noteKeyWindowStateChanged]; + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPWindowDidResignKeyNotification + object:self]; +} + +/*! + Initiates a drag operation from the receiver to another view that accepts dragged data. + @param anImage the image to be dragged + @param aLocation the lower-left corner coordinate of \c anImage + @param mouseOffset the distance from the \c -mouseDown: location and the current location + @param anEvent the \c -mouseDown: that triggered the drag + @param aPasteboard the pasteboard that holds the drag data + @param aSourceObject the drag operation controller + @param slideBack Whether the image should 'slide back' if the drag is rejected +*/ +- (void)dragImage:(CPImage)anImage at:(CGPoint)imageLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack +{ + [[CPDragServer sharedDragServer] dragImage:anImage fromWindow:self at:[self convertBaseToGlobal:imageLocation] offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack]; +} + +- (void)_noteRegisteredDraggedTypes:(CPSet)pasteboardTypes +{ + if (!pasteboardTypes) + return; + + if (!_inclusiveRegisteredDraggedTypes) + _inclusiveRegisteredDraggedTypes = [CPCountedSet set]; + + [_inclusiveRegisteredDraggedTypes unionSet:pasteboardTypes]; +} + +- (void)_noteUnregisteredDraggedTypes:(CPSet)pasteboardTypes +{ + if (!pasteboardTypes) + return; + + [_inclusiveRegisteredDraggedTypes minusSet:pasteboardTypes]; + + if ([_inclusiveRegisteredDraggedTypes count] === 0) + _inclusiveRegisteredDraggedTypes = nil; +} + +/*! + Initiates a drag operation from the receiver to another view that accepts dragged data. + @param aView the view to be dragged + @param aLocation the lower-left corner coordinate of \c aView + @param mouseOffset the distance from the \c -mouseDown: location and the current location + @param anEvent the \c -mouseDown: that triggered the drag + @param aPasteboard the pasteboard that holds the drag data + @param aSourceObject the drag operation controller + @param slideBack Whether the view should 'slide back' if the drag is rejected +*/ +- (void)dragView:(CPView)aView at:(CGPoint)viewLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack +{ + [[CPDragServer sharedDragServer] dragView:aView fromWindow:self at:[self convertBaseToGlobal:viewLocation] offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack]; +} + +/*! + Sets the receiver's list of acceptable data types for a dragging operation. + @param pasteboardTypes an array of CPPasteboards +*/ +- (void)registerForDraggedTypes:(CPArray)pasteboardTypes +{ + if (!pasteboardTypes) + return; + + [self _noteUnregisteredDraggedTypes:_registeredDraggedTypes]; + [_registeredDraggedTypes addObjectsFromArray:pasteboardTypes]; + [self _noteRegisteredDraggedTypes:_registeredDraggedTypes]; + + _registeredDraggedTypesArray = nil; +} + +/*! + Returns an array of all types the receiver accepts for dragging operations. + @return an array of CPPasteBoards +*/ +- (CPArray)registeredDraggedTypes +{ + if (!_registeredDraggedTypesArray) + _registeredDraggedTypesArray = [_registeredDraggedTypes allObjects]; + + return _registeredDraggedTypesArray; +} + +/*! + Resets the array of acceptable data types for a dragging operation. +*/ +- (void)unregisterDraggedTypes +{ + [self _noteUnregisteredDraggedTypes:_registeredDraggedTypes]; + + _registeredDraggedTypes = [CPSet set]; + _registeredDraggedTypesArray = []; +} + +// Accessing Editing Status + +/*! + Sets whether the document has been edited. + @param isDocumentEdited \c YES if the document has been edited. +*/ +- (void)setDocumentEdited:(BOOL)isDocumentEdited +{ + if (_isDocumentEdited == isDocumentEdited) + return; + + _isDocumentEdited = isDocumentEdited; + + [CPMenu _setMenuBarIconImageAlphaValue:_isDocumentEdited ? 0.5 : 1.0]; + + [_windowView setDocumentEdited:isDocumentEdited]; +} + +/*! + Returns \c YES if the document has been edited. +*/ +- (BOOL)isDocumentEdited +{ + return _isDocumentEdited; +} + +- (void)setDocumentSaving:(BOOL)isDocumentSaving +{ + if (_isDocumentSaving == isDocumentSaving) + return; + + _isDocumentSaving = isDocumentSaving; + + [self _synchronizeSaveMenuWithDocumentSaving]; + + [_windowView windowDidChangeDocumentSaving]; +} + +- (BOOL)isDocumentSaving +{ + return _isDocumentSaving; +} + +/* @ignore */ +- (void)_synchronizeSaveMenuWithDocumentSaving +{ + if (![self isMainWindow]) + return; + + var mainMenu = [CPApp mainMenu], + index = [mainMenu indexOfItemWithTitle:_isDocumentSaving ? @"Save" : @"Saving..."]; + + if (index == CPNotFound) + return; + + var item = [mainMenu itemAtIndex:index]; + + if (_isDocumentSaving) + { + CPWindowSaveImage = [item image]; + + [item setTitle:@"Saving..."]; + [item setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"spinning-regular-gif" forClass:CPProgressIndicator]]; + [item setEnabled:NO]; + } + else + { + [item setTitle:@"Save"]; + [item setImage:CPWindowSaveImage]; + [item setEnabled:YES]; + } +} + +// Minimizing Windows + +/*! + Simulates the user minimizing the window, then minimizes the window. + @param aSender the object making this request +*/ +- (void)performMiniaturize:(id)aSender +{ + //FIXME show stuff + [self miniaturize:aSender]; +} + +/*! + Minimizes the window. Posts a \c CPWindowWillMiniaturizeNotification to the + notification center before minimizing the window. +*/ +- (void)miniaturize:(id)sender +{ + [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillMiniaturizeNotification object:self]; + + [[self platformWindow] miniaturize:sender]; + + [self _updateMainAndKeyWindows]; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidMiniaturizeNotification object:self]; + + _isMiniaturized = YES; +} + +/*! + Restores a minimized window to it's original size. +*/ +- (void)deminiaturize:(id)sender +{ + [[self platformWindow] deminiaturize:sender]; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidDeminiaturizeNotification object:self]; + + _isMiniaturized = NO; +} + +/*! + Returns YES if the window is minimized. +*/ +- (void)isMiniaturized +{ + return _isMiniaturized; +} + +// Closing Windows + +/*! + Simulates the user closing the window, then closes the window. + @param aSender the object making this request +*/ +- (void)performClose:(id)aSender +{ + if (!(_styleMask & CPClosableWindowMask)) + return; + + if ([self isFullBridge]) + { + var event = [CPApp currentEvent]; + + if ([event type] === CPKeyDown && [event characters] === "w" && ([event modifierFlags] & CPPlatformActionKeyMask)) + { + [[self platformWindow] _propagateCurrentDOMEvent:YES]; + return; + } + } + + // Only send ONE windowShouldClose: message. + if ([_delegate respondsToSelector:@selector(windowShouldClose:)]) + { + if (![_delegate windowShouldClose:self]) + return; + } + + // Only check self is delegate does NOT implement this. This also ensures this when delegate == self (returns true). + else if ([self respondsToSelector:@selector(windowShouldClose:)] && ![self windowShouldClose:self]) + return; + + var documents = [_windowController documents]; + + if ([documents count]) + { + var index = [documents indexOfObject:[_windowController document]]; + + [documents[index] shouldCloseWindowController:_windowController + delegate:self + shouldCloseSelector:@selector(_windowControllerContainingDocument:shouldClose:contextInfo:) + contextInfo:{documents:[documents copy], visited:0, index:index}]; + } + else + [self close]; +} + +- (void)_windowControllerContainingDocument:(CPDocument)document shouldClose:(BOOL)shouldClose contextInfo:(Object)context +{ + if (shouldClose) + { + var windowController = [self windowController], + documents = context.documents, + count = [documents count], + visited = ++context.visited, + index = ++context.index % count; + + [document removeWindowController:windowController]; + + if (visited < count) + { + [windowController setDocument:documents[index]]; + + [documents[index] shouldCloseWindowController:_windowController + delegate:self + shouldCloseSelector:@selector(_windowControllerContainingDocument:shouldClose:contextInfo:) + contextInfo:context]; + } + else + [self close]; + } +} + +/*! + Closes the window. Posts a \c CPWindowWillCloseNotification to the + notification center before closing the window. +*/ +- (void)close +{ + [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillCloseNotification object:self]; + + [self orderOut:nil]; +} + +// Managing Main Status +/*! + Returns \c YES if this the main window. +*/ +- (BOOL)isMainWindow +{ + return [CPApp mainWindow] === self; +} + +/*! + Returns \c YES if the window can become the main window. +*/ +- (BOOL)canBecomeMainWindow +{ + // Note that the Cocoa documentation says that this method returns YES if + // the window is visible and has a title bar or a "resize mechanism". It turns + // out a "resize mechanism" is not the same as having the resize mask set. + // In practice a window must have a title bar to become main, but we make + // an exception for a full platform window. + return ([self isVisible] && ((_styleMask & CPTitledWindowMask) || _isFullPlatformWindow)); +} + +/*! + Makes the receiver the main window. +*/ +- (void)makeMainWindow +{ + if ([CPApp mainWindow] === self || ![self canBecomeMainWindow]) + return; + + [[CPApp mainWindow] resignMainWindow]; + [self becomeMainWindow]; +} + +/*! + Called to tell the receiver that it has become the main window. +*/ +- (void)becomeMainWindow +{ + CPApp._mainWindow = self; + + [self _synchronizeMenuBarTitleWithWindowTitle]; + [self _synchronizeSaveMenuWithDocumentSaving]; + + [_windowView noteMainWindowStateChanged]; + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPWindowDidBecomeMainNotification + object:self]; +} + +/*! + Called when the window resigns main window status. +*/ +- (void)resignMainWindow +{ + [[CPNotificationCenter defaultCenter] + postNotificationName:CPWindowDidResignMainNotification + object:self]; + + if (CPApp._mainWindow === self) + CPApp._mainWindow = nil; + + [_windowView noteMainWindowStateChanged]; +} + +- (void)_updateMainAndKeyWindows +{ + var allWindows = [CPApp orderedWindows], + windowCount = [allWindows count]; + + if ([self isKeyWindow]) + { + var keyWindow = [CPApp keyWindow]; + [self resignKeyWindow]; + + if (keyWindow && keyWindow !== self && [keyWindow canBecomeKeyWindow]) + [keyWindow makeKeyWindow]; + else + { + var mainMenu = [CPApp mainMenu], + menuBarClass = objj_getClass("_CPMenuBarWindow"), + menuWindow; + + for (var i = 0; i < windowCount; i++) + { + var currentWindow = allWindows[i]; + + if ([currentWindow isKindOfClass:menuBarClass]) + menuWindow = currentWindow; + + if (currentWindow === self || currentWindow === menuWindow) + continue; + + if ([currentWindow isVisible] && [currentWindow canBecomeKeyWindow]) + { + [currentWindow makeKeyWindow]; + break; + } + } + + if (![CPApp keyWindow]) + [menuWindow makeKeyWindow]; + } + } + + if ([self isMainWindow]) + { + var mainWindow = [CPApp mainWindow]; + [self resignMainWindow]; + + if (mainWindow && mainWindow !== self && [mainWindow canBecomeMainWindow]) + [mainWindow makeMainWindow]; + else + { + var mainMenu = [CPApp mainMenu], + menuBarClass = objj_getClass("_CPMenuBarWindow"), + menuWindow; + + for (var i = 0; i < windowCount; i++) + { + var currentWindow = allWindows[i]; + + if ([currentWindow isKindOfClass:menuBarClass]) + menuWindow = currentWindow; + + if (currentWindow === self || currentWindow === menuWindow) + continue; + + if ([currentWindow isVisible] && [currentWindow canBecomeMainWindow]) + { + [currentWindow makeMainWindow]; + break; + } + } + } + } +} + +// Managing Toolbars +/*! + Return's the window's toolbar +*/ +- (CPToolbar)toolbar +{ + return _toolbar; +} + +/*! + Sets the window's toolbar. + @param aToolbar the window's new toolbar +*/ +- (void)setToolbar:(CPToolbar)aToolbar +{ + if (_toolbar === aToolbar) + return; + + // If this has an owner, dump it! + [[aToolbar _window] setToolbar:nil]; + + // This is no longer out toolbar. + [_toolbar _setWindow:nil]; + + _toolbar = aToolbar; + + // THIS is our toolbar. + [_toolbar _setWindow:self]; + + [self _noteToolbarChanged]; +} + +- (void)toggleToolbarShown:(id)aSender +{ + var toolbar = [self toolbar]; + + [toolbar setVisible:![toolbar isVisible]]; +} + +- (void)_noteToolbarChanged +{ + var frame = _CGRectMakeCopy([self frame]), + newFrame; + + [_windowView noteToolbarChanged]; + + if (_isFullPlatformWindow) + newFrame = [_platformWindow visibleFrame]; + else + { + newFrame = _CGRectMakeCopy([self frame]); + + newFrame.origin = frame.origin; + } + + [self setFrame:newFrame]; + /* + [_windowView setAnimatingToolbar:YES]; + [self setFrame:frame]; + [self setFrame:newFrame display:YES animate:YES]; + [_windowView setAnimatingToolbar:NO]; + */ +} + +- (void)_setFrame:(CGRect)aFrame delegate:(id)delegate duration:(int)duration curve:(CPAnimationCurve)curve +{ + [_frameAnimation stopAnimation]; + _frameAnimation = [[_CPWindowFrameAnimation alloc] initWithWindow:self targetFrame:aFrame]; + [_frameAnimation setDelegate:delegate]; + [_frameAnimation setAnimationCurve:curve]; + [_frameAnimation setDuration:duration]; + [_frameAnimation startAnimation]; +} + +- (CPTimeInterval)animationResizeTime:(CGRect)newWindowFrame +{ + return CPWindowResizeTime; +} + +/* @ignore */ +- (void)_setAttachedSheetFrameOrigin +{ + // Position the sheet above the contentRect. + var attachedSheet = [self attachedSheet], + contentRect = [_contentView frame], + sheetFrame = _CGRectMakeCopy([attachedSheet frame]); + + sheetFrame.origin.y = _CGRectGetMinY(_frame) + _CGRectGetMinY(contentRect); + sheetFrame.origin.x = _CGRectGetMinX(_frame) + FLOOR((_CGRectGetWidth(_frame) - _CGRectGetWidth(sheetFrame)) / 2.0); + + [attachedSheet setFrame:sheetFrame display:YES animate:NO]; +} + +/* @ignore + Starting point for sheet session, called from CPApplication beginSheet: +*/ +- (void)_attachSheet:(CPWindow)aSheet modalDelegate:(id)aModalDelegate + didEndSelector:(SEL)aDidEndSelector contextInfo:(id)aContextInfo +{ + if (_sheetContext) + { + [CPException raise:CPInternalInconsistencyException + reason:@"The target window of beginSheet: already has a sheet, did you forget orderOut: ?"]; + return; + } + + var sheetFrame = [aSheet frame]; + + _sheetContext = {"sheet": aSheet, "modalDelegate": aModalDelegate, "endSelector": aDidEndSelector, + "contextInfo": aContextInfo, "frame": _CGRectMakeCopy(sheetFrame), "returnCode": -1, + "opened": NO}; + + [self _attachSheetWindow]; +} + +/* @ignore + Called to animate the sheet in. The timer seems to solve a bug where sheets would + be partially animated under certain conditions. +*/ +- (void)_attachSheetWindow +{ + _sheetContext["isAttached"] = YES; + + // it would be ideal to block here and spin an event loop, until attach is complete + [CPTimer scheduledTimerWithTimeInterval:0.0 + target:self + selector:@selector(_sheetShouldAnimateIn:) + userInfo:nil + repeats:NO]; +} + +/* @ignore + Called to end the sheet. Note that orderOut: is needed to animate the sheet out, as in Cocoa. + The sheet isn't completely gone until _cleanupSheetWindow gets called. +*/ +- (void)_endSheet +{ + var delegate = _sheetContext["modalDelegate"], + endSelector = _sheetContext["endSelector"]; + + // If the sheet has been ordered out, defer didEndSelector until after sheet animates out. + // This must be done since we cannot block and wait for the animation to complete. + if (delegate && endSelector) + { + if (_sheetContext["isAttached"]) + objj_msgSend(delegate, endSelector, _sheetContext["sheet"], _sheetContext["returnCode"], + _sheetContext["contextInfo"]); + else + _sheetContext["deferDidEndSelector"] = YES; + } +} + +/* @ignore + Called to animate the sheet out. If called while animating in, schedules an animate + out at completion +*/ +- (void)_detachSheetWindow +{ + _sheetContext["isAttached"] = NO; + + // it would be ideal to block here and spin the event loop, until attach is complete + [CPTimer scheduledTimerWithTimeInterval:0.0 + target:self + selector:@selector(_sheetShouldAnimateOut:) + userInfo:nil + repeats:NO]; +} + +/* @ignore + Called to cleanup sheet, when we are definitely done with it +*/ +- (void)_cleanupSheetWindow +{ + var sheet = _sheetContext["sheet"], + lastFrame = _sheetContext["frame"], + deferDidEnd = _sheetContext["deferDidEndSelector"]; + + [sheet setFrame:lastFrame]; + [self _restoreMasksForView:[sheet contentView]]; + + // if the parent window is modal, the sheet started its own modal session + if (sheet._isModal) + [CPApp stopModal]; + + // restore the state of window before it was sheetified + [sheet._windowView _enableSheet:NO]; + + // close it + sheet._isSheet = NO; + [sheet orderOut:self]; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidEndSheetNotification object:self]; + + if (deferDidEnd) + { + var delegate = _sheetContext["modalDelegate"], + selector = _sheetContext["endSelector"], + returnCode = _sheetContext["returnCode"], + contextInfo = _sheetContext["contextInfo"]; + + // context must be destroyed, since didEnd might want to attach another sheet + _sheetContext = nil; + sheet._parentView = nil; + + objj_msgSend(delegate, selector, sheet, returnCode, contextInfo); + } + else + { + _sheetContext = nil; + sheet._parentView = nil; + } +} + +/* @ignore */ +- (void)animationDidEnd:(id)anim +{ + var sheet = _sheetContext["sheet"]; + + if (anim._window != sheet) + return; + + [CPTimer scheduledTimerWithTimeInterval:0.0 + target:self + selector:@selector(_sheetAnimationDidEnd:) + userInfo:nil + repeats:NO]; +} + +/* @ignore */ +- (void)_sheetShouldAnimateIn:(CPTimer)timer +{ + // can't open sheet while opening or closing animation is going on + if (_sheetContext["isOpening"] || + _sheetContext["isClosing"]) + return; + + var sheet = _sheetContext["sheet"], + sheetFrame = [sheet frame], + frame = [self frame]; + + [self _setUpMasksForView:[sheet contentView]]; + + sheet._isSheet = YES; + sheet._parentView = self; + + var originx = frame.origin.x + FLOOR((frame.size.width - sheetFrame.size.width) / 2), + originy = frame.origin.y + [_contentView frame].origin.y, + startFrame = _CGRectMake(originx, originy, sheetFrame.size.width, 0), + endFrame = _CGRectMake(originx, originy, sheetFrame.size.width, sheetFrame.size.height); + + [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillBeginSheetNotification object:self]; + + // if sheet is attached to a modal window, the sheet runs + // as if itself and the parent window are modal + sheet._isModal = NO; + + if ([CPApp modalWindow] === self) + { + [CPApp runModalForWindow:sheet]; + sheet._isModal = YES; + } + + [sheet orderFront:self]; + [sheet setFrame:startFrame display:YES animate:NO]; + + _sheetContext["opened"] = YES; + _sheetContext["shouldClose"] = NO; + _sheetContext["isOpening"] = YES; + + [sheet _setFrame:endFrame delegate:self duration:[self animationResizeTime:endFrame] curve:CPAnimationEaseOut]; + + // NOTE: cocoa doesn't make window key until animation is done, but a + // keypress while animating eventually gets to the window. Therefore, + // there must be a runloop specifically designed for sheets? + [sheet makeKeyWindow]; +} + +/* @ignore */ +- (void)_sheetShouldAnimateOut:(CPTimer)timer +{ + var sheet = _sheetContext["sheet"], + startFrame = [sheet frame], + endFrame = _CGRectMakeCopy(startFrame); + + if (_sheetContext["isOpening"]) + { + // allow sheet to be closed while opening, it will close when animate in completes + _sheetContext["shouldClose"] = YES; + return; + } + + if (_sheetContext["isClosing"]) + return; + + _sheetContext["opened"] = NO; + _sheetContext["frame"] = startFrame; + _sheetContext["isClosing"] = YES; + + // the parent window can be orderedOut to disable the sheet animate out, as in Cocoa + if ([self isVisible]) + { + endFrame.size.height = 0; + [self _setUpMasksForView:[sheet contentView]]; + [sheet _setFrame:endFrame delegate:self duration:[self animationResizeTime:endFrame] curve:CPAnimationEaseIn]; + } + else + { + [self _sheetAnimationDidEnd:nil]; + } +} + +/* @ignore */ +- (void)_sheetAnimationDidEnd:(CPTimer)timer +{ + var sheet = _sheetContext["sheet"]; + + _sheetContext["isOpening"] = NO; + _sheetContext["isClosing"] = NO; + + if (_sheetContext["opened"] === YES) + { + // sheet is open and completely visible + [self _restoreMasksForView:[sheet contentView]]; + + // we wanted to close the sheet while it animated in, do that now + if (_sheetContext["shouldClose"] === YES) + [self _detachSheetWindow]; + } + else + { + // sheet is closed and not visible + [self _cleanupSheetWindow]; + } +} + +- (void)_setUpMasksForView:(CPView)aView +{ + var views = [aView subviews]; + + [views addObject:aView]; + + for (var i = 0, count = [views count]; i < count; i++) + { + var view = [views objectAtIndex:i], + mask = [view autoresizingMask], + maskToAdd = (mask & CPViewMinYMargin) ? 128 : CPViewMinYMargin; + + [view setAutoresizingMask:(mask | maskToAdd)]; + } +} + +- (void)_restoreMasksForView:(CPView)aView +{ + var views = [aView subviews]; + + [views addObject:aView]; + + for (var i = 0, count = [views count]; i < count; i++) + { + var view = [views objectAtIndex:i], + mask = [view autoresizingMask], + maskToRemove = (mask & 128) ? 128 : CPViewMinYMargin; + + [view setAutoresizingMask:(mask & (~ maskToRemove))]; + } +} + +/*! + Returns the window's attached sheet. +*/ +- (CPWindow)attachedSheet +{ + if (_sheetContext === nil) + return nil; + + return _sheetContext["sheet"]; +} + +/*! + Returns \c YES if the window has ever run as a sheet. +*/ +- (BOOL)isSheet +{ + return _isSheet; +} + +// +/* + Used privately. + @ignore +*/ +- (BOOL)becomesKeyOnlyIfNeeded +{ + return NO; +} + +/*! + Returns \c YES if the receiver is able to receive input events + even when a modal session is active. +*/ +- (BOOL)worksWhenModal +{ + return NO; +} + +- (BOOL)performKeyEquivalent:(CPEvent)anEvent +{ + // FIXME: should we be starting at the root, in other words _windowView? + // The evidence seems to point to no... + return [_contentView performKeyEquivalent:anEvent]; +} + +- (void)keyDown:(CPEvent)anEvent +{ + // It's not clear why we do performKeyEquivalent again here... + // Perhaps to allow something to happen between sendEvent: and keyDown:? + if ([anEvent _couldBeKeyEquivalent] && [self performKeyEquivalent:anEvent]) + return; + + // Apple's documentation is inconsistent with their behavior here. According to the docs + // an event going of the responder chain is passed to the input system as a last resort. + // However, the only methods I could get Cocoa to call automatically are + // moveUp: moveDown: moveLeft: moveRight: pageUp: pageDown: and complete: + // Unhandled events just travel further up the responder chain _past_ the window. + if (![self _processKeyboardUIKey:anEvent]) + [super keyDown:anEvent]; +} + +/* + @ignore + Interprets the key event for action messages and sends the action message down the responder chain + Cocoa only sends moveDown:, moveUp:, moveLeft:, moveRight:, pageUp:, pageDown: and complete: messages. + We deviate from this by sending (the default) scrollPageUp:, scrollPageDown:, scrollToBeginningOfDocument: and scrollToEndOfDocument: for pageUp, pageDown, home and end keys. + @param anEvent the event to handle. + @return YES if the key event was handled, NO if no responder handled the key event +*/ +- (BOOL)_processKeyboardUIKey:(CPEvent)anEvent +{ + var character = [anEvent charactersIgnoringModifiers]; + + if (![CPWindowActionMessageKeys containsObject:character]) + return NO; + + var selectors = [CPKeyBinding selectorsForKey:character modifierFlags:0]; + + if ([selectors count] <= 0) + return NO; + + if (character !== CPEscapeFunctionKey) + { + var selector = [selectors objectAtIndex:0]; + return [[self firstResponder] tryToPerform:selector with:self]; + } + else + { + /* + Cocoa sends complete: for the escape key (instead of the default cancelOperation:). This is also the only action that is not sent directly to the first responder, but through doCommandBySelector. The difference is that doCommandBySelector: will also send the action to the window and application delegates. + */ + [[self firstResponder] doCommandBySelector:@selector(complete:)]; + } + + return NO; +} + +- (void)_dirtyKeyViewLoop +{ + if (_autorecalculatesKeyViewLoop) + _keyViewLoopIsDirty = YES; +} + +/* + Recursively traverse an array of views (depth last) until we find one that has a next or previous key view set. Return nil if none can be found. + + We don't use _viewsSortedByPosition here because it is wasteful to enumerate the entire view hierarchy when we will probably find a key view at the top level. +*/ +- (BOOL)_hasKeyViewLoop:(CPArray)theViews +{ + var i, + count = [theViews count]; + + for (i = 0; i < count; ++i) + { + var view = theViews[i]; + + if ([view nextKeyView] || [view previousKeyView]) + return YES; + } + + for (i = 0; i < count; ++i) + { + var subviews = [theViews[i] subviews]; + + if ([subviews count] && [self _hasKeyViewLoop:subviews]) + return YES; + } + + return NO; +} + +/*! + Recalculates the key view loop, based on geometric position. + Note that the Cocoa documentation says that this method only marks the loop + as dirty, the recalculation is not done until the next or previous key view + of the window is requested. In reality, Cocoa does recalculate the loop + when this method is called. +*/ +- (void)recalculateKeyViewLoop +{ + [self _doRecalculateKeyViewLoop]; +} + +- (CPArray)_viewsSortedByPosition +{ + var views = [CPArray arrayWithObject:_contentView]; + + views = views.concat([self _subviewsSortedByPosition:[_contentView subviews]]); + + return views; +} + +- (CPArray)_subviewsSortedByPosition:(CPArray)theSubviews +{ + /* + We first sort the subviews according to geometric order. + Then we go through each subview, and if it has subviews, + they are sorted and inserted after the superview. This + is done recursively. + */ + theSubviews = [theSubviews copy]; + [theSubviews sortUsingFunction:keyViewComparator context:nil]; + + var sortedViews = []; + + for (var i = 0, count = [theSubviews count]; i < count; ++i) + { + var view = theSubviews[i], + subviews = [view subviews]; + + sortedViews.push(view); + + if ([subviews count]) + sortedViews = sortedViews.concat([self _subviewsSortedByPosition:subviews]); + } + + return sortedViews; +} + +- (void)_doRecalculateKeyViewLoop +{ + var views = [self _viewsSortedByPosition]; + + for (var index = 0, count = [views count]; index < count; ++index) + [views[index] setNextKeyView:views[(index + 1) % count]]; + + _keyViewLoopIsDirty = NO; +} + +- (void)setAutorecalculatesKeyViewLoop:(BOOL)shouldRecalculate +{ + if (_autorecalculatesKeyViewLoop === shouldRecalculate) + return; + + _autorecalculatesKeyViewLoop = shouldRecalculate; +} + +- (BOOL)autorecalculatesKeyViewLoop +{ + return _autorecalculatesKeyViewLoop; +} + +- (void)selectNextKeyView:(id)sender +{ + if (_keyViewLoopIsDirty) + [self _doRecalculateKeyViewLoop]; + + var nextValidKeyView = nil; + + if ([_firstResponder isKindOfClass:[CPView class]]) + nextValidKeyView = [_firstResponder nextValidKeyView]; + + if (!nextValidKeyView) + { + if ([_initialFirstResponder acceptsFirstResponder]) + nextValidKeyView = _initialFirstResponder; + else + nextValidKeyView = [_initialFirstResponder nextValidKeyView]; + } + + if (nextValidKeyView) + [self makeFirstResponder:nextValidKeyView]; +} + +- (void)selectPreviousKeyView:(id)sender +{ + if (_keyViewLoopIsDirty) + [self _doRecalculateKeyViewLoop]; + + var previousValidKeyView = nil; + + if ([_firstResponder isKindOfClass:[CPView class]]) + previousValidKeyView = [_firstResponder previousValidKeyView]; + + if (!previousValidKeyView) + { + if ([_initialFirstResponder acceptsFirstResponder]) + previousValidKeyView = _initialFirstResponder; + else + previousValidKeyView = [_initialFirstResponder previousValidKeyView]; + } + + if (previousValidKeyView) + [self makeFirstResponder:previousValidKeyView]; +} + +- (void)selectKeyViewFollowingView:(CPView)aView +{ + if (_keyViewLoopIsDirty) + [self _doRecalculateKeyViewLoop]; + + var nextValidKeyView = [aView nextValidKeyView]; + + if ([nextValidKeyView isKindOfClass:[CPView class]]) + [self makeFirstResponder:nextValidKeyView]; +} + +- (void)selectKeyViewPrecedingView:(CPView)aView +{ + if (_keyViewLoopIsDirty) + [self _doRecalculateKeyViewLoop]; + + var previousValidKeyView = [aView previousValidKeyView]; + + if ([previousValidKeyView isKindOfClass:[CPView class]]) + [self makeFirstResponder:previousValidKeyView]; +} + +/*! + Sets the default button for the window. + Note: this method is deprecated use setDefaultButton: instead. + @param aButton - The button that should become default. +*/ +- (void)setDefaultButtonCell:(CPButton)aButton +{ + [self setDefaultButton:aButton]; +} + +/*! + Returns the default button of the receiver. + NOTE: This method is deprecated. Use defaultButton instead. +*/ +- (CPButton)defaultButtonCell +{ + return [self defaultButton]; +} + +/*! + Sets the default button for the window. + This is equivalent to setting the the key equivalent of the button to "return". + Additionally this will turn your button blue (with the Aristo theme). + @param aButton - The button that should become default. +*/ +- (void)setDefaultButton:(CPButton)aButton +{ + if (_defaultButton === aButton) + return; + + if ([_defaultButton keyEquivalent] === CPCarriageReturnCharacter) + [_defaultButton setKeyEquivalent:nil]; + + _defaultButton = aButton; + + if ([_defaultButton keyEquivalent] !== CPCarriageReturnCharacter) + [_defaultButton setKeyEquivalent:CPCarriageReturnCharacter]; +} + +/*! + Returns the default button of the receiver. +*/ +- (CPButton)defaultButton +{ + return _defaultButton; +} + +/*! + Sets the default button key equivalent to "return". +*/ +- (void)enableKeyEquivalentForDefaultButton +{ + _defaultButtonEnabled = YES; +} + +/*! + Sets the default button key equivalent to "return". + NOTE: this method is deprecated. Use enableKeyEquivalentForDefaultButton instead. +*/ +- (void)enableKeyEquivalentForDefaultButtonCell +{ + [self enableKeyEquivalentForDefaultButton]; +} + +/*! + Removes the key equivalent for the default button. +*/ +- (void)disableKeyEquivalentForDefaultButton +{ + _defaultButtonEnabled = NO; +} + +/*! + Removes the key equivalent for the default button. + Note: this method is deprecated. Use disableKeyEquivalentForDefaultButton instead. +*/ +- (void)disableKeyEquivalentForDefaultButtonCell +{ + [self disableKeyEquivalentForDefaultButton]; +} + +@end + +var keyViewComparator = function(lhs, rhs, context) +{ + var lhsBounds = [lhs convertRect:[lhs bounds] toView:nil], + rhsBounds = [rhs convertRect:[rhs bounds] toView:nil], + lhsY = _CGRectGetMinY(lhsBounds), + rhsY = _CGRectGetMinY(rhsBounds), + lhsX = _CGRectGetMinX(lhsBounds), + rhsX = _CGRectGetMinX(rhsBounds), + intersectsVertically = MIN(_CGRectGetMaxY(lhsBounds), _CGRectGetMaxY(rhsBounds)) - MAX(lhsY, rhsY); + + // If two views are "on the same line" (intersect vertically), then rely on the x comparison. + if (intersectsVertically > 0) + { + if (lhsX < rhsX) + return CPOrderedAscending; + + if (lhsX === rhsX) + return CPOrderedSame; + + return CPOrderedDescending; + } + + if (lhsY < rhsY) + return CPOrderedAscending; + + if (lhsY === rhsY) + return CPOrderedSame; + + return CPOrderedDescending; +}; + +@implementation CPWindow (MenuBar) + +- (void)_synchronizeMenuBarTitleWithWindowTitle +{ + // Windows with Documents automatically update the native window title and the menu bar title. + if (![_windowController document] || ![self isMainWindow]) + return; + + [CPMenu setMenuBarTitle:_title]; +} + +@end + +@implementation CPWindow (BridgeSupport) + +/* + @ignore +*/ +- (void)resizeWithOldPlatformWindowSize:(CGSize)aSize +{ + if ([self isFullPlatformWindow]) + return [self setFrame:[_platformWindow visibleFrame]]; + + if (_autoresizingMask == CPWindowNotSizable) + return; + + var frame = [_platformWindow contentBounds], + newFrame = _CGRectMakeCopy(_frame), + dX = (_CGRectGetWidth(frame) - aSize.width) / + (((_autoresizingMask & CPWindowMinXMargin) ? 1 : 0) + (_autoresizingMask & CPWindowWidthSizable ? 1 : 0) + (_autoresizingMask & CPWindowMaxXMargin ? 1 : 0)), + dY = (_CGRectGetHeight(frame) - aSize.height) / + ((_autoresizingMask & CPWindowMinYMargin ? 1 : 0) + (_autoresizingMask & CPWindowHeightSizable ? 1 : 0) + (_autoresizingMask & CPWindowMaxYMargin ? 1 : 0)); + + if (_autoresizingMask & CPWindowMinXMargin) + newFrame.origin.x += dX; + if (_autoresizingMask & CPWindowWidthSizable) + newFrame.size.width += dX; + + if (_autoresizingMask & CPWindowMinYMargin) + newFrame.origin.y += dY; + if (_autoresizingMask & CPWindowHeightSizable) + newFrame.size.height += dY; + + [self setFrame:newFrame]; +} + +/* + @ignore +*/ +- (void)setAutoresizingMask:(unsigned)anAutoresizingMask +{ + _autoresizingMask = anAutoresizingMask; +} + +/* + @ignore +*/ +- (unsigned)autoresizingMask +{ + return _autoresizingMask; +} + +/*! + Converts aPoint from the window coordinate system to the global coordinate system. +*/ +- (CGPoint)convertBaseToGlobal:(CGPoint)aPoint +{ + return [CPPlatform isBrowser] ? [self convertBaseToPlatformWindow:aPoint] : [self convertBaseToScreen:aPoint]; +} + +/*! + Converts aPoint from the global coordinate system to the window coordinate system. +*/ +- (CGPoint)convertGlobalToBase:(CGPoint)aPoint +{ + return [CPPlatform isBrowser] ? [self convertPlatformWindowToBase:aPoint] : [self convertScreenToBase:aPoint]; +} + +/*! + Converts aPoint from the window coordinate system to the coordinate system of the parent platform window. +*/ +- (CGPoint)convertBaseToPlatformWindow:(CGPoint)aPoint +{ + if ([self _sharesChromeWithPlatformWindow]) + return _CGPointMakeCopy(aPoint); + + var origin = [self frame].origin; + + return _CGPointMake(aPoint.x + origin.x, aPoint.y + origin.y); +} + +/*! + Converts aPoint from the parent platform window coordinate system to the window's coordinate system. +*/ +- (CGPoint)convertPlatformWindowToBase:(CGPoint)aPoint +{ + if ([self _sharesChromeWithPlatformWindow]) + return _CGPointMakeCopy(aPoint); + + var origin = [self frame].origin; + + return _CGPointMake(aPoint.x - origin.x, aPoint.y - origin.y); +} + +- (CGPoint)convertScreenToBase:(CGPoint)aPoint +{ + return [self convertPlatformWindowToBase:[_platformWindow convertScreenToBase:aPoint]]; +} + +- (CGPoint)convertBaseToScreen:(CGPoint)aPoint +{ + return [_platformWindow convertBaseToScreen:[self convertBaseToPlatformWindow:aPoint]]; +} + +- (void)_setSharesChromeWithPlatformWindow:(BOOL)shouldShareFrameWithPlatformWindow +{ + // We canna' do it captain! We just don't have the power! + if (shouldShareFrameWithPlatformWindow && [CPPlatform isBrowser]) + return; + + _sharesChromeWithPlatformWindow = shouldShareFrameWithPlatformWindow; + + [self _updateShadow]; +} + +- (BOOL)_sharesChromeWithPlatformWindow +{ + return _sharesChromeWithPlatformWindow; +} + +// Undo and Redo Support +/*! + Returns the window's undo manager. +*/ +- (CPUndoManager)undoManager +{ + // If we've ever created an undo manager, return it. + if (_undoManager) + return _undoManager; + + // If not, check to see if the document has one. + var documentUndoManager = [[_windowController document] undoManager]; + + if (documentUndoManager) + return documentUndoManager; + + // If not, check to see if the delegate has one. + if (_delegateRespondsToWindowWillReturnUndoManagerSelector) + return [_delegate windowWillReturnUndoManager:self]; + + // If not, create one. + if (!_undoManager) + _undoManager = [[CPUndoManager alloc] init]; + + return _undoManager; +} + +/*! + Sends the undo manager an \c -undo: message. + @param aSender the object requesting this +*/ +- (void)undo:(id)aSender +{ + [[self undoManager] undo]; +} + +/*! + Sends the undo manager a \c -redo: message. + @param aSender the object requesting this +*/ +- (void)redo:(id)aSender +{ + [[self undoManager] redo]; +} + +- (BOOL)containsPoint:(CGPoint)aPoint +{ + return _CGRectContainsPoint(_frame, aPoint); +} + +- (BOOL)_isValidMousePoint:(CGPoint)aPoint +{ + // If we are using the new resizing mode, mouse events are valid + // outside the window's frame for non-full platform windows. + var mouseFrame = (!_isFullPlatformWindow && (_styleMask & CPResizableWindowMask) && (CPWindowResizeStyle === CPWindowResizeStyleModern)) ? _CGRectInset(_frame, -CPWindowResizeSlop, -CPWindowResizeSlop) : _frame; + + return _CGRectContainsPoint(mouseFrame, aPoint); +} + +@end + +@implementation CPWindow (Deprecated) +/*! + Sets the CPWindow to fill the whole browser window. + NOTE: this method has been deprecated in favor of setFullPlatformWindow: +*/ +- (void)setFullBridge:(BOOL)shouldBeFullBridge +{ + [self setFullPlatformWindow:shouldBeFullBridge]; +} + +/*! + Returns YES if the window fills the full browser window, otherwise NO. + NOTE: this method has been deprecated in favor of isFullPlatformWindow. +*/ +- (BOOL)isFullBridge +{ + return [self isFullPlatformWindow]; +} + +/* + @ignore +*/ +- (CGPoint)convertBaseToBridge:(CGPoint)aPoint +{ + return [self convertBaseToPlatformWindow:aPoint]; +} + +/* + @ignore +*/ +- (CGPoint)convertBridgeToBase:(CGPoint)aPoint +{ + return [self convertPlatformWindowToBase:aPoint]; +} + +@end + +var interpolate = function(fromValue, toValue, progress) +{ + return fromValue + (toValue - fromValue) * progress; +}; + +/* @ignore */ +@implementation _CPWindowFrameAnimation : CPAnimation +{ + CPWindow _window; + + CGRect _startFrame; + CGRect _targetFrame; +} + +- (id)initWithWindow:(CPWindow)aWindow targetFrame:(CGRect)aTargetFrame +{ + self = [super initWithDuration:[aWindow animationResizeTime:aTargetFrame] animationCurve:CPAnimationLinear]; + + if (self) + { + _window = aWindow; + + _targetFrame = _CGRectMakeCopy(aTargetFrame); + _startFrame = _CGRectMakeCopy([_window frame]); + } + + return self; +} + +- (void)startAnimation +{ + [super startAnimation]; + + _window._isAnimating = YES; +} + +- (void)setCurrentProgress:(float)aProgress +{ + [super setCurrentProgress:aProgress]; + + var value = [self currentValue]; + + if (value == 1.0) + _window._isAnimating = NO; + + var newFrame = _CGRectMake( + interpolate(_CGRectGetMinX(_startFrame), _CGRectGetMinX(_targetFrame), value), + interpolate(_CGRectGetMinY(_startFrame), _CGRectGetMinY(_targetFrame), value), + interpolate(_CGRectGetWidth(_startFrame), _CGRectGetWidth(_targetFrame), value), + interpolate(_CGRectGetHeight(_startFrame), _CGRectGetHeight(_targetFrame), value)); + + [_window setFrame:newFrame display:YES animate:NO]; +} + +@end + +function _CPWindowFullPlatformWindowSessionMake(aWindowView, aContentRect, hasShadow, aLevel) +{ + return { windowView:aWindowView, contentRect:aContentRect, hasShadow:hasShadow, level:aLevel }; +} + +CPStandardWindowShadowStyle = 0; +CPMenuWindowShadowStyle = 1; +CPPanelWindowShadowStyle = 2; +CPCustomWindowShadowStyle = 3; diff --git a/AppKit/CPWindow/_CPWindowView.j b/AppKit/CPWindow/_CPWindowView.j index af367ed0f..e051ee9d5 100644 --- a/AppKit/CPWindow/_CPWindowView.j +++ b/AppKit/CPWindow/_CPWindowView.j @@ -23,6 +23,18 @@ @import "CPImageView.j" @import "CPView.j" +@class CPCursor +@class CPMenu +@class CPPlatformWindow + +@global CPApp +@global CPHUDBackgroundWindowMask +@global CPResizableWindowMask +@global CPWindowResizeSlop +@global CPWindowResizeStyle +@global CPWindowResizeStyleLegacy +@global CPWindowResizeStyleModern + var _CPWindowViewCornerResizeRectWidth = 10, _CPWindowViewResizeRegionNone = -1, diff --git a/AppKit/CPWindowController.j b/AppKit/CPWindowController.j index f8c0a2149..feb4ae668 100644 --- a/AppKit/CPWindowController.j +++ b/AppKit/CPWindowController.j @@ -27,6 +27,13 @@ @import "CPWindow.j" @import "CPDocument.j" +@class CPDocument + +@global CPApp +@global CPDocumentWillSaveNotification +@global CPDocumentDidSaveNotification +@global CPDocumentDidFailToSaveNotification + /*! @ingroup appkit diff --git a/AppKit/Cib/_CPCibCustomObject.j b/AppKit/Cib/_CPCibCustomObject.j index 165b24153..89652fe84 100644 --- a/AppKit/Cib/_CPCibCustomObject.j +++ b/AppKit/Cib/_CPCibCustomObject.j @@ -1,6 +1,8 @@ @import
+CPToolbarItemVisibilityPriorityStandard +CPToolbarItemVisibilityPriorityLow +CPToolbarItemVisibilityPriorityHigh +CPToolbarItemVisibilityPriorityUser ++*/ +- (int)visibilityPriority +{ + return _visibilityPriority; +} + +/*! + Sets the item's visibility priority. The value must be one of: +
+CPToolbarItemVisibilityPriorityStandard +CPToolbarItemVisibilityPriorityLow +CPToolbarItemVisibilityPriorityHigh +CPToolbarItemVisibilityPriorityUser ++ @param aVisiblityPriority the priority +*/ +- (void)setVisibilityPriority:(int)aVisibilityPriority +{ + _visibilityPriority = aVisibilityPriority; +} + +- (void)validate +{ + var action = [self action], + target = [self target]; + + // View items do not do any target-action analysis. + if (_view) + { + if ([target respondsToSelector:@selector(validateToolbarItem:)]) + { + var shouldBeEnabled = [target validateToolbarItem:self]; + if (_isEnabled !== shouldBeEnabled) + [self setEnabled:shouldBeEnabled]; + } + + return; + } + + if (!action) + { + if (_isEnabled) + return [self setEnabled:NO]; + return; + } + + if (target && ![target respondsToSelector:action]) + { + if (_isEnabled) + return [self setEnabled:NO]; + return; + } + + target = [CPApp targetForAction:action to:target from:self]; + + if (!target) + { + if (_isEnabled) + return [self setEnabled:NO]; + return; + } + + if ([target respondsToSelector:@selector(validateToolbarItem:)]) + { + var shouldBeEnabled = [target validateToolbarItem:self]; + if (_isEnabled !== shouldBeEnabled) + [self setEnabled:shouldBeEnabled]; + } + else + { + if (!_isEnabled) + [self setEnabled:YES]; + } +} + +- (BOOL)autovalidates +{ + return _autovalidates; +} + +- (void)setAutovalidates:(BOOL)shouldAutovalidate +{ + _autovalidates = !!shouldAutovalidate; +} + +@end + +var CPToolbarItemItemIdentifierKey = @"CPToolbarItemItemIdentifierKey", + CPToolbarItemLabelKey = @"CPToolbarItemLabelKey", + CPToolbarItemPaletteLabelKey = @"CPToolbarItemPaletteLabelKey", + CPToolbarItemToolTipKey = @"CPToolbarItemToolTipKey", + CPToolbarItemTagKey = @"CPToolbarItemTagKey", + CPToolbarItemTargetKey = @"CPToolbarItemTargetKey", + CPToolbarItemActionKey = @"CPToolbarItemActionKey", + CPToolbarItemEnabledKey = @"CPToolbarItemEnabledKey", + CPToolbarItemImageKey = @"CPToolbarItemImageKey", + CPToolbarItemAlternateImageKey = @"CPToolbarItemAlternateImageKey", + CPToolbarItemViewKey = @"CPToolbarItemViewKey", + CPToolbarItemMinSizeKey = @"CPToolbarItemMinSizeKey", + CPToolbarItemMaxSizeKey = @"CPToolbarItemMaxSizeKey", + CPToolbarItemVisibilityPriorityKey = @"CPToolbarItemVisibilityPriorityKey", + CPToolbarItemAutovalidatesKey = @"CPToolbarItemAutovalidatesKey"; + +@implementation CPToolbarItem (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + if (self) + { + _itemIdentifier = [aCoder decodeObjectForKey:CPToolbarItemItemIdentifierKey]; + + _minSize = [aCoder decodeSizeForKey:CPToolbarItemMinSizeKey]; + _maxSize = [aCoder decodeSizeForKey:CPToolbarItemMaxSizeKey]; + + [self setLabel:[aCoder decodeObjectForKey:CPToolbarItemLabelKey]]; + [self setPaletteLabel:[aCoder decodeObjectForKey:CPToolbarItemPaletteLabelKey]]; + [self setToolTip:[aCoder decodeObjectForKey:CPToolbarItemToolTipKey]]; + + [self setTag:[aCoder decodeObjectForKey:CPToolbarItemTagKey]]; + [self setTarget:[aCoder decodeObjectForKey:CPToolbarItemTargetKey]]; + [self setAction:CPSelectorFromString([aCoder decodeObjectForKey:CPToolbarItemActionKey])]; + + [self setEnabled:[aCoder decodeBoolForKey:CPToolbarItemEnabledKey]]; + + [self setImage:[aCoder decodeObjectForKey:CPToolbarItemImageKey]]; + [self setAlternateImage:[aCoder decodeObjectForKey:CPToolbarItemAlternateImageKey]]; + + [self setView:[aCoder decodeObjectForKey:CPToolbarItemViewKey]]; + + [self setVisibilityPriority:[aCoder decodeIntForKey:CPToolbarItemVisibilityPriorityKey]]; + [self setAutovalidates:[aCoder decodeBoolForKey:CPToolbarItemAutovalidatesKey]]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeObject:_itemIdentifier forKey:CPToolbarItemItemIdentifierKey]; + + [aCoder encodeObject:[self label] forKey:CPToolbarItemLabelKey]; + [aCoder encodeObject:[self paletteLabel] forKey:CPToolbarItemPaletteLabelKey]; + + [aCoder encodeObject:[self toolTip] forKey:CPToolbarItemToolTipKey]; + + [aCoder encodeObject:[self tag] forKey:CPToolbarItemTagKey]; + [aCoder encodeObject:[self target] forKey:CPToolbarItemTargetKey]; + [aCoder encodeObject:[self action] forKey:CPToolbarItemActionKey]; + + [aCoder encodeObject:[self isEnabled] forKey:CPToolbarItemEnabledKey]; + + [aCoder encodeObject:[self image] forKey:CPToolbarItemImageKey]; + [aCoder encodeObject:[self alternateImage] forKey:CPToolbarItemAlternateImageKey]; + + [aCoder encodeObject:[self view] forKey:CPToolbarItemViewKey]; + + [aCoder encodeSize:[self minSize] forKey:CPToolbarItemMinSizeKey]; + [aCoder encodeSize:[self maxSize] forKey:CPToolbarItemMaxSizeKey]; + + [aCoder encodeObject:[self visibilityPriority] forKey:CPToolbarItemVisibilityPriorityKey]; + [aCoder encodeBool:[self autovalidates] forKey:CPToolbarItemAutovalidatesKey]; +} + +@end + +@implementation CPToolbarItem (CPCopying) + +- (id)copy +{ + var copy = [[[self class] alloc] initWithItemIdentifier:_itemIdentifier]; + + if (_view) + [copy setView:[CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:_view]]]; + + [copy _setToolbar:_toolbar]; + + [copy setLabel:_label]; + [copy setPaletteLabel:_paletteLabel]; + [copy setToolTip:[self toolTip]]; + + [copy setTag:[self tag]]; + [copy setTarget:[self target]]; + [copy setAction:[self action]]; + + [copy setEnabled:[self isEnabled]]; + + [copy setImage:[self image]]; + [copy setAlternateImage:[self alternateImage]]; + + [copy setMinSize:_minSize]; + [copy setMaxSize:_maxSize]; + + [copy setVisibilityPriority:[self visibilityPriority]]; + [copy setAutovalidates:[self autovalidates]]; + + return copy; +} + +@end + +// Standard toolbar identifiers + +@implementation CPToolbarItem (Standard) + +/* @ignore */ ++ (CPToolbarItem)_standardItemWithItemIdentifier:(CPString)anItemIdentifier +{ + switch (anItemIdentifier) + { + case CPToolbarSeparatorItemIdentifier: return [_CPToolbarSeparatorItem new]; + case CPToolbarSpaceItemIdentifier: return [_CPToolbarSpaceItem new]; + case CPToolbarFlexibleSpaceItemIdentifier: return [_CPToolbarFlexibleSpaceItem new]; + case CPToolbarShowColorsItemIdentifier: return [_CPToolbarShowColorsItem new]; + case CPToolbarShowFontsItemIdentifier: return nil; + case CPToolbarCustomizeToolbarItemIdentifier: return nil; + case CPToolbarPrintItemIdentifier: return nil; + } + + return nil; +} + +@end + +/*@import "_CPToolbarFlexibleSpaceItem.j" +@import "_CPToolbarShowColorsItem.j" +@import "_CPToolbarSeparatorItem.j" +@import "_CPToolbarSpaceItem.j" +*/ diff --git a/AppKit/_CPToolbarSeparatorItem.j b/AppKit/_CPToolbarSeparatorItem.j index 7b4059565..4b50ba17f 100644 --- a/AppKit/_CPToolbarSeparatorItem.j +++ b/AppKit/_CPToolbarSeparatorItem.j @@ -20,7 +20,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -@import "CPToolbarItem.j" +@import "_CPToolbarItem.j" @implementation _CPToolbarSeparatorItem : CPToolbarItem diff --git a/AppKit/_CPToolbarShowColorsItem.j b/AppKit/_CPToolbarShowColorsItem.j index 42c76ecf2..012ea37c5 100644 --- a/AppKit/_CPToolbarShowColorsItem.j +++ b/AppKit/_CPToolbarShowColorsItem.j @@ -21,7 +21,7 @@ */ @import "CPApplication.j" -@import "CPToolbarItem.j" +@import "_CPToolbarItem.j" @implementation _CPToolbarShowColorsItem : CPToolbarItem diff --git a/AppKit/_CPToolbarSpaceItem.j b/AppKit/_CPToolbarSpaceItem.j index a77454d15..efa587351 100644 --- a/AppKit/_CPToolbarSpaceItem.j +++ b/AppKit/_CPToolbarSpaceItem.j @@ -20,7 +20,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -@import "CPToolbarItem.j" +@import "_CPToolbarItem.j" @implementation _CPToolbarSpaceItem : CPToolbarItem diff --git a/Foundation/CPArray+KVO.j b/Foundation/CPArray+KVO.j index fc65294ef..e5c381a70 100644 --- a/Foundation/CPArray+KVO.j +++ b/Foundation/CPArray+KVO.j @@ -24,6 +24,7 @@ @import "CPNull.j" @import "_CPCollectionKVCOperators.j" +@class CPIndexSet @implementation CPObject (CPArrayKVO) @@ -347,7 +348,7 @@ while ((index = [self indexOfObject:theObject inRange:theRange]) !== CPNotFound) { [self removeObjectAtIndex:index]; - theRange = CPIntersectionRange(CPMakeRange(index, length - index), theRange); + theRange = CPIntersectionRange(CPMakeRange(index, self.length - index), theRange); } } } @@ -427,7 +428,7 @@ [CPException raise:CPInvalidArgumentException reason:"called valueForKey: on an array with a complex key (" + aKey + "). use valueForKeyPath:"]; if (aKey === "@count") - return length; + return self.length; return [self valueForUndefinedKey:aKey]; } diff --git a/Foundation/CPArray/CPArray.j b/Foundation/CPArray/CPArray.j old mode 100755 new mode 100644 index d835196ad..5d6505c36 --- a/Foundation/CPArray/CPArray.j +++ b/Foundation/CPArray/CPArray.j @@ -20,999 +20,5 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -@import "CPEnumerator.j" -@import "CPException.j" -@import "CPObject.j" -@import "CPRange.j" -@import "CPSortDescriptor.j" - - -CPEnumerationNormal = 0; -CPEnumerationConcurrent = 1 << 0; -CPEnumerationReverse = 1 << 1; - -CPBinarySearchingFirstEqual = 1 << 8; -CPBinarySearchingLastEqual = 1 << 9; -CPBinarySearchingInsertionIndex = 1 << 10; - -var concat = Array.prototype.concat, - join = Array.prototype.join, - push = Array.prototype.push; - -#define FORWARD_TO_CONCRETE_CLASS()\ - if (self === _CPSharedPlaceholderArray)\ - {\ - arguments[0] = [_CPJavaScriptArray alloc];\ - return objj_msgSend.apply(this, arguments);\ - }\ - return [super init]; - -/*! - @class CPArray - @brief A mutable array backed by a JavaScript Array. - @ingroup foundation - - A mutable array class backed by a JavaScript Array. - There is also a CPMutableArray class, - but it is just a child class of this class with an - empty implementation. All mutable functionality is - implemented directly in CPArray. -*/ -@implementation CPArray : CPObject - -/*! - Returns a new uninitialized CPArray. -*/ -+ (id)alloc -{ - if (self === CPArray || self === CPMutableArray) - return [_CPPlaceholderArray alloc]; - - return [super alloc]; -} - -/*! - Returns a new initialized CPArray. -*/ -+ (id)array -{ - return [[self alloc] init]; -} - -/*! - Creates a new array containing the objects in \c anArray. - @param anArray Objects in this array will be added to the new array - @return a new CPArray of the provided objects -*/ -+ (id)arrayWithArray:(CPArray)anArray -{ - return [[self alloc] initWithArray:anArray]; -} - -/*! - Creates a new array with \c anObject in it. - @param anObject the object to be added to the array - @return a new CPArray containing a single object -*/ -+ (id)arrayWithObject:(id)anObject -{ - return [[self alloc] initWithObjects:anObject]; -} - -/*! - Creates a new CPArray containing all the objects passed as arguments to the method. - @param anObject the objects that will be added to the new array - @return a new CPArray containing the argument objects -*/ -+ (id)arrayWithObjects:(id)anObject, ... -{ - arguments[0] = [self alloc]; - arguments[1] = @selector(initWithObjects:); - - return objj_msgSend.apply(this, arguments); -} - -/*! - Creates a CPArray from a JavaScript array of objects. - @param objects the JavaScript Array - @param aCount the number of objects in the JS Array - @return a new CPArray containing the specified objects -*/ -+ (id)arrayWithObjects:(id)objects count:(unsigned)aCount -{ - return [[self alloc] initWithObjects:objects count:aCount]; -} - -/*! - Initializes the CPArray. - @return the initialized array -*/ -- (id)init -{ - FORWARD_TO_CONCRETE_CLASS(); -} - -// Creating an Array -/*! - Creates a new CPArray from \c anArray. - @param anArray objects in this array will be added to the new array - @return a new CPArray containing the objects of \c anArray -*/ -- (id)initWithArray:(CPArray)anArray -{ - FORWARD_TO_CONCRETE_CLASS(); -} - -/*! - Initializes a the array with the contents of \c anArray - and optionally performs a deep copy of the objects based on \c copyItems. - @param anArray the array to copy the data from - @param shouldCopyItems if \c YES, each object will be copied by having a \c -copy message - sent to it, and the returned object will be added to the receiver. Otherwise, no copying will be performed. - @return the initialized array of objects -*/ -- (id)initWithArray:(CPArray)anArray copyItems:(BOOL)shouldCopyItems -{ - FORWARD_TO_CONCRETE_CLASS(); -} - -/*! - initializes an array with the contents of anArray -*/ -- (id)initWithObjects:(id)anObject, ... -{ - FORWARD_TO_CONCRETE_CLASS(); -} - -/*! - Initializes the array with a JavaScript array of objects. - @param objects the array of objects to add to the receiver - @param aCount the number of objects in \c objects - @return the initialized CPArray -*/ -- (id)initWithObjects:(id)objects count:(unsigned)aCount -{ - FORWARD_TO_CONCRETE_CLASS(); -} - -// FIXME: This should be defined in CPMutableArray, not here. -- (id)initWithCapacity:(unsigned)aCapacity -{ - FORWARD_TO_CONCRETE_CLASS(); -} - -// Querying an array -/*! - Returns \c YES if the array contains \c anObject. Otherwise, it returns \c NO. - @param anObject the method checks if this object is already in the array -*/ -- (BOOL)containsObject:(id)anObject -{ - return [self indexOfObject:anObject] !== CPNotFound; -} - -- (BOOL)containsObjectIdenticalTo:(id)anObject -{ - return [self indexOfObjectIdenticalTo:anObject] !== CPNotFound; -} - -/*! - Returns the number of elements in the array -*/ -- (int)count -{ - _CPRaiseInvalidAbstractInvocation(self, _cmd); -} - -/*! - Returns the first object in the array. If the array is empty, returns \c nil -*/ -- (id)firstObject -{ - var count = [self count]; - - if (count > 0) - return [self objectAtIndex:0]; - - return nil; -} - -/*! - Returns the last object in the array. If the array is empty, returns \c nil -*/ -- (id)lastObject -{ - var count = [self count]; - - if (count <= 0) - return nil; - - return [self objectAtIndex:count - 1]; -} - -/*! - Returns the object at index \c anIndex. - @throws CPRangeException if \c anIndex is out of bounds -*/ -- (id)objectAtIndex:(int)anIndex -{ - _CPRaiseInvalidAbstractInvocation(self, _cmd); -} - -/*! - Returns the objects at \c indexes in a new CPArray. - @param indexes the set of indices - @throws CPRangeException if any of the indices is greater than or equal to the length of the array -*/ -- (CPArray)objectsAtIndexes:(CPIndexSet)indexes -{ - var index = CPNotFound, - objects = []; - - while ((index = [indexes indexGreaterThanIndex:index]) !== CPNotFound) - objects.push([self objectAtIndex:index]); - - return objects; -} - -/*! - Returns an enumerator describing the array sequentially - from the first to the last element. You should not modify - the array during enumeration. -*/ -- (CPEnumerator)objectEnumerator -{ - return [[_CPArrayEnumerator alloc] initWithArray:self]; -} - -/*! - Returns an enumerator describing the array sequentially - from the last to the first element. You should not modify - the array during enumeration. -*/ -- (CPEnumerator)reverseObjectEnumerator -{ - return [[_CPReverseArrayEnumerator alloc] initWithArray:self]; -} - -/*! - Returns the index of \c anObject in this array. - If the object is not in the array, - returns \c CPNotFound. It first attempts to find - a match using \c -isEqual:, then \c ===. - @param anObject the object to search for -*/ -- (CPUInteger)indexOfObject:(id)anObject -{ - return [self indexOfObject:anObject inRange:nil]; -} - -/*! - Returns the index of \c anObject in the array - within \c aRange. It first attempts to find - a match using \c -isEqual:, then \c ===. - @param anObject the object to search for - @param aRange the range to search within - @return the index of the object, or \c CPNotFound if it was not found. -*/ -- (CPUInteger)indexOfObject:(id)anObject inRange:(CPRange)aRange -{ - // Only use isEqual: if our object is a CPObject. - if (anObject && anObject.isa) - { - var index = aRange ? aRange.location : 0, - count = aRange ? CPMaxRange(aRange) : [self count]; - - for (; index < count; ++index) - if ([[self objectAtIndex:index] isEqual:anObject]) - return index; - - return CPNotFound; - } - - return [self indexOfObjectIdenticalTo:anObject inRange:aRange]; -} - -/*! - Returns the index of \c anObject in the array. The test for equality is done using only \c ===. - @param anObject the object to search for - @return the index of the object in the array. \c CPNotFound if the object is not in the array. -*/ -- (CPUInteger)indexOfObjectIdenticalTo:(id)anObject -{ - return [self indexOfObjectIdenticalTo:anObject inRange:nil]; -} - -/*! - Returns the index of \c anObject in the array - within \c aRange. The test for equality is - done using only \c ==. - @param anObject the object to search for - @param aRange the range to search within - @return the index of the object, or \c CPNotFound if it was not found. -*/ -- (CPUInteger)indexOfObjectIdenticalTo:(id)anObject inRange:(CPRange)aRange -{ - var index = aRange ? aRange.location : 0, - count = aRange ? CPMaxRange(aRange) : [self count]; - - for (; index < count; ++index) - if ([self objectAtIndex:index] === anObject) - return index; - - return CPNotFound; -} - -/*! - Returns the index of the first object in the receiver that passes a test in a given Javascript function. - @param predicate The function to apply to objects of the array. The function should have the signature: - @code function(object, index) @endcode - The predicate function should either return a Boolean value that indicates whether the object passed the test, - or nil to stop the search, which will return \c CPNotFound to the sender. - @return The index of the first matching object, or \c CPNotFound if there is no matching object. -*/ -- (unsigned)indexOfObjectPassingTest:(Function /*(id anObject, int idx)*/)aPredicate -{ - return [self indexOfObjectWithOptions:CPEnumerationNormal passingTest:aPredicate context:undefined]; -} - -/*! - Returns the index of the first object in the receiver that passes a test in a given Javascript function. - @param predicate The function to apply to objects of the array. The function should have the signature: - @code function(object, index, context) @endcode - The predicate function should either return a Boolean value that indicates whether the object passed the test, - or nil to stop the search, which will return \c CPNotFound to the sender. - @param context An object that contains context information you want passed to the predicate function. - @return The index of the first matching object, or \c CPNotFound if there is no matching object. -*/ -- (unsigned)indexOfObjectPassingTest:(Function /*(id anObject, int idx, id context)*/)aPredicate context:(id)aContext -{ - return [self indexOfObjectWithOptions:CPEnumerationNormal passingTest:aPredicate context:aContext]; -} - -/*! - Returns the index of the first object in the receiver that passes a test in a given Javascript function. - @param options Specifies the direction in which the array is searched. Pass CPEnumerationNormal to search forwards - or CPEnumerationReverse to search in reverse. - @param predicate The function to apply to objects of the array. The function should have the signature: - @code function(object, index) @endcode - The predicate function should either return a Boolean value that indicates whether the object passed the test, - or nil to stop the search, which will return CPNotFound to the sender. - @return The index of the first matching object, or \c CPNotFound if there is no matching object. -*/ -- (unsigned)indexOfObjectWithOptions:(CPEnumerationOptions)options passingTest:(Function /*(id anObject, int idx)*/)aPredicate -{ - return [self indexOfObjectWithOptions:options passingTest:aPredicate context:undefined]; -} - -/*! - Returns the index of the first object in the receiver that passes a test in a given Javascript function. - @param options Specifies the direction in which the array is searched. Pass CPEnumerationNormal to search forwards - or CPEnumerationReverse to search in reverse. - @param predicate The function to apply to objects of the array. The function should have the signature: - @code function(object, index, context) @endcode - The predicate function should either return a Boolean value that indicates whether the object passed the test, - or nil to stop the search, which will return CPNotFound to the sender. - @param context An object that contains context information you want passed to the predicate function. - @return The index of the first matching object, or \c CPNotFound if there is no matching object. -*/ -- (unsigned)indexOfObjectWithOptions:(CPEnumerationOptions)options passingTest:(Function /*(id anObject, int idx, id context)*/)aPredicate context:(id)aContext -{ - // We don't use an enumerator because they return nil to indicate end of enumeration, - // but nil may actually be the value we are looking for, so we have to loop over the array. - if (options & CPEnumerationReverse) - { - var index = [self count] - 1, - stop = -1, - increment = -1; - } - else - { - var index = 0, - stop = [self count], - increment = 1; - } - - for (; index !== stop; index += increment) - if (aPredicate([self objectAtIndex:index], index, aContext)) - return index; - - return CPNotFound; -} - -- (CPUInteger)indexOfObject:(id)anObject - inSortedRange:(CPRange)aRange - options:(CPBinarySearchingOptions)options - usingComparator:(Function)aComparator -{ - // FIXME: comparator is not a function - if (!aComparator) - _CPRaiseInvalidArgumentException(self, _cmd, "comparator is nil"); - - if ((options & CPBinarySearchingFirstEqual) && (options & CPBinarySearchingLastEqual)) - _CPRaiseInvalidArgumentException(self, _cmd, - "both CPBinarySearchingFirstEqual and CPBinarySearchingLastEqual options cannot be specified"); - - var count = [self count]; - - if (count <= 0) - return (options & CPBinarySearchingInsertionIndex) ? 0 : CPNotFound; - - var first = aRange ? aRange.location : 0, - last = (aRange ? CPMaxRange(aRange) : [self count]) - 1; - - if (first < 0) - _CPRaiseRangeException(self, _cmd, first, count); - - if (last >= count) - _CPRaiseRangeException(self, _cmd, last, count); - - while (first <= last) - { - var middle = FLOOR((first + last) / 2), - result = aComparator(anObject, [self objectAtIndex:middle]); - - if (result > 0) - first = middle + 1; - - else if (result < 0) - last = middle - 1; - - else - { - if (options & CPBinarySearchingFirstEqual) - while (middle > first && aComparator(anObject, [self objectAtIndex:middle - 1]) === CPOrderedSame) - --middle; - - else if (options & CPBinarySearchingLastEqual) - { - while (middle < last && aComparator(anObject, [self objectAtIndex:middle + 1]) === CPOrderedSame) - ++middle; - - if (options & CPBinarySearchingInsertionIndex) - ++middle; - } - - return middle; - } - } - - if (options & CPBinarySearchingInsertionIndex) - return MAX(first, 0); - - return CPNotFound; -} - -/*! - Returns the indexes of the objects in the receiver that pass a test in a given Javascript function. - @param predicate The function to apply to objects of the array. The function should have the signature: - @code function(object, index) @endcode - The predicate function should either return a Boolean value that indicates whether the object passed the test, - or nil to stop the search, which will return \c CPNotFound to the sender. - @return A CPIndexSet of the matching object indexes. -*/ -- (CPIndexSet)indexesOfObjectsPassingTest:(Function /*(id anObject, int idx)*/)aPredicate -{ - return [self indexesOfObjectsWithOptions:CPEnumerationNormal passingTest:aPredicate context:undefined]; -} - -/*! - Returns the indexes of the objects in the receiver that pass a test in a given Javascript function. - @param predicate The function to apply to objects of the array. The function should have the signature: - @code function(object, index, context) @endcode - The predicate function should either return a Boolean value that indicates whether the object passed the test, - or nil to stop the search, which will return \c CPNotFound to the sender. - @param context An object that contains context information you want passed to the predicate function. - @return A CPIndexSet of the matching object indexes. -*/ -- (CPIndexSet)indexesOfObjectsPassingTest:(Function /*(id anObject, int idx, id context)*/)aPredicate context:(id)aContext -{ - return [self indexesOfObjectsWithOptions:CPEnumerationNormal passingTest:aPredicate context:aContext]; -} - -/*! - Returns the indexes of the objects in the receiver that pass a test in a given Javascript function. - @param options Specifies the direction in which the array is searched. Pass CPEnumerationNormal to search forwards - or CPEnumerationReverse to search in reverse. - @param predicate The function to apply to objects of the array. The function should have the signature: - @code function(object, index) @endcode - The predicate function should either return a Boolean value that indicates whether the object passed the test, - or nil to stop the search, which will return CPNotFound to the sender. - @return A CPIndexSet of the matching object indexes. -*/ -- (CPIndexSet)indexesOfObjectsWithOptions:(CPEnumerationOptions)options passingTest:(Function /*(id anObject, int idx)*/)aPredicate -{ - return [self indexesOfObjectsWithOptions:options passingTest:aPredicate context:undefined]; -} - -/*! - Returns the indexes of the objects in the receiver that pass a test in a given Javascript function. - @param options Specifies the direction in which the array is searched. Pass CPEnumerationNormal to search forwards - or CPEnumerationReverse to search in reverse. - @param predicate The function to apply to objects of the array. The function should have the signature: - @code function(object, index, context) @endcode - The predicate function should either return a Boolean value that indicates whether the object passed the test, - or nil to stop the search, which will return CPNotFound to the sender. - @param context An object that contains context information you want passed to the predicate function. - @return A CPIndexSet of the matching object indexes. -*/ -- (CPIndexSet)indexesOfObjectsWithOptions:(CPEnumerationOptions)options passingTest:(Function /*(id anObject, int idx, id context)*/)aPredicate context:(id)aContext -{ - // We don't use an enumerator because they return nil to indicate end of enumeration, - // but nil may actually be the value we are looking for, so we have to loop over the array. - if (options & CPEnumerationReverse) - { - var index = [self count] - 1, - stop = -1, - increment = -1; - } - else - { - var index = 0, - stop = [self count], - increment = 1; - } - - var indexes = [CPIndexSet indexSet]; - - for (; index !== stop; index += increment) - if (aPredicate([self objectAtIndex:index], index, aContext)) - [indexes addIndex:index]; - - return indexes; -} - -// Sending messages to elements -/*! - Sends each element in the array a message. - @param aSelector the selector of the message to send - @throws CPInvalidArgumentException if \c aSelector is \c nil -*/ -- (void)makeObjectsPerformSelector:(SEL)aSelector -{ - [self makeObjectsPerformSelector:aSelector withObjects:nil]; -} - -/*! - Sends each element in the array a message with an argument. - @param aSelector the selector of the message to send - @param anObject the first argument of the message - @throws CPInvalidArgumentException if \c aSelector is \c nil -*/ -- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)anObject -{ - return [self makeObjectsPerformSelector:aSelector withObjects:[anObject]]; -} - -- (void)makeObjectsPerformSelector:(SEL)aSelector withObjects:(CPArray)objects -{ - if (!aSelector) - [CPException raise:CPInvalidArgumentException - reason:"makeObjectsPerformSelector:withObjects: 'aSelector' can't be nil"]; - - var index = 0, - count = [self count]; - - if ([objects count]) - { - var argumentsArray = [[nil, aSelector] arrayByAddingObjectsFromArray:objects]; - - for (; index < count; ++index) - { - argumentsArray[0] = [self objectAtIndex:index]; - objj_msgSend.apply(this, argumentsArray); - } - } - - else - for (; index < count; ++index) - objj_msgSend([self objectAtIndex:index], aSelector); -} - -- (void)enumerateObjectsUsingBlock:(Function /*(id anObject, int idx, @ref BOOL stop)*/)aFunction -{ - // This could have been [self enumerateObjectsWithOptions:CPEnumerationNormal usingBlock:aFunction] - // but this method should be as fast as possible. - var index = 0, - count = [self count], - shouldStop = NO, - shouldStopRef = AT_REF(shouldStop); - - for (; index < count; ++index) - { - aFunction([self objectAtIndex:index], index, shouldStopRef); - if (shouldStop) - return; - } -} - -- (void)enumerateObjectsWithOptions:(CPEnumerationOptions)options usingBlock:(Function /*(id anObject, int idx, @ref BOOL stop)*/)aFunction -{ - var shouldStop = NO; - - if (options & CPEnumerationReverse) - { - var index = [self count] - 1, - stop = -1, - increment = -1; - } - else - { - var index = 0, - stop = [self count], - increment = 1; - } - - for (; index !== stop; index += increment) - { - aFunction([self objectAtIndex:index], index, AT_REF(shouldStop)); - if (shouldStop) - return; - } -} - -// Comparing arrays -/*! - Returns the first object found in the receiver (starting at index 0) which is present in the - \c otherArray as determined by using the \c -containsObject: method. - @return the first object found, or \c nil if no common object was found. -*/ -- (id)firstObjectCommonWithArray:(CPArray)anArray -{ - var count = [self count]; - - if (![anArray count] || !count) - return nil; - - var index = 0; - - for (; index < count; ++index) - { - var object = [self objectAtIndex:index]; - - if ([anArray containsObject:object]) - return object; - } - - return nil; -} - -/*! - Returns true if anArray contains exactly the same objects as the receiver. -*/ -- (BOOL)isEqualToArray:(id)anArray -{ - if (self === anArray) - return YES; - - if (![anArray isKindOfClass:CPArray]) - return NO; - - var count = [self count], - otherCount = [anArray count]; - - if (anArray === nil || count !== otherCount) - return NO; - - var index = 0; - - for (; index < count; ++index) - { - var lhs = [self objectAtIndex:index], - rhs = [anArray objectAtIndex:index]; - - // If they're not equal, and either doesn't have an isa, or they're !isEqual (not isEqual) - if (lhs !== rhs && (lhs && !lhs.isa || rhs && !rhs.isa || ![lhs isEqual:rhs])) - return NO; - } - - return YES; -} - -- (BOOL)isEqual:(id)anObject -{ - return (self === anObject) || [self isEqualToArray:anObject]; -} - -- (Array)_javaScriptArrayCopy -{ - var index = 0, - count = [self count], - copy = []; - - for (; index < count; ++index) - push.call(copy, [self objectAtIndex:index]); - - return copy; -} - -// Deriving new arrays -/*! - Returns a copy of this array plus \c anObject inside the copy. - @param anObject the object to be added to the array copy - @throws CPInvalidArgumentException if \c anObject is \c nil - @return a new array that should be n+1 in size compared to the receiver. -*/ -- (CPArray)arrayByAddingObject:(id)anObject -{ - var argumentArray = [self _javaScriptArrayCopy]; - - // We push instead of concat,because concat flattens arrays, so if the object - // passed in is an array, we end up with its contents added instead of itself. - push.call(argumentArray, anObject); - - return objj_msgSend([self class], @selector(arrayWithArray:), argumentArray); -} - -/*! - Returns a new array which is the concatenation of \c self and otherArray (in this precise order). - @param anArray the array that will be concatenated to the receiver's copy -*/ -- (CPArray)arrayByAddingObjectsFromArray:(CPArray)anArray -{ - if (!anArray) - return [self copy]; - - var anArray = anArray.isa === _CPJavaScriptArray ? anArray : [anArray _javaScriptArrayCopy], - argumentArray = concat.call([self _javaScriptArrayCopy], anArray); - - return objj_msgSend([self class], @selector(arrayWithArray:), argumentArray); -} - -/* -- (CPArray)filteredArrayUsingPredicate:(CPPredicate)aPredicate -{ - var i= 0, - count = [self count], - array = [CPArray array]; - - for (; i
grade == "7" or firstName like "Shaffiq"
- - Case/diacritic insensitive lookups, such as name contains[cd] "itroen"
- - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley")
- - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}.
-
- You can create predicates for relationships, such as:
-
- - group.name like "work*"
- - ALL children.age > 12
- - ANY children.age > 12
-
- You can create predicates for operations, such as \@sum.items.price < 1000.
-
- You can also create predicates that include variables, so that the predicate can be pre-defined before
- substituting concrete values at runtime with the \c evaluateWithObject:substitutionVariables: method.
-*/
-
-@implementation CPPredicate : CPObject
-{
-}
-
-/*!
- Creates and returns a new predicate formed by creating a new string with a given format and parsing the result.
- @param format The format string for the new predicate.
- @param … A comma-separated list of arguments to substitute into format.
- @return A new predicate formed by creating a new string with format and parsing the result.
-*/
-+ (CPPredicate)predicateWithFormat:(CPString)format, ...
-{
- if (!format)
- [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"];
-
- var args = Array.prototype.slice.call(arguments, 3);
- return [self predicateWithFormat:arguments[2] argumentArray:args];
-}
-
-/*!
- Creates and returns a new predicate by substituting the values in a given array into a format string and parsing the result.
- @param format The format string for the new predicate.
- @param arguments The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the array.
- @return A new predicate by substituting the values in arguments into predicateFormat, and parsing the result.
-*/
-+ (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)args
-{
- if (!format)
- [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"];
-
- var s = [[CPPredicateScanner alloc] initWithString:format args:args],
- p = [s parse];
-
- return p;
-}
-
-/*!
- Creates and returns a new predicate by substituting the values in an argument list into a format string and parsing the result.
- @param format The format string for the new predicate.
- @param argList The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the argument list.
- @return A new predicate by substituting the values in argList into predicateFormat and parsing the result.
-*/
-+ (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList
-{
- // UNIMPLEMENTED
- return nil;
-}
-
-/*!
- Returns a copy of the receiver with the receiver’s variables substituted by values specified in a given substitution variables dictionary.
- @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver.
- @return A copy of the receiver with the receiver’s variables substituted by values specified in variables.
-*/
-- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables
-{
- // IMPLEMENTED BY SUBCLASSES
-}
-
-/*!
- Creates and returns a predicate that always evaluates to a given value.
- @param value The value to which the new predicate should evaluate.
- @return A predicate that always evaluates to value.
-*/
-+ (CPPredicate)predicateWithValue:(BOOL)value
-{
- return [[CPPredicate_BOOL alloc] initWithBool:value];
-}
-
-// Evaluating a Predicate
-/*!
- Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver.
- @param object The object against which to evaluate the receiver.
- @return YES if object matches the conditions specified by the receiver, otherwise NO.
-*/
-- (BOOL)evaluateWithObject:(id)object
-{
- // IMPLEMENTED BY SUBCLASSES
-}
-
-/*!
- Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver after substituting in the values in a given variables dictionary.
- @param object The object against which to evaluate the receiver.
- @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver.
- @return YES if object matches the conditions specified by the receiver after substituting in the values in variables for any replacement tokens, otherwise NO.
-*/
-- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
-{
- // IMPLEMENTED BY SUBCLASSES
-}
-
-// Getting Format Information
-/*!
- Returns the receiver’s format string.
- @return The receiver’s format string.
-*/
-- (CPString)predicateFormat
-{
- // IMPLEMENTED BY SUBCLASSES
-}
-
-- (CPString)description
-{
- return [self predicateFormat];
-}
-
-@end
-
-@implementation CPPredicate_BOOL : CPPredicate
-{
- BOOL _value;
-}
-
-- (id)initWithBool:(BOOL)value
-{
- _value = value;
- return self;
-}
-
-- (BOOL)isEqual:(id)anObject
-{
- if (self === anObject)
- return YES;
-
- if (self.isa !== anObject.isa || _value !== [anObject evaluateObject:nil])
- return NO;
-
- return YES;
-}
-
-- (BOOL)evaluateObject:(id)object
-{
- return _value;
-}
-
-- (CPString)predicateFormat
-{
- return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE";
-}
-
-@end
-
-
-@implementation CPArray (CPPredicate)
-
-- (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate
-{
- var count = [self count],
- result = [CPArray array],
- i = 0;
-
- for (; i < count; i++)
- {
- var object = [self objectAtIndex:i];
- if ([predicate evaluateWithObject:object])
- result.push(object);
- }
-
- return result;
-}
-
-- (void)filterUsingPredicate:(CPPredicate)predicate
-{
- var count = [self count];
-
- while (count--)
- {
- if (![predicate evaluateWithObject:[self objectAtIndex:count]])
- splice(count, 1);
- }
-}
-
-@end
-
-@implementation CPSet (CPPredicate)
-
-- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate
-{
- var count = [self count],
- result = [CPSet set],
- i = 0;
-
- for (; i < count; i++)
- {
- var object = [self objectAtIndex:i];
-
- if ([predicate evaluateWithObject:object])
- [result addObject:object];
- }
-
- return result;
-}
-
-- (void)filterUsingPredicate:(CPPredicate)predicate
-{
- var count = [self count];
-
- while (--count >= 0)
- {
- var object = [self objectAtIndex:count];
-
- if (![predicate evaluateWithObject:object])
- [self removeObjectAtIndex:count];
- }
-}
-
-@end
-
-#define REFERENCE(variable) \
-function(newValue)\
-{\
- var oldValue = variable;\
- if (typeof newValue != 'undefined')\
- variable = newValue;\
- return oldValue;\
-}
-
-@implementation CPPredicateScanner : CPScanner
-{
- CPEnumerator _args;
- unsigned _retrieved;
-}
-
-- (id)initWithString:(CPString)format args:(CPArray)args
-{
- self = [super initWithString:format]
-
- if (self)
- {
- _args = [args objectEnumerator];
- }
- return self;
-}
-
-- (id)nextArg
-{
- return [_args nextObject];
-}
-
-- (BOOL)scanPredicateKeyword:(CPString)key
-{
- var loc = [self scanLocation];
-
- [self setCaseSensitive:NO];
- if (![self scanString:key intoString:NULL])
- return NO;
-
- if ([self isAtEnd])
- return YES;
-
- var c = [[self string] characterAtIndex:[self scanLocation]];
- if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c])
- return YES;
-
- [self setScanLocation:loc];
-
- return NO;
-}
-
-- (CPPredicate)parse
-{
- var r = nil;
-
- try
- {
- [self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]];
- r = [self parsePredicate];
- }
- catch(error)
- {
- CPLogConsole(@"Unable to parse predicate '" + [self string] + "' with " + error);
- }
- finally
- {
- if (![self isAtEnd])
- {
- var pstr = [self string],
- loc = [self scanLocation];
- CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'");
- }
- }
-
- return r;
-}
-
-- (CPPredicate)parsePredicate
-{
- return [self parseAnd];
-}
-
-- (CPPredicate)parseAnd
-{
- var l = [self parseOr];
-
- while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"])
- {
- var r = [self parseOr];
-
- if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType)
- {
- if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType)
- {
- [[l subpredicates] addObjectsFromArray:[r subpredicates]];
- }
- else
- {
- [[r subpredicates] insertObject:l atIndex:0];
- l = r;
- }
- }
- else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType)
- {
- [[l subpredicates] addObject:r];
- }
- else
- {
- l = [CPCompoundPredicate andPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]];
- }
- }
- return l;
-}
-
-- (CPPredicate)parseNot
-{
- if ([self scanString:@"(" intoString:NULL])
- {
- var r = [self parsePredicate];
-
- if (![self scanString:@")" intoString:NULL])
- CPRaiseParseError(self, @"predicate");
-
- return r;
- }
-
- if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"])
- {
- return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]];
- }
- if ([self scanPredicateKeyword:@"TRUEPREDICATE"])
- {
- return [CPPredicate predicateWithValue:YES];
- }
- if ([self scanPredicateKeyword:@"FALSEPREDICATE"])
- {
- return [CPPredicate predicateWithValue:NO];
- }
-
- return [self parseComparison];
-}
-
-- (CPPredicate)parseOr
-{
- var l = [self parseNot];
- while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"])
- {
- var r = [self parseNot];
-
- if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType)
- {
- if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType)
- {
- [[l subpredicates] addObjectsFromArray:[r subpredicates]];
- }
- else
- {
- [[r subpredicates] insertObject:l atIndex:0];
- l = r;
- }
- }
- else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType)
- {
- [[l subpredicates] addObject:r];
- }
- else
- {
- l = [CPCompoundPredicate orPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]];
- }
- }
- return l;
-}
-
-- (CPPredicate)parseComparison
-{
- var modifier = CPDirectPredicateModifier,
- type = 0,
- opts = 0,
- left,
- right,
- p,
- negate = NO;
-
- if ([self scanPredicateKeyword:@"ANY"])
- {
- modifier = CPAnyPredicateModifier;
- }
- else if ([self scanPredicateKeyword:@"ALL"])
- {
- modifier = CPAllPredicateModifier;
- }
- else if ([self scanPredicateKeyword:@"NONE"])
- {
- modifier = CPAnyPredicateModifier;
- negate = YES;
- }
- else if ([self scanPredicateKeyword:@"SOME"])
- {
- modifier = CPAllPredicateModifier;
- negate = YES;
- }
-
- left = [self parseExpression];
- if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL])
- {
- type = CPNotEqualToPredicateOperatorType;
- }
- else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL])
- {
- type = CPLessThanOrEqualToPredicateOperatorType;
- }
- else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL])
- {
- type = CPGreaterThanOrEqualToPredicateOperatorType;
- }
- else if ([self scanString:@"<" intoString:NULL])
- {
- type = CPLessThanPredicateOperatorType;
- }
- else if ([self scanString:@">" intoString:NULL])
- {
- type = CPGreaterThanPredicateOperatorType;
- }
- else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL])
- {
- type = CPEqualToPredicateOperatorType;
- }
- else if ([self scanPredicateKeyword:@"MATCHES"])
- {
- type = CPMatchesPredicateOperatorType;
- }
- else if ([self scanPredicateKeyword:@"LIKE"])
- {
- type = CPLikePredicateOperatorType;
- }
- else if ([self scanPredicateKeyword:@"BEGINSWITH"])
- {
- type = CPBeginsWithPredicateOperatorType;
- }
- else if ([self scanPredicateKeyword:@"ENDSWITH"])
- {
- type = CPEndsWithPredicateOperatorType;
- }
- else if ([self scanPredicateKeyword:@"IN"])
- {
- type = CPInPredicateOperatorType;
- }
- else if ([self scanPredicateKeyword:@"CONTAINS"])
- {
- type = CPContainsPredicateOperatorType;
- }
- else if ([self scanPredicateKeyword:@"BETWEEN"])
- {
- type = CPBetweenPredicateOperatorType;
- }
- else
- CPRaiseParseError(self, @"comparison predicate");
-
- if ([self scanString:@"[cd]" intoString:NULL])
- {
- opts = CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption;
- }
- else if ([self scanString:@"[c]" intoString:NULL])
- {
- opts = CPCaseInsensitivePredicateOption;
- }
- else if ([self scanString:@"[d]" intoString:NULL])
- {
- opts = CPDiacriticInsensitivePredicateOption;
- }
-
- right = [self parseExpression];
-
- p = [CPComparisonPredicate predicateWithLeftExpression:left
- rightExpression:right
- modifier:modifier
- type:type
- options:opts];
-
- return negate ? [CPCompoundPredicate notPredicateWithSubpredicate:p]:p;
-}
-
-- (CPExpression)parseExpression
-{
- return [self parseBinaryExpression];
-}
-
-- (CPExpression)parseSimpleExpression
-{
- var identifier,
- location,
- ident,
- dbl;
-
- if ([self scanDouble:REFERENCE(dbl)])
- return [CPExpression expressionForConstantValue:dbl];
-
- // FIXME: handle integer, hex constants, 0x 0o 0b
- if ([self scanString:@"-" intoString:NULL])
- return [CPExpression expressionForFunction:@"chs:" arguments:[CPArray arrayWithObject:[self parseExpression]]];
-
- if ([self scanString:@"(" intoString:NULL])
- {
- var arg = [self parseExpression];
-
- if (![self scanString:@")" intoString:NULL])
- CPRaiseParseError(self, @"expression");
-
- return arg;
- }
-
- if ([self scanString:@"{" intoString:NULL])
- {
- if ([self scanString:@"}" intoString:NULL])
- return [CPExpression expressionForConstantValue:a];
-
- var a = [];
-
- [a addObject:[self parseExpression]];
-
- while ([self scanString:@"," intoString:NULL])
- [a addObject:[self parseExpression]];
-
- if (![self scanString:@"}" intoString:NULL])
- CPRaiseParseError(self, @"expression");
-
- return [CPExpression expressionForAggregate:a];
- }
-
- if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"])
- {
- return [CPExpression expressionForConstantValue:[CPNull null]];
- }
- if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"])
- {
- return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]];
- }
- if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"])
- {
- return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]];
- }
- if ([self scanPredicateKeyword:@"SELF"])
- {
- return [CPExpression expressionForEvaluatedObject];
- }
-
- if ([self scanString:@"$" intoString:NULL])
- {
- var variable = [self parseSimpleExpression];
-
- if (![variable keyPath])
- CPRaiseParseError(self, @"expression");
-
- return [CPExpression expressionForVariable:variable];
- }
-
- location = [self scanLocation];
-
- if ([self scanString:@"%" intoString:NULL])
- {
- if ([self isAtEnd] == NO)
- {
- var c = [[self string] characterAtIndex:[self scanLocation]];
-
- switch (c)
- {
- case '%':// '%%' is treated as '%'
- location = [self scanLocation];
- break;
- case 'K':
- [self setScanLocation:[self scanLocation] + 1];
- return [CPExpression expressionForKeyPath:[self nextArg]];
- case '@':
- case 'c':
- case 'C':
- case 'd':
- case 'D':
- case 'i':
- case 'o':
- case 'O':
- case 'u':
- case 'U':
- case 'x':
- case 'X':
- case 'e':
- case 'E':
- case 'f':
- case 'g':
- case 'G':
- [self setScanLocation:[self scanLocation] + 1];
- return [CPExpression expressionForConstantValue:[self nextArg]];
- case 'h':
- [self scanString:@"h" intoString:NULL];
- if ([self isAtEnd] == NO)
- {
- c = [[self string] characterAtIndex:[self scanLocation]];
- if (c == 'i' || c == 'u')
- {
- [self setScanLocation:[self scanLocation] + 1];
- return [CPExpression expressionForConstantValue:[self nextArg]];
- }
- }
- break;
- case 'q':
- [self scanString:@"q" intoString:NULL];
- if ([self isAtEnd] == NO)
- {
- c = [[self string] characterAtIndex:[self scanLocation]];
- if (c == 'i' || c == 'u' || c == 'x' || c == 'X')
- {
- [self setScanLocation:[self scanLocation] + 1];
- return [CPExpression expressionForConstantValue:[self nextArg]];
- }
- }
- break;
- }
- }
-
- [self setScanLocation:location];
- }
-
- if ([self scanString:@"\"" intoString:NULL])
- {
- var skip = [self charactersToBeSkipped],
- str = @"";
-
- [self setCharactersToBeSkipped:nil];
- [self scanUpToString:@"\"" intoString:REFERENCE(str)];
-
- if ([self scanString:@"\"" intoString:NULL] == NO)
- CPRaiseParseError(self, @"expression");
-
- [self setCharactersToBeSkipped:skip];
-
- return [CPExpression expressionForConstantValue:str];
- }
-
- if ([self scanString:@"'" intoString:NULL])
- {
- var skip = [self charactersToBeSkipped],
- str = @"";
-
- [self setCharactersToBeSkipped:nil];
- [self scanUpToString:@"'" intoString:REFERENCE(str)];
-
- if ([self scanString:@"'" intoString:NULL] == NO)
- CPRaiseParseError(self, @"expression");
-
- [self setCharactersToBeSkipped:skip];
-
- return [CPExpression expressionForConstantValue:str];
- }
-
- if ([self scanString:@"@" intoString:NULL])
- {
- var e = [self parseExpression];
-
- if (![e keyPath])
- CPRaiseParseError(self, @"expression");
-
- return [CPExpression expressionForKeyPath:[e keyPath] + "@"];
- }
-
- if ([self scanString:@"SUBQUERY" intoString:NULL])
- {
- if (![self scanString:@"(" intoString:NULL])
- CPRaiseParseError(self, @"expression");
-
- var collection = [self parseExpression],
- variableExpression,
- subpredicate;
-
- if (![self scanString:@"," intoString:NULL])
- CPRaiseParseError(self, @"expression");
- variableExpression = [self parseExpression];
-
- if (![self scanString:@"," intoString:NULL])
- CPRaiseParseError(self, @"expression");
- subpredicate = [self parsePredicate];
-
- if (![self scanString:@")" intoString:NULL])
- CPRaiseParseError(self, @"expression");
-
- return [[_CPSubqueryExpression alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate];
- }
-
- if ([self scanString:@"FUNCTION" intoString:NULL])
- {
- if (![self scanString:@"(" intoString:NULL])
- CPRaiseParseError(self, @"expression");
-
- var args = [CPArray arrayWithObject:[self parseExpression]];
- while ([self scanString:@"," intoString:NULL])
- [args addObject:[self parseExpression]];
-
- if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType)
- CPRaiseParseError(self, @"expression");
-
- return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)];
- }
-
- [self scanString:@"#" intoString:NULL];
- if (!identifier)
- identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"];
-
- if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)])
- CPRaiseParseError(self, @"expression");
-
- return [CPExpression expressionForKeyPath:ident];
-}
-
-- (CPExpression)parseFunctionalExpression
-{
- var left = [self parseSimpleExpression];
-
- while (YES)
- {
- if ([self scanString:@"." intoString:NULL])
- {
- var right = [self parseSimpleExpression],
- expressionType = [right expressionType];
-
- if (expressionType == CPKeyPathExpressionType)
- left = [[_CPKeyPathExpression alloc] initWithOperand:left andKeyPath:[right keyPath]];
- else if (expressionType == CPVariableExpressionType)
- left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]];
- else
- CPRaiseParseError(self, @"expression");
- }
- else if ([self scanString:@"[" intoString:NULL])
- {
- // index expression
- if ([self scanPredicateKeyword:@"FIRST"])
- {
- left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]];
- }
- else if ([self scanPredicateKeyword:@"LAST"])
- {
- left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]];
- }
- else if ([self scanPredicateKeyword:@"SIZE"])
- {
- left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]];
- }
- else
- {
- var index = [self parseExpression];
- left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]];
- }
-
- if (![self scanString:@"]" intoString:NULL])
- CPRaiseParseError(self, @"expression");
- }
- else if ([self scanString:@":" intoString:NULL])
- {
- // function - this parser allows for (max)(a, b, c) to be properly
- // recognized and even (%K)(a, b, c) if %K evaluates to "max"
-
- if (![left keyPath])
- CPRaiseParseError(self, @"expression");
-
- var selector = [left keyPath] + @":",
- args = [];
-
- if (![self scanString:@"(" intoString:NULL])
- {
- var str;
- [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)];
-
- if (![self scanString:@":(" intoString:NULL])
- CPRaiseParseError(self, @"expression");
-
- selector += str + @":";
- }
-
- if (![self scanString:@")" intoString:NULL])
- {
- [args addObject:[self parseExpression]];
- while ([self scanString:@"," intoString:NULL])
- [args addObject:[self parseExpression]];
-
- if (![self scanString:@")" intoString:NULL])
- CPRaiseParseError(self, @"expression");
- }
-
- left = [CPExpression expressionForFunction:selector arguments:args];
- }
- else if ([self scanString:@"UNION" intoString:NULL])
- {
- left = [CPExpression expressionForUnionSet:left with:[self parseExpression]];
- }
- else if ([self scanString:@"INTERSECT" intoString:NULL])
- {
- left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]];
- }
- else if ([self scanString:@"MINUS" intoString:NULL])
- {
- left = [CPExpression expressionForMinusSet:left with:[self parseExpression]];
- }
- else
- {
- // done with suffixes
- return left;
- }
- }
-}
-
-- (CPExpression)parsePowerExpression
-{
- var left = [self parseFunctionalExpression];
-
- while (YES)
- {
- var right;
-
- if ([self scanString:@"**" intoString:NULL])
- {
- right = [self parseFunctionalExpression];
- left = [CPExpression expressionForFunction:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]];
- }
- else
- {
- return left;
- }
- }
-}
-
-- (CPExpression)parseMultiplicationExpression
-{
- var left = [self parsePowerExpression];
-
- while (YES)
- {
- var right;
-
- if ([self scanString:@"*" intoString:NULL])
- {
- right = [self parsePowerExpression];
- left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]];
- }
- else if ([self scanString:@"/" intoString:NULL])
- {
- right = [self parsePowerExpression];
- left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]];
- }
- else
- {
- return left;
- }
- }
-}
-
-- (CPExpression)parseAdditionExpression
-{
- var left = [self parseMultiplicationExpression];
-
- while (YES)
- {
- var right;
-
- if ([self scanString:@"+" intoString:NULL])
- {
- right = [self parseMultiplicationExpression];
- left = [CPExpression expressionForFunction:@"add:to:" arguments:[CPArray arrayWithObjects:left, right]];
- }
- else if ([self scanString:@"-" intoString:NULL])
- {
- right = [self parseMultiplicationExpression];
- left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]];
- }
- else
- {
- return left;
- }
- }
-}
-
-- (CPExpression)parseBinaryExpression
-{
- var left = [self parseAdditionExpression];
-
- while (YES)
- {
- var right;
-
- if ([self scanString:@":=" intoString:NULL]) // assignment
- {
- // check left to be a variable?
- right = [self parseAdditionExpression];
- // FIXME
- }
- else
- {
- return left;
- }
- }
-}
-
-@end
-
-var CPRaiseParseError = function CPRaiseParseError(aScanner, target)
-{
- [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]];
-};
-
+@import "_CPPredicate.j"
@import "CPCompoundPredicate.j"
@import "CPComparisonPredicate.j"
@import "CPExpression.j"
diff --git a/Foundation/CPPredicate/_CPExpression.j b/Foundation/CPPredicate/_CPExpression.j
new file mode 100644
index 000000000..5a9561cb7
--- /dev/null
+++ b/Foundation/CPPredicate/_CPExpression.j
@@ -0,0 +1,397 @@
+/*
+ * CPExpression.j
+ *
+ * Created by cacaodev.
+ * Copyright 2010.
+ *
+ * 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 "CPArray.j"
+@import "CPDictionary.j"
+@import "CPKeyValueCoding.j"
+@import "CPObject.j"
+@import "CPString.j"
+
+/*!
+ An expression that always returns the same value.
+*/
+CPConstantValueExpressionType = 0;
+/*!
+ An expression that always returns the parameter object itself.
+*/
+CPEvaluatedObjectExpressionType = 1;
+/*!
+ An expression that always returns whatever value is associated with the key specified by ‘variable’ in the bindings dictionary.
+*/
+CPVariableExpressionType = 2;
+/*!
+ An expression that returns something that can be used as a key path.
+*/
+CPKeyPathExpressionType = 3;
+/*!
+ An expression that returns the result of evaluating a function.
+*/
+CPFunctionExpressionType = 4;
+/*!
+ An expression that defines an aggregate of CPExpression objects.
+*/
+CPAggregateExpressionType = 5;
+/*!
+ An expression that filters a collection using a subpredicate.
+*/
+CPSubqueryExpressionType = 6;
+/*!
+ An expression that creates a union of the results of two nested expressions.
+*/
+CPUnionSetExpressionType = 7;
+/*!
+ An expression that creates an intersection of the results of two nested expressions.
+*/
+CPIntersectSetExpressionType = 8;
+/*!
+ An expression that combines two nested expression results by set subtraction.
+*/
+CPMinusSetExpressionType = 9;
+
+/*!
+ @ingroup foundation
+ @class CPExpression
+ @brief CPExpression is used to represent expressions in a predicate.
+
+ Comparison operations in an CPPredicate are based on two expressions, as represented by instances of the CPExpression class.
+ Expressions are created for constant values, key paths, and so on.
+
+ Generally, anywhere in the CPExpression class hierarchy where there is composite API and subtypes
+ that may only reasonably respond to a subset of that API, invoking a method that does not make sense
+ for that subtype will cause an exception to be thrown.
+*/
+
+@implementation CPExpression : CPObject
+{
+ int _type;
+}
+
+// Initializing an Expression
+/*!
+ Initializes the receiver with the specified expression type.
+ @param type The type of the new expression, as defined by CPExpressionType.
+ @return An initialized CPExpression object of the type type.
+*/
+- (id)initWithExpressionType:(int)type
+{
+ _type = type;
+
+ return self;
+}
+
+//Creating an Expression for a Value
+/*!
+ Returns a new expression that represents a given constant value.
+ @param value The constant value the new expression is to represent.
+ @return A new expression that represents the constant value.
+*/
++ (CPExpression)expressionForConstantValue:(id)value
+{
+ return [[_CPConstantValueExpression alloc] initWithValue:value];
+}
+
+/*!
+ Returns a new expression that represents the object being evaluated.
+ @return A new expression that represents the object being evaluated.
+*/
++ (CPExpression)expressionForEvaluatedObject
+{
+ return [_CPSelfExpression evaluatedObject];
+}
+
+/*!
+ Returns a new expression that extracts a value from the variable bindings dictionary for a given key.
+ @param string The key for the variable to extract from the variable bindings dictionary.
+ @return A new expression that extracts from the variable bindings dictionary the value for the key string.
+*/
++ (CPExpression)expressionForVariable:(CPString)string
+{
+ return [[_CPVariableExpression alloc] initWithVariable:string];
+}
+
+/*!
+ Returns a new expression that invokes valueForKeyPath: with a given key path.
+ @param keyPath The key path that the new expression should evaluate.
+ @return A new expression that invokes valueForKeyPath: with keyPath.
+*/
++ (CPExpression)expressionForKeyPath:(CPString)keyPath
+{
+ return [[_CPKeyPathExpression alloc] initWithKeyPath:keyPath];
+}
+
+/*!
+ Returns a new aggregate expression for a given collection.
+ @param collection A collection object (an instance of CPArray, CPSet, or CPDictionary) that contains further expressions.
+ @return A new expression that contains the expressions in collection.
+*/
++ (CPExpression)expressionForAggregate:(CPArray)collection
+{
+ return [[_CPAggregateExpression alloc] initWithAggregate:collection];
+}
+
+/*!
+ Returns a new CPExpression object that represent the union of a given set and collection.
+ @param left An expression that evaluates to a CPSet object.
+ @param right An expression that evaluates to a collection object (an instance of CPArray, CPSet, or CPDictionary).
+ @return A new CPExpression object that represents the union of left and right.
+*/
++ (CPExpression)expressionForUnionSet:(CPExpression)left with:(CPExpression)right
+{
+ return [[_CPSetExpression alloc] initWithType:CPUnionSetExpressionType left:left right:right];
+}
+
+/*!
+ Returns a new CPExpression object that represent the intersection of a given set and collection.
+ @param left An expression that evaluates to a CPSet object.
+ @param right An expression that evaluates to a collection object (an instance of CPArray, CPSet, or CPDictionary).
+ @return A new CPExpression object that represents the intersection of left and right.
+*/
++ (CPExpression)expressionForIntersectSet:(CPExpression)left with:(CPExpression)right
+{
+ return [[_CPSetExpression alloc] initWithType:CPIntersectSetExpressionType left:left right:right];
+}
+
+/*!
+ Returns a new CPExpression object that represent the subtraction of a given collection from a given set.
+ @param left An expression that evaluates to a CPSet object.
+ @param left An expression that evaluates to a collection object (an instance of CPArray, CPSet, or CPDictionary).
+ @return A new CPExpression object that represents the subtraction of right from left.
+*/
++ (CPExpression)expressionForMinusSet:(CPExpression)left with:(CPExpression)right
+{
+ return [[_CPSetExpression alloc] initWithType:CPMinusSetExpressionType left:left right:right];
+}
+
+// Creating an Expression for a Function
+/*!
+ Returns a new expression that will invoke one of the predefined functions.
+ @param function_name The name of the function to invoke.
+ @param parameters An array containing CPExpression objects that will be used as parameters during the invocation of selector.
+ @return A new expression that invokes the function name using the parameters in parameters.
+
+ For a selector taking no parameters, the array should be empty. For a selector taking one or more parameters,
+ the array should contain one CPExpression object which will evaluate to an instance of the appropriate type for each parameter.
+
+ If there is a mismatch between the number of parameters expected and the number you provide during evaluation,
+ an exception may be raised or missing parameters may simply be replaced by nil (which occurs depends on how many
+ parameters are provided, and whether you have over- or underflow).
+
+ The name parameter can be one of the following predefined functions:
+ @verbatim
+ name parameter array contents returns
+ -------------------------------------------------------------------------------------------------------------------------------------
+ sum: CPExpression instances representing numbers CPNumber
+ count: CPExpression instances representing numbers CPNumber
+ min: CPExpression instances representing numbers CPNumber
+ max: CPExpression instances representing numbers CPNumber
+ average: CPExpression instances representing numbers CPNumber
+ median: CPExpression instances representing numbers CPNumber
+ mode: CPExpression instances representing numbers CPArray (returned array will contain all occurrences of the mode)
+ stddev: CPExpression instances representing numbers CPNumber
+ add:to: CPExpression instances representing numbers CPNumber
+ from:subtract: two CPExpression instances representing numbers CPNumber
+ multiply:by: two CPExpression instances representing numbers CPNumber
+ divide:by: two CPExpression instances representing numbers CPNumber
+ modulus:by: two CPExpression instances representing numbers CPNumber
+ sqrt: one CPExpression instance representing numbers CPNumber
+ log: one CPExpression instance representing a number CPNumber
+ ln: one CPExpression instance representing a number CPNumber
+ raise:toPower: one CPExpression instance representing a number CPNumber
+ exp: one CPExpression instance representing a number CPNumber
+ floor: one CPExpression instance representing a number CPNumber
+ ceiling: one CPExpression instance representing a number CPNumber
+ abs: one CPExpression instance representing a number CPNumber
+ trunc: one CPExpression instance representing a number CPNumber
+ uppercase: one CPExpression instance representing a string CPString
+ lowercase: one CPExpression instance representing a string CPString
+ random: one CPExpression instance representing a number CPNumber (integer) such that 0 <= rand < param
+ now: none [CPDate now]
+
+ This method raises an exception immediately if the selector is invalid; it raises an exception at runtime if the parameters are incorrect.
+@endverbatim
+*/
++ (CPExpression)expressionForFunction:(CPString)function_name arguments:(CPArray)parameters
+{
+ return [[_CPFunctionExpression alloc] initWithSelector:CPSelectorFromString(function_name) arguments:parameters];
+}
+
+/*!
+ Returns an expression which will return the result of invoking on a given target a selector with a given name using given arguments.
+ @param target A CPExpression object which will evaluate an object on which the selector identified by name may be invoked.
+ @param selectorName The name of the method to be invoked.
+ @param parameters An array containing CPExpression objects which can be evaluated to provide parameters for the method specified by name.
+ @return An expression which will return the result of invoking the selector named name on the result of evaluating the target expression with the parameters specified by evaluating the elements of parameters.
+
+ See the description of \c expressionForFunction:arguments: for examples of how to construct the parameter array.
+*/
++ (CPExpression)expressionForFunction:(CPExpression)target selectorName:(CPString)selectorName arguments:(CPArray)parameters
+{
+ return [[_CPFunctionExpression alloc] initWithTarget:target selector:CPSelectorFromString(selectorName) arguments:parameters];
+}
+
+/*!
+ Returns an expression that filters a collection by storing elements in the collection in a given variable and keeping the elements for which qualifier returns true.
+ @param expression A CPExpression that evaluates to a collection.
+ @param variable Used as a local variable, and will shadow any instances of variable in the bindings dictionary. The variable is removed or the old value replaced once evaluation completes.
+ @param predicate The predicate used to determine whether the element belongs in the result collection.
+ @return An expression that filters a collection by storing elements in the collection in the variable variable and keeping the elements for which qualifier returns true.
+*/
++ (CPExpression)expressionForSubquery:(CPExpression)expression usingIteratorVariable:(CPString)variable predicate:(CPPredicate)predicate
+{
+ return [[_CPSubqueryExpression alloc] initWithExpression:expression usingIteratorVariable:variable predicate:predicate];
+}
+
+// Getting Information About an Expression
+/*!
+ Returns the expression type for the receiver.
+ @return The expression type for the receiver.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (int)expressionType
+{
+ return _type;
+}
+
+/*!
+ Returns the constant value of the receiver.
+ @return The constant value of the receiver.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (id)constantValue
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+/*!
+ Returns the variable for the receiver.
+ @return The variable for the receiver.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (CPString)variable
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+/*!
+ Returns the key path for the receiver.
+ @return The key path for the receiver.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (CPString)keyPath
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+/*!
+ Returns the function for the receiver.
+ @return The function for the receiver.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (CPString)function
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+/*!
+ Returns the arguments for the receiver.
+ @return The arguments for the receiver—that is, the array of expressions that will be passed as parameters during invocation of the selector on the operand of a function expression.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (CPArray)arguments
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+/*!
+ Returns the collection of expressions in an aggregate expression, or the collection element of a subquery expression.
+ @return The collection of expressions in an aggregate expression, or the collection element of a subquery expression.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (id)collection
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+/*!
+ Returns the predicate in a subquery expression.
+ @return The predicate in a subquery expression..
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (CPPredicate)predicate
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+/*!
+ Returns the operand for the receiver.
+ @return The operand for the receiver—that is, the object on which the selector will be invoked.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (CPExpression)operand
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+/*!
+ Returns the left expression of a set expression.
+ @return The left expression of a set expression.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (CPExpression)leftExpression
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+/*!
+ Returns the right expression of a set expression.
+ @return The right expression of a set expression.
+ This method raises an exception if it is not applicable to the receiver.
+*/
+- (CPExpression)rightExpression
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+ return nil;
+}
+
+- (CPExpression)_expressionWithSubstitutionVariables:(CPDictionary)variables
+{
+ return self;
+}
+
+@end
+
+//@import "_CPConstantValueExpression.j"
+//@import "_CPSelfExpression.j"
+//@import "_CPVariableExpression.j"
+//@import "_CPKeyPathExpression.j"
+//@import "_CPFunctionExpression.j"
+//@import "_CPAggregateExpression.j"
+//@import "_CPSetExpression.j"
+//@import "_CPSubqueryExpression.j"
diff --git a/Foundation/CPPredicate/_CPKeyPathExpression.j b/Foundation/CPPredicate/_CPKeyPathExpression.j
index e60e8f331..53b1cfc2e 100644
--- a/Foundation/CPPredicate/_CPKeyPathExpression.j
+++ b/Foundation/CPPredicate/_CPKeyPathExpression.j
@@ -26,6 +26,7 @@
@import "_CPFunctionExpression.j"
@import "CPKeyValueCoding.j"
@import "CPString.j"
+@import "_CPConstantValueExpression.j"
@implementation _CPKeyPathExpression : _CPFunctionExpression
{
diff --git a/Foundation/CPPredicate/_CPPredicate.j b/Foundation/CPPredicate/_CPPredicate.j
new file mode 100644
index 000000000..2fc1392fe
--- /dev/null
+++ b/Foundation/CPPredicate/_CPPredicate.j
@@ -0,0 +1,1001 @@
+/*
+ * CPPredicate.j
+ *
+ * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/)
+ * Copyright (c) 2005 Free Software Foundation.
+ *
+ * Created by cacaodev.
+ * Copyright 2010.
+ *
+ * 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 "CPArray.j"
+@import "CPException.j"
+@import "CPNull.j"
+@import "CPObject.j"
+@import "CPScanner.j"
+@import "CPSet.j"
+@import "CPValue.j"
+@import "CPCharacterSet.j"
+
+@class CPCompoundPredicate
+@class CPComparisonPredicate
+@class _CPSubqueryExpression
+
+@global CPAndPredicateType
+@global CPDirectPredicateModifier
+@global CPAnyPredicateModifier
+@global CPAllPredicateModifier
+@global CPOrPredicateType
+@global CPEqualToPredicateOperatorType
+@global CPNotEqualToPredicateOperatorType
+@global CPLessThanPredicateOperatorType
+@global CPGreaterThanPredicateOperatorType
+@global CPLessThanOrEqualToPredicateOperatorType
+@global CPGreaterThanOrEqualToPredicateOperatorType
+@global CPMatchesPredicateOperatorType
+@global CPLikePredicateOperatorType
+@global CPBeginsWithPredicateOperatorType
+@global CPEndsWithPredicateOperatorType
+@global CPInPredicateOperatorType
+@global CPContainsPredicateOperatorType
+@global CPBetweenPredicateOperatorType
+@global CPCaseInsensitivePredicateOption
+@global CPDiacriticInsensitivePredicateOption
+
+
+/*!
+ @ingroup foundation
+ @class CPPredicate
+ @brief The CPPredicate class is used to define logical conditions used to constrain a search either for a fetch or for in-memory filtering.
+
+ You use predicates to represent logical conditions, used for describing objects in persistent stores
+ and in-memory filtering of objects. Although it is common to create predicates directly from instances
+ of CPComparisonPredicate, CPCompoundPredicate, and CPExpression, you often create predicates from a
+ format string which is parsed by the class methods on CPPredicate. Examples of predicate format strings include:
+
+ - Simple comparisons, such as grade == "7" or firstName like "Shaffiq"
+ - Case/diacritic insensitive lookups, such as name contains[cd] "itroen"
+ - Logical operations, such as (firstName like "Mark") OR (lastName like "Adderley")
+ - “Between” predicates such as date between {$YESTERDAY, $TOMORROW}.
+
+ You can create predicates for relationships, such as:
+
+ - group.name like "work*"
+ - ALL children.age > 12
+ - ANY children.age > 12
+
+ You can create predicates for operations, such as \@sum.items.price < 1000.
+
+ You can also create predicates that include variables, so that the predicate can be pre-defined before
+ substituting concrete values at runtime with the \c evaluateWithObject:substitutionVariables: method.
+*/
+
+@implementation CPPredicate : CPObject
+{
+}
+
+/*!
+ Creates and returns a new predicate formed by creating a new string with a given format and parsing the result.
+ @param format The format string for the new predicate.
+ @param … A comma-separated list of arguments to substitute into format.
+ @return A new predicate formed by creating a new string with format and parsing the result.
+*/
++ (CPPredicate)predicateWithFormat:(CPString)format, ...
+{
+ if (!format)
+ [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"];
+
+ var args = Array.prototype.slice.call(arguments, 3);
+ return [self predicateWithFormat:arguments[2] argumentArray:args];
+}
+
+/*!
+ Creates and returns a new predicate by substituting the values in a given array into a format string and parsing the result.
+ @param format The format string for the new predicate.
+ @param arguments The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the array.
+ @return A new predicate by substituting the values in arguments into predicateFormat, and parsing the result.
+*/
++ (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)args
+{
+ if (!format)
+ [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"];
+
+ var s = [[CPPredicateScanner alloc] initWithString:format args:args],
+ p = [s parse];
+
+ return p;
+}
+
+/*!
+ Creates and returns a new predicate by substituting the values in an argument list into a format string and parsing the result.
+ @param format The format string for the new predicate.
+ @param argList The arguments to substitute into predicateFormat. Values are substituted into predicateFormat in the order they appear in the argument list.
+ @return A new predicate by substituting the values in argList into predicateFormat and parsing the result.
+*/
++ (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList
+{
+ // UNIMPLEMENTED
+ return nil;
+}
+
+/*!
+ Returns a copy of the receiver with the receiver’s variables substituted by values specified in a given substitution variables dictionary.
+ @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver.
+ @return A copy of the receiver with the receiver’s variables substituted by values specified in variables.
+*/
+- (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables
+{
+ // IMPLEMENTED BY SUBCLASSES
+}
+
+/*!
+ Creates and returns a predicate that always evaluates to a given value.
+ @param value The value to which the new predicate should evaluate.
+ @return A predicate that always evaluates to value.
+*/
++ (CPPredicate)predicateWithValue:(BOOL)value
+{
+ return [[CPPredicate_BOOL alloc] initWithBool:value];
+}
+
+// Evaluating a Predicate
+/*!
+ Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver.
+ @param object The object against which to evaluate the receiver.
+ @return YES if object matches the conditions specified by the receiver, otherwise NO.
+*/
+- (BOOL)evaluateWithObject:(id)object
+{
+ // IMPLEMENTED BY SUBCLASSES
+}
+
+/*!
+ Returns a Boolean value that indicates whether a given object matches the conditions specified by the receiver after substituting in the values in a given variables dictionary.
+ @param object The object against which to evaluate the receiver.
+ @param variables The substitution variables dictionary. The dictionary must contain key-value pairs for all variables in the receiver.
+ @return YES if object matches the conditions specified by the receiver after substituting in the values in variables for any replacement tokens, otherwise NO.
+*/
+- (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
+{
+ // IMPLEMENTED BY SUBCLASSES
+}
+
+// Getting Format Information
+/*!
+ Returns the receiver’s format string.
+ @return The receiver’s format string.
+*/
+- (CPString)predicateFormat
+{
+ // IMPLEMENTED BY SUBCLASSES
+}
+
+- (CPString)description
+{
+ return [self predicateFormat];
+}
+
+@end
+
+@implementation CPPredicate_BOOL : CPPredicate
+{
+ BOOL _value;
+}
+
+- (id)initWithBool:(BOOL)value
+{
+ _value = value;
+ return self;
+}
+
+- (BOOL)isEqual:(id)anObject
+{
+ if (self === anObject)
+ return YES;
+
+ if (self.isa !== anObject.isa || _value !== [anObject evaluateObject:nil])
+ return NO;
+
+ return YES;
+}
+
+- (BOOL)evaluateObject:(id)object
+{
+ return _value;
+}
+
+- (CPString)predicateFormat
+{
+ return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE";
+}
+
+@end
+
+
+@implementation CPArray (CPPredicate)
+
+- (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate
+{
+ var count = [self count],
+ result = [CPArray array],
+ i = 0;
+
+ for (; i < count; i++)
+ {
+ var object = [self objectAtIndex:i];
+ if ([predicate evaluateWithObject:object])
+ result.push(object);
+ }
+
+ return result;
+}
+
+- (void)filterUsingPredicate:(CPPredicate)predicate
+{
+ var count = [self count];
+
+ while (count--)
+ {
+ if (![predicate evaluateWithObject:[self objectAtIndex:count]])
+ self.splice(count, 1);
+ }
+}
+
+@end
+
+@implementation CPSet (CPPredicate)
+
+- (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate
+{
+ var count = [self count],
+ result = [CPSet set],
+ i = 0;
+
+ for (; i < count; i++)
+ {
+ var object = [self objectAtIndex:i];
+
+ if ([predicate evaluateWithObject:object])
+ [result addObject:object];
+ }
+
+ return result;
+}
+
+- (void)filterUsingPredicate:(CPPredicate)predicate
+{
+ var count = [self count];
+
+ while (--count >= 0)
+ {
+ var object = [self objectAtIndex:count];
+
+ if (![predicate evaluateWithObject:object])
+ [self removeObjectAtIndex:count];
+ }
+}
+
+@end
+
+#define REFERENCE(variable) \
+function(newValue)\
+{\
+ var oldValue = variable;\
+ if (typeof newValue != 'undefined')\
+ variable = newValue;\
+ return oldValue;\
+}
+
+@implementation CPPredicateScanner : CPScanner
+{
+ CPEnumerator _args;
+ unsigned _retrieved;
+}
+
+- (id)initWithString:(CPString)format args:(CPArray)args
+{
+ self = [super initWithString:format]
+
+ if (self)
+ {
+ _args = [args objectEnumerator];
+ }
+ return self;
+}
+
+- (id)nextArg
+{
+ return [_args nextObject];
+}
+
+- (BOOL)scanPredicateKeyword:(CPString)key
+{
+ var loc = [self scanLocation];
+
+ [self setCaseSensitive:NO];
+ if (![self scanString:key intoString:NULL])
+ return NO;
+
+ if ([self isAtEnd])
+ return YES;
+
+ var c = [[self string] characterAtIndex:[self scanLocation]];
+ if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c])
+ return YES;
+
+ [self setScanLocation:loc];
+
+ return NO;
+}
+
+- (CPPredicate)parse
+{
+ var r = nil;
+
+ try
+ {
+ [self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]];
+ r = [self parsePredicate];
+ }
+ catch(error)
+ {
+ CPLogConsole(@"Unable to parse predicate '" + [self string] + "' with " + error);
+ }
+ finally
+ {
+ if (![self isAtEnd])
+ {
+ var pstr = [self string],
+ loc = [self scanLocation];
+ CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'");
+ }
+ }
+
+ return r;
+}
+
+- (CPPredicate)parsePredicate
+{
+ return [self parseAnd];
+}
+
+- (CPPredicate)parseAnd
+{
+ var l = [self parseOr];
+
+ while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"])
+ {
+ var r = [self parseOr];
+
+ if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType)
+ {
+ if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType)
+ {
+ [[l subpredicates] addObjectsFromArray:[r subpredicates]];
+ }
+ else
+ {
+ [[r subpredicates] insertObject:l atIndex:0];
+ l = r;
+ }
+ }
+ else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType)
+ {
+ [[l subpredicates] addObject:r];
+ }
+ else
+ {
+ l = [CPCompoundPredicate andPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]];
+ }
+ }
+ return l;
+}
+
+- (CPPredicate)parseNot
+{
+ if ([self scanString:@"(" intoString:NULL])
+ {
+ var r = [self parsePredicate];
+
+ if (![self scanString:@")" intoString:NULL])
+ CPRaiseParseError(self, @"predicate");
+
+ return r;
+ }
+
+ if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"])
+ {
+ return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]];
+ }
+ if ([self scanPredicateKeyword:@"TRUEPREDICATE"])
+ {
+ return [CPPredicate predicateWithValue:YES];
+ }
+ if ([self scanPredicateKeyword:@"FALSEPREDICATE"])
+ {
+ return [CPPredicate predicateWithValue:NO];
+ }
+
+ return [self parseComparison];
+}
+
+- (CPPredicate)parseOr
+{
+ var l = [self parseNot];
+ while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"])
+ {
+ var r = [self parseNot];
+
+ if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType)
+ {
+ if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType)
+ {
+ [[l subpredicates] addObjectsFromArray:[r subpredicates]];
+ }
+ else
+ {
+ [[r subpredicates] insertObject:l atIndex:0];
+ l = r;
+ }
+ }
+ else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType)
+ {
+ [[l subpredicates] addObject:r];
+ }
+ else
+ {
+ l = [CPCompoundPredicate orPredicateWithSubpredicates:[CPArray arrayWithObjects:l, r]];
+ }
+ }
+ return l;
+}
+
+- (CPPredicate)parseComparison
+{
+ var modifier = CPDirectPredicateModifier,
+ type = 0,
+ opts = 0,
+ left,
+ right,
+ p,
+ negate = NO;
+
+ if ([self scanPredicateKeyword:@"ANY"])
+ {
+ modifier = CPAnyPredicateModifier;
+ }
+ else if ([self scanPredicateKeyword:@"ALL"])
+ {
+ modifier = CPAllPredicateModifier;
+ }
+ else if ([self scanPredicateKeyword:@"NONE"])
+ {
+ modifier = CPAnyPredicateModifier;
+ negate = YES;
+ }
+ else if ([self scanPredicateKeyword:@"SOME"])
+ {
+ modifier = CPAllPredicateModifier;
+ negate = YES;
+ }
+
+ left = [self parseExpression];
+ if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL])
+ {
+ type = CPNotEqualToPredicateOperatorType;
+ }
+ else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL])
+ {
+ type = CPLessThanOrEqualToPredicateOperatorType;
+ }
+ else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL])
+ {
+ type = CPGreaterThanOrEqualToPredicateOperatorType;
+ }
+ else if ([self scanString:@"<" intoString:NULL])
+ {
+ type = CPLessThanPredicateOperatorType;
+ }
+ else if ([self scanString:@">" intoString:NULL])
+ {
+ type = CPGreaterThanPredicateOperatorType;
+ }
+ else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL])
+ {
+ type = CPEqualToPredicateOperatorType;
+ }
+ else if ([self scanPredicateKeyword:@"MATCHES"])
+ {
+ type = CPMatchesPredicateOperatorType;
+ }
+ else if ([self scanPredicateKeyword:@"LIKE"])
+ {
+ type = CPLikePredicateOperatorType;
+ }
+ else if ([self scanPredicateKeyword:@"BEGINSWITH"])
+ {
+ type = CPBeginsWithPredicateOperatorType;
+ }
+ else if ([self scanPredicateKeyword:@"ENDSWITH"])
+ {
+ type = CPEndsWithPredicateOperatorType;
+ }
+ else if ([self scanPredicateKeyword:@"IN"])
+ {
+ type = CPInPredicateOperatorType;
+ }
+ else if ([self scanPredicateKeyword:@"CONTAINS"])
+ {
+ type = CPContainsPredicateOperatorType;
+ }
+ else if ([self scanPredicateKeyword:@"BETWEEN"])
+ {
+ type = CPBetweenPredicateOperatorType;
+ }
+ else
+ CPRaiseParseError(self, @"comparison predicate");
+
+ if ([self scanString:@"[cd]" intoString:NULL])
+ {
+ opts = CPCaseInsensitivePredicateOption | CPDiacriticInsensitivePredicateOption;
+ }
+ else if ([self scanString:@"[c]" intoString:NULL])
+ {
+ opts = CPCaseInsensitivePredicateOption;
+ }
+ else if ([self scanString:@"[d]" intoString:NULL])
+ {
+ opts = CPDiacriticInsensitivePredicateOption;
+ }
+
+ right = [self parseExpression];
+
+ p = [CPComparisonPredicate predicateWithLeftExpression:left
+ rightExpression:right
+ modifier:modifier
+ type:type
+ options:opts];
+
+ return negate ? [CPCompoundPredicate notPredicateWithSubpredicate:p]:p;
+}
+
+- (CPExpression)parseExpression
+{
+ return [self parseBinaryExpression];
+}
+
+- (CPExpression)parseSimpleExpression
+{
+ var identifier,
+ location,
+ ident,
+ dbl;
+
+ if ([self scanDouble:REFERENCE(dbl)])
+ return [CPExpression expressionForConstantValue:dbl];
+
+ // FIXME: handle integer, hex constants, 0x 0o 0b
+ if ([self scanString:@"-" intoString:NULL])
+ return [CPExpression expressionForFunction:@"chs:" arguments:[CPArray arrayWithObject:[self parseExpression]]];
+
+ if ([self scanString:@"(" intoString:NULL])
+ {
+ var arg = [self parseExpression];
+
+ if (![self scanString:@")" intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+
+ return arg;
+ }
+
+ if ([self scanString:@"{" intoString:NULL])
+ {
+ var a = [];
+
+ if ([self scanString:@"}" intoString:NULL])
+ return [CPExpression expressionForConstantValue:a];
+
+ [a addObject:[self parseExpression]];
+
+ while ([self scanString:@"," intoString:NULL])
+ [a addObject:[self parseExpression]];
+
+ if (![self scanString:@"}" intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+
+ return [CPExpression expressionForAggregate:a];
+ }
+
+ if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"])
+ {
+ return [CPExpression expressionForConstantValue:[CPNull null]];
+ }
+ if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"])
+ {
+ return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]];
+ }
+ if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"])
+ {
+ return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]];
+ }
+ if ([self scanPredicateKeyword:@"SELF"])
+ {
+ return [CPExpression expressionForEvaluatedObject];
+ }
+
+ if ([self scanString:@"$" intoString:NULL])
+ {
+ var variable = [self parseSimpleExpression];
+
+ if (![variable keyPath])
+ CPRaiseParseError(self, @"expression");
+
+ return [CPExpression expressionForVariable:variable];
+ }
+
+ location = [self scanLocation];
+
+ if ([self scanString:@"%" intoString:NULL])
+ {
+ if ([self isAtEnd] == NO)
+ {
+ var c = [[self string] characterAtIndex:[self scanLocation]];
+
+ switch (c)
+ {
+ case '%':// '%%' is treated as '%'
+ location = [self scanLocation];
+ break;
+ case 'K':
+ [self setScanLocation:[self scanLocation] + 1];
+ return [CPExpression expressionForKeyPath:[self nextArg]];
+ case '@':
+ case 'c':
+ case 'C':
+ case 'd':
+ case 'D':
+ case 'i':
+ case 'o':
+ case 'O':
+ case 'u':
+ case 'U':
+ case 'x':
+ case 'X':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ [self setScanLocation:[self scanLocation] + 1];
+ return [CPExpression expressionForConstantValue:[self nextArg]];
+ case 'h':
+ [self scanString:@"h" intoString:NULL];
+ if ([self isAtEnd] == NO)
+ {
+ c = [[self string] characterAtIndex:[self scanLocation]];
+ if (c == 'i' || c == 'u')
+ {
+ [self setScanLocation:[self scanLocation] + 1];
+ return [CPExpression expressionForConstantValue:[self nextArg]];
+ }
+ }
+ break;
+ case 'q':
+ [self scanString:@"q" intoString:NULL];
+ if ([self isAtEnd] == NO)
+ {
+ c = [[self string] characterAtIndex:[self scanLocation]];
+ if (c == 'i' || c == 'u' || c == 'x' || c == 'X')
+ {
+ [self setScanLocation:[self scanLocation] + 1];
+ return [CPExpression expressionForConstantValue:[self nextArg]];
+ }
+ }
+ break;
+ }
+ }
+
+ [self setScanLocation:location];
+ }
+
+ if ([self scanString:@"\"" intoString:NULL])
+ {
+ var skip = [self charactersToBeSkipped],
+ str = @"";
+
+ [self setCharactersToBeSkipped:nil];
+ [self scanUpToString:@"\"" intoString:REFERENCE(str)];
+
+ if ([self scanString:@"\"" intoString:NULL] == NO)
+ CPRaiseParseError(self, @"expression");
+
+ [self setCharactersToBeSkipped:skip];
+
+ return [CPExpression expressionForConstantValue:str];
+ }
+
+ if ([self scanString:@"'" intoString:NULL])
+ {
+ var skip = [self charactersToBeSkipped],
+ str = @"";
+
+ [self setCharactersToBeSkipped:nil];
+ [self scanUpToString:@"'" intoString:REFERENCE(str)];
+
+ if ([self scanString:@"'" intoString:NULL] == NO)
+ CPRaiseParseError(self, @"expression");
+
+ [self setCharactersToBeSkipped:skip];
+
+ return [CPExpression expressionForConstantValue:str];
+ }
+
+ if ([self scanString:@"@" intoString:NULL])
+ {
+ var e = [self parseExpression];
+
+ if (![e keyPath])
+ CPRaiseParseError(self, @"expression");
+
+ return [CPExpression expressionForKeyPath:[e keyPath] + "@"];
+ }
+
+ if ([self scanString:@"SUBQUERY" intoString:NULL])
+ {
+ if (![self scanString:@"(" intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+
+ var collection = [self parseExpression],
+ variableExpression,
+ subpredicate;
+
+ if (![self scanString:@"," intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+ variableExpression = [self parseExpression];
+
+ if (![self scanString:@"," intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+ subpredicate = [self parsePredicate];
+
+ if (![self scanString:@")" intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+
+ return [[_CPSubqueryExpression alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate];
+ }
+
+ if ([self scanString:@"FUNCTION" intoString:NULL])
+ {
+ if (![self scanString:@"(" intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+
+ var args = [CPArray arrayWithObject:[self parseExpression]];
+ while ([self scanString:@"," intoString:NULL])
+ [args addObject:[self parseExpression]];
+
+ if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType)
+ CPRaiseParseError(self, @"expression");
+
+ return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)];
+ }
+
+ [self scanString:@"#" intoString:NULL];
+ if (!identifier)
+ identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"];
+
+ if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)])
+ CPRaiseParseError(self, @"expression");
+
+ return [CPExpression expressionForKeyPath:ident];
+}
+
+- (CPExpression)parseFunctionalExpression
+{
+ var left = [self parseSimpleExpression];
+
+ while (YES)
+ {
+ if ([self scanString:@"." intoString:NULL])
+ {
+ var right = [self parseSimpleExpression],
+ expressionType = [right expressionType];
+
+ if (expressionType == CPKeyPathExpressionType)
+ left = [[_CPKeyPathExpression alloc] initWithOperand:left andKeyPath:[right keyPath]];
+ else if (expressionType == CPVariableExpressionType)
+ left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]];
+ else
+ CPRaiseParseError(self, @"expression");
+ }
+ else if ([self scanString:@"[" intoString:NULL])
+ {
+ // index expression
+ if ([self scanPredicateKeyword:@"FIRST"])
+ {
+ left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]];
+ }
+ else if ([self scanPredicateKeyword:@"LAST"])
+ {
+ left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]];
+ }
+ else if ([self scanPredicateKeyword:@"SIZE"])
+ {
+ left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]];
+ }
+ else
+ {
+ var index = [self parseExpression];
+ left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]];
+ }
+
+ if (![self scanString:@"]" intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+ }
+ else if ([self scanString:@":" intoString:NULL])
+ {
+ // function - this parser allows for (max)(a, b, c) to be properly
+ // recognized and even (%K)(a, b, c) if %K evaluates to "max"
+
+ if (![left keyPath])
+ CPRaiseParseError(self, @"expression");
+
+ var selector = [left keyPath] + @":",
+ args = [];
+
+ if (![self scanString:@"(" intoString:NULL])
+ {
+ var str;
+ [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)];
+
+ if (![self scanString:@":(" intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+
+ selector += str + @":";
+ }
+
+ if (![self scanString:@")" intoString:NULL])
+ {
+ [args addObject:[self parseExpression]];
+ while ([self scanString:@"," intoString:NULL])
+ [args addObject:[self parseExpression]];
+
+ if (![self scanString:@")" intoString:NULL])
+ CPRaiseParseError(self, @"expression");
+ }
+
+ left = [CPExpression expressionForFunction:selector arguments:args];
+ }
+ else if ([self scanString:@"UNION" intoString:NULL])
+ {
+ left = [CPExpression expressionForUnionSet:left with:[self parseExpression]];
+ }
+ else if ([self scanString:@"INTERSECT" intoString:NULL])
+ {
+ left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]];
+ }
+ else if ([self scanString:@"MINUS" intoString:NULL])
+ {
+ left = [CPExpression expressionForMinusSet:left with:[self parseExpression]];
+ }
+ else
+ {
+ // done with suffixes
+ return left;
+ }
+ }
+}
+
+- (CPExpression)parsePowerExpression
+{
+ var left = [self parseFunctionalExpression];
+
+ while (YES)
+ {
+ var right;
+
+ if ([self scanString:@"**" intoString:NULL])
+ {
+ right = [self parseFunctionalExpression];
+ left = [CPExpression expressionForFunction:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]];
+ }
+ else
+ {
+ return left;
+ }
+ }
+}
+
+- (CPExpression)parseMultiplicationExpression
+{
+ var left = [self parsePowerExpression];
+
+ while (YES)
+ {
+ var right;
+
+ if ([self scanString:@"*" intoString:NULL])
+ {
+ right = [self parsePowerExpression];
+ left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]];
+ }
+ else if ([self scanString:@"/" intoString:NULL])
+ {
+ right = [self parsePowerExpression];
+ left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]];
+ }
+ else
+ {
+ return left;
+ }
+ }
+}
+
+- (CPExpression)parseAdditionExpression
+{
+ var left = [self parseMultiplicationExpression];
+
+ while (YES)
+ {
+ var right;
+
+ if ([self scanString:@"+" intoString:NULL])
+ {
+ right = [self parseMultiplicationExpression];
+ left = [CPExpression expressionForFunction:@"add:to:" arguments:[CPArray arrayWithObjects:left, right]];
+ }
+ else if ([self scanString:@"-" intoString:NULL])
+ {
+ right = [self parseMultiplicationExpression];
+ left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]];
+ }
+ else
+ {
+ return left;
+ }
+ }
+}
+
+- (CPExpression)parseBinaryExpression
+{
+ var left = [self parseAdditionExpression];
+
+ while (YES)
+ {
+ var right;
+
+ if ([self scanString:@":=" intoString:NULL]) // assignment
+ {
+ // check left to be a variable?
+ right = [self parseAdditionExpression];
+ // FIXME
+ }
+ else
+ {
+ return left;
+ }
+ }
+}
+
+@end
+
+var CPRaiseParseError = function CPRaiseParseError(aScanner, target)
+{
+ [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]];
+};
+
+//@import "CPCompoundPredicate.j"
+//@import "CPComparisonPredicate.j"
+//@import "CPExpression.j"
diff --git a/Foundation/CPProxy.j b/Foundation/CPProxy.j
index e935ad344..ceeac578d 100644
--- a/Foundation/CPProxy.j
+++ b/Foundation/CPProxy.j
@@ -27,6 +27,7 @@
@implementation CPProxy
{
+ Class isa;
}
+ (void)load
@@ -80,12 +81,12 @@
if (typeof self._UID === "undefined")
self._UID = objj_generateObjectUID();
- return _UID;
+ return self._UID;
}
- (BOOL)isEqual:(id)anObject
{
- return self === object;
+ return self === anObject;
}
- (id)self
diff --git a/Foundation/CPSet+KVO.j b/Foundation/CPSet+KVO.j
index 4af539b36..6153cf5d1 100644
--- a/Foundation/CPSet+KVO.j
+++ b/Foundation/CPSet+KVO.j
@@ -22,7 +22,7 @@
@import "CPException.j"
@import "CPObject.j"
-@import "CPSet.j"
+@import "CPMutableSet.j"
@import "_CPCollectionKVCOperators.j"
@implementation CPObject (CPSetKVO)
diff --git a/Foundation/CPSet/CPMutableSet.j b/Foundation/CPSet/CPMutableSet.j
index 79b76aee0..a70c74c55 100644
--- a/Foundation/CPSet/CPMutableSet.j
+++ b/Foundation/CPSet/CPMutableSet.j
@@ -7,7 +7,7 @@
this class only exists for source compatability.
*/
-@import "CPSet.j"
+@import "_CPSet.j"
@implementation CPMutableSet : CPSet
@@ -146,4 +146,4 @@
@end
-@import "_CPConcreteMutableSet.j"
+//@import "_CPConcreteMutableSet.j"
diff --git a/Foundation/CPSet/CPSet.j b/Foundation/CPSet/CPSet.j
index 4c0773bb4..3e2a88286 100644
--- a/Foundation/CPSet/CPSet.j
+++ b/Foundation/CPSet/CPSet.j
@@ -24,491 +24,4 @@
*
*/
-@import "CPArray.j"
-@import "CPEnumerator.j"
-@import "CPNumber.j"
-@import "CPObject.j"
-
-/*!
- @class CPMutableSet
- @ingroup Foundation
-
- CPSet is a data structure for storing an an unordered collection of unique objects.
- Sets have O(1) insertion/lookup/deletion time complexity.
-*/
-
-@implementation CPSet : CPObject
-{
-}
-
-+ (id)alloc
-{
- if (self === [CPSet class] || self === [CPMutableSet class])
- return [_CPPlaceholderSet alloc];
-
- return [super alloc];
-}
-
-/*!
- Creates and returns an empty set.
-*/
-+ (id)set
-{
- return [[self alloc] init];
-}
-
-/*!
- Creates and returns a set containing a uniqued collection of those objects contained in a given array.
- @param anArray array containing the objects to add to the new set. If the same object appears more than once objects, it is added only once to the returned set.
-*/
-+ (id)setWithArray:(CPArray)anArray
-{
- return [[self alloc] initWithArray:anArray];
-}
-
-/*!
- Creates and returns a set that contains a single given object.
- @param anObject The object to add to the new set.
-*/
-+ (id)setWithObject:(id)anObject
-{
- return [[self alloc] initWithObjects:anObject];
-}
-
-/*!
- Creates and returns a set containing a specified number of objects from a given array of objects.
- @param objects A array of objects to add to the new set. If the same object appears more than once objects, it is added only once to the returned set.
- @param count The number of objects from objects to add to the new set.
-*/
-+ (id)setWithObjects:(id)objects count:(CPUInteger)count
-{
- return [[self alloc] initWithObjects:objects count:count];
-}
-
-/*!
- Creates and returns a set containing the objects in a given argument list.
- @param anObject The first object to add to the new set.
- @param ... A comma-separated list of objects, ending with nil, to add to the new set. If the same object appears more than once objects, it is added only once to the returned set.
-*/
-+ (id)setWithObjects:(id)anObject, ...
-{
- var argumentsArray = Array.prototype.slice.apply(arguments);
-
- argumentsArray[0] = [self alloc];
- argumentsArray[1] = @selector(initWithObjects:);
-
- return objj_msgSend.apply(this, argumentsArray);
-}
-
-/*!
- Creates and returns a set containing the objects from another set.
- @param aSet A set containing the objects to add to the new set.
-*/
-+ (id)setWithSet:(CPSet)set
-{
- return [[self alloc] initWithSet:set];
-}
-
-/*!
- Creates and returns a set by adding anObject.
- @param anObject to add to the new set.
-*/
-- (id)setByAddingObject:(id)anObject
-{
- return [[self class] setWithArray:[[self allObjects] arrayByAddingObject:anObject]];
-}
-
-/*!
- Creates and returns a set by adding the objects from another set.
- @param aSet to add objects to add to the new set.
-*/
-- (id)setByAddingObjectsFromSet:(CPSet)aSet
-{
- return [self setByAddingObjectsFromArray:[aSet allObjects]];
-}
-
-/*!
- Creates and returns a set by adding the objects from an array.
- @param anArray with objects to add to a new set.
-*/
-- (id)setByAddingObjectsFromArray:(CPArray)anArray
-{
- return [[self class] setWithArray:[[self allObjects] arrayByAddingObjectsFromArray:anArray]];
-}
-
-/*!
- Basic initializer, returns an empty set.
-*/
-- (id)init
-{
- return [self initWithObjects:nil count:0];
-}
-
-/*!
- Initializes a newly allocated set with the objects that are contained in a given array.
- @param array An array of objects to add to the new set. If the same object appears more than once in array, it is represented only once in the returned set.
-*/
-- (id)initWithArray:(CPArray)anArray
-{
- return [self initWithObjects:anArray count:[anArray count]];
-}
-
-/*!
- Initializes a newly allocated set with members taken from the specified list of objects.
- @param anObject The first object to add to the new set.
- @param ... A comma-separated list of objects, ending with nil, to add to the new set. If the same object appears more than once in the list, it is represented only once in the returned set.
-*/
-- (id)initWithObjects:(id)anObject, ...
-{
- var index = 2,
- count = arguments.length;
-
- for (; index < count; ++index)
- if (arguments[index] === nil)
- break;
-
- return [self initWithObjects:Array.prototype.slice.call(arguments, 2, index) count:index - 2];
-}
-
-/*!
- Creates and returns a set containing the objects from an array.
- @param anArray An array containing the objects to add to the new set.
- @param aCount the number of objects in anArray.
-*/
-- (id)initWithObjects:(CPArray)objects count:(CPUInteger)aCount
-{
- if (self === _CPSharedPlaceholderSet)
- return [[_CPConcreteMutableSet alloc] initWithObjects:objects count:aCount];
-
- return [super init];
-}
-
-/*!
- Initializes a newly allocated set and adds to it objects from another given set.
- @param aSet a set containing objects to add to the new set.
-*/
-- (id)initWithSet:(CPSet)aSet
-{
- return [self initWithArray:[aSet allObjects]];
-}
-
-/*!
- Initializes a newly allocated set and adds to it members of another given set. Only included for compatability.
- @param aSet a set of objects to add to the new set.
- @param shouldCopyItems a boolean value. If YES the objects would be copied, if NO the objects will not be copied.
-*/
-- (id)initWithSet:(CPSet)aSet copyItems:(BOOL)shouldCopyItems
-{
- if (shouldCopyItems)
- return [aSet valueForKey:@"copy"];
-
- return [self initWithSet:aSet];
-}
-
-/*!
- Returns the number of members in the receiver.
-*/
-- (CPUInteger)count
-{
- _CPRaiseInvalidAbstractInvocation(self, _cmd);
-}
-
-/*!
- Returns an array containing the receiver’s members, or an empty array if the receiver has no members.
-*/
-- (CPArray)allObjects
-{
- var objects = [],
- object,
- objectEnumerator = [self objectEnumerator];
-
- while ((object = [objectEnumerator nextObject]) !== nil)
- objects.push(object);
-
- return objects;
-}
-
-/*!
- Returns one of the objects in the receiver, or nil if the receiver contains no objects.
-*/
-- (id)anyObject
-{
- return [[self objectEnumerator] nextObject];
-}
-
-/*!
- Returns a Boolean value that indicates whether a given object is present in the receiver.
- @param anObject The object for which to test membership of the receiver.
-*/
-- (BOOL)containsObject:(id)anObject
-{
- return [self member:anObject] !== nil;
-}
-
-/*!
- Returns a set filtered using a given predicate.
- @prarm aPredicate a CPPredicate object used to filter the objects in the set.
-*/
-- (CPSet)filteredSetUsingPredicate:(CPPredicate)aPredicate
-{
- var objects = [],
- object,
- objectEnumerator = [self objectEnumerator];
-
- while ((object = [objectEnumerator nextObject]) !== nil)
- if ([aPredicate evaluateWithObject:object])
- objects.push(object);
-
- return [[[self class] alloc] initWithArray:objects];
-}
-
-/*!
- Sends to each object in the receiver a message specified by a given selector.
- @param aSelector A selector that specifies the message to send to the members of the receiver. The method must not take any arguments. It should not have the side effect of modifying the receiver. This value must not be NULL.
-*/
-- (void)makeObjectsPerformSelector:(SEL)aSelector
-{
- [self makeObjectsPerformSelector:aSelector withObjects:nil];
-}
-
-/*!
- Sends to each object in the receiver a message specified by a given selector.
- @param aSelector A selector that specifies the message to send to the receiver's members. The method must take a single argument of type id. The method should not, as a side effect, modify the receiver. The value must not be NULL.
- @param anObject The object to pass as an argument to the method specified by aSelector.
-*/
-- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)anObject
-{
- [self makeObjectsPerformSelector:aSelector withObjects:[anObject]];
-}
-
-/*!
- Sends to each object in the receiver a message specified by a given selector.
- @param aSelector A selector that specifies the message to send to the receiver's members. The method must take a single argument of type id. The method should not, as a side effect, modify the receiver. The value must not be NULL.
- @param objects The objects to pass as an argument to the method specified by aSelector.
-*/
-- (void)makeObjectsPerformSelector:(SEL)aSelector withObjects:(CPArray)objects
-{
- var object,
- objectEnumerator = [self objectEnumerator],
- argumentsArray = [nil, aSelector].concat(objects || []);
-
- while ((object = [objectEnumerator nextObject]) !== nil)
- {
- argumentsArray[0] = object;
- objj_msgSend.apply(this, argumentsArray);
- }
-}
-
-/*!
- Determines whether the receiver contains an object equal to a given object, and returns that object if it is present.
- @param anObject The object for which to test for membership of the receiver.
-*/
-- (id)member:(id)anObject
-{
- _CPRaiseInvalidAbstractInvocation(self, _cmd);
-}
-
-/*!
- Returns an object enumerator (CPEnumerator) for the receiver.
-*/
-- (CPEnumerator)objectEnumerator
-{
- _CPRaiseInvalidAbstractInvocation(self, _cmd);
-}
-
-/*!
- Enumberates over the objects in a set using a given function.
- @param aFunction a callback for each itteration, should be of the format: function(anObject).
-*/
-- (void)enumerateObjectsUsingBlock:(Function)aFunction
-{
- var object,
- objectEnumerator = [self objectEnumerator];
-
- while ((object = [objectEnumerator nextObject]) !== nil)
- if (aFunction(object))
- break;
-}
-
-// FIXME: stop is broken.
-- (CPSet)objectsPassingTest:(Function)aFunction
-{
- var objects = [],
- object = nil,
- objectEnumerator = [self objectEnumerator];
-
- while ((object = [objectEnumerator nextObject]) !== nil)
- if (aFunction(object))
- objects.push(object);
-
- return [[[self class] alloc] initWithArray:objects];
-}
-
-/*!
- Returns a Boolean value that indicates whether every object in the receiver is also present in another given set.
- @param set The set with which to compare the receiver.
-*/
-- (BOOL)isSubsetOfSet:(CPSet)aSet
-{
- var object = nil,
- objectEnumerator = [self objectEnumerator];
-
- while ((object = [objectEnumerator nextObject]) !== nil)
- if (![aSet containsObject:object])
- return NO;
-
- return YES;
-}
-
-/*!
- Returns a Boolean value that indicates whether at least one object in the receiver is also present in another given set.
- @param set The set with which to compare the receiver.
-*/
-- (BOOL)intersectsSet:(CPSet)aSet
-{
- if (self === aSet)
- // The empty set intersects nothing
- return [self count] > 0;
-
- var object = nil,
- objectEnumerator = [self objectEnumerator];
-
- while ((object = [objectEnumerator nextObject]) !== nil)
- if ([aSet containsObject:object])
- return YES;
-
- return NO;
-}
-
-/*!
- Returns an array of the set's content sorted as specified by a given array of sort descriptors.
-
- @param sortDescriptors an array of CPSortDescriptor objects.
-*/
-- (CPArray)sortedArrayUsingDescriptors:(CPArray)someSortDescriptors
-{
- return [[self allObjects] sortedArrayUsingDescriptors:someSortDescriptors];
-}
-
-/*!
- Compares the receiver to another set.
- @param set The set with which to compare the receiver.
-*/
-- (BOOL)isEqualToSet:(CPSet)aSet
-{
- return [self isEqual:aSet];
-}
-
-/*!
- Returns YES if BOTH sets are a subset of the other.
- @param aSet a set of objects
-*/
-- (BOOL)isEqual:(CPSet)aSet
-{
- // If both are subsets of each other, they are equal
- return self === aSet ||
- [aSet isKindOfClass:[CPSet class]] &&
- ([self count] === [aSet count] &&
- [aSet isSubsetOfSet:self]);
-}
-
-- (CPString)description
-{
- var string = "{(\n",
- objects = [self allObjects],
- index = 0,
- count = [objects count];
-
- for (; index < count; ++index)
- {
- var object = objects[index];
-
- string += "\t" + String(object).split('\n').join("\n\t") + "\n";
- }
-
- return string + ")}";
-}
-
-@end
-
-@implementation CPSet (CPCopying)
-
-- (id)copy
-{
- return [[self class] setWithSet:self];
-}
-
-- (id)mutableCopy
-{
- return [self copy];
-}
-
-@end
-
-var CPSetObjectsKey = @"CPSetObjectsKey";
-
-@implementation CPSet (CPCoding)
-
-- (id)initWithCoder:(CPCoder)aCoder
-{
- return [self initWithArray:[aCoder decodeObjectForKey:CPSetObjectsKey]];
-}
-
-- (void)encodeWithCoder:(CPCoder)aCoder
-{
- [aCoder encodeObject:[self allObjects] forKey:CPSetObjectsKey];
-}
-
-@end
-
-@implementation CPSet (CPKeyValueCoding)
-
-- (id)valueForKey:(CPString)aKey
-{
- if (aKey === "@count")
- return [self count];
-
- var valueSet = [CPSet set],
- object,
- objectEnumerator = [self objectEnumerator];
-
- while ((object = [objectEnumerator nextObject]) !== nil)
- {
- var value = [object valueForKey:aKey];
-
- [valueSet addObject:value];
- }
-
- return valueSet;
-}
-
-- (void)setValue:(id)aValue forKey:(CPString)aKey
-{
- var object,
- objectEnumerator = [self objectEnumerator];
-
- while ((object = [objectEnumerator nextObject]) !== nil)
- [object setValue:aValue forKey:aKey];
-}
-
-@end
-
-var _CPSharedPlaceholderSet = nil;
-
-@implementation _CPPlaceholderSet : CPSet
-{
-}
-
-+ (id)alloc
-{
- if (!_CPSharedPlaceholderSet)
- _CPSharedPlaceholderSet = [super alloc];
-
- return _CPSharedPlaceholderSet;
-}
-
-@end
-
-// We actually want _CPConcreteMutableSet, but this introduces the possibility of an invalid @import loop.
-// This will be correctly solved when we move to true immutable/mutable pairs.
-@import "CPMutableSet.j"
+ @import "_CPConcreteMutableSet.j"
diff --git a/Foundation/CPSet/_CPSet.j b/Foundation/CPSet/_CPSet.j
new file mode 100644
index 000000000..f80124c31
--- /dev/null
+++ b/Foundation/CPSet/_CPSet.j
@@ -0,0 +1,516 @@
+/*
+ * CPSet.j
+ * Foundation
+ *
+ * Created by Bailey Carlson
+ * Extended by Ross Boucher
+ * Extended by Nabil Elisa
+ * Rewritten by Francisco Tolmasky
+ * Copyright 2008, 280 North, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+@import "CPArray.j"
+@import "CPEnumerator.j"
+@import "CPNumber.j"
+@import "CPObject.j"
+
+@class _CPConcreteMutableSet
+
+/*!
+ @class CPMutableSet
+ @ingroup Foundation
+
+ CPSet is a data structure for storing an an unordered collection of unique objects.
+ Sets have O(1) insertion/lookup/deletion time complexity.
+*/
+
+@implementation CPSet : CPObject
+{
+}
+
++ (id)alloc
+{
+ if (self === [CPSet class] || self === [CPMutableSet class])
+ return [_CPPlaceholderSet alloc];
+
+ return [super alloc];
+}
+
+/*!
+ Creates and returns an empty set.
+*/
++ (id)set
+{
+ return [[self alloc] init];
+}
+
+/*!
+ Creates and returns a set containing a uniqued collection of those objects contained in a given array.
+ @param anArray array containing the objects to add to the new set. If the same object appears more than once objects, it is added only once to the returned set.
+*/
++ (id)setWithArray:(CPArray)anArray
+{
+ return [[self alloc] initWithArray:anArray];
+}
+
+/*!
+ Creates and returns a set that contains a single given object.
+ @param anObject The object to add to the new set.
+*/
++ (id)setWithObject:(id)anObject
+{
+ return [[self alloc] initWithObjects:anObject];
+}
+
+/*!
+ Creates and returns a set containing a specified number of objects from a given array of objects.
+ @param objects A array of objects to add to the new set. If the same object appears more than once objects, it is added only once to the returned set.
+ @param count The number of objects from objects to add to the new set.
+*/
++ (id)setWithObjects:(id)objects count:(CPUInteger)count
+{
+ return [[self alloc] initWithObjects:objects count:count];
+}
+
+/*!
+ Creates and returns a set containing the objects in a given argument list.
+ @param anObject The first object to add to the new set.
+ @param ... A comma-separated list of objects, ending with nil, to add to the new set. If the same object appears more than once objects, it is added only once to the returned set.
+*/
++ (id)setWithObjects:(id)anObject, ...
+{
+ var argumentsArray = Array.prototype.slice.apply(arguments);
+
+ argumentsArray[0] = [self alloc];
+ argumentsArray[1] = @selector(initWithObjects:);
+
+ return objj_msgSend.apply(this, argumentsArray);
+}
+
+/*!
+ Creates and returns a set containing the objects from another set.
+ @param aSet A set containing the objects to add to the new set.
+*/
++ (id)setWithSet:(CPSet)set
+{
+ return [[self alloc] initWithSet:set];
+}
+
+/*!
+ Creates and returns a set by adding anObject.
+ @param anObject to add to the new set.
+*/
+- (id)setByAddingObject:(id)anObject
+{
+ return [[self class] setWithArray:[[self allObjects] arrayByAddingObject:anObject]];
+}
+
+/*!
+ Creates and returns a set by adding the objects from another set.
+ @param aSet to add objects to add to the new set.
+*/
+- (id)setByAddingObjectsFromSet:(CPSet)aSet
+{
+ return [self setByAddingObjectsFromArray:[aSet allObjects]];
+}
+
+/*!
+ Creates and returns a set by adding the objects from an array.
+ @param anArray with objects to add to a new set.
+*/
+- (id)setByAddingObjectsFromArray:(CPArray)anArray
+{
+ return [[self class] setWithArray:[[self allObjects] arrayByAddingObjectsFromArray:anArray]];
+}
+
+/*!
+ Basic initializer, returns an empty set.
+*/
+- (id)init
+{
+ return [self initWithObjects:nil count:0];
+}
+
+/*!
+ Initializes a newly allocated set with the objects that are contained in a given array.
+ @param array An array of objects to add to the new set. If the same object appears more than once in array, it is represented only once in the returned set.
+*/
+- (id)initWithArray:(CPArray)anArray
+{
+ return [self initWithObjects:anArray count:[anArray count]];
+}
+
+/*!
+ Initializes a newly allocated set with members taken from the specified list of objects.
+ @param anObject The first object to add to the new set.
+ @param ... A comma-separated list of objects, ending with nil, to add to the new set. If the same object appears more than once in the list, it is represented only once in the returned set.
+*/
+- (id)initWithObjects:(id)anObject, ...
+{
+ var index = 2,
+ count = arguments.length;
+
+ for (; index < count; ++index)
+ if (arguments[index] === nil)
+ break;
+
+ return [self initWithObjects:Array.prototype.slice.call(arguments, 2, index) count:index - 2];
+}
+
+/*!
+ Creates and returns a set containing the objects from an array.
+ @param anArray An array containing the objects to add to the new set.
+ @param aCount the number of objects in anArray.
+*/
+- (id)initWithObjects:(CPArray)objects count:(CPUInteger)aCount
+{
+ if (self === _CPSharedPlaceholderSet)
+ return [[_CPConcreteMutableSet alloc] initWithObjects:objects count:aCount];
+
+ return [super init];
+}
+
+/*!
+ Initializes a newly allocated set and adds to it objects from another given set.
+ @param aSet a set containing objects to add to the new set.
+*/
+- (id)initWithSet:(CPSet)aSet
+{
+ return [self initWithArray:[aSet allObjects]];
+}
+
+/*!
+ Initializes a newly allocated set and adds to it members of another given set. Only included for compatability.
+ @param aSet a set of objects to add to the new set.
+ @param shouldCopyItems a boolean value. If YES the objects would be copied, if NO the objects will not be copied.
+*/
+- (id)initWithSet:(CPSet)aSet copyItems:(BOOL)shouldCopyItems
+{
+ if (shouldCopyItems)
+ return [aSet valueForKey:@"copy"];
+
+ return [self initWithSet:aSet];
+}
+
+/*!
+ Returns the number of members in the receiver.
+*/
+- (CPUInteger)count
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+}
+
+/*!
+ Returns an array containing the receiver’s members, or an empty array if the receiver has no members.
+*/
+- (CPArray)allObjects
+{
+ var objects = [],
+ object,
+ objectEnumerator = [self objectEnumerator];
+
+ while ((object = [objectEnumerator nextObject]) !== nil)
+ objects.push(object);
+
+ return objects;
+}
+
+/*!
+ Returns one of the objects in the receiver, or nil if the receiver contains no objects.
+*/
+- (id)anyObject
+{
+ return [[self objectEnumerator] nextObject];
+}
+
+/*!
+ Returns a Boolean value that indicates whether a given object is present in the receiver.
+ @param anObject The object for which to test membership of the receiver.
+*/
+- (BOOL)containsObject:(id)anObject
+{
+ return [self member:anObject] !== nil;
+}
+
+/*!
+ Returns a set filtered using a given predicate.
+ @prarm aPredicate a CPPredicate object used to filter the objects in the set.
+*/
+- (CPSet)filteredSetUsingPredicate:(CPPredicate)aPredicate
+{
+ var objects = [],
+ object,
+ objectEnumerator = [self objectEnumerator];
+
+ while ((object = [objectEnumerator nextObject]) !== nil)
+ if ([aPredicate evaluateWithObject:object])
+ objects.push(object);
+
+ return [[[self class] alloc] initWithArray:objects];
+}
+
+/*!
+ Sends to each object in the receiver a message specified by a given selector.
+ @param aSelector A selector that specifies the message to send to the members of the receiver. The method must not take any arguments. It should not have the side effect of modifying the receiver. This value must not be NULL.
+*/
+- (void)makeObjectsPerformSelector:(SEL)aSelector
+{
+ [self makeObjectsPerformSelector:aSelector withObjects:nil];
+}
+
+/*!
+ Sends to each object in the receiver a message specified by a given selector.
+ @param aSelector A selector that specifies the message to send to the receiver's members. The method must take a single argument of type id. The method should not, as a side effect, modify the receiver. The value must not be NULL.
+ @param anObject The object to pass as an argument to the method specified by aSelector.
+*/
+- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)anObject
+{
+ [self makeObjectsPerformSelector:aSelector withObjects:[anObject]];
+}
+
+/*!
+ Sends to each object in the receiver a message specified by a given selector.
+ @param aSelector A selector that specifies the message to send to the receiver's members. The method must take a single argument of type id. The method should not, as a side effect, modify the receiver. The value must not be NULL.
+ @param objects The objects to pass as an argument to the method specified by aSelector.
+*/
+- (void)makeObjectsPerformSelector:(SEL)aSelector withObjects:(CPArray)objects
+{
+ var object,
+ objectEnumerator = [self objectEnumerator],
+ argumentsArray = [nil, aSelector].concat(objects || []);
+
+ while ((object = [objectEnumerator nextObject]) !== nil)
+ {
+ argumentsArray[0] = object;
+ objj_msgSend.apply(this, argumentsArray);
+ }
+}
+
+/*!
+ Determines whether the receiver contains an object equal to a given object, and returns that object if it is present.
+ @param anObject The object for which to test for membership of the receiver.
+*/
+- (id)member:(id)anObject
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+}
+
+/*!
+ Returns an object enumerator (CPEnumerator) for the receiver.
+*/
+- (CPEnumerator)objectEnumerator
+{
+ _CPRaiseInvalidAbstractInvocation(self, _cmd);
+}
+
+/*!
+ Enumberates over the objects in a set using a given function.
+ @param aFunction a callback for each itteration, should be of the format: function(anObject).
+*/
+- (void)enumerateObjectsUsingBlock:(Function)aFunction
+{
+ var object,
+ objectEnumerator = [self objectEnumerator];
+
+ while ((object = [objectEnumerator nextObject]) !== nil)
+ if (aFunction(object))
+ break;
+}
+
+// FIXME: stop is broken.
+- (CPSet)objectsPassingTest:(Function)aFunction
+{
+ var objects = [],
+ object = nil,
+ objectEnumerator = [self objectEnumerator];
+
+ while ((object = [objectEnumerator nextObject]) !== nil)
+ if (aFunction(object))
+ objects.push(object);
+
+ return [[[self class] alloc] initWithArray:objects];
+}
+
+/*!
+ Returns a Boolean value that indicates whether every object in the receiver is also present in another given set.
+ @param set The set with which to compare the receiver.
+*/
+- (BOOL)isSubsetOfSet:(CPSet)aSet
+{
+ var object = nil,
+ objectEnumerator = [self objectEnumerator];
+
+ while ((object = [objectEnumerator nextObject]) !== nil)
+ if (![aSet containsObject:object])
+ return NO;
+
+ return YES;
+}
+
+/*!
+ Returns a Boolean value that indicates whether at least one object in the receiver is also present in another given set.
+ @param set The set with which to compare the receiver.
+*/
+- (BOOL)intersectsSet:(CPSet)aSet
+{
+ if (self === aSet)
+ // The empty set intersects nothing
+ return [self count] > 0;
+
+ var object = nil,
+ objectEnumerator = [self objectEnumerator];
+
+ while ((object = [objectEnumerator nextObject]) !== nil)
+ if ([aSet containsObject:object])
+ return YES;
+
+ return NO;
+}
+
+/*!
+ Returns an array of the set's content sorted as specified by a given array of sort descriptors.
+
+ @param sortDescriptors an array of CPSortDescriptor objects.
+*/
+- (CPArray)sortedArrayUsingDescriptors:(CPArray)someSortDescriptors
+{
+ return [[self allObjects] sortedArrayUsingDescriptors:someSortDescriptors];
+}
+
+/*!
+ Compares the receiver to another set.
+ @param set The set with which to compare the receiver.
+*/
+- (BOOL)isEqualToSet:(CPSet)aSet
+{
+ return [self isEqual:aSet];
+}
+
+/*!
+ Returns YES if BOTH sets are a subset of the other.
+ @param aSet a set of objects
+*/
+- (BOOL)isEqual:(CPSet)aSet
+{
+ // If both are subsets of each other, they are equal
+ return self === aSet ||
+ [aSet isKindOfClass:[CPSet class]] &&
+ ([self count] === [aSet count] &&
+ [aSet isSubsetOfSet:self]);
+}
+
+- (CPString)description
+{
+ var string = "{(\n",
+ objects = [self allObjects],
+ index = 0,
+ count = [objects count];
+
+ for (; index < count; ++index)
+ {
+ var object = objects[index];
+
+ string += "\t" + String(object).split('\n').join("\n\t") + "\n";
+ }
+
+ return string + ")}";
+}
+
+@end
+
+@implementation CPSet (CPCopying)
+
+- (id)copy
+{
+ return [[self class] setWithSet:self];
+}
+
+- (id)mutableCopy
+{
+ return [self copy];
+}
+
+@end
+
+var CPSetObjectsKey = @"CPSetObjectsKey";
+
+@implementation CPSet (CPCoding)
+
+- (id)initWithCoder:(CPCoder)aCoder
+{
+ return [self initWithArray:[aCoder decodeObjectForKey:CPSetObjectsKey]];
+}
+
+- (void)encodeWithCoder:(CPCoder)aCoder
+{
+ [aCoder encodeObject:[self allObjects] forKey:CPSetObjectsKey];
+}
+
+@end
+
+@implementation CPSet (CPKeyValueCoding)
+
+- (id)valueForKey:(CPString)aKey
+{
+ if (aKey === "@count")
+ return [self count];
+
+ var valueSet = [CPSet set],
+ object,
+ objectEnumerator = [self objectEnumerator];
+
+ while ((object = [objectEnumerator nextObject]) !== nil)
+ {
+ var value = [object valueForKey:aKey];
+
+ [valueSet addObject:value];
+ }
+
+ return valueSet;
+}
+
+- (void)setValue:(id)aValue forKey:(CPString)aKey
+{
+ var object,
+ objectEnumerator = [self objectEnumerator];
+
+ while ((object = [objectEnumerator nextObject]) !== nil)
+ [object setValue:aValue forKey:aKey];
+}
+
+@end
+
+var _CPSharedPlaceholderSet = nil;
+
+@implementation _CPPlaceholderSet : CPSet
+{
+}
+
++ (id)alloc
+{
+ if (!_CPSharedPlaceholderSet)
+ _CPSharedPlaceholderSet = [super alloc];
+
+ return _CPSharedPlaceholderSet;
+}
+
+@end
+
+// We actually want _CPConcreteMutableSet, but this introduces the possibility of an invalid @import loop.
+// This will be correctly solved when we move to true immutable/mutable pairs.
+//@import "CPMutableSet.j"
diff --git a/Foundation/CPSortDescriptor.j b/Foundation/CPSortDescriptor.j
index ec093160a..3a61d3ca2 100755
--- a/Foundation/CPSortDescriptor.j
+++ b/Foundation/CPSortDescriptor.j
@@ -24,6 +24,8 @@
@import "CPObjJRuntime.j"
@import "CPString.j"
+@class CPString
+
/*!
@class CPSortDescriptor
@ingroup foundation
diff --git a/Foundation/CPString.j b/Foundation/CPString.j
index c7da015b1..7983111dc 100644
--- a/Foundation/CPString.j
+++ b/Foundation/CPString.j
@@ -28,6 +28,11 @@
@import "CPURL.j"
@import "CPValue.j"
+@class CPException
+
+@global CPInvalidArgumentException
+@global CPRangeException
+
#define _CPMaxRange(aRange) ((aRange).location + (aRange).length)
/*!
@@ -194,7 +199,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (int)length
{
- return length;
+ return self.length;
}
/*!
@@ -203,7 +208,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)characterAtIndex:(unsigned)anIndex
{
- return charAt(anIndex);
+ return self.charAt(anIndex);
}
// Combining strings
@@ -246,15 +251,15 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)stringByPaddingToLength:(unsigned)aLength withString:(CPString)aString startingAtIndex:(unsigned)anIndex
{
- if (length == aLength)
+ if (self.length == aLength)
return self;
- if (aLength < length)
- return substr(0, aLength);
+ if (aLength < self.length)
+ return self.substr(0, aLength);
var string = self,
substring = aString.substring(anIndex),
- difference = aLength - length;
+ difference = aLength - self.length;
while ((difference -= substring.length) >= 0)
string += substring;
@@ -279,7 +284,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPArray)componentsSeparatedByString:(CPString)aString
{
- return split(aString);
+ return self.split(aString);
}
/*!
@@ -289,7 +294,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)substringFromIndex:(unsigned)anIndex
{
- return substr(anIndex);
+ return self.substr(anIndex);
}
/*!
@@ -299,10 +304,10 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)substringWithRange:(CPRange)aRange
{
- if (aRange.location < 0 || _CPMaxRange(aRange) > length)
+ if (aRange.location < 0 || _CPMaxRange(aRange) > self.length)
[CPException raise:CPRangeException reason:"aRange out of bounds"];
- return substr(aRange.location, aRange.length);
+ return self.substr(aRange.location, aRange.length);
}
/*!
@@ -314,10 +319,10 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)substringToIndex:(unsigned)anIndex
{
- if (anIndex > length)
+ if (anIndex > self.length)
[CPException raise:CPRangeException reason:"index out of bounds"];
- return substring(0, anIndex);
+ return self.substring(0, anIndex);
}
// Finding characters and substrings
@@ -436,9 +441,9 @@ var CPStringUIDs = new CFMutableDictionary(),
- (CPString)stringByReplacingOccurrencesOfString:(CPString)target withString:(CPString)replacement options:(int)options range:(CPRange)searchRange
{
- var start = substring(0, searchRange.location),
- stringSegmentToSearch = substr(searchRange.location, searchRange.length),
- end = substring(searchRange.location + searchRange.length, self.length),
+ var start = self.substring(0, searchRange.location),
+ stringSegmentToSearch = self.substr(searchRange.location, searchRange.length),
+ end = self.substring(searchRange.location + searchRange.length, self.length),
target = [target stringByEscapingRegexControlCharacters],
regExp;
@@ -459,7 +464,7 @@ var CPStringUIDs = new CFMutableDictionary(),
- (CPString)stringByReplacingCharactersInRange:(CPRange)range withString:(CPString)replacement
{
- return '' + substring(0, range.location) + replacement + substring(range.location + range.length, self.length);
+ return '' + self.substring(0, range.location) + replacement + self.substring(range.location + range.length, self.length);
}
/*!
@@ -546,7 +551,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (BOOL)hasPrefix:(CPString)aString
{
- return aString && aString != "" && indexOf(aString) == 0;
+ return aString && aString != "" && self.indexOf(aString) == 0;
}
/*!
@@ -556,7 +561,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (BOOL)hasSuffix:(CPString)aString
{
- return aString && aString != "" && length >= aString.length && lastIndexOf(aString) == (length - aString.length);
+ return aString && aString != "" && self.length >= aString.length && self.lastIndexOf(aString) == (self.length - aString.length);
}
- (BOOL)isEqual:(id)anObject
@@ -657,7 +662,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)lowercaseString
{
- return toLowerCase();
+ return self.toLowerCase();
}
/*!
@@ -665,7 +670,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)uppercaseString
{
- return toUpperCase();
+ return self.toUpperCase();
}
/*!
@@ -710,13 +715,13 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPArray)pathComponents
{
- if (length === 0)
+ if (self.length === 0)
return [""];
if (self === "/")
return ["/"];
- var result = split('/');
+ var result = self.split('/');
if (result[0] === "")
result[0] = "/";
@@ -787,10 +792,10 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)pathExtension
{
- if (lastIndexOf('.') === CPNotFound)
+ if (self.lastIndexOf('.') === CPNotFound)
return "";
- return substr(lastIndexOf('.') + 1);
+ return self.substr(self.lastIndexOf('.') + 1);
}
/*!
@@ -830,7 +835,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)stringByAppendingPathExtension:(CPString)ext
{
- if (ext.indexOf('/') >= 0 || length === 0 || self === "/") // Can't handle these
+ if (ext.indexOf('/') >= 0 || self.length === 0 || self === "/") // Can't handle these
return self;
var components = [self pathComponents],
@@ -852,7 +857,7 @@ var CPStringUIDs = new CFMutableDictionary(),
*/
- (CPString)stringByDeletingLastPathComponent
{
- if (length === 0)
+ if (self.length === 0)
return "";
else if (self === "/")
return "/";
@@ -880,10 +885,10 @@ var CPStringUIDs = new CFMutableDictionary(),
if (extension === "")
return self;
- else if (lastIndexOf('.') < 1)
+ else if (self.lastIndexOf('.') < 1)
return self;
- return substr(0, [self length] - (extension.length + 1));
+ return self.substr(0, [self length] - (extension.length + 1));
}
- (CPString)stringByStandardizingPath
diff --git a/Foundation/CPURLConnection.j b/Foundation/CPURLConnection.j
index e539cec10..8b71cf91b 100644
--- a/Foundation/CPURLConnection.j
+++ b/Foundation/CPURLConnection.j
@@ -94,7 +94,7 @@ var CPURLConnectionDelegate = nil;
@param anError not used
@return the data at the URL or \c nil if there was an error
*/
-+ (CPData)sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:({CPURLResponse})aURLResponse
++ (CPData)sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:(/*{*/CPURLResponse/*}*/)aURLResponse
{
try
{
@@ -277,7 +277,7 @@ var CPURLConnectionDelegate = nil;
@implementation CPURLConnection (Deprecated)
-+ (CPData)sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:({CPURLResponse})aURLResponse error:(id)anError
++ (CPData)sendSynchronousRequest:(CPURLRequest)aRequest returningResponse:(/*{*/CPURLResponse/*}*/)aURLResponse error:(id)anError
{
_CPReportLenientDeprecation(self, _cmd, @selector(sendSynchronousRequest:returningResponse:));
diff --git a/Foundation/CPUndoManager.j b/Foundation/CPUndoManager.j
index 42c24163b..25006a4af 100644
--- a/Foundation/CPUndoManager.j
+++ b/Foundation/CPUndoManager.j
@@ -858,7 +858,7 @@ var CPUndoManagerRedoStackKey = @"CPUndoManagerRedoStackKey",
_undoStack = [aCoder decodeObjectForKey:CPUndoManagerUndoStackKey];
_levelsOfUndo = [aCoder decodeObjectForKey:CPUndoManagerLevelsOfUndoKey];
- _actionName = [aCoder decodeObjectForKey:CPUndoManagerActionNameKey];
+// _actionName = [aCoder decodeObjectForKey:CPUndoManagerActionNameKey];
_currentGrouping = [aCoder decodeObjectForKey:CPUndoManagerCurrentGroupingKey];
_state = CPUndoManagerNormal;
@@ -876,7 +876,7 @@ var CPUndoManagerRedoStackKey = @"CPUndoManagerRedoStackKey",
[aCoder encodeObject:_undoStack forKey:CPUndoManagerUndoStackKey];
[aCoder encodeInt:_levelsOfUndo forKey:CPUndoManagerLevelsOfUndoKey];
- [aCoder encodeObject:_actionName forKey:CPUndoManagerActionNameKey];
+// [aCoder encodeObject:_actionName forKey:CPUndoManagerActionNameKey];
[aCoder encodeObject:_currentGrouping forKey:CPUndoManagerCurrentGroupingKey];
diff --git a/Foundation/CPUserDefaults.j b/Foundation/CPUserDefaults.j
index 88685aa4a..4805aca51 100644
--- a/Foundation/CPUserDefaults.j
+++ b/Foundation/CPUserDefaults.j
@@ -34,6 +34,9 @@
@import "CPURLConnection.j"
@import "CPURLRequest.j"
+@class CPCookie
+
+@global CPApp
CPArgumentDomain = @"CPArgumentDomain";
CPApplicationDomain = [[[CPBundle mainBundle] infoDictionary] objectForKey:@"CPBundleIdentifier"] || @"CPApplicationDomain";
diff --git a/Foundation/CPValueTransformer.j b/Foundation/CPValueTransformer.j
index 69090a51a..1802a2bc0 100644
--- a/Foundation/CPValueTransformer.j
+++ b/Foundation/CPValueTransformer.j
@@ -43,10 +43,10 @@ var transformerMap = [CPDictionary dictionary];
if (self !== [CPValueTransformer class])
return;
- [CPValueTransformer setValueTransformer:[[CPNegateBooleanTransformer alloc] init] forName:CPNegateBooleanTransformerName];
- [CPValueTransformer setValueTransformer:[[CPIsNilTransformer alloc] init] forName:CPIsNilTransformerName];
- [CPValueTransformer setValueTransformer:[[CPIsNotNilTransformer alloc] init] forName:CPIsNotNilTransformerName];
- [CPValueTransformer setValueTransformer:[[CPUnarchiveFromDataTransformer alloc] init] forName:CPUnarchiveFromDataTransformerName];
+// [CPValueTransformer setValueTransformer:[[CPNegateBooleanTransformer alloc] init] forName:CPNegateBooleanTransformerName];
+// [CPValueTransformer setValueTransformer:[[CPIsNilTransformer alloc] init] forName:CPIsNilTransformerName];
+// [CPValueTransformer setValueTransformer:[[CPIsNotNilTransformer alloc] init] forName:CPIsNotNilTransformerName];
+// [CPValueTransformer setValueTransformer:[[CPUnarchiveFromDataTransformer alloc] init] forName:CPUnarchiveFromDataTransformerName];
}
+ (void)setValueTransformer:(CPValueTransformer)transformer forName:(CPString)aName
@@ -192,3 +192,8 @@ CPIsNilTransformerName = @"CPIsNil";
CPIsNotNilTransformerName = @"CPIsNotNil";
CPUnarchiveFromDataTransformerName = @"CPUnarchiveFromData";
CPKeyedUnarchiveFromDataTransformerName = @"CPKeyedUnarchiveFromData";
+
+ [CPValueTransformer setValueTransformer:[[CPNegateBooleanTransformer alloc] init] forName:CPNegateBooleanTransformerName];
+ [CPValueTransformer setValueTransformer:[[CPIsNilTransformer alloc] init] forName:CPIsNilTransformerName];
+ [CPValueTransformer setValueTransformer:[[CPIsNotNilTransformer alloc] init] forName:CPIsNotNilTransformerName];
+ [CPValueTransformer setValueTransformer:[[CPUnarchiveFromDataTransformer alloc] init] forName:CPUnarchiveFromDataTransformerName];
diff --git a/Foundation/_CPCollectionKVCOperators.j b/Foundation/_CPCollectionKVCOperators.j
index f2be4101d..7046fe58f 100644
--- a/Foundation/_CPCollectionKVCOperators.j
+++ b/Foundation/_CPCollectionKVCOperators.j
@@ -20,6 +20,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+@import "CPObject.j"
+
@implementation _CPCollectionKVCOperator : CPObject
+ (id)performOperation:(CPString)operator withCollection:(id)aCollection propertyPath:(CPString)propertyPath
diff --git a/Objective-J/CFBundle.js b/Objective-J/CFBundle.js
index 44280e871..0faec4273 100644
--- a/Objective-J/CFBundle.js
+++ b/Objective-J/CFBundle.js
@@ -106,6 +106,15 @@ function addClassToBundle(aClass, aBundle)
CFBundlesForClasses[aClass.name] = aBundle;
}
+function resetBundle()
+{
+ CFBundlesForURLStrings = { };
+ CFBundlesForClasses = { };
+ //CFCacheBuster = new Date().getTime(),
+ CFTotalBytesLoaded = 0;
+ CPApplicationSizeInBytes = 0;
+}
+
CFBundle.bundleForClass = function(/*Class*/ aClass)
{
return CFBundlesForClasses[aClass.name] || CFBundle.mainBundle();
diff --git a/Objective-J/CFHTTPRequest.js b/Objective-J/CFHTTPRequest.js
index 6d57fc3da..5ba2725bc 100644
--- a/Objective-J/CFHTTPRequest.js
+++ b/Objective-J/CFHTTPRequest.js
@@ -300,6 +300,32 @@ function FileRequest(/*CFURL*/ aURL, onsuccess, onfailure)
if (aURL.pathExtension() === "plist")
request.overrideMimeType("text/xml");
+#if COMMONJS
+ if (aURL.pathExtension() === "j")
+ {
+ var aFilePath = aURL.toString().substring(5),
+ OS = require("os"),
+ gccFlags = require("objective-j").currentCompilerFlags(),
+ gcc = OS.popen("gcc -E -x c -P " + (gccFlags ? gccFlags : "") + " " + OS.enquote(aFilePath), { charset:"UTF-8" }),
+ chunk,
+ fileContents = "";
+
+ while (chunk = gcc.stdout.read())
+ fileContents += chunk;
+
+ if (fileContents.length > 0)
+ {
+ request._nativeRequest.responseText = fileContents;
+ onsuccess({request: request});
+ }
+ else
+ {
+ onfailure({request: request});
+ }
+ return;
+ }
+#endif
+
if (exports.asyncLoader)
{
request.onsuccess = Asynchronous(onsuccess);
diff --git a/Objective-J/CommonJS/lib/objective-j.js b/Objective-J/CommonJS/lib/objective-j.js
index 8b0d4e2f5..254a96c16 100644
--- a/Objective-J/CommonJS/lib/objective-j.js
+++ b/Objective-J/CommonJS/lib/objective-j.js
@@ -94,7 +94,7 @@ exports.run = function(args)
var arg0 = argv.shift();
var mainFilePath = FILE.canonical(arg0);
- exports.make_narwhal_factory(mainFilePath)(require, { }, module, system, print, window);
+ exports.make_narwhal_factory(mainFilePath)(require, { }, module, system, print);
if (typeof main === "function")
main([arg0].concat(argv));
@@ -149,13 +149,14 @@ exports.repl = function()
};
// creates a narwhal factory function in the objj module scope
-exports.make_narwhal_factory = function(path)
+exports.make_narwhal_factory = function(path, basePath, filenameTranslateDictionary)
{
return function(require, exports, module, system, print)
{
Executable.setCommonJSParameters("require", "exports", "module", "system", "print", "window");
Executable.setCommonJSArguments(require, exports, module, system, print, window);
- Executable.fileImporterForURL(FILE.dirname(path))(path, YES);
+ filenameTranslateDictionary && Executable.setFilenameTranslateDictionary(filenameTranslateDictionary);
+ Executable.fileImporterForURL(basePath ? basePath : FILE.dirname(path))(path, YES);
}
};
diff --git a/Objective-J/CommonJS/lib/objective-j/compiler.js b/Objective-J/CommonJS/lib/objective-j/compiler.js
index f7b15a8a1..2c7d582b9 100644
--- a/Objective-J/CommonJS/lib/objective-j/compiler.js
+++ b/Objective-J/CommonJS/lib/objective-j/compiler.js
@@ -1,13 +1,14 @@
var FILE = require("file"),
OS = require("os"),
- ObjectiveJ = require("objective-j");
+ ObjectiveJ = require("objective-j"),
+ JAKE = require("jake");
require("objective-j/rhino/regexp-rhino-patch");
-ObjectiveJ.Preprocessor.Flags.Preprocess = 1 << 10;
-ObjectiveJ.Preprocessor.Flags.Compress = 1 << 11;
-ObjectiveJ.Preprocessor.Flags.CheckSyntax = 1 << 12;
+ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess = 1 << 10;
+ObjectiveJ.ObjJAcornCompiler.Flags.Compress = 1 << 11;
+ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax = 1 << 12;
var compressors = {
ss : { id : "minify/shrinksafe" }
@@ -36,48 +37,71 @@ function compressor(code)
function compileWithResolvedFlags(aFilePath, objjcFlags, gccFlags, asPlainJavascript)
{
- var shouldObjjPreprocess = objjcFlags & ObjectiveJ.Preprocessor.Flags.Preprocess,
- shouldCheckSyntax = objjcFlags & ObjectiveJ.Preprocessor.Flags.CheckSyntax,
- shouldCompress = objjcFlags & ObjectiveJ.Preprocessor.Flags.Compress,
+ var shouldObjjPreprocess = objjcFlags & ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess,
+ shouldCheckSyntax = objjcFlags & ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax,
+ shouldCompress = objjcFlags & ObjectiveJ.ObjJAcornCompiler.Flags.Compress,
fileContents = "",
executable,
code;
- if (OS.popen("which gcc").stdout.read().length === 0)
- fileContents = FILE.read(aFilePath, { charset:"UTF-8" });
-
- else
- {
- // GCC preprocess the file.
- var gcc = OS.popen("gcc -E -x c -P " + (gccFlags ? gccFlags.join(" ") : "") + " " + OS.enquote(aFilePath), { charset:"UTF-8" }),
- chunk = "";
-
- while (chunk = gcc.stdout.read())
- fileContents += chunk;
- }
-
if (!shouldObjjPreprocess)
+ {
+ if (OS.popen("which gcc").stdout.read().length === 0)
+ fileContents = FILE.read(aFilePath, { charset:"UTF-8" });
+ else
+ {
+ // GCC preprocess the file.
+ var gcc = OS.popen("gcc -E -x c -P " + (gccFlags ? gccFlags.join(" ") : "") + " " + OS.enquote(aFilePath), { charset:"UTF-8" }),
+ chunk = "";
+
+ while (chunk = gcc.stdout.read())
+ fileContents += chunk;
+
+ }
return fileContents;
+ }
// Preprocess contents into fragments.
// FIXME: should calculate relative path, etc.
try
{
- executable = ObjectiveJ.preprocess(fileContents, FILE.basename(aFilePath), objjcFlags);
+ var sources = new JAKE.FileList("**/*.j"),
+ translateFilenameToPath = {},
+ otherwayTranslateFilenameToPath = {};
+
+ // Create a filename to filename path dictionary. (For example: CPArray.j -> CPArray/CPArray.j)
+ sources.forEach(function(/*String*/ aFilename)
+ {
+ translateFilenameToPath[FILE.basename(aFilename)] = aFilename;
+ otherwayTranslateFilenameToPath[aFilename] = FILE.basename(aFilename);
+ }, this);
+
+ var translatedFilename = translateFilenameToPath[aFilePath] ? translateFilenameToPath[aFilePath] : aFilePath,
+ otherwayTranslatedFilename = otherwayTranslateFilenameToPath[aFilePath] ? otherwayTranslateFilenameToPath[aFilePath] : aFilePath,
+ theTranslatedFilename = otherwayTranslatedFilename ? otherwayTranslatedFilename : translatedFilename,
+ absolutePath = FILE.absolute(theTranslatedFilename),
+ basePath = absolutePath.substring(0, absolutePath.length - theTranslatedFilename.length);
+
+ // This ugly fix to make Cappuccino compile with rhino can be removed when the compiler is not loading classes into the runtime
+ var rhinoUglyFix = false;
+ if (system.engine === "rhino")
+ {
+ if (typeof document == "undefined") {
+ document = { createElement: function(x) { return { innerText: ""}}};
+ rhinoUglyFix = true;
+ }
+ }
+
+ ObjectiveJ.setCurrentCompilerFlags(objjcFlags);
+ ObjectiveJ.make_narwhal_factory(absolutePath, basePath, translateFilenameToPath)(require, {}, module, system, print);
+
+ executable = new ObjectiveJ.FileExecutable(FILE.basename(aFilePath));
+
+ if (rhinoUglyFix)
+ delete document;
}
catch (anException)
- {print(anException);
- var lines = fileContents.split("\n"),
- PAD = 3,
- lineNumber = anException.lineNumber || anException.line,
- errorInfo = "Syntax error in " + aFilePath +
- " on preprocessed line number " + lineNumber + "\n\n" +
- "\t" + lines.slice(Math.max(0, lineNumber - 1 - PAD), lineNumber + PAD).join("\n\t");
-
- print(errorInfo);
-
- throw errorInfo;
- }
+ {}
if (shouldCompress)
{
@@ -100,7 +124,7 @@ function resolveFlags(args)
count = args.length,
gccFlags = [],
- objjcFlags = ObjectiveJ.Preprocessor.Flags.Preprocess | ObjectiveJ.Preprocessor.Flags.CheckSyntax;
+ objjcFlags = ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess | ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax;
for (; index < count; ++index)
{
@@ -128,19 +152,19 @@ function resolveFlags(args)
}
else if (argument.indexOf("-E") === 0)
- objjcFlags &= ~ObjectiveJ.Preprocessor.Flags.Preprocess;
+ objjcFlags &= ~ObjectiveJ.ObjJAcornCompiler.Flags.Preprocess;
else if (argument.indexOf("-S") === 0)
- objjcFlags &= ~ObjectiveJ.Preprocessor.Flags.CheckSyntax;
+ objjcFlags &= ~ObjectiveJ.ObjJAcornCompiler.Flags.CheckSyntax;
else if (argument.indexOf("-T") === 0)
- objjcFlags |= ObjectiveJ.Preprocessor.Flags.IncludeTypeSignatures;
+ objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.IncludeTypeSignatures;
else if (argument.indexOf("-g") === 0)
- objjcFlags |= ObjectiveJ.Preprocessor.Flags.IncludeDebugSymbols;
+ objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.IncludeDebugSymbols;
else if (argument.indexOf("-O") === 0)
- objjcFlags |= ObjectiveJ.Preprocessor.Flags.Compress;
+ objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.Compress;
else
filePaths.push(argument);
@@ -191,14 +215,14 @@ exports.main = function(args)
if (argv[0] === "-T" || argv[0] === "--includeTypeSignatures")
{
- objjcFlags |= ObjectiveJ.Preprocessor.Flags.IncludeTypeSignatures;
+ objjcFlags |= ObjectiveJ.ObjJAcornCompiler.Flags.IncludeTypeSignatures;
argv.shift();
continue;
}
if (argv[0] === "--help" || argv[0].substr(0, 1) == '-')
{
- print("Usage: " + args[0] + " [options] [--] file...");
+ print("Usage (objjc 2.0): " + args[0] + " [options] [--] file...");
print(" -p, --print print the output directly to stdout");
print(" --unmarked don't tag the output with @STATIC header");
print("");
diff --git a/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js b/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js
index 156c855ab..34e7c4c5b 100644
--- a/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js
+++ b/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js
@@ -789,7 +789,13 @@ BundleTask.prototype.defineStaticTask = function()
fileStream.write("e;");
fileStream.close();
- });
+
+ // Make sure all classes are removed and all FileExecutables are removed.
+ ObjectiveJ.Executable.resetCachedFileExecutableSearchers();
+ ObjectiveJ.StaticResource.resetRootResources();
+ ObjectiveJ.FileExecutable.resetFileExecutables();
+ objj_resetRegisterClasses();
+ });
this.enhance([staticPath]);
}, this);
@@ -832,7 +838,18 @@ BundleTask.prototype.defineSourceTasks = function()
environmentCompilerFlags = anEnvironment.compilerFlags().join(" ") + " " + compilerFlags,
flattensSources = this.flattensSources(),
basePath = directoryInCommon(environmentSources),
- basePathLength = basePath.length;
+ basePathLength = basePath.length,
+ translateFilenameToPath = {},
+ otherwayTranslateFilenameToPath = {};
+
+ // Create a filename to filename path dictionary. (For example: CPArray.j -> CPArray/CPArray.j)
+ environmentSources.forEach(function(/*String*/ aFilename)
+ {
+ translateFilenameToPath[flattensSources ? FILE.basename(aFilename) : aFilename] = aFilename;
+ otherwayTranslateFilenameToPath[aFilename] = flattensSources ? FILE.basename(aFilename) : aFilename;
+ }, this);
+
+ var e = {};
environmentSources.forEach(function(/*String*/ aFilename)
{
@@ -844,7 +861,17 @@ BundleTask.prototype.defineSourceTasks = function()
filedir (compiledEnvironmentSource, [aFilename], function()
{
- var compile
+ // This ugly fix to make Cappuccino compile with rhino can be removed when the compiler is not loading classes into the runtime
+ var rhinoUglyFix = false;
+ if (system.engine === "rhino")
+ {
+ if (typeof document == "undefined") {
+ document = { createElement: function(x) { return { innerText: ""}}};
+ rhinoUglyFix = true;
+ }
+ }
+
+ var compile;
// if this file doesn't exist or isn't a .j file, don't preprocess it.
if (FILE.extension(aFilename) !== ".j")
{
@@ -853,21 +880,38 @@ BundleTask.prototype.defineSourceTasks = function()
}
else
{
+ var translatedFilename = translateFilenameToPath[aFilename] ? translateFilenameToPath[aFilename] : aFilename,
+ otherwayTranslatedFilename = otherwayTranslateFilenameToPath[aFilename] ? otherwayTranslateFilenameToPath[aFilename] : aFilename,
+ theTranslatedFilename = otherwayTranslatedFilename ? otherwayTranslatedFilename : translatedFilename,
+ absolutePath = FILE.absolute(theTranslatedFilename),
+ basePath = absolutePath.substring(0, absolutePath.length - theTranslatedFilename.length);
+
+ ObjectiveJ.setCurrentCompilerFlags(environmentCompilerFlags);
+ ObjectiveJ.make_narwhal_factory(absolutePath, basePath, translateFilenameToPath)(require, e, module, system, print);
TERM.stream.write("Compiling [\0blue(" + anEnvironment + "\0)] \0purple(" + aFilename + "\0)").flush();
- var compiled = require("objective-j/compiler").compile(aFilename, environmentCompilerFlags);
+
+ var otherwayTranslatedFilename = otherwayTranslateFilenameToPath[aFilename] ? otherwayTranslateFilenameToPath[aFilename] : aFilename,
+ translatedFilename = translateFilenameToPath[aFilename] ? translateFilenameToPath[aFilename] : aFilename,
+ executer = new ObjectiveJ.FileExecutable(otherwayTranslatedFilename);
+
+ var compiled = executer.toMarkedString();
}
+ if (rhinoUglyFix)
+ delete document;
+
TERM.stream.print(Array(Math.round(compiled.length / 1024) + 3).join("."));
FILE.write(compiledEnvironmentSource, compiled, { charset:"UTF-8" });
});
filedir (staticPath, [compiledEnvironmentSource]);
+
replacedFiles.push(flattensSources ? FILE.basename(aFilename) : relativePath);
}, this);
this._replacedFiles[anEnvironment] = replacedFiles;
- }, this);
+ }, this);
}
exports.BundleTask = BundleTask;
diff --git a/Objective-J/Executable.js b/Objective-J/Executable.js
index 55d4d05ec..e604ec9cf 100644
--- a/Objective-J/Executable.js
+++ b/Objective-J/Executable.js
@@ -26,16 +26,19 @@ var ExecutableUnloadedFileDependencies = 0,
ExecutableLoadedFileDependencies = 2,
AnonymousExecutableCount = 0;
-function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*CFURL|String*/ aURL, /*Function*/ aFunction)
+function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*CFURL|String*/ aURL, /*Function*/ aFunction, /*ObjJCompiler*/aCompiler, /*Dictionary*/ aFilenameTranslateDictionary)
{
if (arguments.length === 0)
return this;
this._code = aCode;
- this._function = aFunction || NULL;
+ this._function = aFunction || null;
this._URL = makeAbsoluteURL(aURL || new CFURL("(Anonymous" + (AnonymousExecutableCount++) + ")"));
+ this._compiler = aCompiler || null;
+
this._fileDependencies = fileDependencies;
+ this._filenameTranslateDictionary = aFilenameTranslateDictionary;
if (fileDependencies.length)
{
@@ -48,7 +51,8 @@ function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*CFURL|String
if (this._function)
return;
- this.setCode(aCode);
+ if (!aCompiler)
+ this.setCode(aCode);
}
exports.Executable = Executable;
@@ -114,6 +118,17 @@ Executable.commonJSArguments = function()
return this._commonJSArguments || [];
};
+Executable.setFilenameTranslateDictionary = function(dict)
+{
+ this._filenameTranslateDictionary = dict;
+};
+
+Executable.filenameTranslateDictionary = function()
+{
+ return this._filenameTranslateDictionary || {};
+};
+
+
Executable.prototype.toMarkedString = function()
{
var markedString = "@STATIC;1.0;",
@@ -135,6 +150,26 @@ Executable.prototype.execute = function()
#if EXECUTION_LOGGING
CPLog("EXECUTION: " + this.URL());
#endif
+
+ if (this._compiler)
+ {
+ var fileDependencies = this.fileDependencies(),
+ index = 0,
+ count = fileDependencies.length;
+
+ for (; index < count; ++index)
+ {
+ var fileDependency = fileDependencies[index],
+ isQuoted = fileDependency.isLocal(),
+ URL = fileDependency.URL();
+
+ this.fileExecuter()(URL, isQuoted);
+ }
+
+ this.setCode(this._compiler.compilePass2());
+ this._compiler = null;
+ }
+
var oldContextBundle = CONTEXT_BUNDLE;
// FIXME: Should we have stored this?
@@ -410,10 +445,30 @@ DISPLAY_NAME(Executable.fileImporterForURL);
var cachedFileExecutableSearchers = { },
cachedFileExecutableSearchResults = { };
+function countProp(x) {
+ var count = 0;
+ for (var k in x) {
+ if (x.hasOwnProperty(k)) {
+ ++count;
+ }
+ }
+ return count;
+}
+
+Executable.resetCachedFileExecutableSearchers = function()
+{
+ cachedFileExecutableSearchers = { };
+ cachedFileExecutableSearchResults = { };
+ cachedFileImporters = { };
+ cachedFileExecuters = { };
+ fileDependencyMarkers = { };
+}
+
Executable.fileExecutableSearcherForURL = function(/*CFURL*/ referenceURL)
{
var referenceURLString = referenceURL.absoluteString(),
cachedFileExecutableSearcher = cachedFileExecutableSearchers[referenceURLString],
+ aFilenameTranslateDictionary = Executable.filenameTranslateDictionary ? Executable.filenameTranslateDictionary() : null;
cachedSearchResults = { };
if (!cachedFileExecutableSearcher)
@@ -433,7 +488,7 @@ Executable.fileExecutableSearcherForURL = function(/*CFURL*/ referenceURL)
if (!isAbsoluteURL)
aURL = new CFURL(aURL, referenceURL);
- StaticResource.resolveResourceAtURL(aURL, NO, completed);
+ StaticResource.resolveResourceAtURL(aURL, NO, completed, aFilenameTranslateDictionary);
}
else
StaticResource.resolveResourceAtURLSearchingIncludeURLs(aURL, completed);
@@ -441,11 +496,14 @@ Executable.fileExecutableSearcherForURL = function(/*CFURL*/ referenceURL)
function completed(/*StaticResource*/ aStaticResource)
{
if (!aStaticResource)
- throw new Error("Could not load file at " + aURL);
+ {
+ var compilingFileUrl = ObjJAcornCompiler ? ObjJAcornCompiler.currentCompileFile : null;
+ throw new Error("Could not load file at " + aURL + (compilingFileUrl ? " when compiling " + compilingFileUrl : ""));
+ }
cachedFileExecutableSearchResults[cacheUID] = aStaticResource;
- success(new FileExecutable(aStaticResource.URL()));
+ success(new FileExecutable(aStaticResource.URL(), aFilenameTranslateDictionary));
}
};
diff --git a/Objective-J/FileExecutable.js b/Objective-J/FileExecutable.js
index 2dda22f1a..002586f0f 100644
--- a/Objective-J/FileExecutable.js
+++ b/Objective-J/FileExecutable.js
@@ -22,7 +22,7 @@
var FileExecutablesForURLStrings = { };
-function FileExecutable(/*CFURL|String*/ aURL)
+function FileExecutable(/*CFURL|String*/ aURL, /*Dictionary*/ aFilenameTranslateDictionary)
{
aURL = makeAbsoluteURL(aURL);
@@ -40,14 +40,12 @@ function FileExecutable(/*CFURL|String*/ aURL)
if (fileContents.match(/^@STATIC;/))
executable = decompile(fileContents, aURL);
-
- else if (extension === "j" || !extension)
- executable = exports.preprocess(fileContents, aURL, Preprocessor.Flags.IncludeDebugSymbols);
-
+ else if ((extension === "j" || !extension) && !fileContents.match(/^{/))
+ executable = exports.ObjJAcornCompiler.compileFileDependencies(fileContents, aURL, ObjJAcornCompiler.Flags.IncludeDebugSymbols);
else
executable = new Executable(fileContents, [], aURL);
- Executable.apply(this, [executable.code(), executable.fileDependencies(), aURL, executable._function]);
+ Executable.apply(this, [executable.code(), executable.fileDependencies(), aURL, executable._function, executable._compiler, aFilenameTranslateDictionary]);
this._hasExecuted = NO;
}
@@ -70,6 +68,12 @@ FileExecutable.allFileExecutables = function()
}
#endif
+FileExecutable.resetFileExecutables = function()
+{
+ FileExecutablesForURLStrings = { };
+ FunctionCache = { };
+}
+
FileExecutable.prototype.execute = function(/*BOOL*/ shouldForce)
{
if (this._hasExecuted && !shouldForce)
diff --git a/Objective-J/Includes.js b/Objective-J/Includes.js
index b2cc26fb2..4bfcacad4 100644
--- a/Objective-J/Includes.js
+++ b/Objective-J/Includes.js
@@ -43,6 +43,9 @@
#include "CFBundle.js"
#include "StaticResource.js"
#include "Preprocessor.js"
+#include "acorn.js"
+#include "acornwalk.js"
+#include "ObjJAcornCompiler.js"
#include "FileDependency.js"
#include "Executable.js"
#include "FileExecutable.js"
diff --git a/Objective-J/ObjJAcornCompiler.js b/Objective-J/ObjJAcornCompiler.js
new file mode 100644
index 000000000..f893b5e17
--- /dev/null
+++ b/Objective-J/ObjJAcornCompiler.js
@@ -0,0 +1,807 @@
+/*
+ * ObjJAcornCompiler.js
+ * Objective-J
+ *
+ * Created by Martin Carlberg.
+ * Copyright 2013, Martin Carlberg.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+var Scope = function(prev, base)
+{
+ this.vars = Object.create(null);
+ if (base) for (var key in base) this[key] = base[key];
+ this.prev = prev;
+ if (prev) this.compiler = prev.compiler;
+}
+
+Scope.prototype.compiler = function()
+{
+ return this.compiler;
+}
+
+Scope.prototype.rootScope = function()
+{
+ return this.prev ? this.prev.rootScope() : this;
+}
+
+Scope.prototype.isRootScope = function()
+{
+ return !this.prev;
+}
+
+Scope.prototype.currentClassName = function()
+{
+ return this.classDef ? this.classDef.className : this.prev ? this.prev.currentClassName() : null;
+}
+
+Scope.prototype.getIvarForCurrentClass = function(/* String */ ivarName)
+{
+ if (this.ivars)
+ {
+ var ivar = this.ivars[ivarName];
+ if (ivar)
+ return ivar;
+ }
+
+ var prev = this.prev;
+
+ // Stop at the class declaration
+ if (prev && !this.classDef)
+ return prev.getIvarForCurrentClass(ivarName);
+
+ return null;
+}
+
+Scope.prototype.getLvar = function(/* String */ lvarName, /* BOOL */ stopAtMethod)
+{
+ if (this.vars)
+ {
+ var lvar = this.vars[lvarName];
+ if (lvar)
+ return lvar;
+ }
+
+ var prev = this.prev;
+
+ // Stop at the method declaration
+ if (prev && (!stopAtMethod || !this.methodtype))
+ return prev.getLvar(lvarName, stopAtMethod);
+
+ return null;
+}
+
+Scope.prototype.currentMethodType = function()
+{
+ return this.methodType ? this.methodType : this.prev ? this.prev.currentMethodType() : null;
+}
+
+Scope.prototype.copyAddedSelfToIvarsToParent = function()
+{
+ if (this.prev && this.addedSelfToIvars) for (var key in this.addedSelfToIvars)
+ {
+ var addedSelfToIvar = this.addedSelfToIvars[key],
+ scopeAddedSelfToIvar = (this.prev.addedSelfToIvars || (this.prev.addedSelfToIvars = Object.create(null)))[key] || (this.prev.addedSelfToIvars[key] = []);
+
+ scopeAddedSelfToIvar.push.apply(scopeAddedSelfToIvar, addedSelfToIvar); // Append at end in parent scope
+ }
+}
+
+Scope.prototype.addMaybeWarning = function(warning)
+{
+ var rootScope = this.rootScope();
+
+ (rootScope._maybeWarnings || (rootScope._maybeWarnings = [])).push(warning);
+}
+
+Scope.prototype.maybeWarnings = function()
+{
+ return this.rootScope()._maybeWarnings;
+}
+
+var currentCompilerFlags = "";
+
+var reservedIdentifiers = exports.acorn.makePredicate("self _cmd undefined localStorage arguments");
+
+var ObjJAcornCompiler = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, /*unsigned*/ pass, /* Dictionary */ classDefs)
+{
+ this.source = aString;
+ this.URL = new CFURL(aURL);
+ this.pass = pass;
+ this.jsBuffer = new StringBuffer();
+ this.imBuffer = null;
+ this.cmBuffer = null;
+ this.warnings = [];
+
+ try {
+ this.tokens = exports.acorn.parse(aString);
+ }
+ catch (e) {
+ if (e.lineStart)
+ {
+ var message = this.prettifyMessage(e, "ERROR");
+#ifdef BROWSER
+ console.log(message);
+#else
+ print(message);
+#endif
+ }
+ throw e;
+ }
+
+ this.dependencies = [];
+ this.flags = flags | ObjJAcornCompiler.Flags.IncludeDebugSymbols;
+ this.classDefs = classDefs ? classDefs : Object.create(null);
+ this.lastPos = 0;
+ compile(this.tokens, new Scope(null ,{ compiler: this }), pass === 2 ? pass2 : pass1);
+}
+
+exports.ObjJAcornCompiler = ObjJAcornCompiler;
+
+exports.ObjJAcornCompiler.compileToExecutable = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags)
+{
+ ObjJAcornCompiler.currentCompileFile = aURL;
+ return new ObjJAcornCompiler(aString, aURL, flags, 2).executable();
+}
+
+exports.ObjJAcornCompiler.compileToIMBuffer = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags, classDefs)
+{
+ return new ObjJAcornCompiler(aString, aURL, flags, 2, classDefs).IMBuffer();
+}
+
+exports.ObjJAcornCompiler.compileFileDependencies = function(/*String*/ aString, /*CFURL*/ aURL, /*unsigned*/ flags)
+{
+ ObjJAcornCompiler.currentCompileFile = aURL;
+ return new ObjJAcornCompiler(aString, aURL, flags, 1).executable();
+}
+
+ObjJAcornCompiler.prototype.compilePass2 = function()
+{
+ ObjJAcornCompiler.currentCompileFile = this.URL;
+ this.pass = 2;
+ this.jsBuffer = new StringBuffer();
+ this.warnings = [];
+ compile(this.tokens, new Scope(null ,{ compiler: this }), pass2);
+
+ for (var i = 0; i < this.warnings.length; i++)
+ {
+ var message = this.prettifyMessage(this.warnings[i], "WARNING");
+#ifdef BROWSER
+ console.log(message);
+#else
+ print(message);
+#endif
+ }
+
+ return this.jsBuffer.toString();
+}
+
+var currentCompilerFlags = "";
+
+exports.setCurrentCompilerFlags = function(/*String*/ compilerFlags)
+{
+ currentCompilerFlags = compilerFlags;
+}
+
+exports.currentCompilerFlags = function(/*String*/ compilerFlags)
+{
+ return currentCompilerFlags;
+}
+
+ObjJAcornCompiler.Flags = { };
+
+ObjJAcornCompiler.Flags.IncludeDebugSymbols = 1 << 0;
+ObjJAcornCompiler.Flags.IncludeTypeSignatures = 1 << 1;
+
+ObjJAcornCompiler.prototype.addWarning = function(/* Warning */ aWarning)
+{
+ this.warnings.push(aWarning);
+}
+
+ObjJAcornCompiler.prototype.getIvarForClass = function(/* String */ ivarName, /* Scope */ scope)
+{
+ var ivar = scope.getIvarForCurrentClass(ivarName);
+
+ if (ivar)
+ return ivar;
+
+ var c = this.getClassDef(scope.currentClassName());
+
+ while (c)
+ {
+ var ivars = c.ivars;
+ if (ivars)
+ {
+ var ivarDef = ivars[ivarName];
+ if (ivarDef)
+ return ivarDef;
+ }
+ c = this.getClassDef(c.superClassName);
+ }
+}
+
+ObjJAcornCompiler.prototype.getClassDef = function(/* String */ aClassName)
+{
+ if (!aClassName) return null;
+
+ var c = this.classDefs[aClassName];
+
+ if (c) return c;
+
+ if (objj_getClass)
+ {
+ var aClass = objj_getClass(aClassName);
+ if (aClass)
+ {
+ var ivars = class_copyIvarList(aClass),
+ ivarSize = ivars.length,
+ myIvars = Object.create(null),
+ superClass = aClass.super_class;
+
+ for (var i = 0; i < ivarSize; i++)
+ {
+ var ivar = ivars[i];
+
+ myIvars[ivar.name] = {"type": ivar.type, "name": ivar.name};
+ }
+ c = {"className": aClassName, "ivars": myIvars};
+
+ if (superClass)
+ c.superClassName = superClass.name;
+ this.classDefs[aClassName] = c;
+ return c;
+ }
+ }
+
+ return null;
+// classDef = {"className": className, "superClassName": superClassName, "ivars": Object.create(null), "methods": Object.create(null)};
+}
+
+ObjJAcornCompiler.prototype.executable = function()
+{
+ if (!this._executable)
+ this._executable = new Executable(this.jsBuffer ? this.jsBuffer.toString() : null, this.dependencies, this.URL, null, this);
+ return this._executable;
+}
+
+ObjJAcornCompiler.prototype.IMBuffer = function()
+{
+ return this.imBuffer;
+}
+
+ObjJAcornCompiler.prototype.JSBuffer = function()
+{
+ return this.jsBuffer;
+}
+
+ObjJAcornCompiler.prototype.prettifyMessage = function(/* Message */ aMessage, /* String */ messageType)
+{
+ var line = this.source.substring(aMessage.lineStart, aMessage.lineEnd),
+ message = "\n" + line;
+
+ message += (new Array(aMessage.column + 1)).join(" ");
+ message += (new Array(Math.min(1, line.length) + 1)).join("^") + "\n";
+ message += messageType + " line " + aMessage.line + " in " + this.URL + ": " + aMessage.message;
+
+ return message;
+}
+
+ObjJAcornCompiler.prototype.error_message = function(errorMessage, astNode)
+{
+ return errorMessage + "