![]() |
API 0.9.5
|
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