mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
improved: bindings to CPOutlineView
This commit is contained in:
@@ -116,3 +116,4 @@
|
||||
@import "CPWindowController.j"
|
||||
@import "CPWorkspace.j"
|
||||
@import "CPFontPanel.j"
|
||||
@import "CPTreeController.j"
|
||||
|
||||
+117
-28
@@ -773,6 +773,11 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
[self reloadItem:anItem reloadChildren:NO];
|
||||
}
|
||||
|
||||
- (int)_numberOfRows
|
||||
{
|
||||
return _itemsForRows ? _itemsForRows.length : 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Reloads the data for a given item and optionally the children.
|
||||
|
||||
@@ -784,6 +789,8 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
_pendingItemToClean = [];
|
||||
_itemAddedDuringLastLoading = [];
|
||||
|
||||
var previousRowCount = _itemsForRows.length;
|
||||
|
||||
if (!!shouldReloadChildren || !anItem)
|
||||
[self _loadItemInfoForItem:anItem intermediate:NO];
|
||||
else
|
||||
@@ -791,6 +798,11 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
|
||||
[self _cleanPendingItem];
|
||||
|
||||
// Safely update the table size and force a synchronous layout recalculation
|
||||
// BEFORE the views are reloaded, avoiding the clipping issue.
|
||||
if (_itemsForRows.length !== previousRowCount)
|
||||
[self noteNumberOfRowsChanged];
|
||||
|
||||
[super _reloadDataViews];
|
||||
}
|
||||
|
||||
@@ -837,9 +849,20 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
|
||||
for (var i = [previousItems count] - 1; i >= 0; i--)
|
||||
{
|
||||
var item = previousItems[i];
|
||||
var item = previousItems[i],
|
||||
found = NO;
|
||||
|
||||
if (![children containsObject:item])
|
||||
// Use strict identity (===) instead of containsObject: (which triggers isEqual:)
|
||||
for (var j = 0, count = children.length; j < count; j++)
|
||||
{
|
||||
if (children[j] === item)
|
||||
{
|
||||
found = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
[self _addPendingItem:item];
|
||||
}
|
||||
}
|
||||
@@ -853,7 +876,8 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
|
||||
var children = itemInfo.children;
|
||||
|
||||
for (var i = [children count]; i >= 0; i--)
|
||||
// Fixed out-of-bounds index (was previously [children count])
|
||||
for (var i = children.length - 1; i >= 0; i--)
|
||||
{
|
||||
var child = children[i];
|
||||
[self _addPendingItem:child];
|
||||
@@ -864,7 +888,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
|
||||
- (void)_cleanPendingItem
|
||||
{
|
||||
for (var i = [_pendingItemToClean count]; i >= 0; i--)
|
||||
for (var i = [_pendingItemToClean count] - 1; i >= 0; i--)
|
||||
{
|
||||
var item = _pendingItemToClean[i];
|
||||
|
||||
@@ -908,7 +932,8 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
var weight = itemInfo.weight,
|
||||
descendants = anItem ? [anItem] : [];
|
||||
|
||||
[_itemAddedDuringLastLoading addObject:anItem];
|
||||
if (anItem)
|
||||
[_itemAddedDuringLastLoading addObject:anItem];
|
||||
|
||||
if (itemInfo.isExpanded && [self _sendDataSourceShouldDeferDisplayingChildrenOfItem:anItem])
|
||||
{
|
||||
@@ -1085,7 +1110,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
var parent = itemInfo.parent;
|
||||
|
||||
// Check if the parent is the root item because we never return the actual root item
|
||||
if (itemInfo[[parent UID]] === _rootItemInfo)
|
||||
if (parent && itemInfo[[parent UID]] === _rootItemInfo)
|
||||
parent = nil;
|
||||
|
||||
return parent;
|
||||
@@ -2179,7 +2204,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
|
||||
if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_menuForTableColumn_item_))
|
||||
{
|
||||
var item = [_outlineView itemAtRow:aRow];
|
||||
return [_outlineView._outlineViewDelegate outlineView:_outlineView menuForTableColumn:aTableColumn item:item]
|
||||
return [_outlineView._outlineViewDelegate outlineView:_outlineView menuForTableColumn:aTableColumn item:item];
|
||||
}
|
||||
|
||||
// We reimplement CPView menuForEvent: because we can't call it directly. CPTableView implements menuForEvent:
|
||||
@@ -2386,6 +2411,10 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
|
||||
|
||||
@implementation CPOutlineView (CPBinder)
|
||||
|
||||
- (id)content { return nil; }
|
||||
- (void)setContent:(id)aContent { }
|
||||
- (void)setSelectionIndexPaths:(CPArray)paths { }
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === @"content")
|
||||
@@ -2413,8 +2442,12 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
|
||||
|
||||
- (void)bind
|
||||
{
|
||||
[super bind];
|
||||
// 1. Set the data source FIRST so that it is ready when
|
||||
//[super bind] triggers the initial synchronous setValueFor:
|
||||
[_source setDataSource:self];
|
||||
|
||||
// 2. Establish KVO (which immediately triggers setValueFor:)
|
||||
[super bind];
|
||||
}
|
||||
|
||||
- (void)unbind
|
||||
@@ -2425,16 +2458,30 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
|
||||
[super unbind];
|
||||
}
|
||||
|
||||
- (void)updateSource
|
||||
- (void)setValueFor:(CPString)aBinding
|
||||
{
|
||||
var value = [self valueForBinding:CPObservedKeyPathKey];
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath =[_info objectForKey:CPObservedKeyPathKey],
|
||||
value = [destination valueForKeyPath:keyPath];
|
||||
|
||||
if (!value || ![value isKindOfClass:[CPTreeNode class]])
|
||||
_rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil];
|
||||
else
|
||||
_rootNode = value;
|
||||
|
||||
[_source reloadData];
|
||||
// Because CPBinder triggers setValueFor: synchronously during its initialization
|
||||
// (before -bind is ever called), we must lazily assign the data source here.
|
||||
if ([_source dataSource] !== self)
|
||||
{
|
||||
// Assigning the data source automatically triggers [_source reloadData]
|
||||
// inside CPOutlineView, so we don't need to call it manually here.
|
||||
[_source setDataSource:self];
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it was already set, we just manually trigger the reload.
|
||||
[_source reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPTreeNode)rootNode
|
||||
@@ -2464,8 +2511,6 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
|
||||
|
||||
- (id)outlineView:(CPOutlineView)outlineView objectValueForTableColumn:(CPTableColumn)tableColumn byItem:(id)item
|
||||
{
|
||||
// Normally column values are resolved via the table column's own bindings,
|
||||
// but we return the represented object here as a standard fallback for cell-based tables.
|
||||
if ([item respondsToSelector:@selector(representedObject)])
|
||||
return [item representedObject];
|
||||
|
||||
@@ -2474,7 +2519,6 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// --- Selection Index Paths Binder ---
|
||||
|
||||
/*!
|
||||
@@ -2483,12 +2527,14 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
|
||||
It also intercepts changes from the CPTreeController and auto-expands the tree to highlight them.
|
||||
*/
|
||||
@implementation _CPOutlineViewSelectionIndexPathsBinder : CPBinder
|
||||
{
|
||||
BOOL _isSyncingFromModel;
|
||||
}
|
||||
|
||||
- (void)bind
|
||||
{
|
||||
[super bind];
|
||||
|
||||
// Observe selection changes originating from the user clicking the outline view
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(outlineViewSelectionDidChange:)
|
||||
@@ -2506,28 +2552,33 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
|
||||
[super unbind];
|
||||
}
|
||||
|
||||
- (void)updateSource
|
||||
- (void)setValueFor:(CPString)aBinding
|
||||
{
|
||||
var indexPaths = [self valueForBinding:CPObservedKeyPathKey] || [],
|
||||
indexes = [CPMutableIndexSet indexSet],
|
||||
contentBinder = [CPBinder getBinding:@"content" forObject:_source];
|
||||
// 1. SUPPRESS KVO AT THE VERY TOP to avoid circular updates when expanding parents
|
||||
_isSyncingFromModel = YES;
|
||||
|
||||
var rootNode = [contentBinder respondsToSelector:@selector(rootNode)] ? [contentBinder rootNode] : nil;
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
indexPaths = [destination valueForKeyPath:keyPath] || [],
|
||||
indexes = [CPMutableIndexSet indexSet];
|
||||
|
||||
// 2. Fetch the root node directly from the CPTreeController (destination)
|
||||
var rootNode = [destination respondsToSelector:@selector(arrangedObjects)] ? [destination arrangedObjects] : nil;
|
||||
|
||||
if (rootNode)
|
||||
{
|
||||
for (var i = 0, count = [indexPaths count]; i < count; i++)
|
||||
{
|
||||
var item = [rootNode descendantNodeAtIndexPath:[indexPaths objectAtIndex:i]];
|
||||
|
||||
if (item)
|
||||
{
|
||||
// Auto-expand all parents so the selection becomes visible
|
||||
var parentsToExpand = [CPMutableArray array],
|
||||
parent = [item parentNode];
|
||||
|
||||
while (parent && parent !== rootNode)
|
||||
{
|
||||
[parentsToExpand insertObject:parent atIndex:0]; // Top-down
|
||||
[parentsToExpand insertObject:parent atIndex:0];
|
||||
parent = [parent parentNode];
|
||||
}
|
||||
|
||||
@@ -2535,26 +2586,64 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
|
||||
[_source expandItem:parentsToExpand[j]];
|
||||
|
||||
var row = [_source rowForItem:item];
|
||||
|
||||
if (row !== CPNotFound && row >= 0)
|
||||
[indexes addIndex:row];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Suppress KVO while we programmatically adjust the CPOutlineView selection[self suppressSpecificNotificationFromObject:_source keyPath:@"selectionIndexPaths"];
|
||||
// Adjust the CPOutlineView selection
|
||||
[_source selectRowIndexes:indexes byExtendingSelection:NO];
|
||||
[self unsuppressSpecificNotificationFromObject:_source keyPath:@"selectionIndexPaths"];
|
||||
|
||||
// 3. Re-enable KVO after adjustments are done
|
||||
_isSyncingFromModel = NO;
|
||||
}
|
||||
|
||||
- (void)outlineViewSelectionDidChange:(CPNotification)note
|
||||
{
|
||||
// We only want to push the change back if we aren't currently syncing down from the model
|
||||
if ([self isSpecificNotificationSuppressedFromObject:_source keyPath:@"selectionIndexPaths"])
|
||||
if (_isSyncingFromModel)
|
||||
return;
|
||||
|
||||
var paths = [_source selectionIndexPaths];
|
||||
|
||||
// Reverse-set the value to push it up to the CPTreeController's selectionIndexPaths[self reverseSetValueFor:CPObservedKeyPathKey value:paths];
|
||||
// In CPBinder, reverseSetValueFor: takes the name of the property on _source
|
||||
// it should fetch the updated value from. Since CPOutlineView has the selectionIndexPaths method:
|
||||
[self reverseSetValueFor:@"selectionIndexPaths"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPOutlineViewContentBinder (DynamicColumns)
|
||||
|
||||
- (id)outlineView:(CPOutlineView)outlineView objectValueForTableColumn:(CPTableColumn)tableColumn byItem:(id)item
|
||||
{
|
||||
var rep = [item respondsToSelector:@selector(representedObject)] ? [item representedObject] : item;
|
||||
|
||||
// Dynamically fetch the value using the column's identifier (e.g., "name")
|
||||
if (rep && [tableColumn identifier])
|
||||
return [rep valueForKey:[tableColumn identifier]];
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
// Add this to support inline bidirectional editing in the outline view
|
||||
- (void)outlineView:(CPOutlineView)outlineView setObjectValue:(id)value forTableColumn:(CPTableColumn)tableColumn byItem:(id)item
|
||||
{
|
||||
var rep = [item respondsToSelector:@selector(representedObject)] ?[item representedObject] : item;
|
||||
|
||||
// Push the inline edit back to the model using the column's identifier
|
||||
if (rep && [tableColumn identifier])
|
||||
[rep setValue:value forKey:[tableColumn identifier]];
|
||||
}
|
||||
|
||||
- (id)content
|
||||
{
|
||||
// CPTableView internals probe the binder for its flat content to draw rows.
|
||||
// For an outline view, the flat content is exactly the internally mapped items for rows.
|
||||
if (_source && _source._itemsForRows)
|
||||
return _source._itemsForRows;
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
}
|
||||
|
||||
- (void)_setContentArray:(id)anArray {[self setContent:anArray]; }
|
||||
- (id)contentArray { return [self content]; }
|
||||
- (id)contentArray { return _contentObject; }
|
||||
- (id)arrangedObjects { return _arrangedObjects; }
|
||||
|
||||
- (void)rearrangeObjects
|
||||
@@ -226,9 +226,12 @@
|
||||
- (CPArray)_buildTreeNodesForObjects:(CPArray)objects
|
||||
{
|
||||
var count = [objects count];
|
||||
if (count === 0) return [CPArray array];
|
||||
|
||||
if (count === 0)
|
||||
return [];
|
||||
|
||||
var sortedObjects = objects;
|
||||
|
||||
if (_sortDescriptors && [_sortDescriptors count] > 0)
|
||||
sortedObjects = [objects sortedArrayUsingDescriptors:_sortDescriptors];
|
||||
|
||||
@@ -242,12 +245,15 @@
|
||||
if (_childrenKeyPath)
|
||||
{
|
||||
var childObjects = [obj valueForKeyPath:_childrenKeyPath];
|
||||
|
||||
if (childObjects && [childObjects count] > 0)
|
||||
{
|
||||
var childNodes = [self _buildTreeNodesForObjects:childObjects];
|
||||
[[node mutableChildNodes] addObjectsFromArray:childNodes];
|
||||
}
|
||||
}[nodes addObject:node];
|
||||
}
|
||||
|
||||
[nodes addObject:node];
|
||||
}
|
||||
|
||||
return nodes;
|
||||
@@ -291,15 +297,23 @@
|
||||
if ([_selectionIndexPaths isEqualToArray:newPaths])
|
||||
return NO;
|
||||
|
||||
[self willChangeValueForKey:@"selectionIndexPaths"];
|
||||
|
||||
_selectionIndexPaths = [newPaths copy];
|
||||
|
||||
|
||||
var binderClass = [[self class] _binderClassForBinding:@"selectionIndexPaths"];
|
||||
|
||||
if (binderClass)
|
||||
{
|
||||
var binding = [binderClass getBinding:@"selectionIndexPaths" forObject:self];
|
||||
if (binding)[binding reverseSetValueFor:@"selectionIndexPaths"];
|
||||
|
||||
if (binding)
|
||||
[binding reverseSetValueFor:@"selectionIndexPaths"];
|
||||
}
|
||||
|
||||
[self didChangeValueForKey:@"selectionIndexPaths"];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -441,7 +455,8 @@
|
||||
}
|
||||
|
||||
- (void)insertObjects:(CPArray)objects atArrangedObjectIndexPaths:(CPArray)indexPaths
|
||||
{[self willChangeValueForKey:@"content"];
|
||||
{
|
||||
[self willChangeValueForKey:@"content"];
|
||||
_disableSetContent = YES;
|
||||
|
||||
var count = [objects count];
|
||||
@@ -496,7 +511,8 @@
|
||||
}
|
||||
|
||||
- (void)removeObjectAtArrangedObjectIndexPath:(CPIndexPath)indexPath
|
||||
{[self removeObjectsAtArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]];
|
||||
{
|
||||
[self removeObjectsAtArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]];
|
||||
}
|
||||
|
||||
- (void)removeObjectsAtArrangedObjectIndexPaths:(CPArray)indexPaths
|
||||
@@ -513,7 +529,8 @@
|
||||
length = [path length];
|
||||
|
||||
if (length === 1)
|
||||
{[_contentObject removeObjectAtIndex:[path indexAtPosition:0]];
|
||||
{
|
||||
[_contentObject removeObjectAtIndex:[path indexAtPosition:0]];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+78
-38
@@ -22,13 +22,12 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPIndexPath.j>
|
||||
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@implementation CPTreeNode : CPObject
|
||||
{
|
||||
id _representedObject @accessors(readonly, property=representedObject);
|
||||
|
||||
CPTreeNode _parentNode @accessors(readonly, property=parentNode);
|
||||
id _representedObject @accessors(property=representedObject);
|
||||
CPTreeNode _parentNode @accessors(property=parentNode);
|
||||
CPMutableArray _childNodes;
|
||||
}
|
||||
|
||||
@@ -52,36 +51,34 @@
|
||||
|
||||
- (CPIndexPath)indexPath
|
||||
{
|
||||
if (_parentNode != nil)
|
||||
// If we have a parent, calculate path based on parent's path + our index
|
||||
if (_parentNode)
|
||||
{
|
||||
var path;
|
||||
var index;
|
||||
var index = [_childNodes indexOfObjectIdenticalTo:self];
|
||||
|
||||
index = [[_parentNode childNodes] indexOfObject:self];
|
||||
path = [_parentNode indexPath];
|
||||
// If the parent is the root (and technically has no path itself in some implementations),
|
||||
// we might get nil. Handle that gracefully.
|
||||
var parentPath = [_parentNode indexPath];
|
||||
|
||||
if (path != nil)
|
||||
{
|
||||
return [path indexPathByAddingIndex:index];
|
||||
}
|
||||
else
|
||||
{
|
||||
return [CPIndexPath indexPathWithIndex:index];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
if (parentPath)
|
||||
return [parentPath indexPathByAddingIndex:index];
|
||||
|
||||
return [CPIndexPath indexPathWithIndex:index];
|
||||
}
|
||||
|
||||
// If we are the root, we don't have an index path in the context of a tree controller usually,
|
||||
// or we are [] (empty path). Returning nil is acceptable for the absolute root.
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isLeaf
|
||||
{
|
||||
return [_childNodes count] <= 0;
|
||||
return [_childNodes count] == 0;
|
||||
}
|
||||
|
||||
- (CPArray)childNodes
|
||||
{
|
||||
// Return a copy to prevent external modification without KVC
|
||||
return [_childNodes copy];
|
||||
}
|
||||
|
||||
@@ -90,18 +87,29 @@
|
||||
return [self mutableArrayValueForKey:@"childNodes"];
|
||||
}
|
||||
|
||||
- (void)insertObject:(id)aTreeNode inChildNodesAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
[[aTreeNode._parentNode mutableChildNodes] removeObjectIdenticalTo:aTreeNode];
|
||||
// MARK: - KVC Compliance Methods
|
||||
|
||||
aTreeNode._parentNode = self;
|
||||
- (void)insertObject:(CPTreeNode)aTreeNode inChildNodesAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
// Optional: Auto-detach from old parent if strictly moving nodes
|
||||
if ([aTreeNode isKindOfClass:[CPTreeNode class]] && aTreeNode._parentNode)
|
||||
{
|
||||
[[aTreeNode._parentNode mutableChildNodes] removeObjectIdenticalTo:aTreeNode];
|
||||
}
|
||||
|
||||
// Direct ivar access is allowed here since we are inside the class implementation
|
||||
if ([aTreeNode isKindOfClass:[CPTreeNode class]])
|
||||
aTreeNode._parentNode = self;
|
||||
|
||||
[_childNodes insertObject:aTreeNode atIndex:anIndex];
|
||||
}
|
||||
|
||||
- (void)removeObjectFromChildNodesAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
[_childNodes objectAtIndex:anIndex]._parentNode = nil;
|
||||
var node = [_childNodes objectAtIndex:anIndex];
|
||||
|
||||
if ([node isKindOfClass:[CPTreeNode class]])
|
||||
node._parentNode = nil;
|
||||
|
||||
[_childNodes removeObjectAtIndex:anIndex];
|
||||
}
|
||||
@@ -110,17 +118,34 @@
|
||||
{
|
||||
var oldTreeNode = [_childNodes objectAtIndex:anIndex];
|
||||
|
||||
oldTreeNode._parentNode = nil;
|
||||
aTreeNode._parentNode = self;
|
||||
if ([oldTreeNode isKindOfClass:[CPTreeNode class]])
|
||||
oldTreeNode._parentNode = nil;
|
||||
|
||||
if ([aTreeNode isKindOfClass:[CPTreeNode class]])
|
||||
aTreeNode._parentNode = self;
|
||||
|
||||
[_childNodes replaceObjectAtIndex:anIndex withObject:aTreeNode];
|
||||
}
|
||||
|
||||
// MARK: - Convenience Accessors
|
||||
|
||||
- (id)objectInChildNodesAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
return _childNodes[anIndex];
|
||||
return [_childNodes objectAtIndex:anIndex];
|
||||
}
|
||||
|
||||
- (CPInteger)count
|
||||
{
|
||||
return [_childNodes count];
|
||||
}
|
||||
|
||||
- (id)objectAtIndex:(CPInteger)anIndex
|
||||
{
|
||||
return [_childNodes objectAtIndex:anIndex];
|
||||
}
|
||||
|
||||
// MARK: - Utility
|
||||
|
||||
- (void)sortWithSortDescriptors:(CPArray)sortDescriptors recursively:(BOOL)shouldSortRecursively
|
||||
{
|
||||
[_childNodes sortUsingDescriptors:sortDescriptors];
|
||||
@@ -129,25 +154,36 @@
|
||||
return;
|
||||
|
||||
var count = [_childNodes count];
|
||||
|
||||
while (count--)
|
||||
[_childNodes[count] sortWithSortDescriptors:sortDescriptors recursively:YES];
|
||||
{
|
||||
var child = [_childNodes objectAtIndex:count];
|
||||
if ([child respondsToSelector:@selector(sortWithSortDescriptors:recursively:)])
|
||||
[child sortWithSortDescriptors:sortDescriptors recursively:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPTreeNode)descendantNodeAtIndexPath:(CPIndexPath)indexPath
|
||||
{
|
||||
var index = 0,
|
||||
count = [indexPath length],
|
||||
node = self;
|
||||
if (!indexPath || [indexPath length] == 0)
|
||||
return self;
|
||||
|
||||
for (; index < count; ++index)
|
||||
node = [node objectInChildNodesAtIndex:[indexPath indexAtPosition:index]];
|
||||
var index = [indexPath indexAtPosition:0],
|
||||
count = [_childNodes count];
|
||||
|
||||
return node;
|
||||
if (index >= count)
|
||||
return nil;
|
||||
|
||||
var child = [_childNodes objectAtIndex:index];
|
||||
|
||||
if ([indexPath length] == 1)
|
||||
return child;
|
||||
|
||||
return [child descendantNodeAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Coding implementation remains correct
|
||||
var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
|
||||
CPTreeNodeParentNodeKey = @"CPTreeNodeParentNodeKey",
|
||||
CPTreeNodeChildNodesKey = @"CPTreeNodeChildNodesKey";
|
||||
@@ -163,6 +199,10 @@ var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
|
||||
_representedObject = [aCoder decodeObjectForKey:CPTreeNodeRepresentedObjectKey];
|
||||
_parentNode = [aCoder decodeObjectForKey:CPTreeNodeParentNodeKey];
|
||||
_childNodes = [aCoder decodeObjectForKey:CPTreeNodeChildNodesKey];
|
||||
|
||||
// Safety check to ensure decoding gave us a CPArray
|
||||
if (!_childNodes)
|
||||
_childNodes = [[CPMutableArray alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* AppController.j
|
||||
* TreeControllerBindingsTest
|
||||
*
|
||||
* Created for testing CPOutlineView and CPTreeController bindings.
|
||||
*/
|
||||
|
||||
@import <AppKit/AppKit.j>
|
||||
@import <AppKit/CPTreeController.j>
|
||||
|
||||
@implementation AppController : CPObject
|
||||
{
|
||||
CPTreeController treeController;
|
||||
CPTextField logField;
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(CPNotification)aNotification
|
||||
{
|
||||
var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask],
|
||||
contentView = [theWindow contentView];
|
||||
|
||||
// 1. Create the Data Model
|
||||
var root1 = [[Node alloc] initWithName:@"Root 1" children:[]],
|
||||
child1 = [[Node alloc] initWithName:@"Child 1.1" children:[]],
|
||||
child2 = [[Node alloc] initWithName:@"Child 1.2" children:[]],
|
||||
root2 = [[Node alloc] initWithName:@"Root 2" children:[]],
|
||||
child3 = [[Node alloc] initWithName:@"Child 2.1" children:[]];
|
||||
|
||||
[root1 setChildren:[child1, child2]];
|
||||
[root2 setChildren:[child3]];
|
||||
var contentArray = [root1, root2];
|
||||
|
||||
// 2. Setup the Tree Controller
|
||||
treeController = [[CPTreeController alloc] init];
|
||||
[treeController setChildrenKeyPath:@"children"];
|
||||
[treeController setContent:contentArray];
|
||||
|
||||
// 3. Setup the Outline View
|
||||
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(20, 20, 250, 300)];
|
||||
[scrollView setAutohidesScrollers:YES];
|
||||
|
||||
var outlineView = [[CPOutlineView alloc] initWithFrame:CGRectMake(0, 0, 250, 300)];
|
||||
var column = [[CPTableColumn alloc] initWithIdentifier:@"name"];
|
||||
[[column headerView] setStringValue:@"Node Name"];
|
||||
[column setWidth:240];
|
||||
[column setEditable:YES]; // Editable to test bidirectional bindings in the tree
|
||||
|
||||
[outlineView addTableColumn:column];
|
||||
[outlineView setOutlineTableColumn:column];
|
||||
[outlineView setAllowsMultipleSelection:YES];
|
||||
[scrollView setDocumentView:outlineView];
|
||||
[contentView addSubview:scrollView];
|
||||
|
||||
// 4. Establish Bindings for the Outline View
|
||||
[outlineView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
|
||||
[outlineView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
|
||||
|
||||
var scrollView2 = [[CPScrollView alloc] initWithFrame:CGRectMake(300, 20, 250, 300)];
|
||||
[scrollView2 setAutohidesScrollers:YES];
|
||||
var outlineView2 = [[CPOutlineView alloc] initWithFrame:CGRectMake(0, 0, 250, 300)];
|
||||
var column2 = [[CPTableColumn alloc] initWithIdentifier:@"name"];
|
||||
[[column2 headerView] setStringValue:@"Node Name"];
|
||||
[column2 setWidth:240];
|
||||
[column2 setEditable:YES]; // Editable to test bidirectional bindings in the tree
|
||||
|
||||
[outlineView2 addTableColumn:column2];
|
||||
[outlineView2 setOutlineTableColumn:column2];
|
||||
[outlineView2 setAllowsMultipleSelection:YES];
|
||||
[scrollView2 setDocumentView:outlineView2];
|
||||
[contentView addSubview:scrollView2];
|
||||
|
||||
// 4. Establish Bindings for the Outline View
|
||||
[outlineView2 bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
|
||||
[outlineView2 bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
|
||||
|
||||
|
||||
|
||||
[theWindow orderFront:self];
|
||||
}
|
||||
|
||||
- (void)selectSpecificNode:(id)sender
|
||||
{
|
||||
// Programmatically select index path [0, 1] which is "Child 1.2"
|
||||
// This tests the `_CPOutlineViewSelectionIndexPathsBinder` auto-expand logic.
|
||||
var path =[CPIndexPath indexPathWithIndexes:[0, 1]];
|
||||
[treeController setSelectionIndexPath:path];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context
|
||||
{
|
||||
if (keyPath === @"selectionIndexPaths")
|
||||
{
|
||||
var selectedObjects = [treeController selectedObjects];
|
||||
if ([selectedObjects count] > 0)
|
||||
{
|
||||
var names = [CPMutableArray array];
|
||||
for (var i = 0; i <[selectedObjects count]; i++)
|
||||
[names addObject:[selectedObjects[i] name]];[logField setStringValue:[names componentsJoinedByString:@", "]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[logField setStringValue:@"Nothing selected"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// --- Custom Data Model ---
|
||||
|
||||
@implementation Node : CPObject
|
||||
{
|
||||
CPString name;
|
||||
CPArray children;
|
||||
}
|
||||
|
||||
- (id)initWithName:(CPString)aName children:(CPArray)someChildren
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
name = aName;
|
||||
children = someChildren;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Explicit accessors to ensure Key-Value Observing (KVO) works flawlessly.
|
||||
- (void)setName:(CPString)aName
|
||||
{
|
||||
[self willChangeValueForKey:@"name"];
|
||||
name = aName;[self didChangeValueForKey:@"name"];
|
||||
}
|
||||
|
||||
- (CPString)name
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
- (void)setChildren:(CPArray)someChildren
|
||||
{
|
||||
[self willChangeValueForKey:@"children"];
|
||||
children = someChildren;
|
||||
[self didChangeValueForKey:@"children"];
|
||||
}
|
||||
|
||||
- (CPArray)children
|
||||
{
|
||||
return children;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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>CPApplicationDelegateClass</key>
|
||||
<string>AppController</string>
|
||||
<key>CPBundleName</key>
|
||||
<string>treecontroller</string>
|
||||
<key>CPPrincipalClass</key>
|
||||
<string>CPApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Jakefile
|
||||
* tooltips
|
||||
*
|
||||
* Created by You on April 26, 2011.
|
||||
* Copyright 2011, Your Company All rights reserved.
|
||||
*/
|
||||
|
||||
var ENV = require("system").env,
|
||||
FILE = require("file"),
|
||||
JAKE = require("jake"),
|
||||
task = JAKE.task,
|
||||
FileList = JAKE.FileList,
|
||||
app = require("cappuccino/jake").app,
|
||||
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug",
|
||||
OS = require("os");
|
||||
|
||||
app ("tooltips", function(task)
|
||||
{
|
||||
task.setBuildIntermediatesPath(FILE.join("Build", "tooltips.build", configuration));
|
||||
task.setBuildPath(FILE.join("Build", configuration));
|
||||
|
||||
task.setProductName("tooltips");
|
||||
task.setIdentifier("com.yourcompany.tooltips");
|
||||
task.setVersion("1.0");
|
||||
task.setAuthor("Your Company");
|
||||
task.setEmail("feedback @nospam@ yourcompany.com");
|
||||
task.setSummary("tooltips");
|
||||
task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**")));
|
||||
task.setResources(new FileList("Resources/**"));
|
||||
task.setIndexFilePath("index.html");
|
||||
task.setInfoPlistPath("Info.plist");
|
||||
|
||||
if (configuration === "Debug")
|
||||
task.setCompilerFlags("-DDEBUG -g");
|
||||
else
|
||||
task.setCompilerFlags("-O");
|
||||
});
|
||||
|
||||
task ("default", ["tooltips"], function()
|
||||
{
|
||||
printResults(configuration);
|
||||
});
|
||||
|
||||
task ("build", ["default"]);
|
||||
|
||||
task ("debug", function()
|
||||
{
|
||||
ENV["CONFIGURATION"] = "Debug";
|
||||
JAKE.subjake(["."], "build", ENV);
|
||||
});
|
||||
|
||||
task ("release", function()
|
||||
{
|
||||
ENV["CONFIGURATION"] = "Release";
|
||||
JAKE.subjake(["."], "build", ENV);
|
||||
});
|
||||
|
||||
task ("run", ["debug"], function()
|
||||
{
|
||||
OS.system(["open", FILE.join("Build", "Debug", "tooltips", "index.html")]);
|
||||
});
|
||||
|
||||
task ("run-release", ["release"], function()
|
||||
{
|
||||
OS.system(["open", FILE.join("Build", "Release", "tooltips", "index.html")]);
|
||||
});
|
||||
|
||||
task ("deploy", ["release"], function()
|
||||
{
|
||||
FILE.mkdirs(FILE.join("Build", "Deployment", "tooltips"));
|
||||
OS.system(["press", "-f", FILE.join("Build", "Release", "tooltips"), FILE.join("Build", "Deployment", "tooltips")]);
|
||||
printResults("Deployment")
|
||||
});
|
||||
|
||||
task ("desktop", ["release"], function()
|
||||
{
|
||||
FILE.mkdirs(FILE.join("Build", "Desktop", "tooltips"));
|
||||
require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "tooltips"), FILE.join("Build", "Desktop", "tooltips", "tooltips.app"));
|
||||
printResults("Desktop")
|
||||
});
|
||||
|
||||
task ("run-desktop", ["desktop"], function()
|
||||
{
|
||||
OS.system([FILE.join("Build", "Desktop", "tooltips", "tooltips.app", "Contents", "MacOS", "NativeHost"), "-i"]);
|
||||
});
|
||||
|
||||
function printResults(configuration)
|
||||
{
|
||||
print("----------------------------");
|
||||
print(configuration+" app built at path: "+FILE.join("Build", configuration, "tooltips"));
|
||||
print("----------------------------");
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<!--
|
||||
index-debug.html
|
||||
__project.name__
|
||||
|
||||
Created by __user.name__ on __project.date__.
|
||||
Copyright __project.year__, __organization.name__ All rights reserved.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!--[if lte IE 8]>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1">
|
||||
<![endif]-->
|
||||
<!--[if gte IE 9]>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
|
||||
<![endif]-->
|
||||
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
|
||||
<link rel="apple-touch-icon" href="Resources/icon.png">
|
||||
<link rel="apple-touch-startup-image" href="Resources/default.png">
|
||||
|
||||
<title>__project.name__</title>
|
||||
|
||||
<!-- Custom javascript goes here -->
|
||||
<!-- End custom javascript -->
|
||||
|
||||
<script type="text/javascript">
|
||||
OBJJ_MAIN_FILE = "main.j";
|
||||
OBJJ_INCLUDE_PATHS = ["Frameworks/Debug", "Frameworks"];
|
||||
// The below will tell the compiler to generate debug symbols, type signatures and not inline objj_msgSend functions.
|
||||
// This will affect only Objective-J code that is compiled when loading the application. It will not affect precompiled
|
||||
// code like the Cappuccino frameworks.
|
||||
//
|
||||
// Debug symbols will give each Objective-J method a Javascript function name. Without a name it will be very hard to find
|
||||
// the methods in the debugger.
|
||||
// Type Signatures will give type information to the Objective-J runtime for instance variables and methods.
|
||||
// Inline objj_msgSend will give a speed increase but decorators will not work. Check below on debug options for
|
||||
// more information on decorators.
|
||||
//
|
||||
// Uncomment or comment on the line below to change the flags
|
||||
OBJJ_COMPILER_FLAGS = ["IncludeDebugSymbols", "IncludeTypeSignatures", "SourceMap", "InlineMsgSend"];
|
||||
|
||||
var progressBar = null;
|
||||
|
||||
OBJJ_PROGRESS_CALLBACK = function(percent, appSize, path)
|
||||
{
|
||||
percent = percent * 100;
|
||||
|
||||
if (!progressBar)
|
||||
progressBar = document.getElementById("progress-bar");
|
||||
|
||||
if (progressBar)
|
||||
progressBar.style.width = Math.min(percent, 100) + "%";
|
||||
}
|
||||
|
||||
var loadingHTML =
|
||||
'<div id="loading">' +
|
||||
' <div id="loading-text">Loading...</div>' +
|
||||
' <div id="progress-indicator">' +
|
||||
' <span id="progress-bar" style="width:0%"></span>' +
|
||||
' </div>' +
|
||||
'</div>';
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="Frameworks/Debug/Objective-J/Objective-J.js" charset="UTF-8"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
objj_msgSend_reset();
|
||||
|
||||
// DEBUG OPTIONS:
|
||||
// Decorators will only work when the code is compiled without the compiler option 'InlineMsgSend'. Check above.
|
||||
|
||||
// Uncomment to enable printing of backtraces on exceptions:
|
||||
//objj_msgSend_decorate(objj_backtrace_decorator);
|
||||
|
||||
// Uncomment to supress exceptions that take place inside a message
|
||||
//objj_msgSend_decorate(objj_supress_exceptions_decorator)
|
||||
|
||||
// Uncomment to enable runtime type checking:
|
||||
//objj_msgSend_decorate(objj_typecheck_decorator);
|
||||
|
||||
// Uncomment (along with both above) to print backtraces on type check errors:
|
||||
//objj_typecheck_prints_backtrace = true;
|
||||
|
||||
// Uncomment to disable the default logger (CPLogConsole if window.console exists, CPLogPopup otherwise):
|
||||
//CPLogUnregister(CPLogDefault);
|
||||
|
||||
// Uncomment to enable a specific logger:
|
||||
//CPLogRegister(CPLogConsole);
|
||||
//CPLogRegister(CPLogPopup);
|
||||
|
||||
// Tag view DOM elements with a "data-cappuccino-view" attribute that contains
|
||||
// the class name of the view that created them. Comment this or set to false to disable.
|
||||
appkit_tag_dom_elements = true;
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
html, body, h1, p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* We need a body wrapper because Cappuccino is unhappy if we change the body element */
|
||||
#cappuccino-body {
|
||||
/* Position it absolutely so it will fill the height without content */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
/* Put it at the bottom of the stack so it doesn't interfere with UI */
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#cappuccino-body .container {
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#cappuccino-body .content {
|
||||
display: table-cell;
|
||||
height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#loading {
|
||||
position: relative;
|
||||
top: 35%;
|
||||
}
|
||||
|
||||
#loading-text {
|
||||
height: 1.5em;
|
||||
color: #555;
|
||||
font: normal bold 36px/36px Arial, sans-serif;
|
||||
}
|
||||
|
||||
#progress-indicator {
|
||||
padding: 0px;
|
||||
height: 16px;
|
||||
border: 5px solid #555;
|
||||
border-radius: 18px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#progress-bar {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
display: block;
|
||||
height: 18px;
|
||||
|
||||
/* Compensate for moving the bar left 1px to overlap the indicator border */
|
||||
border-right: 1px solid #555;
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
#noscript {
|
||||
position: relative;
|
||||
top: 35%;
|
||||
padding: 1em 1.5em;
|
||||
border: 5px solid #555;
|
||||
border-radius: 16px;
|
||||
background-color: white;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
font: bold 24px Arial, sans-serif;
|
||||
}
|
||||
|
||||
#noscript a {
|
||||
color: #98c0ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="cappuccino-body">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<script type="text/javascript">
|
||||
document.write(loadingHTML);
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<noscript style="position:absolute; top:0; left:0; width:100%; height:100%">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div id="noscript">
|
||||
<p style="font-size:120%; margin-bottom:.75em">JavaScript is required for this application.</p>
|
||||
<p><a href="http://www.enable-javascript.com" target="_blank">Enable JavaScript</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,166 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<!--
|
||||
index.html
|
||||
__project.name__
|
||||
|
||||
Created by __user.name__ on __project.date__.
|
||||
Copyright __project.year__, __organization.name__ All rights reserved.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!--[if lte IE 8]>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1">
|
||||
<![endif]-->
|
||||
<!--[if gte IE 9]>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
|
||||
<![endif]-->
|
||||
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
|
||||
<link rel="apple-touch-icon" href="Resources/icon.png">
|
||||
<link rel="apple-touch-startup-image" href="Resources/default.png">
|
||||
|
||||
<title>__project.name__</title>
|
||||
|
||||
<!-- Custom javascript goes here -->
|
||||
<!-- End custom javascript -->
|
||||
|
||||
<script type="text/javascript">
|
||||
OBJJ_MAIN_FILE = "main.j";
|
||||
// The below will tell the compiler to not generate debug symbols but will generate type signatures and inline objj_msgSend functions.
|
||||
// This will affect only Objective-J code that is compiled when loading the application. It will not affect precompiled
|
||||
// code like the Cappuccino frameworks.
|
||||
// Uncomment or comment on the line below to change the flags
|
||||
OBJJ_COMPILER_FLAGS = [/*"IncludeDebugSymbols"*/, "IncludeTypeSignatures"/*, "SourceMap"*/, "InlineMsgSend"];
|
||||
|
||||
var progressBar = null;
|
||||
|
||||
OBJJ_PROGRESS_CALLBACK = function(percent, appSize, path)
|
||||
{
|
||||
percent = percent * 100;
|
||||
|
||||
if (!progressBar)
|
||||
progressBar = document.getElementById("progress-bar");
|
||||
|
||||
if (progressBar)
|
||||
progressBar.style.width = Math.min(percent, 100) + "%";
|
||||
}
|
||||
|
||||
var loadingHTML =
|
||||
'<div id="loading">' +
|
||||
' <div id="loading-text">Loading...</div>' +
|
||||
' <div id="progress-indicator">' +
|
||||
' <span id="progress-bar" style="width:0%"></span>' +
|
||||
' </div>' +
|
||||
'</div>';
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="Frameworks/Objective-J/Objective-J.js" charset="UTF-8"></script>
|
||||
|
||||
<style type="text/css">
|
||||
html, body, h1, p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* We need a body wrapper because Cappuccino is unhappy if we change the body element */
|
||||
#cappuccino-body {
|
||||
/* Position it absolutely so it will fill the height without content */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
/* Put it at the bottom of the stack so it doesn't interfere with UI */
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#cappuccino-body .container {
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#cappuccino-body .content {
|
||||
display: table-cell;
|
||||
height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#loading {
|
||||
position: relative;
|
||||
top: 35%;
|
||||
}
|
||||
|
||||
#loading-text {
|
||||
height: 1.5em;
|
||||
color: #555;
|
||||
font: normal bold 36px/36px Arial, sans-serif;
|
||||
}
|
||||
|
||||
#progress-indicator {
|
||||
padding: 0px;
|
||||
height: 16px;
|
||||
border: 5px solid #555;
|
||||
border-radius: 18px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#progress-bar {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
display: block;
|
||||
height: 18px;
|
||||
|
||||
/* Compensate for moving the bar left 1px to overlap the indicator border */
|
||||
border-right: 1px solid #555;
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
#noscript {
|
||||
position: relative;
|
||||
top: 35%;
|
||||
padding: 1em 1.5em;
|
||||
border: 5px solid #555;
|
||||
border-radius: 16px;
|
||||
background-color: white;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
font: bold 24px Arial, sans-serif;
|
||||
}
|
||||
|
||||
#noscript a {
|
||||
color: #98c0ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="cappuccino-body">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<script type="text/javascript">
|
||||
document.write(loadingHTML);
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<noscript style="position:absolute; top:0; left:0; width:100%; height:100%">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div id="noscript">
|
||||
<p style="font-size:120%; margin-bottom:.75em">JavaScript is required for this application.</p>
|
||||
<p><a href="http://www.enable-javascript.com" target="_blank">Enable JavaScript</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* AppController.j
|
||||
* tooltips
|
||||
*
|
||||
* Created by You on April 26, 2011.
|
||||
* Copyright 2011, Your Company 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