In preparation for the Augmented Reality class that Jonathan Blocksom and I taught at the recent iPhone / iPad devcon, Using AVFoundation video capture, we modernized and completed the example found at morethantechnical.com using NyARToolkit (GPL) on iOS. It's a great example of the kinds of things we can do now that we have fast devices with cameras to play with.
You can view a shaky video of the example running on an iPad 2 here.
You can get the code from github here.
Be aware that this code is GPL, which (among other things) means that you have to release any changes you make to the code under GPL and that this code cannot be used in a commercial project. There is also some speculation that the terms of using the App Store preclude GPL licensed projects as well. Caveat Coder.
In preparation for the Augmented Reality class that Jonathan Blocksom and I taught at the recent iPhone / iPad devcon, Using AVFoundation video capture, we modernized and completed the example found at morethantechnical.com using NyARToolkit (GPL) on iOS. It's a great example of the kinds of things we can do now that we have fast devices with cameras to play with.
You can view a shaky video of the example running on an iPad 2 here.
You can get the code from github here.
Be aware that this code is GPL, which (among other things) means that you have to release any changes you make to the code under GPL and that this code cannot be used in a commercial project. There is also some speculation that the terms of using the App Store preclude GPL licensed projects as well. Caveat Coder.
- (void)viewDidLoad
{
[super viewDidLoad];
self.fontNames = [NSMutableArray array];
NSArray *fontFamilyNames = [UIFont familyNames];
for (NSString *familyName in fontFamilyNames) {
NSLog(@"familyName = %@", familyName);
NSArray *names = [UIFont fontNamesForFamilyName:familyName];
NSLog(@"FontNames = %@", fontNames);
[self.fontNames addObjectsFromArray:names];
}
}
.
.
.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
NSString *name = [self.fontNames objectAtIndex:indexPath.row];
cell.text = name;
cell.font = [UIFont fontWithName:name size:14];
return cell;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.fontNames = [NSMutableArray array];
NSArray *fontFamilyNames = [UIFont familyNames];
for (NSString *familyName in fontFamilyNames) {
NSLog(@"familyName = %@", familyName);
NSArray *names = [UIFont fontNamesForFamilyName:familyName];
NSLog(@"FontNames = %@", fontNames);
[self.fontNames addObjectsFromArray:names];
}
}
.
.
.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
NSString *name = [self.fontNames objectAtIndex:indexPath.row];
cell.text = name;
cell.font = [UIFont fontWithName:name size:14];
return cell;
}
Time to pray to google. Ah! Zorn! You beautiful helpful coding-type person. You've pasted it for us. Thank you!
Now all that is left is to pythonify it. (I made a few modifications to the behavior, but it's the same general idea)
#
# CBTimeLengthFormatter.py
# PuppyTracker
#
# Created by Jonathan Saggau on 12/3/07.
# Copyright (c) 2007 __MyCompanyName__. All rights reserved.
from Foundation import *
from math import floor
#modified from http://paste.lisp.org/display/21854
class CBTimeLengthFormatter(NSNumberFormatter):
def stringForObjectValue_(self, anObject):
if (not (anObject.isKindOfClass_(NSNumber))):
return(None)
if (anObject.intValue() <= 0):
return("00:00:00")
intval = int(floor(anObject))
hours = intval / (60*60)
minutes = (intval - (hours * 60 * 60)) / 60
seconds = intval - (minutes * 60) - (hours * 60 * 60)
string = "%02i:%02i:%02i" %((hours), (minutes), (seconds))
return(string)
def getObjectValue_forString_errorDescription_(self, objVal, inString, err):
"""Take a string like "00:00:00" and turns it into a NSNumber and returns YES
Also able to handle 10:10 (as 10 minutes, 10 seconds) and 10 (as 10 seconds)"""
string = NSString.stringWithString_(inString)
#catch for nil or empty string
if (string == None or string.isEqualToString_("")):
return True, 0, None
stringList = string.split(":")
#make seconds first, instead of hours
stringList.reverse()
#turn each into an integer, filtering out empty strings
try:
stringList = [int(each) for each in stringList if each is not u'']
#if we can't make any part of this into an int, bail
except ValueError, e:
return False, 0, None
#make sure we have Seconds, Hours, Minutes by padding the list with zeros
#in case we have (say) Seconds, Hours only
while ( len(stringList) < 3):
stringList.append(0)
#sec #min #hour
timeInSeconds = stringList[0] + stringList[1]*60 + stringList[2]*60*60
return True, timeInSeconds, None
Time to pray to google. Ah! Zorn! You beautiful helpful coding-type person. You've pasted it for us. Thank you!
Now all that is left is to pythonify it. (I made a few modifications to the behavior, but it's the same general idea)
#
# CBTimeLengthFormatter.py
# PuppyTracker
#
# Created by Jonathan Saggau on 12/3/07.
# Copyright (c) 2007 __MyCompanyName__. All rights reserved.
from Foundation import *
from math import floor
#modified from http://paste.lisp.org/display/21854
class CBTimeLengthFormatter(NSNumberFormatter):
def stringForObjectValue_(self, anObject):
if (not (anObject.isKindOfClass_(NSNumber))):
return(None)
if (anObject.intValue() <= 0):
return("00:00:00")
intval = int(floor(anObject))
hours = intval / (60*60)
minutes = (intval - (hours * 60 * 60)) / 60
seconds = intval - (minutes * 60) - (hours * 60 * 60)
string = "%02i:%02i:%02i" %((hours), (minutes), (seconds))
return(string)
def getObjectValue_forString_errorDescription_(self, objVal, inString, err):
"""Take a string like "00:00:00" and turns it into a NSNumber and returns YES
Also able to handle 10:10 (as 10 minutes, 10 seconds) and 10 (as 10 seconds)"""
string = NSString.stringWithString_(inString)
#catch for nil or empty string
if (string == None or string.isEqualToString_("")):
return True, 0, None
stringList = string.split(":")
#make seconds first, instead of hours
stringList.reverse()
#turn each into an integer, filtering out empty strings
try:
stringList = [int(each) for each in stringList if each is not u'']
#if we can't make any part of this into an int, bail
except ValueError, e:
return False, 0, None
#make sure we have Seconds, Hours, Minutes by padding the list with zeros
#in case we have (say) Seconds, Hours only
while ( len(stringList) < 3):
stringList.append(0)
#sec #min #hour
timeInSeconds = stringList[0] + stringList[1]*60 + stringList[2]*60*60
return True, timeInSeconds, None
@implementation JSKVC
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
{
if (aSelector == @selector(object:willChangeValueForKey:))
return NO;
if (aSelector == @selector(object:didChangeValueForKey:))
return NO;
return YES;
}
+ (NSString *)webScriptNameForSelector:(SEL)aSelector
{
if (aSelector == @selector(object:willChangeValueForKey:))
return @"willChange";
if (aSelector == @selector(object:didChangeValueForKey:))
return @"didChange";
return nil;
}
- (void)object:(id)object willChangeValueForKey:(NSString *)key
{
[object willChangeValueForKey:key];
}
- (void)object:(id)object didChangeValueForKey:(NSString *)key
{
[object didChangeValueForKey:key];
}
<html>
<head>
<title>bindings example</title>
<script type="text/javascript" language="javascript" src="boxinfo.js"></script>
</head>
<body>
<p><input type="text" id="box" value="0" maxlength="4" name="contentBox" /></p>
</body>
</html>
</pre>
Then we'll need a method that will call our cocoa KVO willChange and didChange methods. JSKVC will get injected into the javascript runtime from cocoa below.
// bindings.js BoxInfo.prototype = { get content() { if (!this.box) return null; return this.box.value; }, set content(w) { if (!this.box) return; this.box.value = w; }, }
function BoxInfo() {
this.box = null;
this.setBox = function(newBox) {
// store the new box value, with KVO notifcations
JSKVC.willChange(document, 'boxInfo');
this.box = newBox;
JSKVC.didChange(document, 'boxInfo');
}
}
To finish things off in javascript, we need to tell it which box to deal with. Jim's code has a nice bit of functionality that allows the user to choose what he/she is modifying by clicking on an image. Mine is simply getting the box we're interested in through its unique ID.
function installBoxInfo() {
// create an BoxInfo object for the (DOM) document, using KVO notifications
JSKVC.willChange(document, 'boxInfo');
document.boxInfo = new BoxInfo();
JSKVC.didChange(document, 'boxInfo');
var boxElement = document.getElementById( 'box' );
document.boxInfo.setBox(boxElement);
//boxElement.setAttribute('style', 'outline: -3px solid white;');
boxElement.onblur = function() {
JSKVC.willChange(document, 'boxInfo');
JSKVC.didChange(document, 'boxInfo');
};
}
The boxElement.onblur event (for those of us, like me who aren't familiar with javascript) is the event that gets triggered when the user leaves the text box. Since the user is likely to have changed the contents of the text box, we call willChange and didChange to let cocoa know it needs to update the other widgets and doodads. More on this in a bit.
- (void)webView:(WebView *)sender windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject
{
// as soon as the window object is available, inject the code for the boxInfo object
NSString *stringForBoxInfo = [self stringForResource:@"boxinfo" ofType:@"js"];
[windowScriptObject evaluateWebScript:stringForBoxInfo];
// add the key-value notification object to the script environment
[windowScriptObject setValue:[[JSKVC alloc] init] forKey:@"JSKVC"];
}
Then, once the web frame is loaded, we need to setup our BoxInfo object by calling the installBoxInfo(); function
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
// modify the DOM document each time a new page loads
[[webView windowScriptObject] evaluateWebScript:@"installBoxInfo();"];
}
And, should we ever want to (say) call a method in javascript from cocoa, we set up a WebScriptObject by evaluating a small bit of javascript code corresponding to the object we want to message (document.boxInfo below) and then call- (id)callWebScriptMethod:(NSString *)name withArguments:(NSArray *)args;
WebScriptObject *boxInfo = [[webView windowScriptObject] evaluateWebScript:@"document.boxInfo"]; [boxInfo callWebScriptMethod:@"setBox" withArguments:[NSArray arrayWithObject:obj]];That's about all of the code. Now let's take a look at the NIB file.
#!/bin/tcsh set PATH=`dirname $0` setenv DYLD_FRAMEWORK_PATH /Volumes/WebKit/WebKit.app/Contents/Resources/ setenv WebKit_UNSET_DYLD_FRAMEWORK_PATH YES $PATH/build/debug/JavaScriptBindings.app/Contents/MacOS/JavaScriptBindings
Actually, depending on context of implementation, using a linked list is both quite easy and very efficient. Obviously, inserting/deleting objects into a linked list is trivial and fast if you have both a next and a previous pointer. Given that Core Data will maintain the inverse relationship automatically, it will also maintain the linked list pointers automatically.He's writing about a common data structure called a double (or doubly) linked list wherein each object in the list holds a pointer to the next and previous objects that can, in certain situations, be more useful for ordering data than an indexed set. We can enumerate in either direction very easily, asking each object in the chain for next or previous until we get nil and we can insert, remove and reorder objects with little hassle. All we have to do is make sure that next and previous values of adjacent objects are set appropriately (more on this in a second). Unfortunately, We can't (efficiently) get an object at an index like we can with an indexed list because we're purposely not indexing the collection. Also, remember that much of cocoa will want a data structure that responds to objectAtIndex which, as Bill mentiones above, will require us to iterate the chain and put it into an array. We also can't place an object in the chain at two different positions (but two objects with identical attributes can coexist in the chain).
Given that many UI type usage contexts will require all of the objects to be faulted into memory anyway, walking the "next" relations and filling an NSMutableArray for display purposes is about three lines of code.
... In this particular example, the amount of overhead in the form of additional lines of code incurred by the developer is pretty trivial. ... b.bum
svn co https://jonathansaggau.com/svn/MOLinkedListData Model
There are four entities here:
JSMOLLContainer (abstract entity):
Contains most of the logic of inserting, deleting, reordering, etc.
Relationship:
Nodes: To-Many relationship
Delete Rule: cascade (removing the container removes the nodes)
ThingContainer (subentity of JSMOLLContainer):
You can replace this with your subentity, including any attributes you might want to add.
JSMONode(abstract entity)::
Contains only getters and setters for next and previous.
Relationships:
Container (JSMOLLContainer):
Delete Rule: Nullify
Next (JSMONode):
Delete Rule: Nullify
Prev (JSMONode):
Delete Rule: Nullify
Thing (subentity of JSMONode):
You can replace this with your subentity, including any attributes you might want to add. I've chosen to add a "name" attribute (a string) as a demonstration.
Implementation
#import "_JSMOLLContainer.h"
//_JSMOLLContainer is the machine generated code from mogenerator. It has only
//the accessors and the KVO notifications within.
@interface JSMOLLContainer : _JSMOLLContainer
{
//we cache first and last values because the fetch takes a while
@private
JSMONode *_cachedFirst;
JSMONode *_cachedLast;
}
- (void)addNodesObjectAtFront:(JSMONode*)value_;
- (void)addNodesObjectAtEnd:(JSMONode*)value_;
- (void) moveNode: (JSMONode *)moveMe
before: (JSMONode *)beforeMe;
- (void) moveNode: (JSMONode *)moveMe
after: (JSMONode *)afterMe;
- (JSMONode *)first;
- (JSMONode *)last;
- (NSArray *)nodesArray;
- (NSEnumerator *)nodesEnumerator;
- (NSEnumerator *)reverseNodesEnumerator;
@end
Adding a node
- (void)addNodesObject: (JSMONode*)value_
{
//add node object at the END of the chain.
[super addNodesObject: value_];
JSMONode *last = [self last];
JSMONode *newNext = [last next]; // should be null
[value_ setPrev: last];
[value_ setNext: newNext];
[self set_cachedLast: value_];
if (IsEmpty(last))
[self set_cachedFirst:value_];
}
There is a similar method for putting a node object at the head of the chain.
- (void)removeNodesObject: (JSMONode*)value_
{
[super removeNodesObject: value_];
//set next and prev as needed on bordering objs
if (!IsEmpty([value_ prev]))
[[value_ prev] setNext: [value_ next]];
else
[[value_ next] setPrev: [value_ prev]];
}
Reordering
- (void) moveNode: (JSMONode *)moveMe
before: (JSMONode *)beforeMe
{
[self removeNodesObject: moveMe];
JSMONode *myNewPrev = [beforeMe prev];
if(!IsEmpty(myNewPrev))
{
//if we are not moving moveMe to the front of the chain, this works
//otherwise there IS NO myNewPrev and we have to use the method below.
[myNewPrev setNext: moveMe];
[moveMe setNext: beforeMe];
}
else
{
[moveMe setPrev: myNewPrev];
[beforeMe setPrev: moveMe];
}
}
The rest of the implementation deals with building up simple enumerators and also with fetching the first and last nodes (where prev is Null or next is Null respectively) from the Managed Object Context when it turns out we haven't cached it appropriately. Eventually, the fetch requests will go away. Right now, they'll only likely be needed when we've done some reordering as that code doesn't update the cached first and last values as it should. To make sure that we're really returning the first node, we simply check that the _cachedFirst node's prev value is Null (actually [NSNull null]); that's what IsEmpty does here.
- (NSArray *)_nodesFilteredUsingPredicate: (NSPredicate *)predicate
{
if(0 == [[self nodesSet] count]) return [NSArray array];
NSEntityDescription * entity = [NSEntityDescription entityForName: @"JSMONode"
inManagedObjectContext: [self managedObjectContext]];
NSFetchRequest * fetch = [[NSFetchRequest alloc] init];
[fetch setEntity: entity];
[fetch setPredicate: predicate];
NSManagedObjectContext *context = [self managedObjectContext];
NSArray * results = [context executeFetchRequest: fetch error: nil];
[fetch release];
return results;
}
- (JSMONode *)first
{
if(0 == [[self nodesSet] count]) return nil;
//If we haven't yet cachedFirst we need to find it.
//IF cachedFirst previous value is not null, cachedFirst is no longer actually first.
if (!IsEmpty(_cachedFirst) && IsEmpty([_cachedFirst prev]))
return _cachedFirst;
JSMONode *first;
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"container == %@ AND prev == %@", self, [NSNull null]];
NSArray *results = [self _nodesFilteredUsingPredicate: predicate];
if (0 == [results count])
{
[self set_cachedFirst: nil];
return nil;
}
NSAssert(([results count] == 1), @"There should be one or fewer firstThing(s)");
first = [results objectAtIndex: 0];
[self set_cachedFirst: first];
return first;
}
- (JSMONode *)last
{
if(0 == [[self nodesSet] count]) return nil;
if (!IsEmpty(_cachedLast) && IsEmpty([_cachedLast next]))
return _cachedLast;
JSMONode *last;
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"container == %@ AND next == %@", self, [NSNull null]];
NSArray *results = [self _nodesFilteredUsingPredicate: predicate];
if (0 == [results count])
{
[self set_cachedLast: nil];
return nil;
}
NSAssert(([results count] == 1), @"There should be one or fewer lastThing(s)");
last = [results objectAtIndex: 0];
[self set_cachedLast: last];
return last;
}
Meanwhile, back at the Hall of Justice: usage
So how do we use it? We simply insert a JSMOLLContainer subclass (in the case of the diagram above, it's a ThingContainer) into the managed object context like so:
container = [NSEntityDescription insertNewObjectForEntityForName:@"ThingContainer"
inManagedObjectContext:moc];
thingZero = [[NSEntityDescription insertNewObjectForEntityForName:@"Thing"
inManagedObjectContext:moc]
//perhaps we want to set an attribute
[thingZero setName:@"Thing Zero Here"];
[container addNodesObject:thingZero];
thingOne = [[NSEntityDescription insertNewObjectForEntityForName:@"Thing"
inManagedObjectContext:moc]
//perhaps we want to set an attribute
[thingOne setName:@"Thing One Here"];
[container addNodesObject: thingOne];
Now we've a container with two things in this order: thingZero -> thingOne.
JSMONode *currentThing;
NSEnumerator *nodesEnumerator = [container nodesEnumerator];
while (currentThing = [nodesEnumerator nextObject])
{
//do something
}
2. We can ask the container for the first object and keep asking for next
JSMONode *currentThing = [container first];
while (!IsEmpty(currentThing))
{
//do something
currentThing = [currentThing next];
}
At this point, I should probably show you the IsEmpty function. This is mostly stolen from Wil Shipley's blog . I just added the test for [NSNull null], which is the "empty" object for insertion into cocoa collections (like a core data graph, for instance).
//Thanks Wil
static inline BOOL IsEmpty(id thing) {
return thing == nil
|| ([thing isEqual:[NSNull null]]) //JS addition for coredata
|| ([thing respondsToSelector:@selector(length)]
&& [(NSData *)thing length] == 0)
|| ([thing respondsToSelector:@selector(count)]
&& [(NSArray *)thing count] == 0);
}
[container addNodesObject: node7]2. Then place it after node3
[container moveNode: node7
after: node3;]
Actually, depending on context of implementation, using a linked list is both quite easy and very efficient. Obviously, inserting/deleting objects into a linked list is trivial and fast if you have both a next and a previous pointer. Given that Core Data will maintain the inverse relationship automatically, it will also maintain the linked list pointers automatically.He's writing about a common data structure called a double (or doubly) linked list wherein each object in the list holds a pointer to the next and previous objects that can, in certain situations, be more useful for ordering data than an indexed set. We can enumerate in either direction very easily, asking each object in the chain for next or previous until we get nil and we can insert, remove and reorder objects with little hassle. All we have to do is make sure that next and previous values of adjacent objects are set appropriately (more on this in a second). Unfortunately, We can't (efficiently) get an object at an index like we can with an indexed list because we're purposely not indexing the collection. Also, remember that much of cocoa will want a data structure that responds to objectAtIndex which, as Bill mentiones above, will require us to iterate the chain and put it into an array. We also can't place an object in the chain at two different positions (but two objects with identical attributes can coexist in the chain).
Given that many UI type usage contexts will require all of the objects to be faulted into memory anyway, walking the "next" relations and filling an NSMutableArray for display purposes is about three lines of code.
... In this particular example, the amount of overhead in the form of additional lines of code incurred by the developer is pretty trivial. ... b.bum
svn co https://jonathansaggau.com/svn/MOLinkedListData Model
There are four entities here:
JSMOLLContainer (abstract entity):
Contains most of the logic of inserting, deleting, reordering, etc.
Relationship:
Nodes: To-Many relationship
Delete Rule: cascade (removing the container removes the nodes)
ThingContainer (subentity of JSMOLLContainer):
You can replace this with your subentity, including any attributes you might want to add.
JSMONode(abstract entity)::
Contains only getters and setters for next and previous.
Relationships:
Container (JSMOLLContainer):
Delete Rule: Nullify
Next (JSMONode):
Delete Rule: Nullify
Prev (JSMONode):
Delete Rule: Nullify
Thing (subentity of JSMONode):
You can replace this with your subentity, including any attributes you might want to add. I've chosen to add a "name" attribute (a string) as a demonstration.
Implementation
#import "_JSMOLLContainer.h"
//_JSMOLLContainer is the machine generated code from mogenerator. It has only
//the accessors and the KVO notifications within.
@interface JSMOLLContainer : _JSMOLLContainer
{
//we cache first and last values because the fetch takes a while
@private
JSMONode *_cachedFirst;
JSMONode *_cachedLast;
}
- (void)addNodesObjectAtFront:(JSMONode*)value_;
- (void)addNodesObjectAtEnd:(JSMONode*)value_;
- (void) moveNode: (JSMONode *)moveMe
before: (JSMONode *)beforeMe;
- (void) moveNode: (JSMONode *)moveMe
after: (JSMONode *)afterMe;
- (JSMONode *)first;
- (JSMONode *)last;
- (NSArray *)nodesArray;
- (NSEnumerator *)nodesEnumerator;
- (NSEnumerator *)reverseNodesEnumerator;
@end
Adding a node
- (void)addNodesObject: (JSMONode*)value_
{
//add node object at the END of the chain.
[super addNodesObject: value_];
JSMONode *last = [self last];
JSMONode *newNext = [last next]; // should be null
[value_ setPrev: last];
[value_ setNext: newNext];
[self set_cachedLast: value_];
if (IsEmpty(last))
[self set_cachedFirst:value_];
}
There is a similar method for putting a node object at the head of the chain.
- (void)removeNodesObject: (JSMONode*)value_
{
[super removeNodesObject: value_];
//set next and prev as needed on bordering objs
if (!IsEmpty([value_ prev]))
[[value_ prev] setNext: [value_ next]];
else
[[value_ next] setPrev: [value_ prev]];
}
Reordering
- (void) moveNode: (JSMONode *)moveMe
before: (JSMONode *)beforeMe
{
[self removeNodesObject: moveMe];
JSMONode *myNewPrev = [beforeMe prev];
if(!IsEmpty(myNewPrev))
{
//if we are not moving moveMe to the front of the chain, this works
//otherwise there IS NO myNewPrev and we have to use the method below.
[myNewPrev setNext: moveMe];
[moveMe setNext: beforeMe];
}
else
{
[moveMe setPrev: myNewPrev];
[beforeMe setPrev: moveMe];
}
}
The rest of the implementation deals with building up simple enumerators and also with fetching the first and last nodes (where prev is Null or next is Null respectively) from the Managed Object Context when it turns out we haven't cached it appropriately. Eventually, the fetch requests will go away. Right now, they'll only likely be needed when we've done some reordering as that code doesn't update the cached first and last values as it should. To make sure that we're really returning the first node, we simply check that the _cachedFirst node's prev value is Null (actually [NSNull null]); that's what IsEmpty does here.
- (NSArray *)_nodesFilteredUsingPredicate: (NSPredicate *)predicate
{
if(0 == [[self nodesSet] count]) return [NSArray array];
NSEntityDescription * entity = [NSEntityDescription entityForName: @"JSMONode"
inManagedObjectContext: [self managedObjectContext]];
NSFetchRequest * fetch = [[NSFetchRequest alloc] init];
[fetch setEntity: entity];
[fetch setPredicate: predicate];
NSManagedObjectContext *context = [self managedObjectContext];
NSArray * results = [context executeFetchRequest: fetch error: nil];
[fetch release];
return results;
}
- (JSMONode *)first
{
if(0 == [[self nodesSet] count]) return nil;
//If we haven't yet cachedFirst we need to find it.
//IF cachedFirst previous value is not null, cachedFirst is no longer actually first.
if (!IsEmpty(_cachedFirst) && IsEmpty([_cachedFirst prev]))
return _cachedFirst;
JSMONode *first;
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"container == %@ AND prev == %@", self, [NSNull null]];
NSArray *results = [self _nodesFilteredUsingPredicate: predicate];
if (0 == [results count])
{
[self set_cachedFirst: nil];
return nil;
}
NSAssert(([results count] == 1), @"There should be one or fewer firstThing(s)");
first = [results objectAtIndex: 0];
[self set_cachedFirst: first];
return first;
}
- (JSMONode *)last
{
if(0 == [[self nodesSet] count]) return nil;
if (!IsEmpty(_cachedLast) && IsEmpty([_cachedLast next]))
return _cachedLast;
JSMONode *last;
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"container == %@ AND next == %@", self, [NSNull null]];
NSArray *results = [self _nodesFilteredUsingPredicate: predicate];
if (0 == [results count])
{
[self set_cachedLast: nil];
return nil;
}
NSAssert(([results count] == 1), @"There should be one or fewer lastThing(s)");
last = [results objectAtIndex: 0];
[self set_cachedLast: last];
return last;
}
Meanwhile, back at the Hall of Justice: usage
So how do we use it? We simply insert a JSMOLLContainer subclass (in the case of the diagram above, it's a ThingContainer) into the managed object context like so:
container = [NSEntityDescription insertNewObjectForEntityForName:@"ThingContainer"
inManagedObjectContext:moc];
thingZero = [[NSEntityDescription insertNewObjectForEntityForName:@"Thing"
inManagedObjectContext:moc]
//perhaps we want to set an attribute
[thingZero setName:@"Thing Zero Here"];
[container addNodesObject:thingZero];
thingOne = [[NSEntityDescription insertNewObjectForEntityForName:@"Thing"
inManagedObjectContext:moc]
//perhaps we want to set an attribute
[thingOne setName:@"Thing One Here"];
[container addNodesObject: thingOne];
Now we've a container with two things in this order: thingZero -> thingOne.
JSMONode *currentThing;
NSEnumerator *nodesEnumerator = [container nodesEnumerator];
while (currentThing = [nodesEnumerator nextObject])
{
//do something
}
2. We can ask the container for the first object and keep asking for next
JSMONode *currentThing = [container first];
while (!IsEmpty(currentThing))
{
//do something
currentThing = [currentThing next];
}
At this point, I should probably show you the IsEmpty function. This is mostly stolen from Wil Shipley's blog . I just added the test for [NSNull null], which is the "empty" object for insertion into cocoa collections (like a core data graph, for instance).
//Thanks Wil
static inline BOOL IsEmpty(id thing) {
return thing == nil
|| ([thing isEqual:[NSNull null]]) //JS addition for coredata
|| ([thing respondsToSelector:@selector(length)]
&& [(NSData *)thing length] == 0)
|| ([thing respondsToSelector:@selector(count)]
&& [(NSArray *)thing count] == 0);
}
[container addNodesObject: node7]2. Then place it after node3
[container moveNode: node7
after: node3;]
It's not just for icons. :)
There is some talk of resolution-independence these days, and for good reason. Them pixels is getting smaller.
Those of us who like to cheat and use 2D impostors for 3D objects get to be a little more careful these days as well. Luckily, thanks largely to information I got at the Big Nerd Ranch OpenGL class, one can easily make the 3D equivalent of vector graphics in something like (say) maya or wings3D and wrap them up in a display list in openGL for speed of rendering.
This makes one's visualization happily zoomable and resolution-independent without much of a performance hit. Just load your model in your initialization and wrap the drawing in a display list.
My pet project at a normal zoom level:

and zoomed waaaaay in:

Can you tell which part of the graph is still a stretched image + simple texture?
It's not just for icons. :)
There is some talk of resolution-independence these days, and for good reason. Them pixels is getting smaller.
Those of us who like to cheat and use 2D impostors for 3D objects get to be a little more careful these days as well. Luckily, thanks largely to information I got at the Big Nerd Ranch OpenGL class, one can easily make the 3D equivalent of vector graphics in something like (say) maya or wings3D and wrap them up in a display list in openGL for speed of rendering.
This makes one's visualization happily zoomable and resolution-independent without much of a performance hit. Just load your model in your initialization and wrap the drawing in a display list.
My pet project at a normal zoom level:

and zoomed waaaaay in:

Can you tell which part of the graph is still a stretched image + simple texture?
As apparently have a number of other developers/bloggers, I've been reading and thinking about the implicit HIG since John Gruber spoke on the topic at the C4 conference. It seems the consensus thus far is:
1. Apple isn't likely to help.
Unfortunately, this raises the barrier to entry if you don't want Gruber to call you up with the words "2001 called. They want their UI back."
2. It would be nice if there were one place to go for example code for these kinds of things.
I'm relatively new on the Mac Geek scene and, given the personalities involved and some of the other fantastic resources already out there (links here to the various mailing list aggregations), am surprised that there isn't a community code repository for Cocoa programmers to post / retrieve solutions to common development problems like the implicit HIG.
So! Let's do something about this. With the above in mind, I've started keeping a list of links on del.icio.us where I've found interesting and/or slick cocoa widgets and frameworks using the tag newHIG. I'm hoping that other developers out there who have solved a common Cocoa development problem like this will be willing to share links and code. Once the number of newHIG tags I have hits a critical mass, it's time to set up a wiki / repository for this stuff. I'll do it. If anybody has any ideas or would like to donate some code, some time, or some beer, shout!
So far, I've tried only one of the solutions I've found and it seems "Batteries included" to me. The new ilifecontrols framework from Sean Patrick O’Brien simply rocks. All I had to do was embed the framework in my little test app, drag and drop the headers into IB, and set the appropriate classes for widgets and windows. Five minutes of work and my app is skinned iLife style. Odd how the visual change already makes it seem more fun to play around with this little app. I must be a fanboy now, huh?
You've seen the visualization I'm working on just a few times now.
Before:

After:

No, it's not a pretty app to begin with; it's a test app. (By the way, the fps numbers are meaningless after the view has drawn. The framework did not slow anything down.) Hopefully though, this gives us an idea of what one can do in a hurry with the framework. Rock on Sean.
In the next few days, I'll start dropping others' widgets into my work to see how they look together. There are a bunch of nice widgets and little "here's how you do this or that" from Matt Gemmell, including an i-tunes 5/6 window style, that look awesome as well. There is a nice little "Gear menu" from RogueSheep's Bleatings (baaaaaah). Considering the above, it looks like our implicit HIG toolkit is well underway.
#!/usr/bin/env python
#for the developers who write code so I don't have to
for i in xrange(1000000):
print "thanks"
No, NSA, not that C4. I'm talking about the conference I went to this weekend full of mac programmers. Sheesh.
First, wowie zowie many thinks to Rentzsch. Not only is this guy wicked-smaht, he can put on a conference with 100 attendees without breaking a sweat. Well, at least it looked easy.
I met a bunch of awesome people, many of them bloggers I read religiously. Apparently, John Gruber shares my inability to leave IKEA without melting a credit card, which somehow makes me feel better about myself. Daniel Jalkut who was awarded a music degree (wee!) last year, and I had a great time talking about how surviving a jury in music school makes it impossible to feel all that nervous for any job interview or presentation ever again. Duncan Davidson shoots some fine pictures. Thanks for shooting little ol' me while you were at it, Duncan!

I met a bunch of other developers and everybody left me thinking "what a helluva nice guy/gal."
Some of the topics have me thinking, so I'm sure I'll be spilling a few pixels on them. I'm newly interested in ways of easing concurrency in app development, thanks of course to Steve Dekorte for the brain food. My new old friend from the Big Nerd Ranch Aaron Hillegass has ambushed my brain into thinking with big thoughts. ...again.
Oh! And this is my first post in MarsEdit, which is hacked by two presenters from the conference Brent Simmons of NetNewsWire fame and (for a time) Gus Mueller who founded Flying Meat Software. This is a great app. Where's my credit card?
No, NSA, not that C4. I'm talking about the conference I went to this weekend full of mac programmers. Sheesh.
First, wowie zowie many thinks to Rentzsch. Not only is this guy wicked-smaht, he can put on a conference with 100 attendees without breaking a sweat. Well, at least it looked easy.
I met a bunch of awesome people, many of them bloggers I read religiously. Apparently, John Gruber shares my inability to leave IKEA without melting a credit card, which somehow makes me feel better about myself. Daniel Jalkut who was awarded a music degree (wee!) last year, and I had a great time talking about how surviving a jury in music school makes it impossible to feel all that nervous for any job interview or presentation ever again. Duncan Davidson shoots some fine pictures. Thanks for shooting little ol' me while you were at it, Duncan!

I met a bunch of other developers and everybody left me thinking "what a helluva nice guy/gal."
Some of the topics have me thinking, so I'm sure I'll be spilling a few pixels on them. I'm newly interested in ways of easing concurrency in app development, thanks of course to Steve Dekorte for the brain food. My new old friend from the Big Nerd Ranch Aaron Hillegass has ambushed my brain into thinking with big thoughts. ...again.
Oh! And this is my first post in MarsEdit, which is hacked by two presenters from the conference Brent Simmons of NetNewsWire fame and (for a time) Gus Mueller who founded Flying Meat Software. This is a great app. Where's my credit card?
I don't know how he finds the time, but the python / mac community owes Bob several rounds of applause. Let me be the first to stand (soon as I get this here foot outta mah mouth). I'm also wowed at how he managed to find my blog and comment before I got round to sending a question to the mailing lists. And ... well you know... he kicked my butt and I deserved it.
PyObjC is cool. Really. No Kidding. What I'm about to say, though, is making me think that my friend Patrick www.patrickkidd.com has a better idea in using pyQT for gui work...
When I'm using an external library, say perhaps one that sends information over a network, I try to be careful to keep from passing the lower-level code anything that could muck things up. Well... it turns out that it's nearly impossible to keep python's object types from being converted to pyobjc objects. This isn't too surprising, given that many ints, strings, etc. have to make their way into objc arguments, though I was making the improper assumption that python objects were converted through some kind of transparent proxy only when crossing that boundary to the objc runtime. I find out now that python objects are sometimes just converted. That being the case, I didn't expect those converted objects to act like anything but your typical python object from the point of view of python (ala duck typing... sorta). Again. WRONG. Somehow, for instance, the integer type sometimes turns into an OC_PythonInt (which has a method intValue() to get to the python int it represents, FYI) and it doesn't have the same methods as a python int. Bummer. It's kinda hard to send that over the network.
What to do?
"assert" statements, I guess.
From Ronald Oussoren to me (+ pyobjcdev and pythonmac-sig):
I haven't look at your example just yet, will do that later on. I'm
happy you took the time to create a small program that demonstrates
the problem you're seeing, that makes is so much easier to search the
problem.
> What did I learn from this:
>
> 1. Try to use cocoa objects instead of python objects in code that
> uses bindings; pay special attention to collections (lists, etc.).
> At least this *seems* safer because a python object being
> referenced from cocoa is probably more likely to cause problems
> than a cocoa object controlled from python. (?) Then again, it's
> pretty stupid to generalize from one experience...
Using Cocoa datastructures instead of python ones when KVO is
involved is always a good idea. Python doesn't have enough hooks to
allow reliable interception __setitem__. We could (but don't)
get 95% of the way by using the same class-swiffling technique as
used by the ObjC runtime, but could never go all the way.
> 2. GDB is very useful for debugging pyobjc.
> 3. There are a lot of nice pyobjc debugging features, but you have
> to turn them on (See my example project referenced above).
>
> Things I want to know:
> 1. Is there a way, when one gets the memory address of a pyobjc
> proxy object (Like OC_PythonArray) from gdb to figure out which
> python object it's proxying?
The PyObject* is stored as the instance variable 'value' on the proxy
object.
> 2. Is there a way to get the python object from a memory address?
You mean like "(PyObject*)0xDEADBEEF"?
> 3. Is there a reliable way to get (in gdb or pdb) the "Python List
> Object at 0x03423blahblahblah information from the python object
> being proxied
> 4. Can I compile pyobjc with debugging symbols? GDB without them
> hurts my brain.
PyObjC is compiled with debugging symbols by default. Py2app strips
debugging symbols, unless you build with the -A flag.
Ronald
Well, I think I've figured out the *WTF* part of my little NSZombie problem.
It really boils down to this:
Bind an NSPopubButton (contents) to a Python List object that is an attribute of a python class placed in an NSArray controlled by an NSArrayController and the OC_PythonArray object from PyObjC corresponding to the Python List gets freed too early. (There's a mouthful... Confusing enough?) Wrap that Python List object in an NSArray.alloc().initWithArray_copyItems_([u'the', u'list'], objc.NO) and (I guess) the NSArray...init... adds one more -retain than does OC_PythonArray and the binding doesn't break.
I'm always nervous to call things like this a bug because maybe I'm not *supposed* to be able to do what I'm trying to do in the way that I'm trying to do it, but this one looks like a bug to me. (With the caveat that I am currently learning pyobjc/cocoa and don't always know the *right* way to do things...)
I've been learning cocoa bindings (I like this voodoo) and have hit a brick wall. I've been asking pythonmac-sig if they've any ideas and they pointed me in the direction of NSZombieEnabled, but i haven't been able to get very far.
I have an NSArray controller with a python object subclassing NSObject. I've put a + button on the gui to add the python object like this:::
def addTriggerModel_(self, sender): # BUTTON
self.setCanAddTriggerModel_(False)
newTriggerModel = scNSDrumTriggerModel.alloc().init()
newTriggerModel.resetWithNodeBusAndInput(node = self.nextNode, bus = self.nextBus, \
input = self.nextInput, sendInfo = self.sendingTriggersToClient)
self.nextNode += 100
self.nextBus += 10
self.nextInput += 1
NSLog("Adding newTriggerModel")
self.triggerArrayController.addObject_(newTriggerModel)
NSLog("Added newTriggerModel")
self.setCanAddTriggerModel_(True)
This works just peachy the first time I do it, but fails on a second addition of the new scNSDrumTriggerModel object. At first I was getting Signal 5/6 errors, then I did:
from PyObjCTools import Signals
Signals.dumpStackOnFatalSignal()
from PyObjCTools import Debugging
Debugging.installVerboseExceptionHandler()
This told me:
NSGenericException - *** Selector 'retain' sent to dealloced instance 0x1147cb0 of class OC_PythonArray
So I turned on NSZombieEnabled and tried running in gdb with a breakpoint on -[_NSZombie retain], but I don't really know what I'm looking for. I'm looking at a lot of hex memory addresses with very little human-readable stuff in there.
I tried setting a breakpoint at (+[OC_PythonArray newWithPythonObject:]) to see perhaps what proxy objects are being instantiated, but I'm clearly in over my depth. The variable corresponding to the pythonObject in OC_PythonArray newWithPythonObject: up there ^ has address 0x0 (Is that a null pointer?) in gdb each time I hit that breakpoint.
When I run malloc_history on the memory address that the NSZombie has, I get this:
For those of us who live with pyobjc's xcode templates, there is an annoying little bug when creating a new project, at least on my machines. There are no executables created when you make a new python/cocoa project of any kind which causes xCode to disable "build and run" button. Sure, we can build in Xcode, navigate to the build directory, and run the application, but that's what the "build and run" button is for! How do we fix this? Add the project executable to the Executables group in xcode!
Like this:
In your xCode session, there is a pane down the left side that shows the contents of the project. In that pane, ctrl-click the "Executables" group, select "Add," then "New Custom Executable". Put the name of your application appended by .app in the "Executable Name field." (i.e. if your app is called "Trigger," put "Trigger.app" in this field). Use the "choose" button to navigate to your application's build directory on your hard disk and choose your application bundle. (If it doesn't exist, try building your application with the build button once first.) Now in the "Project" menu of xCode, "Set Active Executable" to your application name. This should enable the "build and run" button.
UPDATE: Once you've done that ^, you can then change the path to the executable to $BUILT_PRODUCTS_DIR/YourApplicationNameHere.app so that your project doesn't have any hard-coded paths.
If you thought it was cool to drag connections between GUI widgets and actions in a controller object when you first started using Cocoa, you’ll flip when you see this in action. Bindings really are just what they say they are: tell Interface Builder to connect this GUI widget to this part of the data model and, presto-chango, no significant glue code.
I have a little PyObjC application that is coming along quickly thanks to cocoa bindings. My first PyObjC app took me weeks to figure out (partly due to the lack of cocoa object knowledge) and is several hundred lines of code. This app was started a few days ago, is considerably more complex, is nearly finished, and is barely any code.
Info regarding dead-ends I hit and how I’ve gotten this to work for me will be posted here soon. I may post a pseudo-wiki sort of thing that clarifies some of the things I found confusing while trying to use this in a real application. For now, here are links to the tutorials I’ve found most helpful:
This one is dirt simple, but clarifies a lot of the voodoo involved:
http://bob.pythonmac.org/archives/2004/04/04/pyobjc-nstableview-cocoa-bindings-tutorial/
This one is down right now, but was up yesterday. I assume they’re having temporary problems. This is a bummer as this tutorial shows one how to get and set values directly in the data model without a controller object.
OK. It's a little late for a resolution for the New Year.
I am finally going to learn cocoa bindings. I have the right app for it. I've a couple of books with info. I've got tutorials all over my dev documentation folder.
Come along. I'll chronicle the ride here.
It's going to happen. I can feel it. This time, PyObjC. Next time, objc