/* * 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 @import @import @import "CPThemedAttribute.j" @implementation CPBlend : CPObject { CPBundle _bundle; CPArray _themes @accessors(readonly); id _loadDelegate; } - (id)initWithContentsOfURL:(CPURL)aURL { self = [super init]; if (self) { _bundle = [[CPBundle alloc] initWithPath:aURL + "/Info.plist"]; } return self; } - (void)loadWithDelegate:(id)aDelegate { _loadDelegate = aDelegate; [_bundle loadWithDelegate:self]; } - (void)bundleDidFinishLoading:(CPBundle)aBundle { var paths = [_bundle objectForInfoDictionaryKey:@"CPBundleReplacedFiles"], index = 0, count = paths.length, bundlePath = [_bundle bundlePath]; while (count--) { var path = paths[count]; if ([path pathExtension] === "keyedtheme") { var unarchiver = [[_CPThemeKeyedUnarchiver alloc] initForReadingWithData:[CPData dataWithString:objj_files[bundlePath + '/' + path].contents] bundle:_bundle]; [unarchiver decodeObjectForKey:@"root"]; [unarchiver finishDecoding]; } } [_loadDelegate blendDidFinishLoading:self]; } @end var CPThemesByName = nil, CPThemeDefaultTheme = nil, CPThemeViewClass = Nil, CPThemeWindowClass = Nil; @implementation CPTheme : CPObject { CPString _name; Class _activeClass; JSObject _classTable; } + (void)initialize { if (self !== [CPTheme class]) return; CPThemesByName = {}; CPThemeViewClass = [CPView class]; CPThemeWindowClass = [CPWindow class]; var bundle = [CPBundle bundleForClass:self]; defaultThemePath = [bundle objectForInfoDictionaryKey:@"Default Themes Path"], defaultThemeName = [bundle objectForInfoDictionaryKey:@"Default Theme Name"]; if (!defaultThemePath) return; defaultThemePath = [bundle pathForResource:defaultThemePath]; var file = objj_files[defaultThemePath + "/Info.plist"]; if (!file) return; // Oh boy are we tricky. var infoDictionary = CPPropertyListCreateFromData([CPData dataWithString:objj_files[defaultThemePath + "/Info.plist"].contents]); themes = [infoDictionary objectForKey:@"CPBundleReplacedFiles"], themeIndex = 0, themeCount = [themes count]; for (; themeIndex < themeCount; ++themeIndex) { var unarchiver = [[_CPThemeKeyedUnarchiver alloc] initForReadingWithData:[CPData dataWithString:objj_files[defaultThemePath + '/' + themes[themeIndex]].contents] bundle:file.bundle]; [unarchiver decodeObjectForKey:@"root"]; [unarchiver finishDecoding]; } [self setDefaultTheme:[CPTheme themeNamed:defaultThemeName]]; } + (void)setDefaultTheme:(CPTheme)aTheme { CPThemeDefaultTheme = aTheme; } + (CPTheme)defaultTheme { return CPThemeDefaultTheme; } + (CPTheme)themeNamed:(CPString)aName { return CPThemesByName[aName]; } + (void)loadThemesAtURL:(CPURL)aURL delegate:(id)aLoadDelegate { } - (id)initWithName:(CPString)aName { self = [super init]; if (self) { _name = aName; _classTable = {}; _classTable[[[self class] className]] = {}; CPThemesByName[_name] = aName; } return self; } - (CPString)name { return _name; } - (void)setActiveClass:(Class)aClass { _activeClass = aClass; } - (Class)activeClass { return _activeClass; } - (id)valueForAttributeName:(CPString)anAttributeName inClass:(CPClass)aClass { if (!aClass) return nil; var className = [aClass className], table = _classTable[className]; if (!table) return nil; var value = table[anAttributeName]; if (!value) return nil; return value; } - (void)takeThemeFromObject:(id)anObject { var attributes = [anObject _themedAttributes], attributeName = nil, attributeNames = [attributes keyEnumerator], objectClass = [anObject class]; while (attributeName = [attributeNames nextObject]) [self addValue:[attributes objectForKey:attributeName] forAttributeName:attributeName inClass:objectClass]; } - (void)setDefaultValue:(id)aValue forAttributeName:(CPString)anAttributeName { [self addValue:aValue forAttributeName:anAttributeName inClass:[self class]]; } - (void)addValue:(id)aValue forAttributeName:(CPString)anAttributeName inClass:(Class)aClass { if (!aValue) return; var className = [aClass className], table = _classTable[className]; if (!table) { var classNames = []; while (!table && (aClass !== CPThemeViewClass) && (aClass !== CPThemeWindowClass)) { classNames.push(className); aClass = [aClass superclass]; className = [aClass className]; table = _classTable[className]; } if (!table) table = _classTable[[[self class] className]]; var count = [classNames count]; while (count--) { className = classNames[count]; _classTable[className] = {}; _classTable[className].prototype = table; table = _classTable[className]; } } var existingAttribute = table[anAttributeName]; if (existingAttribute) table[anAttributeName] = [existingAttribute themedAttributeMergedWithThemedAttribute:aValue]; else table[anAttributeName] = aValue; } @end var CPThemeNameKey = @"CPThemeNameKey", CPThemeClassNamesArrayKey = @"CPThemeClassNamesArrayKey"; @implementation CPTheme (CPCoding) - (id)initWithCoder:(CPCoder)aCoder { self = [super init]; _name = [aCoder decodeObjectForKey:CPThemeNameKey]; _classTable = {}; CPThemesByName[_name] = self; if (self) { var classNamesArray = [aCoder decodeObjectForKey:CPThemeClassNamesArrayKey], count = classNamesArray.length; while (count--) { var className = classNamesArray[count], theClass = objj_getClass(className), values = [aCoder decodeObjectForKey:className], keys = [values allKeys], keyCount = keys.length; while (keyCount--) { var key = keys[keyCount]; [self addValue:[values objectForKey:key] forAttributeName:key inClass:theClass]; } } } return self; } - (void)encodeWithCoder:(CPCoder)aCoder { var classNamesArray = []; for (var className in _classTable) { if (_classTable.hasOwnProperty(className)) { var values = _classTable[className], valuesDictionary = [CPDictionary dictionary]; for (var key in values) if (key !== "prototype" && values.hasOwnProperty(key)) [valuesDictionary setObject:values[key] forKey:key]; [aCoder encodeObject:valuesDictionary forKey:className]; classNamesArray.push(className); } } [aCoder encodeObject:_name forKey:CPThemeNameKey]; [aCoder encodeObject:classNamesArray forKey:CPThemeClassNamesArrayKey]; } @end @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