diff --git a/AppKit/AppKit.j b/AppKit/AppKit.j index 0a98e86d1..6dd81c956 100644 --- a/AppKit/AppKit.j +++ b/AppKit/AppKit.j @@ -66,6 +66,7 @@ @import "CPOutlineView.j" @import "CPPanel.j" @import "CPPasteboard.j" +@import "CPPopover.j" @import "CPPopUpButton.j" @import "CPPredicateEditor.j" @import "CPPredicateEditorRowTemplate.j" diff --git a/AppKit/CPPopover.j b/AppKit/CPPopover.j new file mode 100644 index 000000000..ad3a83205 --- /dev/null +++ b/AppKit/CPPopover.j @@ -0,0 +1,239 @@ +/* + * CPPopover.j + * AppKit + * + * Created by Antoine Mercadal. + * Copyright 2011 Antoine Mercadal. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + +@import "CPButton.j" +@import "CPColor.j" +@import "CPImage.j" +@import "CPImageView.j" +@import "CPResponder.j" +@import "CPView.j" +@import "_CPAttachedWindow.j" + + +CPPopoverBehaviorApplicationDefined = 0; +CPPopoverBehaviorTransient = 1; +CPPopoverBehaviorSemitransient = 2; + + +/*! @ingroup appkit + @class CPPopover + + This class represent a widget that displays a attached + view relative to another one. + + Delegate can implement: + – popoverShouldClose:(CPPopover)aPopOver + – popoverWillShow:(CPPopover)aPopOver + – popoverDidShow:(CPPopover)aPopOver + – popoverWillClose:(CPPopover)aPopOver + – popoverDidClose:(CPPopover)aPopOver +*/ +@implementation CPPopover : CPResponder +{ + @outlet CPViewController _contentViewController @accessors(property=contentViewController); + @outlet id _delegate @accessors(property=delegate); + + BOOL _animates @accessors(property=animates); + BOOL _shown @accessors(getter=shown); + int _appearance @accessors(property=appearance); + int _behavior @accessors(getter=behavior); + + BOOL _needsCompute; + _CPAttachedWindow _attachedWindow; +} + + +#pragma mark - +#pragma mark INitialization + +/*! + Initialize the CPPopover witn default values + + @returns anInitialized CPPopover +*/ +- (CPPopover)init +{ + if (self = [super init]) + { + _animates = YES; + _appearance = CPPopoverAppearanceMinimal; + _behavior = CPPopoverBehaviorApplicationDefined; + _needsCompute = YES; + _shown = NO; + } + + return self; +} + + +#pragma mark - +#pragma mark Getters / Setters + +/*! + Returns the current rect of the popover + + @return CPRect represeting the frame of the popover +*/ +- (CPRect)positionningRect +{ + if (!_attachedWindow || ![_attachedWindow isVisible]) + return nil; + return [_attachedWindow frame]; +} + +/*! Sets the frame of the popover + @param aRect the desired frame +*/ +- (void)setPositionningRect:(CPRect)aRect +{ + if (!_attachedWindow || ![_attachedWindow isVisible]) + return; + [_attachedWindow setFrame:aRect]; +} + +/*! + Returns the size of the popover's view + + @return CPSize represeting the size of the popover's view +*/ +- (CPRect)contentSize +{ + if (!_attachedWindow || ![_attachedWindow isVisible]) + return nil; + return [[_contentViewController view] frameSize]; +} + +/*! + Sets the size of of the popover's view + + @param aSize the desired size +*/ +- (void)setContentSize:(CPSize)aSize +{ + [[_contentViewController view] setFrameSize:aSize]; +} + +/*! + Indicates if CPPopover is visible + + @returns YES if visible +*/ +- (BOOL)shown +{ + if (!_attachedWindow) + return NO; + return [_attachedWindow isVisible]; +} + +/*! + Set the behaviour of the CPPopover. It can be + - CPPopoverBehaviorTransient: the popover will be close if another control outside the popover become the responder + - CPPopoverBehaviorApplicationDefined: (DEFAULT) the application is responsible for closing the popover + + @param aBehaviour the desired behaviour +*/ +- (void)setBehaviour:(int)aBehaviour +{ + if (_behavior == aBehaviour) + return; + + _behavior = aBehaviour; + _needsCompute = YES; +} + + +#pragma mark - +#pragma mark Positionning + +/*! + Show the popover + + @param positioningRect if set, the popover will be positionned to a random rect relative to the window + @param positioningView if set, the popover will be positioned relative to this view + @param preferredEdge: CPRectEdge representing the prefered positionning. +*/ +- (void)showRelativeToRect:(CPRect)positioningRect ofView:(CPView)positioningView preferredEdge:(CPRectEdge)preferredEdge +{ + if (_delegate && [_delegate respondsToSelector:@selector(popoverWillShow:)]) + [_delegate popoverWillShow:self]; + + if (!_contentViewController) + [CPException raise:CPInternalInconsistencyException reason:@"contentViewController must not be nil"]; + + if (_needsCompute) + { + var styleMask = (_behavior == CPPopoverBehaviorTransient) ? CPClosableOnBlurWindowMask : nil; + _attachedWindow = [[_CPAttachedWindow alloc] initWithContentRect:CPRectMakeZero() styleMask:styleMask]; + } + + [_attachedWindow setAppearance:_appearance]; + [_attachedWindow setAnimates:_animates]; + [_attachedWindow setMovableByWindowBackground:NO]; + [_attachedWindow setFrame:[_attachedWindow frameRectForContentRect:[[_contentViewController view] frame]]]; + [_attachedWindow setContentView:[_contentViewController view]]; + + if (positioningRect) + [_attachedWindow positionRelativeToRect:positioningRect preferredEdge:preferredEdge]; + else if (positioningView) + [_attachedWindow positionRelativeToView:positioningView preferredEdge:preferredEdge]; + else + [CPException raise:CPInvalidArgumentException reason:@"you must set positioningRect or positioningRect"]; + + if (_delegate && [_delegate respondsToSelector:@selector(popoverDidShow:)]) + [_delegate popoverDidShow:self]; +} + +/*! + Closes the popover +*/ +- (void)close +{ + if (_delegate && [_delegate respondsToSelector:@selector(popoverShouldClose:)]); + if (![_delegate popoverShouldClose:self]) + return; + + if (_delegate && [_delegate respondsToSelector:@selector(popoverWillClose:)]) + [_delegate popoverWillClose:self]; + + [_attachedWindow close]; + + if (_delegate && [_delegate respondsToSelector:@selector(popoverDidClose:)]) + [_delegate popoverDidClose:self]; +} + + +#pragma mark - +#pragma mark Action + +/*! + Close the popover + + @param aSender the sender of the action +*/ +- (IBAction)performClose:(id)aSender +{ + [self close]; +} + +@end \ No newline at end of file diff --git a/AppKit/CPWindow/CPWindow.j b/AppKit/CPWindow/CPWindow.j index b87bde7c4..1238017a5 100644 --- a/AppKit/CPWindow/CPWindow.j +++ b/AppKit/CPWindow/CPWindow.j @@ -2928,5 +2928,6 @@ CPCustomWindowShadowStyle = 3; @import "_CPHUDWindowView.j" @import "_CPBorderlessWindowView.j" @import "_CPBorderlessBridgeWindowView.j" +@import "_CPAttachedWindowView.j" @import "CPDragServer.j" @import "CPView.j" diff --git a/AppKit/CPWindow/_CPAttachedWindowView.j b/AppKit/CPWindow/_CPAttachedWindowView.j new file mode 100644 index 000000000..dd7b6e925 --- /dev/null +++ b/AppKit/CPWindow/_CPAttachedWindowView.j @@ -0,0 +1,317 @@ +/* + * _CPAttachedWindowView.j + * AppKit + * + * Created by Antoine Mercadal + * Copyright 2011 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import "_CPWindowView.j" + + +/*! + @ignore + + A custom CPWindowView that manage border and cursor +*/ +@implementation _CPAttachedWindowView : _CPWindowView +{ + BOOL _mouseDownPressed @accessors(getter=isMouseDownPressed, setter=setMouseDownPressed:); + float _arrowOffsetX @accessors(property=arrowOffsetX); + float _arrowOffsetY @accessors(property=arrowOffsetY); + int _appearance @accessors(property=appearance); + unsigned _preferredEdge @accessors(property=preferredEdge); + + BOOL _useGlowingEffect; + CPColor _backgroundColor; + CPColor _strokeColor; + CPImage _cursorBackgroundBottom; + CPImage _cursorBackgroundLeft; + CPImage _cursorBackgroundRight; + CPImage _cursorBackgroundTop; + CPSize _cursorSize; +} + +/*! + Compute the contentView frame from a given window frame + + @param aFrameRect the window frame +*/ +- (CGRect)contentRectForFrameRect:(CGRect)aFrameRect +{ + var contentRect = CGRectMakeCopy(aFrameRect); + + // @todo change border art and remove this pixel perfect adaptation + // return CGRectInset(contentRect, 20, 20); + + contentRect.origin.x += 18; + contentRect.origin.y += 17; + contentRect.size.width -= 35; + contentRect.size.height -= 37; + + return contentRect; +} + +/*! + Compute the window frame from a given contentView frame + + @param aContentRect the contentView frame +*/ ++ (CGRect)frameRectForContentRect:(CGRect)aContentRect +{ + var frameRect = CGRectMakeCopy(aContentRect); + + // @todo change border art and remove this pixel perfect adaptation + //return CGRectOffset(frameRect, 20, 20); + + frameRect.origin.x -= 18; + frameRect.origin.y -= 17; + frameRect.size.width += 35; + frameRect.size.height += 37; + return frameRect; +} + +/*! + Initialize the _CPWindowView +*/ +- (id)initWithFrame:(CPRect)aFrame styleMask:(unsigned)aStyleMask +{ + if (self = [super initWithFrame:aFrame styleMask:aStyleMask]) + { + var bundle = [CPBundle bundleForClass:[self class]]; + _arrowOffsetX = 0.0; + _arrowOffsetY = 0.0; + + // @TODO: make this themable + _useGlowingEffect = YES; + _appearance = CPPopoverAppearanceMinimal; + _cursorSize = CPSizeMake(15, 10); + } + + return self; +} + +/*! + Hide the cursor +*/ +- (void)hideCursor +{ + _cursorSize = CPSizeMakeZero(); + [self setNeedsDisplay:YES]; +} + +/*! + Show the cursor +*/ +- (void)showCursor +{ + _cursorSize = CPSizeMake(15, 10); + [self setNeedsDisplay:YES]; + _mouseDownPressed = NO; +} + +/*! + Draw the view +*/ +- (void)drawRect:(CGRect)aRect +{ + [super drawRect:aRect]; + + if (_appearance == CPPopoverAppearanceMinimal) + { + _strokeColor = [CPColor colorWithCalibratedRed:(172.0 / 255) green:(172.0 / 255) blue:(172.0 / 255) alpha:1.0]; + _backgroundColor = [CPColor colorWithCalibratedRed:(241.0 / 255) green:(241.0 / 255) blue:(241.0 / 255) alpha:0.93]; + } + else + { + _strokeColor = [CPColor colorWithCalibratedRed:(172.0 / 255) green:(172.0 / 255) blue:(172.0 / 255) alpha:1.0]; + _backgroundColor = [CPColor colorWithCalibratedRed:(50.0 / 255) green:(50.0 / 255) blue:(50.0 / 255) alpha:0.93]; + } + + + var context = [[CPGraphicsContext currentContext] graphicsPort], + radius = 5, + arrowWidth = _cursorSize.width, + arrowHeight = _cursorSize.height, + strokeWidth = 2; + + CGContextSetStrokeColor(context, _strokeColor); + CGContextSetLineWidth(context, strokeWidth); + CGContextBeginPath(context); + + aRect.origin.x += strokeWidth; + aRect.origin.y += strokeWidth; + aRect.size.width -= strokeWidth * 2; + aRect.size.height -= strokeWidth * 2; + + if (_useGlowingEffect) + { + var shadowColor = [[CPColor blackColor] colorWithAlphaComponent:.2], + shadowSize = CGSizeMake(0, 0), + shadowBlur = 15; + + //compensate for the shadow blur + aRect.origin.x += shadowBlur; + aRect.origin.y += shadowBlur; + aRect.size.width -= shadowBlur * 2; + aRect.size.height -= shadowBlur * 2; + + //set the shadow + CGContextSetShadow(context, CGSizeMake(0,0), 20); + CGContextSetShadowWithColor(context, shadowSize, shadowBlur, shadowColor); + } + + CGContextSetFillColor(context, _backgroundColor); + + var xMin = _CGRectGetMinX(aRect), + xMax = _CGRectGetMaxX(aRect), + yMin = _CGRectGetMinY(aRect), + yMax = _CGRectGetMaxY(aRect); + + // draw! + switch (_preferredEdge) + { + case CPMinXEdge: + // origin ne + CGContextMoveToPoint(context, xMin + radius, yMin); + + // ne + CGContextAddLineToPoint(context, xMax - radius, yMin); + CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius); + + // arrow CPMinXEdge + CGContextAddLineToPoint(context, xMax, (aRect.size.height / 2) + aRect.origin.y + _arrowOffsetY - (arrowHeight - 2)); + CGContextAddLineToPoint(context, aRect.size.width + arrowHeight + aRect.origin.x + _arrowOffsetX, (aRect.size.height / 2) + aRect.origin.y + _arrowOffsetY); + CGContextAddLineToPoint(context, aRect.size.width + aRect.origin.x + _arrowOffsetX, (aRect.size.height / 2 + (arrowWidth / 2)) + aRect.origin.y + _arrowOffsetY); + + // se + CGContextAddLineToPoint(context, xMax, yMax - radius); + CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax); + + // sw + CGContextAddLineToPoint(context, xMin + radius, yMax); + CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius); + + // nw + CGContextAddLineToPoint(context, xMin, yMin + radius); + CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin); + break; + + case CPMaxXEdge: + // origin ne + CGContextMoveToPoint(context, xMin + radius, yMin); + + // ne + CGContextAddLineToPoint(context, xMax - radius, yMin); + CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius); + + // se + CGContextAddLineToPoint(context, xMax, yMax - radius); + CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax); + + // sw + CGContextAddLineToPoint(context, xMin + radius, yMax); + CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius); + + // arrow CPMaxXEdge + CGContextAddLineToPoint(context, xMin, (aRect.size.height / 2 + (arrowWidth / 2) + aRect.origin.y + _arrowOffsetY)); + CGContextAddLineToPoint(context, aRect.origin.x - arrowHeight + _arrowOffsetX, (aRect.size.height / 2) + aRect.origin.y + _arrowOffsetY); + CGContextAddLineToPoint(context, aRect.origin.x + _arrowOffsetX, (aRect.size.height / 2 - (arrowWidth / 2) + aRect.origin.y + _arrowOffsetY)); + + // nw + CGContextAddLineToPoint(context, xMin, yMin + radius); + CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin); + + break; + + case CPMaxYEdge: + // origin nw + CGContextMoveToPoint(context, xMin, yMin + yMin); + + // nw + CGContextAddLineToPoint(context, xMin, yMin + radius); + CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin); + + // arrow CPMaxYEdge + CGContextAddLineToPoint(context, (aRect.size.width / 2) + aRect.origin.x + _arrowOffsetX - (arrowWidth / 2), yMin); + CGContextAddLineToPoint(context, (aRect.size.width / 2) + aRect.origin.x + _arrowOffsetX, aRect.origin.y - arrowHeight + _arrowOffsetY); + CGContextAddLineToPoint(context, (aRect.size.width / 2) + (arrowWidth / 2) + aRect.origin.x + _arrowOffsetX, aRect.origin.y + _arrowOffsetY); + + // ne + CGContextAddLineToPoint(context, xMax - radius, yMin); + CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius); + + // se + CGContextAddLineToPoint(context, xMax, yMax - radius); + CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax); + + // sw + CGContextAddLineToPoint(context, xMin + radius, yMax); + CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius); + break; + + case CPMinYEdge: + // origin nw + CGContextMoveToPoint(context, xMin, yMin + yMin); + + // nw + CGContextAddLineToPoint(context, xMin, yMin + radius); + CGContextAddCurveToPoint(context, xMin, yMin + radius, xMin, yMin, xMin + radius, yMin); + + // ne + CGContextAddLineToPoint(context, xMax - radius, yMin); + CGContextAddCurveToPoint(context, xMax - radius, yMin, xMax, yMin, xMax, yMin + radius); + + // se + CGContextAddLineToPoint(context, xMax, yMax - radius); + CGContextAddCurveToPoint(context, xMax, yMax - radius, xMax, yMax, xMax - radius, yMax); + + // arrow CPMinYEdge + CGContextAddLineToPoint(context, (aRect.size.width / 2) + (arrowWidth / 2) + aRect.origin.x + _arrowOffsetX , yMax); + CGContextAddLineToPoint(context, (aRect.size.width / 2) + aRect.origin.x + _arrowOffsetX, aRect.size.height + aRect.origin.y + arrowHeight + _arrowOffsetY); + CGContextAddLineToPoint(context, (aRect.size.width / 2) - (arrowWidth / 2) + aRect.origin.x + _arrowOffsetX, aRect.size.height + aRect.origin.y + _arrowOffsetY); + + // sw + CGContextAddLineToPoint(context, xMin + radius, yMax); + CGContextAddCurveToPoint(context, xMin + radius, yMax, xMin, yMax, xMin, yMax - radius); + break; + + default: + // no computed edge means standard rounded rect + CGContextAddPath(context, CGPathWithRoundedRectangleInRect(aRect, radius, radius, YES, YES, YES, YES)); + } + + CGContextClosePath(context); + + //Draw it + CGContextStrokePath(context); + CGContextFillPath(context); +} + +- (void)mouseDown:(CPEvent)anEvent +{ + _mouseDownPressed = YES; + [super mouseDown:anEvent]; +} + +- (void)mouseUp:(CPEvent)anEvent +{ + _mouseDownPressed = NO; + [super mouseUp:anEvent]; +} + +@end diff --git a/AppKit/_CPAttachedWindow.j b/AppKit/_CPAttachedWindow.j new file mode 100644 index 000000000..28f94ddf3 --- /dev/null +++ b/AppKit/_CPAttachedWindow.j @@ -0,0 +1,501 @@ +/* + * _CPToolTipWindowView.j + * AppKit + * + * Created by Antoine Mercadal + * Copyright 2011 + * + * 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 + +@import "CPButton.j" +@import "CPWindow.j" + + +CPClosableOnBlurWindowMask = 1 << 4; +CPPopoverAppearanceMinimal = 0; +CPPopoverAppearanceHUD = 1; + + +/*! + @ignore + + This is a simple attached window like the one that pops up + when you double click on a meeting in iCal +*/ +@implementation _CPAttachedWindow : CPWindow +{ + BOOL _animates @accessors(property=animates); + id _targetView @accessors(property=targetView); + int _appearance @accessors(getter=appearance); + + BOOL _closeOnBlur; + BOOL _isClosed; + BOOL _shouldPerformAnimation; + CPButton _closeButton; + float _animationDuration; +} + +/*! + override default windowView class loader + + @param aStyleMask the window mask + @return the windowView class +*/ + +#pragma mark - +#pragma mark Class methods + ++ (Class)_windowViewClassForStyleMask:(unsigned)aStyleMask +{ + return _CPAttachedWindowView; +} + + +#pragma mark - +#pragma mark Initialization + +/*! + Create and init a _CPAttachedWindow with given size of and view + + @param aSize the size of the attached window + @param aView the target view + @return ready to use _CPAttachedWindow +*/ ++ (id)attachedWindowWithSize:(CGSize)aSize forView:(CPView)aView +{ + return [_CPAttachedWindow attachedWindowWithSize:aSize forView:aView styleMask:nil]; +} + +/*! + Create and init a _CPAttachedWindow with given size of and view + + @param aSize the size of the attached window + @param aView the target view + @return ready to use _CPAttachedWindow + @param styleMask the window style mask (combine CPClosableWindowMask and CPClosableOnBlurWindowMask) +*/ ++ (id)attachedWindowWithSize:(CGSize)aSize forView:(CPView)aView styleMask:(int)aMask +{ + var attachedWindow = [[_CPAttachedWindow alloc] initWithContentRect:CPRectMake(0.0, 0.0, aSize.width, aSize.height) styleMask:aMask]; + + [attachedWindow attachToView:aView]; + + return attachedWindow; +} + +/*! + Create and init a _CPAttachedWindow with given frame + + @param aFrame the frame of the attached window + @return ready to use _CPAttachedWindow +*/ +- (id)initWithContentRect:(CGRect)aFrame +{ + self = [self initWithContentRect:aFrame styleMask:nil] + return self; +} + +/*! + Create and init a _CPAttachedWindow with given frame + + @param aFrame the frame of the attached window + @param styleMask the window style mask (combine CPClosableWindowMask and CPClosableOnBlurWindowMask) + @return ready to use _CPAttachedWindow +*/ +- (id)initWithContentRect:(CGRect)aFrame styleMask:(unsigned)aStyleMask +{ + if (self = [super initWithContentRect:aFrame styleMask:aStyleMask]) + { + _animates = YES; + _animates = YES; + _animationDuration = 150; + _closeOnBlur = (aStyleMask & CPClosableOnBlurWindowMask); + _isClosed = NO; + _shouldPerformAnimation = _animates; + + [self setLevel:CPStatusWindowLevel]; + [self setMovableByWindowBackground:YES]; + [self setHasShadow:NO]; + + _DOMElement.style.WebkitBackfaceVisibility = "hidden"; + _DOMElement.style.WebkitTransitionProperty = "-webkit-transform, opacity"; + _DOMElement.style.WebkitTransitionDuration = _animationDuration + "ms"; + + [_windowView setNeedsDisplay:YES]; + } + + return self; +} + +#pragma mark - +#pragma mark Getters / Setters + +- (void)setAppearance:(int)anAppearance +{ + if (_appearance == anAppearance) + return; + + [_windowView setAppearance:anAppearance]; +} + +#pragma mark - +#pragma mark Observer + +/*! + Update the _CPAttachedWindow frame if a resize event is observed + +*/ +- (void)observeValueForKeyPath:(CPString)aPath ofObject:(id)anObject change:(CPDictionary)theChange context:(void)aContext +{ + if ([aPath isEqual:@"frame"]) + { + // @TODO: not recompute everything, just compute the move offset + var g = [_windowView preferredEdge]; + + [self positionRelativeToView:_targetView preferredEdge:g]; + } +} + + +#pragma mark - +#pragma mark Notification handlers + +- (void)_attachedWindowDidMove:(CPNotification)aNotification +{ + if ([_windowView isMouseDownPressed]) + { + [_targetView removeObserver:self forKeyPath:@"frame"]; + [_windowView hideCursor]; + [self setLevel:CPNormalWindowLevel]; + [_closeButton setFrameOrigin:CPPointMake(1.0, 1.0)]; + [[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidMoveNotification object:self]; + } +} + + +#pragma mark - +#pragma mark Utilities + +- (CPPoint)computeOrigin:(CPView)aView preferredEdge:(int)anEdge +{ + var frameView = [aView frame], + currentView = aView, + origin = [aView frameOrigin], + lastView; + + // if somebody succeed to use the conversion function of CPView + // to get this working, please do. + while (currentView = [currentView superview]) + { + origin.x += [currentView frameOrigin].x; + origin.y += [currentView frameOrigin].y; + lastView = currentView; + } + + origin.x += [[lastView window] frame].origin.x; + origin.y += [[lastView window] frame].origin.y; + + // take care of the scrolling point + if ([aView enclosingScrollView]) + { + var offsetPoint = [[[aView enclosingScrollView] contentView] boundsOrigin]; + origin.x -= offsetPoint.x; + origin.y -= offsetPoint.y; + } + + return [self computeOriginFromRect:CPRectMake(origin.x, origin.y, CPRectGetWidth(frameView), CPRectGetHeight(frameView)) preferredEdge:anEdge]; +} + +- (CPPoint)computeOriginFromRect:(CPRect)aRect preferredEdge:(int)anEdge +{ + var nativeRect = [[[CPApp mainWindow] platformWindow] nativeContentRect], + originLeft = CPPointCreateCopy(aRect.origin), + originRight = CPPointCreateCopy(aRect.origin), + originTop = CPPointCreateCopy(aRect.origin), + originBottom = CPPointCreateCopy(aRect.origin); + + // CPMaxXEdge + originRight.x += aRect.size.width; + originRight.y += (aRect.size.height / 2.0) - (CPRectGetHeight([self frame]) / 2.0) + + // CPMinXEdge + originLeft.x -= CPRectGetWidth([self frame]); + originLeft.y += (aRect.size.height / 2.0) - (CPRectGetHeight([self frame]) / 2.0) + + // CPMaxYEdge + originBottom.x += aRect.size.width / 2.0 - CPRectGetWidth([self frame]) / 2.0; + originBottom.y += aRect.size.height; + + // CPMinYEdge + originTop.x += aRect.size.width / 2.0 - CPRectGetWidth([self frame]) / 2.0; + originTop.y -= CPRectGetHeight([self frame]); + + var requestedEdge = (anEdge !== nil) ? anEdge : CPMaxXEdge, + requestedOrigin; + + switch (requestedEdge) + { + case CPMaxXEdge: + requestedOrigin = originRight; + break; + case CPMinXEdge: + requestedOrigin = originLeft; + break; + case CPMinYEdge: + requestedOrigin = originTop; + break; + case CPMaxYEdge: + requestedOrigin = originBottom; + break; + } + + var origins = [requestedOrigin, originRight, originLeft, originTop, originBottom], + edges = [requestedEdge, CPMaxXEdge, CPMinXEdge, CPMinYEdge, CPMaxYEdge]; + + for (var i = 0; i < origins.length; i++) + { + var o = origins[i], + g = edges[i]; + + [_windowView setArrowOffsetX:0]; + [_windowView setArrowOffsetY:0]; + [_windowView setPreferredEdge:g]; + + if (o.x < 0) + { + [_windowView setArrowOffsetX:o.x]; + o.x = 0; + } + if (o.x + CPRectGetWidth([self frame]) > nativeRect.size.width) + { + [_windowView setArrowOffsetX:(o.x + CPRectGetWidth([self frame]) - nativeRect.size.width)]; + o.x = nativeRect.size.width - CPRectGetWidth([self frame]); + } + if (o.y < 0) + { + [_windowView setArrowOffsetY:o.y]; + o.y = 0; + } + if (o.y + CPRectGetHeight([self frame]) > nativeRect.size.height) + { + [_windowView setArrowOffsetY:(CPRectGetHeight([self frame]) + o.y - nativeRect.size.height)]; + o.y = nativeRect.size.height - CPRectGetHeight([self frame]); + } + + switch (g) + { + case CPMaxXEdge: + if (o.x >= (aRect.origin.x + aRect.size.width)) + return o; + break; + case CPMinXEdge: + if ((o.x + _frame.size.width) <= aRect.origin.x) + return o; + break; + case CPMaxYEdge: + if (o.y >= (aRect.origin.y + aRect.size.height)) + return o; + break; + case CPMinYEdge: + if ((o.y + _frame.size.height) <= aRect.origin.y) + return o; + break; + } + } + + [_windowView setPreferredEdge:nil]; + return requestedOrigin; +} + +/*! + Compute the frame needed to be placed to the given view + and position the attached window according to this view (edge will be automatic) + + @param aView the view where _CPAttachedWindow must be attached +*/ +- (void)positionRelativeToView:(CPView)aView +{ + [self positionRelativeToView:aView preferredEdge:nil]; +} + +/*! + Compute the frame needed to be placed to the given view + and position the attached window according to this view + + @param aView the view where _CPAttachedWindow must be attached + @param anEdge the preferd edge to use +*/ +- (void)positionRelativeToView:(CPView)aView preferredEdge:(int)anEdge +{ + var point = [self computeOrigin:aView preferredEdge:anEdge]; + + [self setFrameOrigin:point]; + [_windowView showCursor]; + [self setLevel:CPStatusWindowLevel]; + [_closeButton setFrameOrigin:CPPointMake(1.0, 1.0)]; + [_windowView setNeedsDisplay:YES]; + [self makeKeyAndOrderFront:nil]; + + _targetView = aView; + [_targetView addObserver:self forKeyPath:@"frame" options:nil context:nil]; +} + +/*! + Position the _CPAttachedWindow to a random point + + @param aPoint the point where the _CPAttachedWindow will be attached +*/ +- (void)positionRelativeToRect:(CPRect)aRect +{ + [self positionRelativeToRect:aRect preferredEdge:nil] +} + +/*! + Position the _CPAttachedWindow to a random point + + @param aPoint the point where the _CPAttachedWindow will be attached + @param anEdge the prefered edge +*/ +- (void)positionRelativeToRect:(CPRect)aRect preferredEdge:(int)anEdge +{ + var point = [self computeOriginFromRect:aRect preferredEdge:anEdge]; + + [self setFrameOrigin:point]; + [_windowView showCursor]; + [self setLevel:CPStatusWindowLevel]; + [_closeButton setFrameOrigin:CPPointMake(1.0, 1.0)]; + [_windowView setNeedsDisplay:YES]; + [self makeKeyAndOrderFront:nil]; +} + + +#pragma mark - +#pragma mark Actions + +/*! + Closes the _CPAttachedWindow + + @param sender the sender of the action +*/ +- (IBAction)close:(id)aSender +{ + [self close]; +} + + +#pragma mark - +#pragma mark Overrides + +/*! + Called when the window is loowing focus and close the window if CPClosableOnBlurWindowMask is setted +*/ +- (void)resignMainWindow +{ + if (_closeOnBlur && !_isClosed) + { + // set a close flag to avoid infinite loop + _isClosed = YES; + [self close]; + + if (_delegate && [_delegate respondsToSelector:@selector(didAttachedWindowClose:)]) + [_delegate didAttachedWindowClose:self]; + } +} + +/*! + Order front the window as usual and add listener for CPWindowDidMoveNotification + + @param sender the sender of the action +*/ +- (IBAction)orderFront:(is)aSender +{ + [super orderFront:aSender]; + + var tranformOrigin = "50% 100%"; + + switch ([_windowView preferredEdge]) + { + case CPMaxYEdge: + var posX = 50 + (([_windowView arrowOffsetX] * 100) / _frame.size.width); + tranformOrigin = posX + "% 0%"; // 50 0 + break; + case CPMinYEdge: + var posX = 50 + (([_windowView arrowOffsetX] * 100) / _frame.size.width); + tranformOrigin = posX + "% 100%"; // 50 100 + break; + case CPMinXEdge: + var posY = 50 + (([_windowView arrowOffsetY] * 100) / _frame.size.height); + tranformOrigin = "100% " + posY + "%"; // 100 50 + break; + case CPMaxXEdge: + var posY = 50 + (([_windowView arrowOffsetY] * 100) / _frame.size.height); + tranformOrigin = "0% "+ posY + "%"; // 0 50 + break; + } + + // @TODO: implement for FF + if (_animates && _shouldPerformAnimation && typeof(_DOMElement.style.WebkitTransform) != "undefined") + { + _DOMElement.style.opacity = 0; + _DOMElement.style.WebkitTransform = "scale(0)"; + _DOMElement.style.WebkitTransformOrigin = tranformOrigin; + window.setTimeout(function(){ + _DOMElement.style.height = _frame.size.height + @"px"; + _DOMElement.style.width = _frame.size.width + @"px"; + _DOMElement.style.opacity = 1; + _DOMElement.style.WebkitTransform = "scale(1.1)"; + var transitionEndFunction = function(){ + _DOMElement.style.WebkitTransform = "scale(1)"; + _DOMElement.removeEventListener("webkitTransitionEnd", transitionEndFunction, YES); + }; + _DOMElement.addEventListener("webkitTransitionEnd", transitionEndFunction, YES) + },0); + } + + [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_attachedWindowDidMove:) name:CPWindowDidMoveNotification object:self]; + + _shouldPerformAnimation = NO; + _isClosed = NO; +} + +/*! + Close the windo with animation +*/ +- (void)close +{ + if (_animates) + { + _DOMElement.style.opacity = 0; + var transitionEndFunction = function(){ + [super close]; + _DOMElement.removeEventListener("webkitTransitionEnd", transitionEndFunction, YES); + }; + _DOMElement.addEventListener("webkitTransitionEnd", transitionEndFunction, YES); + } + else + [super close]; + + [_targetView removeObserver:self forKeyPath:@"frame"]; + + _shouldPerformAnimation = _animates; + + if (_delegate && [_delegate respondsToSelector:@selector(didAttachedWindowClose:)]) + [_delegate didAttachedWindowClose:self]; +} + +@end diff --git a/Tests/Manual/CPPopover/AppController.j b/Tests/Manual/CPPopover/AppController.j new file mode 100644 index 000000000..3891d89f2 --- /dev/null +++ b/Tests/Manual/CPPopover/AppController.j @@ -0,0 +1,168 @@ +/* + * AppController.j + * test + * + * Created by You on July 6, 2011. + * Copyright 2011, Your Company All rights reserved. + */ + +@import + +@import + +@implementation AppController : CPObject +{ + CPPopUpButton buttonGravity; + CPPopUpButton buttonStyle; + CPPopUpButton buttonAnimation; + CPPopUpButton buttonBehaviour; +} + +- (void)applicationDidFinishLaunching:(CPNotification)aNotification +{ + var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask], + contentView = [theWindow contentView]; + + button = [CPButton buttonWithTitle:@"click"]; + [button setTarget:self]; + [button setAction:@selector(open:)]; + [button setFrameOrigin:CPPointMake(10, 60)]; + [contentView addSubview:button]; + + button = [CPButton buttonWithTitle:@"click"]; + [button setTarget:self]; + [button setAction:@selector(open:)]; + [button setFrameOrigin:CPPointMake( [contentView frameSize].width - 50, 60)]; + [contentView addSubview:button]; + + + button = [CPButton buttonWithTitle:@"click"]; + [button setTarget:self]; + [button setAction:@selector(open:)]; + [button setFrameOrigin:CPPointMake( [contentView frameSize].width - 50, [contentView frameSize].height - 50)]; + [contentView addSubview:button]; + + button = [CPButton buttonWithTitle:@"click"]; + [button setTarget:self]; + [button setAction:@selector(open:)]; + [button setFrameOrigin:CPPointMake( 10, [contentView frameSize].height - 50)]; + [contentView addSubview:button]; + + button = [CPButton buttonWithTitle:@"click"]; + [button setTarget:self]; + [button setAction:@selector(open:)]; + [button setAutoresizingMask:CPViewMinXMargin | CPViewMaxXMargin | CPViewMinYMargin | CPViewMaxYMargin] + [button setCenter:[contentView center]]; + [contentView addSubview:button]; + + buttonGravity = [[CPPopUpButton alloc] initWithFrame:CPRectMake(10, 10, 130, 24)]; + [buttonGravity addItemWithTitle:"Automatic"]; + [buttonGravity addItemWithTitle:"Bottom"]; + [buttonGravity addItemWithTitle:"Top"]; + [buttonGravity addItemWithTitle:"Right"]; + [buttonGravity addItemWithTitle:"Left"]; + [contentView addSubview:buttonGravity]; + + buttonStyle = [[CPPopUpButton alloc] initWithFrame:CPRectMake(150, 10, 130, 24)]; + [buttonStyle addItemWithTitle:"Minimal"]; + [buttonStyle addItemWithTitle:"HUD"]; + [contentView addSubview:buttonStyle]; + + buttonAnimation = [[CPPopUpButton alloc] initWithFrame:CPRectMake(290, 10, 130, 24)]; + [buttonAnimation addItemWithTitle:"With animation"]; + [buttonAnimation addItemWithTitle:"No animation"]; + [contentView addSubview:buttonAnimation]; + + buttonBehaviour = [[CPPopUpButton alloc] initWithFrame:CPRectMake(430, 10, 130, 24)]; + [buttonBehaviour addItemWithTitle:"Transient"]; + [buttonBehaviour addItemWithTitle:"Not managed"]; + [contentView addSubview:buttonBehaviour]; + + + [theWindow orderFront:self]; +} + +- (IBAction)open:(id)sender +{ + var g; + switch([buttonGravity title]) + { + case "Automatic": + g = nil; + break; + case "Bottom": + g = CPMaxYEdge; + break; + case "Top": + g = CPMinYEdge; + break; + case "Left": + g = CPMinXEdge; + break; + case "Right": + g = CPMaxXEdge; + break; + } + + var a; + switch([buttonStyle title]) + { + case "Minimal": + a = CPPopoverAppearanceMinimal; + break; + case "HUD": + a = CPPopoverAppearanceHUD; + break; + } + + var p = [[CPPopover alloc] init], + viewC = [[CPViewController alloc] init]; + view = [[CPView alloc] initWithFrame:CPRectMake(0.0, 0.0, 320, 300)], + label = [CPTextField labelWithTitle:[buttonGravity title]]; + + [label setFont:[CPFont boldSystemFontOfSize:30.0]]; + [label setFrameOrigin:CPPointMake(0, 70)]; + [label setValue:[CPColor colorWithHexString:@"fff"] forThemeAttribute:@"text-shadow-color"]; + [label setValue:CGSizeMake(0.0, 1.0) forThemeAttribute:@"text-shadow-offset"]; + [label setTextColor:[CPColor colorWithHexString:@"444"]]; + [label setFrameSize:CPSizeMake([view frame].size.width, 50)]; + [label setAlignment:CPCenterTextAlignment]; + [view addSubview:label]; + + [viewC setView:view]; + [p setContentViewController:viewC]; + [p setAnimates:([buttonAnimation title] == @"With animation")]; + [p setBehaviour:([buttonBehaviour title] == @"Transient") ? CPPopoverBehaviorTransient : CPPopoverBehaviorApplicationDefined]; + [p setAppearance:a]; + [p setDelegate:self]; + [p showRelativeToRect:nil ofView:sender preferredEdge:g]; + CPLog.info("content size - w:" + [p contentSize].width + " h:" + [p contentSize].width); + CPLog.info("positionning rect - x: " + [p positionningRect].origin.x + " y: " + [p positionningRect].origin.x + + " w:" + [p positionningRect].size.width + " h:" + [p positionningRect].size.width); +} + + +#pragma mark - +#pragma mark CPPopover Delegate + +- (void)popoverWillShow:(CPPopover)aPopover +{ + CPLog.info("popover " + aPopover + " will show"); +} + +- (void)popoverDidShow:(CPPopover)aPopover +{ + CPLog.info("popover " + aPopover + " did show"); +} + +- (void)popoverWillClose:(CPPopover)aPopover +{ + CPLog.info("popover " + aPopover + " will close"); +} + +- (void)popoverDidClose:(CPPopover)aPopover +{ + CPLog.info("popover " + aPopover + " did close"); +} + +@end diff --git a/Tests/Manual/CPPopover/Info.plist b/Tests/Manual/CPPopover/Info.plist new file mode 100644 index 000000000..3eb7a23af --- /dev/null +++ b/Tests/Manual/CPPopover/Info.plist @@ -0,0 +1,12 @@ + + + + + CPApplicationDelegateClass + AppController + CPBundleName + test + CPPrincipalClass + CPApplication + + diff --git a/Tests/Manual/CPPopover/Jakefile b/Tests/Manual/CPPopover/Jakefile new file mode 100644 index 000000000..07be3fd32 --- /dev/null +++ b/Tests/Manual/CPPopover/Jakefile @@ -0,0 +1,93 @@ +/* + * Jakefile + * test + * + * Created by You on July 6, 2011. + * Copyright 2011, Your Company All rights reserved. + */ + +var ENV = require("system").env, + FILE = require("file"), + JAKE = require("jake"), + task = JAKE.task, + FileList = JAKE.FileList, + app = require("cappuccino/jake").app, + configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug", + OS = require("os"); + +app ("test", function(task) +{ + task.setBuildIntermediatesPath(FILE.join("Build", "test.build", configuration)); + task.setBuildPath(FILE.join("Build", configuration)); + + task.setProductName("test"); + task.setIdentifier("com.yourcompany.test"); + task.setVersion("1.0"); + task.setAuthor("Your Company"); + task.setEmail("feedback @nospam@ yourcompany.com"); + task.setSummary("test"); + task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**"))); + task.setResources(new FileList("Resources/**")); + task.setIndexFilePath("index.html"); + task.setInfoPlistPath("Info.plist"); + + if (configuration === "Debug") + task.setCompilerFlags("-DDEBUG -g"); + else + task.setCompilerFlags("-O"); +}); + +task ("default", ["test"], function() +{ + printResults(configuration); +}); + +task ("build", ["default"]); + +task ("debug", function() +{ + ENV["CONFIGURATION"] = "Debug"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("release", function() +{ + ENV["CONFIGURATION"] = "Release"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("run", ["debug"], function() +{ + OS.system(["open", FILE.join("Build", "Debug", "test", "index.html")]); +}); + +task ("run-release", ["release"], function() +{ + OS.system(["open", FILE.join("Build", "Release", "test", "index.html")]); +}); + +task ("deploy", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Deployment", "test")); + OS.system(["press", "-f", FILE.join("Build", "Release", "test"), FILE.join("Build", "Deployment", "test")]); + printResults("Deployment") +}); + +task ("desktop", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Desktop", "test")); + require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "test"), FILE.join("Build", "Desktop", "test", "test.app")); + printResults("Desktop") +}); + +task ("run-desktop", ["desktop"], function() +{ + OS.system([FILE.join("Build", "Desktop", "test", "test.app", "Contents", "MacOS", "NativeHost"), "-i"]); +}); + +function printResults(configuration) +{ + print("----------------------------"); + print(configuration+" app built at path: "+FILE.join("Build", configuration, "test")); + print("----------------------------"); +} diff --git a/Tests/Manual/CPPopover/Resources/spinner.gif b/Tests/Manual/CPPopover/Resources/spinner.gif new file mode 100644 index 000000000..06dbc2bc2 Binary files /dev/null and b/Tests/Manual/CPPopover/Resources/spinner.gif differ diff --git a/Tests/Manual/CPPopover/index-debug.html b/Tests/Manual/CPPopover/index-debug.html new file mode 100644 index 000000000..631a52ca1 --- /dev/null +++ b/Tests/Manual/CPPopover/index-debug.html @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + CPPopover Test + + + + + + + + + + + + + + +
+
+ + + +
+
+ + + diff --git a/Tests/Manual/CPPopover/index.html b/Tests/Manual/CPPopover/index.html new file mode 100644 index 000000000..20629ad2d --- /dev/null +++ b/Tests/Manual/CPPopover/index.html @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + CPPopover Test + + + + + + + + + + + + +
+
+ + + +
+
+ + + + diff --git a/Tests/Manual/CPPopover/main.j b/Tests/Manual/CPPopover/main.j new file mode 100644 index 000000000..ff9216569 --- /dev/null +++ b/Tests/Manual/CPPopover/main.j @@ -0,0 +1,18 @@ +/* + * AppController.j + * test + * + * Created by You on July 6, 2011. + * Copyright 2011, Your Company All rights reserved. + */ + +@import +@import + +@import "AppController.j" + + +function main(args, namedArgs) +{ + CPApplicationMain(args, namedArgs); +}