API 0.9.5
AppKit/CPTheme.j
Go to the documentation of this file.
00001 /*
00002  * CPTheme.j
00003  * AppKit
00004  *
00005  * Created by Francisco Tolmasky.
00006  * Copyright 2009, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 
00025 var CPThemesByName          = { },
00026     CPThemeDefaultTheme     = nil,
00027     CPThemeDefaultHudTheme  = nil;
00028 
00029 
00034 @implementation CPTheme : CPObject
00035 {
00036     CPString        _name;
00037     CPDictionary    _attributes;
00038 }
00039 
00040 + (void)setDefaultTheme:(CPTheme)aTheme
00041 {
00042     CPThemeDefaultTheme = aTheme;
00043 }
00044 
00045 + (CPTheme)defaultTheme
00046 {
00047     return CPThemeDefaultTheme;
00048 }
00049 
00054 + (void)setDefaultHudTheme:(CPTheme)aTheme
00055 {
00056     CPThemeDefaultHudTheme = aTheme;
00057 }
00058 
00064 + (CPTheme)defaultHudTheme
00065 {
00066     if (!CPThemeDefaultHudTheme)
00067         CPThemeDefaultHudTheme = [CPTheme themeNamed:[[self defaultTheme] name] + "-HUD"];
00068     return CPThemeDefaultHudTheme;
00069 }
00070 
00071 + (CPTheme)themeNamed:(CPString)aName
00072 {
00073     return CPThemesByName[aName];
00074 }
00075 
00076 - (id)initWithName:(CPString)aName
00077 {
00078     self = [super init];
00079 
00080     if (self)
00081     {
00082         _name = aName;
00083         _attributes = [CPDictionary dictionary];
00084 
00085         CPThemesByName[_name] = self;
00086     }
00087 
00088     return self;
00089 }
00090 
00091 - (CPString)name
00092 {
00093     return _name;
00094 }
00095 
00104 - (CPArray)classNames
00105 {
00106     return [_attributes allKeys];
00107 }
00108 
00119 - (CPDictionary)attributesForClass:(id)aClass
00120 {
00121     if (!aClass)
00122         return nil;
00123 
00124     var className = nil;
00125 
00126     if ([aClass isKindOfClass:[CPString class]])
00127     {
00128         // See if it is a class name
00129         var theClass = CPClassFromString(aClass);
00130 
00131         if (theClass)
00132             aClass = theClass;
00133         else
00134             className = aClass;
00135     }
00136 
00137     if (!className)
00138     {
00139         if ([aClass isKindOfClass:[CPView class]])
00140         {
00141             if ([aClass respondsToSelector:@selector(defaultThemeClass)])
00142                 className = [aClass defaultThemeClass];
00143             else if ([aClass respondsToSelector:@selector(themeClass)])
00144             {
00145                 CPLog.warn(@"%@ themeClass is deprecated in favor of defaultThemeClass",CPStringFromClass([anObject class]));
00146                 className = [aClass themeClass];
00147             }
00148             else
00149                 return nil;
00150         }
00151         else
00152             [CPException raise:CPInvalidArgumentException reason:@"aClass must be a class object or a string."];
00153     }
00154 
00155     return [_attributes objectForKey:className];
00156 }
00157 
00174 - (CPDictionary)attributeNamesForClass:(id)aClass
00175 {
00176     var attributes = [self attributesForClass:aClass];
00177 
00178     if (attributes)
00179         return [attributes allKeys];
00180     else
00181         return [CPArray array];
00182 }
00183 
00197 - (_CPThemeAttribute)attributeWithName:(CPString)aName forClass:(id)aClass
00198 {
00199     var attributes = [self attributesForClass:aClass];
00200 
00201     if (!attributes)
00202         return nil;
00203 
00204     return [attributes objectForKey:aName];
00205 }
00206 
00220 - (id)valueForAttributeWithName:(CPString)aName forClass:(id)aClass
00221 {
00222     return [self valueForAttributeWithName:aName inState:CPThemeStateNormal forClass:aClass];
00223 }
00224 
00238 - (id)valueForAttributeWithName:(CPString)aName inState:(CPThemeState)aState forClass:(id)aClass
00239 {
00240     var attribute = [self attributeWithName:aName forClass:aClass];
00241 
00242     if (!attribute)
00243         return nil;
00244 
00245     return [attribute valueForState:aState];
00246 }
00247 
00248 - (void)takeThemeFromObject:(id)anObject
00249 {
00250     var attributes = [anObject _themeAttributeDictionary],
00251         attributeName = nil,
00252         attributeNames = [attributes keyEnumerator],
00253         objectThemeClass = [anObject themeClass];
00254 
00255     while (attributeName = [attributeNames nextObject])
00256         [self _recordAttribute:[attributes objectForKey:attributeName] forClass:objectThemeClass];
00257 }
00258 
00259 - (void)_recordAttribute:(_CPThemeAttribute)anAttribute forClass:(CPString)aClass
00260 {
00261     if (![anAttribute hasValues])
00262         return;
00263 
00264     var attributes = [_attributes objectForKey:aClass];
00265 
00266     if (!attributes)
00267     {
00268         attributes = [CPDictionary dictionary];
00269 
00270         [_attributes setObject:attributes forKey:aClass];
00271     }
00272 
00273     var name = [anAttribute name],
00274         existingAttribute = [attributes objectForKey:name];
00275 
00276     if (existingAttribute)
00277         [attributes setObject:[existingAttribute attributeMergedWithAttribute:anAttribute] forKey:name];
00278     else
00279         [attributes setObject:anAttribute forKey:name];
00280 }
00281 
00282 @end
00283 
00284 var CPThemeNameKey          = @"CPThemeNameKey",
00285     CPThemeAttributesKey    = @"CPThemeAttributesKey";
00286 
00287 @implementation CPTheme (CPCoding)
00288 
00289 - (id)initWithCoder:(CPCoder)aCoder
00290 {
00291     self = [super init];
00292 
00293     if (self)
00294     {
00295         _name = [aCoder decodeObjectForKey:CPThemeNameKey];
00296         _attributes = [aCoder decodeObjectForKey:CPThemeAttributesKey];
00297 
00298         CPThemesByName[_name] = self;
00299     }
00300 
00301     return self;
00302 }
00303 
00304 - (void)encodeWithCoder:(CPCoder)aCoder
00305 {
00306     [aCoder encodeObject:_name forKey:CPThemeNameKey];
00307     [aCoder encodeObject:_attributes forKey:CPThemeAttributesKey];
00308 }
00309 
00310 @end
00311 
00312 @implementation _CPThemeKeyedUnarchiver : CPKeyedUnarchiver
00313 {
00314     CPBundle    _bundle;
00315 }
00316 
00317 - (id)initForReadingWithData:(CPData)data bundle:(CPBundle)aBundle
00318 {
00319     self = [super initForReadingWithData:data];
00320 
00321     if (self)
00322         _bundle = aBundle;
00323 
00324     return self;
00325 }
00326 
00327 - (CPBundle)bundle
00328 {
00329     return _bundle;
00330 }
00331 
00332 - (BOOL)awakenCustomResources
00333 {
00334     return YES;
00335 }
00336 
00337 @end
00338 
00339 var CPThemeStates       = {},
00340     CPThemeStateNames   = {},
00341     CPThemeStateCount   = 0;
00342 
00343 function CPThemeState(aStateName)
00344 {
00345     var state = CPThemeStates[aStateName];
00346 
00347     if (state === undefined)
00348     {
00349         if (aStateName.indexOf('+') === -1)
00350             state = 1 << CPThemeStateCount++;
00351         else
00352         {
00353             var state = 0,
00354                 states = aStateName.split('+'),
00355                 count = states.length;
00356 
00357             while (count--)
00358             {
00359                 var stateName = states[count],
00360                     individualState = CPThemeStates[stateName];
00361 
00362                 if (individualState === undefined)
00363                 {
00364                     individualState = 1 << CPThemeStateCount++;
00365                     CPThemeStates[stateName] = individualState;
00366                     CPThemeStateNames[individualState] = stateName;
00367                 }
00368 
00369                 state |= individualState;
00370             }
00371         }
00372 
00373         CPThemeStates[aStateName] = state;
00374         CPThemeStateNames[state] = aStateName;
00375     }
00376 
00377     return state;
00378 }
00379 
00380 function CPThemeStateName(aState)
00381 {
00382     var name = CPThemeStateNames[aState];
00383 
00384     if (name !== undefined)
00385         return name;
00386 
00387     if (!(aState & (aState - 1)))
00388         return "";
00389 
00390     var state = 1,
00391         name = "";
00392 
00393     for (; state < aState; state <<= 1)
00394         if (aState & state)
00395             name += (name.length === 0 ? '' : '+') + CPThemeStateNames[state];
00396 
00397     CPThemeStateNames[aState] = name;
00398 
00399     return name;
00400 }
00401 
00402 CPThemeStateNames[0]        = "normal";
00403 CPThemeStateNormal          = CPThemeStates["normal"] = 0;
00404 CPThemeStateDisabled        = CPThemeState("disabled");
00405 CPThemeStateHovered         = CPThemeState("hovered");
00406 CPThemeStateHighlighted     = CPThemeState("highlighted");
00407 CPThemeStateSelected        = CPThemeState("selected");
00408 CPThemeStateTableDataView   = CPThemeState("tableDataView");
00409 CPThemeStateSelectedDataView = CPThemeStateSelectedTableDataView = CPThemeState("selectedTableDataView");
00410 CPThemeStateGroupRow        = CPThemeState("CPThemeStateGroupRow");
00411 CPThemeStateBezeled         = CPThemeState("bezeled");
00412 CPThemeStateBordered        = CPThemeState("bordered");
00413 CPThemeStateEditable        = CPThemeState("editable");
00414 CPThemeStateEditing         = CPThemeState("editing");
00415 CPThemeStateVertical        = CPThemeState("vertical");
00416 CPThemeStateDefault         = CPThemeState("default");
00417 CPThemeStateCircular        = CPThemeState("circular");
00418 
00419 @implementation _CPThemeAttribute : CPObject
00420 {
00421     CPString            _name;
00422     id                  _defaultValue;
00423     CPDictionary        _values;
00424 
00425     JSObject            _cache;
00426     _CPThemeAttribute   _parentAttribute;
00427 }
00428 
00429 - (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue
00430 {
00431     self = [super init];
00432 
00433     if (self)
00434     {
00435         _cache = { };
00436         _name = aName;
00437         _defaultValue = aDefaultValue;
00438         _values = [CPDictionary dictionary];
00439     }
00440 
00441     return self;
00442 }
00443 
00444 - (CPString)name
00445 {
00446     return _name;
00447 }
00448 
00449 - (id)defaultValue
00450 {
00451     return _defaultValue;
00452 }
00453 
00454 - (BOOL)hasValues
00455 {
00456     return [_values count] > 0;
00457 }
00458 
00459 - (BOOL)isTrivial
00460 {
00461     return ([_values count] === 1) && (Number([_values allKeys][0]) === CPThemeStateNormal);
00462 }
00463 
00464 - (void)setValue:(id)aValue
00465 {
00466     _cache = {};
00467 
00468     if (aValue === undefined || aValue === nil)
00469         _values = [CPDictionary dictionary];
00470     else
00471         _values = [CPDictionary dictionaryWithObject:aValue forKey:String(CPThemeStateNormal)];
00472 }
00473 
00474 - (void)setValue:(id)aValue forState:(CPThemeState)aState
00475 {
00476     _cache = { };
00477 
00478     if ((aValue === undefined) || (aValue === nil))
00479         [_values removeObjectForKey:String(aState)];
00480     else
00481         [_values setObject:aValue forKey:String(aState)];
00482 }
00483 
00484 - (id)value
00485 {
00486     return [self valueForState:CPThemeStateNormal];
00487 }
00488 
00489 - (id)valueForState:(CPThemeState)aState
00490 {
00491     var value = _cache[aState];
00492 
00493     // This can be nil.
00494     if (value !== undefined)
00495         return value;
00496 
00497     value = [_values objectForKey:String(aState)];
00498 
00499     // If we don't have a value, and we have a non-normal state...
00500     if ((value === undefined || value === nil) && aState !== CPThemeStateNormal)
00501     {
00502         // If this is a composite state (not a power of 2), find the closest partial subset match.
00503         if (aState & (aState - 1))
00504         {
00505             var highestOneCount = 0,
00506                 states = [_values allKeys],
00507                 count = states.length;
00508 
00509             while (count--)
00510             {
00511                 // states[count] is a string!
00512                 var state = Number(states[count]);
00513 
00514                 // A & B = A iff A < B
00515                 if ((state & aState) === state)
00516                 {
00517                     var oneCount = cachedNumberOfOnes[state];
00518 
00519                     if (oneCount === undefined)
00520                         oneCount = numberOfOnes(state);
00521 
00522                     if (oneCount > highestOneCount)
00523                     {
00524                         highestOneCount = oneCount;
00525                         value = [_values objectForKey:String(state)];
00526                     }
00527                 }
00528             }
00529         }
00530 
00531         // Still don't have a value? OK, let's use the normal value.
00532         if (value === undefined || value === nil)
00533             value = [_values objectForKey:String(CPThemeStateNormal)];
00534     }
00535 
00536     if (value === undefined || value === nil)
00537         value = [_parentAttribute valueForState:aState];
00538 
00539     if (value === undefined || value === nil)
00540         value = _defaultValue;
00541 
00542     _cache[aState] = value;
00543 
00544     return value;
00545 }
00546 
00547 - (void)setParentAttribute:(_CPThemeAttribute)anAttribute
00548 {
00549     if (_parentAttribute === anAttribute)
00550         return;
00551 
00552     _cache = { };
00553     _parentAttribute = anAttribute;
00554 }
00555 
00556 - (_CPThemeAttribute)attributeMergedWithAttribute:(_CPThemeAttribute)anAttribute
00557 {
00558     var mergedAttribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue];
00559 
00560     mergedAttribute._values = [_values copy];
00561     [mergedAttribute._values addEntriesFromDictionary:anAttribute._values];
00562 
00563     return mergedAttribute;
00564 }
00565 
00566 @end
00567 
00568 @implementation _CPThemeAttribute (CPCoding)
00569 
00570 - (id)initWithCoder:(CPCoder)aCoder
00571 {
00572     self = [super init];
00573 
00574     if (self)
00575     {
00576         _cache = {};
00577 
00578         _name = [aCoder decodeObjectForKey:@"name"];
00579         _values = [CPDictionary dictionary];
00580 
00581         if ([aCoder containsValueForKey:@"value"])
00582         {
00583             var state = CPThemeStateNormal;
00584 
00585             if ([aCoder containsValueForKey:@"state"])
00586                 state = CPThemeState([aCoder decodeObjectForKey:@"state"]);
00587 
00588             [_values setObject:[aCoder decodeObjectForKey:"value"] forKey:state];
00589         }
00590         else
00591         {
00592             var encodedValues = [aCoder decodeObjectForKey:@"values"],
00593                 keys = [encodedValues allKeys],
00594                 count = keys.length;
00595 
00596             while (count--)
00597             {
00598                 var key = keys[count];
00599 
00600                 [_values setObject:[encodedValues objectForKey:key] forKey:CPThemeState(key)];
00601             }
00602         }
00603     }
00604 
00605     return self;
00606 }
00607 
00608 - (void)encodeWithCoder:(CPCoder)aCoder
00609 {
00610     [aCoder encodeObject:_name forKey:@"name"];
00611 
00612     var keys = [_values allKeys],
00613         count = keys.length;
00614 
00615     if (count === 1)
00616     {
00617         var onlyKey = keys[0];
00618 
00619         if (Number(onlyKey) !== CPThemeStateNormal)
00620             [aCoder encodeObject:CPThemeStateName(Number(onlyKey)) forKey:@"state"];
00621 
00622         [aCoder encodeObject:[_values objectForKey:onlyKey] forKey:@"value"];
00623     }
00624     else
00625     {
00626         var encodedValues = [CPDictionary dictionary];
00627 
00628         while (count--)
00629         {
00630             var key = keys[count];
00631 
00632             [encodedValues setObject:[_values objectForKey:key] forKey:CPThemeStateName(Number(key))];
00633         }
00634 
00635         [aCoder encodeObject:encodedValues forKey:@"values"];
00636     }
00637 }
00638 
00639 @end
00640 
00641 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 ,
00642                             3 , 1 , 2 , 2 , 3 , 2 , 3 ,
00643                             3 , 4 , 1 , 2 , 2 , 3 , 2 ,
00644                             3 , 3 , 4 , 2 , 3 , 3 , 4 ,
00645                             3 , 4 , 4 , 5 , 1 , 2 , 2 ,
00646                             3 , 2 , 3 , 3 , 4 , 2 , 3 ,
00647                             3 , 4 , 3 , 4 , 4 , 5 , 2 ,
00648                             3 , 3 , 4 , 3 , 4 , 4 , 5 ,
00649                             3 , 4 , 4 , 5 , 4 , 5 , 5 ,
00650                             6  ];
00651 
00652 var numberOfOnes = function(aNumber)
00653 {
00654     var count = 0,
00655         slot = aNumber;
00656 
00657     while (aNumber)
00658     {
00659         ++count;
00660         aNumber &= (aNumber - 1);
00661     }
00662 
00663     cachedNumberOfOnes[slot] = count;
00664 
00665     return count;
00666 }
00667 
00668 numberOfOnes.displayName = "numberOfOnes";
00669 
00670 function CPThemeAttributeEncode(aCoder, aThemeAttribute)
00671 {
00672     var values = aThemeAttribute._values,
00673         count = [values count],
00674         key = "$a" + [aThemeAttribute name];
00675 
00676     if (count === 1)
00677     {
00678         var state = [values allKeys][0];
00679 
00680         if (Number(state) === 0)
00681         {
00682             [aCoder encodeObject:[values objectForKey:state] forKey:key];
00683 
00684             return YES;
00685         }
00686     }
00687 
00688     if (count >= 1)
00689     {
00690         [aCoder encodeObject:aThemeAttribute forKey:key];
00691 
00692         return YES;
00693     }
00694 
00695     return NO;
00696 }
00697 
00698 function CPThemeAttributeDecode(aCoder, anAttributeName, aDefaultValue, aTheme, aClass)
00699 {
00700     var key = "$a" + anAttributeName;
00701 
00702     if (![aCoder containsValueForKey:key])
00703         var attribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
00704 
00705     else
00706     {
00707         var attribute = [aCoder decodeObjectForKey:key];
00708 
00709         if (!attribute.isa || ![attribute isKindOfClass:[_CPThemeAttribute class]])
00710         {
00711             var themeAttribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
00712 
00713             [themeAttribute setValue:attribute];
00714 
00715             attribute = themeAttribute;
00716         }
00717     }
00718 
00719     if (aTheme && aClass)
00720         [attribute setParentAttribute:[aTheme attributeWithName:anAttributeName forClass:aClass]];
00721 
00722     return attribute;
00723 }
00724 
00725 /* TO AUTO CREATE THESE:
00726 function bit_count(bits)
00727     {
00728         var count = 0;
00729 
00730         while (bits)
00731         {
00732             ++count;
00733             bits &= (bits - 1);
00734         }
00735 
00736         return count ;
00737     }
00738 
00739 zeros = "000000000";
00740 
00741 function pad(string, digits)
00742 {
00743     return zeros.substr(0, digits - string.length) + string;
00744 }
00745 
00746 var str = ""
00747 str += '[';
00748 for (i = 0;i < Math.pow(2,6);++i)
00749 {
00750     str += bit_count(i) + " /*" + pad(i.toString(2),6) + "*" + "/, ";
00751 }
00752 print(str+']');
00753 
00754 */
 All Classes Files Functions Variables Defines