mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-11-09 08:28:45 +00:00
Additions to the theme API to open it up to developers and provide access to theme attributes defined in ThemeDescriptors.j:
- Current theme blend is available via CPApplication -themeBlend. - List of themes is available via CPApplication -themes. - CPThemeBlend adds the following methods: themes themeNames - CPTheme adds a number of methods to access theme attributes: classNames attributesForClass: attributeNamesForClass: attributeWithName:forClass: (was _attributeWithName:forClass:) valueForAttributeWithName:forClass valueForAttributeWithName:inState:forClass: - _CPThemeAttribute -values was added to access the dictionary of attribute values. - Theme browser application that demonstrates the API. - In the course of making the test app, added the function CGInsetEqualToInset().
This commit is contained in:
1 parent
9b8cadb91c
commit
0c4ce1e756
16 files changed
+1571
-15
No files matched your search
@@ -108,6 +108,8 @@ CPRunContinuesResponse = -1002;
|
||||
CPImage _applicationIconImage;
|
||||
|
||||
CPPanel _aboutPanel;
|
||||
|
||||
CPThemeBlend _themeBlend @accessors(property=themeBlend);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1129,6 +1131,7 @@ var _CPAppBootstrapperActions = nil;
|
||||
|
||||
+ (void)blendDidFinishLoading:(CPThemeBlend)aThemeBlend
|
||||
{
|
||||
[[CPApplication sharedApplication] setThemeBlend:aThemeBlend];
|
||||
[CPTheme setDefaultTheme:[CPTheme themeNamed:[CPApplication defaultThemeName]]];
|
||||
|
||||
[self performActions];
|
||||
@@ -1226,7 +1229,7 @@ var _CPAppBootstrapperActions = nil;
|
||||
|
||||
+ (void)reset
|
||||
{
|
||||
_CPAppBootstrapperActions = nil;
|
||||
_CPAppBootstrapperActions = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
+145
-8
@@ -95,9 +95,105 @@ var CPThemesByName = { },
|
||||
return _name;
|
||||
}
|
||||
|
||||
- (_CPThemeAttribute)_attributeWithName:(CPString)aName forClass:(CPString)aClass
|
||||
/*!
|
||||
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' +themeClass method. For example, the name for CPCheckBox is "check-box",
|
||||
as defined in CPCheckBox::themeClass.
|
||||
*/
|
||||
- (CPArray)classNames
|
||||
{
|
||||
var attributes = [_attributes objectForKey:aClass];
|
||||
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 isKindOfClass:[CPView class]])
|
||||
{
|
||||
if ([aClass respondsToSelector:@selector(themeClass)])
|
||||
className = [aClass themeClass];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
else
|
||||
[CPException raise:CPInvalidArgumentException reason:@"aClass must be a class object or a string."];
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -105,6 +201,47 @@ var CPThemesByName = { },
|
||||
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:(CPThemeState)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],
|
||||
@@ -278,10 +415,10 @@ CPThemeStateCircular = CPThemeState("circular");
|
||||
{
|
||||
CPString _name;
|
||||
id _defaultValue;
|
||||
CPDictionary _values;
|
||||
CPDictionary _values @accessors(readonly, getter=values);
|
||||
|
||||
JSObject _cache;
|
||||
CPThemeAttribute _parentAttribute;
|
||||
_CPThemeAttribute _parentAttribute;
|
||||
}
|
||||
|
||||
- (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue
|
||||
@@ -402,7 +539,7 @@ CPThemeStateCircular = CPThemeState("circular");
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)setParentAttribute:(CPThemeAttribute)anAttribute
|
||||
- (void)setParentAttribute:(_CPThemeAttribute)anAttribute
|
||||
{
|
||||
if (_parentAttribute === anAttribute)
|
||||
return;
|
||||
@@ -411,7 +548,7 @@ CPThemeStateCircular = CPThemeState("circular");
|
||||
_parentAttribute = anAttribute;
|
||||
}
|
||||
|
||||
- (CPThemeAttribute)attributeMergedWithAttribute:(_CPThemeAttribute)anAttribute
|
||||
- (_CPThemeAttribute)attributeMergedWithAttribute:(_CPThemeAttribute)anAttribute
|
||||
{
|
||||
var mergedAttribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue];
|
||||
|
||||
@@ -575,7 +712,7 @@ function CPThemeAttributeDecode(aCoder, anAttributeName, aDefaultValue, aTheme,
|
||||
}
|
||||
|
||||
if (aTheme && aClass)
|
||||
[attribute setParentAttribute:[aTheme _attributeWithName:anAttributeName forClass:aClass]];
|
||||
[attribute setParentAttribute:[aTheme attributeWithName:anAttributeName forClass:aClass]];
|
||||
|
||||
return attribute;
|
||||
}
|
||||
@@ -609,4 +746,4 @@ for (i = 0;i < Math.pow(2,6);++i)
|
||||
}
|
||||
print(str+']');
|
||||
|
||||
*/
|
||||
*/
|
||||
+27
-4
@@ -33,7 +33,7 @@
|
||||
@implementation CPThemeBlend : CPObject
|
||||
{
|
||||
CPBundle _bundle;
|
||||
CPArray _themes @accessors(readonly, getter=themes);
|
||||
CPArray _themes;
|
||||
id _loadDelegate;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,28 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of names of the keyed theme archives that make up this blend.
|
||||
Each item in the array will have the extension ".keyedtheme".
|
||||
*/
|
||||
- (CPArray)themes
|
||||
{
|
||||
return _themes;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of names of the themes that make up this blend.
|
||||
*/
|
||||
- (CPArray)themeNames
|
||||
{
|
||||
var names = [];
|
||||
|
||||
for (var i = 0; i < _themes.length; ++i)
|
||||
names.push(_themes[i].substring(0, _themes[i].indexOf(".keyedtheme")));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
- (void)loadWithDelegate:(id)aDelegate
|
||||
{
|
||||
_loadDelegate = aDelegate;
|
||||
@@ -58,12 +80,13 @@
|
||||
|
||||
- (void)bundleDidFinishLoading:(CPBundle)aBundle
|
||||
{
|
||||
var themes = [_bundle objectForInfoDictionaryKey:@"CPKeyedThemes"],
|
||||
count = themes.length;
|
||||
_themes = [_bundle objectForInfoDictionaryKey:@"CPKeyedThemes"];
|
||||
|
||||
var count = _themes.length;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var path = [aBundle pathForResource:themes[count]],
|
||||
var path = [aBundle pathForResource:_themes[count]],
|
||||
unarchiver = [[_CPThemeKeyedUnarchiver alloc]
|
||||
initForReadingWithData:[[CPURL URLWithString:path] staticResourceData]
|
||||
bundle:_bundle];
|
||||
|
||||
+2
-2
@@ -2323,7 +2323,7 @@ setBoundsOrigin:
|
||||
var attributeName = attributes[count--],
|
||||
attribute = [[_CPThemeAttribute alloc] initWithName:attributeName defaultValue:attributes[count]];
|
||||
|
||||
[attribute setParentAttribute:[theme _attributeWithName:attributeName forClass:themeClass]];
|
||||
[attribute setParentAttribute:[theme attributeWithName:attributeName forClass:themeClass]];
|
||||
|
||||
_themeAttributes[attributeName] = attribute;
|
||||
}
|
||||
@@ -2354,7 +2354,7 @@ setBoundsOrigin:
|
||||
|
||||
for (var attributeName in _themeAttributes)
|
||||
if (_themeAttributes.hasOwnProperty(attributeName))
|
||||
[_themeAttributes[attributeName] setParentAttribute:[theme _attributeWithName:attributeName forClass:themeClass]];
|
||||
[_themeAttributes[attributeName] setParentAttribute:[theme attributeWithName:attributeName forClass:themeClass]];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#define _CGInsetMakeCopy(anInset) _CGInsetMake(anInset.top, anInset.right, anInset.bottom, anInset.left)
|
||||
#define _CGInsetMakeZero() _CGInsetMake(0, 0, 0, 0)
|
||||
#define _CGInsetIsEmpty(anInset) ((anInset).top === 0 && (anInset).right === 0 && (anInset).bottom === 0 && (anInset).left === 0)
|
||||
#define _CGInsetEqualToInset(lhsInset, rhsInset) ((lhsInset).top === (rhsInset).top && (lhsInset).right === (rhsInset).right && (lhsInset).bottom === (rhsInset).bottom && (lhsInset).left === (rhsInset).left)
|
||||
|
||||
// DEPRECATED
|
||||
#define _CGPointCreateCopy(aPoint) _CGPointMake(aPoint.x, aPoint.y)
|
||||
|
||||
@@ -69,6 +69,7 @@ _function(CGInsetMake(top, right, bottom, left))
|
||||
_function(CGInsetMakeZero())
|
||||
_function(CGInsetMakeCopy(anInset))
|
||||
_function(CGInsetIsEmpty(anInset))
|
||||
_function(CGInsetEqualToInset(lhsInset, rhsInset))
|
||||
|
||||
CGMinXEdge = 0;
|
||||
CGMinYEdge = 1;
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* AppController.j
|
||||
* CPButtonImageTest
|
||||
*
|
||||
* Created by Aparajita Fishman on August 31, 2010.
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
@import <AppKit/AppKit.j>
|
||||
|
||||
@import "SCMultiLineLabel.j"
|
||||
@import "SCString.j"
|
||||
|
||||
var BrowserColumnTheme = 0,
|
||||
BrowserColumnClass = 1,
|
||||
BrowserColumnAttribute = 2,
|
||||
BrowserColumnState = 3,
|
||||
|
||||
ColumnTitles = ["Theme", "Class", "Attribute", "State"],
|
||||
|
||||
ImageDescriptionFormat = "<%s> {\n filename: \"%s\",\n size: { width:%f, height:%f }\n}",
|
||||
ValueLabelTemplate = "Attribute value#0#:# <span style=\"font-weight:normal\">($0 / $1 / $2 / $3):</span>#";
|
||||
|
||||
|
||||
@implementation AppController : CPObject
|
||||
{
|
||||
CPWindow theWindow;
|
||||
|
||||
CPBrowser browser;
|
||||
CPArray rootNodes;
|
||||
|
||||
SCMultiLineLabel valueLabel;
|
||||
SCMultiLineLabel valueView;
|
||||
}
|
||||
|
||||
- (void)awakeFromCib
|
||||
{
|
||||
SharedController = self;
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(CPNotification)aNotification
|
||||
{
|
||||
[theWindow setFullPlatformWindow:YES];
|
||||
|
||||
var contentView = [theWindow contentView],
|
||||
themeNames = [[[CPApplication sharedApplication] themeBlend] themeNames],
|
||||
frame = CGRectMake(20, 20, 800, 20),
|
||||
browserFrame = CGRectMake(20, 50, 800, 300),
|
||||
label = [[SCMultiLineLabel alloc] initWithFrame:frame];
|
||||
|
||||
[label setFont:[CPFont boldSystemFontOfSize:14]];
|
||||
[label setStringValue:@"Browse theme attributes from ThemeDescriptors.j"];
|
||||
[contentView addSubview:label];
|
||||
|
||||
rootNodes = [];
|
||||
browser = [[CPBrowser alloc] initWithFrame:browserFrame];
|
||||
|
||||
for (var i = 0; i < themeNames.length; ++i)
|
||||
{
|
||||
var node = [[ThemeNode alloc] initWithBrowser:browser value:[CPTheme themeNamed:themeNames[i]] type:NodeTypeTheme];
|
||||
rootNodes.push(node);
|
||||
}
|
||||
|
||||
[browser setLastColumn:3];
|
||||
[browser setMinColumnWidth:200];
|
||||
[browser setTarget:self];
|
||||
[browser setAction:@selector(browserClicked:)];
|
||||
[browser setDelegate:self];
|
||||
|
||||
var box = [CPBox boxEnclosingView:browser];
|
||||
|
||||
[contentView addSubview:box];
|
||||
|
||||
var frame = CGRectMake(CGRectGetMinX(browserFrame), CGRectGetMaxY(browserFrame) + 20, CGRectGetWidth(browserFrame), 17),
|
||||
valueLabel = [[SCMultiLineLabel alloc] initWithFrame:frame],
|
||||
fonts = [[CPFontManager sharedFontManager] availableFonts],
|
||||
frame = CGRectMake(CGRectGetMinX(frame), CGRectGetMaxY(frame) + 7, CGRectGetWidth(browserFrame), 250);
|
||||
|
||||
[valueLabel setStyled:YES];
|
||||
[valueLabel setFont:[CPFont boldSystemFontOfSize:12]];
|
||||
[valueLabel setColor:[CPColor colorWithHexString:@"444444"]];
|
||||
[valueLabel setStringValue:[SCString stringWithTemplate:ValueLabelTemplate, @"", @"", @"", @""]];
|
||||
[contentView addSubview:valueLabel];
|
||||
|
||||
valueView = [[SCMultiLineLabel alloc] initWithFrame:frame];
|
||||
|
||||
if ([fonts indexOfObjectIdenticalTo:@"Courier"])
|
||||
[valueView setFont:[CPFont fontWithName:@"Courier" size:12]];
|
||||
else if ([fonts indexOfObjectIdenticalTo:@"Courier New"])
|
||||
[valueView setFont:[CPFont fontWithName:@"Courier New" size:12]];
|
||||
|
||||
[valueView setContentInset:CGInsetMake(7, 7, 7, 7)];
|
||||
|
||||
box = [CPBox boxEnclosingView:valueView];
|
||||
[contentView addSubview:box];
|
||||
}
|
||||
|
||||
- (void)browserClicked:(id)sender
|
||||
{
|
||||
var column = [browser selectedColumn];
|
||||
|
||||
if (column != BrowserColumnState)
|
||||
return;
|
||||
|
||||
var row = [browser selectedRowInColumn:BrowserColumnTheme],
|
||||
theme = [[browser itemAtRow:row inColumn:BrowserColumnTheme] value],
|
||||
row = [browser selectedRowInColumn:BrowserColumnClass],
|
||||
className = [[browser itemAtRow:row inColumn:BrowserColumnClass] value],
|
||||
row = [browser selectedRowInColumn:BrowserColumnAttribute],
|
||||
attribute = [[browser itemAtRow:row inColumn:BrowserColumnAttribute] value],
|
||||
row = [browser selectedRowInColumn:BrowserColumnState],
|
||||
state = [[browser itemAtRow:row inColumn:BrowserColumnState] value]["key"];
|
||||
|
||||
var value = [theme valueForAttributeWithName:[attribute name] inState:state forClass:className];
|
||||
|
||||
[valueLabel setStringValue:[SCString stringWithTemplate:ValueLabelTemplate, [theme name], className, [attribute name], CPThemeStateName(state)]]
|
||||
[self showValueDescription:value];
|
||||
}
|
||||
|
||||
- (void)showValueDescription:(id)aValue
|
||||
{
|
||||
var description = "";
|
||||
|
||||
if (!aValue.isa)
|
||||
{
|
||||
if (typeof(aValue) === "object")
|
||||
{
|
||||
if (aValue.hasOwnProperty("width") && aValue.hasOwnProperty("height"))
|
||||
description = [CPString stringWithFormat:@"CGSize: { width:%f, height:%f }", aValue.width, aValue.height];
|
||||
else if (aValue.hasOwnProperty("x") && aValue.hasOwnProperty("y"))
|
||||
description = [CPString stringWithFormat:@"CGPoint: { x:%f, y:%f }", aValue.x, aValue.y];
|
||||
else if (aValue.hasOwnProperty("origin") && aValue.hasOwnProperty("size"))
|
||||
description = [CPString stringWithFormat:@"CGRect: { x:%f, y:%f }, { width:%f, height:%f }", aValue.origin.x, aValue.origin.y, aValue.size.width, aValue.size.height];
|
||||
else if (aValue.hasOwnProperty("top") && aValue.hasOwnProperty("right") && aValue.hasOwnProperty("bottom") && aValue.hasOwnProperty("left"))
|
||||
description = [CPString stringWithFormat:@"CGInset: { top:%f, right:%f, bottom:%f, left:%f }", aValue.top, aValue.right, aValue.bottom, aValue.left];
|
||||
else
|
||||
{
|
||||
description = "Object\n{\n";
|
||||
|
||||
for (var property in aValue)
|
||||
{
|
||||
if (aValue.hasOwnProperty(property))
|
||||
description += " " + property + ":" + aValue[property] + "\n";
|
||||
}
|
||||
|
||||
description += "}";
|
||||
}
|
||||
}
|
||||
else
|
||||
description = "Unknown object";
|
||||
}
|
||||
else if ([aValue isKindOfClass:[CPImage class]])
|
||||
description = [self imageDescription:aValue];
|
||||
else if ([aValue isKindOfClass:[CPColor class]])
|
||||
description = [self colorDescription:aValue];
|
||||
else
|
||||
description = [aValue description];
|
||||
|
||||
[valueView setStringValue:description];
|
||||
}
|
||||
|
||||
- (CPString)imageDescription:(CPImage)anImage
|
||||
{
|
||||
var filename = [anImage filename],
|
||||
size = [anImage size];
|
||||
|
||||
if (filename.indexOf("data:") === 0)
|
||||
{
|
||||
var index = filename.indexOf(",");
|
||||
|
||||
if (index > 0)
|
||||
filename = [CPString stringWithFormat:@"%s,%s...%s", filename.substr(0, index), filename.substr(index + 1, 10), filename.substr(filename.length - 10)];
|
||||
else
|
||||
filename = "data:<unknown type>";
|
||||
}
|
||||
|
||||
return [CPString stringWithFormat:ImageDescriptionFormat, [anImage className], filename, size.width, size.height];
|
||||
}
|
||||
|
||||
- (CPString)colorDescription:(CPColor)aColor
|
||||
{
|
||||
var patternImage = [aColor patternImage];
|
||||
|
||||
if (!patternImage || !([patternImage isThreePartImage] || [patternImage isNinePartImage]))
|
||||
return [aColor description];
|
||||
|
||||
var description = "<CPColor> {\n",
|
||||
slices = [patternImage imageSlices];
|
||||
|
||||
if ([patternImage isThreePartImage])
|
||||
description += " orientation: " + ([patternImage isVertical] ? "vertical" : "horizontal") + ",\n";
|
||||
|
||||
description += " patternImage: [\n";
|
||||
|
||||
for (var i = 0; i < slices.length; ++i)
|
||||
{
|
||||
var imageDescription = [self imageDescription:slices[i]];
|
||||
|
||||
description += imageDescription.replace(/^/mg, " ") + ",\n";
|
||||
}
|
||||
|
||||
description = description.substr(0, description.length - 2) + "\n ]\n}";
|
||||
return description;
|
||||
}
|
||||
|
||||
- (CPString)browser:(id)aBrowser titleOfColumn:(int)column
|
||||
{
|
||||
return ColumnTitles[column];
|
||||
}
|
||||
|
||||
- (id)browser:(id)aBrowser numberOfChildrenOfItem:(id)anItem
|
||||
{
|
||||
if (!anItem)
|
||||
return [rootNodes count];
|
||||
|
||||
return [[anItem children] count];
|
||||
}
|
||||
|
||||
- (id)browser:(id)aBrowser child:(int)index ofItem:(id)anItem
|
||||
{
|
||||
if (!anItem)
|
||||
return [rootNodes objectAtIndex:index];
|
||||
|
||||
return [[anItem children] objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (id)browser:(id)aBrowser objectValueForItem:(id)anItem
|
||||
{
|
||||
return [anItem objectValue];
|
||||
}
|
||||
|
||||
- (id)browser:(id)aBrowser isLeafItem:(id)anItem
|
||||
{
|
||||
return [anItem type] === NodeTypeValue;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var NodeTypeTheme = 0,
|
||||
NodeTypeClass = 1,
|
||||
NodeTypeAttribute = 2,
|
||||
NodeTypeValue = 3;
|
||||
|
||||
@implementation ThemeNode : CPObject
|
||||
{
|
||||
CPBrowser browser;
|
||||
int type @accessors(readonly);
|
||||
id value @accessors(readonly);
|
||||
CPArray children @accessors(readonly);
|
||||
}
|
||||
|
||||
- (id)initWithBrowser:(CPBrowser)aBrowser value:(id)aValue type:(int)aType
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
browser = aBrowser;
|
||||
value = aValue;
|
||||
type = aType;
|
||||
children = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)objectValue
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case NodeTypeTheme:
|
||||
return [value name];
|
||||
|
||||
case NodeTypeClass:
|
||||
return value;
|
||||
|
||||
case NodeTypeAttribute:
|
||||
return [value name];
|
||||
|
||||
case NodeTypeValue:
|
||||
return CPThemeStateName(value["key"]);
|
||||
}
|
||||
}
|
||||
|
||||
- (CPArray)children
|
||||
{
|
||||
if (children)
|
||||
return children;
|
||||
|
||||
children = [];
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NodeTypeTheme:
|
||||
var classes = [value classNames].sort();
|
||||
|
||||
for (var i = 0; i < classes.length; ++i)
|
||||
children.push([[ThemeNode alloc] initWithBrowser:browser value:classes[i] type:NodeTypeClass]);
|
||||
break;
|
||||
|
||||
case NodeTypeClass:
|
||||
var selectedRow = [browser selectedRowInColumn:0],
|
||||
theme = [[browser itemAtRow:selectedRow inColumn:0] value],
|
||||
attributes = [theme attributesForClass:value],
|
||||
keys = [attributes allKeys].sort();
|
||||
|
||||
// NOTE: I could use [attributes objectForKey:keys[i]] in the loop, but I want
|
||||
// to do code coverage of CPTheme.
|
||||
for (var i = 0; i < keys.length; ++i)
|
||||
children.push([[ThemeNode alloc] initWithBrowser:browser value:[theme attributeWithName:keys[i] forClass:value] type:NodeTypeAttribute]);
|
||||
break;
|
||||
|
||||
case NodeTypeAttribute:
|
||||
var selectedRow = [browser selectedRowInColumn:0],
|
||||
theme = [[browser itemAtRow:selectedRow inColumn:0] value],
|
||||
selectedRow = [browser selectedRowInColumn:1],
|
||||
className = [[browser itemAtRow:selectedRow inColumn:1] value],
|
||||
attributeValues = [value values],
|
||||
keys = [attributeValues allKeys];
|
||||
|
||||
[keys sortUsingFunction:attributeCompareFunc context:nil];
|
||||
|
||||
// NOTE: I could use [attributes objectForKey:keys[i]] in the loop, but I want
|
||||
// to do code coverage of CPTheme.
|
||||
for (var i = 0; i < keys.length; ++i)
|
||||
{
|
||||
var nodeValue =
|
||||
{
|
||||
key:keys[i],
|
||||
attribute:[theme valueForAttributeWithName:[value name] inState:keys[i] forClass:className]
|
||||
};
|
||||
|
||||
children.push([[ThemeNode alloc] initWithBrowser:browser value:nodeValue type:NodeTypeValue]);
|
||||
}
|
||||
break;
|
||||
|
||||
case NodeTypeValue:
|
||||
break;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var attributeCompareFunc = function(lhs, rhs)
|
||||
{
|
||||
var lhsInt = parseInt(lhs),
|
||||
rhsInt = parseInt(rhs);
|
||||
|
||||
if (lhsInt === rhsInt)
|
||||
return CPOrderedSame;
|
||||
else if (lhsInt > rhsInt)
|
||||
return CPOrderedDescending;
|
||||
else
|
||||
return CPOrderedAscending;
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Main cib file base name</key>
|
||||
<string>MainMenu.cib</string>
|
||||
<key>CPBundleName</key>
|
||||
<string>ThemeBrowser</string>
|
||||
<key>CPPrincipalClass</key>
|
||||
<string>CPApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
280NPLIST;1.0;D;K;4;$topD;K;18;CPCibObjectDataKeyD;K;6;CP$UIDd;1;2E;E;K;8;$objectsA;S;5;$nullD;K;10;$classnameS;16;_CPCibObjectDataK;8;$classesA;S;16;_CPCibObjectDataS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;1E;K;28;_CPCibObjectDataNamesKeysKeyD;K;6;CP$UIDd;1;4E;K;30;_CPCibObjectDataNamesValuesKeyD;K;6;CP$UIDd;1;5E;K;30;_CPCibObjectDataClassesKeysKeyD;K;6;CP$UIDd;1;6E;K;32;_CPCibObjectDataClassesValuesKeyD;K;6;CP$UIDd;1;7E;K;30;_CPCibObjectDataConnectionsKeyD;K;6;CP$UIDd;1;8E;K;28;_CPCibObjectDataFrameworkKeyD;K;6;CP$UIDd;1;9E;K;26;_CPCibObjectDataNextOidKeyD;K;6;CP$UIDd;2;10E;K;30;_CPCibObjectDataObjectsKeysKeyD;K;6;CP$UIDd;2;11E;K;32;_CPCibObjectDataObjectsValuesKeyD;K;6;CP$UIDd;2;12E;K;26;_CPCibObjectDataOidKeysKeyD;K;6;CP$UIDd;2;13E;K;28;_CPCibObjectDataOidValuesKeyD;K;6;CP$UIDd;2;14E;K;28;_CPCibObjectDataFileOwnerKeyD;K;6;CP$UIDd;2;16E;K;33;_CPCibObjectDataVisibleWindowsKeyD;K;6;CP$UIDd;2;18E;E;D;K;10;$classnameS;7;CPArrayK;8;$classesA;S;7;CPArrayS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;19E;D;K;6;CP$UIDd;2;21E;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;22E;D;K;6;CP$UIDd;2;24E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;25E;D;K;6;CP$UIDd;2;26E;D;K;6;CP$UIDd;2;27E;D;K;6;CP$UIDd;2;28E;D;K;6;CP$UIDd;2;29E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;31E;D;K;6;CP$UIDd;2;32E;E;E;S;16;IBCocoaFrameworkD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;19E;D;K;6;CP$UIDd;2;21E;D;K;6;CP$UIDd;2;24E;D;K;6;CP$UIDd;2;22E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;21E;D;K;6;CP$UIDd;2;16E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;18;_CPCibCustomObjectK;8;$classesA;S;18;_CPCibCustomObjectS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;15E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;33E;E;D;K;10;$classnameS;5;CPSetK;8;$classesA;S;5;CPSetS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;17E;K;15;CPSetObjectsKeyD;K;6;CP$UIDd;2;34E;E;D;K;6;$classD;K;6;CP$UIDd;2;15E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;33E;E;D;K;10;$classnameS;20;_CPCibWindowTemplateK;8;$classesA;S;20;_CPCibWindowTemplateS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;20E;K;30;_CPCibWindowTemplateMaxSizeKeyD;K;6;CP$UIDd;2;35E;K;32;_CPCibWindowTemplateViewClassKeyD;K;6;CP$UIDd;1;0E;K;34;_CPCibWindowTemplateWindowClassKeyD;K;6;CP$UIDd;2;36E;K;33;_CPCibWindowTemplateWindowRectKeyD;K;6;CP$UIDd;2;37E;K;30;_CPCibWindowTempatStyleMaskKeyD;K;6;CP$UIDd;2;38E;K;34;_CPCibWindowTemplateWindowTitleKeyD;K;6;CP$UIDd;2;39E;K;33;_CPCibWindowTemplateWindowViewKeyD;K;6;CP$UIDd;2;24E;E;D;K;6;$classD;K;6;CP$UIDd;2;15E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;40E;E;D;K;10;$classnameS;6;CPViewK;8;$classesA;S;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;23E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;41E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;42E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;42E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;43E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;44E;E;S;11;ApplicationS;6;WindowS;12;File's OwnerS;14;App ControllerS;12;Content ViewD;K;10;$classnameS;20;CPCibOutletConnectorK;8;$classesA;S;20;CPCibOutletConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;30E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;16E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;22E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;45E;E;D;K;6;$classD;K;6;CP$UIDd;2;30E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;22E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;21E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;46E;E;S;13;CPApplicationD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;21E;E;E;S;28;{1.79769e+308, 1.79769e+308}S;8;CPWindowS;23;{{408, 63}, {569, 418}}d;1;7S;12;CPTheme TestS;13;AppControllerd;1;0S;20;{{0, 0}, {569, 418}}d;2;18S;6;normalS;8;delegateS;9;theWindowE;K;9;$archiverS;15;CPKeyedArchiverK;8;$versionS;6;100000E;
|
||||
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
|
||||
<data>
|
||||
<int key="IBDocument.SystemTarget">1060</int>
|
||||
<string key="IBDocument.SystemVersion">10F569</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">788</string>
|
||||
<string key="IBDocument.AppKitVersion">1038.29</string>
|
||||
<string key="IBDocument.HIToolboxVersion">461.00</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="NS.object.0">788</string>
|
||||
</object>
|
||||
<array class="NSMutableArray" key="IBDocument.EditedObjectIDs"/>
|
||||
<array key="IBDocument.PluginDependencies">
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
</array>
|
||||
<dictionary class="NSMutableDictionary" key="IBDocument.Metadata"/>
|
||||
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
|
||||
<object class="NSCustomObject" id="1021">
|
||||
<string key="NSClassName">NSApplication</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="1014">
|
||||
<string key="NSClassName">FirstResponder</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="1050">
|
||||
<string key="NSClassName">NSApplication</string>
|
||||
</object>
|
||||
<object class="NSWindowTemplate" id="972006081">
|
||||
<int key="NSWindowStyleMask">7</int>
|
||||
<int key="NSWindowBacking">2</int>
|
||||
<string key="NSWindowRect">{{408, 547}, {569, 418}}</string>
|
||||
<int key="NSWTFlags">1946157056</int>
|
||||
<string key="NSWindowTitle">CPTheme Browser</string>
|
||||
<string key="NSWindowClass">NSWindow</string>
|
||||
<nil key="NSViewClass"/>
|
||||
<string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||
<object class="NSView" key="NSWindowView" id="439893737">
|
||||
<reference key="NSNextResponder"/>
|
||||
<int key="NSvFlags">274</int>
|
||||
<string key="NSFrameSize">{569, 418}</string>
|
||||
<reference key="NSSuperview"/>
|
||||
<reference key="NSWindow"/>
|
||||
</object>
|
||||
<string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string>
|
||||
<string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="635946545">
|
||||
<string key="NSClassName">AppController</string>
|
||||
</object>
|
||||
</array>
|
||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||
<array class="NSMutableArray" key="connectionRecords">
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">delegate</string>
|
||||
<reference key="source" ref="1021"/>
|
||||
<reference key="destination" ref="635946545"/>
|
||||
</object>
|
||||
<int key="connectionID">451</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">theWindow</string>
|
||||
<reference key="source" ref="635946545"/>
|
||||
<reference key="destination" ref="972006081"/>
|
||||
</object>
|
||||
<int key="connectionID">1090</int>
|
||||
</object>
|
||||
</array>
|
||||
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||
<array key="orderedObjects">
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">0</int>
|
||||
<array key="object" id="0"/>
|
||||
<reference key="children" ref="1048"/>
|
||||
<nil key="parent"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-2</int>
|
||||
<reference key="object" ref="1021"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">File's Owner</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-1</int>
|
||||
<reference key="object" ref="1014"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">First Responder</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-3</int>
|
||||
<reference key="object" ref="1050"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">Application</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">371</int>
|
||||
<reference key="object" ref="972006081"/>
|
||||
<array class="NSMutableArray" key="children">
|
||||
<reference ref="439893737"/>
|
||||
</array>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">Window</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">372</int>
|
||||
<reference key="object" ref="439893737"/>
|
||||
<array class="NSMutableArray" key="children"/>
|
||||
<reference key="parent" ref="972006081"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">450</int>
|
||||
<reference key="object" ref="635946545"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
</object>
|
||||
</array>
|
||||
</object>
|
||||
<dictionary class="NSMutableDictionary" key="flattenedProperties">
|
||||
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="371.IBEditorWindowLastContentRect">{{531, 479}, {569, 418}}</string>
|
||||
<string key="371.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="371.IBWindowTemplateEditedContentRect">{{531, 479}, {569, 418}}</string>
|
||||
<integer value="1" key="371.NSWindowTemplate.visibleAtLaunch"/>
|
||||
<string key="371.editorWindowContentRectSynchronizationRect">{{33, 99}, {480, 360}}</string>
|
||||
<string key="372.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
</dictionary>
|
||||
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
|
||||
<nil key="activeLocalization"/>
|
||||
<dictionary class="NSMutableDictionary" key="localizations"/>
|
||||
<nil key="sourceID"/>
|
||||
<int key="maxID">1186</int>
|
||||
</object>
|
||||
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">AppController</string>
|
||||
<string key="superclassName">NSObject</string>
|
||||
<dictionary class="NSMutableDictionary" key="outlets">
|
||||
<string key="browser">NSBrowser</string>
|
||||
<string key="theWindow">NSWindow</string>
|
||||
</dictionary>
|
||||
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<object class="IBToOneOutletInfo" key="browser">
|
||||
<string key="name">browser</string>
|
||||
<string key="candidateClassName">NSBrowser</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo" key="theWindow">
|
||||
<string key="name">theWindow</string>
|
||||
<string key="candidateClassName">NSWindow</string>
|
||||
</object>
|
||||
</dictionary>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBUserSource</string>
|
||||
<string key="minorKey"/>
|
||||
</object>
|
||||
</object>
|
||||
</array>
|
||||
</object>
|
||||
<int key="IBDocument.localizationMode">0</int>
|
||||
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||
<string key="IBDocument.LastKnownRelativeProjectPath">../registration.xcodeproj</string>
|
||||
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||
</data>
|
||||
</archive>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* LPMultiLineLabel.j
|
||||
*
|
||||
* Based on LPMultiLineTextField by Ludwig Pettersson.
|
||||
*
|
||||
*/
|
||||
@import <AppKit/CPTextField.j>
|
||||
|
||||
@implementation SCMultiLineLabel : CPView
|
||||
{
|
||||
id _DOMTextareaElement;
|
||||
CPString _stringValue;
|
||||
CGInset _contentInset @accessors(readonly, getter=contentInset);
|
||||
CPColor _color @accessors(readonly, getter=color);
|
||||
CPFont _font @accessors(readonly, getter=font);
|
||||
BOOL _styled @accessors(readonly, getter=isStyled);
|
||||
}
|
||||
|
||||
- (DOMElement)_DOMTextareaElement
|
||||
{
|
||||
if (!_DOMTextareaElement)
|
||||
{
|
||||
_DOMTextareaElement = document.createElement(_styled ? "div" : "textarea");
|
||||
_DOMTextareaElement.style.position = @"absolute";
|
||||
_DOMTextareaElement.style.background = @"none";
|
||||
_DOMTextareaElement.style.border = @"0";
|
||||
_DOMTextareaElement.style.outline = @"0";
|
||||
_DOMTextareaElement.style.zIndex = @"100";
|
||||
_DOMTextareaElement.style.resize = @"none";
|
||||
_DOMTextareaElement.style.padding = @"0";
|
||||
_DOMTextareaElement.style.margin = @"0";
|
||||
_DOMTextareaElement.style.overflowX = @"hidden";
|
||||
|
||||
_DOMTextareaElement.onblur = function(){
|
||||
[[CPTextFieldInputOwner window] makeFirstResponder:nil];
|
||||
CPTextFieldInputOwner = nil;
|
||||
};
|
||||
|
||||
self._DOMElement.appendChild(_DOMTextareaElement);
|
||||
}
|
||||
|
||||
return _DOMTextareaElement;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
var theme = [CPTheme defaultTheme];
|
||||
|
||||
_contentInset = CGInsetMakeZero();
|
||||
_color = [theme valueForAttributeWithName:@"text-color" forClass:[CPTextField class]];
|
||||
_font = [theme valueForAttributeWithName:@"font" forClass:[CPTextField class]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"bezel-view"];
|
||||
[contentView setHidden:YES];
|
||||
|
||||
var DOMElement = [self _DOMTextareaElement],
|
||||
bounds = [self bounds];
|
||||
|
||||
DOMElement.style.paddingTop = _contentInset.top + @"px";
|
||||
DOMElement.style.paddingBottom = _contentInset.bottom + @"px";
|
||||
DOMElement.style.paddingLeft = _contentInset.left + @"px";
|
||||
DOMElement.style.paddingRight = _contentInset.right + @"px";
|
||||
|
||||
DOMElement.style.width = (CGRectGetWidth(bounds) - _contentInset.left - _contentInset.right) + @"px";
|
||||
DOMElement.style.height = (CGRectGetHeight(bounds) - _contentInset.top - _contentInset.bottom) + @"px";
|
||||
|
||||
DOMElement.style.color = [_color cssString];
|
||||
DOMElement.style.font = [_font cssString];
|
||||
|
||||
if (_styled)
|
||||
DOMElement.innerHTML = _stringValue || @"";
|
||||
else
|
||||
DOMElement.value = _stringValue || @"";
|
||||
}
|
||||
|
||||
- (CPString)stringValue
|
||||
{
|
||||
return (!!_DOMTextareaElement) ? _DOMTextareaElement.value : @"";
|
||||
}
|
||||
|
||||
- (void)setStringValue:(CPString)aString
|
||||
{
|
||||
if (_stringValue === aString)
|
||||
return;
|
||||
|
||||
_stringValue = aString;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setContentInset:(CGInset)inset
|
||||
{
|
||||
if (CGInsetEqualToInset(_contentInset, inset))
|
||||
return;
|
||||
|
||||
_contentInset = CGInsetMakeCopy(inset);
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
if ([_color isEqual:aColor])
|
||||
return;
|
||||
|
||||
_color = aColor;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
if ([_font isEqual:aFont])
|
||||
return;
|
||||
|
||||
_font = aFont;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setStyled:(BOOL)flag
|
||||
{
|
||||
flag = !!flag;
|
||||
|
||||
if (_styled === flag)
|
||||
return;
|
||||
|
||||
_styled = flag;
|
||||
_DOMTextareaElement = nil;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* SCString.j
|
||||
*
|
||||
* Created by Aparajita on 8/9/10.
|
||||
* Copyright Victory-Heart Productions 2010. All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
|
||||
@implementation SCString : CPObject
|
||||
|
||||
/*
|
||||
A string template engine.
|
||||
|
||||
Long ago I was poking through the resources used by the Macintosh Finder and saw strings like this:
|
||||
|
||||
The folder ^1 contains #2#no#^2# file|2||s|#2## for ^3 byte|3||s|#
|
||||
|
||||
I realized they had created a meta-language to allow a single string to allow dynamic argument
|
||||
substitution and express two types of linguistic variations:
|
||||
|
||||
- Zero/non-zero
|
||||
- Singular/plural
|
||||
|
||||
In most languages, the syntax changes based on those two factors. For example, let's suppose we want
|
||||
to tell users how many messages they have after logging in. In natural English,
|
||||
there are three distinct forms:
|
||||
|
||||
- Zero: "You have no messages"
|
||||
- Singular: "You have 1 message"
|
||||
- Plural: "You have 7 messages"
|
||||
|
||||
In French it would be something like this:
|
||||
|
||||
- Zero: "Vous n'avez aucun message"
|
||||
- Singular: "Vous avez 1 message"
|
||||
- Plural: "Vous avez 7 messages"
|
||||
|
||||
You could encode these linguistic rules in your code,
|
||||
but that doesn't work very well if you need to translate your application into languages
|
||||
that don't follow the same rules. Or you could just give up and do something like this:
|
||||
|
||||
You have 0 message(s)
|
||||
|
||||
If that solution doesn't satisfy you, then stringWithTemplate is the solution.
|
||||
|
||||
stringWithTemplate is an Objective-J port of the meta-language used in the original Mac Finder,
|
||||
but with an extended and more familiar syntax.
|
||||
|
||||
You apply one or more arguments to a template. Argument placeholders indicated by "$key" or "${key}"
|
||||
in the template are replaced with the corresponding replacement arguments. Replacement arguments
|
||||
can be passed in several ways:
|
||||
|
||||
- You may pass one or more unnamed parameters following the last named parameter.
|
||||
- If there is only unnamed parameter and it is a CPDictionary, <key> should be the key in that dictionary.
|
||||
- If there is only unnamed parameter and it is an array, <key> should be a numeric, zero-based
|
||||
index into that array.
|
||||
- If there is only unnamed parameter and it is an object (but not a CPDictionary),
|
||||
<key> should be a property of that object.
|
||||
- Otherwise <key> should be a numeric, zero-based index of an unnamed parameter, with 0 being
|
||||
the first unnamed parameter, 1 the second, and so on.
|
||||
|
||||
Note that because stringWithTemplate supports indexing, the <key> can be a number in addition to an identifier.
|
||||
If <key> does not exist in an argument dictionary or is out of the range of positional arguments,
|
||||
the argument placeholder is replaced with an empty string.
|
||||
|
||||
When using the ${key} syntax for replacement parameters, you may add a format separated by a semicolon
|
||||
like this:
|
||||
|
||||
${key:format}
|
||||
|
||||
If the replacement argument is a date and the Date object has had the function dateFormat()
|
||||
added to its prototype, then [argsDict objectForKey:key].dateFormat(format)
|
||||
is used to format it. One such dateFormat() function is available here:
|
||||
|
||||
http://code.google.com/p/flexible-js-formatting/
|
||||
|
||||
For the available date formats of this library, see:
|
||||
|
||||
http://www.xaprb.com/articles/date-formatting-demo.html
|
||||
|
||||
The formatting options of flexible-js-formatting are basically the same as the date() function in php:
|
||||
|
||||
http://php.net/manual/en/function.date.php
|
||||
|
||||
If the argument is not a date, the equivalent of:
|
||||
|
||||
[SCKit stringWithFormat:format, [argsDict objectForKey:key]]
|
||||
|
||||
is performed.
|
||||
|
||||
For example:
|
||||
|
||||
${total:.2f}
|
||||
${date:F j, Y}
|
||||
|
||||
is the same as:
|
||||
|
||||
[SCKit stringWithFormat:@"%.2f", [argsDict objectForKey:@"total"]]
|
||||
[argsDict objectForKey:@"date"].dateFormat("F j, Y")
|
||||
|
||||
In addition to a format, you may also add a default value after a vertical bar:
|
||||
|
||||
${key|default} or ${key:format|default}
|
||||
|
||||
The default is used if [argsDict objectForKey:key] evaluates to false, which means nil, an empty string, etc.
|
||||
|
||||
Inline formats and defaults are nice, but the real power of stringWithTemplate is the meta-language that
|
||||
allows you to deal with linguistic rules and alternatives in a single template.
|
||||
In addition to argument replacement, you can specify alternate subsections of
|
||||
the template to be used. Which subsection is used depends on the value of the replacement arguments.
|
||||
Alternate subsections are marked in the form:
|
||||
|
||||
<delim><key><delim><alt1><delim><alt2><delim>
|
||||
|
||||
where <delim> is a delimiter character, <key> is in the same format as a replacement argument,
|
||||
<alt1> is the "true" choice, and <alt2> is the "false" choice. The delimiter characters are taken from the first
|
||||
two characters of the delimiters parameter in stringWithTemplate:delimiters:. If you use stringWithTemplate:
|
||||
or if the length of the delimiters < 2, the default delimiters are "#|".
|
||||
|
||||
Three passes are made over the template:
|
||||
|
||||
1. Zero/non-zero (false/true) subsections (<delim> == delimiters[0]) are matched. If [argsDict objectForKey:key]
|
||||
evaluates to zero, <alt1> is used, else <alt2> is used. An argument is considered zero if:
|
||||
- It is a number and == 0
|
||||
- !arg == true
|
||||
|
||||
2. Singular/plural subsections (<delim> == delimiters[1]) are matched, and if [argsDict objectForKey:key]
|
||||
evaluates to 1, <alt1> is used, else <alt2> is used. An argument evaluates to 1 if:
|
||||
- It is a number and == 1
|
||||
- It is a string and can be converted to a number which == 1.0
|
||||
- It is an object with a length property which == 1
|
||||
|
||||
3. Any occurrence of $key or ${key} is replaced with the corresponding replacement argument according
|
||||
to the rules stated above.
|
||||
|
||||
IMPORTANT: Subsections with the same delimiter may not be nested.
|
||||
|
||||
If all of the matching rules for a subsection fail, the entire subsection is replaced with an empty string.
|
||||
|
||||
ESCAPING/CHANGING THE DELIMITERS
|
||||
If your template string uses one of the defaults delimiters ("#" or "|") or the key marker ("$")
|
||||
in its regular text, you have two options:
|
||||
|
||||
- Precede the literal delimiter characters with backslashes
|
||||
- Change the delimiters by using stringWithTemplate:delimiters: instead of stringWithTemplate:
|
||||
- Use "$$" to represent a literal "$"
|
||||
|
||||
EXAMPLES
|
||||
Let's look at some examples to see how we might use stringWithTemplate. We'll start with a simple example. We
|
||||
have a song object parsed from JSON for which we want to display the date it was composed and the name of the
|
||||
composer:
|
||||
|
||||
var args = {composer:song.composer, date:song.dateComposed},
|
||||
text = [SCKit stringWithTemplate:@"Composed by $composer on $dateComposed", args]
|
||||
|
||||
RESULT: "Composed by Pat Metheny on 2005-03-30"
|
||||
|
||||
Simple enough. Now we want to format the date to be a little more friendly:
|
||||
|
||||
text = [SCKit stringWithTemplate:@"Composed by $composer on ${dateComposed:F j, Y}", args];
|
||||
|
||||
RESULT: "Composed by Pat Metheny on March 30, 2005"
|
||||
|
||||
That's better. Now we want to translate it into French:
|
||||
|
||||
text = [SCKit stringWithTemplate:@"Composée par $composer le ${dateComposed:j F, Y}", args];
|
||||
|
||||
RESULT: "Composée par Pat Metheny le 30 March, 2005"
|
||||
|
||||
Now we realize that that we may not know the composition date, in which case the date is nil.
|
||||
So we use the default value:
|
||||
|
||||
text = [SCKit stringWithTemplate:@"Composed by $composer ${dateComposed:\\o\\n F j, Y|(date unknown)}", args];
|
||||
|
||||
RESULT: "Composed by Pat Metheny (date unknown)" # if dateComposed is nil
|
||||
|
||||
Note that we backslash escaped the characters in "on" so they would not be interpreted as format
|
||||
characters.
|
||||
|
||||
Now we want to add "and translated" if the song has a translation by the composer:
|
||||
|
||||
var args = {composer:song.composer, date:song.dateComposed, translated:song.hasTranslation},
|
||||
template = @"Composed #translated##and translated #by $composer ${dateComposed:\o\n F j, Y|(date unknown)}",
|
||||
text = [SCKit stringWithTemplate:template, args];
|
||||
|
||||
Note that we are using the zero/non-zero selector as a false/true selector in this case.
|
||||
|
||||
Just for reference, here is how the template would look if you wanted to pass the arguments as positional
|
||||
arguments:
|
||||
|
||||
var template = @"Composed #0##and translated #by $1 ${2:\o\n F j, Y|(date unknown)}",
|
||||
text = [SCKit stringWithTemplate:template, song.hasTranslation, song.composer, song.dateComposed];
|
||||
|
||||
Finally, we find that sometimes we don't know the exact date of composition, but we know the month
|
||||
or year. In that case we have a text date like "February 1980". So our logic ends up like this:
|
||||
|
||||
- If there is a text date, display that
|
||||
- If there is an exact date, format and display that
|
||||
- Otherwise display "(date unknown)"
|
||||
|
||||
Here's how we encode all of this into the stringWithTemplate:
|
||||
|
||||
var args = {
|
||||
composer:song.composer,
|
||||
date:song.dateComposed,
|
||||
textDate:song.textDate,
|
||||
translated:song.hasTranslation
|
||||
};
|
||||
|
||||
var template = @"Composed #translated##and translated #by $composer " +
|
||||
@"#textDate#${dateComposed:\o\n F j, Y|(date unknown)}#$textDate#",
|
||||
text = [SCKit stringWithTemplate:template, args];
|
||||
|
||||
Now let's see how the singular/plural selector works. Returning to a variation on the first example at the top
|
||||
of this doc, we want to encode these three variations into a single template:
|
||||
|
||||
You don't have any messages
|
||||
You have only 1 message
|
||||
You have 7 messages
|
||||
|
||||
Here is the template:
|
||||
|
||||
var template = @"You #count#don't ##have #count#any#|count|only ||$count# message|count||s|.",
|
||||
text = [SCKit stringWithTemplate:template, {count:messageCount};
|
||||
|
||||
Let's break down the pattern:
|
||||
|
||||
#count#don't ##
|
||||
If $count evaluates to zero, insert "don't ".
|
||||
|
||||
#count#any#|count|only ||$count#
|
||||
Here we have a one selector and argument placeholder nested inside a zero selector.
|
||||
|
||||
- The zero selector is evaluated first. If $count evaluates to zero, substitute "any",
|
||||
else substitute "|count|only ||$count".
|
||||
|
||||
- Then the one selector is evaluated. If the zero selector returned "|count|only ||$count",
|
||||
that is parsed. If $count evaluates to 1, "only " is substituted, otherwise nothing.
|
||||
|
||||
- Finally the argument placeholders are substituted.
|
||||
|
||||
message|count||s|
|
||||
This is a common idiom for expressing singular/plural. If count evaluates to 1, add no
|
||||
suffix to "message", otherwise add "s" as a suffix to make it plural.
|
||||
|
||||
To help you picture what is happening, let's view the steps of the transformation of the string given
|
||||
a count of zero, 1 and 7.
|
||||
|
||||
count == 0
|
||||
|
||||
1. Zero selector applied -> "You don't have any message|count||s|."
|
||||
2. One selector applied -> "You don't have any messages."
|
||||
3. Arguments replaced -> "You don't have any messages."
|
||||
|
||||
count == 1
|
||||
|
||||
1. Zero selector applied -> "You have |count|only ||$count message|count||s|."
|
||||
2. One selector applied -> "You have only $count message."
|
||||
3. Arguments replaced -> "You have only 1 message."
|
||||
|
||||
count == 7
|
||||
|
||||
1. Zero selector applied -> "You have |count|only ||$count message|count||s|."
|
||||
2. One selector applied -> "You have $count messages."
|
||||
3. Arguments replaced -> "You have only 7 messages."
|
||||
|
||||
Hopefully that should be enough to give you an idea how to use stringWithTemplate. Enjoy!
|
||||
*/
|
||||
+ (id)stringWithTemplate:(CPString)template, ...
|
||||
{
|
||||
var args = [template, SCKit.TemplateDefaultDelimiters];
|
||||
|
||||
return SCKit.stringWithTemplate.apply(self, args.concat(Array.prototype.slice.call(arguments, 3)));
|
||||
}
|
||||
|
||||
+ (id)stringWithTemplate:(CPString)template delimiters:(CPString)delimiters, ...
|
||||
{
|
||||
var args = [template, delimiters];
|
||||
|
||||
return SCKit.stringWithTemplate.apply(self, args.concat(Array.prototype.slice.call(arguments, 4)));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
SCKit = (function() {
|
||||
|
||||
var my = {},
|
||||
|
||||
// Template argument RE
|
||||
TemplateArgREPattern =
|
||||
'\\$' + // template argument indicator followed by...
|
||||
'(?:' +
|
||||
'(\\$)' + // another arg indicator, indicating a literal
|
||||
'|(\\w+)' + // or an identifer
|
||||
'|{' + // or the start of a braced identifier, followed by...
|
||||
'(\\w+)' + // an identifier
|
||||
'(?::([^}|]+))?' + // followed by an optional format
|
||||
'(?:\\|([^}]+))?' + // followed by an optional default
|
||||
'}' + // end of braced identifier
|
||||
'|(\\S+)' + // invalid stuff following arg indicator
|
||||
')',
|
||||
|
||||
TemplateArgRE = new RegExp(TemplateArgREPattern, "g"),
|
||||
|
||||
// Tests for an integer or float
|
||||
DigitRE = new RegExp("^(?:\\d+|\\d*\\.\\d+)$");
|
||||
|
||||
// Default subsection delimiters. The first character delimits zero/non-zero subsections,
|
||||
// the second character delimits singular/plural subsections.
|
||||
TemplateDefaultDelimiters = "#|";
|
||||
|
||||
// Default argument delimiter
|
||||
TemplateDefaultArgDelimiter = "$";
|
||||
|
||||
function _selectorReplace(text, selector, delimiter, argsDict)
|
||||
{
|
||||
// First see if the first part of the text is <identifier>#.
|
||||
// If not, continue the after leading delimiter.
|
||||
var re = new RegExp("^[" + delimiter + "](\\w+)[" + delimiter + "]"),
|
||||
match = re.exec(text);
|
||||
|
||||
if (!match)
|
||||
return {text:delimiter, nextIndex:1};
|
||||
|
||||
var identifier = match[1],
|
||||
valid = NO,
|
||||
lastChar = "",
|
||||
alternatives = ["", ""],
|
||||
haveAlternative = NO,
|
||||
lastIndex = match[0].length;
|
||||
|
||||
// Now scan for the two alternatives
|
||||
for (var alternative = 0; alternative < 2; ++alternative)
|
||||
{
|
||||
for (; lastIndex < text.length; ++lastIndex)
|
||||
{
|
||||
var c = text.charAt(lastIndex);
|
||||
|
||||
if (c === delimiter && lastChar !== "\\")
|
||||
{
|
||||
++lastIndex;
|
||||
haveAlternative = YES;
|
||||
break;
|
||||
}
|
||||
else if (c !== "\\")
|
||||
alternatives[alternative] += c;
|
||||
|
||||
lastChar = c;
|
||||
}
|
||||
|
||||
// Make sure we didn't exhaust the source before finding the alternative.
|
||||
if (!haveAlternative)
|
||||
return {text:text, endIndex:text.length};
|
||||
}
|
||||
|
||||
// Get the specified argument, convert to a string
|
||||
var arg = [argsDict objectForKey:identifier],
|
||||
type = typeof arg,
|
||||
result = nil;
|
||||
|
||||
if (selector === 0)
|
||||
{
|
||||
var isZero;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "string":
|
||||
if (arg.length === 0)
|
||||
isZero = true;
|
||||
else
|
||||
isZero = DigitRE.test(arg) && parseFloat(arg) === 0;
|
||||
break;
|
||||
|
||||
case "number":
|
||||
isZero = arg === 0;
|
||||
break;
|
||||
|
||||
case "boolean":
|
||||
isZero = arg === false;
|
||||
break;
|
||||
|
||||
case "object":
|
||||
isZero = arg === nil || (arg.hasOwnProperty("length") && arg.length === 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
result = "";
|
||||
}
|
||||
|
||||
if (result === nil)
|
||||
result = isZero ? alternatives[0] : alternatives[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
var isOne;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "string":
|
||||
isOne = arg.length && DigitRE.test(arg) && parseFloat(arg) === 1;
|
||||
break;
|
||||
|
||||
case "number":
|
||||
isOne = arg === 1;
|
||||
break;
|
||||
|
||||
case "boolean":
|
||||
isOne = arg === true;
|
||||
break;
|
||||
|
||||
case "object":
|
||||
isOne = arg !== nil && arg.hasOwnProperty("length") && arg.length === 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
result = "";
|
||||
}
|
||||
|
||||
if (result === nil)
|
||||
result = isOne ? alternatives[0] : alternatives[1];
|
||||
}
|
||||
|
||||
return {text:result, nextIndex:lastIndex};
|
||||
}
|
||||
|
||||
// Helper function for replacing args
|
||||
function _convert(value, format, defaultValue)
|
||||
{
|
||||
if (!value)
|
||||
return defaultValue ? defaultValue : "";
|
||||
|
||||
if (format)
|
||||
{
|
||||
if (value.constructor === Date)
|
||||
{
|
||||
if (Date.prototype.dateFormat)
|
||||
return value.dateFormat(format);
|
||||
else
|
||||
return value.toLocaleString();
|
||||
}
|
||||
else
|
||||
{
|
||||
format = "%" + format;
|
||||
return ObjectiveJ.sprintf(format, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
return String(value, 10);
|
||||
}
|
||||
|
||||
my.stringWithTemplate = function(/* CPString */ template, /* CPString */ delimiters, /* CPArray|CPDictionary */ args)
|
||||
{
|
||||
if (!template)
|
||||
return "";
|
||||
|
||||
if (!delimiters || delimiters.length < 2)
|
||||
delimiters = TemplateDefaultDelimiters;
|
||||
|
||||
// Normalize the arguments into a dictionary
|
||||
var argsDict = null,
|
||||
argsArray = [];
|
||||
|
||||
if (arguments.length < 3)
|
||||
return "";
|
||||
|
||||
if (arguments.length === 3)
|
||||
{
|
||||
var arg = arguments[2];
|
||||
|
||||
if (arg.hasOwnProperty("isa"))
|
||||
{
|
||||
if ([arg isKindOfClass:[CPArray class]])
|
||||
argsArray = arg;
|
||||
else if ([arg isKindOfClass:[CPDictionary class]])
|
||||
argsDict = arg;
|
||||
}
|
||||
else if (arg.constructor === Array)
|
||||
argsArray = arg;
|
||||
else if (typeof(arg) === "object")
|
||||
argsDict = [CPDictionary dictionaryWithJSObject:arg];
|
||||
else
|
||||
argsArray = [arguments[2]];
|
||||
}
|
||||
else
|
||||
{
|
||||
argsArray = Array.prototype.slice.call(arguments, 2);
|
||||
}
|
||||
|
||||
if (!argsDict)
|
||||
{
|
||||
if (argsArray.length === 0)
|
||||
return template;
|
||||
|
||||
argsDict = [CPDictionary dictionary];
|
||||
|
||||
for (var i = 0; i < argsArray.length; ++i)
|
||||
[argsDict setObject:argsArray[i] forKey:String(i, 10)];
|
||||
}
|
||||
|
||||
var text = template;
|
||||
|
||||
// We have a zero/non-zero selector and one/non-one selector
|
||||
for (var selector = 0; selector <= 1; ++selector)
|
||||
{
|
||||
var delim = delimiters.charAt(selector),
|
||||
lastChar = "";
|
||||
|
||||
for (var i = 0; i < text.length; ++i)
|
||||
{
|
||||
var c = text.charAt(i);
|
||||
|
||||
if (c === delim)
|
||||
{
|
||||
if (lastChar !== "\\")
|
||||
{
|
||||
var leftContext = text.slice(0, i),
|
||||
rightContext = text.slice(i),
|
||||
replacement = _selectorReplace(rightContext, selector, delim, argsDict);
|
||||
|
||||
text = leftContext + replacement.text + rightContext.slice(replacement.nextIndex);
|
||||
i += replacement.text.length - 1;
|
||||
}
|
||||
}
|
||||
else if (c === "\\")
|
||||
text = text.slice(0, i) + text.slice(i + 1);
|
||||
|
||||
lastChar = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Define as a closure so we can access argsDict
|
||||
var argReplace = function(str, escaped, named, braced, format, defaultValue, invalid)
|
||||
{
|
||||
named = named || braced;
|
||||
|
||||
if (named)
|
||||
return _convert([argsDict objectForKey:named], format, defaultValue);
|
||||
|
||||
if (escaped)
|
||||
return TemplateDefaultArgDelimiter;
|
||||
|
||||
// FIXME: raise
|
||||
return "";
|
||||
};
|
||||
|
||||
return text.replace(TemplateArgRE, argReplace);
|
||||
};
|
||||
|
||||
return my;
|
||||
}());
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns = "http://www.w3.org/1999/xhtml" xml:lang = "en" lang = "en">
|
||||
<!--
|
||||
index-debug.html
|
||||
TableCibTest
|
||||
|
||||
Created by Francisco Tolmasky on July 5, 2009.
|
||||
Copyright 2009, 280 North, Inc. All rights reserved.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
|
||||
|
||||
<title>Theme Browser</title>
|
||||
|
||||
<script type = "text/javascript">
|
||||
OBJJ_MAIN_FILE = "main.j";
|
||||
OBJJ_INCLUDE_PATHS = ["Frameworks/Debug", "Frameworks", "SomethingElse"];
|
||||
</script>
|
||||
|
||||
<script src = "Frameworks/Debug/Objective-J/Objective-J.js" type = "text/javascript"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
objj_msgSend_reset();
|
||||
|
||||
// DEBUG OPTIONS:
|
||||
|
||||
// Uncomment to enable printing of backtraces on exceptions:
|
||||
//objj_msgSend_decorate(objj_backtrace_decorator);
|
||||
</script>
|
||||
|
||||
<style type = "text/css">
|
||||
body{margin:0; padding:0;}
|
||||
#container {position: absolute; top:50%; left:50%;}
|
||||
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
|
||||
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
|
||||
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
|
||||
</style>
|
||||
|
||||
<!--[if lt IE 7]>
|
||||
<STYLE type="text/css">
|
||||
#container { position: relative; top: 50%; }
|
||||
#content { position: relative;}
|
||||
</STYLE>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body style="">
|
||||
<div id="loadingcontainer" style=" background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
|
||||
<script type = "text/javascript">
|
||||
document.write("<div id='container'><p id='content'>" +
|
||||
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
|
||||
"Loading Theme Browser...</p></div>");
|
||||
</script>
|
||||
|
||||
<noscript>
|
||||
<div id="container">
|
||||
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
|
||||
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
|
||||
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino.org/noscript">Show me how to enable JavaScript</a></p>
|
||||
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
|
||||
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
|
||||
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
|
||||
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
|
||||
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
|
||||
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
|
||||
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns = "http://www.w3.org/1999/xhtml" xml:lang = "en" lang = "en">
|
||||
<!--
|
||||
index.html
|
||||
TableCibTest
|
||||
|
||||
Created by Francisco Tolmasky on July 5, 2009.
|
||||
Copyright 2009, 280 North, Inc. All rights reserved.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
|
||||
|
||||
<title>Theme Browser</title>
|
||||
|
||||
<script type = "text/javascript">
|
||||
OBJJ_MAIN_FILE = "main.j";
|
||||
</script>
|
||||
|
||||
<script src = "Frameworks/Objective-J/Objective-J.js" type = "text/javascript"></script>
|
||||
|
||||
<style type = "text/css">
|
||||
body{margin:0; padding:0;}
|
||||
#container {position: absolute; top:50%; left:50%;}
|
||||
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
|
||||
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
|
||||
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
|
||||
</style>
|
||||
|
||||
<!--[if lt IE 7]>
|
||||
<STYLE type="text/css">
|
||||
#container { position: relative; top: 50%; }
|
||||
#content { position: relative;}
|
||||
</STYLE>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body style="">
|
||||
<div id="loadingcontainer" style=" background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
|
||||
<script type = "text/javascript">
|
||||
document.write("<div id='container'><p id='content'>" +
|
||||
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
|
||||
"Loading Theme Browser...</p></div>");
|
||||
</script>
|
||||
|
||||
<noscript>
|
||||
<div id="container">
|
||||
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
|
||||
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
|
||||
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino.org/noscript">Show me how to enable JavaScript</a></p>
|
||||
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
|
||||
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
|
||||
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
|
||||
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
|
||||
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
|
||||
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
|
||||
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* AppController.j
|
||||
* TableCibTest
|
||||
*
|
||||
* Created by Francisco Tolmasky on July 5, 2009.
|
||||
* Copyright 2009, 280 North, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
@import <AppKit/AppKit.j>
|
||||
|
||||
@import "AppController.j"
|
||||
|
||||
|
||||
function main(args, namedArgs)
|
||||
{
|
||||
CPApplicationMain(args, namedArgs);
|
||||
}
|
||||
Reference in new issue
Block a user