improved: bindings to CPOutlineView

This commit is contained in:
daboe01
2026-03-08 18:03:52 +01:00
parent 8ac2b94400
commit c70079ae71
10 changed files with 873 additions and 80 deletions
+1
View File
@@ -116,3 +116,4 @@
@import "CPWindowController.j"
@import "CPWorkspace.j"
@import "CPFontPanel.j"
@import "CPTreeController.j"
+121 -32
View File
@@ -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];
var rootNode = [contentBinder respondsToSelector:@selector(rootNode)] ? [contentBinder rootNode] : nil;
// 1. SUPPRESS KVO AT THE VERY TOP to avoid circular updates when expanding parents
_isSyncingFromModel = YES;
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
+24 -7
View File
@@ -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
{
+81 -41
View File
@@ -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;
index = [[_parentNode childNodes] indexOfObject:self];
path = [_parentNode indexPath];
if (path != nil)
{
return [path indexPathByAddingIndex:index];
}
else
{
return [CPIndexPath indexPathWithIndex:index];
}
}
else
{
return nil;
var index = [_childNodes indexOfObjectIdenticalTo:self];
// 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 (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]];
return node;
var index = [indexPath indexAtPosition:0],
count = [_childNodes count];
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>
+18
View File
@@ -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);
}