API 0.9.5
AppKit/CPTextField.j
Go to the documentation of this file.
00001 /*
00002  * CPTextField.j
00003  * AppKit
00004  *
00005  * Created by Francisco Tolmasky.
00006  * Copyright 2008, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 #import "../Foundation/Ref.h"
00024 
00025 
00026 
00027 CPTextFieldSquareBezel          = 0;    
00028 CPTextFieldRoundedBezel         = 1;    
00030 CPTextFieldDidFocusNotification = @"CPTextFieldDidFocusNotification";
00031 CPTextFieldDidBlurNotification  = @"CPTextFieldDidBlurNotification";
00032 
00033 #if PLATFORM(DOM)
00034 
00035 var CPTextFieldDOMInputElement = nil,
00036     CPTextFieldDOMPasswordInputElement = nil,
00037     CPTextFieldDOMStandardInputElement = nil,
00038     CPTextFieldInputOwner = nil,
00039     CPTextFieldTextDidChangeValue = nil,
00040     CPTextFieldInputResigning = NO,
00041     CPTextFieldInputDidBlur = NO,
00042     CPTextFieldInputIsActive = NO,
00043     CPTextFieldCachedSelectStartFunction = nil,
00044     CPTextFieldCachedDragFunction = nil,
00045     CPTextFieldBlurFunction = nil;
00046 
00047 #endif
00048 
00049 var CPSecureTextFieldCharacter = "\u2022";
00050 
00051 @implementation CPString (CPTextFieldAdditions)
00052 
00056 - (CPString)string
00057 {
00058     return self;
00059 }
00060 
00061 @end
00062 
00063 CPTextFieldStateRounded     = CPThemeState("rounded");
00064 CPTextFieldStatePlaceholder = CPThemeState("placeholder");
00065 
00070 @implementation CPTextField : CPControl
00071 {
00072     BOOL                    _isEditing;
00073 
00074     BOOL                    _isEditable;
00075     BOOL                    _isSelectable;
00076     BOOL                    _isSecure;
00077     BOOL                    _willBecomeFirstResponderByClick;
00078 
00079     BOOL                    _drawsBackground;
00080 
00081     CPColor                 _textFieldBackgroundColor;
00082 
00083     CPString                _placeholderString;
00084     CPString                _stringValue;
00085 
00086     id                      _delegate;
00087 
00088     // NS-style Display Properties
00089     CPTextFieldBezelStyle   _bezelStyle;
00090     BOOL                    _isBordered;
00091     CPControlSize           _controlSize;
00092 }
00093 
00094 + (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth
00095 {
00096     return [self textFieldWithStringValue:aStringValue placeholder:aPlaceholder width:aWidth theme:[CPTheme defaultTheme]];
00097 }
00098 
00099 + (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
00100 {
00101     var textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
00102 
00103     [textField setTheme:aTheme];
00104     [textField setStringValue:aStringValue];
00105     [textField setPlaceholderString:aPlaceholder];
00106     [textField setBordered:YES];
00107     [textField setBezeled:YES];
00108     [textField setEditable:YES];
00109 
00110     [textField sizeToFit];
00111 
00112     return textField;
00113 }
00114 
00115 + (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth
00116 {
00117     return [self roundedTextFieldWithStringValue:aStringValue placeholder:aPlaceholder width:aWidth theme:[CPTheme defaultTheme]];
00118 }
00119 
00120 + (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
00121 {
00122     var textField = [[CPTextField alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
00123 
00124     [textField setTheme:aTheme];
00125     [textField setStringValue:aStringValue];
00126     [textField setPlaceholderString:aPlaceholder];
00127     [textField setBezelStyle:CPTextFieldRoundedBezel];
00128     [textField setBordered:YES];
00129     [textField setBezeled:YES];
00130     [textField setEditable:YES];
00131 
00132     [textField sizeToFit];
00133 
00134     return textField;
00135 }
00136 
00137 + (CPTextField)labelWithTitle:(CPString)aTitle
00138 {
00139     return [self labelWithTitle:aTitle theme:[CPTheme defaultTheme]];
00140 }
00141 
00142 + (CPTextField)labelWithTitle:(CPString)aTitle theme:(CPTheme)aTheme
00143 {
00144     var textField = [[self alloc] init];
00145 
00146     [textField setStringValue:aTitle];
00147     [textField sizeToFit];
00148 
00149     return textField;
00150 }
00151 
00152 + (CPString)defaultThemeClass
00153 {
00154     return "textfield";
00155 }
00156 
00157 + (Class)_binderClassForBinding:(CPString)theBinding
00158 {
00159     if (theBinding === CPValueBinding)
00160         return [_CPTextFieldValueBinder class];
00161 
00162     return [super _binderClassForBinding:theBinding];
00163 }
00164 
00165 + (id)themeAttributes
00166 {
00167     return [CPDictionary dictionaryWithObjects:[_CGInsetMakeZero(), _CGInsetMake(2.0, 2.0, 2.0, 2.0), [CPNull null]]
00168                                        forKeys:[@"bezel-inset", @"content-inset", @"bezel-color"]];
00169 }
00170 
00171 /* @ignore */
00172 #if PLATFORM(DOM)
00173 - (DOMElement)_inputElement
00174 {
00175     if (!CPTextFieldDOMInputElement)
00176     {
00177         CPTextFieldDOMInputElement = document.createElement("input");
00178         CPTextFieldDOMInputElement.style.position = "absolute";
00179         CPTextFieldDOMInputElement.style.border = "0px";
00180         CPTextFieldDOMInputElement.style.padding = "0px";
00181         CPTextFieldDOMInputElement.style.margin = "0px";
00182         CPTextFieldDOMInputElement.style.whiteSpace = "pre";
00183         CPTextFieldDOMInputElement.style.background = "transparent";
00184         CPTextFieldDOMInputElement.style.outline = "none";
00185 
00186         CPTextFieldBlurFunction = function(anEvent)
00187         {
00188             if (CPTextFieldInputOwner && CPTextFieldInputOwner._DOMElement != CPTextFieldDOMInputElement.parentNode)
00189                 return;
00190 
00191             if (!CPTextFieldInputResigning)
00192             {
00193                 [[CPTextFieldInputOwner window] makeFirstResponder:nil];
00194                 return;
00195             }
00196 
00197             CPTextFieldHandleBlur(anEvent, CPTextFieldDOMInputElement);
00198             CPTextFieldInputDidBlur = YES;
00199 
00200             return true;
00201         }
00202 
00203         CPTextFieldHandleBlur = function(anEvent)
00204         {
00205             CPTextFieldInputOwner = nil;
00206 
00207             [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
00208         }
00209 
00210         //FIXME make this not onblur
00211         CPTextFieldDOMInputElement.onblur = CPTextFieldBlurFunction;
00212 
00213         CPTextFieldDOMStandardInputElement = CPTextFieldDOMInputElement;
00214     }
00215 
00216     if (CPFeatureIsCompatible(CPInputTypeCanBeChangedFeature))
00217     {
00218         if ([self isSecure])
00219             CPTextFieldDOMInputElement.type = "password";
00220         else
00221             CPTextFieldDOMInputElement.type = "text";
00222 
00223         return CPTextFieldDOMInputElement;
00224     }
00225 
00226     if ([self isSecure])
00227     {
00228         if (!CPTextFieldDOMPasswordInputElement)
00229         {
00230             CPTextFieldDOMPasswordInputElement = document.createElement("input");
00231             CPTextFieldDOMPasswordInputElement.style.position = "absolute";
00232             CPTextFieldDOMPasswordInputElement.style.border = "0px";
00233             CPTextFieldDOMPasswordInputElement.style.padding = "0px";
00234             CPTextFieldDOMPasswordInputElement.style.margin = "0px";
00235             CPTextFieldDOMPasswordInputElement.style.whiteSpace = "pre";
00236             CPTextFieldDOMPasswordInputElement.style.background = "transparent";
00237             CPTextFieldDOMPasswordInputElement.style.outline = "none";
00238             CPTextFieldDOMPasswordInputElement.type = "password";
00239 
00240             CPTextFieldDOMPasswordInputElement.onblur = CPTextFieldBlurFunction;
00241         }
00242 
00243         CPTextFieldDOMInputElement = CPTextFieldDOMPasswordInputElement;
00244     }
00245     else
00246     {
00247         CPTextFieldDOMInputElement = CPTextFieldDOMStandardInputElement;
00248     }
00249 
00250     return CPTextFieldDOMInputElement;
00251 }
00252 #endif
00253 
00254 - (id)initWithFrame:(CGRect)aFrame
00255 {
00256     self = [super initWithFrame:aFrame];
00257 
00258     if (self)
00259     {
00260         [self setStringValue:@""];
00261         [self setPlaceholderString:@""];
00262 
00263         _sendActionOn = CPKeyUpMask | CPKeyDownMask;
00264 
00265         [self setValue:CPLeftTextAlignment forThemeAttribute:@"alignment"];
00266     }
00267 
00268     return self;
00269 }
00270 
00271 #pragma mark Controlling Editability and Selectability
00272 
00277 - (void)setEditable:(BOOL)shouldBeEditable
00278 {
00279     if (_isEditable === shouldBeEditable)
00280         return;
00281 
00282     _isEditable = shouldBeEditable;
00283 
00284     if (shouldBeEditable)
00285         _isSelectable = YES;
00286 
00287     // We only allow first responder status if the field is editable and enabled.
00288     if (!shouldBeEditable && [[self window] firstResponder] === self)
00289         [[self window] makeFirstResponder:nil];
00290 }
00291 
00295 - (BOOL)isEditable
00296 {
00297     return _isEditable;
00298 }
00299 
00304 - (void)setEnabled:(BOOL)shouldBeEnabled
00305 {
00306     [super setEnabled:shouldBeEnabled];
00307 
00308     // We only allow first responder status if the field is editable and enabled.
00309     if (!shouldBeEnabled && [[self window] firstResponder] === self)
00310         [[self window] makeFirstResponder:nil];
00311 }
00312 
00317 - (void)setSelectable:(BOOL)aFlag
00318 {
00319     _isSelectable = aFlag;
00320 }
00321 
00325 - (BOOL)isSelectable
00326 {
00327     return _isSelectable;
00328 }
00329 
00334 - (void)setSecure:(BOOL)aFlag
00335 {
00336     _isSecure = aFlag;
00337 }
00338 
00342 - (BOOL)isSecure
00343 {
00344     return _isSecure;
00345 }
00346 
00347 // Setting the Bezel Style
00352 - (void)setBezeled:(BOOL)shouldBeBezeled
00353 {
00354     if (shouldBeBezeled)
00355         [self setThemeState:CPThemeStateBezeled];
00356     else
00357         [self unsetThemeState:CPThemeStateBezeled];
00358 }
00359 
00363 - (BOOL)isBezeled
00364 {
00365     return [self hasThemeState:CPThemeStateBezeled];
00366 }
00367 
00372 - (void)setBezelStyle:(CPTextFieldBezelStyle)aBezelStyle
00373 {
00374     var shouldBeRounded = aBezelStyle === CPTextFieldRoundedBezel;
00375 
00376     if (shouldBeRounded)
00377         [self setThemeState:CPTextFieldStateRounded];
00378     else
00379         [self unsetThemeState:CPTextFieldStateRounded];
00380 }
00381 
00385 - (CPTextFieldBezelStyle)bezelStyle
00386 {
00387     if ([self hasThemeState:CPTextFieldStateRounded])
00388         return CPTextFieldRoundedBezel;
00389 
00390     return CPTextFieldSquareBezel;
00391 }
00392 
00397 - (void)setBordered:(BOOL)shouldBeBordered
00398 {
00399     if (shouldBeBordered)
00400         [self setThemeState:CPThemeStateBordered];
00401     else
00402         [self unsetThemeState:CPThemeStateBordered];
00403 }
00404 
00408 - (BOOL)isBordered
00409 {
00410     return [self hasThemeState:CPThemeStateBordered];
00411 }
00412 
00417 - (void)setDrawsBackground:(BOOL)shouldDrawBackground
00418 {
00419     if (_drawsBackground == shouldDrawBackground)
00420         return;
00421 
00422     _drawsBackground = shouldDrawBackground;
00423 
00424     [self setNeedsLayout];
00425     [self setNeedsDisplay:YES];
00426 }
00427 
00431 - (BOOL)drawsBackground
00432 {
00433     return _drawsBackground;
00434 }
00435 
00440 - (void)setTextFieldBackgroundColor:(CPColor)aColor
00441 {
00442     if (_textFieldBackgroundColor == aColor)
00443         return;
00444 
00445     _textFieldBackgroundColor = aColor;
00446 
00447     [self setNeedsLayout];
00448     [self setNeedsDisplay:YES];
00449 }
00450 
00454 - (CPColor)textFieldBackgroundColor
00455 {
00456     return _textFieldBackgroundColor;
00457 }
00458 
00459 /* @ignore */
00460 - (BOOL)acceptsFirstResponder
00461 {
00462     return [self isEditable] && [self isEnabled];
00463 }
00464 
00465 /* @ignore */
00466 - (BOOL)becomeFirstResponder
00467 {
00468 #if PLATFORM(DOM)
00469     if (CPTextFieldInputOwner && [CPTextFieldInputOwner window] !== [self window])
00470         [[CPTextFieldInputOwner window] makeFirstResponder:nil];
00471 #endif
00472 
00473     [self setThemeState:CPThemeStateEditing];
00474 
00475     [self _updatePlaceholderState];
00476 
00477     [self setNeedsLayout];
00478 
00479     _isEditing = NO;
00480     _stringValue = [self stringValue];
00481 
00482 #if PLATFORM(DOM)
00483 
00484     var element = [self _inputElement],
00485         font = [self currentValueForThemeAttribute:@"font"];
00486 
00487     // generate the font metric
00488     [font _getMetrics];
00489 
00490     element.value = _stringValue;
00491     element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
00492     element.style.font = [font cssString];
00493     element.style.zIndex = 1000;
00494 
00495     switch ([self alignment])
00496     {
00497         case CPCenterTextAlignment: element.style.textAlign = "center";
00498                                     break;
00499         case CPRightTextAlignment:  element.style.textAlign = "right";
00500                                     break;
00501         default:                    element.style.textAlign = "left";
00502     }
00503 
00504     var contentRect = [self contentRectForBounds:[self bounds]],
00505         verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"];
00506 
00507     switch (verticalAlign)
00508     {
00509         case CPTopVerticalTextAlignment:
00510             var topPoint = (_CGRectGetMinY(contentRect) + 1) + "px"; // for the same reason we have a -1 for the left, we also have a + 1 here
00511             break;
00512 
00513         case CPCenterVerticalTextAlignment:
00514             var topPoint = (_CGRectGetMidY(contentRect) - (font._lineHeight / 2) + 1) + "px";
00515             break;
00516 
00517         case CPBottomVerticalTextAlignment:
00518             var topPoint = (_CGRectGetMaxY(contentRect) - font._lineHeight) + "px";
00519             break;
00520 
00521         default:
00522             var topPoint = (_CGRectGetMinY(contentRect) + 1) + "px";
00523             break;
00524     }
00525 
00526     element.style.top = topPoint;
00527     element.style.left = (_CGRectGetMinX(contentRect) - 1) + "px"; // why -1?
00528     element.style.width = _CGRectGetWidth(contentRect) + "px";
00529     element.style.height = font._lineHeight + "px"; // private ivar for the line height of the DOM text at this particular size
00530 
00531     _DOMElement.appendChild(element);
00532 
00533     window.setTimeout(function()
00534     {
00535         element.focus();
00536 
00537         // Select the text if the textfield became first responder through keyboard interaction
00538         if (!_willBecomeFirstResponderByClick)
00539             [self selectText:self];
00540 
00541         _willBecomeFirstResponderByClick = NO;
00542 
00543         [self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
00544         CPTextFieldInputOwner = self;
00545     }, 0.0);
00546 
00547     [[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
00548 
00549     CPTextFieldInputIsActive = YES;
00550 
00551     if (document.attachEvent)
00552     {
00553         CPTextFieldCachedSelectStartFunction = [[self window] platformWindow]._DOMBodyElement.onselectstart;
00554         CPTextFieldCachedDragFunction = [[self window] platformWindow]._DOMBodyElement.ondrag;
00555 
00556         [[self window] platformWindow]._DOMBodyElement.ondrag = function () {};
00557         [[self window] platformWindow]._DOMBodyElement.onselectstart = function () {};
00558     }
00559 #endif
00560 
00561     return YES;
00562 }
00563 
00564 /* @ignore */
00565 - (BOOL)resignFirstResponder
00566 {
00567     [self unsetThemeState:CPThemeStateEditing];
00568 
00569 #if PLATFORM(DOM)
00570 
00571     var element = [self _inputElement],
00572         newValue = element.value,
00573         error = @"";
00574 
00575     if (newValue !== _stringValue)
00576     {
00577         [self _setStringValue:newValue];
00578     }
00579 
00580     // If there is a formatter, always give it a chance to reject the resignation,
00581     // even if the value has not changed.
00582     if ([self _valueIsValid:newValue] === NO)
00583     {
00584         [self setThemeState:CPThemeStateEditing];
00585         element.focus();
00586         return NO;
00587     }
00588 
00589 #endif
00590 
00591     // Cache the formatted string
00592     _stringValue = [self stringValue];
00593 
00594     _willBecomeFirstResponderByClick = NO;
00595 
00596     [self _updatePlaceholderState];
00597 
00598     [self setNeedsLayout];
00599 
00600 #if PLATFORM(DOM)
00601 
00602     CPTextFieldInputResigning = YES;
00603 
00604     if (CPTextFieldInputIsActive)
00605         element.blur();
00606 
00607     if (!CPTextFieldInputDidBlur)
00608         CPTextFieldBlurFunction();
00609 
00610     CPTextFieldInputDidBlur = NO;
00611     CPTextFieldInputResigning = NO;
00612 
00613     if (element.parentNode == _DOMElement)
00614         element.parentNode.removeChild(element);
00615 
00616     CPTextFieldInputIsActive = NO;
00617 
00618     if (document.attachEvent)
00619     {
00620         [[self window] platformWindow]._DOMBodyElement.ondrag = CPTextFieldCachedDragFunction;
00621         [[self window] platformWindow]._DOMBodyElement.onselectstart = CPTextFieldCachedSelectStartFunction;
00622 
00623         CPTextFieldCachedSelectStartFunction = nil;
00624         CPTextFieldCachedDragFunction = nil;
00625     }
00626 
00627 #endif
00628 
00629     // post CPControlTextDidEndEditingNotification
00630     if (_isEditing)
00631     {
00632         _isEditing = NO;
00633         [self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
00634 
00635         if ([self sendsActionOnEndEditing])
00636             [self sendAction:[self action] to:[self target]];
00637     }
00638 
00639     [self textDidBlur:[CPNotification notificationWithName:CPTextFieldDidBlurNotification object:self userInfo:nil]];
00640 
00641     return YES;
00642 }
00643 
00644 - (BOOL)_valueIsValid:(CPString)aValue
00645 {
00646 #if PLATFORM(DOM)
00647 
00648     var error = @"";
00649 
00650     if ([self _setStringValue:aValue isNewValue:NO errorDescription:AT_REF(error)] === NO)
00651     {
00652         var acceptInvalidValue = NO;
00653 
00654         if ([_delegate respondsToSelector:@selector(control:didFailToFormatString:errorDescription:)])
00655             acceptInvalidValue = [_delegate control:self didFailToFormatString:[self _inputElement] errorDescription:error];
00656 
00657         if (acceptInvalidValue === NO)
00658             return NO;
00659     }
00660 
00661 #endif
00662 
00663     return YES;
00664 }
00665 
00669 - (BOOL)needsPanelToBecomeKey
00670 {
00671     return YES;
00672 }
00673 
00674 - (void)mouseDown:(CPEvent)anEvent
00675 {
00676     // Don't track! (ever?)
00677     if ([self isEditable] && [self isEnabled])
00678     {
00679         _willBecomeFirstResponderByClick = YES;
00680         [[self window] makeFirstResponder:self];
00681     }
00682     else if ([self isSelectable])
00683     {
00684         if (document.attachEvent)
00685         {
00686             CPTextFieldCachedSelectStartFunction = [[self window] platformWindow]._DOMBodyElement.onselectstart;
00687             CPTextFieldCachedDragFunction = [[self window] platformWindow]._DOMBodyElement.ondrag;
00688 
00689             [[self window] platformWindow]._DOMBodyElement.ondrag = function () {};
00690             [[self window] platformWindow]._DOMBodyElement.onselectstart = function () {};
00691         }
00692         return [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:YES];
00693     }
00694     else
00695         return [[self nextResponder] mouseDown:anEvent];
00696 }
00697 
00698 - (void)mouseUp:(CPEvent)anEvent
00699 {
00700     if (![self isSelectable] && (![self isEditable] || ![self isEnabled]))
00701         [[self nextResponder] mouseUp:anEvent];
00702     else if ([self isSelectable])
00703     {
00704         if (document.attachEvent)
00705         {
00706             [[self window] platformWindow]._DOMBodyElement.ondrag = CPTextFieldCachedDragFunction;
00707             [[self window] platformWindow]._DOMBodyElement.onselectstart = CPTextFieldCachedSelectStartFunction;
00708 
00709             CPTextFieldCachedSelectStartFunction = nil
00710             CPTextFieldCachedDragFunction = nil;
00711         }
00712         return [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:YES];
00713     }
00714 }
00715 
00716 - (void)mouseDragged:(CPEvent)anEvent
00717 {
00718     if (![self isSelectable] && (![self isEditable] || ![self isEnabled]))
00719         [[self nextResponder] mouseDragged:anEvent];
00720     else if ([self isSelectable])
00721         return [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:YES];
00722 }
00723 
00724 - (void)keyUp:(CPEvent)anEvent
00725 {
00726 #if PLATFORM(DOM)
00727 
00728     var newValue = [self _inputElement].value;
00729 
00730     if (newValue !== _stringValue)
00731     {
00732         [self _setStringValue:newValue];
00733 
00734         if (!_isEditing)
00735         {
00736             _isEditing = YES;
00737             [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
00738         }
00739 
00740         [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
00741     }
00742 
00743 #endif
00744 
00745     [[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
00746 }
00747 
00748 - (void)keyDown:(CPEvent)anEvent
00749 {
00750     // CPTextField uses an HTML input element to take the input so we need to
00751     // propagate the dom event so the element is updated. This has to be done
00752     // before interpretKeyEvents: though so individual commands have a chance
00753     // to override this (escape to clear the text in a search field for example).
00754     [[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
00755 
00756     [self interpretKeyEvents:[anEvent]];
00757 
00758     [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
00759 }
00760 
00770 - (void)doCommandBySelector:(SEL)aSelector
00771 {
00772     if ([self respondsToSelector:aSelector])
00773         [self performSelector:aSelector];
00774 }
00775 
00776 - (void)insertNewline:(id)sender
00777 {
00778     var newValue = [self _inputElement].value;
00779 
00780     if (newValue !== _stringValue)
00781     {
00782         [self _setStringValue:newValue];
00783     }
00784 
00785     if ([self _valueIsValid:_stringValue])
00786     {
00787         // If _isEditing == YES then the target action can also be called via
00788         // resignFirstResponder, and it is possible that the target action
00789         // itself will change this textfield's responder status, so start by
00790         // setting the _isEditing flag to NO to prevent the target action being
00791         // called twice (once below and once from resignFirstResponder).
00792         if (_isEditing)
00793         {
00794             _isEditing = NO;
00795             [self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
00796         }
00797 
00798         // If there is no target action, or the sendAction call returns
00799         // success.
00800         if (![self action] || [self sendAction:[self action] to:[self target]])
00801         {
00802             [self selectAll:nil];
00803         }
00804     }
00805 
00806     [[[self window] platformWindow] _propagateCurrentDOMEvent:NO];
00807 }
00808 
00809 - (void)insertNewlineIgnoringFieldEditor:(id)sender
00810 {
00811     [self _insertCharacterIgnoringFieldEditor:CPNewlineCharacter];
00812 }
00813 
00814 - (void)insertTabIgnoringFieldEditor:(id)sender
00815 {
00816     [self _insertCharacterIgnoringFieldEditor:CPTabCharacter];
00817 }
00818 
00819 - (void)_insertCharacterIgnoringFieldEditor:(CPString)aCharacter
00820 {
00821 #if PLATFORM(DOM)
00822 
00823     var oldValue = _stringValue,
00824         range = [self selectedRange],
00825         element = [self _inputElement];
00826 
00827     element.value = [element.value stringByReplacingCharactersInRange:[self selectedRange] withString:aCharacter];
00828     [self _setStringValue:element.value];
00829 
00830     // NOTE: _stringValue is now the current input element value
00831     if (oldValue !== _stringValue)
00832     {
00833         if (!_isEditing)
00834         {
00835             _isEditing = YES;
00836             [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
00837         }
00838 
00839         [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
00840     }
00841 
00842 #endif
00843 }
00844 
00845 - (void)textDidBlur:(CPNotification)note
00846 {
00847     // this looks to prevent false propagation of notifications for other objects
00848     if ([note object] != self)
00849         return;
00850 
00851     [[CPNotificationCenter defaultCenter] postNotification:note];
00852 }
00853 
00854 - (void)textDidFocus:(CPNotification)note
00855 {
00856     // this looks to prevent false propagation of notifications for other objects
00857     if ([note object] != self)
00858         return;
00859 
00860     [[CPNotificationCenter defaultCenter] postNotification:note];
00861 }
00862 
00863 - (void)textDidChange:(CPNotification)note
00864 {
00865     if ([note object] !== self)
00866         return;
00867 
00868     [self _continuouslyReverseSetBinding];
00869 
00870     [super textDidChange:note];
00871 }
00872 
00876 - (id)objectValue
00877 {
00878     return [super objectValue];
00879 }
00880 
00881 /*
00882     @ignore
00883     Sets the internal string value without updating the value in the input element.
00884     This should only be invoked when the underlying text element's value has changed.
00885 */
00886 - (BOOL)_setStringValue:(CPString)aValue
00887 {
00888     return [self _setStringValue:aValue isNewValue:YES errorDescription:nil];
00889 }
00890 
00891 /*
00892     @ignore
00893     Sets the internal string value without updating the value in the input element.
00894     If there is a formatter and formatting fails, returns NO. Otherwise returns YES.
00895 */
00896 - (BOOL)_setStringValue:(CPString)aValue isNewValue:(BOOL)isNewValue errorDescription:(CPStringRef)anError
00897 {
00898     _stringValue = aValue;
00899 
00900     var objectValue = aValue,
00901         formatter = [self formatter],
00902         result = YES;
00903 
00904     if (formatter)
00905     {
00906         var object = nil;
00907 
00908         if ([formatter getObjectValue:AT_REF(object) forString:aValue errorDescription:anError])
00909             objectValue = object;
00910         else
00911         {
00912             objectValue = undefined;  // Mark the value as invalid
00913             result = NO;
00914         }
00915 
00916         isNewValue |= objectValue !== [super objectValue];
00917     }
00918 
00919     if (isNewValue)
00920     {
00921         [self willChangeValueForKey:@"objectValue"];
00922         [super setObjectValue:objectValue];
00923         [self _updatePlaceholderState];
00924         [self didChangeValueForKey:@"objectValue"];
00925     }
00926 
00927     return result;
00928 }
00929 
00930 - (void)setObjectValue:(id)aValue
00931 {
00932     [super setObjectValue:aValue];
00933 
00934     var formatter = [self formatter];
00935 
00936     if (formatter)
00937     {
00938         // If there is a formatter, make sure the object value can be formatted successfully
00939         var formattedString = [self hasThemeState:CPThemeStateEditing] ? [formatter editingStringForObjectValue:aValue] : [formatter stringForObjectValue:aValue];
00940 
00941         if (formattedString === nil)
00942         {
00943             var value = nil;
00944 
00945             // Formatting failed, get an "empty" object by formatting an empty string.
00946             // If that fails, the value is undefined.
00947             if ([formatter getObjectValue:AT_REF(value) forString:@"" errorDescription:nil] === NO)
00948                 value = undefined;
00949 
00950             [super setObjectValue:value];
00951         }
00952     }
00953 
00954     _stringValue = [self stringValue];
00955 
00956 #if PLATFORM(DOM)
00957 
00958     if (CPTextFieldInputOwner === self || [[self window] firstResponder] === self)
00959         [self _inputElement].value = _stringValue;
00960 
00961 #endif
00962 
00963     [self _updatePlaceholderState];
00964 }
00965 
00966 - (void)_updatePlaceholderState
00967 {
00968     if ((!_stringValue || _stringValue.length === 0) && ![self hasThemeState:CPThemeStateEditing])
00969         [self setThemeState:CPTextFieldStatePlaceholder];
00970     else
00971         [self unsetThemeState:CPTextFieldStatePlaceholder];
00972 }
00973 
00978 - (void)setPlaceholderString:(CPString)aStringValue
00979 {
00980     if (_placeholderString === aStringValue)
00981         return;
00982 
00983     _placeholderString = aStringValue;
00984 
00985     // Only update things if we need to show the placeholder
00986     if ([self hasThemeState:CPTextFieldStatePlaceholder])
00987     {
00988         [self setNeedsLayout];
00989         [self setNeedsDisplay:YES];
00990     }
00991 }
00992 
00996 - (CPString)placeholderString
00997 {
00998     return _placeholderString;
00999 }
01000 
01021 - (void)sizeToFit
01022 {
01023     [self setFrameSize:[self _minimumFrameSize]];
01024 }
01025 
01026 - (CGSize)_minimumFrameSize
01027 {
01028     var frameSize = [self frameSize],
01029         contentInset = [self currentValueForThemeAttribute:@"content-inset"],
01030         minSize = [self currentValueForThemeAttribute:@"min-size"],
01031         maxSize = [self currentValueForThemeAttribute:@"max-size"],
01032         lineBreakMode = [self lineBreakMode],
01033         text = (_stringValue || @" "),
01034         textSize = _CGSizeMakeCopy(frameSize),
01035         font = [self currentValueForThemeAttribute:@"font"];
01036 
01037     textSize.width -= contentInset.left + contentInset.right;
01038     textSize.height -= contentInset.top + contentInset.bottom;
01039 
01040     if (frameSize.width !== 0 &&
01041         ![self isBezeled]     &&
01042         (lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping))
01043     {
01044         textSize = [text sizeWithFont:font inWidth:textSize.width];
01045     }
01046     else
01047         textSize = [text sizeWithFont:font];
01048 
01049     frameSize.height = textSize.height + contentInset.top + contentInset.bottom;
01050 
01051     if ([self isBezeled])
01052     {
01053         frameSize.height = MAX(frameSize.height, minSize.height);
01054 
01055         if (maxSize.width > 0.0)
01056             frameSize.width = MIN(frameSize.width, maxSize.width);
01057 
01058         if (maxSize.height > 0.0)
01059             frameSize.height = MIN(frameSize.height, maxSize.height);
01060     }
01061     else
01062         frameSize.width = textSize.width + contentInset.left + contentInset.right;
01063 
01064     frameSize.width = MAX(frameSize.width, minSize.width);
01065 
01066     return frameSize;
01067 }
01068 
01072 - (void)selectText:(id)sender
01073 {
01074     // FIXME Should this really make the text field the first responder?
01075 
01076     if (([self isEditable] || [self isSelectable]))
01077     {
01078 #if PLATFORM(DOM)
01079         var element = [self _inputElement];
01080 
01081         if ([[self window] firstResponder] === self)
01082             window.setTimeout(function() { element.select(); }, 0);
01083         else if ([self window] !== nil && [[self window] makeFirstResponder:self])
01084             window.setTimeout(function() {[self selectText:sender];}, 0);
01085 #else
01086         // Even if we can't actually select the text we need to preserve the first
01087         // responder side effect.
01088         if ([self window] !== nil && [[self window] firstResponder] !== self)
01089             [[self window] makeFirstResponder:self];
01090 #endif
01091     }
01092 }
01093 
01094 - (void)copy:(id)sender
01095 {
01096     if (![CPPlatform isBrowser])
01097     {
01098         var selectedRange = [self selectedRange];
01099 
01100         if (selectedRange.length < 1)
01101             return;
01102 
01103         var pasteboard = [CPPasteboard generalPasteboard],
01104             stringForPasting = [_stringValue substringWithRange:selectedRange];
01105 
01106         [pasteboard declareTypes:[CPStringPboardType] owner:nil];
01107         [pasteboard setString:stringForPasting forType:CPStringPboardType];
01108     }
01109 }
01110 
01111 - (void)cut:(id)sender
01112 {
01113     if (![CPPlatform isBrowser])
01114     {
01115         [self copy:sender];
01116         [self deleteBackward:sender];
01117     }
01118     else
01119         [CPTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(keyUp:) userInfo:nil repeats:NO];
01120 }
01121 
01122 - (void)paste:(id)sender
01123 {
01124     if (![CPPlatform isBrowser])
01125     {
01126         var pasteboard = [CPPasteboard generalPasteboard];
01127 
01128         if (![[pasteboard types] containsObject:CPStringPboardType])
01129             return;
01130 
01131         [self deleteBackward:sender];
01132 
01133         var selectedRange = [self selectedRange],
01134             pasteString = [pasteboard stringForType:CPStringPboardType],
01135             newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:pasteString];
01136 
01137         [self setStringValue:newValue];
01138         [self setSelectedRange:CPMakeRange(selectedRange.location + pasteString.length, 0)];
01139     }
01140     else
01141         [CPTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(keyUp:) userInfo:nil repeats:NO];
01142 }
01143 
01144 - (CPRange)selectedRange
01145 {
01146     if ([[self window] firstResponder] !== self)
01147         return CPMakeRange(0, 0);
01148 
01149 #if PLATFORM(DOM)
01150 
01151     // we wrap this in try catch because firefox will throw an exception in certain instances
01152     try
01153     {
01154         var inputElement = [self _inputElement],
01155             selectionStart = inputElement.selectionStart,
01156             selectionEnd = inputElement.selectionEnd;
01157 
01158         if ([selectionStart isKindOfClass:CPNumber])
01159             return CPMakeRange(selectionStart, selectionEnd - selectionStart);
01160 
01161         // browsers which don't support selectionStart/selectionEnd (aka IE).
01162         var theDocument = inputElement.ownerDocument || inputElement.document,
01163             selectionRange = theDocument.selection.createRange(),
01164             range = inputElement.createTextRange();
01165 
01166         if (range.inRange(selectionRange))
01167         {
01168             range.setEndPoint('EndToStart', selectionRange);
01169             return CPMakeRange(range.text.length, selectionRange.text.length);
01170         }
01171     }
01172     catch (e)
01173     {
01174         // fall through to the return
01175     }
01176 
01177 #endif
01178 
01179     return CPMakeRange(0, 0);
01180 }
01181 
01182 - (void)setSelectedRange:(CPRange)aRange
01183 {
01184     if (![[self window] firstResponder] === self)
01185         return;
01186 
01187 #if PLATFORM(DOM)
01188 
01189     var inputElement = [self _inputElement];
01190 
01191     try
01192     {
01193         if ([inputElement.selectionStart isKindOfClass:CPNumber])
01194         {
01195             inputElement.selectionStart = aRange.location;
01196             inputElement.selectionEnd = CPMaxRange(aRange);
01197         }
01198         else
01199         {
01200             // browsers which don't support selectionStart/selectionEnd (aka IE).
01201             var theDocument = inputElement.ownerDocument || inputElement.document,
01202                 existingRange = theDocument.selection.createRange(),
01203                 range = inputElement.createTextRange();
01204 
01205             if (range.inRange(existingRange))
01206             {
01207                 range.collapse(true);
01208                 range.move('character', aRange.location);
01209                 range.moveEnd('character', aRange.length);
01210                 range.select();
01211             }
01212         }
01213     }
01214     catch (e)
01215     {
01216     }
01217 
01218 #endif
01219 }
01220 
01221 - (void)selectAll:(id)sender
01222 {
01223     [self selectText:sender];
01224 }
01225 
01226 - (void)deleteBackward:(id)sender
01227 {
01228     var selectedRange = [self selectedRange];
01229 
01230     if (selectedRange.length < 2)
01231          return;
01232 
01233     selectedRange.location += 1;
01234     selectedRange.length -= 1;
01235 
01236     var newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:""];
01237 
01238     [self setStringValue:newValue];
01239     [self setSelectedRange:CPMakeRange(selectedRange.location, 0)];
01240 }
01241 
01242 #pragma mark Setting the Delegate
01243 
01244 - (void)setDelegate:(id)aDelegate
01245 {
01246     var defaultCenter = [CPNotificationCenter defaultCenter];
01247 
01248     //unsubscribe the existing delegate if it exists
01249     if (_delegate)
01250     {
01251         [defaultCenter removeObserver:_delegate name:CPControlTextDidBeginEditingNotification object:self];
01252         [defaultCenter removeObserver:_delegate name:CPControlTextDidChangeNotification object:self];
01253         [defaultCenter removeObserver:_delegate name:CPControlTextDidEndEditingNotification object:self];
01254         [defaultCenter removeObserver:_delegate name:CPTextFieldDidFocusNotification object:self];
01255         [defaultCenter removeObserver:_delegate name:CPTextFieldDidBlurNotification object:self];
01256     }
01257 
01258     _delegate = aDelegate;
01259 
01260     if ([_delegate respondsToSelector:@selector(controlTextDidBeginEditing:)])
01261         [defaultCenter
01262             addObserver:_delegate
01263                selector:@selector(controlTextDidBeginEditing:)
01264                    name:CPControlTextDidBeginEditingNotification
01265                  object:self];
01266 
01267     if ([_delegate respondsToSelector:@selector(controlTextDidChange:)])
01268         [defaultCenter
01269             addObserver:_delegate
01270                selector:@selector(controlTextDidChange:)
01271                    name:CPControlTextDidChangeNotification
01272                  object:self];
01273 
01274 
01275     if ([_delegate respondsToSelector:@selector(controlTextDidEndEditing:)])
01276         [defaultCenter
01277             addObserver:_delegate
01278                selector:@selector(controlTextDidEndEditing:)
01279                    name:CPControlTextDidEndEditingNotification
01280                  object:self];
01281 
01282     if ([_delegate respondsToSelector:@selector(controlTextDidFocus:)])
01283         [defaultCenter
01284             addObserver:_delegate
01285                selector:@selector(controlTextDidFocus:)
01286                    name:CPTextFieldDidFocusNotification
01287                  object:self];
01288 
01289     if ([_delegate respondsToSelector:@selector(controlTextDidBlur:)])
01290         [defaultCenter
01291             addObserver:_delegate
01292                selector:@selector(controlTextDidBlur:)
01293                    name:CPTextFieldDidBlurNotification
01294                  object:self];
01295 }
01296 
01297 - (id)delegate
01298 {
01299     return _delegate;
01300 }
01301 
01302 - (CGRect)contentRectForBounds:(CGRect)bounds
01303 {
01304     var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
01305 
01306     if (!contentInset)
01307         return bounds;
01308 
01309     bounds.origin.x += contentInset.left;
01310     bounds.origin.y += contentInset.top;
01311     bounds.size.width -= contentInset.left + contentInset.right;
01312     bounds.size.height -= contentInset.top + contentInset.bottom;
01313 
01314     return bounds;
01315 }
01316 
01317 - (CGRect)bezelRectForBounds:(CGRect)bounds
01318 {
01319     var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
01320 
01321     if (_CGInsetIsEmpty(bezelInset))
01322         return bounds;
01323 
01324     bounds.origin.x += bezelInset.left;
01325     bounds.origin.y += bezelInset.top;
01326     bounds.size.width -= bezelInset.left + bezelInset.right;
01327     bounds.size.height -= bezelInset.top + bezelInset.bottom;
01328 
01329     return bounds;
01330 }
01331 
01332 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
01333 {
01334     if (aName === "bezel-view")
01335         return [self bezelRectForBounds:[self bounds]];
01336 
01337     else if (aName === "content-view")
01338         return [self contentRectForBounds:[self bounds]];
01339 
01340     return [super rectForEphemeralSubviewNamed:aName];
01341 }
01342 
01343 - (CPView)createEphemeralSubviewNamed:(CPString)aName
01344 {
01345     if (aName === "bezel-view")
01346     {
01347         var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
01348 
01349         [view setHitTests:NO];
01350 
01351         return view;
01352     }
01353     else
01354     {
01355         var view = [[_CPImageAndTextView alloc] initWithFrame:_CGRectMakeZero()];
01356         //[view setImagePosition:CPNoImage];
01357 
01358         [view setHitTests:NO];
01359 
01360         return view;
01361     }
01362 
01363     return [super createEphemeralSubviewNamed:aName];
01364 }
01365 
01366 - (void)layoutSubviews
01367 {
01368     var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
01369                                            positioned:CPWindowBelow
01370                       relativeToEphemeralSubviewNamed:@"content-view"];
01371 
01372     if (bezelView)
01373         [bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
01374 
01375     var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
01376                                              positioned:CPWindowAbove
01377                         relativeToEphemeralSubviewNamed:@"bezel-view"];
01378 
01379     if (contentView)
01380     {
01381         [contentView setHidden:[self hasThemeState:CPThemeStateEditing]];
01382 
01383         var string = "";
01384 
01385         if ([self hasThemeState:CPTextFieldStatePlaceholder])
01386             string = [self placeholderString];
01387         else
01388         {
01389             string = _stringValue;
01390 
01391             if ([self isSecure])
01392                 string = secureStringForString(string);
01393         }
01394 
01395         [contentView setText:string];
01396 
01397         [contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
01398         [contentView setFont:[self currentValueForThemeAttribute:@"font"]];
01399         [contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
01400         [contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
01401         [contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
01402         [contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
01403         [contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
01404     }
01405 }
01406 
01407 - (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
01408 {
01409     var count = objects.length,
01410         value = [objects[0] valueForKeyPath:aKeyPath];
01411 
01412     [self setStringValue:value];
01413     [self setPlaceholderString:@""];
01414 
01415     while (count-- > 1)
01416         if (value !== [objects[count] valueForKeyPath:aKeyPath])
01417         {
01418             [self setPlaceholderString:@"Multiple Values"];
01419             [self setStringValue:@""];
01420         }
01421 }
01422 
01423 @end
01424 
01425 var secureStringForString = function(aString)
01426 {
01427     // This is true for when aString === "" and null/undefined.
01428     if (!aString)
01429         return "";
01430 
01431     return Array(aString.length + 1).join(CPSecureTextFieldCharacter);
01432 }
01433 
01434 
01435 var CPTextFieldIsEditableKey            = "CPTextFieldIsEditableKey",
01436     CPTextFieldIsSelectableKey          = "CPTextFieldIsSelectableKey",
01437     CPTextFieldIsBorderedKey            = "CPTextFieldIsBorderedKey",
01438     CPTextFieldIsBezeledKey             = "CPTextFieldIsBezeledKey",
01439     CPTextFieldBezelStyleKey            = "CPTextFieldBezelStyleKey",
01440     CPTextFieldDrawsBackgroundKey       = "CPTextFieldDrawsBackgroundKey",
01441     CPTextFieldLineBreakModeKey         = "CPTextFieldLineBreakModeKey",
01442     CPTextFieldAlignmentKey             = "CPTextFieldAlignmentKey",
01443     CPTextFieldBackgroundColorKey       = "CPTextFieldBackgroundColorKey",
01444     CPTextFieldPlaceholderStringKey     = "CPTextFieldPlaceholderStringKey";
01445 
01446 @implementation CPTextField (CPCoding)
01447 
01453 - (id)initWithCoder:(CPCoder)aCoder
01454 {
01455     self = [super initWithCoder:aCoder];
01456 
01457     if (self)
01458     {
01459         [self setEditable:[aCoder decodeBoolForKey:CPTextFieldIsEditableKey]];
01460         [self setSelectable:[aCoder decodeBoolForKey:CPTextFieldIsSelectableKey]];
01461 
01462         [self setDrawsBackground:[aCoder decodeBoolForKey:CPTextFieldDrawsBackgroundKey]];
01463 
01464         [self setTextFieldBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
01465 
01466         [self setLineBreakMode:[aCoder decodeIntForKey:CPTextFieldLineBreakModeKey]];
01467         [self setAlignment:[aCoder decodeIntForKey:CPTextFieldAlignmentKey]];
01468 
01469         [self setPlaceholderString:[aCoder decodeObjectForKey:CPTextFieldPlaceholderStringKey]];
01470     }
01471 
01472     return self;
01473 }
01474 
01479 - (void)encodeWithCoder:(CPCoder)aCoder
01480 {
01481     [super encodeWithCoder:aCoder];
01482 
01483     [aCoder encodeBool:_isEditable forKey:CPTextFieldIsEditableKey];
01484     [aCoder encodeBool:_isSelectable forKey:CPTextFieldIsSelectableKey];
01485 
01486     [aCoder encodeBool:_drawsBackground forKey:CPTextFieldDrawsBackgroundKey];
01487 
01488     [aCoder encodeObject:_textFieldBackgroundColor forKey:CPTextFieldBackgroundColorKey];
01489 
01490     [aCoder encodeInt:[self lineBreakMode] forKey:CPTextFieldLineBreakModeKey];
01491     [aCoder encodeInt:[self alignment] forKey:CPTextFieldAlignmentKey];
01492 
01493     [aCoder encodeObject:_placeholderString forKey:CPTextFieldPlaceholderStringKey];
01494 }
01495 
01496 @end
01497 @implementation _CPTextFieldValueBinder : CPBinder
01498 {
01499     id __doxygen__;
01500 }
01501 
01502 - (void)setValueFor:(CPString)theBinding
01503 {
01504     var destination = [_info objectForKey:CPObservedObjectKey],
01505         keyPath = [_info objectForKey:CPObservedKeyPathKey],
01506         options = [_info objectForKey:CPOptionsKey],
01507         newValue = [destination valueForKeyPath:keyPath],
01508         isPlaceholder = CPIsControllerMarker(newValue);
01509 
01510     if (isPlaceholder)
01511     {
01512         switch (newValue)
01513         {
01514             case CPMultipleValuesMarker:
01515                 newValue = [options objectForKey:CPMultipleValuesPlaceholderBindingOption] || @"Multiple Values";
01516                 break;
01517 
01518             case CPNoSelectionMarker:
01519                 newValue = [options objectForKey:CPNoSelectionPlaceholderBindingOption] || @"No Selection";
01520                 break;
01521 
01522             case CPNotApplicableMarker:
01523                 if ([options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
01524                     [CPException raise:CPGenericException
01525                                 reason:@"can't transform non applicable key on: "+_source+" value: "+newValue];
01526 
01527                 newValue = [options objectForKey:CPNotApplicablePlaceholderBindingOption] || @"Not Applicable";
01528                 break;
01529 
01530             case CPNullMarker:
01531                 newValue = [options objectForKey:CPNullPlaceholderBindingOption] || @"";
01532                 break;
01533         }
01534 
01535         [_source setPlaceholderString:newValue];
01536         [_source setObjectValue:nil];
01537     }
01538     else
01539     {
01540         newValue = [self transformValue:newValue withOptions:options];
01541         [_source setObjectValue:newValue];
01542     }
01543 }
01544 
01545 @end
01546 
 All Classes Files Functions Variables Defines