API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPTextField.j
Go to the documentation of this file.
1 /*
2  * CPTextField.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 #import "../Foundation/Ref.h"
24 
25 
26 
30 CPTextFieldDidFocusNotification = @"CPTextFieldDidFocusNotification";
31 CPTextFieldDidBlurNotification = @"CPTextFieldDidBlurNotification";
32 
33 #if PLATFORM(DOM)
34 
35 var CPTextFieldDOMInputElement = nil,
36  CPTextFieldDOMPasswordInputElement = nil,
37  CPTextFieldDOMStandardInputElement = nil,
38  CPTextFieldInputOwner = nil,
39  CPTextFieldTextDidChangeValue = nil,
40  CPTextFieldInputResigning = NO,
41  CPTextFieldInputDidBlur = NO,
42  CPTextFieldInputIsActive = NO,
43  CPTextFieldCachedSelectStartFunction = nil,
44  CPTextFieldCachedDragFunction = nil,
45  CPTextFieldBlurHandler = nil,
46  CPTextFieldInputFunction = nil;
47 
48 #endif
49 
51 
52 
53 function CPTextFieldBlurFunction(anEvent, owner, domElement, inputElement, resigning, didBlurRef)
54 {
55  if (owner && domElement != inputElement.parentNode)
56  return;
57 
58  if (!resigning && [[owner window] isKeyWindow])
59  {
60  /*
61  Browsers blur text fields when a click occurs anywhere outside the text field. That is normal for browsers, but in Cocoa the key view retains focus unless the click target accepts first responder. So if we lost focus but were not told to resign and our window is still key, restore focus.
62  */
63  window.setTimeout(function()
64  {
65  inputElement.focus();
66  }, 0.0);
67  }
68 
69  CPTextFieldHandleBlur(anEvent, AT_REF(owner));
70  AT_DEREF(didBlurRef, YES);
71 
72  return true;
73 }
74 
75 function CPTextFieldHandleBlur(anEvent, ownerRef)
76 {
77  AT_DEREF(ownerRef, nil);
78 
79  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
80 }
81 
82 
84 
88 - (CPString)string
89 {
90  return self;
91 }
92 
93 @end
94 
97 
102 @implementation CPTextField : CPControl
103 {
104  BOOL _isEditing;
105 
106  BOOL _isEditable;
107  BOOL _isSelectable;
108  BOOL _isSecure;
109  BOOL _willBecomeFirstResponderByClick;
110 
111  BOOL _drawsBackground;
112 
113  CPColor _textFieldBackgroundColor;
114 
115  CPString _placeholderString;
116  CPString _stringValue;
117 
118  id _delegate;
119 
120  // NS-style Display Properties
121  CPTextFieldBezelStyle _bezelStyle;
122  BOOL _isBordered;
123  CPControlSize _controlSize;
124 }
125 
126 + (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth
127 {
128  return [self textFieldWithStringValue:aStringValue placeholder:aPlaceholder width:aWidth theme:[CPTheme defaultTheme]];
129 }
130 
131 + (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
132 {
133  var textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
134 
135  [textField setTheme:aTheme];
136  [textField setStringValue:aStringValue];
137  [textField setPlaceholderString:aPlaceholder];
138  [textField setBordered:YES];
139  [textField setBezeled:YES];
140  [textField setEditable:YES];
141 
142  [textField sizeToFit];
143 
144  return textField;
145 }
146 
147 + (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth
148 {
149  return [self roundedTextFieldWithStringValue:aStringValue placeholder:aPlaceholder width:aWidth theme:[CPTheme defaultTheme]];
150 }
151 
152 + (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
153 {
154  var textField = [[CPTextField alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
155 
156  [textField setTheme:aTheme];
157  [textField setStringValue:aStringValue];
158  [textField setPlaceholderString:aPlaceholder];
159  [textField setBezelStyle:CPTextFieldRoundedBezel];
160  [textField setBordered:YES];
161  [textField setBezeled:YES];
162  [textField setEditable:YES];
163 
164  [textField sizeToFit];
165 
166  return textField;
167 }
168 
169 + (CPTextField)labelWithTitle:(CPString)aTitle
170 {
171  return [self labelWithTitle:aTitle theme:[CPTheme defaultTheme]];
172 }
173 
174 + (CPTextField)labelWithTitle:(CPString)aTitle theme:(CPTheme)aTheme
175 {
176  var textField = [[self alloc] init];
177 
178  [textField setStringValue:aTitle];
179  [textField sizeToFit];
180 
181  return textField;
182 }
183 
184 + (CPString)defaultThemeClass
185 {
186  return "textfield";
187 }
188 
189 + (Class)_binderClassForBinding:(CPString)theBinding
190 {
191  if (theBinding === CPValueBinding)
192  return [_CPTextFieldValueBinder class];
193 
194  return [super _binderClassForBinding:theBinding];
195 }
196 
197 + (id)themeAttributes
198 {
199  return [CPDictionary dictionaryWithObjects:[_CGInsetMakeZero(), _CGInsetMake(2.0, 2.0, 2.0, 2.0), [CPNull null]]
200  forKeys:[@"bezel-inset", @"content-inset", @"bezel-color"]];
201 }
202 
203 /* @ignore */
204 #if PLATFORM(DOM)
205 - (DOMElement)_inputElement
206 {
207  if (!CPTextFieldDOMInputElement)
208  {
209  CPTextFieldDOMInputElement = document.createElement("input");
210  CPTextFieldDOMInputElement.style.position = "absolute";
211  CPTextFieldDOMInputElement.style.border = "0px";
212  CPTextFieldDOMInputElement.style.padding = "0px";
213  CPTextFieldDOMInputElement.style.margin = "0px";
214  CPTextFieldDOMInputElement.style.whiteSpace = "pre";
215  CPTextFieldDOMInputElement.style.background = "transparent";
216  CPTextFieldDOMInputElement.style.outline = "none";
217 
218  CPTextFieldBlurHandler = function(anEvent)
219  {
221  anEvent,
222  CPTextFieldInputOwner,
223  CPTextFieldInputOwner._DOMElement,
224  CPTextFieldDOMInputElement,
225  CPTextFieldInputResigning,
226  AT_REF(CPTextFieldInputDidBlur));
227  };
228 
230  {
231  CPTextFieldInputFunction = function(anEvent)
232  {
233  if (!CPTextFieldInputOwner)
234  return;
235 
236  var cappEvent = [CPEvent keyEventWithType:CPKeyUp
237  location:_CGPointMakeZero()
238  modifierFlags:0
240  windowNumber:[[CPApp keyWindow] windowNumber]
241  context:nil
242  characters:nil
244  isARepeat:NO
245  keyCode:nil];
246 
247  [CPTextFieldInputOwner keyUp:cappEvent];
248 
249  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
250  }
251 
252  CPTextFieldDOMInputElement.oninput = CPTextFieldInputFunction;
253  }
254 
255  // FIXME make this not onblur
256  CPTextFieldDOMInputElement.onblur = CPTextFieldBlurHandler;
257 
258  CPTextFieldDOMStandardInputElement = CPTextFieldDOMInputElement;
259  }
260 
262  {
263  if ([self isSecure])
264  CPTextFieldDOMInputElement.type = "password";
265  else
266  CPTextFieldDOMInputElement.type = "text";
267 
268  return CPTextFieldDOMInputElement;
269  }
270 
271  if ([self isSecure])
272  {
273  if (!CPTextFieldDOMPasswordInputElement)
274  {
275  CPTextFieldDOMPasswordInputElement = document.createElement("input");
276  CPTextFieldDOMPasswordInputElement.style.position = "absolute";
277  CPTextFieldDOMPasswordInputElement.style.border = "0px";
278  CPTextFieldDOMPasswordInputElement.style.padding = "0px";
279  CPTextFieldDOMPasswordInputElement.style.margin = "0px";
280  CPTextFieldDOMPasswordInputElement.style.whiteSpace = "pre";
281  CPTextFieldDOMPasswordInputElement.style.background = "transparent";
282  CPTextFieldDOMPasswordInputElement.style.outline = "none";
283  CPTextFieldDOMPasswordInputElement.type = "password";
284 
285  CPTextFieldDOMPasswordInputElement.onblur = CPTextFieldBlurHandler;
286  }
287 
288  CPTextFieldDOMInputElement = CPTextFieldDOMPasswordInputElement;
289  }
290  else
291  {
292  CPTextFieldDOMInputElement = CPTextFieldDOMStandardInputElement;
293  }
294 
295  return CPTextFieldDOMInputElement;
296 }
297 #endif
298 
299 - (id)initWithFrame:(CGRect)aFrame
300 {
301  self = [super initWithFrame:aFrame];
302 
303  if (self)
304  {
305  [self setStringValue:@""];
306  [self setPlaceholderString:@""];
307 
308  _sendActionOn = CPKeyUpMask | CPKeyDownMask;
309 
310  [self setValue:CPLeftTextAlignment forThemeAttribute:@"alignment"];
311  }
312 
313  return self;
314 }
315 
316 #pragma mark Controlling Editability and Selectability
317 
322 - (void)setEditable:(BOOL)shouldBeEditable
323 {
324  if (_isEditable === shouldBeEditable)
325  return;
326 
327  _isEditable = shouldBeEditable;
328 
329  if (shouldBeEditable)
330  _isSelectable = YES;
331 
332  if (_isEditable)
333  [self setThemeState:CPThemeStateEditable];
334  else
335  [self unsetThemeState:CPThemeStateEditable];
336 
337  // We only allow first responder status if the field is editable and enabled.
338  if (!shouldBeEditable && [[self window] firstResponder] === self)
339  [[self window] makeFirstResponder:nil];
340 }
341 
345 - (BOOL)isEditable
346 {
347  return _isEditable;
348 }
349 
354 - (void)setEnabled:(BOOL)shouldBeEnabled
355 {
356  [super setEnabled:shouldBeEnabled];
357 
358  // We only allow first responder status if the field is editable and enabled.
359  if (!shouldBeEnabled && [[self window] firstResponder] === self)
360  [[self window] makeFirstResponder:nil];
361 }
362 
367 - (void)setSelectable:(BOOL)aFlag
368 {
369  _isSelectable = aFlag;
370 }
371 
375 - (BOOL)isSelectable
376 {
377  return _isSelectable;
378 }
379 
384 - (void)setSecure:(BOOL)aFlag
385 {
386  _isSecure = aFlag;
387 }
388 
392 - (BOOL)isSecure
393 {
394  return _isSecure;
395 }
396 
397 // Setting the Bezel Style
402 - (void)setBezeled:(BOOL)shouldBeBezeled
403 {
404  if (shouldBeBezeled)
405  [self setThemeState:CPThemeStateBezeled];
406  else
407  [self unsetThemeState:CPThemeStateBezeled];
408 }
409 
413 - (BOOL)isBezeled
414 {
415  return [self hasThemeState:CPThemeStateBezeled];
416 }
417 
422 - (void)setBezelStyle:(CPTextFieldBezelStyle)aBezelStyle
423 {
424  var shouldBeRounded = aBezelStyle === CPTextFieldRoundedBezel;
425 
426  if (shouldBeRounded)
427  [self setThemeState:CPTextFieldStateRounded];
428  else
429  [self unsetThemeState:CPTextFieldStateRounded];
430 }
431 
435 - (CPTextFieldBezelStyle)bezelStyle
436 {
437  if ([self hasThemeState:CPTextFieldStateRounded])
439 
440  return CPTextFieldSquareBezel;
441 }
442 
447 - (void)setBordered:(BOOL)shouldBeBordered
448 {
449  if (shouldBeBordered)
450  [self setThemeState:CPThemeStateBordered];
451  else
452  [self unsetThemeState:CPThemeStateBordered];
453 }
454 
458 - (BOOL)isBordered
459 {
460  return [self hasThemeState:CPThemeStateBordered];
461 }
462 
467 - (void)setDrawsBackground:(BOOL)shouldDrawBackground
468 {
469  if (_drawsBackground == shouldDrawBackground)
470  return;
471 
472  _drawsBackground = shouldDrawBackground;
473 
474  [self setNeedsLayout];
475  [self setNeedsDisplay:YES];
476 }
477 
481 - (BOOL)drawsBackground
482 {
483  return _drawsBackground;
484 }
485 
490 - (void)setTextFieldBackgroundColor:(CPColor)aColor
491 {
492  if (_textFieldBackgroundColor == aColor)
493  return;
494 
495  _textFieldBackgroundColor = aColor;
496 
497  [self setNeedsLayout];
498  [self setNeedsDisplay:YES];
499 }
500 
504 - (CPColor)textFieldBackgroundColor
505 {
506  return _textFieldBackgroundColor;
507 }
508 
509 /* @ignore */
510 - (BOOL)acceptsFirstResponder
511 {
512  return [self isEditable] && [self isEnabled];
513 }
514 
515 /* @ignore */
516 - (BOOL)becomeFirstResponder
517 {
518  // As long as we are the first responder we need to monitor the key status of our window.
519  [self _setObserveWindowKeyNotifications:YES];
520 
521  _isEditing = NO;
522 
523  if ([[self window] isKeyWindow])
524  [self _becomeFirstKeyResponder];
525 
526  return YES;
527 }
528 
534 - (void)_becomeFirstKeyResponder
535 {
536  [self setThemeState:CPThemeStateEditing];
537 
538  [self _updatePlaceholderState];
539 
540  [self setNeedsLayout];
541 
542  _stringValue = [self stringValue];
543 
544 #if PLATFORM(DOM)
545 
546  var element = [self _inputElement],
547  font = [self currentValueForThemeAttribute:@"font"],
548  lineHeight = [font defaultLineHeightForFont];
549 
550  element.value = _stringValue;
551  element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
552 
554  element.style.font = [font cssString];
555 
556  element.style.zIndex = 1000;
557 
558  switch ([self alignment])
559  {
560  case CPCenterTextAlignment: element.style.textAlign = "center";
561  break;
562  case CPRightTextAlignment: element.style.textAlign = "right";
563  break;
564  default: element.style.textAlign = "left";
565  }
566 
567  var contentRect = [self contentRectForBounds:[self bounds]],
568  verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"];
569 
570  switch (verticalAlign)
571  {
573  var topPoint = _CGRectGetMinY(contentRect) + "px";
574  break;
575 
577  var topPoint = (_CGRectGetMidY(contentRect) - (lineHeight / 2)) + "px";
578  break;
579 
581  var topPoint = (_CGRectGetMaxY(contentRect) - lineHeight) + "px";
582  break;
583 
584  default:
585  var topPoint = _CGRectGetMinY(contentRect) + "px";
586  break;
587  }
588 
589  element.style.top = topPoint;
590  var left = _CGRectGetMinX(contentRect);
591 
592  // If the browser has a built in left padding, compensate for it. We need the input text to be exactly on top of the original text.
594  left -= 1;
595 
596  element.style.left = left + "px";
597  element.style.width = _CGRectGetWidth(contentRect) + "px";
598  element.style.height = ROUND(lineHeight) + "px";
599  element.style.lineHeight = ROUND(lineHeight) + "px";
600  element.style.verticalAlign = "top";
601  element.style.cursor = "auto";
602 
603  _DOMElement.appendChild(element);
604 
605  // The font change above doesn't work for some browsers if the element isn't already appendChild'ed.
607  element.style.font = [font cssString];
608 
609  [[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
610 
611  CPTextFieldInputIsActive = YES;
612 
613  if (document.attachEvent)
614  {
615  CPTextFieldCachedSelectStartFunction = [[self window] platformWindow]._DOMBodyElement.onselectstart;
616  CPTextFieldCachedDragFunction = [[self window] platformWindow]._DOMBodyElement.ondrag;
617 
618  [[self window] platformWindow]._DOMBodyElement.ondrag = function () {};
619  [[self window] platformWindow]._DOMBodyElement.onselectstart = function () {};
620  }
621 
622  CPTextFieldInputOwner = self;
623 
624  window.setTimeout(function()
625  {
626  /*
627  setTimeout handlers are not guaranteed to fire in the order they were initiated. This can cause a race condition when several windows with text fields are opened quickly, resulting in several instances of this timeout function being fired, perhaps out of order. So we have to check that by the time this function is fired, CPTextFieldInputOwner has not been changed to another text field in the meantime.
628  */
629  if (CPTextFieldInputOwner !== self)
630  return;
631 
632  element.focus();
633 
634  // Select the text if the textfield became first responder through keyboard interaction
635  if (!_willBecomeFirstResponderByClick)
636  [self _selectText:self immediately:YES];
637 
638  _willBecomeFirstResponderByClick = NO;
639 
640  [self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
641  }, 0.0);
642 
643 #endif
644 }
645 
646 /* @ignore */
647 - (BOOL)resignFirstResponder
648 {
649 #if PLATFORM(DOM)
650 
651  var element = [self _inputElement],
652  newValue = element.value,
653  error = @"";
654 
655  if (newValue !== _stringValue)
656  {
657  [self _setStringValue:newValue];
658  }
659 
660  // If there is a formatter, always give it a chance to reject the resignation,
661  // even if the value has not changed.
662  if ([self _valueIsValid:newValue] === NO)
663  {
664  element.focus();
665  return NO;
666  }
667 
668 #endif
669 
670  // When we are no longer the first responder we don't worry about the key status of our window anymore.
671  [self _setObserveWindowKeyNotifications:NO];
672 
673  [self _resignFirstKeyResponder];
674 
675  _isEditing = NO;
676  [self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
677 
678  if ([self sendsActionOnEndEditing])
679  [self sendAction:[self action] to:[self target]];
680 
681  [self textDidBlur:[CPNotification notificationWithName:CPTextFieldDidBlurNotification object:self userInfo:nil]];
682 
683  return YES;
684 }
685 
686 - (void)_resignFirstKeyResponder
687 {
688  [self unsetThemeState:CPThemeStateEditing];
689 
690  // Cache the formatted string
691  _stringValue = [self stringValue];
692 
693  _willBecomeFirstResponderByClick = NO;
694 
695  [self _updatePlaceholderState];
696  [self setNeedsLayout];
697 
698 #if PLATFORM(DOM)
699 
700  var element = [self _inputElement];
701 
702  CPTextFieldInputResigning = YES;
703 
704  if (CPTextFieldInputIsActive)
705  element.blur();
706 
707  if (!CPTextFieldInputDidBlur)
708  CPTextFieldBlurHandler();
709 
710  CPTextFieldInputDidBlur = NO;
711  CPTextFieldInputResigning = NO;
712 
713  if (element.parentNode == _DOMElement)
714  element.parentNode.removeChild(element);
715 
716  CPTextFieldInputIsActive = NO;
717 
718  if (document.attachEvent)
719  {
720  [[self window] platformWindow]._DOMBodyElement.ondrag = CPTextFieldCachedDragFunction;
721  [[self window] platformWindow]._DOMBodyElement.onselectstart = CPTextFieldCachedSelectStartFunction;
722 
723  CPTextFieldCachedSelectStartFunction = nil;
724  CPTextFieldCachedDragFunction = nil;
725  }
726 
727 #endif
728 }
729 
730 - (void)_setObserveWindowKeyNotifications:(BOOL)shouldObserve
731 {
732  if (shouldObserve)
733  {
734  [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowDidResignKey:) name:CPWindowDidResignKeyNotification object:[self window]];
735  [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowDidBecomeKey:) name:CPWindowDidBecomeKeyNotification object:[self window]];
736  }
737  else
738  {
739  [[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidResignKeyNotification object:[self window]];
740  [[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidBecomeKeyNotification object:[self window]];
741  }
742 }
743 
744 - (void)_windowDidResignKey:(CPNotification)aNotification
745 {
746  if (![[self window] isKeyWindow])
747  [self _resignFirstKeyResponder];
748 }
749 
750 - (void)_windowDidBecomeKey:(CPNotification)aNotification
751 {
752  if ([[self window] isKeyWindow] && [[self window] firstResponder] === self)
753  [self _becomeFirstKeyResponder];
754 }
755 
756 - (BOOL)_valueIsValid:(CPString)aValue
757 {
758 #if PLATFORM(DOM)
759 
760  var error = @"";
761 
762  if ([self _setStringValue:aValue isNewValue:NO errorDescription:AT_REF(error)] === NO)
763  {
764  var acceptInvalidValue = NO;
765 
766  if ([_delegate respondsToSelector:@selector(control:didFailToFormatString:errorDescription:)])
767  acceptInvalidValue = [_delegate control:self didFailToFormatString:aValue errorDescription:error];
768 
769  if (acceptInvalidValue === NO)
770  return NO;
771  }
772 
773 #endif
774 
775  return YES;
776 }
777 
781 - (BOOL)needsPanelToBecomeKey
782 {
783  return YES;
784 }
785 
786 - (void)mouseDown:(CPEvent)anEvent
787 {
788  // Don't track! (ever?)
789  if ([self isEditable] && [self isEnabled])
790  {
791  _willBecomeFirstResponderByClick = YES;
792  [[self window] makeFirstResponder:self];
793  }
794  else if ([self isSelectable])
795  {
796  if (document.attachEvent)
797  {
798  CPTextFieldCachedSelectStartFunction = [[self window] platformWindow]._DOMBodyElement.onselectstart;
799  CPTextFieldCachedDragFunction = [[self window] platformWindow]._DOMBodyElement.ondrag;
800 
801  [[self window] platformWindow]._DOMBodyElement.ondrag = function () {};
802  [[self window] platformWindow]._DOMBodyElement.onselectstart = function () {};
803  }
804  return [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:YES];
805  }
806  else
807  return [[self nextResponder] mouseDown:anEvent];
808 }
809 
810 - (void)mouseUp:(CPEvent)anEvent
811 {
812  if (![self isSelectable] && (![self isEditable] || ![self isEnabled]))
813  [[self nextResponder] mouseUp:anEvent];
814  else if ([self isSelectable])
815  {
816  if (document.attachEvent)
817  {
818  [[self window] platformWindow]._DOMBodyElement.ondrag = CPTextFieldCachedDragFunction;
819  [[self window] platformWindow]._DOMBodyElement.onselectstart = CPTextFieldCachedSelectStartFunction;
820 
821  CPTextFieldCachedSelectStartFunction = nil
822  CPTextFieldCachedDragFunction = nil;
823  }
824  return [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:YES];
825  }
826 }
827 
828 - (void)mouseDragged:(CPEvent)anEvent
829 {
830  if (![self isSelectable] && (![self isEditable] || ![self isEnabled]))
831  [[self nextResponder] mouseDragged:anEvent];
832  else if ([self isSelectable])
833  return [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:YES];
834 }
835 
836 - (void)keyUp:(CPEvent)anEvent
837 {
838 #if PLATFORM(DOM)
839 
840  var newValue = [self _inputElement].value;
841 
842  if (newValue !== _stringValue)
843  {
844  [self _setStringValue:newValue];
845 
846  if (!_isEditing)
847  {
848  _isEditing = YES;
849  [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
850  }
851 
852  [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
853  }
854 
855 #endif
856 
857  [[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
858 }
859 
860 - (void)keyDown:(CPEvent)anEvent
861 {
862  // CPTextField uses an HTML input element to take the input so we need to
863  // propagate the dom event so the element is updated. This has to be done
864  // before interpretKeyEvents: though so individual commands have a chance
865  // to override this (escape to clear the text in a search field for example).
866  [[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
867 
868  [self interpretKeyEvents:[anEvent]];
869 
870  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
871 }
872 
882 - (void)doCommandBySelector:(SEL)aSelector
883 {
884  if ([self respondsToSelector:aSelector])
885  [self performSelector:aSelector];
886 }
887 
888 - (void)insertNewline:(id)sender
889 {
890  var newValue = [self _inputElement].value;
891 
892  if (newValue !== _stringValue)
893  {
894  [self _setStringValue:newValue];
895  }
896 
897  if ([self _valueIsValid:_stringValue])
898  {
899  // If _isEditing == YES then the target action can also be called via
900  // resignFirstResponder, and it is possible that the target action
901  // itself will change this textfield's responder status, so start by
902  // setting the _isEditing flag to NO to prevent the target action being
903  // called twice (once below and once from resignFirstResponder).
904  if (_isEditing)
905  {
906  _isEditing = NO;
907  [self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
908  }
909 
910  // If there is no target action, or the sendAction call returns
911  // success.
912  if (![self action] || [self sendAction:[self action] to:[self target]])
913  {
914  [self selectAll:nil];
915  }
916  }
917 
918  [[[self window] platformWindow] _propagateCurrentDOMEvent:NO];
919 }
920 
921 - (void)insertNewlineIgnoringFieldEditor:(id)sender
922 {
923  [self _insertCharacterIgnoringFieldEditor:CPNewlineCharacter];
924 }
925 
926 - (void)insertTabIgnoringFieldEditor:(id)sender
927 {
928  [self _insertCharacterIgnoringFieldEditor:CPTabCharacter];
929 }
930 
931 - (void)_insertCharacterIgnoringFieldEditor:(CPString)aCharacter
932 {
933 #if PLATFORM(DOM)
934 
935  var oldValue = _stringValue,
936  range = [self selectedRange],
937  element = [self _inputElement];
938 
939  element.value = [element.value stringByReplacingCharactersInRange:[self selectedRange] withString:aCharacter];
940  [self _setStringValue:element.value];
941 
942  // NOTE: _stringValue is now the current input element value
943  if (oldValue !== _stringValue)
944  {
945  if (!_isEditing)
946  {
947  _isEditing = YES;
948  [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
949  }
950 
951  [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
952  }
953 
954 #endif
955 }
956 
957 - (void)textDidBlur:(CPNotification)note
958 {
959  // this looks to prevent false propagation of notifications for other objects
960  if ([note object] != self)
961  return;
962 
964 }
965 
966 - (void)textDidFocus:(CPNotification)note
967 {
968  // this looks to prevent false propagation of notifications for other objects
969  if ([note object] != self)
970  return;
971 
973 }
974 
975 - (void)textDidChange:(CPNotification)note
976 {
977  if ([note object] !== self)
978  return;
979 
980  [self _continuouslyReverseSetBinding];
981 
982  [super textDidChange:note];
983 }
984 
988 - (id)objectValue
989 {
990  return [super objectValue];
991 }
992 
993 /*
994  @ignore
995  Sets the internal string value without updating the value in the input element.
996  This should only be invoked when the underlying text element's value has changed.
997 */
998 - (BOOL)_setStringValue:(CPString)aValue
999 {
1000  return [self _setStringValue:aValue isNewValue:YES errorDescription:nil];
1001 }
1002 
1003 /*
1004  @ignore
1005  Sets the internal string value without updating the value in the input element.
1006  If there is a formatter and formatting fails, returns NO. Otherwise returns YES.
1007 */
1008 - (BOOL)_setStringValue:(CPString)aValue isNewValue:(BOOL)isNewValue errorDescription:(CPStringRef)anError
1009 {
1010  _stringValue = aValue;
1011 
1012  var objectValue = aValue,
1013  formatter = [self formatter],
1014  result = YES;
1015 
1016  if (formatter)
1017  {
1018  var object = nil;
1019 
1020  if ([formatter getObjectValue:AT_REF(object) forString:aValue errorDescription:anError])
1021  objectValue = object;
1022  else
1023  {
1024  objectValue = undefined; // Mark the value as invalid
1025  result = NO;
1026  }
1027 
1028  isNewValue |= objectValue !== [super objectValue];
1029  }
1030 
1031  if (isNewValue)
1032  {
1033  [self willChangeValueForKey:@"objectValue"];
1034  [super setObjectValue:objectValue];
1035  [self _updatePlaceholderState];
1036  [self didChangeValueForKey:@"objectValue"];
1037  }
1038 
1039  return result;
1040 }
1041 
1042 - (void)setObjectValue:(id)aValue
1043 {
1044  [super setObjectValue:aValue];
1045 
1046  var formatter = [self formatter];
1047 
1048  if (formatter)
1049  {
1050  // If there is a formatter, make sure the object value can be formatted successfully
1051  var formattedString = [self hasThemeState:CPThemeStateEditing] ? [formatter editingStringForObjectValue:aValue] : [formatter stringForObjectValue:aValue];
1052 
1053  if (formattedString === nil)
1054  {
1055  var value = nil;
1056 
1057  // Formatting failed, get an "empty" object by formatting an empty string.
1058  // If that fails, the value is undefined.
1059  if ([formatter getObjectValue:AT_REF(value) forString:@"" errorDescription:nil] === NO)
1060  value = undefined;
1061 
1062  [super setObjectValue:value];
1063  }
1064  }
1065 
1066  _stringValue = [self stringValue];
1067 
1068 #if PLATFORM(DOM)
1069 
1070  if (CPTextFieldInputOwner === self || [[self window] firstResponder] === self)
1071  [self _inputElement].value = _stringValue;
1072 
1073 #endif
1074 
1075  [self _updatePlaceholderState];
1076 }
1077 
1078 - (void)_updatePlaceholderState
1079 {
1080  if ((!_stringValue || _stringValue.length === 0) && ![self hasThemeState:CPThemeStateEditing])
1081  [self setThemeState:CPTextFieldStatePlaceholder];
1082  else
1083  [self unsetThemeState:CPTextFieldStatePlaceholder];
1084 }
1085 
1090 - (void)setPlaceholderString:(CPString)aStringValue
1091 {
1092  if (_placeholderString === aStringValue)
1093  return;
1094 
1095  _placeholderString = aStringValue;
1096 
1097  // Only update things if we need to show the placeholder
1098  if ([self hasThemeState:CPTextFieldStatePlaceholder])
1099  {
1100  [self setNeedsLayout];
1101  [self setNeedsDisplay:YES];
1102  }
1103 }
1104 
1108 - (CPString)placeholderString
1109 {
1110  return _placeholderString;
1111 }
1112 
1133 - (void)sizeToFit
1134 {
1135  [self setFrameSize:[self _minimumFrameSize]];
1136 }
1137 
1138 - (CGSize)_minimumFrameSize
1139 {
1140  var frameSize = [self frameSize],
1141  contentInset = [self currentValueForThemeAttribute:@"content-inset"],
1142  minSize = [self currentValueForThemeAttribute:@"min-size"],
1143  maxSize = [self currentValueForThemeAttribute:@"max-size"],
1144  lineBreakMode = [self lineBreakMode],
1145  text = (_stringValue || @" "),
1146  textSize = _CGSizeMakeCopy(frameSize),
1147  font = [self currentValueForThemeAttribute:@"font"];
1148 
1149  textSize.width -= contentInset.left + contentInset.right;
1150  textSize.height -= contentInset.top + contentInset.bottom;
1151 
1152  if (frameSize.width !== 0 &&
1153  ![self isBezeled] &&
1154  (lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping))
1155  {
1156  textSize = [text sizeWithFont:font inWidth:textSize.width];
1157  }
1158  else
1159  {
1160  textSize = [text sizeWithFont:font];
1161 
1162  // Account for possible fractional pixels at right edge
1163  textSize.width += 1;
1164  }
1165 
1166  // Account for possible fractional pixels at bottom edge
1167  textSize.height += 1;
1168 
1169  frameSize.height = textSize.height + contentInset.top + contentInset.bottom;
1170 
1171  if ([self isBezeled])
1172  {
1173  frameSize.height = MAX(frameSize.height, minSize.height);
1174 
1175  if (maxSize.width > 0.0)
1176  frameSize.width = MIN(frameSize.width, maxSize.width);
1177 
1178  if (maxSize.height > 0.0)
1179  frameSize.height = MIN(frameSize.height, maxSize.height);
1180  }
1181  else
1182  frameSize.width = textSize.width + contentInset.left + contentInset.right;
1183 
1184  frameSize.width = MAX(frameSize.width, minSize.width);
1185 
1186  return frameSize;
1187 }
1188 
1192 - (void)selectText:(id)sender
1193 {
1194  [self _selectText:sender immediately:NO];
1195 }
1196 
1197 - (void)_selectText:(id)sender immediately:(BOOL)immediately
1198 {
1199  // Selecting the text in a field makes it the first responder
1200  if (([self isEditable] || [self isSelectable]))
1201  {
1202  var wind = [self window];
1203 
1204 #if PLATFORM(DOM)
1205  var element = [self _inputElement];
1206 
1207  if ([wind firstResponder] === self)
1208  {
1209  if (immediately)
1210  element.select();
1211  else
1212  window.setTimeout(function() { element.select(); }, 0);
1213  }
1214  else if (wind !== nil && [wind makeFirstResponder:self])
1215  [self _selectText:sender immediately:immediately];
1216 #else
1217  // Even if we can't actually select the text we need to preserve the first
1218  // responder side effect.
1219  if (wind !== nil && [wind firstResponder] !== self)
1220  [wind makeFirstResponder:self];
1221 #endif
1222  }
1223 
1224 }
1225 
1226 - (void)copy:(id)sender
1227 {
1228  if (![CPPlatform isBrowser])
1229  {
1230  var selectedRange = [self selectedRange];
1231 
1232  if (selectedRange.length < 1)
1233  return;
1234 
1235  var pasteboard = [CPPasteboard generalPasteboard],
1236  stringForPasting = [_stringValue substringWithRange:selectedRange];
1237 
1238  [pasteboard declareTypes:[CPStringPboardType] owner:nil];
1239  [pasteboard setString:stringForPasting forType:CPStringPboardType];
1240  }
1241 }
1242 
1243 - (void)cut:(id)sender
1244 {
1245  if (![CPPlatform isBrowser])
1246  {
1247  [self copy:sender];
1248  [self deleteBackward:sender];
1249  }
1250  // If we don't have an oninput listener, we won't detect the change made by the cut and need to fake a key up "soon".
1252  [CPTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(keyUp:) userInfo:nil repeats:NO];
1253 }
1254 
1255 - (void)paste:(id)sender
1256 {
1257  if (![CPPlatform isBrowser])
1258  {
1259  var pasteboard = [CPPasteboard generalPasteboard];
1260 
1261  if (![[pasteboard types] containsObject:CPStringPboardType])
1262  return;
1263 
1264  [self deleteBackward:sender];
1265 
1266  var selectedRange = [self selectedRange],
1267  pasteString = [pasteboard stringForType:CPStringPboardType],
1268  newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:pasteString];
1269 
1270  [self setStringValue:newValue];
1271  [self setSelectedRange:CPMakeRange(selectedRange.location + pasteString.length, 0)];
1272  }
1273  // If we don't have an oninput listener, we won't detect the change made by the cut and need to fake a key up "soon".
1275  [CPTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(keyUp:) userInfo:nil repeats:NO];
1276 }
1277 
1278 - (CPRange)selectedRange
1279 {
1280  if ([[self window] firstResponder] !== self)
1281  return CPMakeRange(0, 0);
1282 
1283 #if PLATFORM(DOM)
1284 
1285  // we wrap this in try catch because firefox will throw an exception in certain instances
1286  try
1287  {
1288  var inputElement = [self _inputElement],
1289  selectionStart = inputElement.selectionStart,
1290  selectionEnd = inputElement.selectionEnd;
1291 
1292  if ([selectionStart isKindOfClass:CPNumber])
1293  return CPMakeRange(selectionStart, selectionEnd - selectionStart);
1294 
1295  // browsers which don't support selectionStart/selectionEnd (aka IE).
1296  var theDocument = inputElement.ownerDocument || inputElement.document,
1297  selectionRange = theDocument.selection.createRange(),
1298  range = inputElement.createTextRange();
1299 
1300  if (range.inRange(selectionRange))
1301  {
1302  range.setEndPoint('EndToStart', selectionRange);
1303  return CPMakeRange(range.text.length, selectionRange.text.length);
1304  }
1305  }
1306  catch (e)
1307  {
1308  // fall through to the return
1309  }
1310 
1311 #endif
1312 
1313  return CPMakeRange(0, 0);
1314 }
1315 
1316 - (void)setSelectedRange:(CPRange)aRange
1317 {
1318  if (![[self window] firstResponder] === self)
1319  return;
1320 
1321 #if PLATFORM(DOM)
1322 
1323  var inputElement = [self _inputElement];
1324 
1325  try
1326  {
1327  if ([inputElement.selectionStart isKindOfClass:CPNumber])
1328  {
1329  inputElement.selectionStart = aRange.location;
1330  inputElement.selectionEnd = CPMaxRange(aRange);
1331  }
1332  else
1333  {
1334  // browsers which don't support selectionStart/selectionEnd (aka IE).
1335  var theDocument = inputElement.ownerDocument || inputElement.document,
1336  existingRange = theDocument.selection.createRange(),
1337  range = inputElement.createTextRange();
1338 
1339  if (range.inRange(existingRange))
1340  {
1341  range.collapse(true);
1342  range.move('character', aRange.location);
1343  range.moveEnd('character', aRange.length);
1344  range.select();
1345  }
1346  }
1347  }
1348  catch (e)
1349  {
1350  }
1351 
1352 #endif
1353 }
1354 
1355 - (void)selectAll:(id)sender
1356 {
1357  [self selectText:sender];
1358 }
1359 
1360 - (void)deleteBackward:(id)sender
1361 {
1362  var selectedRange = [self selectedRange];
1363 
1364  if (selectedRange.length < 2)
1365  return;
1366 
1367  selectedRange.location += 1;
1368  selectedRange.length -= 1;
1369 
1370  var newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:""];
1371 
1372  [self setStringValue:newValue];
1373  [self setSelectedRange:CPMakeRange(selectedRange.location, 0)];
1374 }
1375 
1376 #pragma mark Setting the Delegate
1377 
1378 - (void)setDelegate:(id)aDelegate
1379 {
1380  var defaultCenter = [CPNotificationCenter defaultCenter];
1381 
1382  //unsubscribe the existing delegate if it exists
1383  if (_delegate)
1384  {
1385  [defaultCenter removeObserver:_delegate name:CPControlTextDidBeginEditingNotification object:self];
1386  [defaultCenter removeObserver:_delegate name:CPControlTextDidChangeNotification object:self];
1387  [defaultCenter removeObserver:_delegate name:CPControlTextDidEndEditingNotification object:self];
1388  [defaultCenter removeObserver:_delegate name:CPTextFieldDidFocusNotification object:self];
1389  [defaultCenter removeObserver:_delegate name:CPTextFieldDidBlurNotification object:self];
1390  }
1391 
1392  _delegate = aDelegate;
1393 
1394  if ([_delegate respondsToSelector:@selector(controlTextDidBeginEditing:)])
1395  [defaultCenter
1396  addObserver:_delegate
1397  selector:@selector(controlTextDidBeginEditing:)
1398  name:CPControlTextDidBeginEditingNotification
1399  object:self];
1400 
1401  if ([_delegate respondsToSelector:@selector(controlTextDidChange:)])
1402  [defaultCenter
1403  addObserver:_delegate
1404  selector:@selector(controlTextDidChange:)
1405  name:CPControlTextDidChangeNotification
1406  object:self];
1407 
1408 
1409  if ([_delegate respondsToSelector:@selector(controlTextDidEndEditing:)])
1410  [defaultCenter
1411  addObserver:_delegate
1412  selector:@selector(controlTextDidEndEditing:)
1413  name:CPControlTextDidEndEditingNotification
1414  object:self];
1415 
1416  if ([_delegate respondsToSelector:@selector(controlTextDidFocus:)])
1417  [defaultCenter
1418  addObserver:_delegate
1419  selector:@selector(controlTextDidFocus:)
1420  name:CPTextFieldDidFocusNotification
1421  object:self];
1422 
1423  if ([_delegate respondsToSelector:@selector(controlTextDidBlur:)])
1424  [defaultCenter
1425  addObserver:_delegate
1426  selector:@selector(controlTextDidBlur:)
1427  name:CPTextFieldDidBlurNotification
1428  object:self];
1429 }
1430 
1431 - (id)delegate
1432 {
1433  return _delegate;
1434 }
1435 
1436 - (CGRect)contentRectForBounds:(CGRect)bounds
1437 {
1438  var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
1439 
1440  return _CGRectInsetByInset(bounds, contentInset);
1441 }
1442 
1443 - (CGRect)bezelRectForBounds:(CGRect)bounds
1444 {
1445  var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
1446 
1447  return _CGRectInsetByInset(bounds, bezelInset);
1448 }
1449 
1450 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
1451 {
1452  if (aName === "bezel-view")
1453  return [self bezelRectForBounds:[self bounds]];
1454 
1455  else if (aName === "content-view")
1456  return [self contentRectForBounds:[self bounds]];
1457 
1458  return [super rectForEphemeralSubviewNamed:aName];
1459 }
1460 
1461 - (CPView)createEphemeralSubviewNamed:(CPString)aName
1462 {
1463  if (aName === "bezel-view")
1464  {
1465  var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
1466 
1467  [view setHitTests:NO];
1468 
1469  return view;
1470  }
1471  else
1472  {
1473  var view = [[_CPImageAndTextView alloc] initWithFrame:_CGRectMakeZero()];
1474 
1475  [view setHitTests:NO];
1476 
1477  return view;
1478  }
1479 
1480  return [super createEphemeralSubviewNamed:aName];
1481 }
1482 
1483 - (void)layoutSubviews
1484 {
1485  var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
1486  positioned:CPWindowBelow
1487  relativeToEphemeralSubviewNamed:@"content-view"];
1488 
1489  if (bezelView)
1490  [bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
1491 
1492  var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
1493  positioned:CPWindowAbove
1494  relativeToEphemeralSubviewNamed:@"bezel-view"];
1495 
1496  if (contentView)
1497  {
1498  [contentView setHidden:[self hasThemeState:CPThemeStateEditing]];
1499 
1500  var string = "";
1501 
1502  if ([self hasThemeState:CPTextFieldStatePlaceholder])
1503  string = [self placeholderString];
1504  else
1505  {
1506  string = _stringValue;
1507 
1508  if ([self isSecure])
1509  string = secureStringForString(string);
1510  }
1511 
1512  [contentView setText:string];
1513 
1514  [contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
1515  [contentView setFont:[self currentValueForThemeAttribute:@"font"]];
1516  [contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
1517  [contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
1518  [contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
1519  [contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
1520  [contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
1521  }
1522 }
1523 
1524 - (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
1525 {
1526  var count = objects.length,
1527  value = [objects[0] valueForKeyPath:aKeyPath];
1528 
1529  [self setStringValue:value];
1530  [self setPlaceholderString:@""];
1531 
1532  while (count-- > 1)
1533  if (value !== [objects[count] valueForKeyPath:aKeyPath])
1534  {
1535  [self setPlaceholderString:@"Multiple Values"];
1536  [self setStringValue:@""];
1537  }
1538 }
1539 
1540 @end
1541 
1542 var secureStringForString = function(aString)
1543 {
1544  // This is true for when aString === "" and null/undefined.
1545  if (!aString)
1546  return "";
1547 
1548  return Array(aString.length + 1).join(CPSecureTextFieldCharacter);
1549 };
1550 
1551 
1552 var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
1553  CPTextFieldIsSelectableKey = "CPTextFieldIsSelectableKey",
1554  CPTextFieldIsBorderedKey = "CPTextFieldIsBorderedKey",
1555  CPTextFieldIsBezeledKey = "CPTextFieldIsBezeledKey",
1556  CPTextFieldBezelStyleKey = "CPTextFieldBezelStyleKey",
1557  CPTextFieldDrawsBackgroundKey = "CPTextFieldDrawsBackgroundKey",
1558  CPTextFieldLineBreakModeKey = "CPTextFieldLineBreakModeKey",
1559  CPTextFieldAlignmentKey = "CPTextFieldAlignmentKey",
1560  CPTextFieldBackgroundColorKey = "CPTextFieldBackgroundColorKey",
1561  CPTextFieldPlaceholderStringKey = "CPTextFieldPlaceholderStringKey";
1562 
1563 @implementation CPTextField (CPCoding)
1564 
1570 - (id)initWithCoder:(CPCoder)aCoder
1571 {
1572  self = [super initWithCoder:aCoder];
1573 
1574  if (self)
1575  {
1576  [self setEditable:[aCoder decodeBoolForKey:CPTextFieldIsEditableKey]];
1577  [self setSelectable:[aCoder decodeBoolForKey:CPTextFieldIsSelectableKey]];
1578 
1579  [self setDrawsBackground:[aCoder decodeBoolForKey:CPTextFieldDrawsBackgroundKey]];
1580 
1581  [self setTextFieldBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
1582 
1583  [self setLineBreakMode:[aCoder decodeIntForKey:CPTextFieldLineBreakModeKey]];
1584  [self setAlignment:[aCoder decodeIntForKey:CPTextFieldAlignmentKey]];
1585 
1586  [self setPlaceholderString:[aCoder decodeObjectForKey:CPTextFieldPlaceholderStringKey]];
1587  }
1588 
1589  return self;
1590 }
1591 
1596 - (void)encodeWithCoder:(CPCoder)aCoder
1597 {
1598  [super encodeWithCoder:aCoder];
1599 
1600  [aCoder encodeBool:_isEditable forKey:CPTextFieldIsEditableKey];
1601  [aCoder encodeBool:_isSelectable forKey:CPTextFieldIsSelectableKey];
1602 
1603  [aCoder encodeBool:_drawsBackground forKey:CPTextFieldDrawsBackgroundKey];
1604 
1605  [aCoder encodeObject:_textFieldBackgroundColor forKey:CPTextFieldBackgroundColorKey];
1606 
1607  [aCoder encodeInt:[self lineBreakMode] forKey:CPTextFieldLineBreakModeKey];
1608  [aCoder encodeInt:[self alignment] forKey:CPTextFieldAlignmentKey];
1609 
1610  [aCoder encodeObject:_placeholderString forKey:CPTextFieldPlaceholderStringKey];
1611 }
1612 
1613 @end
1614 @implementation _CPTextFieldValueBinder : CPBinder
1615 {
1616  id __doxygen__;
1617 }
1618 
1619 - (void)_updatePlaceholdersWithOptions:(CPDictionary)options
1620 {
1621  [super _updatePlaceholdersWithOptions:options];
1622 
1623  [self _setPlaceholder:@"Multiple Values" forMarker:CPMultipleValuesMarker isDefault:YES];
1624  [self _setPlaceholder:@"No Selection" forMarker:CPNoSelectionMarker isDefault:YES];
1625  [self _setPlaceholder:@"Not Applicable" forMarker:CPNotApplicableMarker isDefault:YES];
1626  [self _setPlaceholder:@"" forMarker:CPNullMarker isDefault:YES];
1627 }
1628 
1629 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
1630 {
1631  [_source setPlaceholderString:aValue];
1632  [_source setObjectValue:nil];
1633 }
1634 
1635 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
1636 {
1637  [_source setObjectValue:aValue];
1638 }
1639 
1640 @end
1641