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 @import <Foundation/CPKeyedUnarchiver.j>
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 }
00558
00559
00560
00561
00562
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572
00573
00574
00575
00576
00577
00578
00579
00580
00581
00582
00583
00584
00585
00586
00587
00588