mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
The corresponding adaptation in CPControl was missing in the previous PR
1237 lines
31 KiB
Plaintext
1237 lines
31 KiB
Plaintext
/*
|
|
* CPControl.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 <Foundation/CPFormatter.j>
|
|
@import <Foundation/CPTimer.j>
|
|
|
|
@import "CPShadow.j"
|
|
@import "CPText.j"
|
|
@import "CPKeyValueBinding.j"
|
|
@import "CPTrackingArea.j"
|
|
|
|
@class CPFont
|
|
|
|
@global CPApp
|
|
|
|
@protocol CPControlTextEditingDelegate <CPObject>
|
|
|
|
@optional
|
|
- (void)controlTextDidBeginEditing:(CPNotification)aNotification;
|
|
- (void)controlTextDidChange:(CPNotification)aNotification;
|
|
- (void)controlTextDidEndEditing:(CPNotification)aNotification;
|
|
- (void)controlTextDidFocus:(CPNotification)aNotification;
|
|
- (void)controlTextDidBlur:(CPNotification)aNotification;
|
|
- (BOOL)control:(CPControl)control didFailToFormatString:(CPString)string errorDescription:(CPString)error;
|
|
|
|
@end
|
|
|
|
@typedef CPControlSize
|
|
CPRegularControlSize = 0;
|
|
CPSmallControlSize = 1;
|
|
CPMiniControlSize = 2;
|
|
|
|
// To get the theme state corresponding to a control size, use CPControlSizeThemeStates[controlSize]
|
|
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini];
|
|
|
|
@typedef CPLineBreakMode
|
|
CPLineBreakByWordWrapping = 0;
|
|
CPLineBreakByCharWrapping = 1;
|
|
CPLineBreakByClipping = 2;
|
|
CPLineBreakByTruncatingHead = 3;
|
|
CPLineBreakByTruncatingTail = 4;
|
|
CPLineBreakByTruncatingMiddle = 5;
|
|
|
|
@typedef CPVerticalTextAlignment
|
|
CPTopVerticalTextAlignment = 1;
|
|
CPCenterVerticalTextAlignment = 2;
|
|
CPBottomVerticalTextAlignment = 3;
|
|
|
|
// Deprecated for use with images, use the CPImageScale constants
|
|
@typedef CPImageScaling
|
|
CPScaleProportionally = 0;
|
|
CPScaleToFit = 1;
|
|
CPScaleNone = 2;
|
|
|
|
CPImageScaleProportionallyDown = 0;
|
|
CPImageScaleAxesIndependently = 1;
|
|
CPImageScaleNone = 2;
|
|
CPImageScaleProportionallyUpOrDown = 3;
|
|
|
|
@typedef CPCellImagePosition
|
|
CPNoImage = 0;
|
|
CPImageOnly = 1;
|
|
CPImageLeft = 2;
|
|
CPImageRight = 3;
|
|
CPImageBelow = 4;
|
|
CPImageAbove = 5;
|
|
CPImageOverlaps = 6;
|
|
|
|
CPOnState = 1;
|
|
CPOffState = 0;
|
|
CPMixedState = -1;
|
|
|
|
CPControlNormalBackgroundColor = "CPControlNormalBackgroundColor";
|
|
CPControlSelectedBackgroundColor = "CPControlSelectedBackgroundColor";
|
|
CPControlHighlightedBackgroundColor = "CPControlHighlightedBackgroundColor";
|
|
CPControlDisabledBackgroundColor = "CPControlDisabledBackgroundColor";
|
|
|
|
CPControlTextDidBeginEditingNotification = "CPControlTextDidBeginEditingNotification";
|
|
CPControlTextDidChangeNotification = "CPControlTextDidChangeNotification";
|
|
CPControlTextDidEndEditingNotification = "CPControlTextDidEndEditingNotification";
|
|
|
|
var CPControlBlackColor = [CPColor blackColor];
|
|
|
|
/*!
|
|
@ingroup appkit
|
|
@class CPControl
|
|
|
|
CPControl is an abstract superclass used to implement user interface elements. As a subclass of CPView and CPResponder it has the ability to handle screen drawing and handling user input.
|
|
*/
|
|
@implementation CPControl : CPView
|
|
{
|
|
id _value;
|
|
CPFormatter _formatter @accessors(property=formatter);
|
|
|
|
// Target-Action Support
|
|
id _target;
|
|
SEL _action;
|
|
int _sendActionOn;
|
|
BOOL _sendsActionOnEndEditing @accessors(property=sendsActionOnEndEditing);
|
|
|
|
// Mouse Tracking Support
|
|
BOOL _continuousTracking;
|
|
BOOL _trackingWasWithinFrame;
|
|
unsigned _trackingMouseDownFlags;
|
|
CGPoint _previousTrackingLocation;
|
|
|
|
CPControlSize _controlSize;
|
|
|
|
CPWritingDirection _baseWritingDirection @accessors(property=baseWritingDirection);
|
|
}
|
|
|
|
+ (CPDictionary)themeAttributes
|
|
{
|
|
return @{
|
|
@"alignment": CPLeftTextAlignment,
|
|
@"vertical-alignment": CPTopVerticalTextAlignment,
|
|
@"line-break-mode": CPLineBreakByClipping,
|
|
@"text-color": [CPColor blackColor],
|
|
@"font": [CPNull null],
|
|
@"text-shadow-color": [CPNull null],
|
|
@"text-shadow-offset": CGSizeMakeZero(),
|
|
@"image-position": CPImageLeft,
|
|
@"image-scaling": CPScaleToFit,
|
|
@"min-size": CGSizeMakeZero(),
|
|
@"max-size": CGSizeMake(-1.0, -1.0)
|
|
};
|
|
}
|
|
|
|
+ (void)initialize
|
|
{
|
|
if (self !== [CPControl class])
|
|
return;
|
|
|
|
[self exposeBinding:@"value"];
|
|
[self exposeBinding:@"objectValue"];
|
|
[self exposeBinding:@"stringValue"];
|
|
[self exposeBinding:@"integerValue"];
|
|
[self exposeBinding:@"intValue"];
|
|
[self exposeBinding:@"doubleValue"];
|
|
[self exposeBinding:@"floatValue"];
|
|
|
|
[self exposeBinding:@"enabled"];
|
|
}
|
|
|
|
+ (Class)_binderClassForBinding:(CPString)aBinding
|
|
{
|
|
if (aBinding === CPValueBinding)
|
|
return [_CPValueBinder class];
|
|
else if ([aBinding hasPrefix:CPEnabledBinding])
|
|
return [CPMultipleValueAndBinding class];
|
|
|
|
return [super _binderClassForBinding:aBinding];
|
|
}
|
|
|
|
/*!
|
|
Reverse set the binding if the CPContinuouslyUpdatesValueBindingOption is set.
|
|
*/
|
|
- (void)_continuouslyReverseSetBinding
|
|
{
|
|
var binderClass = [[self class] _binderClassForBinding:CPValueBinding],
|
|
theBinding = [binderClass getBinding:CPValueBinding forObject:self];
|
|
|
|
if ([theBinding continuouslyUpdatesValue])
|
|
[theBinding reverseSetValueFor:@"objectValue"];
|
|
}
|
|
|
|
- (void)_reverseSetBinding
|
|
{
|
|
var binderClass = [[self class] _binderClassForBinding:CPValueBinding],
|
|
theBinding = [binderClass getBinding:CPValueBinding forObject:self];
|
|
|
|
[theBinding reverseSetValueFor:@"objectValue"];
|
|
}
|
|
|
|
- (id)initWithFrame:(CGRect)aFrame
|
|
{
|
|
self = [super initWithFrame:aFrame];
|
|
|
|
if (self)
|
|
{
|
|
_sendActionOn = CPLeftMouseUpMask;
|
|
_trackingMouseDownFlags = 0;
|
|
|
|
[self setControlSize:CPThemeStateControlSizeRegular];
|
|
[self updateTrackingAreas];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Control Size
|
|
|
|
/*!
|
|
Returns the control's control size
|
|
*/
|
|
- (CPControlSize)controlSize
|
|
{
|
|
return _controlSize;
|
|
}
|
|
|
|
/*!
|
|
Sets the control's size.
|
|
@param aControlSize the control's size
|
|
*/
|
|
- (void)setControlSize:(CPControlSize)aControlSize
|
|
{
|
|
if (_controlSize === aControlSize)
|
|
return;
|
|
|
|
[self unsetThemeState:[self _controlSizeThemeState]];
|
|
_controlSize = aControlSize;
|
|
[self setThemeState:[self _controlSizeThemeState]];
|
|
|
|
[self setNeedsLayout];
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
/*!
|
|
Gets the current theme state according to the current controlSize.
|
|
@return a CPThemeState
|
|
*/
|
|
- (ThemeState)_controlSizeThemeState
|
|
{
|
|
switch (_controlSize)
|
|
{
|
|
case CPSmallControlSize:
|
|
return CPThemeStateControlSizeSmall;
|
|
|
|
case CPMiniControlSize:
|
|
return CPThemeStateControlSizeMini;
|
|
|
|
case CPRegularControlSize:
|
|
default:
|
|
return CPThemeStateControlSizeRegular;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
Change frame size according to the theme control size theme constraints
|
|
Basically for height to min-size.
|
|
*/
|
|
- (void)_sizeToControlSize
|
|
{
|
|
var frameSize = [self frameSize],
|
|
minSize = [self currentValueForThemeAttribute:@"min-size"],
|
|
maxSize = [self currentValueForThemeAttribute:@"max-size"];
|
|
|
|
if (minSize.width > 0)
|
|
{
|
|
frameSize.width = MAX(minSize.width, frameSize.width);
|
|
|
|
if (maxSize.width > 0)
|
|
frameSize.width = MIN(maxSize.width, frameSize.width);
|
|
}
|
|
|
|
if (minSize.height > 0)
|
|
{
|
|
frameSize.height = MAX(minSize.height, frameSize.height);
|
|
|
|
if (maxSize.height > 0)
|
|
frameSize.height = MIN(maxSize.height, frameSize.height);
|
|
}
|
|
|
|
[self setFrameSize:frameSize];
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
/*!
|
|
Sets the receiver's target action.
|
|
|
|
@param anAction Sets the action message that gets sent to the target.
|
|
*/
|
|
- (void)setAction:(SEL)anAction
|
|
{
|
|
_action = anAction;
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's target action.
|
|
*/
|
|
- (SEL)action
|
|
{
|
|
return _action;
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver's target. The target receives action messages from the receiver.
|
|
|
|
@param aTarget the object that will receive the message specified by action
|
|
*/
|
|
- (void)setTarget:(id)aTarget
|
|
{
|
|
_target = aTarget;
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's target. The target receives action messages from the receiver.
|
|
*/
|
|
- (id)target
|
|
{
|
|
return _target;
|
|
}
|
|
|
|
/*!
|
|
Causes \c anAction to be sent to \c anObject.
|
|
|
|
@param anAction the action to send
|
|
@param anObject the object to which the action will be sent
|
|
*/
|
|
- (BOOL)sendAction:(SEL)anAction to:(id)anObject
|
|
{
|
|
[self _reverseSetBinding];
|
|
|
|
var binding = [CPBinder getBinding:CPTargetBinding forObject:self];
|
|
[binding invokeAction];
|
|
|
|
return [CPApp sendAction:anAction to:anObject from:self];
|
|
}
|
|
|
|
- (int)sendActionOn:(int)mask
|
|
{
|
|
var previousMask = _sendActionOn;
|
|
|
|
_sendActionOn = mask;
|
|
|
|
return previousMask;
|
|
}
|
|
|
|
/*!
|
|
Returns whether the control can continuously send its action messages.
|
|
*/
|
|
- (BOOL)isContinuous
|
|
{
|
|
// Some subclasses should redefine this with CPLeftMouseDraggedMask
|
|
return (_sendActionOn & CPPeriodicMask) !== 0;
|
|
}
|
|
|
|
/*!
|
|
Sets whether the cell can continuously send its action messages.
|
|
*/
|
|
- (void)setContinuous:(BOOL)flag
|
|
{
|
|
// Some subclasses should redefine this with CPLeftMouseDraggedMask
|
|
if (flag)
|
|
_sendActionOn |= CPPeriodicMask;
|
|
else
|
|
_sendActionOn &= ~CPPeriodicMask;
|
|
}
|
|
|
|
/*!
|
|
Returns YES if the receiver tracks the mouse outside the frame, otherwise NO.
|
|
*/
|
|
- (BOOL)tracksMouseOutsideOfFrame
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (void)trackMouse:(CPEvent)anEvent
|
|
{
|
|
var type = [anEvent type],
|
|
currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
|
isWithinFrame = [self tracksMouseOutsideOfFrame] || CGRectContainsPoint([self bounds], currentLocation);
|
|
|
|
if (type === CPLeftMouseUp)
|
|
{
|
|
[self stopTracking:_previousTrackingLocation at:currentLocation mouseIsUp:YES];
|
|
|
|
_trackingMouseDownFlags = 0;
|
|
|
|
if (isWithinFrame)
|
|
[self setThemeState:CPThemeStateHovered];
|
|
}
|
|
else
|
|
{
|
|
[self unsetThemeState:CPThemeStateHovered];
|
|
|
|
if (type === CPLeftMouseDown)
|
|
{
|
|
_trackingMouseDownFlags = [anEvent modifierFlags];
|
|
_continuousTracking = [self startTrackingAt:currentLocation];
|
|
}
|
|
else if (type === CPLeftMouseDragged)
|
|
{
|
|
if (isWithinFrame)
|
|
{
|
|
if (!_trackingWasWithinFrame)
|
|
_continuousTracking = [self startTrackingAt:currentLocation];
|
|
|
|
else if (_continuousTracking)
|
|
_continuousTracking = [self continueTracking:_previousTrackingLocation at:currentLocation];
|
|
}
|
|
else
|
|
[self stopTracking:_previousTrackingLocation at:currentLocation mouseIsUp:NO];
|
|
}
|
|
|
|
[CPApp setTarget:self selector:@selector(trackMouse:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
|
}
|
|
|
|
if ((_sendActionOn & (1 << type)) && isWithinFrame)
|
|
[self sendAction:_action to:_target];
|
|
|
|
_trackingWasWithinFrame = isWithinFrame;
|
|
_previousTrackingLocation = currentLocation;
|
|
}
|
|
|
|
- (void)setState:(CPInteger)state
|
|
{
|
|
}
|
|
|
|
- (CPInteger)nextState
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
Perform a click on the receiver.
|
|
|
|
@param sender - The sender object
|
|
*/
|
|
- (void)performClick:(id)sender
|
|
{
|
|
if (![self isEnabled])
|
|
return;
|
|
|
|
[self highlight:YES];
|
|
[self setState:[self nextState]];
|
|
|
|
try
|
|
{
|
|
[self sendAction:[self action] to:[self target]];
|
|
}
|
|
catch (e)
|
|
{
|
|
throw e;
|
|
}
|
|
finally
|
|
{
|
|
[CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(unhighlightButtonTimerDidFinish:) userInfo:nil repeats:NO];
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
Fired when the button timer finished, usually after the user hits enter.
|
|
*/
|
|
- (void)unhighlightButtonTimerDidFinish:(id)sender
|
|
{
|
|
[self highlight:NO];
|
|
}
|
|
|
|
/*!
|
|
Returns the mask of modifier keys held down when the user clicked.
|
|
*/
|
|
- (unsigned)mouseDownFlags
|
|
{
|
|
return _trackingMouseDownFlags;
|
|
}
|
|
|
|
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
|
{
|
|
[self highlight:YES];
|
|
|
|
return (_sendActionOn & CPPeriodicMask) || (_sendActionOn & CPLeftMouseDraggedMask);
|
|
}
|
|
|
|
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
|
|
{
|
|
return (_sendActionOn & CPPeriodicMask) || (_sendActionOn & CPLeftMouseDraggedMask);
|
|
}
|
|
|
|
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
|
|
{
|
|
if (mouseIsUp)
|
|
[self highlight:NO];
|
|
else
|
|
[self highlight:YES];
|
|
}
|
|
|
|
/*!
|
|
Enabled controls accept first mouse by default.
|
|
*/
|
|
- (BOOL)acceptsFirstMouse:(CPEvent)anEvent
|
|
{
|
|
return [self isEnabled];
|
|
}
|
|
|
|
- (void)mouseDown:(CPEvent)anEvent
|
|
{
|
|
if (![self isEnabled])
|
|
return;
|
|
|
|
[self trackMouse:anEvent];
|
|
}
|
|
|
|
- (void)mouseEntered:(CPEvent)anEvent
|
|
{
|
|
if (![self isEnabled])
|
|
return;
|
|
|
|
[self setThemeState:CPThemeStateHovered];
|
|
}
|
|
|
|
- (void)mouseExited:(CPEvent)anEvent
|
|
{
|
|
var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
|
isWithinFrame = [self tracksMouseOutsideOfFrame] || CGRectContainsPoint([self bounds], currentLocation);
|
|
|
|
// Make sure we're not still in the frame because Cappuccino will sent mouseExited events
|
|
// for all of the (ephemeral) subviews of a view as well.
|
|
if (!isWithinFrame)
|
|
[self unsetThemeState:CPThemeStateHovered];
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's object value.
|
|
*/
|
|
- (id)objectValue
|
|
{
|
|
return _value;
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver's object value.
|
|
*/
|
|
- (void)setObjectValue:(id)anObject
|
|
{
|
|
_value = anObject;
|
|
|
|
[self setNeedsLayout];
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's float value.
|
|
*/
|
|
- (float)floatValue
|
|
{
|
|
var floatValue = parseFloat(_value, 10);
|
|
return isNaN(floatValue) ? 0.0 : floatValue;
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver's float value.
|
|
*/
|
|
- (void)setFloatValue:(float)aValue
|
|
{
|
|
[self setObjectValue:aValue];
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's double value.
|
|
*/
|
|
- (double)doubleValue
|
|
{
|
|
var doubleValue = parseFloat(_value, 10);
|
|
return isNaN(doubleValue) ? 0.0 : doubleValue;
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver's double value.
|
|
*/
|
|
- (void)setDoubleValue:(double)anObject
|
|
{
|
|
[self setObjectValue:anObject];
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's int value.
|
|
*/
|
|
- (int)intValue
|
|
{
|
|
var intValue = parseInt(_value, 10);
|
|
return isNaN(intValue) ? 0.0 : intValue;
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver's int value.
|
|
*/
|
|
- (void)setIntValue:(int)anObject
|
|
{
|
|
[self setObjectValue:anObject];
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's int value.
|
|
*/
|
|
- (int)integerValue
|
|
{
|
|
var intValue = parseInt(_value, 10);
|
|
return isNaN(intValue) ? 0.0 : intValue;
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver's int value.
|
|
*/
|
|
- (void)setIntegerValue:(int)anObject
|
|
{
|
|
[self setObjectValue:anObject];
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's string value.
|
|
*/
|
|
- (CPString)stringValue
|
|
{
|
|
if (_formatter && _value != nil)
|
|
{
|
|
var formattedValue = [self hasThemeState:CPThemeStateEditing] ? [_formatter editingStringForObjectValue:_value] : [_formatter stringForObjectValue:_value];
|
|
|
|
if (formattedValue != nil)
|
|
return formattedValue;
|
|
}
|
|
|
|
return _value == nil ? @"" : String(_value);
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver's string value.
|
|
*/
|
|
- (void)setStringValue:(CPString)aString
|
|
{
|
|
// Cocoa raises an invalid parameter assertion and returns if you pass nil.
|
|
if (aString == nil)
|
|
{
|
|
CPLog.warn("nil or undefined sent to CPControl -setStringValue");
|
|
return;
|
|
}
|
|
|
|
var value;
|
|
|
|
if (_formatter)
|
|
{
|
|
value = nil;
|
|
|
|
if ([_formatter getObjectValue:@ref(value) forString:aString errorDescription:nil] === NO)
|
|
{
|
|
// If the given string is non-empty and doesn't work, Cocoa tries an empty string.
|
|
if (!aString || [_formatter getObjectValue:@ref(value) forString:@"" errorDescription:nil] === NO)
|
|
value = undefined; // Means the value is invalid
|
|
}
|
|
}
|
|
else
|
|
value = aString;
|
|
|
|
[self setObjectValue:value];
|
|
}
|
|
|
|
- (void)takeDoubleValueFrom:(id)sender
|
|
{
|
|
if ([sender respondsToSelector:@selector(doubleValue)])
|
|
[self setDoubleValue:[sender doubleValue]];
|
|
}
|
|
|
|
|
|
- (void)takeFloatValueFrom:(id)sender
|
|
{
|
|
if ([sender respondsToSelector:@selector(floatValue)])
|
|
[self setFloatValue:[sender floatValue]];
|
|
}
|
|
|
|
- (void)takeIntegerValueFrom:(id)sender
|
|
{
|
|
if ([sender respondsToSelector:@selector(integerValue)])
|
|
[self setIntegerValue:[sender integerValue]];
|
|
}
|
|
|
|
- (void)takeIntValueFrom:(id)sender
|
|
{
|
|
if ([sender respondsToSelector:@selector(intValue)])
|
|
[self setIntValue:[sender intValue]];
|
|
}
|
|
|
|
- (void)takeObjectValueFrom:(id)sender
|
|
{
|
|
if ([sender respondsToSelector:@selector(objectValue)])
|
|
[self setObjectValue:[sender objectValue]];
|
|
}
|
|
|
|
- (void)takeStringValueFrom:(id)sender
|
|
{
|
|
if ([sender respondsToSelector:@selector(stringValue)])
|
|
[self setStringValue:[sender stringValue]];
|
|
}
|
|
|
|
- (void)textDidBeginEditing:(CPNotification)note
|
|
{
|
|
//this looks to prevent false propagation of notifications for other objects
|
|
if ([note object] != self)
|
|
return;
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidBeginEditingNotification object:self userInfo:@{"CPFieldEditor": [note object]}];
|
|
}
|
|
|
|
- (void)textDidChange:(CPNotification)note
|
|
{
|
|
//this looks to prevent false propagation of notifications for other objects
|
|
if ([note object] != self)
|
|
return;
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidChangeNotification object:self userInfo:@{"CPFieldEditor": [note object]}];
|
|
}
|
|
|
|
- (void)textDidEndEditing:(CPNotification)note
|
|
{
|
|
//this looks to prevent false propagation of notifications for other objects
|
|
if ([note object] != self)
|
|
return;
|
|
|
|
[self _reverseSetBinding];
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidEndEditingNotification object:self userInfo:[note userInfo]];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
Return the currentTextMovement needed by the delegate textDidEndEditing
|
|
This is going to check the currentEvent of the CPApp
|
|
*/
|
|
- (unsigned)_currentTextMovement
|
|
{
|
|
var currentEvent = [CPApp currentEvent],
|
|
keyCode = [currentEvent keyCode],
|
|
modifierFlags = [currentEvent modifierFlags];
|
|
|
|
switch (keyCode)
|
|
{
|
|
case CPEscapeKeyCode:
|
|
return CPCancelTextMovement;
|
|
|
|
case CPLeftArrowKeyCode:
|
|
return CPLeftTextMovement;
|
|
|
|
case CPRightArrowKeyCode:
|
|
return CPRightTextMovement;
|
|
|
|
case CPUpArrowKeyCode:
|
|
return CPUpTextMovement;
|
|
|
|
case CPDownArrowKeyCode:
|
|
return CPDownTextMovement;
|
|
|
|
case CPReturnKeyCode:
|
|
return CPReturnTextMovement;
|
|
|
|
case CPTabKeyCode:
|
|
if (modifierFlags & CPShiftKeyMask)
|
|
return CPBacktabTextMovement;
|
|
|
|
return CPTabTextMovement;
|
|
|
|
default:
|
|
return CPOtherTextMovement;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Sets the text alignment of the control.
|
|
|
|
<pre>
|
|
CPLeftTextAlignment
|
|
CPCenterTextAlignment
|
|
CPRightTextAlignment
|
|
CPJustifiedTextAlignment
|
|
CPNaturalTextAlignment
|
|
</pre>
|
|
*/
|
|
- (void)setAlignment:(CPTextAlignment)alignment
|
|
{
|
|
[self setValue:alignment forThemeAttribute:@"alignment"];
|
|
}
|
|
|
|
/*!
|
|
Returns the text alignment of the control.
|
|
*/
|
|
- (CPTextAlignment)alignment
|
|
{
|
|
return [self valueForThemeAttribute:@"alignment"];
|
|
}
|
|
|
|
/*!
|
|
Set the vertical text alignment of the control.
|
|
|
|
<pre>
|
|
CPTopVerticalTextAlignment
|
|
CPCenterVerticalTextAlignment
|
|
CPBottomVerticalTextAlignment
|
|
</pre>
|
|
*/
|
|
- (void)setVerticalAlignment:(CPVerticalTextAlignment)alignment
|
|
{
|
|
[self setValue:alignment forThemeAttribute:@"vertical-alignment"];
|
|
}
|
|
|
|
/*!
|
|
Returns the vertical text alignment of the receiver.
|
|
*/
|
|
- (CPTextVerticalAlignment)verticalAlignment
|
|
{
|
|
return [self valueForThemeAttribute:@"vertical-alignment"];
|
|
}
|
|
|
|
/*!
|
|
Sets the line break mode of the receiver.
|
|
|
|
<pre>
|
|
CPLineBreakByWordWrapping
|
|
CPLineBreakByCharWrapping
|
|
CPLineBreakByClipping
|
|
CPLineBreakByTruncatingHead
|
|
CPLineBreakByTruncatingTail
|
|
CPLineBreakByTruncatingMiddle
|
|
</pre>
|
|
*/
|
|
- (void)setLineBreakMode:(CPLineBreakMode)mode
|
|
{
|
|
[self setValue:mode forThemeAttribute:@"line-break-mode"];
|
|
}
|
|
|
|
/*!
|
|
Returns the line break mode of the control.
|
|
*/
|
|
- (CPLineBreakMode)lineBreakMode
|
|
{
|
|
return [self valueForThemeAttribute:@"line-break-mode"];
|
|
}
|
|
|
|
/*!
|
|
Sets the text color of the receiver.
|
|
|
|
@param aColor - A CPColor object.
|
|
*/
|
|
- (void)setTextColor:(CPColor)aColor
|
|
{
|
|
[self setValue:aColor forThemeAttribute:@"text-color" inState:[self themeState]];
|
|
}
|
|
|
|
- (void)setTextColor:(CPColor)aColor inThemeStates:(CPArray)themeStates
|
|
{
|
|
[self setValue:aColor forThemeAttribute:@"text-color" inStates:themeStates];
|
|
}
|
|
|
|
/*!
|
|
Returns the text color of the receiver.
|
|
*/
|
|
- (CPColor)textColor
|
|
{
|
|
return [self valueForThemeAttribute:@"text-color"];
|
|
}
|
|
|
|
/*!
|
|
Sets the shadow color of the text for the receiver.
|
|
*/
|
|
- (void)setTextShadowColor:(CPColor)aColor
|
|
{
|
|
[self setValue:aColor forThemeAttribute:@"text-shadow-color"];
|
|
}
|
|
|
|
/*!
|
|
Returns the shadow color of the text for the control.
|
|
*/
|
|
- (CPColor)textShadowColor
|
|
{
|
|
return [self valueForThemeAttribute:@"text-shadow-color"];
|
|
}
|
|
|
|
/*!
|
|
Sets the shadow offset for the text.
|
|
|
|
@param offset - a CGSize with the x and y offsets.
|
|
*/
|
|
- (void)setTextShadowOffset:(CGSize)offset
|
|
{
|
|
[self setValue:offset forThemeAttribute:@"text-shadow-offset"];
|
|
}
|
|
|
|
/*!
|
|
Returns the text shadow offset of the receiver.
|
|
*/
|
|
- (CGSize)textShadowOffset
|
|
{
|
|
return [self valueForThemeAttribute:@"text-shadow-offset"];
|
|
}
|
|
|
|
/*!
|
|
Sets the font of the control.
|
|
*/
|
|
- (void)setFont:(CPFont)aFont
|
|
{
|
|
[self setValue:aFont forThemeAttribute:@"font"];
|
|
}
|
|
|
|
/*!
|
|
Returns the font of the control.
|
|
*/
|
|
- (CPFont)font
|
|
{
|
|
return [self currentValueForThemeAttribute:@"font"] || [CPFont systemFontForControlSize:_controlSize];
|
|
}
|
|
|
|
/*!
|
|
Sets the image position of the control.
|
|
|
|
<pre>
|
|
CPNoImage
|
|
CPImageOnly
|
|
CPImageLeft
|
|
CPImageRight
|
|
CPImageBelow
|
|
CPImageAbove
|
|
CPImageOverlaps
|
|
</pre>
|
|
*/
|
|
- (void)setImagePosition:(CPCellImagePosition)position
|
|
{
|
|
[self setValue:position forThemeAttribute:@"image-position"];
|
|
}
|
|
|
|
/*!
|
|
Returns the image position of the receiver.
|
|
*/
|
|
- (CPCellImagePosition)imagePosition
|
|
{
|
|
return [self valueForThemeAttribute:@"image-position"];
|
|
}
|
|
|
|
/*!
|
|
Sets the image scaling of the control.
|
|
|
|
<pre>
|
|
CPImageScaleProportionallyDown
|
|
CPImageScaleAxesIndependently
|
|
CPImageScaleNone
|
|
CPImageScaleProportionallyUpOrDown
|
|
</pre>
|
|
*/
|
|
- (void)setImageScaling:(CPImageScaling)scaling
|
|
{
|
|
[self setValue:scaling forThemeAttribute:@"image-scaling"];
|
|
}
|
|
|
|
/*!
|
|
Returns the image scaling of the control.
|
|
*/
|
|
- (CPUInteger)imageScaling
|
|
{
|
|
return [self valueForThemeAttribute:@"image-scaling"];
|
|
}
|
|
|
|
/*!
|
|
Sets the enabled status of the control.
|
|
Controls that are not enabled can not be used by the user and obtain the CPThemeStateDisabled theme state.
|
|
|
|
@param BOOL - YES if the control should be enabled, otherwise NO.
|
|
*/
|
|
- (void)setEnabled:(BOOL)isEnabled
|
|
{
|
|
if (isEnabled)
|
|
[self unsetThemeState:CPThemeStateDisabled];
|
|
else
|
|
[self setThemeState:CPThemeStateDisabled];
|
|
}
|
|
|
|
/*!
|
|
Returns YES if the receiver is enabled, otherwise NO.
|
|
*/
|
|
- (BOOL)isEnabled
|
|
{
|
|
return ![self hasThemeState:CPThemeStateDisabled];
|
|
}
|
|
|
|
/*!
|
|
Highlights the receiver.
|
|
|
|
@param BOOL - YES if the receiver should be highlighted, otherwise NO.
|
|
*/
|
|
- (void)highlight:(BOOL)shouldHighlight
|
|
{
|
|
[self setHighlighted:shouldHighlight];
|
|
}
|
|
|
|
/*!
|
|
Highlights the receiver.
|
|
|
|
@param BOOL - YES if the receiver should be highlighted, otherwise NO.
|
|
*/
|
|
- (void)setHighlighted:(BOOL)isHighlighted
|
|
{
|
|
if (isHighlighted)
|
|
[self setThemeState:CPThemeStateHighlighted];
|
|
else
|
|
[self unsetThemeState:CPThemeStateHighlighted];
|
|
}
|
|
|
|
/*!
|
|
Returns YES if the control is highlighted, otherwise NO.
|
|
*/
|
|
- (BOOL)isHighlighted
|
|
{
|
|
return [self hasThemeState:CPThemeStateHighlighted];
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark Base writing direction
|
|
|
|
/*!
|
|
Sets the initial writing direction of the receiver
|
|
@param writingDirection - It could be CPWritingDirectionNatural, CPWritingDirectionLeftToRight, CPWritingDirectionRightToLeft
|
|
*/
|
|
- (void)setBaseWritingDirection:(CPWritingDirection)writingDirection
|
|
{
|
|
if (writingDirection == _baseWritingDirection)
|
|
return;
|
|
|
|
[self willChangeValueForKey:@"baseWritingDirection"];
|
|
_baseWritingDirection = writingDirection;
|
|
[self didChangeValueForKey:@"baseWritingDirection"];
|
|
|
|
#if PLATFORM(DOM)
|
|
|
|
var style;
|
|
|
|
switch (_baseWritingDirection)
|
|
{
|
|
case CPWritingDirectionNatural:
|
|
style = "initial";
|
|
break;
|
|
|
|
case CPWritingDirectionLeftToRight:
|
|
style = "ltr";
|
|
break;
|
|
|
|
case CPWritingDirectionRightToLeft:
|
|
style = "rtl";
|
|
break;
|
|
|
|
default:
|
|
style = "initial";
|
|
}
|
|
|
|
_DOMElement.style.direction = style;
|
|
#endif
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPControl (CPTrackingArea)
|
|
{
|
|
CPTrackingArea _controlTrackingArea;
|
|
}
|
|
|
|
- (void)updateTrackingAreas
|
|
{
|
|
if (_controlTrackingArea)
|
|
[self removeTrackingArea:_controlTrackingArea];
|
|
|
|
_controlTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
|
|
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect
|
|
owner:self
|
|
userInfo:nil];
|
|
[self addTrackingArea:_controlTrackingArea];
|
|
[super updateTrackingAreas];
|
|
}
|
|
|
|
@end
|
|
|
|
var CPControlActionKey = @"CPControlActionKey",
|
|
CPControlControlSizeKey = @"CPControlControlSizeKey",
|
|
CPControlControlStateKey = @"CPControlControlStateKey",
|
|
CPControlFormatterKey = @"CPControlFormatterKey",
|
|
CPControlIsEnabledKey = @"CPControlIsEnabledKey",
|
|
CPControlSendActionOnKey = @"CPControlSendActionOnKey",
|
|
CPControlSendsActionOnEndEditingKey = @"CPControlSendsActionOnEndEditingKey",
|
|
CPControlTargetKey = @"CPControlTargetKey",
|
|
CPControlValueKey = @"CPControlValueKey",
|
|
CPControlBaseWrittingDirectionKey = @"CPControlBaseWrittingDirectionKey";
|
|
|
|
__Deprecated__CPImageViewImageKey = @"CPImageViewImageKey";
|
|
|
|
@implementation CPControl (CPCoding)
|
|
|
|
/*
|
|
Initializes the control by unarchiving it from a coder.
|
|
|
|
@param aCoder the coder from which to unarchive the control
|
|
@return the initialized control
|
|
*/
|
|
- (id)initWithCoder:(CPCoder)aCoder
|
|
{
|
|
self = [super initWithCoder:aCoder];
|
|
|
|
if (self)
|
|
{
|
|
[self setObjectValue:[aCoder decodeObjectForKey:CPControlValueKey]];
|
|
|
|
[self setTarget:[aCoder decodeObjectForKey:CPControlTargetKey]];
|
|
[self setAction:[aCoder decodeObjectForKey:CPControlActionKey]];
|
|
|
|
[self sendActionOn:[aCoder decodeIntForKey:CPControlSendActionOnKey]];
|
|
[self setSendsActionOnEndEditing:[aCoder decodeBoolForKey:CPControlSendsActionOnEndEditingKey]];
|
|
|
|
[self setFormatter:[aCoder decodeObjectForKey:CPControlFormatterKey]];
|
|
|
|
[self setControlSize:[aCoder decodeIntForKey:CPControlControlSizeKey]];
|
|
|
|
[self setBaseWritingDirection:[aCoder decodeIntForKey:CPControlBaseWrittingDirectionKey]];
|
|
[self updateTrackingAreas];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
Archives the control to the provided coder.
|
|
|
|
@param aCoder the coder to which the control will be archived.
|
|
*/
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
[super encodeWithCoder:aCoder];
|
|
|
|
if (_sendsActionOnEndEditing)
|
|
[aCoder encodeBool:_sendsActionOnEndEditing forKey:CPControlSendsActionOnEndEditingKey];
|
|
|
|
var objectValue = [self objectValue];
|
|
|
|
if (objectValue != nil)
|
|
[aCoder encodeObject:objectValue forKey:CPControlValueKey];
|
|
|
|
if (_target != nil)
|
|
[aCoder encodeConditionalObject:_target forKey:CPControlTargetKey];
|
|
|
|
if (_action != nil)
|
|
[aCoder encodeObject:_action forKey:CPControlActionKey];
|
|
|
|
[aCoder encodeInt:_sendActionOn forKey:CPControlSendActionOnKey];
|
|
|
|
if (_formatter != nil)
|
|
[aCoder encodeObject:_formatter forKey:CPControlFormatterKey];
|
|
|
|
[aCoder encodeInt:_controlSize forKey:CPControlControlSizeKey];
|
|
|
|
[aCoder encodeInt:_baseWritingDirection forKey:CPControlBaseWrittingDirectionKey];
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
var _CPControlSizeIdentifiers = [],
|
|
_CPControlCachedColorWithPatternImages = {},
|
|
_CPControlCachedThreePartImagePattern = {};
|
|
|
|
_CPControlSizeIdentifiers[CPRegularControlSize] = "Regular";
|
|
_CPControlSizeIdentifiers[CPSmallControlSize] = "Small";
|
|
_CPControlSizeIdentifiers[CPMiniControlSize] = "Mini";
|
|
|
|
function _CPControlIdentifierForControlSize(aControlSize)
|
|
{
|
|
return _CPControlSizeIdentifiers[aControlSize];
|
|
}
|
|
|
|
function _CPControlColorWithPatternImage(sizes, aClassName)
|
|
{
|
|
var index = 1,
|
|
count = arguments.length,
|
|
identifier = "";
|
|
|
|
for (; index < count; ++index)
|
|
identifier += arguments[index];
|
|
|
|
var color = _CPControlCachedColorWithPatternImages[identifier];
|
|
|
|
if (!color)
|
|
{
|
|
var bundle = [CPBundle bundleForClass:[CPControl class]];
|
|
|
|
color = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:aClassName + "/" + identifier + ".png"] size:sizes[identifier]]];
|
|
|
|
_CPControlCachedColorWithPatternImages[identifier] = color;
|
|
}
|
|
|
|
return color;
|
|
}
|
|
|
|
function _CPControlThreePartImagePattern(isVertical, sizes, aClassName)
|
|
{
|
|
var index = 2,
|
|
count = arguments.length,
|
|
identifier = "";
|
|
|
|
for (; index < count; ++index)
|
|
identifier += arguments[index];
|
|
|
|
var color = _CPControlCachedThreePartImagePattern[identifier];
|
|
|
|
if (!color)
|
|
{
|
|
var bundle = [CPBundle bundleForClass:[CPControl class]],
|
|
path = aClassName + "/" + identifier;
|
|
|
|
sizes = sizes[identifier];
|
|
|
|
color = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:[
|
|
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "0.png"] size:sizes[0]],
|
|
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "1.png"] size:sizes[1]],
|
|
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "2.png"] size:sizes[2]]
|
|
] isVertical:isVertical]];
|
|
|
|
_CPControlCachedThreePartImagePattern[identifier] = color;
|
|
}
|
|
|
|
return color;
|
|
}
|