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.
I have always loved driving fast cars. I drove a blue '71 Mustang Mach 1 through most of college and a pretty red '93 Ford Escort wagon got me to and from teaching gigs and concerts. I have motor oil in my blood. I needed two cars to satisfy the "carry my stuff around without breaking the bank... or the car" and the "get me someplace fast and in style" requirements.
Fast forward (ahem... cough... already?) years and I've lately had the pleasure of driving a Silver 2003 BMW M3. This is a machine that laps the Nüburgring in 8:22, gets from "naught to sixty" in 4.6 seconds. You wouldn't expect it, but the back seat is actually usable for two non-American-sized people and they both fold down to reveal almost station-wagon-like cargo space, passing through to the trunk. No kidding. I brought a 6' wire shelf home the other night. In real - world driving, it gets just barely less than 30 miles / gallon on the highway, which is waaay better than the latest M3, which, while it has 414 HP and obviously flies, gets about 20 MPG on the highway. That's worse than my E46 city numbers of 23 MPG or so, even though I drive with occasional (ahem) spirit. The engine redlines at 8000 RPM and the peak horsepower and torque both live very near that (around 7000), which means you have to grow accustomed to keeping the car above about 3000 RPM if you want any kind of torque. Oddly, that's a good thing. When you want to drive conservatively, it's easy to do so as the car has relatively little low-end power. If you want to throw it around a bit, keep the revs up. Simple. Very girlfriend friendly driving down low, grins up high.
(Over the break for pics and more)
Continue reading "The E46 (penultimate) BMW M3 just might be the perfect car for you, too." »
./contrib/gdata http://gdata-objectivec-client.googlecode.com/svn/trunk/ svn ./contrib/hgsubversion https://jonmarimba@bitbucket.org/durin42/hgsubversion hg ./contrib/mogenerator http://github.com/rentzsch/mogenerator.git gitIf you then run hg externals in the project. You'll see that the subversion, mercurial, and git repositories are checked-out much like subversion handles svn-externals. Note: Be aware that the hgexternals python scrip will actually run svn or git binaries in a subprocess, so svn and git will have to be installed for this to work properly. If you wanted to hold the external project to a given revision, which is usually a good idea, you just append the external version control system's usual revision flag. For example, if you wanted the Gdata Objective-C code pegged at revision 533, the first line in the .hgexternals file would look like this.
./contrib/gdata http://gdata-objectivec-client.googlecode.com/svn/trunk/ svn --revision 533In order to get the hg externals command to act a little more like subversion, you'll want to add the hgexternals.extstatushook to your .hgrc to get the status of the external projects when you type hg stat. To do this, you add the below to your .hgrc as suggested in the hgexternals installation instructions.
[hooks] post-status = python:hgexternals.extstatushookWith a couple of extra plugins, mercurial can pull from subversion and git, in addition to the usual mercurial repositories. If you prefer to let hgexternals shell out to git or svn (thus requiring you to use git and svn tools within the subprojects themselves), you can stop here. If you would like to use mercurial commands within all subprojects, regardless of whether they are Mercurial repositories, read on. Either way, we can now pull subprojects from anywhere and we can also use hg stat as we normally would in the working copy of the project and each subproject status will also be shown.
./contrib/mogenerator git://github.com/rentzsch/mogenerator.git hgWhat we're doing here is telling the mercurial hgexternals extension to pull from the git repository using mercurial itself. When hg-git is installed, this works with a git-based subproject just as it would with a Mercurial-based project. Make this edit to .hgexternals, remove the mogenerator directory that was checked-out with git, and rerun hg external command. Mercurial will now pull mogenerator from github and we can now work within the mogenerator subproject using regular hg commands.
./contrib/gdata svn+http://gdata-objectivec-client.googlecode.com/svn/trunk/ hg -r 533 ./contrib/hgsubversion https://jonmarimba@bitbucket.org/durin42/hgsubversion hg ./contrib/mogenerator git://github.com/rentzsch/mogenerator.git hgIf we remove the gdata subdirectory tha wall pulled using Subversion from contrib and rerun hg external so that Mercurial will now pull it using its hgsubversion plugin, we now have three projects in our contrib directory that Mercurial itself pulled from git, svn, and hg repositories. With Mercurial plus the addition of a few plugins, we now have an über distributed version control system that makes it much simpler to work with and to update external code from many different sources.
I'm performing a 5 minute "lightning" talk at the iPad/iPhone DevCon tomorrow and I'm not planning to use slides. I don't want to burn half of the 5 minutes getting keynote working, but hopefully attendees would like to have some of the links and code I'll reference. So! Here are my notes, links, and other such fodder. There is a lot more than 5 minutes worth of material here as I've been making notes about my habits for the last week or so; this is all of them. I plan to talk until I run out of air or out of time...
A foolish consistency is the hobgoblin of little minds, adored by little statesmen and philosophers and divines.-- R.W. Emerson (Essays. First Series. Self-Reliance.)
[self setFloozle:[floozaWhat whatWithString:[NSString stringWithFormat:@"jeeminy %@ Christmas, %@", frickin, virginiaName]];Where you gonna put a breakpoint in that crap?
static inline BOOL IsEmpty(id thing) {
return thing == nil
|| ([thing isEqual:[NSNull null]]) //My addition for things like coredata
|| ([thing respondsToSelector:@selector(length)]
&& [(NSData *)thing length] == 0)
|| ([thing respondsToSelector:@selector(count)]
&& [(NSArray *)thing count] == 0);
}
//Modified from Aaron hillegass's code #define LogMethod() NSLog(@"-[%@ %s]", self, _cmd)
return @"";
return [NSArray array];
return [NSDictionary dictionary];
I often use Shipley's IsEmpty() to find out if some object is empty, rather than comparing its pointer to nil.
That way, nil messaging returning nil doesn't bite you in the ass when you're expecting something that is not an NSObject and you don't get that ugly (null) string in your UI. I hate that.
#define LogMethod() SBLog(@"-[%@ %s]", self, _cmd) #define WarnMethod() SBWarn(@"-[%@ %s]", self, _cmd) #define SBWarn NSLog #ifdef DEBUG #define SBLog NSLog #else #define SBLog #endif
+[NSObject cancelPreviousPerformRequestsWithTarget:] +[NSObject cancelPreviousPerformRequestsWithTarget:selector:object:]Incolsolata, because menlo makes left square brackets look indented.
I've been a command-line user of subversion (svn) for some time and have long enjoyed these little bits of bash foo.
rm -rfv `find . -name *\.svn`The find . -name *\.svn finds all of the .svn folders that svn uses to track the repository and with the help of those handy back-tics, rm -rv does the recursive removal of svn tracking directories.
svn add `svn stat |grep \? |awk '{print $2}'`
Grep finds every svn stat output line that includes a question mark (meaning the item is not currently under version control) and pipes it through awk '{print $2}', which shows only the text (the filename) from the second column of the svn stat output. The back-tics and svn add finish the magic.
svn revert --recursive && rm -rf `svn stat |grep \? |awk '{print $2}'`
As with any other nerd foo, make sure that you really want to do what you're asking for when you use these commands. These make my life with svn just a little easier.
Continue reading "Using Shark and custom DTrace probes to debug Nagios on Mac OS X" »
Some software is, in my experience, not quite WYSIWYG when it comes to the final printed output so I have gotten into the habit of viewing a PDF in preview before I actually send a document to my printer. This has saved many trees.
Apple makes it fairly easy to do this. Just command-P and click on the weird-looking PDF button to get the drop down menu to select "Open PDF In Preview."

I would like to see most every print job automatically open in preview without needing to take my hands off of the keyboard. One can make this happen with a little bit of effort, some help from Folder Actions, and a little piece of free third-party software. Here's how I do it.
First, download and install Cups PDF per the instructions on its home page. This will create a printer and driver that dumps a pdf file into ~/Desktop/cups-pdf/. Set your default printer to CUPS-PDF.
Next, you will setup a folder action on the ~/Desktop/cups-pdf/ to open each file that gets dumped in there in preview. I keep my folder actions scripts in ~/Desktop/Folder\ Actions/, so I put the following applescript (derived from a similar script that ships with Leopard) there.
(* add - new item alertThis Folder Action handler is triggered whenever items are added to the attached folder.
The script will display an alert containing the number of items added and offering the user
the option to reveal the added items in Finder.Copyright © 2002–2007 Apple Inc.
You may incorporate this Apple sample code into your program(s) without
restriction. This Apple sample code has been provided "AS IS" and the
responsibility for its operation is yours. You are not permitted to
redistribute this Apple sample code as "Apple sample code" after having
made changes. If you're going to redistribute the code, we require
that you make it clear that the code was descended from Apple sample
code, but that you've made changes.
*)property dialog_timeout : 5 -- set the amount of time before dialogs auto-answer.
on adding folder items to this_folder after receiving added_items
try
tell application "Finder"
--get the name of the folder
set the folder_name to the name of this_folder
end tell
-- find out how many new items have been placed in the folder
set the item_count to the number of items in the added_items
--create the alert string
set alert_message to ("Folder Actions Alert:" & return & return) as Unicode text
if the item_count is greater than 1 then
set alert_message to alert_message & (the item_count as text) & " new items have "
else
set alert_message to alert_message & "One new item has "
end if
set alert_message to alert_message & "been placed in folder " & «data utxt201C» & the folder_name & «data utxt201D» & "."
set the alert_message to (the alert_message & return & return & "Would you like to view the added items?")
display dialog the alert_message buttons {"Yes", "No"} default button 1 with icon 1 giving up after dialog_timeout
set the user_choice to the button returned of the result
if user_choice is "Yes" then
tell application "Preview"
--fire it up
activate
--open the items
open the added_items
end tell
end if
end try
end adding folder items to
Ctrl (or right) click the cups-pdf folder, Enable Folder Actions, then Configure Folder Actions via the contextual menues seen below.
When you select "Configure Folder Actions" you'll set the script that runs every time a new file is added to that folder to the one above.

Now every time you hit command - P to print in a application, you'll hit enter to print to your default CUPS-PDF printer, which will write a PDF to the cups-pdf folder on your desktop. The folder action will then pop-up this window (for five seconds).

All you have to do is hit enter.
So! Every time I print I hit command - P, enter, wait a sec, enter. Instant preview.
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
--- ./tree-1.5.1.1/tree.c +++ ./tree-1.5.1.1JS/tree.c @@ -17,7 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include+//#include #include #include #include @@ -182,7 +182,7 @@ #ifdef CYGWIN extern int MB_CUR_MAX; #else -extern size_t MB_CUR_MAX; +extern int __mb_cur_max; #endif int main(int argc, char **argv)
As you know from my last post, I have lots and lots of calendars. It used to be much much worse. Sharing calendars between my three Macs before Leopard server was a pain in the ass. Sure, you could put a calendar on a regular DAV server and subscribe to that calendar on your other machines, but you could not write to that calendar from any machine other than that which created it initially. All subscribed machines were read-only. What does this mean to me? You know about my 14 calendars, right? Triple it. That's right. I used to have 42 calendars on each of my 3 machines. 14 calendars to which I could write and 28 to which I was subscribed so I could read any appointments from my other two machines. What a mess.
Leopard calendar server has brought me back to a debatably sane 14 calendars. But how does one migrate all of those calendars spread across multiple machines onto the shiny new calDAV server? Like so:
1. Subscribe to your shiny new calDAV server (under the Accounts tab) in iCal preferences.
2. Create a new calendar on your calDAV server through iCal (File Menu -> new Calendar -> your server) for each of your 14 calendars.
3. Create a *.ics file from your old calendar by:
a. Selecting the calendars you want to export by using the export functionality in iCal (File Menu -> Export) on each of your machines or...
b. Mounting your DAV server and downloading all of 'em at once (assuming you're keeping your calendars on a DAV server now, which I was, this method is a lot easier for obvious reasons)...
4. Select the calendar from the source list (one residing on your new calDAV server) and import the *.ics files you wish to store in the new calendar (File Menu -> import).
Your appointments are now on your calDAV server and will show up on all of your subscribed machines fully editable from all.
For those wishing to use Mozilla Sunbird with the calDAV server that comes with Leopard, it's not setup in Sunbird in (quite) the same way as it is in iCal.
Let's say you have the calDAV Account URL working in ical (which will subscribe you to all of your calendars) and its URL is
https://somedomain.com:8443/principals/users/username/
In Sunbird, you'll have to subscribe to the your calendars individually. Every calDAV user I've setup so far in Leopard server has been given a default calendar called simply "calendar", which you can subscribed to from Sunbird at
https://somedomain.com:8443/calendars/users/username/calendar/Easy enough. However, if you add a new calendar in iCal, it will create a calendar named with a UUID. In order to figure out what that UUID is, it's easiest to just mount the calDAV in the finder (Command - K) at
https://somedomain.com:8443/calendars/users/username/You'll see several directories in there: calendar, dropbox, oubox, notifications, etc. The one that looks like a UUID (for example, 037D3206-9C1D-4C6C-8E54-B3E3CAF90ABF) is the directory you'll want to subscribe to in Sunbird. In the example above, you would subscribe to
https://somedomain.com:8443/calendars/users/username/037D3206-9C1D-4C6C-8E54-B3E3CAF90ABF/
Having been a MarsEdit user since before its Red-Sweater-ization, of course I just bought me an upgrade for that there brand spankin' new MarsEdit 2.0. It's awesome, as expected.
Daniel Rocks.
Carry on, punkass.
The native iPhone hack I put together for the C4[1] conference is in the process of a "wow, I hope somebody can read this code someday" cleanup. I will release the source in the coming days. Stay tuned for a download link.
In the meantime, here are a couple of teasers.
Screenshots:


