00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import <Foundation/CPObject.j>
00024 @import <Foundation/CPString.j>
00025
00026
00027 var CPThemesByName = { },
00028 CPThemeDefaultTheme = nil;
00029
00030
00035 @implementation CPTheme : CPObject
00036 {
00037 CPString _name;
00038 CPDictionary _attributes;
00039 }
00040
00041 + (void)setDefaultTheme:(CPTheme)aTheme
00042 {
00043 CPThemeDefaultTheme = aTheme;
00044 }
00045
00046 + (CPTheme)defaultTheme
00047 {
00048 return CPThemeDefaultTheme;
00049 }
00050
00051 + (CPTheme)themeNamed:(CPString)aName
00052 {
00053 return CPThemesByName[aName];
00054 }
00055
00056 - (id)initWithName:(CPString)aName
00057 {
00058 self = [super init];
00059
00060 if (self)
00061 {
00062 _name = aName;
00063 _attributes = [CPDictionary dictionary];
00064
00065 CPThemesByName[_name] = self;
00066 }
00067
00068 return self;
00069 }
00070
00071 - (CPString)name
00072 {
00073 return _name;
00074 }
00075
00076 - (_CPThemeAttribute)_attributeWithName:(CPString)aName forClass:(CPString)aClass
00077 {
00078 var attributes = [_attributes objectForKey:aClass];
00079
00080 if (!attributes)
00081 return nil;
00082
00083 return [attributes objectForKey:aName];
00084 }
00085
00086 - (void)takeThemeFromObject:(id)anObject
00087 {
00088 var attributes = [anObject _themeAttributeDictionary],
00089 attributeName = nil,
00090 attributeNames = [attributes keyEnumerator],
00091 objectThemeClass = [[anObject class] themeClass];
00092
00093 while (attributeName = [attributeNames nextObject])
00094 [self _recordAttribute:[attributes objectForKey:attributeName] forClass:objectThemeClass];
00095 }
00096
00097 - (void)_recordAttribute:(_CPThemeAttribute)anAttribute forClass:(CPString)aClass
00098 {
00099 if (![anAttribute hasValues])
00100 return;
00101
00102 var attributes = [_attributes objectForKey:aClass];
00103
00104 if (!attributes)
00105 {
00106 attributes = [CPDictionary dictionary];
00107
00108 [_attributes setObject:attributes forKey:aClass];
00109 }
00110
00111 var name = [anAttribute name],
00112 existingAttribute = [attributes objectForKey:name];
00113
00114 if (existingAttribute)
00115 [attributes setObject:[existingAttribute attributeMergedWithAttribute:anAttribute] forKey:name];
00116 else
00117 [attributes setObject:anAttribute forKey:name];
00118 }
00119
00120 @end
00121
00122 var CPThemeNameKey = @"CPThemeNameKey",
00123 CPThemeAttributesKey = @"CPThemeAttributesKey";
00124
00125 @implementation CPTheme (CPCoding)
00126
00127 - (id)initWithCoder:(CPCoder)aCoder
00128 {
00129 self = [super init];
00130
00131 if (self)
00132 {
00133 _name = [aCoder decodeObjectForKey:CPThemeNameKey];
00134 _attributes = [aCoder decodeObjectForKey:CPThemeAttributesKey];
00135
00136 CPThemesByName[_name] = self;
00137 }
00138
00139 return self;
00140 }
00141
00142 - (void)encodeWithCoder:(CPCoder)aCoder
00143 {
00144 [aCoder encodeObject:_name forKey:CPThemeNameKey];
00145 [aCoder encodeObject:_attributes forKey:CPThemeAttributesKey];
00146 }
00147
00148 @end
00149
00150 @implementation _CPThemeKeyedUnarchiver : CPKeyedUnarchiver
00151 {
00152 CPBundle _bundle;
00153 }
00154
00155 - (id)initForReadingWithData:(CPData)data bundle:(CPBundle)aBundle
00156 {
00157 self = [super initForReadingWithData:data];
00158
00159 if (self)
00160 _bundle = aBundle;
00161
00162 return self;
00163 }
00164
00165 - (CPBundle)bundle
00166 {
00167 return _bundle;
00168 }
00169
00170 - (BOOL)awakenCustomResources
00171 {
00172 return YES;
00173 }
00174
00175 @end
00176
00177 var CPThemeStates = {},
00178 CPThemeStateNames = {},
00179 CPThemeStateCount = 0;
00180
00181 function CPThemeState(aStateName)
00182 {
00183 var state = CPThemeStates[aStateName];
00184
00185 if (state === undefined)
00186 {
00187 if (aStateName.indexOf('+') === -1)
00188 state = 1 << CPThemeStateCount++;
00189 else
00190 {
00191 var state = 0,
00192 states = aStateName.split('+'),
00193 count = states.length;
00194
00195 while (count--)
00196 {
00197 var stateName = states[count],
00198 individualState = CPThemeStates[stateName];
00199
00200 if (individualState === undefined)
00201 {
00202 individualState = 1 << CPThemeStateCount++;
00203 CPThemeStates[stateName] = individualState;
00204 CPThemeStateNames[individualState] = stateName;
00205 }
00206
00207 state |= individualState;
00208 }
00209 }
00210
00211 CPThemeStates[aStateName] = state;
00212 CPThemeStateNames[state] = aStateName;
00213 }
00214
00215 return state;
00216 }
00217
00218 function CPThemeStateName(aState)
00219 {
00220 var name = CPThemeStateNames[aState];
00221
00222 if (name !== undefined)
00223 return name;
00224
00225 if (!(aState & (aState - 1)))
00226 return "";
00227
00228 var state = 1,
00229 name = "";
00230
00231 for (; state < aState; state <<= 1)
00232 if (aState & state)
00233 name += (name.length === 0 ? '' : '+') + CPThemeStateNames[state];
00234
00235 CPThemeStateNames[aState] = name;
00236
00237 return name;
00238 }
00239
00240 CPThemeStateNames[0] = "normal";
00241 CPThemeStateNormal = CPThemeStates["normal"] = 0;
00242 CPThemeStateDisabled = CPThemeState("disabled");
00243 CPThemeStateHighlighted = CPThemeState("highlighted");
00244 CPThemeStateSelected = CPThemeState("selected");
00245 CPThemeStateBezeled = CPThemeState("bezeled");
00246 CPThemeStateBordered = CPThemeState("bordered");
00247 CPThemeStateEditable = CPThemeState("editable");
00248 CPThemeStateEditing = CPThemeState("editing");
00249 CPThemeStateVertical = CPThemeState("vertical");
00250 CPThemeStateDefault = CPThemeState("default");
00251 CPThemeStateCircular = CPThemeState("circular");
00252
00253 @implementation _CPThemeAttribute : CPObject
00254 {
00255 CPString _name;
00256 id _defaultValue;
00257 CPDictionary _values;
00258
00259 JSObject _cache;
00260 CPThemeAttribute _parentAttribute;
00261 }
00262
00263 - (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue
00264 {
00265 self = [super init];
00266
00267 if (self)
00268 {
00269 _cache = { };
00270 _name = aName;
00271 _defaultValue = aDefaultValue;
00272 _values = [CPDictionary dictionary];
00273 }
00274
00275 return self;
00276 }
00277
00278 - (CPString)name
00279 {
00280 return _name;
00281 }
00282
00283 - (id)defaultValue
00284 {
00285 return _defaultValue;
00286 }
00287
00288 - (BOOL)hasValues
00289 {
00290 return [_values count] > 0;
00291 }
00292
00293 - (BOOL)isTrivial
00294 {
00295 return ([_values count] === 1) && (Number([_values allKeys][0]) === CPThemeStateNormal);
00296 }
00297
00298 - (void)setValue:(id)aValue
00299 {
00300 _cache = {};
00301
00302 if (aValue === undefined || aValue === nil)
00303 _values = [CPDictionary dictionary];
00304 else
00305 _values = [CPDictionary dictionaryWithObject:aValue forKey:String(CPThemeStateNormal)];
00306 }
00307
00308 - (void)setValue:(id)aValue forState:(CPThemeState)aState
00309 {
00310 _cache = { };
00311
00312 if ((aValue === undefined) || (aValue === nil))
00313 [_values removeObjectForKey:String(aState)];
00314 else
00315 [_values setObject:aValue forKey:String(aState)];
00316 }
00317
00318 - (id)value
00319 {
00320 return [self valueForState:CPThemeStateNormal];
00321 }
00322
00323 - (id)valueForState:(CPThemeState)aState
00324 {
00325 var value = _cache[aState];
00326
00327
00328 if (value !== undefined)
00329 return value;
00330
00331 value = [_values objectForKey:String(aState)];
00332
00333
00334 if ((value === undefined || value === nil) && aState !== CPThemeStateNormal)
00335 {
00336
00337 if (aState & (aState - 1))
00338 {
00339 var highestOneCount = 0,
00340 states = [_values allKeys],
00341 count = states.length;
00342
00343 while (count--)
00344 {
00345
00346 var state = Number(states[count]);
00347
00348
00349 if ((state & aState) === state)
00350 {
00351 var oneCount = cachedNumberOfOnes[state];
00352
00353 if (oneCount === undefined)
00354 oneCount = numberOfOnes(state);
00355
00356 if (oneCount > highestOneCount)
00357 {
00358 highestOneCount = oneCount;
00359 value = [_values objectForKey:String(state)];
00360 }
00361 }
00362 }
00363 }
00364
00365
00366 if (value === undefined || value === nil)
00367 value = [_values objectForKey:String(CPThemeStateNormal)];
00368 }
00369
00370 if (value === undefined || value === nil)
00371 value = [_parentAttribute valueForState:aState];
00372
00373 if (value === undefined || value === nil)
00374 value = _defaultValue;
00375
00376 _cache[aState] = value;
00377
00378 return value;
00379 }
00380
00381 - (void)setParentAttribute:(CPThemeAttribute)anAttribute
00382 {
00383 if (_parentAttribute === anAttribute)
00384 return;
00385
00386 _cache = { };
00387 _parentAttribute = anAttribute;
00388 }
00389
00390 - (CPThemeAttribute)attributeMergedWithAttribute:(_CPThemeAttribute)anAttribute
00391 {
00392 var mergedAttribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue];
00393
00394 mergedAttribute._values = [_values copy];
00395 [mergedAttribute._values addEntriesFromDictionary:anAttribute._values];
00396
00397 return mergedAttribute;
00398 }
00399
00400 @end
00401
00402 @implementation _CPThemeAttribute (CPCoding)
00403
00404 - (id)initWithCoder:(CPCoder)aCoder
00405 {
00406 self = [super init];
00407
00408 if (self)
00409 {
00410 _cache = {};
00411
00412 _name = [aCoder decodeObjectForKey:@"name"];
00413 _values = [CPDictionary dictionary];
00414
00415 if ([aCoder containsValueForKey:@"value"])
00416 {
00417 var state = CPThemeStateNormal;
00418
00419 if ([aCoder containsValueForKey:@"state"])
00420 state = CPThemeState([aCoder decodeObjectForKey:@"state"]);
00421
00422 [_values setObject:[aCoder decodeObjectForKey:"value"] forKey:state];
00423 }
00424 else
00425 {
00426 var encodedValues = [aCoder decodeObjectForKey:@"values"],
00427 keys = [encodedValues allKeys],
00428 count = keys.length;
00429
00430 while (count--)
00431 {
00432 var key = keys[count];
00433
00434 [_values setObject:[encodedValues objectForKey:key] forKey:CPThemeState(key)];
00435 }
00436 }
00437 }
00438
00439 return self;
00440 }
00441
00442 - (void)encodeWithCoder:(CPCoder)aCoder
00443 {
00444 [aCoder encodeObject:_name forKey:@"name"];
00445
00446 var keys = [_values allKeys],
00447 count = keys.length;
00448
00449 if (count === 1)
00450 {
00451 var onlyKey = keys[0];
00452
00453 if (Number(onlyKey) !== CPThemeStateNormal)
00454 [aCoder encodeObject:CPThemeStateName(Number(onlyKey)) forKey:@"state"];
00455
00456 [aCoder encodeObject:[_values objectForKey:onlyKey] forKey:@"value"];
00457 }
00458 else
00459 {
00460 var encodedValues = [CPDictionary dictionary];
00461
00462 while (count--)
00463 {
00464 var key = keys[count];
00465
00466 [encodedValues setObject:[_values objectForKey:key] forKey:CPThemeStateName(Number(key))];
00467 }
00468
00469 [aCoder encodeObject:encodedValues forKey:@"values"];
00470 }
00471 }
00472
00473 @end
00474
00475 var cachedNumberOfOnes = [ 0 , 1 , 1 , 2 , 1 , 2 , 2 ,
00476 3 , 1 , 2 , 2 , 3 , 2 , 3 ,
00477 3 , 4 , 1 , 2 , 2 , 3 , 2 ,
00478 3 , 3 , 4 , 2 , 3 , 3 , 4 ,
00479 3 , 4 , 4 , 5 , 1 , 2 , 2 ,
00480 3 , 2 , 3 , 3 , 4 , 2 , 3 ,
00481 3 , 4 , 3 , 4 , 4 , 5 , 2 ,
00482 3 , 3 , 4 , 3 , 4 , 4 , 5 ,
00483 3 , 4 , 4 , 5 , 4 , 5 , 5 ,
00484 6 ];
00485
00486 var numberOfOnes = function(aNumber)
00487 {
00488 var count = 0,
00489 slot = aNumber;
00490
00491 while (aNumber)
00492 {
00493 ++count;
00494 aNumber &= (aNumber - 1);
00495 }
00496
00497 cachedNumberOfOnes[slot] = count;
00498
00499 return count;
00500 }
00501
00502 numberOfOnes.displayName = "numberOfOnes";
00503
00504 function CPThemeAttributeEncode(aCoder, aThemeAttribute)
00505 {
00506 var values = aThemeAttribute._values,
00507 count = [values count],
00508 key = "$a" + [aThemeAttribute name];
00509
00510 if (count === 1)
00511 {
00512 var state = [values allKeys][0];
00513
00514 if (Number(state) === 0)
00515 {
00516 [aCoder encodeObject:[values objectForKey:state] forKey:key];
00517
00518 return YES;
00519 }
00520 }
00521
00522 if (count >= 1)
00523 {
00524 [aCoder encodeObject:aThemeAttribute forKey:key];
00525
00526 return YES;
00527 }
00528
00529 return NO;
00530 }
00531
00532 function CPThemeAttributeDecode(aCoder, anAttributeName, aDefaultValue, aTheme, aClass)
00533 {
00534 var key = "$a" + anAttributeName;
00535
00536 if (![aCoder containsValueForKey:key])
00537 var attribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
00538
00539 else
00540 {
00541 var attribute = [aCoder decodeObjectForKey:key];
00542
00543 if (!attribute.isa || ![attribute isKindOfClass:[_CPThemeAttribute class]])
00544 {
00545 var themeAttribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
00546
00547 [themeAttribute setValue:attribute];
00548
00549 attribute = themeAttribute;
00550 }
00551 }
00552
00553 if (aTheme && aClass)
00554 [attribute setParentAttribute:[aTheme _attributeWithName:anAttributeName forClass:aClass]];
00555
00556 return attribute;
00557 }