API  1.0.0
CPColor.j
Go to the documentation of this file.
1 /*
2  * CPColor.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 
24 
25 
26 
31 
36 
38 
39 var _redComponent = 0,
40  _greenComponent = 1,
41  _blueComponent = 2,
42  _alphaCompnent = 3;
43 
44 var _hueComponent = 0,
45  _saturationComponent = 1,
46  _brightnessComponent = 2;
47 
48 var cachedBlackColor,
49  cachedRedColor,
50  cachedGreenColor,
51  cachedBlueColor,
52  cachedYellowColor,
53  cachedGrayColor,
54  cachedLightGrayColor,
55  cachedDarkGrayColor,
56  cachedWhiteColor,
57  cachedBrownColor,
58  cachedCyanColor,
59  cachedMagentaColor,
60  cachedOrangeColor,
61  cachedPurpleColor,
62  cachedShadowColor,
63  cachedClearColor,
64  cachedThemeColor;
65 
67 
77 @implementation CPColor : CPObject <CPTheme>
78 {
79  CPArray _components;
80 
81  CPImage _patternImage;
82  CPString _cssString;
83 }
84 
85 
86 #pragma mark -
87 #pragma mark Theming
88 
89 + (CPString)defaultThemeClass
90 {
91  return "color";
92 }
93 
94 + (CPDictionary)themeAttributes
95 {
96  return @{
97  @"alternate-selected-control-color": [CPNull null],
98  @"secondary-selected-control-color": [CPNull null],
99  @"selected-text-background-color": [CPNull null],
100  @"selected-text-inactive-background-color": [CPNull null],
101  @"css-based": NO
102  };
103 }
104 
105 
106 #pragma mark -
107 #pragma mark Static methods
108 
122 + (CPColor)colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
123 {
124  return [[CPColor alloc] _initWithRGBA:[MAX(0.0, MIN(1.0, red)), MAX(0.0, MIN(1.0, green)), MAX(0.0, MIN(1.0, blue)), MAX(0.0, MIN(1.0, alpha))]];
125 }
126 
142 + (CPColor)colorWithCalibratedRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
143 {
144  return [self colorWithRed:red green:green blue:blue alpha:alpha];
145 }
146 
147 
157 + (CPColor)colorWithWhite:(float)white alpha:(float)alpha
158 {
159  return [[CPColor alloc] _initWithRGBA:[white, white, white, alpha]];
160 }
161 
173 + (CPColor)colorWithCalibratedWhite:(float)white alpha:(float)alpha
174 {
175  return [self colorWithWhite:white alpha:alpha];
176 }
177 
191 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness
192 {
193  return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0];
194 }
195 
201 + (CPColor)colorWithCalibratedHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
202 {
203  return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
204 }
205 
220 + (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
221 {
222  // Clamp values.
223  hue = MAX(MIN(hue, 1.0), 0.0);
224  saturation = MAX(MIN(saturation, 1.0), 0.0);
225  brightness = MAX(MIN(brightness, 1.0), 0.0);
226 
227  if (saturation === 0.0)
228  return [CPColor colorWithCalibratedWhite:brightness alpha:alpha];
229 
230  var f = (hue * 360) % 60,
231  p = (brightness * (1 - saturation)),
232  q = (brightness * (60 - saturation * f)) / 60,
233  t = (brightness * (60 - saturation * (60 - f))) / 60,
234  b = brightness;
235 
236  switch (FLOOR(hue * 6))
237  {
238  case 0:
239  case 6:
240  return [CPColor colorWithCalibratedRed:b green:t blue:p alpha:alpha];
241  case 1:
242  return [CPColor colorWithCalibratedRed:q green:b blue:p alpha:alpha];
243  case 2:
244  return [CPColor colorWithCalibratedRed:p green:b blue:t alpha:alpha];
245  case 3:
246  return [CPColor colorWithCalibratedRed:p green:q blue:b alpha:alpha];
247  case 4:
248  return [CPColor colorWithCalibratedRed:t green:p blue:b alpha:alpha];
249  case 5:
250  return [CPColor colorWithCalibratedRed:b green:p blue:q alpha:alpha];
251  }
252 }
253 
264 + (CPColor)colorWithHexString:(string)hex
265 {
266  var rgba = hexToRGB(hex);
267  return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null;
268 }
269 
274 + (CPColor)colorWithSRGBRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
275 {
276  // TODO If Cappuccino is ported to a colorspace aware platform, this color should be in
277  // sRGBColorSpace.
278  return [self colorWithRed:red green:green blue:blue alpha:alpha];
279 }
280 
284 + (CPColor)blackColor
285 {
286  if (!cachedBlackColor)
287  cachedBlackColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0]];
288 
289  return cachedBlackColor;
290 }
291 
295 + (CPColor)blueColor
296 {
297  if (!cachedBlueColor)
298  cachedBlueColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 1.0, 1.0]];
299 
300  return cachedBlueColor;
301 }
302 
306 + (CPColor)darkGrayColor
307 {
308  if (!cachedDarkGrayColor)
309  cachedDarkGrayColor = [CPColor colorWithCalibratedWhite:1.0 / 3.0 alpha:1.0];
310 
311  return cachedDarkGrayColor;
312 }
313 
317 + (CPColor)grayColor
318 {
319  if (!cachedGrayColor)
320  cachedGrayColor = [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0];
321 
322  return cachedGrayColor;
323 }
324 
328 + (CPColor)greenColor
329 {
330  if (!cachedGreenColor)
331  cachedGreenColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 0.0, 1.0]];
332 
333  return cachedGreenColor;
334 }
335 
339 + (CPColor)lightGrayColor
340 {
341  if (!cachedLightGrayColor)
342  cachedLightGrayColor = [CPColor colorWithCalibratedWhite:2.0 / 3.0 alpha:1.0];
343 
344  return cachedLightGrayColor;
345 }
346 
350 + (CPColor)redColor
351 {
352  if (!cachedRedColor)
353  cachedRedColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 0.0, 1.0]];
354 
355  return cachedRedColor;
356 }
357 
361 + (CPColor)whiteColor
362 {
363  if (!cachedWhiteColor)
364  cachedWhiteColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 1.0, 1.0]];
365 
366  return cachedWhiteColor;
367 }
368 
372 + (CPColor)yellowColor
373 {
374  if (!cachedYellowColor)
375  cachedYellowColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 0.0, 1.0]];
376 
377  return cachedYellowColor;
378 }
379 
383 + (CPColor)brownColor
384 {
385  if (!cachedBrownColor)
386  cachedBrownColor = [[CPColor alloc] _initWithRGBA:[0.6, 0.4, 0.2, 1.0]];
387 
388  return cachedBrownColor;
389 }
390 
394 + (CPColor)cyanColor
395 {
396  if (!cachedCyanColor)
397  cachedCyanColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 1.0, 1.0]];
398 
399  return cachedCyanColor;
400 }
401 
405 + (CPColor)magentaColor
406 {
407  if (!cachedMagentaColor)
408  cachedMagentaColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 1.0, 1.0]];
409 
410  return cachedMagentaColor;
411 }
412 
416 + (CPColor)orangeColor
417 {
418  if (!cachedOrangeColor)
419  cachedOrangeColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.5, 0.0, 1.0]];
420 
421  return cachedOrangeColor;
422 }
423 
427 + (CPColor)purpleColor
428 {
429  if (!cachedPurpleColor)
430  cachedPurpleColor = [[CPColor alloc] _initWithRGBA:[0.5, 0.0, 0.5, 1.0]];
431 
432  return cachedPurpleColor;
433 }
434 
439 + (CPColor)shadowColor
440 {
441  if (!cachedShadowColor)
442  cachedShadowColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0 / 3.0]];
443 
444  return cachedShadowColor;
445 }
446 
451 + (CPColor)clearColor
452 {
453  if (!cachedClearColor)
454  cachedClearColor = [self colorWithCalibratedWhite:0.0 alpha:0.0];
455 
456  return cachedClearColor;
457 }
458 
459 + (CPColor)_cachedThemeColor
460 {
461  if (!cachedThemeColor)
462  cachedThemeColor = [self colorWithCalibratedWhite:0.0 alpha:0.0];
463 
464  return cachedThemeColor;
465 }
466 
467 + (CPColor)alternateSelectedControlColor
468 {
469  return [[self _cachedThemeColor] valueForThemeAttribute:@"alternate-selected-control-color"];
470 }
471 
472 + (CPColor)secondarySelectedControlColor
473 {
474  return [[self _cachedThemeColor] valueForThemeAttribute:@"secondary-selected-control-color"];
475 }
476 
482 + (CPColor)colorWithPatternImage:(CPImage)anImage
483 {
484  return [[CPColor alloc] _initWithPatternImage:anImage];
485 }
486 
493 + (CPColor)colorWithCSSString:(CPString)aString
494 {
495  return [[CPColor alloc] _initWithCSSString: aString];
496 }
497 
498 + (CPColor)selectedTextBackgroundColor
499 {
500  return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-background-color"] || [CPColor colorWithHexString:"99CCFF"];
501 }
502 
503 + (CPColor)_selectedTextBackgroundColorUnfocussed
504 {
505  return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-inactive-background-color"] || [CPColor colorWithHexString:"CCCCCC"];
506 }
507 
508 /* @ignore */
509 - (id)_initWithCSSString:(CPString)aString
510 {
511  if (aString.indexOf("rgb") == CPNotFound)
512  return nil;
513 
514  self = [super init];
515 
516  var startingIndex = aString.indexOf("("),
517  parts = aString.substring(startingIndex + 1).split(',');
518 
519  _components = [
520  parseInt(parts[0], 10) / 255.0,
521  parseInt(parts[1], 10) / 255.0,
522  parseInt(parts[2], 10) / 255.0,
523  parts[3] ? parseFloat(parts[3], 10) : 1.0
524  ];
525 
526  // We can't reuse aString as _cssString because the browser might not support the `rgba` syntax, and aString might
527  // use it (issue #1413.)
528  [self _initCSSStringFromComponents];
529 
530  _theme = [CPTheme defaultTheme];
531  _themeState = CPThemeStateNormal;
532  [self _loadThemeAttributes];
533 
534  return self;
535 }
536 
537 /* @ignore */
538 - (id)_initWithRGBA:(CPArray)components
539 {
540  self = [super init];
541 
542  if (self)
543  {
544  _components = components;
545 
546  [self _initCSSStringFromComponents];
547 
548  _theme = [CPTheme defaultTheme];
549  _themeState = CPThemeStateNormal;
550  [self _loadThemeAttributes];
551  }
552 
553  return self;
554 }
555 
556 - (void)_initCSSStringFromComponents
557 {
558  var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
559 
560  _cssString = (hasAlpha ? "rgba(" : "rgb(") +
561  parseInt(_components[0] * 255.0) + ", " +
562  parseInt(_components[1] * 255.0) + ", " +
563  parseInt(_components[2] * 255.0) +
564  (hasAlpha ? (", " + _components[3]) : "") + ")";
565 }
566 
567 /* @ignore */
568 - (id)_initWithPatternImage:(CPImage)anImage
569 {
570  self = [super init];
571 
572  if (self)
573  {
574  _patternImage = anImage;
575  _cssString = "url(\"" + [_patternImage filename] + "\")";
576  _components = [0.0, 0.0, 0.0, 1.0];
577 
578  _theme = [CPTheme defaultTheme];
579  _themeState = CPThemeStateNormal;
580  [self _loadThemeAttributes];
581  }
582 
583  return self;
584 }
585 
589 - (CPImage)patternImage
590 {
591  return _patternImage;
592 }
593 
597 - (float)alphaComponent
598 {
599  return _components[3];
600 }
601 
605 - (float)blueComponent
606 {
607  return _components[2];
608 }
609 
613 - (float)greenComponent
614 {
615  return _components[1];
616 }
617 
621 - (float)redComponent
622 {
623  return _components[0];
624 }
625 
637 - (CPArray)components
638 {
639  return _components;
640 }
641 
649 - (CPColor)colorWithAlphaComponent:(float)anAlphaComponent
650 {
651  var components = _components.slice();
652 
653  components[components.length - 1] = anAlphaComponent;
654 
655  return [[[self class] alloc] _initWithRGBA:components];
656 }
657 
661 - (CPColor)colorUsingColorSpaceName:(id)aColorSpaceName
662 {
663  return self;
664 }
665 
679 - (CPArray)hsbComponents
680 {
681  var red = ROUND(_components[_redComponent] * 255.0),
682  green = ROUND(_components[_greenComponent] * 255.0),
683  blue = ROUND(_components[_blueComponent] * 255.0);
684 
685  var max = MAX(red, green, blue),
686  min = MIN(red, green, blue),
687  delta = max - min;
688 
689  var brightness = max / 255.0,
690  saturation = (max != 0) ? delta / max : 0;
691 
692  var hue;
693 
694  if (saturation == 0)
695  {
696  hue = 0;
697  }
698  else
699  {
700  var rr = (max - red) / delta,
701  gr = (max - green) / delta,
702  br = (max - blue) / delta;
703 
704  if (red == max)
705  hue = br - gr;
706  else if (green == max)
707  hue = 2 + rr - br;
708  else
709  hue = 4 + gr - rr;
710 
711  hue /= 6;
712  if (hue < 0)
713  hue++;
714  }
715 
716  return [
717  hue,
718  saturation,
719  brightness
720  ];
721 }
722 
726 - (float)hueComponent
727 {
728  return [self hsbComponents][0];
729 }
730 
734 - (float)saturationComponent
735 {
736  return [self hsbComponents][1];
737 }
738 
742 - (float)brightnessComponent
743 {
744  return [self hsbComponents][2];
745 }
746 
756 - (CPString)cssString
757 {
758  return _cssString;
759 }
760 
764 - (CPString)hexString
765 {
766  return rgbToHex([self redComponent], [self greenComponent], [self blueComponent]);
767 }
768 
769 - (BOOL)isEqual:(CPColor)aColor
770 {
771  if (!aColor)
772  return NO;
773 
774  if (aColor === self)
775  return YES;
776 
777  if (![aColor isKindOfClass:CPColor])
778  return NO;
779 
780  if (_patternImage || [aColor patternImage])
781  return [_patternImage isEqual:[aColor patternImage]];
782 
783  // We don't require the components to be equal beyond 8 bits since otherwise
784  // simple rounding errors will make two colours which are exactly the same on
785  // screen compare unequal.
786  return ROUND([self redComponent] * 255.0) == ROUND([aColor redComponent] * 255.0) &&
787  ROUND([self greenComponent] * 255.0) == ROUND([aColor greenComponent] * 255.0) &&
788  ROUND([self blueComponent] * 255.0) == ROUND([aColor blueComponent] * 255.0) &&
789  [self alphaComponent] == [aColor alphaComponent];
790 }
791 
793 {
794  var description = [super description],
795  patternImage = [self patternImage];
796 
797  if (!patternImage)
798  return description + " " + [self cssString];
799 
800  description += " {\n";
801 
802  if ([patternImage isThreePartImage] || [patternImage isNinePartImage])
803  {
804  var slices = [patternImage imageSlices];
805 
806  if ([patternImage isThreePartImage])
807  description += " orientation: " + ([patternImage isVertical] ? "vertical" : "horizontal") + ",\n";
808 
809  description += " patternImage (" + slices.length + " part): [\n";
810 
811  for (var i = 0; i < slices.length; ++i)
812  {
813  var imgDescription = [slices[i] description] || "nil";
814 
815  description += imgDescription.replace(/^/mg, " ") + ",\n";
816  }
817 
818  description = description.substr(0, description.length - 2) + "\n ]\n}";
819  }
820  else
821  description += ([patternImage description] || "nil").replace(/^/mg, " ") + "\n}";
822 
823  return description;
824 }
825 
826 @end
827 
829 
833 - (void)set
834 {
835  [self setFill];
836  [self setStroke];
837 }
838 
842 - (void)setFill
843 {
845  CGContextSetFillColor(ctx, self);
846 }
847 
851 - (void)setStroke
852 {
854  CGContextSetStrokeColor(ctx, self);
855 }
856 
857 @end
858 
859 @implementation CPColor (Debugging)
860 
861 + (CPColor)randomColor
862 {
863  return [CPColor colorWithRed:RAND() green:RAND() blue:RAND() alpha:1.0];
864 }
865 
866 + (CPColor)checkerBoardColor
867 {
868  // Thanks to cocco http://stackoverflow.com/a/18368212/76900.
869  return [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEX////MzMw46qqDAAAAEElEQVQImWNg+M+AFeEQBgB+vw/xfUUZkgAAAABJRU5ErkJggg=="]];
870 }
871 
872 @end
873 
874 #pragma mark -
875 #pragma mark CSS Theming
876 
877 // The code below adds support for CSS theming with 100% compatibility with current theming system.
878 // The idea is to extend CPColor (and CPImage) with CSS components and adapt low level UI components to
879 // support this new kind of CPColor/CPImage. See CPImageView, CPView and _CPImageAndTextView.
880 //
881 // To create a CPColor that uses CSS, simply use the new class method :
882 // + (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary
883 // where beforeDictionary & afterDictionary are related to ::before & ::after pseudo-elements.
884 // If you don't need them, just use the simplified class method :
885 // + (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary
886 //
887 // Example :
888 // buttonCssColor = [CPColor colorWithCSSDictionary:@{
889 // @"background-color": A3ColorBackgroundWhite,
890 // @"border-color": A3ColorActiveBorder,
891 // @"border-style": @"solid",
892 // @"border-width": @"1px",
893 // @"border-radius": @"3px",
894 // @"box-sizing": @"border-box"
895 // }
896 // beforeDictionary:@{
897 // @"background-color": @"rgb(225,225,225)",
898 // @"bottom": @"3px",
899 // @"content": @"''",
900 // @"position": @"absolute",
901 // @"right": @"21px",
902 // @"top": @"3px",
903 // @"width": @"1px"
904 // }
905 // afterDictionary:@{
906 // @"content": @"''",
907 // @"bottom": @"3px",
908 // @"right": @"6px",
909 // @"top": @"1px",
910 // @"position": @"absolute",
911 // @"height": @"14px",
912 // @"width": @"9px",
913 // @"margin": @"2px 0px 2px 0px",
914 // @"background-image": @"url(%%packed.png)",
915 // @"background-position": @"0px -64px",
916 // @"background-repeat": @"no-repeat",
917 // @"background-size": @"100px 400px"
918 // }];
919 //
920 // Remark : Please note the special URL of the background image used in this example : url(%%packed.png)
921 // During theme loading, "%%" will be replaced by the path to the theme blend resources folder.
922 // Typically, a CSS theme will use some (rare) images all packed together in a single image resource (see packed.png in Aristo3 theme)
923 //
924 // Also, please note that if you don't use one of the CSS components, you can either set it to nil (best solution) or to an empty dictionary, like :
925 // aCssColor = [CPColor colorWithCSSDictionary:@{} beforeDictionary:nil afterDictionary:@{ ... }];
926 //
927 // You can use -(BOOL)isCSSBased to determine how to cope with it in your code.
928 // -(BOOL)hasCSSDictionary, -(BOOL)hasCSSBeforeDictionary and -(BOOL)hasCSSAfterDictionary are convience methods you can use.
929 //
930 // Remark : -(void)restorePreviousCSSState and -(DOMElement)applyCSSColorForView are meant to be used by low level UI widgets (like CPView) to implement
931 // CSS theme support.
932 
933 @implementation CPColor (CSSTheming)
934 {
935  CPDictionary _cssDictionary;
936  CPDictionary _cssBeforeDictionary;
937  CPDictionary _cssAfterDictionary;
938 }
939 
940 + (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary
941 {
942  return [[CPColor alloc] _initWithCSSDictionary:aDictionary beforeDictionary:nil afterDictionary:nil];
943 }
944 
945 + (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary
946 {
947  return [[CPColor alloc] _initWithCSSDictionary:aDictionary beforeDictionary:beforeDictionary afterDictionary:afterDictionary];
948 }
949 
950 - (id)_initWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary
951 {
952  self = [super init];
953 
954  if (self)
955  {
956  _cssDictionary = aDictionary;
957  _cssBeforeDictionary = beforeDictionary;
958  _cssAfterDictionary = afterDictionary;
959  _components = [0.0, 0.0, 0.0, 1.0];
960 
961  _theme = [CPTheme defaultTheme];
962  _themeState = CPThemeStateNormal;
963  [self _loadThemeAttributes];
964  }
965 
966  return self;
967 }
968 
969 - (BOOL)isCSSBased
970 {
971  return !!(_cssDictionary || _cssBeforeDictionary || _cssAfterDictionary);
972 }
973 
974 - (BOOL)hasCSSDictionary
975 {
976  return ([_cssDictionary count] > 0);
977 }
978 
979 - (BOOL)hasCSSBeforeDictionary
980 {
981  return ([_cssBeforeDictionary count] > 0);
982 }
983 
984 - (BOOL)hasCSSAfterDictionary
985 {
986  return ([_cssAfterDictionary count] > 0);
987 }
988 
989 - (void)restorePreviousCSSState:(CPArrayRef)aPreviousStateRef forDOMElement:(DOMElement)aDOMElement
990 {
991 #if PLATFORM(DOM)
992  var aPreviousState = @deref(aPreviousStateRef);
993 
994  for (var i = 0, count = aPreviousState.length; i < count; i++)
995  aDOMElement.style[aPreviousState[i][0]] = aPreviousState[i][1];
996 
997  @deref(aPreviousStateRef) = @[];
998 #endif
999 }
1000 
1001 - (DOMElement)applyCSSColorForView:(CPView)aView onDOMElement:(DOMElement)aDOMElement styleNode:(DOMElement)aStyleNode previousState:(CPArrayRef)aPreviousStateRef
1002 {
1003 #if PLATFORM(DOM)
1004  var aPreviousState = @deref(aPreviousStateRef);
1005 
1006  [_cssDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
1007  {
1008  [aPreviousState addObject:@[aKey, aDOMElement.style[aKey]]];
1009  aDOMElement.style[aKey] = anObject;
1010  }];
1011 
1012  if ([self hasCSSBeforeDictionary] || [self hasCSSAfterDictionary])
1013  {
1014  // We need to create a unique class name
1015 
1016  var styleClassName = @".CP" + [aView UID],
1017  styleContent = @"";
1018 
1019  if ([self hasCSSBeforeDictionary])
1020  {
1021  styleContent += styleClassName + @"::before { ";
1022 
1023  [_cssBeforeDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
1024  {
1025  styleContent += aKey + ": " + anObject + "; ";
1026  }];
1027 
1028  styleContent += "} ";
1029  }
1030 
1031  if ([self hasCSSAfterDictionary])
1032  {
1033  styleContent += styleClassName + @"::after { ";
1034 
1035  [_cssAfterDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
1036  {
1037  styleContent += aKey + ": " + anObject + "; ";
1038  }];
1039 
1040  styleContent += "} ";
1041  }
1042 
1043  var styleDescription = document.createTextNode(styleContent);
1044 
1045  if (!aStyleNode)
1046  {
1047  aStyleNode = document.createElement("style");
1048 
1049  aView._DOMElement.insertBefore(aStyleNode, aView._DOMElement.firstChild);
1050 
1051  aStyleNode.appendChild(styleDescription);
1052  }
1053  else
1054  {
1055  aStyleNode.replaceChild(styleDescription, aStyleNode.firstChild);
1056  }
1057 
1058  [aView setDOMClassName:@"CP"+[aView UID]];
1059  }
1060  else
1061  {
1062  // no before/after so remove aStyleNode if existing
1063 
1064  if (aStyleNode)
1065  {
1066  aView._DOMElement.removeChild(aStyleNode);
1067  aStyleNode = nil;
1068  }
1069  }
1070 
1071  // Return actualised values
1072 
1073  @deref(aPreviousStateRef) = aPreviousState;
1074 
1075  return aStyleNode;
1076 #endif
1077 }
1078 
1079 @end
1080 
1081 #pragma mark -
1082 
1084 var CPColorComponentsKey = @"CPColorComponentsKey",
1085  CPColorPatternImageKey = @"CPColorPatternImageKey",
1086  CPColorCssDictionaryKey = @"CPColorCssDictionaryKey",
1087  CPColorCssBeforeDictionaryKey = @"CPColorCssBeforeDictionaryKey",
1088  CPColorCssAfterDictionaryKey = @"CPColorCssAfterDictionaryKey";
1090 
1091 @implementation CPColor (CPCoding)
1092 
1097 - (id)initWithCoder:(CPCoder)aCoder
1098 {
1099  if ([aCoder containsValueForKey:CPColorPatternImageKey])
1100  self = [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]];
1101  else if ([aCoder containsValueForKey:CPColorCssDictionaryKey])
1102  self = [self _initWithCSSDictionary:[aCoder decodeObjectForKey:CPColorCssDictionaryKey]
1103  beforeDictionary:[aCoder decodeObjectForKey:CPColorCssBeforeDictionaryKey]
1104  afterDictionary:[aCoder decodeObjectForKey:CPColorCssAfterDictionaryKey]];
1105  else
1106  self = [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]];
1107 
1108  [self _decodeThemeObjectsWithCoder:aCoder];
1109 
1110  return self;
1111 }
1112 
1117 - (void)encodeWithCoder:(CPCoder)aCoder
1118 {
1119  if (_patternImage)
1120  [aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey];
1121  else if (_cssDictionary)
1122  {
1123  [aCoder encodeObject:_cssDictionary forKey:CPColorCssDictionaryKey];
1124  [aCoder encodeObject:_cssBeforeDictionary forKey:CPColorCssBeforeDictionaryKey];
1125  [aCoder encodeObject:_cssAfterDictionary forKey:CPColorCssAfterDictionaryKey];
1126  }
1127  else
1128  [aCoder encodeObject:_components forKey:CPColorComponentsKey];
1129 
1130  [self _encodeThemeObjectsWithCoder:aCoder];
1131 }
1132 
1133 @end
1134 
1135 
1137 var hexCharacters = "0123456789ABCDEF";
1138 
1139 /*
1140  Used for the CPColor +colorWithHexString: implementation.
1141  Returns an array of rgb components.
1142 */
1143 var hexToRGB = function(hex)
1144 {
1145  if (hex.length == 3)
1146  hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
1147 
1148  if (hex.length != 6)
1149  return null;
1150 
1151  hex = hex.toUpperCase();
1152 
1153  for (var i = 0; i < hex.length; i++)
1154  if (hexCharacters.indexOf(hex.charAt(i)) == -1)
1155  return null;
1156 
1157  var red = (hexCharacters.indexOf(hex.charAt(0)) * 16 + hexCharacters.indexOf(hex.charAt(1))) / 255.0,
1158  green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0,
1159  blue = (hexCharacters.indexOf(hex.charAt(4)) * 16 + hexCharacters.indexOf(hex.charAt(5))) / 255.0;
1160 
1161  return [red, green, blue, 1.0];
1162 };
1163 
1164 var rgbToHex = function(r,g,b)
1165 {
1166  return byteToHex(r) + byteToHex(g) + byteToHex(b);
1167 };
1168 
1169 var byteToHex = function(n)
1170 {
1171  if (!n || isNaN(n))
1172  return "00";
1173 
1174  n = FLOOR(MIN(255, MAX(0, 256 * n)));
1175 
1176  return hexCharacters.charAt((n - n % 16) / 16) +
1177  hexCharacters.charAt(n % 16);
1178 };
1179 
1399 function CPColorWithImages()
1400 {
1401  var slices = nil,
1402  numParts = 0,
1403  isVertical = false,
1404  imageFactory = CPImageInBundle,
1405  args = Array.prototype.slice.apply(arguments);
1406 
1407  if (typeof(args[args.length - 1]) === "function")
1408  imageFactory = args.pop();
1409 
1410  switch (args.length)
1411  {
1412  case 1:
1413  return imageFromSlices(args[0], isVertical, imageFactory);
1414 
1415  case 2:
1416  // New-style 3-part and 9-part images
1417  if (typeof(args[0]) === "string")
1418  return patternColorsFromPattern.call(this, args[0], args[1], imageFactory);
1419 
1420  return imageFromSlices(args[0], args[1], imageFactory);
1421 
1422  case 3:
1423  case 4:
1424  return [CPColor colorWithPatternImage:imageFactory(args[0], args[1], args[2], args[3])];
1425 
1426  default:
1427  throw("ERROR: Invalid argument count: " + args.length);
1428  }
1429 }
1430 
1431 var imageFromSlices = function(slices, isVertical, imageFactory)
1432 {
1433  var imageSlices = [];
1434 
1435  for (var i = 0; i < slices.length; ++i)
1436  {
1437  var slice = slices[i];
1438 
1439  imageSlices.push(slice ? imageFactory(slice[0], slice[1], slice[2], slice[3]) : nil);
1440  }
1441 
1442  switch (slices.length)
1443  {
1444  case 3:
1446 
1447  case 9:
1449 
1450  default:
1451  throw("ERROR: Invalid number of image slices: " + slices.length);
1452  }
1453 };
1454 
1455 var patternColorsFromPattern = function(pattern, attributes, imageFactory)
1456 {
1457  if (pattern.match(/^.*\{[^}]+\}/))
1458  {
1459  var width = attributes["width"],
1460  height = attributes["height"],
1461  separator = attributes["separator"] || "-",
1462  orientation = attributes["orientation"],
1463  rightWidth,
1464  bottomHeight,
1465  centerWidthHeight,
1466  centerIsNil,
1467  numParts,
1468  isVertical;
1469 
1470  // positions are mandatory
1471  if (pattern.indexOf("{position}") < 0)
1472  throw("ERROR: Pattern strings must have a {position} placeholder (\"" + pattern + "\")");
1473 
1474  if (orientation === undefined)
1475  {
1476  numParts = 9;
1477 
1478  if (attributes["centerIsNil"] !== undefined)
1479  centerIsNil = attributes["centerIsNil"];
1480  }
1481  else
1482  {
1483  numParts = 3;
1484  isVertical = orientation === PatternIsVertical;
1485 
1486  if (isVertical)
1487  {
1488  if (attributes["centerHeight"])
1489  centerWidthHeight = attributes["centerHeight"];
1490  }
1491  else
1492  {
1493  if (attributes["centerWidth"])
1494  centerWidthHeight = attributes["centerWidth"];
1495  }
1496  }
1497 
1498  if (attributes["rightWidth"])
1499  rightWidth = attributes["rightWidth"];
1500 
1501  if (attributes["bottomHeight"])
1502  bottomHeight = attributes["bottomHeight"];
1503 
1504  var positions = attributes["positions"] || "@",
1505  states = nil,
1506  styles = nil;
1507 
1508  if (numParts === 3)
1509  {
1510  if (positions === "@")
1511  {
1512  if (isVertical)
1513  positions = ["top", "center", "bottom"];
1514  else
1515  positions = ["left", "center", "right"];
1516  }
1517  else if (positions === "#")
1518  positions = ["0", "1", "2"];
1519  else
1520  throw("ERROR: Invalid positions: " + positions)
1521  }
1522  else // numParts === 9
1523  {
1524  if (positions === "@" || positions === "abbrev")
1525  positions = ["top-left", "top", "top-right", "left", "center", "right", "bottom-left", "bottom", "bottom-right"];
1526  else if (positions === "full")
1527  positions = ["top-left", "top-center", "top-right", "center-left", "center-center", "center-right", "bottom-left", "bottom-center", "bottom-right"];
1528  else if (positions === "#")
1529  positions = ["0", "1", "2", "3", "4", "5", "6", "7", "8"];
1530  else
1531  throw("ERROR: Invalid positions: " + positions)
1532  }
1533 
1534  // states
1535  if (pattern.indexOf("{state}") >= 0)
1536  {
1537  states = attributes["states"];
1538 
1539  if (!states)
1540  throw("ERROR: {state} placeholder in the pattern (\"" + pattern + "\") but no states item in the attributes");
1541  }
1542 
1543  // styles
1544  if (pattern.indexOf("{style}") >= 0)
1545  {
1546  styles = attributes["styles"];
1547 
1548  if (!styles)
1549  throw("ERROR: {style} placeholder in the pattern (\"" + pattern + "\") but no styles item in the attributes");
1550  }
1551 
1552  // Now assemble the hierarchy
1553  var placeholder = "{position}",
1554  pos = pattern.indexOf(placeholder),
1555  i;
1556 
1557  for (i = 0; i < positions.length; ++i)
1558  positions[i] = pattern.replace(placeholder, pos === 0 ? positions[i] + separator : separator + positions[i]);
1559 
1560  var slices = positions,
1561  object = slices,
1562  key,
1563  sep;
1564 
1565  if (states)
1566  {
1567  placeholder = "{state}";
1568  pos = pattern.indexOf(placeholder);
1569  object = {};
1570 
1571  for (i = 0; i < states.length; ++i)
1572  {
1573  var state = states[i];
1574  key = state || "@";
1575  sep = state ? separator : "";
1576 
1577  object[key] = slices.slice(0);
1578  replacePlaceholderInArray(object[key], placeholder, pos === 0 ? state + sep : sep + state);
1579  }
1580  }
1581 
1582  if (styles)
1583  {
1584  placeholder = "{style}";
1585  pos = pattern.indexOf(placeholder);
1586 
1587  var styleObject = {};
1588 
1589  for (i = 0; i < styles.length; ++i)
1590  {
1591  var style = styles[i];
1592  key = style || "@";
1593  sep = style ? separator : "";
1594 
1595  if (states)
1596  {
1597  styleObject[key] = cloneObject(object);
1598  replacePlaceholderInObject(styleObject[key], placeholder, pos === 0 ? style + sep : sep + style);
1599  }
1600  else
1601  {
1602  styleObject[key] = slices.slice(0);
1603  replacePlaceholderInArray(styleObject[key], placeholder, pos === 0 ? style + sep : sep + style);
1604  }
1605  }
1606 
1607  object = styleObject;
1608  }
1609 
1610  if (styles || states)
1611  {
1612  if (numParts === 3)
1613  makeThreePartSlicesFromObject(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1614  else
1615  makeNinePartSlicesFromObject(object, width, height, rightWidth, bottomHeight, centerIsNil);
1616 
1617  makeImagesFromObject(object, isVertical, imageFactory);
1618  return object;
1619  }
1620  else
1621  {
1622  if (numParts === 3)
1623  makeThreePartSlicesFromArray(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1624  else
1625  makeNinePartSlicesFromArray(object, width, height, rightWidth, bottomHeight, centerIsNil);
1626 
1627  return imageFromSlices(object, isVertical, imageFactory);
1628  }
1629  }
1630  else
1631  throw("ERROR: No placeholders in slice pattern (\"" + pattern + "\")");
1632 };
1633 
1634 var replacePlaceholderInArray = function(array, find, replacement)
1635 {
1636  for (var i = 0; i < array.length; ++i)
1637  array[i] = array[i].replace(find, replacement);
1638 };
1639 
1640 var replacePlaceholderInObject = function(object, find, replacement)
1641 {
1642  for (var key in object)
1643  if (object.hasOwnProperty(key))
1644  if (object[key].constructor === Array)
1645  replacePlaceholderInArray(object[key], find, replacement);
1646  else
1647  replacePlaceholderInObject(object[key], find, replacement);
1648 };
1649 
1650 var cloneObject = function(object)
1651 {
1652  var clone = {};
1653 
1654  for (var key in object)
1655  if (object.hasOwnProperty(key))
1656  if (object[key].constructor === Array)
1657  clone[key] = object[key].slice(0);
1658  else if (typeof(object[key]) === "object")
1659  clone[key] = cloneObject(object[key]);
1660  else
1661  clone[key] = object[key];
1662 
1663  return clone;
1664 };
1665 
1666 var makeThreePartSlicesFromObject = function(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical)
1667 {
1668  for (var key in object)
1669  if (object.hasOwnProperty(key))
1670  if (object[key].constructor === Array)
1671  makeThreePartSlicesFromArray(object[key], width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1672  else // object
1673  makeThreePartSlicesFromObject(object[key], width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
1674 };
1675 
1676 var makeThreePartSlicesFromArray = function(array, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical)
1677 {
1678  array[0] = [array[0], width, height];
1679 
1680  if (isVertical)
1681  {
1682  array[1] = [array[1], width, centerWidthHeight ? centerWidthHeight : 1.0];
1683  array[2] = [array[2], width, bottomHeight ? bottomHeight : height];
1684  }
1685  else
1686  {
1687  array[1] = [array[1], centerWidthHeight ? centerWidthHeight : 1.0, height];
1688  array[2] = [array[2], rightWidth ? rightWidth : width, height];
1689  }
1690 };
1691 
1692 var makeNinePartSlicesFromObject = function(object, width, height, rightWidth, bottomHeight, centerIsNil)
1693 {
1694  for (var key in object)
1695  if (object.hasOwnProperty(key))
1696  if (object[key].constructor === Array)
1697  makeNinePartSlicesFromArray(object[key], width, height, rightWidth, bottomHeight, centerIsNil);
1698  else // object
1699  makeNinePartSlicesFromObject(object[key], width, height, rightWidth, bottomHeight, centerIsNil);
1700 };
1701 
1702 var makeNinePartSlicesFromArray = function(array, width, height, rightWidth, bottomHeight, centerIsNil)
1703 {
1704  rightWidth = rightWidth ? rightWidth : width;
1705  bottomHeight = bottomHeight ? bottomHeight : height;
1706 
1707  array[0] = [array[0], width, height]; // top-left
1708  array[1] = [array[1], 1.0, height]; // top
1709  array[2] = [array[2], rightWidth, height]; // top-right
1710  array[3] = [array[3], width, 1.0]; // left
1711  array[4] = centerIsNil ? nil : [array[4], 1.0, 1.0]; // center
1712  array[5] = [array[5], rightWidth, 1.0]; // right
1713  array[6] = [array[6], width, bottomHeight]; // bottom-left
1714  array[7] = [array[7], 1.0, bottomHeight]; // bottom
1715  array[8] = [array[8], rightWidth, bottomHeight]; // bottom-right
1716 };
1717 
1718 var makeImagesFromObject = function(object, isVertical, imageFactory)
1719 {
1720  for (var key in object)
1721  if (object.hasOwnProperty(key))
1722  if (object[key].constructor === Array)
1723  object[key] = imageFromSlices(object[key], isVertical, imageFactory);
1724  else // object
1725  makeImagesFromObject(object[key], isVertical, imageFactory);
1726 };
1727 
function CPImageInBundle()
Definition: CPImage.j:72
id initWithImageSlices:isVertical:(CPArray imageSlices, [isVertical] BOOL isVertical)
Definition: CPImage.j:750
CPColor colorWithHexString:(string hex)
Definition: CPColor.j:264
CPCSSRGBAFeature
var isEqual
CPGraphicsContext currentContext()
An object representation of nil.
Definition: CPNull.h:2
CPColor colorWithPatternImage:(CPImage anImage)
Definition: CPColor.j:482
function CGContextSetStrokeColor(aContext, aColor)
Definition: CGContext.j:675
CPString cssString()
Definition: CPColor.j:756
CPColor colorWithCalibratedRed:green:blue:alpha:(float red, [green] float green, [blue] float blue, [alpha] float alpha)
Definition: CPColor.j:142
CPArray hsbComponents()
Definition: CPColor.j:679
id initWithImageSlices:(CPArray imageSlices)
Definition: CPImage.j:827
int width
float alphaComponent()
Definition: CPColor.j:597
A mutable key-value pair collection.
Definition: CPDictionary.h:2
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
void setStroke()
Definition: CPColor.j:851
if(CPFeatureIsCompatible(CPHTMLCanvasFeature))
Definition: CPImage.h:2
function CPFeatureIsCompatible(aFeature)
CPColor colorWithCalibratedWhite:alpha:(float white, [alpha] float alpha)
Definition: CPColor.j:173
CPColor colorWithRed:green:blue:alpha:(float red, [green] float green, [blue] float blue, [alpha] float alpha)
Definition: CPColor.j:122
function CGContextSetFillColor(aContext, aColor)
Definition: CGContext.j:663
CPColor colorWithHue:saturation:brightness:alpha:(float hue, [saturation] float saturation, [brightness] float brightness, [alpha] float alpha)
Definition: CPColor.j:220
CPColorPatternIsVertical
Definition: CPColor.j:30
id initWithContentsOfFile:(CPString aFilename)
Definition: CPImage.j:192
CPColorPatternIsHorizontal
Definition: CPColor.j:35
var bottomHeight
Definition: CPAlert.j:53
CPTheme defaultTheme()
Definition: CPTheme.j:44
CPColor colorWithWhite:alpha:(float white, [alpha] float alpha)
Definition: CPColor.j:157
void setDOMClassName:(CPString aClassName)
Definition: CPView.j:3457
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
CPNotFound
Definition: CPObjJRuntime.j:62
Definition: CPTheme.h:2
CPString description()
Definition: CPImage.j:440
CPImage patternImage()
Definition: CPColor.j:589
void setFill()
Definition: CPColor.j:842
id alloc()
Definition: CPObject.j:130
Definition: CPView.j:137
FrameUpdater prototype description