API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPTheme.j
Go to the documentation of this file.
1 /*
2  * CPTheme.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2009, 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 @class CPView
25 
26 var CPThemesByName = { },
27  CPThemeDefaultTheme = nil,
28  CPThemeDefaultHudTheme = nil;
29 
30 
35 @implementation CPTheme : CPObject
36 {
37  CPString _name;
38  CPDictionary _attributes;
39 }
40 
41 + (void)setDefaultTheme:(CPTheme)aTheme
42 {
43  CPThemeDefaultTheme = aTheme;
44 }
45 
46 + (CPTheme)defaultTheme
47 {
48  return CPThemeDefaultTheme;
49 }
50 
55 + (void)setDefaultHudTheme:(CPTheme)aTheme
56 {
57  CPThemeDefaultHudTheme = aTheme;
58 }
59 
65 + (CPTheme)defaultHudTheme
66 {
67  if (!CPThemeDefaultHudTheme)
68  CPThemeDefaultHudTheme = [CPTheme themeNamed:[[self defaultTheme] name] + "-HUD"];
69  return CPThemeDefaultHudTheme;
70 }
71 
72 + (CPTheme)themeNamed:(CPString)aName
73 {
74  return CPThemesByName[aName];
75 }
76 
77 - (id)initWithName:(CPString)aName
78 {
79  self = [super init];
80 
81  if (self)
82  {
83  _name = aName;
84  _attributes = @{};
85 
86  CPThemesByName[_name] = self;
87  }
88 
89  return self;
90 }
91 
92 - (CPString)name
93 {
94  return _name;
95 }
96 
105 - (CPArray)classNames
106 {
107  return [_attributes allKeys];
108 }
109 
120 - (CPDictionary)attributesForClass:(id)aClass
121 {
122  if (!aClass)
123  return nil;
124 
125  var className = nil;
126 
127  if ([aClass isKindOfClass:[CPString class]])
128  {
129  // See if it is a class name
130  var theClass = CPClassFromString(aClass);
131 
132  if (theClass)
133  aClass = theClass;
134  else
135  className = aClass;
136  }
137 
138  if (!className)
139  {
140  if ([aClass isKindOfClass:[CPView class]])
141  {
142  if ([aClass respondsToSelector:@selector(defaultThemeClass)])
143  className = [aClass defaultThemeClass];
144  else if ([aClass respondsToSelector:@selector(themeClass)])
145  {
146  CPLog.warn(@"%@ themeClass is deprecated in favor of defaultThemeClass", CPStringFromClass(aClass));
147  className = [aClass themeClass];
148  }
149  else
150  return nil;
151  }
152  else
153  [CPException raise:CPInvalidArgumentException reason:@"aClass must be a class object or a string."];
154  }
155 
156  return [_attributes objectForKey:className];
157 }
158 
175 - (CPDictionary)attributeNamesForClass:(id)aClass
176 {
177  var attributes = [self attributesForClass:aClass];
178 
179  if (attributes)
180  return [attributes allKeys];
181  else
182  return [CPArray array];
183 }
184 
198 - (_CPThemeAttribute)attributeWithName:(CPString)aName forClass:(id)aClass
199 {
200  var attributes = [self attributesForClass:aClass];
201 
202  if (!attributes)
203  return nil;
204 
205  return [attributes objectForKey:aName];
206 }
207 
221 - (id)valueForAttributeWithName:(CPString)aName forClass:(id)aClass
222 {
223  return [self valueForAttributeWithName:aName inState:CPThemeStateNormal forClass:aClass];
224 }
225 
239 - (id)valueForAttributeWithName:(CPString)aName inState:(CPThemeState)aState forClass:(id)aClass
240 {
241  var attribute = [self attributeWithName:aName forClass:aClass];
242 
243  if (!attribute)
244  return nil;
245 
246  return [attribute valueForState:aState];
247 }
248 
249 - (void)takeThemeFromObject:(id)anObject
250 {
251  var attributes = [anObject _themeAttributeDictionary],
252  attributeName = nil,
253  attributeNames = [attributes keyEnumerator],
254  objectThemeClass = [anObject themeClass];
255 
256  while ((attributeName = [attributeNames nextObject]) !== nil)
257  [self _recordAttribute:[attributes objectForKey:attributeName] forClass:objectThemeClass];
258 }
259 
260 - (void)_recordAttribute:(_CPThemeAttribute)anAttribute forClass:(CPString)aClass
261 {
262  if (![anAttribute hasValues])
263  return;
264 
265  var attributes = [_attributes objectForKey:aClass];
266 
267  if (!attributes)
268  {
269  attributes = @{};
270 
271  [_attributes setObject:attributes forKey:aClass];
272  }
273 
274  var name = [anAttribute name],
275  existingAttribute = [attributes objectForKey:name];
276 
277  if (existingAttribute)
278  [attributes setObject:[existingAttribute attributeMergedWithAttribute:anAttribute] forKey:name];
279  else
280  [attributes setObject:anAttribute forKey:name];
281 }
282 
283 @end
284 
285 var CPThemeNameKey = @"CPThemeNameKey",
286  CPThemeAttributesKey = @"CPThemeAttributesKey";
287 
288 @implementation CPTheme (CPCoding)
289 
290 - (id)initWithCoder:(CPCoder)aCoder
291 {
292  self = [super init];
293 
294  if (self)
295  {
296  _name = [aCoder decodeObjectForKey:CPThemeNameKey];
297  _attributes = [aCoder decodeObjectForKey:CPThemeAttributesKey];
298 
299  CPThemesByName[_name] = self;
300  }
301 
302  return self;
303 }
304 
305 - (void)encodeWithCoder:(CPCoder)aCoder
306 {
307  [aCoder encodeObject:_name forKey:CPThemeNameKey];
308  [aCoder encodeObject:_attributes forKey:CPThemeAttributesKey];
309 }
310 
311 @end
312 
313 @implementation _CPThemeKeyedUnarchiver : CPKeyedUnarchiver
314 {
315  CPBundle _bundle;
316 }
317 
318 - (id)initForReadingWithData:(CPData)data bundle:(CPBundle)aBundle
319 {
320  self = [super initForReadingWithData:data];
321 
322  if (self)
323  _bundle = aBundle;
324 
325  return self;
326 }
327 
328 - (CPBundle)bundle
329 {
330  return _bundle;
331 }
332 
333 - (BOOL)awakenCustomResources
334 {
335  return YES;
336 }
337 
338 @end
339 
340 var CPThemeStates = {},
341  CPThemeStateNames = {},
342  CPThemeStateCount = 0;
343 
344 function CPThemeState(aStateName)
345 {
346  var state = CPThemeStates[aStateName];
347 
348  if (state === undefined)
349  {
350  if (aStateName.indexOf('+') === -1)
351  state = 1 << CPThemeStateCount++;
352  else
353  {
354  var state = 0,
355  states = aStateName.split('+'),
356  count = states.length;
357 
358  while (count--)
359  {
360  var stateName = states[count],
361  individualState = CPThemeStates[stateName];
362 
363  if (individualState === undefined)
364  {
365  individualState = 1 << CPThemeStateCount++;
366  CPThemeStates[stateName] = individualState;
367  CPThemeStateNames[individualState] = stateName;
368  }
369 
370  state |= individualState;
371  }
372  }
373 
374  CPThemeStates[aStateName] = state;
375  CPThemeStateNames[state] = aStateName;
376  }
377 
378  return state;
379 }
380 
381 function CPThemeStateName(aState)
382 {
383  var name = CPThemeStateNames[aState];
384 
385  if (name !== undefined)
386  return name;
387 
388  if (!(aState & (aState - 1)))
389  return "";
390 
391  var state = 1,
392  name = "";
393 
394  for (; state < aState; state <<= 1)
395  if (aState & state)
396  name += (name.length === 0 ? '' : '+') + CPThemeStateNames[state];
397 
398  CPThemeStateNames[aState] = name;
399 
400  return name;
401 }
402 
403 CPThemeStateNames[0] = "normal";
404 CPThemeStateNormal = CPThemeStates["normal"] = 0;
405 CPThemeStateDisabled = CPThemeState("disabled");
406 CPThemeStateHovered = CPThemeState("hovered");
407 CPThemeStateHighlighted = CPThemeState("highlighted");
408 CPThemeStateSelected = CPThemeState("selected");
409 CPThemeStateTableDataView = CPThemeState("tableDataView");
410 CPThemeStateSelectedDataView = CPThemeState("selectedTableDataView");
411 CPThemeStateGroupRow = CPThemeState("CPThemeStateGroupRow");
412 CPThemeStateBezeled = CPThemeState("bezeled");
413 CPThemeStateBordered = CPThemeState("bordered");
414 CPThemeStateEditable = CPThemeState("editable");
415 CPThemeStateEditing = CPThemeState("editing");
416 CPThemeStateVertical = CPThemeState("vertical");
417 CPThemeStateDefault = CPThemeState("default");
418 CPThemeStateCircular = CPThemeState("circular");
419 CPThemeStateAutocompleting = CPThemeState("autocompleting");
420 CPThemeStateMainWindow = CPThemeState("mainWindow");
421 CPThemeStateKeyWindow = CPThemeState("keyWindow");
422 
423 @implementation _CPThemeAttribute : CPObject
424 {
425  CPString _name;
426  id _defaultValue;
427  CPDictionary _values;
428 
429  JSObject _cache;
430  _CPThemeAttribute _themeDefaultAttribute;
431 }
432 
433 - (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue
434 {
435  self = [super init];
436 
437  if (self)
438  {
439  _cache = { };
440  _name = aName;
441  _defaultValue = aDefaultValue;
442  _values = @{};
443  }
444 
445  return self;
446 }
447 
448 - (CPString)name
449 {
450  return _name;
451 }
452 
453 - (id)defaultValue
454 {
455  return _defaultValue;
456 }
457 
458 - (BOOL)hasValues
459 {
460  return [_values count] > 0;
461 }
462 
463 - (BOOL)isTrivial
464 {
465  return ([_values count] === 1) && (Number([_values allKeys][0]) === CPThemeStateNormal);
466 }
467 
468 - (void)setValue:(id)aValue
469 {
470  _cache = {};
471 
472  if (aValue === undefined || aValue === nil)
473  _values = @{};
474  else
475  _values = @{ String(CPThemeStateNormal): aValue };
476 }
477 
478 - (void)setValue:(id)aValue forState:(CPThemeState)aState
479 {
480  _cache = { };
481 
482  if ((aValue === undefined) || (aValue === nil))
483  [_values removeObjectForKey:String(aState)];
484  else
485  [_values setObject:aValue forKey:String(aState)];
486 }
487 
488 - (id)value
489 {
490  return [self valueForState:CPThemeStateNormal];
491 }
492 
493 - (id)valueForState:(CPThemeState)aState
494 {
495  var value = _cache[aState];
496 
497  // This can be nil.
498  if (value !== undefined)
499  return value;
500 
501  value = [_values objectForKey:String(aState)];
502 
503  // If we don't have a value, and we have a non-normal state...
504  if ((value === undefined || value === nil) && aState !== CPThemeStateNormal)
505  {
506  // If this is a composite state (not a power of 2), find the closest partial subset match.
507  if (aState & (aState - 1))
508  {
509  var highestOneCount = 0,
510  states = [_values allKeys],
511  count = states.length;
512 
513  while (count--)
514  {
515  // states[count] is a string!
516  var state = Number(states[count]);
517 
518  // A & B = A iff A < B
519  if ((state & aState) === state)
520  {
521  var oneCount = cachedNumberOfOnes[state];
522 
523  if (oneCount === undefined)
524  oneCount = numberOfOnes(state);
525 
526  if (oneCount > highestOneCount)
527  {
528  highestOneCount = oneCount;
529  value = [_values objectForKey:String(state)];
530  }
531  }
532  }
533  }
534 
535  // Still don't have a value? OK, let's use the normal value.
536  if (value === undefined || value === nil)
537  value = [_values objectForKey:String(CPThemeStateNormal)];
538  }
539 
540  if (value === undefined || value === nil)
541  value = [_themeDefaultAttribute valueForState:aState];
542 
543  if (value === undefined || value === nil)
544  {
545  value = _defaultValue;
546 
547  // Class theme attributes cannot use nil because it's a dictionary.
548  // So transform CPNull into nil.
549  if (value === [CPNull null])
550  value = nil;
551  }
552 
553  _cache[aState] = value;
554 
555  return value;
556 }
557 
558 - (void)setParentAttribute:(_CPThemeAttribute)anAttribute
559 {
560  if (_themeDefaultAttribute === anAttribute)
561  return;
562 
563  _cache = { };
564  _themeDefaultAttribute = anAttribute;
565 }
566 
567 - (_CPThemeAttribute)attributeMergedWithAttribute:(_CPThemeAttribute)anAttribute
568 {
569  var mergedAttribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue];
570 
571  mergedAttribute._values = [_values copy];
572  [mergedAttribute._values addEntriesFromDictionary:anAttribute._values];
573 
574  return mergedAttribute;
575 }
576 
577 @end
578 
579 @implementation _CPThemeAttribute (CPCoding)
580 
581 - (id)initWithCoder:(CPCoder)aCoder
582 {
583  self = [super init];
584 
585  if (self)
586  {
587  _cache = {};
588 
589  _name = [aCoder decodeObjectForKey:@"name"];
590  _defaultValue = [aCoder decodeObjectForKey:@"defaultValue"];
591  _values = @{};
592 
593  if ([aCoder containsValueForKey:@"value"])
594  {
595  var state = CPThemeStateNormal;
596 
597  if ([aCoder containsValueForKey:@"state"])
598  state = CPThemeState([aCoder decodeObjectForKey:@"state"]);
599 
600  [_values setObject:[aCoder decodeObjectForKey:"value"] forKey:state];
601  }
602  else
603  {
604  var encodedValues = [aCoder decodeObjectForKey:@"values"],
605  keys = [encodedValues allKeys],
606  count = keys.length;
607 
608  while (count--)
609  {
610  var key = keys[count];
611 
612  [_values setObject:[encodedValues objectForKey:key] forKey:CPThemeState(key)];
613  }
614  }
615  }
616 
617  return self;
618 }
619 
620 - (void)encodeWithCoder:(CPCoder)aCoder
621 {
622  [aCoder encodeObject:_name forKey:@"name"];
623  [aCoder encodeObject:_defaultValue forKey:@"defaultValue"];
624 
625  var keys = [_values allKeys],
626  count = keys.length;
627 
628  if (count === 1)
629  {
630  var onlyKey = keys[0];
631 
632  if (Number(onlyKey) !== CPThemeStateNormal)
633  [aCoder encodeObject:CPThemeStateName(Number(onlyKey)) forKey:@"state"];
634 
635  [aCoder encodeObject:[_values objectForKey:onlyKey] forKey:@"value"];
636  }
637  else
638  {
639  var encodedValues = @{};
640 
641  while (count--)
642  {
643  var key = keys[count];
644 
645  [encodedValues setObject:[_values objectForKey:key] forKey:CPThemeStateName(Number(key))];
646  }
647 
648  [aCoder encodeObject:encodedValues forKey:@"values"];
649  }
650 }
651 
652 @end
653 
654 var cachedNumberOfOnes = /*000000*//*000001*//*000010*//*000011*//*000100*//*000101*//*000110*//*000111*//*001000*//*001001*//*001010*//*001011*//*001100*//*001101*//*001110*//*001111*//*010000*//*010001*//*010010*//*010011*//*010100*//*010101*//*010110*//*010111*//*011000*//*011001*//*011010*//*011011*//*011100*//*011101*//*011110*//*011111*//*100000*//*100001*//*100010*//*100011*//*100100*//*100101*//*100110*//*100111*//*101000*//*101001*//*101010*//*101011*//*101100*//*101101*//*101110*//*101111*//*110000*//*110001*//*110010*//*110011*//*110100*//*110101*//*110110*//*110111*//*111000*//*111001*//*111010*//*111011*//*111100*//*111101*//*111110*//*111111*/[ 0 , 1 , 1 , 2 , 1 , 2 , 2 ,
655  3 , 1 , 2 , 2 , 3 , 2 , 3 ,
656  3 , 4 , 1 , 2 , 2 , 3 , 2 ,
657  3 , 3 , 4 , 2 , 3 , 3 , 4 ,
658  3 , 4 , 4 , 5 , 1 , 2 , 2 ,
659  3 , 2 , 3 , 3 , 4 , 2 , 3 ,
660  3 , 4 , 3 , 4 , 4 , 5 , 2 ,
661  3 , 3 , 4 , 3 , 4 , 4 , 5 ,
662  3 , 4 , 4 , 5 , 4 , 5 , 5 ,
663  6 ];
664 
665 var numberOfOnes = function(aNumber)
666 {
667  var count = 0,
668  slot = aNumber;
669 
670  while (aNumber)
671  {
672  ++count;
673  aNumber &= (aNumber - 1);
674  }
675 
676  cachedNumberOfOnes[slot] = count;
677 
678  return count;
679 };
680 
681 numberOfOnes.displayName = "numberOfOnes";
682 
683 function CPThemeAttributeEncode(aCoder, aThemeAttribute)
684 {
685  var values = aThemeAttribute._values,
686  count = [values count],
687  key = "$a" + [aThemeAttribute name];
688 
689  if (count === 1)
690  {
691  var state = [values allKeys][0];
692 
693  if (Number(state) === 0)
694  {
695  [aCoder encodeObject:[values objectForKey:state] forKey:key];
696 
697  return YES;
698  }
699  }
700 
701  if (count >= 1)
702  {
703  [aCoder encodeObject:aThemeAttribute forKey:key];
704 
705  return YES;
706  }
707 
708  return NO;
709 }
710 
711 function CPThemeAttributeDecode(aCoder, anAttributeName, aDefaultValue, aTheme, aClass)
712 {
713  var key = "$a" + anAttributeName;
714 
715  if (![aCoder containsValueForKey:key])
716  var attribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
717 
718  else
719  {
720  var attribute = [aCoder decodeObjectForKey:key];
721 
722  if (!attribute.isa || ![attribute isKindOfClass:[_CPThemeAttribute class]])
723  {
724  var themeAttribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
725 
726  [themeAttribute setValue:attribute];
727 
728  attribute = themeAttribute;
729  }
730  }
731 
732  if (aTheme && aClass)
733  [attribute setParentAttribute:[aTheme attributeWithName:anAttributeName forClass:aClass]];
734 
735  return attribute;
736 }
737 
738 /* TO AUTO CREATE THESE:
739 function bit_count(bits)
740  {
741  var count = 0;
742 
743  while (bits)
744  {
745  ++count;
746  bits &= (bits - 1);
747  }
748 
749  return count ;
750  }
751 
752 zeros = "000000000";
753 
754 function pad(string, digits)
755 {
756  return zeros.substr(0, digits - string.length) + string;
757 }
758 
759 var str = ""
760 str += '[';
761 for (i = 0;i < Math.pow(2,6);++i)
762 {
763  str += bit_count(i) + " /*" + pad(i.toString(2),6) + "*" + "/, ";
764 }
765 print(str+']');
766 
767 */