![]() |
API 0.9.5
|
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 */