And here is some code for playing a sound file from your native iPhone Application's bundle (with the class-dump'd headers you'll need, too). The iPhone, as was mentioned by everyone with a native hack at C4, has some really intelligent API. Hats off, ladies and gentlemen, to the iPhone team. They must be having a grand ol' time internally playing with this thing.
//
// PongAudio.h
//
// Created by Jonathan Saggau on 2007-08-12.
// Copyright (c) Jonathan Saggau All rights reserved.
//#import <Foundation/Foundation.h>
@class AVItem;
@class AVController;
@class AVQueue;@interface PongAudio : NSObject
{
AVItem *bounce;
AVItem *loser;
AVQueue *q;
AVController *controller;
}-(void)playBounce;
-(void)playLoser;-(void)stop;
@end//
// PongAudio.m
//
// Created by Jonathan Saggau on 2007-08-12.
// Copyright (c) 2007 __MyCompanyName__. All rights reserved.
//#import "PongAudio.h"
#import "AVItem.h"
#import "AVController.h"
#import "AVQueue.h"//From Aaron hillegass
#define LogMethod() NSLog(@"-[%@ %s]", self, _cmd)@interface PongAudio (PrivateAPI)
-(void)play:(AVItem *)item;
@end@implementation PongAudio
- (id)init
{
self = [super init];
if (nil!= self)
{
NSError *err;
NSString *path = [[NSBundle mainBundle] pathForResource:@"PongBounce" ofType:@"wav" inDirectory:@"/"];
bounce = [[AVItem alloc] initWithPath:path error:&err];
if (nil != err)
{
NSLog(@"err! = %@ \n item = [[AVItem alloc] initWithPath:path error:&err];", err);
exit(1);
}
path = [[NSBundle mainBundle] pathForResource:@"PongLose" ofType:@"wav" inDirectory:@"/"];
loser = [[AVItem alloc] initWithPath:path error:&err];
if (nil != err)
{
NSLog(@"err! = %@ \n item = [[AVItem alloc] initWithPath:path error:&err];", err);
exit(1);
}
controller = [[AVController alloc] init];
[controller setDelegate:self];
q = [[AVQueue alloc] init];
[q appendItem:bounce error:&err];
if (nil != err)
{
NSLog(@"err! = %@ \n [q appendItem:item error:&err];", err);
exit(1);
}
[q appendItem:loser error:&err];
if (nil != err)
{
NSLog(@"err! = %@ \n [q appendItem:item error:&err];", err);
exit(1);
}
}
return self;
}- (void)dealloc
{
[bounce release]; bounce = nil;
[loser release]; loser = nil;
[q release]; q = nil;
[controller release]; controller = nil;
[super dealloc];
}-(void)playBounce
{
[self play:bounce];
}-(void)playLoser
{
[self play:loser];
}-(void)play:(AVItem *)item;
{
[controller setCurrentItem:item];
//play NOW
[controller setCurrentTime:(double)0.0];
//should probably check this eventually, too.
//- (BOOL)isCurrentItemReady;
NSError *err;
[controller play:&err];
if(nil != err)
{
NSLog(@"err! = %@ [controller play:&err];", err);
exit(1);
}
}-(void)stop;
{
[controller pause];
}- (void)queueItemWasAdded:(id)fp8
{
LogMethod();
}
@end
//AVController.h
/*
* Generated by class-dump 3.1.1.
*
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard.
*/
#import <Foundation/Foundation.h>
#import "CDStructures.h"@interface AVController : NSObject
{
struct AVControllerPrivate *_priv;
}+ (void)setEnableNetworkMode:(BOOL)fp8;
+ (id)avController;
+ (id)avControllerWithQueue:(id)fp8 error:(id *)fp12;
- (id)initWithError:(id *)fp8;
- (void)setAVItemClass:(Class)fp8;
- (id)init;
- (void)dealloc;
- (struct AVControllerPrivate *)privateStorage;
- (BOOL)isNewImageAvailableForTime:(const CDAnonymousStruct1 *)fp8 willNeverBeAvailable:(char *)fp12;
- (long)copyImageForTime:(struct __CVBuffer **)fp8 time:(const CDAnonymousStruct1 *)fp12;
- (void)itemPropertyDidChangeNotification:(id)fp8;
- (void)scheduleQueueSpaceCheck;
- (void)checkQueueSpace;
- (void)queueItemWasAdded:(id)fp8;
- (void)queueItemWasAddedNotification:(id)fp8;
- (void)queueItemWillBeRemovedNotification:(id)fp8;
- (void)cancelPrepareForPlayback;
- (void)setQueue:(id)fp8;
- (id)queue;
- (void)feederRangeWasInserted:(id)fp8;
- (void)feederRangeWasRemoved:(id)fp8;
- (void)feederInvalidatedWithCurrentItemMoved:(id)fp8;
- (void)setQueueFeeder:(id)fp8 withIndex:(int)fp12;
- (void)setQueueFeeder:(id)fp8;
- (id)queueFeeder;
- (BOOL)setRepeatMode:(int)fp8;
- (int)repeatMode;
- (BOOL)havePlayedCurrentItem;
- (id)currentItem;
- (id)currentItemPriv;
- (void)setCurrentItem:(id)fp8 preservingRate:(BOOL)fp12;
- (void)setCurrentItem:(id)fp8;
- (void)makeCurrentItemReady;
- (BOOL)isCurrentItemReady;
- (void)continueAfterRepeatGap;
- (void)cancelContinueAfterRepeatGap;
- (void)doCancelContinueAfterRepeatGap;
- (void)doScheduleContinueAfterRepeatGap:(id)fp8;
- (BOOL)beginRepeatGap;
- (void)itemFailedPlaying;
- (void)itemFinishedPlaying:(id)fp8;
- (void)itemCompletedDecode;
- (BOOL)play:(id *)fp8;
- (void)pause;
- (void)dequeueFirstItem;
- (unsigned int)indexOfCurrentQueueFeederItem;
- (BOOL)setIndexOfCurrentQueueFeederItem:(unsigned int)fp8 error:(id *)fp12;
- (id)addNextFeederItemToQueue;
- (BOOL)playNextItem:(id *)fp8;
- (float)rate;
- (BOOL)shouldBeginPlayingItem:(id)fp8 error:(id *)fp12;
- (BOOL)setRate:(float)fp8 error:(id *)fp12;
- (BOOL)resumePlayback:(double)fp8 error:(id *)fp16;
- (id)errorWithDescription:(id)fp8 code:(long)fp12;
- (void)makeError:(id *)fp8 withDescription:(id)fp12 code:(long)fp16;
- (BOOL)beginInterruption:(id *)fp8;
- (BOOL)activate:(id *)fp8;
- (void)endInterruptionWithStatus:(id)fp8;
- (float)volume;
- (void)setVolume:(float)fp8;
- (double)currentTime;
- (void)setCurrentTime:(double)fp8;
- (BOOL)muted;
- (void)setMuted:(BOOL)fp8;
- (void)setEQPreset:(int)fp8;
- (int)eqPreset;
- (struct OpaqueFigVisualContext *)visualContext;
- (void)setVisualContext:(struct OpaqueFigVisualContext *)fp8;
- (id)outputQTESFilePath;
- (void)setOutputQTESFilePath:(id)fp8;
- (id)lkLayer;
- (void)setLayer:(id)fp8;
- (id)attributeForKey:(id)fp8;
- (BOOL)setAttribute:(id)fp8 forKey:(id)fp12 error:(id *)fp16;
- (struct _LKImageQueue *)lkImageQueue;
- (struct _LKImageQueue *)lkEnsureQueueForWidth:(unsigned int)fp8 Height:(unsigned int)fp12;
- (double)lkServerTime;
- (BOOL)okToNotifyFromThisThread;
- (void)fmpTimeJumped;
- (void)fmpRateDidChange;
- (void)rateDidChangeToRate:(float)fp8;
- (void)repeatModeHasChanged:(int)fp8;
- (void)currentItemWillChangeToItem:(id)fp8 oldItemCurrentTime:(double)fp12;
- (void)currentItemHasChanged:(id)fp8;
- (void)itemHasFinishedPlayingNotification:(id)fp8;
- (void)resynchronizeTiming;
- (id)delegate;
- (void)setDelegate:(id)fp8;
- (BOOL)setItemAttribute:(id)fp8 value:(id)fp12 forKey:(id)fp16 error:(id *)fp20;
- (id)itemAttribute:(id)fp8 forKey:(id)fp12;
- (id)initWithQueue:(id)fp8 error:(id *)fp12;
- (BOOL)isValid;
- (id)initWithQueue:(id)fp8 fmpType:(unsigned long)fp12 error:(id *)fp16;
- (void)applyAttributesFromItem:(id)fp8;
- (void)fmpRelease:(id)fp8;
- (void)failPlayback:(id)fp8 reason:(long)fp12 notifyClient:(unsigned char)fp16;
- (void)prepareForPlaybackReply:(long)fp8;
- (int)instantiateFMPRef:(struct opaqueFigMoviePlaybackRef **)fp8 forItem:(id)fp12;
- (void)maybeDumpPerformanceDictionary:(struct opaqueFigMoviePlaybackRef *)fp8;
- (void)removeFMPRefListeners:(struct opaqueFigMoviePlaybackRef *)fp8;
- (void)shutdownFMPRef:(struct opaqueFigMoviePlaybackRef *)fp8;
- (void)updateTimeMarkerObservations;
- (void)scheduleUpdateTimeMarkerObservations;
- (void)registerTimeMarkerObserver:(id)fp8 forItem:(id)fp12 times:(id)fp16 context:(id)fp20;
- (void)removeObserver:(id)fp8 fromTMOArray:(id)fp12;
- (void)unregisterTimeMarkerObserver:(id)fp8 forItem:(id)fp12;@end
//AVExternalAudio.h
/*
* Generated by class-dump 3.1.1.
*
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard.
*/#import <Foundation/Foundation.h>
#import "CDStructures.h"@interface AVExternalAudio : NSObject
{
struct AVExternalAudioPrivate *_priv;
}+ (id)avExternalAudio:(id)fp8;
- (id)initWithDelegate:(id)fp8;
- (void)dealloc;
- (id)attributeForKey:(id)fp8;
- (BOOL)setAttribute:(id)fp8 forKey:(id)fp12 error:(id *)fp16;
- (void)makeError:(id *)fp8 withDescription:(id)fp12 code:(long)fp16;
- (void)postServerConnectionDiedNotification:(id)fp8;
- (void)fmpServerConnectionDied;
- (BOOL)okToNotifyFromThisThread;
- (BOOL)activate:(id *)fp8;
- (float)volume;
- (BOOL)isActive;
- (void)postUserVolumeChangedNotification:(id)fp8;
- (void)fmpUserVolumeDidChange;
- (void)fmpChangeConnectionActive:(BOOL)fp8;@end
//AVItem.h
/*
* Generated by class-dump 3.1.1.
*
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard.
*/#import <Foundation/Foundation.h>
#import "CDStructures.h"@interface AVItem : NSObject
{
struct AVItemPrivate *_priv;
}+ (id)avItem;
+ (id)avItemWithPath:(id)fp8 error:(id *)fp12;
- (id)initWithError:(id *)fp8;
- (id)init;
- (id)initWithPath:(id)fp8 error:(id *)fp12;
- (void)dealloc;
- (BOOL)setPath:(id)fp8 error:(id *)fp12;
- (int)_instantiateItem;
- (id)path;
- (double)duration;
- (struct CGSize)naturalSize;
- (float)volume;
- (void)setVolume:(float)fp8;
- (void)setEQPreset:(int)fp8;
- (int)eqPreset;
- (id)attributeForKey:(id)fp8;
- (BOOL)setAttribute:(id)fp8 forKey:(id)fp12 error:(id *)fp16;@end
//AVQueue.h
/*
* Generated by class-dump 3.1.1.
*
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard.
*/#import <Foundation/Foundation.h>
@class NSMutableArray, NSRecursiveLock;
@interface AVQueue : NSObject
{
NSRecursiveLock *_mutex;
NSMutableArray *_items;
void *_reserved;
unsigned int _flags;
}+ (id)avQueue;
+ (id)queueWithArray:(id)fp8 error:(id *)fp12;
- (id)initWithError:(id *)fp8;
- (id)init;
- (id)initWithArray:(id)fp8 error:(id *)fp12;
- (void)dealloc;
- (int)_instantiateItem;
- (unsigned int)itemCount;
- (id)itemAtIndex:(unsigned int)fp8;
- (unsigned int)indexOfItem:(id)fp8;
- (id)itemAfterItem:(id)fp8;
- (BOOL)appendItemsFromArray:(id)fp8 error:(id *)fp12;
- (BOOL)appendItem:(id)fp8 error:(id *)fp12;
- (void)itemWasAdded:(id)fp8;
- (BOOL)insertItem:(id)fp8 atIndex:(unsigned int)fp12 error:(id *)fp16;
- (BOOL)insertItem:(id)fp8 afterItem:(id)fp12 error:(id *)fp16;
- (void)itemWillBeRemoved:(id)fp8;
- (BOOL)removeItem:(id)fp8;
- (BOOL)removeItemAtIndex:(unsigned int)fp8;
- (void)removeItemsInRange:(struct _NSRange)fp8;
- (void)removeAllItems;@end
This post may be short, given that I'm learning to type with this thing. I do have to make a little confession. This is my backup iPhone. That's right. Thinking a client (who, I've learned, got his before me) would want one, and realizing I could return a sealed product, I grabbed two when I went to the Apple store. With the number transfer and activation problems, I made an impulsive decision to pay the restocking fee on this one and unboxed iPhone number 2. Now I think ATT got suspicious of me trying to activate two phones on two plans... Well, let's just say that the prepaid plans are no great deal. (yes, they accepted my credit the first time around). Oh well. My other iPhone is a rental.
@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;]
I needed to come up with an innovative (pun intended, for those who get the joke) way of tracking time by task for one of my clients recently. I considered dusting off DIA or perhaps omnigraffle, but I realized that the tedium involved in laying out the hierarchical structure I wanted to see would probably kill me. So, I thought I would give Graphviz a shot.
The nice thing here is that it will lay out a hierarchy for you and flow your chart in an efficient manner. I would have gone slowly mad doing this by hand.
Click on the image below to see a larger version.
You can see the markup I gave graphviz in the extended portion of the post.