mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
The purpose of this feature is to create theme for every object of the AppKit. Previously, we could not theme object like CPColor, however CPColor could be very interesting to theme, value like alternateSelectedControlColor were hard coded in the framework. With this PR, we can now theme a CPColor and take some values from the current theme of the application. Then, this PR offers the possibility to refactor the themeDescriptors, some classes contains theme attributes for other class (CPTabView and CPTabViewItem for instance). How doe it work ? A new category _CPObject+Theme.j has been added to the AppKit. This category contains every theme methods needed to theme an object (previously this category was in CPView). We add utils method like _encodeThemeObjectsWithCoder: and _decodeThemeObjectsWithCoder: in this category to be able to code and decode easily a coder for every object (this is generic). This PR did not refactor the themeDescriptors, it only add this new mechanism for the methods alternateSelectedControlColor and secondarySelectedControlColor of CPColor. We will need to refactor that gradually
826 lines
22 KiB
Plaintext
826 lines
22 KiB
Plaintext
/*
|
|
* CPTheme.j
|
|
* AppKit
|
|
*
|
|
* Created by Francisco Tolmasky.
|
|
* Copyright 2009, 280 North, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
@import <Foundation/CPObject.j>
|
|
@import <Foundation/CPMutableArray.j>
|
|
@import <Foundation/CPString.j>
|
|
@import <Foundation/CPKeyedUnarchiver.j>
|
|
|
|
@class CPView
|
|
@class _CPThemeAttribute
|
|
|
|
var CPThemesByName = { },
|
|
CPThemeDefaultTheme = nil,
|
|
CPThemeDefaultHudTheme = nil;
|
|
|
|
|
|
/*!
|
|
@ingroup appkit
|
|
*/
|
|
|
|
@implementation CPTheme : CPObject
|
|
{
|
|
CPString _name;
|
|
CPDictionary _attributes;
|
|
}
|
|
|
|
+ (void)setDefaultTheme:(CPTheme)aTheme
|
|
{
|
|
CPThemeDefaultTheme = aTheme;
|
|
}
|
|
|
|
+ (CPTheme)defaultTheme
|
|
{
|
|
return CPThemeDefaultTheme;
|
|
}
|
|
|
|
/*!
|
|
Set the default HUD theme. If set to nil, the default described in defaultHudTheme
|
|
will be used.
|
|
*/
|
|
+ (void)setDefaultHudTheme:(CPTheme)aTheme
|
|
{
|
|
CPThemeDefaultHudTheme = aTheme;
|
|
}
|
|
|
|
/*!
|
|
The default HUD theme is (sometimes) used for windows with the CPHUDBackgroundWindowMask
|
|
style mask. The default is theme with the name of the default theme with -HUD appended
|
|
at the end.
|
|
*/
|
|
+ (CPTheme)defaultHudTheme
|
|
{
|
|
if (!CPThemeDefaultHudTheme)
|
|
CPThemeDefaultHudTheme = [CPTheme themeNamed:[[self defaultTheme] name] + "-HUD"];
|
|
return CPThemeDefaultHudTheme;
|
|
}
|
|
|
|
+ (CPTheme)themeNamed:(CPString)aName
|
|
{
|
|
return CPThemesByName[aName];
|
|
}
|
|
|
|
- (id)initWithName:(CPString)aName
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_name = aName;
|
|
_attributes = @{};
|
|
|
|
CPThemesByName[_name] = self;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (CPString)name
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
/*!
|
|
Returns an array of names of themed classes defined in this theme, as found in its
|
|
ThemeDescriptors.j file.
|
|
|
|
NOTE: The names are not class names (such as "CPButton"), but the names returned
|
|
by the class' +defaultThemeClass method. For example, the name for CPCheckBox is "check-box",
|
|
as defined in CPCheckBox::themeClass.
|
|
*/
|
|
- (CPArray)classNames
|
|
{
|
|
return [_attributes allKeys];
|
|
}
|
|
|
|
/*!
|
|
Returns a dictionary of all theme attributes defined for the given class, as found in the
|
|
theme's ThemeDescriptors.j file. The keys of the dictionary are attribute names, and the values
|
|
are instances of _CPThemeAttribute.
|
|
|
|
For a description of valid values for \c aClass, see \ref attributeNamesForClass:.
|
|
|
|
@param aClass The themed class whose attributes you want to retrieve
|
|
@return A dictionary of attributes or nil
|
|
*/
|
|
- (CPDictionary)attributesForClass:(id)aClass
|
|
{
|
|
if (!aClass)
|
|
return nil;
|
|
|
|
var className = nil;
|
|
|
|
if ([aClass isKindOfClass:[CPString class]])
|
|
{
|
|
// See if it is a class name
|
|
var theClass = CPClassFromString(aClass);
|
|
|
|
if (theClass)
|
|
aClass = theClass;
|
|
else
|
|
className = aClass;
|
|
}
|
|
|
|
if (!className)
|
|
{
|
|
if ([aClass respondsToSelector:@selector(defaultThemeClass)])
|
|
{
|
|
className = [aClass defaultThemeClass];
|
|
}
|
|
else if ([aClass respondsToSelector:@selector(themeClass)])
|
|
{
|
|
CPLog.warn(@"%@ themeClass is deprecated in favor of defaultThemeClass", CPStringFromClass(aClass));
|
|
className = [aClass themeClass];
|
|
}
|
|
else
|
|
{
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
return [_attributes objectForKey:className];
|
|
}
|
|
|
|
/*!
|
|
Returns an array of names of all theme attributes defined for the given class, as found in the
|
|
theme's ThemeDescriptors.j file.
|
|
|
|
The \c aClass parameter can be one of the following:
|
|
|
|
- A class instance, for example the result of [CPCheckBox class]. The class must be a subclass
|
|
of CPView.
|
|
- A class name, for example "CPCheckBox".
|
|
- A themed class name, for example "check-box".
|
|
|
|
If \c aClass does not refer to a themed class in this theme, nil is returned.
|
|
|
|
@param aClass The themed class whose attributes you want to retrieve
|
|
@return An array of attribute names or nil
|
|
*/
|
|
- (CPDictionary)attributeNamesForClass:(id)aClass
|
|
{
|
|
var attributes = [self attributesForClass:aClass];
|
|
|
|
if (attributes)
|
|
return [attributes allKeys];
|
|
else
|
|
return [CPArray array];
|
|
}
|
|
|
|
/*!
|
|
Returns a theme attribute defined for the given class, as found in the
|
|
theme's ThemeDescriptors.j file.
|
|
|
|
\c aName should be the attribute name as you would pass to the method
|
|
CPView::valueForThemeAttribute:.
|
|
|
|
For a description of valid values for \c aClass, see \ref attributeNamesForClass:.
|
|
|
|
@param aName The name of the attribute you want to retrieve
|
|
@param aClass The themed class in which to look for the attribute
|
|
@return An instance of _CPThemeAttribute or nil
|
|
*/
|
|
- (_CPThemeAttribute)attributeWithName:(CPString)aName forClass:(id)aClass
|
|
{
|
|
var attributes = [self attributesForClass:aClass];
|
|
|
|
if (!attributes)
|
|
return nil;
|
|
|
|
return [attributes objectForKey:aName];
|
|
}
|
|
|
|
/*!
|
|
Returns the value for a theme attribute in its normal state, as defined for the given class
|
|
in the theme's ThemeDescriptors.j file.
|
|
|
|
\c aName should be the attribute name as you would pass to the method
|
|
CPView::valueForThemeAttribute:.
|
|
|
|
For a description of valid values for \c aClass, see \ref attributeNamesForClass:.
|
|
|
|
@param aName The name of the attribute whose value you want to retrieve
|
|
@param aClass The themed class in which to look for the attribute
|
|
@return A value or nil
|
|
*/
|
|
- (id)valueForAttributeWithName:(CPString)aName forClass:(id)aClass
|
|
{
|
|
return [self valueForAttributeWithName:aName inState:CPThemeStateNormal forClass:aClass];
|
|
}
|
|
|
|
/*!
|
|
Returns the value for a theme attribute in a given state, as defined for the given class
|
|
in the theme's ThemeDescriptors.j file. This is the equivalent of the method
|
|
CPView::valueForThemeAttribute:inState:, but retrieves the value from the theme definition as
|
|
opposed to a single view's current theme state.
|
|
|
|
For a description of valid values for \c aClass, see \ref attributeNamesForClass:.
|
|
|
|
@param aName The name of the attribute whose value you want to retrieve
|
|
@param aState The state qualifier for the attribute
|
|
@param aClass The themed class in which to look for the attribute
|
|
@return A value or nil
|
|
*/
|
|
- (id)valueForAttributeWithName:(CPString)aName inState:(ThemeState)aState forClass:(id)aClass
|
|
{
|
|
var attribute = [self attributeWithName:aName forClass:aClass];
|
|
|
|
if (!attribute)
|
|
return nil;
|
|
|
|
return [attribute valueForState:aState];
|
|
}
|
|
|
|
- (void)takeThemeFromObject:(id)anObject
|
|
{
|
|
var attributes = [anObject _themeAttributeDictionary],
|
|
attributeName = nil,
|
|
attributeNames = [attributes keyEnumerator],
|
|
objectThemeClass = [anObject themeClass];
|
|
|
|
while ((attributeName = [attributeNames nextObject]) !== nil)
|
|
[self _recordAttribute:[attributes objectForKey:attributeName] forClass:objectThemeClass];
|
|
}
|
|
|
|
- (void)_recordAttribute:(_CPThemeAttribute)anAttribute forClass:(CPString)aClass
|
|
{
|
|
if (![anAttribute hasValues])
|
|
return;
|
|
|
|
var attributes = [_attributes objectForKey:aClass];
|
|
|
|
if (!attributes)
|
|
{
|
|
attributes = @{};
|
|
|
|
[_attributes setObject:attributes forKey:aClass];
|
|
}
|
|
|
|
var name = [anAttribute name],
|
|
existingAttribute = [attributes objectForKey:name];
|
|
|
|
if (existingAttribute)
|
|
[attributes setObject:[existingAttribute attributeMergedWithAttribute:anAttribute] forKey:name];
|
|
else
|
|
[attributes setObject:anAttribute forKey:name];
|
|
}
|
|
|
|
@end
|
|
|
|
var CPThemeNameKey = @"CPThemeNameKey",
|
|
CPThemeAttributesKey = @"CPThemeAttributesKey";
|
|
|
|
@implementation CPTheme (CPCoding)
|
|
|
|
- (id)initWithCoder:(CPCoder)aCoder
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_name = [aCoder decodeObjectForKey:CPThemeNameKey];
|
|
_attributes = [aCoder decodeObjectForKey:CPThemeAttributesKey];
|
|
|
|
CPThemesByName[_name] = self;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
[aCoder encodeObject:_name forKey:CPThemeNameKey];
|
|
[aCoder encodeObject:_attributes forKey:CPThemeAttributesKey];
|
|
}
|
|
|
|
@end
|
|
|
|
/*!
|
|
* ThemeStates are immutable objects representing a particular ThemeState. Applications should never be creating
|
|
* ThemeStates directly but should instead use the CPThemeState function.
|
|
*/
|
|
function ThemeState(stateNames)
|
|
{
|
|
var stateNameKeys = [];
|
|
this._stateNames = {};
|
|
|
|
for (key in stateNames)
|
|
{
|
|
if (!stateNames.hasOwnProperty(key))
|
|
continue;
|
|
if (key !== 'normal')
|
|
{
|
|
this._stateNames[key] = true;
|
|
stateNameKeys.push(key);
|
|
}
|
|
}
|
|
|
|
if (stateNameKeys.length === 0)
|
|
{
|
|
stateNameKeys.push('normal');
|
|
this._stateNames['normal'] = true;
|
|
}
|
|
|
|
stateNameKeys.sort();
|
|
this._stateNameString = stateNameKeys[0];
|
|
|
|
var stateNameLength = stateNameKeys.length;
|
|
for (var stateIndex = 1; stateIndex < stateNameLength; stateIndex++)
|
|
this._stateNameString = this._stateNameString + "+" + stateNameKeys[stateIndex];
|
|
this._stateNameCount = stateNameLength;
|
|
}
|
|
|
|
ThemeState.prototype.toString = function()
|
|
{
|
|
return this._stateNameString;
|
|
}
|
|
|
|
ThemeState.prototype.hasThemeState = function(aState)
|
|
{
|
|
if (!aState || !aState._stateNames)
|
|
return false;
|
|
|
|
// We can do this in O(n) because both states have their stateNames already sorted.
|
|
for (var stateName in aState._stateNames)
|
|
{
|
|
if (!aState._stateNames.hasOwnProperty(stateName))
|
|
continue;
|
|
|
|
if (!this._stateNames[stateName])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ThemeState.prototype.isSubsetOf = function(aState)
|
|
{
|
|
if (aState._stateNameCount < this._stateNameCount)
|
|
return false;
|
|
|
|
for (var key in this._stateNames)
|
|
{
|
|
if (!this._stateNames.hasOwnProperty(key))
|
|
continue;
|
|
|
|
if (!aState._stateNames[key])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ThemeState.prototype.without = function(aState)
|
|
{
|
|
if (!aState || aState === [CPNull null])
|
|
return this;
|
|
|
|
var newStates = {};
|
|
for (var stateName in this._stateNames)
|
|
{
|
|
if (!this._stateNames.hasOwnProperty(stateName))
|
|
continue;
|
|
|
|
if (!aState._stateNames[stateName])
|
|
newStates[stateName] = true;
|
|
}
|
|
|
|
return ThemeState._cacheThemeState(new ThemeState(newStates));
|
|
}
|
|
|
|
ThemeState.prototype.and = function(aState)
|
|
{
|
|
return CPThemeState(this, aState);
|
|
}
|
|
|
|
var CPThemeStates = {};
|
|
|
|
ThemeState._cacheThemeState = function(aState)
|
|
{
|
|
// We do this caching so themeState equality works. Basically, doing CPThemeState('foo+bar') === CPThemeState('bar', 'foo') will return true.
|
|
var themeState = CPThemeStates[String(aState)];
|
|
if (themeState === undefined)
|
|
{
|
|
themeState = aState;
|
|
CPThemeStates[String(themeState)] = themeState;
|
|
}
|
|
return themeState;
|
|
}
|
|
|
|
/*!
|
|
* This method can be called in multiple ways:
|
|
* CPThemeState('state1') - creates a new CPThemeState that corresponds to the string 'state1'
|
|
* CPThemeState('state1', 'state2') - creates a new composite CPThemeState made up of both 'state1' or 'state2'
|
|
* CPThemeState('state1+state2') - The same as CPThemeState('state1', 'state2')
|
|
* CPThemeState(state1, state2) - creates a new composite CPThemeState made up of state1 and state2
|
|
* where state1 and state2 are not strings but are themselves CPThemeStates.
|
|
*/
|
|
function CPThemeState()
|
|
{
|
|
if (arguments.length < 1)
|
|
throw "CPThemeState() must be called with at least one string argument";
|
|
|
|
var themeState;
|
|
if (arguments.length === 1 && typeof arguments[0] === 'string')
|
|
{
|
|
themeState = CPThemeStates[arguments[0]];
|
|
if (themeState !== undefined)
|
|
return themeState;
|
|
}
|
|
|
|
var stateNames = {};
|
|
for (var argIndex = 0; argIndex < arguments.length; argIndex++)
|
|
{
|
|
if (arguments[argIndex] === [CPNull null] || !arguments[argIndex])
|
|
continue;
|
|
|
|
if (typeof arguments[argIndex] === 'object')
|
|
{
|
|
for (var stateName in arguments[argIndex]._stateNames)
|
|
{
|
|
if (!arguments[argIndex]._stateNames.hasOwnProperty(stateName))
|
|
continue;
|
|
stateNames[stateName] = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var allNames = arguments[argIndex].split('+');
|
|
for (var nameIndex = 0; nameIndex < allNames.length; nameIndex++)
|
|
stateNames[allNames[nameIndex]] = true;
|
|
}
|
|
}
|
|
|
|
themeState = ThemeState._cacheThemeState(new ThemeState(stateNames));
|
|
return themeState;
|
|
}
|
|
|
|
@implementation _CPThemeKeyedUnarchiver : CPKeyedUnarchiver
|
|
{
|
|
CPBundle _bundle;
|
|
}
|
|
|
|
- (id)initForReadingWithData:(CPData)data bundle:(CPBundle)aBundle
|
|
{
|
|
self = [super initForReadingWithData:data];
|
|
|
|
if (self)
|
|
_bundle = aBundle;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (CPBundle)bundle
|
|
{
|
|
return _bundle;
|
|
}
|
|
|
|
- (BOOL)awakenCustomResources
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
@end
|
|
|
|
CPThemeStateNormal = CPThemeState("normal");
|
|
CPThemeStateDisabled = CPThemeState("disabled");
|
|
CPThemeStateHovered = CPThemeState("hovered");
|
|
CPThemeStateHighlighted = CPThemeState("highlighted");
|
|
CPThemeStateSelected = CPThemeState("selected");
|
|
CPThemeStateTableDataView = CPThemeState("tableDataView");
|
|
CPThemeStateSelectedDataView = CPThemeState("selectedTableDataView");
|
|
CPThemeStateGroupRow = CPThemeState("CPThemeStateGroupRow");
|
|
CPThemeStateBezeled = CPThemeState("bezeled");
|
|
CPThemeStateBordered = CPThemeState("bordered");
|
|
CPThemeStateEditable = CPThemeState("editable");
|
|
CPThemeStateEditing = CPThemeState("editing");
|
|
CPThemeStateVertical = CPThemeState("vertical");
|
|
CPThemeStateDefault = CPThemeState("default");
|
|
CPThemeStateCircular = CPThemeState("circular");
|
|
CPThemeStateAutocompleting = CPThemeState("autocompleting");
|
|
CPThemeStateFirstResponder = CPThemeState("firstResponder");
|
|
CPThemeStateMainWindow = CPThemeState("mainWindow");
|
|
CPThemeStateKeyWindow = CPThemeState("keyWindow");
|
|
CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular");
|
|
CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall");
|
|
CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
|
|
|
|
@implementation _CPThemeAttribute : CPObject
|
|
{
|
|
CPString _name;
|
|
id _defaultValue;
|
|
CPDictionary _values @accessors(readonly, getter=values);
|
|
|
|
JSObject _cache;
|
|
_CPThemeAttribute _themeDefaultAttribute;
|
|
}
|
|
|
|
- (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_cache = { };
|
|
_name = aName;
|
|
_defaultValue = aDefaultValue;
|
|
_values = @{};
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (CPString)name
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
- (id)defaultValue
|
|
{
|
|
return _defaultValue;
|
|
}
|
|
|
|
- (BOOL)hasValues
|
|
{
|
|
return [_values count] > 0;
|
|
}
|
|
|
|
- (void)setValue:(id)aValue
|
|
{
|
|
_cache = {};
|
|
|
|
if (aValue === undefined || aValue === nil)
|
|
_values = @{};
|
|
else
|
|
_values = @{ String(CPThemeStateNormal): aValue };
|
|
}
|
|
|
|
- (void)setValue:(id)aValue forState:(ThemeState)aState
|
|
{
|
|
_cache = { };
|
|
|
|
if ((aValue === undefined) || (aValue === nil))
|
|
[_values removeObjectForKey:String(aState)];
|
|
else
|
|
[_values setObject:aValue forKey:String(aState)];
|
|
}
|
|
|
|
- (id)value
|
|
{
|
|
return [self valueForState:CPThemeStateNormal];
|
|
}
|
|
|
|
- (id)valueForState:(ThemeState)aState
|
|
{
|
|
var stateName = String(aState),
|
|
value = _cache[stateName];
|
|
|
|
// This can be nil.
|
|
if (value !== undefined)
|
|
return value;
|
|
|
|
value = [_values objectForKey:stateName];
|
|
|
|
if (value === undefined || value === nil)
|
|
{
|
|
// If this is a composite state, find the closest partial subset match.
|
|
if (aState._stateNameCount > 1)
|
|
{
|
|
var states = [_values allKeys],
|
|
count = states.length,
|
|
largestThemeState = 0;
|
|
|
|
while (count--)
|
|
{
|
|
var stateObject = CPThemeState(states[count]);
|
|
|
|
if (stateObject.isSubsetOf(aState) && stateObject._stateNameCount > largestThemeState)
|
|
{
|
|
value = [_values objectForKey:states[count]];
|
|
largestThemeState = stateObject._stateNameCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Still don't have a value? OK, let's use the normal value.
|
|
if (value === undefined || value === nil)
|
|
value = [_values objectForKey:String(CPThemeStateNormal)];
|
|
}
|
|
|
|
if (value === undefined || value === nil)
|
|
value = [_themeDefaultAttribute valueForState:aState];
|
|
|
|
if (value === undefined || value === nil)
|
|
{
|
|
value = _defaultValue;
|
|
|
|
// Class theme attributes cannot use nil because it's a dictionary.
|
|
// So transform CPNull into nil.
|
|
if (value === [CPNull null])
|
|
value = nil;
|
|
}
|
|
|
|
_cache[stateName] = value;
|
|
|
|
return value;
|
|
}
|
|
|
|
- (void)setParentAttribute:(_CPThemeAttribute)anAttribute
|
|
{
|
|
if (_themeDefaultAttribute === anAttribute)
|
|
return;
|
|
|
|
_cache = { };
|
|
_themeDefaultAttribute = anAttribute;
|
|
}
|
|
|
|
- (_CPThemeAttribute)attributeMergedWithAttribute:(_CPThemeAttribute)anAttribute
|
|
{
|
|
var mergedAttribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue];
|
|
|
|
mergedAttribute._values = [_values copy];
|
|
[mergedAttribute._values addEntriesFromDictionary:anAttribute._values];
|
|
|
|
return mergedAttribute;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _CPThemeAttribute (CPCoding)
|
|
|
|
- (id)initWithCoder:(CPCoder)aCoder
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_cache = {};
|
|
|
|
_name = [aCoder decodeObjectForKey:@"name"];
|
|
_defaultValue = [aCoder decodeObjectForKey:@"defaultValue"];
|
|
_values = @{};
|
|
|
|
if ([aCoder containsValueForKey:@"value"])
|
|
{
|
|
var state = String(CPThemeStateNormal);
|
|
|
|
if ([aCoder containsValueForKey:@"state"])
|
|
state = [aCoder decodeObjectForKey:@"state"];
|
|
|
|
[_values setObject:[aCoder decodeObjectForKey:"value"] forKey:state];
|
|
}
|
|
else
|
|
{
|
|
var encodedValues = [aCoder decodeObjectForKey:@"values"],
|
|
keys = [encodedValues allKeys],
|
|
count = keys.length;
|
|
|
|
while (count--)
|
|
{
|
|
var key = keys[count];
|
|
|
|
[_values setObject:[encodedValues objectForKey:key] forKey:key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
[aCoder encodeObject:_name forKey:@"name"];
|
|
[aCoder encodeObject:_defaultValue forKey:@"defaultValue"];
|
|
|
|
var keys = [_values allKeys],
|
|
count = keys.length;
|
|
|
|
if (count === 1)
|
|
{
|
|
var onlyKey = keys[0];
|
|
|
|
if (onlyKey !== String(CPThemeStateNormal))
|
|
[aCoder encodeObject:onlyKey forKey:@"state"];
|
|
|
|
[aCoder encodeObject:[_values objectForKey:onlyKey] forKey:@"value"];
|
|
}
|
|
else
|
|
{
|
|
var encodedValues = @{};
|
|
|
|
while (count--)
|
|
{
|
|
var key = keys[count];
|
|
|
|
[encodedValues setObject:[_values objectForKey:key] forKey:key];
|
|
}
|
|
|
|
[aCoder encodeObject:encodedValues forKey:@"values"];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
function CPThemeAttributeEncode(aCoder, aThemeAttribute)
|
|
{
|
|
var values = aThemeAttribute._values,
|
|
count = [values count],
|
|
key = "$a" + [aThemeAttribute name];
|
|
|
|
if (count === 1)
|
|
{
|
|
var state = [values allKeys][0];
|
|
|
|
if (state === String(CPThemeStateNormal))
|
|
{
|
|
[aCoder encodeObject:[values objectForKey:state] forKey:key];
|
|
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
if (count >= 1)
|
|
{
|
|
[aCoder encodeObject:aThemeAttribute forKey:key];
|
|
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
function CPThemeAttributeDecode(aCoder, anAttributeName, aDefaultValue, aTheme, aClass)
|
|
{
|
|
var key = "$a" + anAttributeName;
|
|
|
|
if (![aCoder containsValueForKey:key])
|
|
var attribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
|
|
|
|
else
|
|
{
|
|
var attribute = [aCoder decodeObjectForKey:key];
|
|
|
|
if (!attribute || !attribute.isa || ![attribute isKindOfClass:[_CPThemeAttribute class]])
|
|
{
|
|
var themeAttribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
|
|
|
|
[themeAttribute setValue:attribute];
|
|
|
|
attribute = themeAttribute;
|
|
}
|
|
}
|
|
|
|
if (aTheme && aClass)
|
|
[attribute setParentAttribute:[aTheme attributeWithName:anAttributeName forClass:aClass]];
|
|
|
|
return attribute;
|
|
}
|
|
|
|
/* TO AUTO CREATE THESE:
|
|
function bit_count(bits)
|
|
{
|
|
var count = 0;
|
|
|
|
while (bits)
|
|
{
|
|
++count;
|
|
bits &= (bits - 1);
|
|
}
|
|
|
|
return count ;
|
|
}
|
|
|
|
zeros = "000000000";
|
|
|
|
function pad(string, digits)
|
|
{
|
|
return zeros.substr(0, digits - string.length) + string;
|
|
}
|
|
|
|
var str = ""
|
|
str += '[';
|
|
for (i = 0;i < Math.pow(2,6);++i)
|
|
{
|
|
str += bit_count(i) + " /*" + pad(i.toString(2),6) + "*" + "/, ";
|
|
}
|
|
print(str+']');
|
|
|
|
*/
|