API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPControl.j
Go to the documentation of this file.
1 /*
2  * CPControl.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 
31 
35 
42 
46 
47 // Deprecated for use with images, use the CPImageScale constants
51 
56 
64 
68 
69 CPControlNormalBackgroundColor = "CPControlNormalBackgroundColor";
70 CPControlSelectedBackgroundColor = "CPControlSelectedBackgroundColor";
71 CPControlHighlightedBackgroundColor = "CPControlHighlightedBackgroundColor";
72 CPControlDisabledBackgroundColor = "CPControlDisabledBackgroundColor";
73 
74 CPControlTextDidBeginEditingNotification = "CPControlTextDidBeginEditingNotification";
75 CPControlTextDidChangeNotification = "CPControlTextDidChangeNotification";
76 CPControlTextDidEndEditingNotification = "CPControlTextDidEndEditingNotification";
77 
78 var CPControlBlackColor = [CPColor blackColor];
79 
86 @implementation CPControl : CPView
87 {
88  id _value;
89  CPFormatter _formatter;
90 
91  // Target-Action Support
92  id _target;
93  SEL _action;
94  int _sendActionOn;
95  BOOL _sendsActionOnEndEditing;
96 
97  // Mouse Tracking Support
98  BOOL _continuousTracking;
99  BOOL _trackingWasWithinFrame;
100  unsigned _trackingMouseDownFlags;
101  CGPoint _previousTrackingLocation;
102 }
103 
104 + (CPDictionary)themeAttributes
105 {
106  return [CPDictionary dictionaryWithObjects:[CPLeftTextAlignment,
107  CPTopVerticalTextAlignment,
108  CPLineBreakByClipping,
110  [CPFont systemFontOfSize:CPFontCurrentSystemSize],
111  [CPNull null],
112  _CGSizeMakeZero(),
113  CPImageLeft,
114  CPScaleToFit,
115  _CGSizeMakeZero(),
116  _CGSizeMake(-1.0, -1.0)]
117  forKeys:[@"alignment",
118  @"vertical-alignment",
119  @"line-break-mode",
120  @"text-color",
121  @"font",
122  @"text-shadow-color",
123  @"text-shadow-offset",
124  @"image-position",
125  @"image-scaling",
126  @"min-size",
127  @"max-size"]];
128 }
129 
130 + (void)initialize
131 {
132  if (self !== [CPControl class])
133  return;
134 
135  [self exposeBinding:@"value"];
136  [self exposeBinding:@"objectValue"];
137  [self exposeBinding:@"stringValue"];
138  [self exposeBinding:@"integerValue"];
139  [self exposeBinding:@"intValue"];
140  [self exposeBinding:@"doubleValue"];
141  [self exposeBinding:@"floatValue"];
142 
143  [self exposeBinding:@"enabled"];
144 }
145 
146 + (Class)_binderClassForBinding:(CPString)theBinding
147 {
148  if (theBinding === CPValueBinding)
149  return [_CPValueBinder class];
150 
151  return [super _binderClassForBinding:theBinding];
152 }
153 
157 - (void)_continuouslyReverseSetBinding
158 {
159  var binderClass = [[self class] _binderClassForBinding:CPValueBinding],
160  theBinding = [binderClass getBinding:CPValueBinding forObject:self];
161 
162  if ([theBinding continuouslyUpdatesValue])
163  [theBinding reverseSetValueFor:@"objectValue"];
164 }
165 
166 - (void)_reverseSetBinding
167 {
168  var binderClass = [[self class] _binderClassForBinding:CPValueBinding],
169  theBinding = [binderClass getBinding:CPValueBinding forObject:self];
170 
171  [theBinding reverseSetValueFor:@"objectValue"];
172 }
173 
174 - (id)initWithFrame:(CGRect)aFrame
175 {
176  self = [super initWithFrame:aFrame];
177 
178  if (self)
179  {
180  _sendActionOn = CPLeftMouseUpMask;
181  _trackingMouseDownFlags = 0;
182  }
183 
184  return self;
185 }
186 
192 - (void)setAction:(SEL)anAction
193 {
194  _action = anAction;
195 }
196 
200 - (SEL)action
201 {
202  return _action;
203 }
204 
210 - (void)setTarget:(id)aTarget
211 {
212  _target = aTarget;
213 }
214 
218 - (id)target
219 {
220  return _target;
221 }
222 
229 - (BOOL)sendAction:(SEL)anAction to:(id)anObject
230 {
231  [self _reverseSetBinding];
232 
233  return [CPApp sendAction:anAction to:anObject from:self];
234 }
235 
236 - (int)sendActionOn:(int)mask
237 {
238  var previousMask = _sendActionOn;
239 
240  _sendActionOn = mask;
241 
242  return previousMask;
243 }
244 
248 - (BOOL)isContinuous
249 {
250  // Some subclasses should redefine this with CPLeftMouseDraggedMask
251  return (_sendActionOn & CPPeriodicMask) !== 0;
252 }
253 
257 - (void)setContinuous:(BOOL)flag
258 {
259  // Some subclasses should redefine this with CPLeftMouseDraggedMask
260  if (flag)
261  _sendActionOn |= CPPeriodicMask;
262  else
263  _sendActionOn &= ~CPPeriodicMask;
264 }
265 
269 - (BOOL)tracksMouseOutsideOfFrame
270 {
271  return NO;
272 }
273 
274 - (void)trackMouse:(CPEvent)anEvent
275 {
276  var type = [anEvent type],
277  currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil],
278  isWithinFrame = [self tracksMouseOutsideOfFrame] || CGRectContainsPoint([self bounds], currentLocation);
279 
280  if (type === CPLeftMouseUp)
281  {
282  [self stopTracking:_previousTrackingLocation at:currentLocation mouseIsUp:YES];
283 
284  _trackingMouseDownFlags = 0;
285 
286  if (isWithinFrame)
287  [self setThemeState:CPThemeStateHovered];
288  }
289  else
290  {
291  [self unsetThemeState:CPThemeStateHovered];
292 
293  if (type === CPLeftMouseDown)
294  {
295  _trackingMouseDownFlags = [anEvent modifierFlags];
296  _continuousTracking = [self startTrackingAt:currentLocation];
297  }
298  else if (type === CPLeftMouseDragged)
299  {
300  if (isWithinFrame)
301  {
302  if (!_trackingWasWithinFrame)
303  _continuousTracking = [self startTrackingAt:currentLocation];
304 
305  else if (_continuousTracking)
306  _continuousTracking = [self continueTracking:_previousTrackingLocation at:currentLocation];
307  }
308  else
309  [self stopTracking:_previousTrackingLocation at:currentLocation mouseIsUp:NO];
310  }
311 
312  [CPApp setTarget:self selector:@selector(trackMouse:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
313  }
314 
315  if ((_sendActionOn & (1 << type)) && isWithinFrame)
316  [self sendAction:_action to:_target];
317 
318  _trackingWasWithinFrame = isWithinFrame;
319  _previousTrackingLocation = currentLocation;
320 }
321 
322 - (void)setState:(int)state
323 {
324 }
325 
326 - (int)nextState
327 {
328  return 0;
329 }
330 
336 - (void)performClick:(id)sender
337 {
338  if (![self isEnabled])
339  return;
340 
341  [self highlight:YES];
342  [self setState:[self nextState]];
343 
344  try
345  {
346  [self sendAction:[self action] to:[self target]];
347  }
348  catch (e)
349  {
350  throw e;
351  }
352  finally
353  {
355  }
356 }
357 
362 - (void)unhighlightButtonTimerDidFinish:(id)sender
363 {
364  [self highlight:NO];
365 }
366 
370 - (unsigned)mouseDownFlags
371 {
372  return _trackingMouseDownFlags;
373 }
374 
375 - (BOOL)startTrackingAt:(CGPoint)aPoint
376 {
377  [self highlight:YES];
378 
379  return (_sendActionOn & CPPeriodicMask) || (_sendActionOn & CPLeftMouseDraggedMask);
380 }
381 
382 - (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
383 {
384  return (_sendActionOn & CPPeriodicMask) || (_sendActionOn & CPLeftMouseDraggedMask);
385 }
386 
387 - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
388 {
389  if (mouseIsUp)
390  [self highlight:NO];
391  else
392  [self highlight:YES];
393 }
394 
395 - (void)mouseDown:(CPEvent)anEvent
396 {
397  if (![self isEnabled])
398  return;
399 
400  [self trackMouse:anEvent];
401 }
402 
403 - (void)mouseEntered:(CPEvent)anEvent
404 {
405  if (![self isEnabled])
406  return;
407 
408  [self setThemeState:CPThemeStateHovered];
409 }
410 
411 - (void)mouseExited:(CPEvent)anEvent
412 {
413  var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil],
414  isWithinFrame = [self tracksMouseOutsideOfFrame] || CGRectContainsPoint([self bounds], currentLocation);
415 
416  // Make sure we're not still in the frame because Cappuccino will sent mouseExited events
417  // for all of the (ephemeral) subviews of a view as well.
418  if (!isWithinFrame)
419  [self unsetThemeState:CPThemeStateHovered];
420 }
421 
425 - (id)objectValue
426 {
427  return _value;
428 }
429 
433 - (void)setObjectValue:(id)anObject
434 {
435  _value = anObject;
436 
437  [self setNeedsLayout];
438  [self setNeedsDisplay:YES];
439 }
440 
444 - (float)floatValue
445 {
446  var floatValue = parseFloat(_value, 10);
447  return isNaN(floatValue) ? 0.0 : floatValue;
448 }
449 
453 - (void)setFloatValue:(float)aValue
454 {
455  [self setObjectValue:aValue];
456 }
457 
461 - (double)doubleValue
462 {
463  var doubleValue = parseFloat(_value, 10);
464  return isNaN(doubleValue) ? 0.0 : doubleValue;
465 }
466 
470 - (void)setDoubleValue:(double)anObject
471 {
472  [self setObjectValue:anObject];
473 }
474 
478 - (int)intValue
479 {
480  var intValue = parseInt(_value, 10);
481  return isNaN(intValue) ? 0.0 : intValue;
482 }
483 
487 - (void)setIntValue:(int)anObject
488 {
489  [self setObjectValue:anObject];
490 }
491 
495 - (int)integerValue
496 {
497  var intValue = parseInt(_value, 10);
498  return isNaN(intValue) ? 0.0 : intValue;
499 }
500 
504 - (void)setIntegerValue:(int)anObject
505 {
506  [self setObjectValue:anObject];
507 }
508 
512 - (CPString)stringValue
513 {
514  if (_formatter && _value !== undefined)
515  {
516  var formattedValue = [self hasThemeState:CPThemeStateEditing] ? [_formatter editingStringForObjectValue:_value] : [_formatter stringForObjectValue:_value];
517 
518  if (formattedValue !== nil && formattedValue !== undefined)
519  return formattedValue;
520  }
521 
522  return (_value === undefined || _value === nil) ? "" : String(_value);
523 }
524 
528 - (void)setStringValue:(CPString)aString
529 {
530  // Cocoa raises an invalid parameter assertion and returns if you pass nil.
531  if (aString === nil || aString === undefined)
532  {
533  CPLog.warn("nil or undefined sent to CPControl -setStringValue");
534  return;
535  }
536 
537  var value;
538 
539  if (_formatter)
540  {
541  value = nil;
542 
543  if ([_formatter getObjectValue:AT_REF(value) forString:aString errorDescription:nil] === NO)
544  {
545  // If the given string is non-empty and doesn't work, Cocoa tries an empty string.
546  if (!aString || [_formatter getObjectValue:AT_REF(value) forString:@"" errorDescription:nil] === NO)
547  value = undefined; // Means the value is invalid
548  }
549  }
550  else
551  value = aString;
552 
553  [self setObjectValue:value];
554 }
555 
556 - (void)takeDoubleValueFrom:(id)sender
557 {
558  if ([sender respondsToSelector:@selector(doubleValue)])
559  [self setDoubleValue:[sender doubleValue]];
560 }
561 
562 
563 - (void)takeFloatValueFrom:(id)sender
564 {
565  if ([sender respondsToSelector:@selector(floatValue)])
566  [self setFloatValue:[sender floatValue]];
567 }
568 
569 - (void)takeIntegerValueFrom:(id)sender
570 {
571  if ([sender respondsToSelector:@selector(integerValue)])
572  [self setIntegerValue:[sender integerValue]];
573 }
574 
575 - (void)takeIntValueFrom:(id)sender
576 {
577  if ([sender respondsToSelector:@selector(intValue)])
578  [self setIntValue:[sender intValue]];
579 }
580 
581 - (void)takeObjectValueFrom:(id)sender
582 {
583  if ([sender respondsToSelector:@selector(objectValue)])
584  [self setObjectValue:[sender objectValue]];
585 }
586 
587 - (void)takeStringValueFrom:(id)sender
588 {
589  if ([sender respondsToSelector:@selector(stringValue)])
590  [self setStringValue:[sender stringValue]];
591 }
592 
593 - (void)textDidBeginEditing:(CPNotification)note
594 {
595  //this looks to prevent false propagation of notifications for other objects
596  if ([note object] != self)
597  return;
598 
599  [[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidBeginEditingNotification object:self userInfo:[CPDictionary dictionaryWithObject:[note object] forKey:"CPFieldEditor"]];
600 }
601 
602 - (void)textDidChange:(CPNotification)note
603 {
604  //this looks to prevent false propagation of notifications for other objects
605  if ([note object] != self)
606  return;
607 
608  [[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidChangeNotification object:self userInfo:[CPDictionary dictionaryWithObject:[note object] forKey:"CPFieldEditor"]];
609 }
610 
611 - (void)textDidEndEditing:(CPNotification)note
612 {
613  //this looks to prevent false propagation of notifications for other objects
614  if ([note object] != self)
615  return;
616 
617  [self _reverseSetBinding];
618 
619  [[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidEndEditingNotification object:self userInfo:[CPDictionary dictionaryWithObject:[note object] forKey:"CPFieldEditor"]];
620 }
621 
633 - (void)setAlignment:(CPTextAlignment)alignment
634 {
635  [self setValue:alignment forThemeAttribute:@"alignment"];
636 }
637 
641 - (CPTextAlignment)alignment
642 {
643  return [self valueForThemeAttribute:@"alignment"];
644 }
645 
655 - (void)setVerticalAlignment:(CPTextVerticalAlignment)alignment
656 {
657  [self setValue:alignment forThemeAttribute:@"vertical-alignment"];
658 }
659 
663 - (CPTextVerticalAlignment)verticalAlignment
664 {
665  return [self valueForThemeAttribute:@"vertical-alignment"];
666 }
667 
680 - (void)setLineBreakMode:(CPLineBreakMode)mode
681 {
682  [self setValue:mode forThemeAttribute:@"line-break-mode"];
683 }
684 
688 - (CPLineBreakMode)lineBreakMode
689 {
690  return [self valueForThemeAttribute:@"line-break-mode"];
691 }
692 
698 - (void)setTextColor:(CPColor)aColor
699 {
700  [self setValue:aColor forThemeAttribute:@"text-color"];
701 }
702 
706 - (CPColor)textColor
707 {
708  return [self valueForThemeAttribute:@"text-color"];
709 }
710 
714 - (void)setTextShadowColor:(CPColor)aColor
715 {
716  [self setValue:aColor forThemeAttribute:@"text-shadow-color"];
717 }
718 
722 - (CPColor)textShadowColor
723 {
724  return [self valueForThemeAttribute:@"text-shadow-color"];
725 }
726 
732 - (void)setTextShadowOffset:(CGSize)offset
733 {
734  [self setValue:offset forThemeAttribute:@"text-shadow-offset"];
735 }
736 
740 - (CGSize)textShadowOffset
741 {
742  return [self valueForThemeAttribute:@"text-shadow-offset"];
743 }
744 
748 - (void)setFont:(CPFont)aFont
749 {
750  [self setValue:aFont forThemeAttribute:@"font"];
751 }
752 
756 - (CPFont)font
757 {
758  return [self valueForThemeAttribute:@"font"];
759 }
760 
774 - (void)setImagePosition:(CPCellImagePosition)position
775 {
776  [self setValue:position forThemeAttribute:@"image-position"];
777 }
778 
782 - (CPCellImagePosition)imagePosition
783 {
784  return [self valueForThemeAttribute:@"image-position"];
785 }
786 
797 - (void)setImageScaling:(CPImageScaling)scaling
798 {
799  [self setValue:scaling forThemeAttribute:@"image-scaling"];
800 }
801 
805 - (CPImageScaling)imageScaling
806 {
807  return [self valueForThemeAttribute:@"image-scaling"];
808 }
809 
816 - (void)setEnabled:(BOOL)isEnabled
817 {
818  if (isEnabled)
819  [self unsetThemeState:CPThemeStateDisabled];
820  else
821  [self setThemeState:CPThemeStateDisabled];
822 }
823 
827 - (BOOL)isEnabled
828 {
829  return ![self hasThemeState:CPThemeStateDisabled];
830 }
831 
837 - (void)highlight:(BOOL)shouldHighlight
838 {
839  [self setHighlighted:shouldHighlight];
840 }
841 
847 - (void)setHighlighted:(BOOL)isHighlighted
848 {
849  if (isHighlighted)
850  [self setThemeState:CPThemeStateHighlighted];
851  else
852  [self unsetThemeState:CPThemeStateHighlighted];
853 }
854 
858 - (BOOL)isHighlighted
859 {
860  return [self hasThemeState:CPThemeStateHighlighted];
861 }
862 
863 @end
864 
865 var CPControlValueKey = @"CPControlValueKey",
866  CPControlControlStateKey = @"CPControlControlStateKey",
867  CPControlIsEnabledKey = @"CPControlIsEnabledKey",
868  CPControlTargetKey = @"CPControlTargetKey",
869  CPControlActionKey = @"CPControlActionKey",
870  CPControlSendActionOnKey = @"CPControlSendActionOnKey",
871  CPControlFormatterKey = @"CPControlFormatterKey",
872  CPControlSendsActionOnEndEditingKey = @"CPControlSendsActionOnEndEditingKey",
873 
874  __Deprecated__CPImageViewImageKey = @"CPImageViewImageKey";
875 
876 @implementation CPControl (CPCoding)
877 
878 /*
879  Initializes the control by unarchiving it from a coder.
880 
881  @param aCoder the coder from which to unarchive the control
882  @return the initialized control
883 */
884 - (id)initWithCoder:(CPCoder)aCoder
885 {
886  self = [super initWithCoder:aCoder];
887 
888  if (self)
889  {
890  [self setObjectValue:[aCoder decodeObjectForKey:CPControlValueKey]];
891 
892  [self setTarget:[aCoder decodeObjectForKey:CPControlTargetKey]];
893  [self setAction:[aCoder decodeObjectForKey:CPControlActionKey]];
894 
895  [self sendActionOn:[aCoder decodeIntForKey:CPControlSendActionOnKey]];
896  [self setSendsActionOnEndEditing:[aCoder decodeBoolForKey:CPControlSendsActionOnEndEditingKey]];
897 
898  [self setFormatter:[aCoder decodeObjectForKey:CPControlFormatterKey]];
899  }
900 
901  return self;
902 }
903 
904 /*
905  Archives the control to the provided coder.
906 
907  @param aCoder the coder to which the control will be archived.
908 */
909 - (void)encodeWithCoder:(CPCoder)aCoder
910 {
911  [super encodeWithCoder:aCoder];
912 
913  if (_sendsActionOnEndEditing)
914  [aCoder encodeBool:_sendsActionOnEndEditing forKey:CPControlSendsActionOnEndEditingKey];
915 
916  var objectValue = [self objectValue];
917 
918  if (objectValue !== nil)
919  [aCoder encodeObject:objectValue forKey:CPControlValueKey];
920 
921  if (_target !== nil)
922  [aCoder encodeConditionalObject:_target forKey:CPControlTargetKey];
923 
924  if (_action !== nil)
925  [aCoder encodeObject:_action forKey:CPControlActionKey];
926 
927  [aCoder encodeInt:_sendActionOn forKey:CPControlSendActionOnKey];
928 
929  if (_formatter !== nil)
930  [aCoder encodeObject:_formatter forKey:CPControlFormatterKey];
931 }
932 
933 @end
934 
935 var _CPControlSizeIdentifiers = [],
936  _CPControlCachedColorWithPatternImages = {},
937  _CPControlCachedThreePartImagePattern = {};
938 
939 _CPControlSizeIdentifiers[CPRegularControlSize] = "Regular";
940 _CPControlSizeIdentifiers[CPSmallControlSize] = "Small";
941 _CPControlSizeIdentifiers[CPMiniControlSize] = "Mini";
942 
943 function _CPControlIdentifierForControlSize(aControlSize)
944 {
945  return _CPControlSizeIdentifiers[aControlSize];
946 }
947 
948 function _CPControlColorWithPatternImage(sizes, aClassName)
949 {
950  var index = 1,
951  count = arguments.length,
952  identifier = "";
953 
954  for (; index < count; ++index)
955  identifier += arguments[index];
956 
957  var color = _CPControlCachedColorWithPatternImages[identifier];
958 
959  if (!color)
960  {
961  var bundle = [CPBundle bundleForClass:[CPControl class]];
962 
963  color = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:aClassName + "/" + identifier + ".png"] size:sizes[identifier]]];
964 
965  _CPControlCachedColorWithPatternImages[identifier] = color;
966  }
967 
968  return color;
969 }
970 
971 function _CPControlThreePartImagePattern(isVertical, sizes, aClassName)
972 {
973  var index = 2,
974  count = arguments.length,
975  identifier = "";
976 
977  for (; index < count; ++index)
978  identifier += arguments[index];
979 
980  var color = _CPControlCachedThreePartImagePattern[identifier];
981 
982  if (!color)
983  {
984  var bundle = [CPBundle bundleForClass:[CPControl class]],
985  path = aClassName + "/" + identifier;
986 
987  sizes = sizes[identifier];
988 
990  [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "0.png"] size:sizes[0]],
991  [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "1.png"] size:sizes[1]],
992  [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "2.png"] size:sizes[2]]
993  ] isVertical:isVertical]];
994 
995  _CPControlCachedThreePartImagePattern[identifier] = color;
996  }
997 
998  return color;
999 }
1000 
1002 
1006 - (CPFormatter)formatter
1007 {
1008  return _formatter;
1009 }
1010 
1014 - (void)setFormatter:(CPFormatter)aValue
1015 {
1016  _formatter = aValue;
1017 }
1018 
1022 - (BOOL)sendsActionOnEndEditing
1023 {
1024  return _sendsActionOnEndEditing;
1025 }
1026 
1030 - (void)setSendsActionOnEndEditing:(BOOL)aValue
1031 {
1032  _sendsActionOnEndEditing = aValue;
1033 }
1034 
1035 @end