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