Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Me1000/248392 to your computer and use it in GitHub Desktop.
Save Me1000/248392 to your computer and use it in GitHub Desktop.
/*
* CPTextField.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 "CPControl.j"
@import "CPStringDrawing.j"
@import "CPCompatibility.j"
#include "CoreGraphics/CGGeometry.h"
#include "Platform/Platform.h"
#include "Platform/DOM/CPDOMDisplayServer.h"
CPLineBreakByWordWrapping = 0;
CPLineBreakByCharWrapping = 1;
CPLineBreakByClipping = 2;
CPLineBreakByTruncatingHead = 3;
CPLineBreakByTruncatingTail = 4;
CPLineBreakByTruncatingMiddle = 5;
CPTextFieldSquareBezel = 0; /*! A textfield bezel with a squared corners. */
CPTextFieldRoundedBezel = 1; /*! A textfield bezel with rounded corners. */
CPTextFieldDidFocusNotification = @"CPTextFieldDidFocusNotification";
CPTextFieldDidBlurNotification = @"CPTextFieldDidBlurNotification";
#if PLATFORM(DOM)
var CPTextFieldDOMInputElement = nil,
CPTextFieldDOMPasswordInputElement = nil,
CPTextFieldDOMStandardInputElement = nil,
CPTextFieldDOMMultilineInputElement = nil,
CPTextFieldInputOwner = nil,
CPTextFieldTextDidChangeValue = nil,
CPTextFieldInputResigning = NO,
CPTextFieldInputDidBlur = NO,
CPTextFieldInputIsActive = NO,
CPTextFieldCachedSelectStartFunction = nil,
CPTextFieldCachedDragFunction = nil,
CPTextFieldBlurFunction = nil,
CPTextFieldKeyUpFunction = nil,
CPTextFieldKeyPressFunction = nil,
CPTextFieldKeyDownFunction = nil;
#endif
var CPSecureTextFieldCharacter = "\u2022";
@implementation CPString (CPTextFieldAdditions)
/*!
Returns the string (\c self).
*/
- (CPString)string
{
return self;
}
@end
CPTextFieldStateRounded = CPThemeState("rounded");
CPTextFieldStatePlaceholder = CPThemeState("placeholder");
CPThemeStateEditable = CPThemeState("editable");
/*!
@ingroup appkit
This control displays editable text in a Cappuccino application.
*/
@implementation CPTextField : CPControl
{
BOOL _isEditing;
BOOL _isEditable;
BOOL _isSelectable;
BOOL _isSecure;
BOOL _drawsBackground;
CPColor _textFieldBackgroundColor;
id _placeholderString;
id _delegate;
CPString _textDidChangeValue;
// NS-style Display Properties
CPTextFieldBezelStyle _bezelStyle;
BOOL _isBordered;
CPControlSize _controlSize;
BOOL _wraps; // if true we use a textarea
}
+ (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth
{
return [self textFieldWithStringValue:aStringValue placeholder:aPlaceholder width:aWidth theme:[CPTheme defaultTheme]];
}
+ (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
{
var textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
[textField setTheme:aTheme];
[textField setStringValue:aStringValue];
[textField setPlaceholderString:aPlaceholder];
[textField setBordered:YES];
[textField setBezeled:YES];
[textField setEditable:YES];
[textField sizeToFit];
return textField;
}
+ (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth
{
return [self roundedTextFieldWithStringValue:aStringValue placeholder:aPlaceholder width:aWidth theme:[CPTheme defaultTheme]];
}
+ (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
{
var textField = [[CPTextField alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
[textField setTheme:aTheme];
[textField setStringValue:aStringValue];
[textField setPlaceholderString:aPlaceholder];
[textField setBezelStyle:CPTextFieldRoundedBezel];
[textField setBordered:YES];
[textField setBezeled:YES];
[textField setEditable:YES];
[textField sizeToFit];
return textField;
}
+ (CPTextField)labelWithTitle:(CPString)aTitle
{
return [self labelWithTitle:aTitle theme:[CPTheme defaultTheme]];
}
+ (CPTextField)labelWithTitle:(CPString)aTitle theme:(CPTheme)aTheme
{
var textField = [[self alloc] init];
[textField setStringValue:aTitle];
[textField sizeToFit];
return textField;
}
+ (CPString)themeClass
{
return "textfield";
}
+ (id)themeAttributes
{
return [CPDictionary dictionaryWithObjects:[_CGInsetMakeZero(), _CGInsetMake(2.0, 2.0, 2.0, 2.0), nil, nil, _CGInsetMakeZero()]
forKeys:[@"bezel-inset", @"content-inset", @"bezel-color", @"focus-ring-color", @"focus-inset"]];
}
/* @ignore */
#if PLATFORM(DOM)
- (DOMElement)_inputElement
{
if (!CPTextFieldDOMInputElement)
{
CPTextFieldDOMInputElement = document.createElement("input");
CPTextFieldDOMInputElement.style.position = "absolute";
CPTextFieldDOMInputElement.style.border = "0px";
CPTextFieldDOMInputElement.style.padding = "0px";
CPTextFieldDOMInputElement.style.margin = "0px";
CPTextFieldDOMInputElement.style.whiteSpace = "pre";
CPTextFieldDOMInputElement.style.background = "transparent";
CPTextFieldDOMInputElement.style.outline = "none";
CPTextFieldBlurFunction = function(anEvent)
{
if (CPTextFieldInputOwner && CPTextFieldInputOwner._DOMElement != CPTextFieldDOMInputElement.parentNode)
return;
if (!CPTextFieldInputResigning)
{
[[CPTextFieldInputOwner window] makeFirstResponder:nil];
return;
}
CPTextFieldHandleBlur(anEvent, CPTextFieldDOMInputElement);
CPTextFieldInputDidBlur = YES;
return true;
}
CPTextFieldHandleBlur = function(anEvent)
{
CPTextFieldInputOwner = nil;
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}
//FIXME make this not onblur
CPTextFieldDOMInputElement.onblur = CPTextFieldBlurFunction;
CPTextFieldDOMStandardInputElement = CPTextFieldDOMInputElement;
}
/*if (CPFeatureIsCompatible(CPInputTypeCanBeChangedFeature))
{
if ([self isSecure])
CPTextFieldDOMInputElement.type = "password";
else if([self wraps])
else
CPTextFieldDOMInputElement.type = "textarea";
return CPTextFieldDOMInputElement;
}*/
if ([self isSecure])
{
if (!CPTextFieldDOMPasswordInputElement)
{
CPTextFieldDOMPasswordInputElement = document.createElement("input");
CPTextFieldDOMPasswordInputElement.style.position = "absolute";
CPTextFieldDOMPasswordInputElement.style.border = "0px";
CPTextFieldDOMPasswordInputElement.style.padding = "0px";
CPTextFieldDOMPasswordInputElement.style.margin = "0px";
CPTextFieldDOMPasswordInputElement.style.whiteSpace = "pre";
CPTextFieldDOMPasswordInputElement.style.background = "transparent";
CPTextFieldDOMPasswordInputElement.style.outline = "none";
CPTextFieldDOMPasswordInputElement.type = "password";
if (document.attachEvent)
{
CPTextFieldDOMPasswordInputElement.attachEvent("on" + CPDOMEventKeyUp, CPTextFieldKeyUpFunction);
CPTextFieldDOMPasswordInputElement.attachEvent("on" + CPDOMEventKeyDown, CPTextFieldKeyDownFunction);
CPTextFieldDOMPasswordInputElement.attachEvent("on" + CPDOMEventKeyPress, CPTextFieldKeyPressFunction);
}
else
{
CPTextFieldDOMPasswordInputElement.addEventListener(CPDOMEventKeyUp, CPTextFieldKeyUpFunction, NO);
CPTextFieldDOMPasswordInputElement.addEventListener(CPDOMEventKeyDown, CPTextFieldKeyDownFunction, NO);
CPTextFieldDOMPasswordInputElement.addEventListener(CPDOMEventKeyPress, CPTextFieldKeyPressFunction, NO);
}
CPTextFieldDOMPasswordInputElement.onblur = CPTextFieldBlurFunction;
}
CPTextFieldDOMInputElement = CPTextFieldDOMPasswordInputElement;
}
else if ([self wraps])
{
//alert("wraps");
if (!CPTextFieldDOMMultilineInputElement)
{
//console.log("here");
CPTextFieldDOMMultilineInputElement = document.createElement("textarea");
CPTextFieldDOMMultilineInputElement.style.resize = "none";
CPTextFieldDOMMultilineInputElement.style.overflow = "hidden";
CPTextFieldDOMMultilineInputElement.style.position = "absolute";
CPTextFieldDOMMultilineInputElement.style.border = "0px";
CPTextFieldDOMMultilineInputElement.style.padding = "0px";
CPTextFieldDOMMultilineInputElement.style.margin = "0px";
CPTextFieldDOMMultilineInputElement.style.whiteSpace = "pre";
CPTextFieldDOMMultilineInputElement.style.background = "transparent";
CPTextFieldDOMMultilineInputElement.style.outline = "none";
if (document.attachEvent)
{
CPTextFieldDOMMultilineInputElement.attachEvent("on" + CPDOMEventKeyUp, CPTextFieldKeyUpFunction);
CPTextFieldDOMMultilineInputElement.attachEvent("on" + CPDOMEventKeyDown, CPTextFieldKeyDownFunction);
CPTextFieldDOMMultilineInputElement.attachEvent("on" + CPDOMEventKeyPress, CPTextFieldKeyPressFunction);
}
else
{
CPTextFieldDOMMultilineInputElement.addEventListener(CPDOMEventKeyUp, CPTextFieldKeyUpFunction, NO);
CPTextFieldDOMMultilineInputElement.addEventListener(CPDOMEventKeyDown, CPTextFieldKeyDownFunction, NO);
CPTextFieldDOMMultilineInputElement.addEventListener(CPDOMEventKeyPress, CPTextFieldKeyPressFunction, NO);
}
CPTextFieldDOMMultilineInputElement.onblur = CPTextFieldBlurFunction;
}
CPTextFieldDOMInputElement = CPTextFieldDOMMultilineInputElement;
}
else
{
CPTextFieldDOMInputElement = CPTextFieldDOMStandardInputElement;
}
return CPTextFieldDOMInputElement;
}
#endif
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
[self setStringValue:@""];
[self setPlaceholderString:@""];
_sendActionOn = CPKeyUpMask | CPKeyDownMask;
[self setValue:CPLeftTextAlignment forThemeAttribute:@"alignment"];
}
return self;
}
#pragma mark Controlling Editability and Selectability
/*!
Sets whether or not the receiver text field can be edited
*/
- (void)setEditable:(BOOL)shouldBeEditable
{
_isEditable = shouldBeEditable;
if (shouldBeEditable)
[self setThemeState:CPThemeStateEditable];
else
[self unsetThemeState:CPThemeStateEditable];
}
/*!
Returns \c YES if the textfield is currently editable by the user.
*/
- (BOOL)isEditable
{
return _isEditable;
}
/*!
Sets whether the field's text is selectable by the user.
@param aFlag \c YES makes the text selectable
*/
- (void)setSelectable:(BOOL)aFlag
{
_isSelectable = aFlag;
}
/*!
Returns \c YES if the field's text is selectable by the user.
*/
- (BOOL)isSelectable
{
return _isSelectable;
}
/*!
Sets whether the field's text is secure.
@param aFlag \c YES makes the text secure
*/
- (void)setSecure:(BOOL)aFlag
{
_isSecure = aFlag;
}
/*!
Returns \c YES if the field's text is secure (password entry).
*/
- (BOOL)isSecure
{
return _isSecure;
}
// Setting the Bezel Style
/*!
Sets whether the textfield will have a bezeled border.
@param shouldBeBezeled \c YES means the textfield will draw a bezeled border
*/
- (void)setBezeled:(BOOL)shouldBeBezeled
{
if (shouldBeBezeled)
[self setThemeState:CPThemeStateBezeled];
else
[self unsetThemeState:CPThemeStateBezeled];
}
/*!
Returns \c YES if the textfield draws a bezeled border.
*/
- (BOOL)isBezeled
{
return [self hasThemeState:CPThemeStateBezeled];
}
/*!
Sets the textfield's bezel style.
@param aBezelStyle the constant for the desired bezel style
*/
- (void)setBezelStyle:(CPTextFieldBezelStyle)aBezelStyle
{
var shouldBeRounded = aBezelStyle === CPTextFieldRoundedBezel;
if (shouldBeRounded)
[self setThemeState:CPTextFieldStateRounded];
else
[self unsetThemeState:CPTextFieldStateRounded];
}
/*!
Returns the textfield's bezel style.
*/
- (CPTextFieldBezelStyle)bezelStyle
{
if ([self hasThemeState:CPTextFieldStateRounded])
return CPTextFieldRoundedBezel;
return CPTextFieldSquareBezel;
}
/*!
Sets whether the textfield will have a border drawn.
@param shouldBeBordered \c YES makes the textfield draw a border
*/
- (void)setBordered:(BOOL)shouldBeBordered
{
if (shouldBeBordered)
[self setThemeState:CPThemeStateBordered];
else
[self unsetThemeState:CPThemeStateBordered];
}
/*!
Returns \c YES if the textfield has a border.
*/
- (BOOL)isBordered
{
return [self hasThemeState:CPThemeStateBordered];
}
/*!
Sets whether the textfield will have a background drawn.
@param shouldDrawBackground \c YES makes the textfield draw a background
*/
- (void)setDrawsBackground:(BOOL)shouldDrawBackground
{
if (_drawsBackground == shouldDrawBackground)
return;
_drawsBackground = shouldDrawBackground;
[self setNeedsLayout];
[self setNeedsDisplay:YES];
}
/*!
Returns \c YES if the textfield draws a background.
*/
- (BOOL)drawsBackground
{
return _drawsBackground;
}
/*!
Sets the background color, which is shown for non-bezeled text fields with drawsBackground set to YES
@param aColor The background color
*/
- (void)setTextFieldBackgroundColor:(CPColor)aColor
{
if (_textFieldBackgroundColor == aColor)
return;
_textFieldBackgroundColor = aColor;
[self setNeedsLayout];
[self setNeedsDisplay:YES];
}
/*!
Returns the background color.
*/
- (CPColor)textFieldBackgroundColor
{
return _textFieldBackgroundColor;
}
/* @ignore */
- (BOOL)acceptsFirstResponder
{
return [self isEditable] && [self isEnabled];
}
/* @ignore */
- (BOOL)becomeFirstResponder
{
#if PLATFORM(DOM)
if (CPTextFieldInputOwner && [CPTextFieldInputOwner window] !== [self window])
[[CPTextFieldInputOwner window] makeFirstResponder:nil];
#endif
[self setThemeState:CPThemeStateEditing];
[self _updatePlaceholderState];
[self setNeedsLayout];
_isEditing = NO;
#if PLATFORM(DOM)
var string = [self stringValue],
element = [self _inputElement];
element.value = string;
element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
element.style.font = [[self currentValueForThemeAttribute:@"font"] cssString];
element.style.zIndex = 1000;
switch ([self alignment])
{
case CPCenterTextAlignment: element.style.textAlign = "center";
break;
case CPRightTextAlignment: element.style.textAlign = "right";
break;
default: element.style.textAlign = "left";
}
var contentRect = [self contentRectForBounds:[self bounds]];
element.style.top = _CGRectGetMinY(contentRect) + "px";
element.style.left = (_CGRectGetMinX(contentRect) - 1) + "px"; // why -1?
element.style.width = _CGRectGetWidth(contentRect) + "px";
element.style.height = _CGRectGetHeight(contentRect) + "px";
_DOMElement.appendChild(element);
window.setTimeout(function()
{
element.focus();
CPTextFieldInputOwner = self;
}, 0.0);
element.value = [self stringValue];
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
CPTextFieldInputIsActive = YES;
if (document.attachEvent)
{
CPTextFieldCachedSelectStartFunction = document.body.onselectstart;
CPTextFieldCachedDragFunction = document.body.ondrag;
document.body.ondrag = function () {};
document.body.onselectstart = function () {};
}
[self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
#endif
return YES;
}
/* @ignore */
- (BOOL)resignFirstResponder
{
[self unsetThemeState:CPThemeStateEditing];
[self _updatePlaceholderState];
[self setNeedsLayout];
#if PLATFORM(DOM)
var element = [self _inputElement];
[self setObjectValue:element.value];
CPTextFieldInputResigning = YES;
element.blur();
if (!CPTextFieldInputDidBlur)
CPTextFieldBlurFunction();
CPTextFieldInputDidBlur = NO;
CPTextFieldInputResigning = NO;
if (element.parentNode == _DOMElement)
element.parentNode.removeChild(element);
CPTextFieldInputIsActive = NO;
if (document.attachEvent)
{
CPTextFieldCachedSelectStartFunction = nil;
CPTextFieldCachedDragFunction = nil;
document.body.ondrag = CPTextFieldCachedDragFunction
document.body.onselectstart = CPTextFieldCachedSelectStartFunction
}
#endif
//post CPControlTextDidEndEditingNotification
if (_isEditing)
{
_isEditing = NO;
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
if ([self sendsActionOnEndEditing])
[self sendAction:[self action] to:[self target]];
}
[self textDidBlur:[CPNotification notificationWithName:CPTextFieldDidBlurNotification object:self userInfo:nil]];
return YES;
}
- (void)mouseDown:(CPEvent)anEvent
{
// Don't track! (ever?)
if ([self isEditable] && [self isEnabled])
return [[self window] makeFirstResponder:self];
else
return [[self nextResponder] mouseDown:anEvent];
}
- (void)mouseUp:(CPEvent)anEvent
{
if (![self isEditable] || ![self isEnabled])
[[self nextResponder] mouseUp:anEvent];
}
- (void)mouseDragged:(CPEvent)anEvent
{
if (![self isEditable] || ![self isEnabled])
[[self nextResponder] mouseDragged:anEvent];
}
- (void)keyUp:(CPEvent)anEvent
{
var oldValue = [self stringValue];
[self _setStringValue:[self _inputElement].value];
if (oldValue !== [self stringValue])
{
if (!_isEditing)
{
_isEditing = YES;
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
}
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
}
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
- (void)keyDown:(CPEvent)anEvent
{
if ([anEvent keyCode] === CPReturnKeyCode)
{
if (_isEditing && ![self wraps])
{
_isEditing = NO;
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
}
if(![self wraps])
{
[self selectText:nil];
[self sendAction:[self action] to:[self target]];
[[[self window] platformWindow] _propagateCurrentDOMEvent:NO];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
return;
}
else
{
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
return;
}
[[[self window] platformWindow] _propagateCurrentDOMEvent:NO];
}
else if ([anEvent keyCode] === CPTabKeyCode)
{
if ([anEvent modifierFlags] & CPShiftKeyMask)
[[self window] selectPreviousKeyView:self];
else
[[self window] selectNextKeyView:self];
if ([[[self window] firstResponder] respondsToSelector:@selector(selectText:)])
[[[self window] firstResponder] selectText:self];
[[[self window] platformWindow] _propagateCurrentDOMEvent:NO];
}
else
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}
- (void)textDidBlur:(CPNotification)note
{
//this looks to prevent false propagation of notifications for other objects
if([note object] != self)
return;
[[CPNotificationCenter defaultCenter] postNotification:note];
}
- (void)textDidFocus:(CPNotification)note
{
//this looks to prevent false propagation of notifications for other objects
if([note object] != self)
return;
[[CPNotificationCenter defaultCenter] postNotification:note];
}
/*!
Returns the string the text field.
*/
- (id)objectValue
{
return [super objectValue];
}
/*
@ignore
*/
- (void)_setStringValue:(id)aValue
{
[self willChangeValueForKey:@"objectValue"];
[super setObjectValue:String(aValue)];
[self _updatePlaceholderState];
[self didChangeValueForKey:@"objectValue"];
}
- (void)setObjectValue:(id)aValue
{
[super setObjectValue:aValue];
#if PLATFORM(DOM)
if (CPTextFieldInputOwner === self)
[self _inputElement].value = aValue;
#endif
[self _updatePlaceholderState];
}
- (void)_updatePlaceholderState
{
var string = [self stringValue];
if ((!string || string.length === 0) && ![self hasThemeState:CPThemeStateEditing])
[self setThemeState:CPTextFieldStatePlaceholder];
else
[self unsetThemeState:CPTextFieldStatePlaceholder];
}
/*!
Sets a placeholder string for the receiver. The placeholder is displayed until editing begins,
and after editing ends, if the text field has an empty string value
*/
-(void)setPlaceholderString:(CPString)aStringValue
{
if (_placeholderString === aStringValue)
return;
_placeholderString = aStringValue;
// Only update things if we need to show the placeholder
if ([self hasThemeState:CPTextFieldStatePlaceholder])
{
[self setNeedsLayout];
[self setNeedsDisplay:YES];
}
}
/*!
Returns the receiver's placeholder string
*/
- (CPString)placeholderString
{
return _placeholderString;
}
/*!
Size to fit has two behavior, depending on if the receiver is an editable text field or not.
For non-editable text fields (typically, a label), sizeToFit will change the frame of the
receiver to perfectly fit the current text in stringValue in the current font, and respecting
the current theme values for content-inset, min-size, and max-size.
For editable text fields, sizeToFit will ONLY change the HEIGHT of the text field. It will not
change the width of the text field. You can use setFrameSize: with the current height to set the
width, and you can get the size of a string with [CPString sizeWithFont:].
The logic behind this decision is that most of the time you do not know what content will be placed
in an editable text field, so you want to just choose a fixed width and leave it at that size.
However, since you don't know how tall it needs to be if you change the font, sizeToFit will still be
useful for making the textfield an appropriate height.
*/
- (void)sizeToFit
{
var size = [([self stringValue] || " ") sizeWithFont:[self currentValueForThemeAttribute:@"font"]],
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
minSize = [self currentValueForThemeAttribute:@"min-size"],
maxSize = [self currentValueForThemeAttribute:@"max-size"];
size.width = MAX(size.width + contentInset.left + contentInset.right, minSize.width);
size.height = MAX(size.height + contentInset.top + contentInset.bottom, minSize.height);
if (maxSize.width >= 0.0)
size.width = MIN(size.width, maxSize.width);
if (maxSize.height >= 0.0)
size.height = MIN(size.height, maxSize.height);
if ([self isEditable])
size.width = CGRectGetWidth([self frame]);
[self setFrameSize:size];
}
/*!
Select all the text in the CPTextField.
*/
- (void)selectText:(id)sender
{
#if PLATFORM(DOM)
var element = [self _inputElement];
if (([self isEditable] || [self isSelectable]))
{
if ([[self window] firstResponder] === self)
window.setTimeout(function() { element.select(); }, 0);
else
{
[[self window] makeFirstResponder:self];
window.setTimeout(function() {[self selectText:sender];}, 0);
}
}
#endif
}
- (void)selectAll:(id)sender
{
[self selectText:sender];
}
#pragma mark Setting the Delegate
- (void)setDelegate:(id)aDelegate
{
var defaultCenter = [CPNotificationCenter defaultCenter];
//unsubscribe the existing delegate if it exists
if (_delegate)
{
[defaultCenter removeObserver:_delegate name:CPControlTextDidBeginEditingNotification object:self];
[defaultCenter removeObserver:_delegate name:CPControlTextDidChangeNotification object:self];
[defaultCenter removeObserver:_delegate name:CPControlTextDidEndEditingNotification object:self];
[defaultCenter removeObserver:_delegate name:CPTextFieldDidFocusNotification object:self];
[defaultCenter removeObserver:_delegate name:CPTextFieldDidBlurNotification object:self];
}
_delegate = aDelegate;
if ([_delegate respondsToSelector:@selector(controlTextDidBeginEditing:)])
[defaultCenter
addObserver:_delegate
selector:@selector(controlTextDidBeginEditing:)
name:CPControlTextDidBeginEditingNotification
object:self];
if ([_delegate respondsToSelector:@selector(controlTextDidChange:)])
[defaultCenter
addObserver:_delegate
selector:@selector(controlTextDidChange:)
name:CPControlTextDidChangeNotification
object:self];
if ([_delegate respondsToSelector:@selector(controlTextDidEndEditing:)])
[defaultCenter
addObserver:_delegate
selector:@selector(controlTextDidEndEditing:)
name:CPControlTextDidEndEditingNotification
object:self];
if ([_delegate respondsToSelector:@selector(controlTextDidFocus:)])
[defaultCenter
addObserver:_delegate
selector:@selector(controlTextDidFocus:)
name:CPTextFieldDidFocusNotification
object:self];
if ([_delegate respondsToSelector:@selector(controlTextDidBlur:)])
[defaultCenter
addObserver:_delegate
selector:@selector(controlTextDidBlur:)
name:CPTextFieldDidBlurNotification
object:self];
}
- (id)delegate
{
return _delegate;
}
- (CGRect)contentRectForBounds:(CGRect)bounds
{
var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
if (!contentInset)
return bounds;
bounds.origin.x += contentInset.left;
bounds.origin.y += contentInset.top;
bounds.size.width -= contentInset.left + contentInset.right;
bounds.size.height -= contentInset.top + contentInset.bottom;
return bounds;
}
- (CGRect)bezelRectForBounds:(CFRect)bounds
{
var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
if (_CGInsetIsEmpty(bezelInset))
return bounds;
bounds.origin.x += bezelInset.left;
bounds.origin.y += bezelInset.top;
bounds.size.width -= bezelInset.left + bezelInset.right;
bounds.size.height -= bezelInset.top + bezelInset.bottom;
return bounds;
}
- (CGRect)focusRingRectForBounds:(CFRect)bounds
{
var focusRingInset = [self currentValueForThemeAttribute:@"focus-inset"];
if (_CGInsetIsEmpty(focusRingInset))
return bounds;
bounds.origin.x += focusRingInset.left;
bounds.origin.y += focusRingInset.top;
bounds.size.width -= focusRingInset.left + focusRingInset.right;
bounds.size.height -= focusRingInset.top + focusRingInset.bottom;
return bounds;
}
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
{
if (aName === "bezel-view")
return [self bezelRectForBounds:[self bounds]];
else if (aName === "focus-ring-view")
return [self focusRingRectForBounds:[self bounds]];
else if (aName === "content-view")
return [self contentRectForBounds:[self bounds]];
return [super rectForEphemeralSubviewNamed:aName];
}
- (CPView)createEphemeralSubviewNamed:(CPString)aName
{
if (aName === "bezel-view")
{
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
[view setHitTests:NO];
return view;
}
else if (aName === "focus-ring-view")
{
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
[view setHitTests:NO];
return view;
}
else
{
var view = [[_CPImageAndTextView alloc] initWithFrame:_CGRectMakeZero()];
//[view setImagePosition:CPNoImage];
return view;
}
return [super createEphemeralSubviewNamed:aName];
}
- (void)layoutSubviews
{
var focusRingView = [self layoutEphemeralSubviewNamed:@"focus-ring-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
if (focusRingView)
[focusRingView setBackgroundColor:[self currentValueForThemeAttribute:@"focus-ring-color"]];
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
if (bezelView)
[bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
if (contentView)
{
[contentView setHidden:[self hasThemeState:CPThemeStateEditing]];
var string = "";
if ([self hasThemeState:CPTextFieldStatePlaceholder])
string = [self placeholderString];
else
{
string = [self stringValue];
if ([self isSecure])
string = secureStringForString(string);
}
[contentView setText:string];
[contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
[contentView setFont:[self currentValueForThemeAttribute:@"font"]];
[contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
[contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
[contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
[contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
[contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
}
}
- (void)setWraps:(BOOL)aFlag
{
_wraps = aFlag;
}
- (BOOL)wraps
{
return _wraps;
}
@end
var secureStringForString = function(aString)
{
// This is true for when aString === "" and null/undefined.
if (!aString)
return "";
return Array(aString.length+1).join(CPSecureTextFieldCharacter);
}
var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
CPTextFieldIsSelectableKey = "CPTextFieldIsSelectableKey",
CPTextFieldIsBorderedKey = "CPTextFieldIsBorderedKey",
CPTextFieldIsBezeledKey = "CPTextFieldIsBezeledKey",
CPTextFieldBezelStyleKey = "CPTextFieldBezelStyleKey",
CPTextFieldDrawsBackgroundKey = "CPTextFieldDrawsBackgroundKey",
CPTextFieldLineBreakModeKey = "CPTextFieldLineBreakModeKey",
CPTextFieldBackgroundColorKey = "CPTextFieldBackgroundColorKey",
CPTextFieldPlaceholderStringKey = "CPTextFieldPlaceholderStringKey";
@implementation CPTextField (CPCoding)
/*!
Initializes the textfield with data from a coder.
@param aCoder the coder from which to read the textfield data
@return the initialized textfield
*/
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
[self setEditable:[aCoder decodeBoolForKey:CPTextFieldIsEditableKey]];
[self setSelectable:[aCoder decodeBoolForKey:CPTextFieldIsSelectableKey]];
[self setDrawsBackground:[aCoder decodeBoolForKey:CPTextFieldDrawsBackgroundKey]];
[self setTextFieldBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
[self setPlaceholderString:[aCoder decodeObjectForKey:CPTextFieldPlaceholderStringKey]];
}
return self;
}
/*!
Encodes the data of this textfield into the provided coder.
@param aCoder the coder into which the data will be written
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeBool:_isEditable forKey:CPTextFieldIsEditableKey];
[aCoder encodeBool:_isSelectable forKey:CPTextFieldIsSelectableKey];
[aCoder encodeBool:_drawsBackground forKey:CPTextFieldDrawsBackgroundKey];
[aCoder encodeObject:_textFieldBackgroundColor forKey:CPTextFieldBackgroundColorKey];
[aCoder encodeObject:_placeholderString forKey:CPTextFieldPlaceholderStringKey];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment