Tuesday, March 25, 2008

Excellent

The feature freeze for R 2.7.0 is in place with an expected April 22nd release. I've noticed some of the locale stuff has changed recently (LC_CTYPE and the like) so I'll need to fix those warning messages. Right now I'm setting up the IKImageBrowserView-backed graphics device interface for the GUI, though the built-in quartz device has been serving me well so far. There are a couple of reasons for the view set up, which uses bitmaps to represent the graphics device on the backend. After the page is completed, the bitmap is converted to a more efficient representation (e.g. PNG) for temporary storage and the display list recorded into the device. We'll also continue to cheat and provide a Cover Flow mode (no, I don't care that it's a private API).

Another interesting feature is that, unlike the current Mac GUI, the graphics device is considered a View on the same Model (the interpreter). We'll see how it plays out, but that might help with some of the fancy footwork usually required when dealing with the NSDocumentController.

Tuesday, March 18, 2008

Implementing XCode 3 parenthesis highlighting

One of the things I like about XCode 3 is the parenthesis matching. It seems very obtrusive at first, but I find it works better than the more subtle Emacs-style matching once you get used to it. For the R GUI I wanted to implement the same thing, which turned out to be fairly simple.

The highlighter is implemented using the new [NSTextView showFindIndicatorInRange:] so there's literally no work to do there.

The first thing you need to know is that adding this functionality in textView:shouldChangeTextInRange:replacementString: won't work because the text is added after the method executes and that removes the find indicator. Fortunately, we just implemented a custom keybinding in the last post so all we need to do is bind the various close delimiters to special selectors (fancyCloseParen: for example). Then we can do things in doCommandBySelector:


// ...
} else if(@selector(fancyCloseParen:) == aSelector) {
[self insertDelimiter:')' withMatch:'(' inTextView:aTextView atPosition:aRange.location];
return YES;
} // ...


next all we have to do is find the delimiter (which we do with a category on NSString) and then insert the find indicator:


- (void)insertDelimiter:(unichar)aDelim withMatch:(unichar)aMatch inTextView:(NSTextView*)aTextView atPosition:(NSInteger)aPos {
[aTextView insertText:[NSString stringWithFormat:@"%c",aDelim]];
NSRange where = [[aTextView string] rangeOfDelimiter:aMatch matching:aDelim inRange:NSMakeRange(lastPrompt,aPos-lastPrompt)];
if(where.location == NSNotFound)
NSBeep();
else
[aTextView showFindIndicatorForRange:where];
}


Easy as pie.

Saturday, March 15, 2008

On Key Bindings

For my R GUI I want user customizable key bindings. I know for a fact that there are customizable key bindings built into the Cocoa text system, but they aren't user accessible and the only response from Apple was a message posted to a mailing list about 2 years ago to the effect of "I have something." Two years later, well, surprise, surprise. So what to do?

Well, for the first pass (found in the R-Multi git repository) I class-dumped AppKit and found NSKeyBindingManager. It turns out that this class has a shared initializer and a means for setting a new key binding dictionary. So, I did the simple thing and just changed that dictionary during app initialization to support some new keybindings similar to the ones found in TextMate (which doesn't use NSTextView as far as I know):


NSMutableDictionary *newBindings = [[NSMutableDictionary alloc]
initWithDictionary:origBindings];
[[NSKeyBindingManager sharedKeyBindingManager] setDictionary:newBindings];
[newBindings release];


About as simple as you can possibly get. Unfortunately, this approach has several drawbacks primarily due to the fact that it is a global change to the entire application (which is more useful that the SYSTEM global version mostly recommended). Can we do better? Let's find out.

For a clue, let's look at one of the R GUI's own crash traces:

[NSTextView keyDown:]
...[NSView interpretKeyEvents:]
......[NSTSMInputContext interpretKeyEvents:]
.........[NSKeyBindingManager(NSKeyBindingManager_MultiClients) flushTextForClient:]


Excellent. So it looks like we'll be needing to subclass and hook into interpretKeyEvents: to get things to work properly in our system. However, we cannot simply use an NSKeyBindingManager because the manager doesn't return a BOOL so we can't tell when we haven't handled a binding. Or does it?

So, it turns out there is a MultiClient category on NSKeyBindingManager that lets us do just that using interpretEventAsCommand:forClient:

So, my interpretKeyEvents now looks like:

- (void)interpretKeyEvents:(id)sender {
if([(NSArray*)sender count] == 1) {
if(YES == [manager interpretEventAsCommand:[(NSArray*)sender objectAtIndex:0]
forClient:self]) return;
}
[super interpretKeyEvents:sender];
}


So far it seems to work in testing, but I'll have to do more testing with it over time. For example, I don't know if I'll ever see more than one event in that array.

Friday, March 14, 2008

Dear iPhone SDK haters,

Nobody is forcing you to develop for the iPhone. If you don't like it, by all means, go develop for 'Android' and be sure to let me know how that works out for you.

Tuesday, March 11, 2008

A little FFI goes a long way

As my two faithful readers have surely noted, one of my little projects is a Leopard-specific GUI for R that tries to leverage as many of the new features as possible. As it happens, one of those features is Garbage Collection, which I can't use because of the way the R framework is built. Perhaps I'll be able to use Simon's method for building self-contained R applications with the framework built in the appropriate way.

What I'd really like to talk about though is the fact that having PyObjC and whatever the Ruby bridge is called means that I can be positive that a copy of libffi is available on every Leopard system. This let me finally build something I've wanted for a long time: an elegant C<->Cocoa thunk for the R function pointers. To wit:

ptr_R_WriteConsoleEx = ffi_bind(self,@selector(writeConsole:length:type:),&ffi_type_void,
4,&ffi_type_pointer,&ffi_type_sint,&ffi_type_sint);


So, how do we do it? Well, first we need a little bit of context to keep a binding between a function pointer and a specific object/method combination. We also keep the types of arguments around as well as the return type. If we were doing a complete libffi bridge we would introspect this from the class at runtime rather than the static method I use here. In the case of R this would be overkill since the R function pointers are very much fixed entities. In any case, here's the little structure I use:

typedef struct {
id obj;
SEL sel;
IMP fn;
int nargs;
ffi_type *retval;
ffi_cif *cif;
ffi_type *types[0];
} ffi_call_struct;


Next, we need to write a little function to use as a closure and move the C function call to the ObjC function call. All libffi closure handlers look have the same function declaration:

void handler(ffi_cif *cif,void *retval,void *args[],void *user_data) {

First we build a place to hold our arguments

int i;
ffi_call_struct *call = (ffi_call_struct*)user_data;
void **values = (void**)malloc(sizeof(void*)*(call->nargs+2));

and simply transfer the arguments accross, making sure that the first two are the target object and target selector (Obj-C methods have two implicit arguments):

for(i=0;inargs;i++) { values[2+i] = args[i]; }
values[0] = &(call->obj);
values[1] = &(call->sel);

Then all we do is use ffi_call to make a call. Note that we use the NSAutoreleasePool method, which wouldn't be necessary if we could enable garbage collection:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ffi_call(call->cif,(void (*)(void))call->fn,retval,values);
[pool release];
free(values);
}


Next we need a little function to build an appropriate closure function to use as the R function pointer:

void *ffi_bind(id obj,SEL selector,ffi_type *retval,int nargs,...) {
int i;
ffi_cif *cif;
ffi_closure *closure;
va_list ap;


We use vargs so that we can define the argument lists inline. First, we allocate our context structure and transfer the argument types

//Allocate the structure and copy the call information into the structure.
ffi_call_struct *call = (ffi_call_struct*)malloc(sizeof(ffi_call_struct)+(nargs+2)*sizeof(ffi_type*));
call->obj = obj;
call->sel = selector;
call->fn = [obj methodForSelector:selector];
call->retval = retval;
call->nargs = nargs;

call->cif = (ffi_cif*)malloc(sizeof(ffi_cif));
cif = (ffi_cif*)malloc(sizeof(ffi_cif));

call->types[0] = &ffi_type_pointer;
call->types[1] = &ffi_type_pointer;
va_start(ap,nargs);
for(i=0;itypes[2+i] = va_arg(ap,ffi_type*);


Next we define the two ffi_cifs. The first one is the ffi_cif of the Obj-C method call and the second is the one for the function pointer we're trying to create. Finally, we create the closure and return it

ffi_prep_cif(call->cif,FFI_DEFAULT_ABI,nargs+2,call->retval,call->types); //The CIF for the ObjC Method
ffi_prep_cif(cif,FFI_DEFAULT_ABI,nargs,call->retval,&(call->types[2])); //The CIF for the function call
closure = (ffi_closure*)malloc(sizeof(ffi_closure));
ffi_prep_closure(closure,cif,handler,call);
return closure;
}


and that's pretty much all there is to it. There are two minor caveats, the first is that anything that employs method swizzling on this class is likely to fail since we cache the function pointer at bind time. Fortunately, swizzling is relatively rare these days so it generally doesn't happen. It may also technically be slower than the hand-coded method, but there are a couple of areas where we could probably speed up the thunking operation---the allocation of
values
in particular could be moved to the context object.

Thursday, March 6, 2008

And the bloggers chime in...

So, the iPhone SDK was announced (good luck with actually downloading it) and I'm looking forward to playing around with it (no, I'm not going to try to get R running. Let's face it, the iPhone UI is more like, say, Data Desk than anything else). I am getting annoyed with assorted bloggers and pundits bitching about perceived shortcomings.

First, the 70/30 split. That seems to be the biggest complaint. In the words of Dr. Evil, "quite typical, really." Jobs is probably right when he says that it's just enough to cover expenses. Credit card processing ain't free, kids. They might see a little net revenue, but it's in the noise relative to the rest of their business. Basically, apps are a way to sell iPhones, and it'll work.

I also saw someone complain about not being able to change the iPhone UI L&F. Well, yeah. Duh. Apple has seen X11 and knows exactly what happens when you allow that sort of control. Basically, rip-offs of the Windows 95 UI (No, don't expect me to be impressed that you can map Windows 95 on to the sides of a cube and spin it around. At the end of the day you've still got Windows 95 with uglier controls).

Speaking of UI and complete non sequiturs (well, not completely since Salesforce presented in the iPhone thing). I ran across an O'Reilly book this evening at Barnes & Noble called "Designing Dashboards," except that the title was longer. For statisticians with at least a mild interest in presenting results there's nothing there that comes as a surprise (and several areas that could have used some help, color palette selection for example). Clearly the author has read Tufte's books (sparklines even made an appearance). Mostly, though, I'm struck by how absolutely horrible the types of dashboard produced by things like Business Objects and Salesforce (there are others, but these are really popular). The outputs are very expensive in terms of real estate (lots of round things, pie charts and dials and such. Usually in some godawful 3d rendering) and deliver very little, usually a single number. Even when they become more traditional/less junky, the little things really start to stand out. There's a lot of "stop light color" usage, which seems like a good idea but really just induces fatigue (I tried this recently in my own workflow tool and could only stand to use the tool for about 10 minutes. The same dimension is now represented by a much more subtle change in glyph size, which is surprisingly easy to assess at a glance). Even when they move away from stop-light colors there's no rhyme or reason to the color choices nor any apparent internal consistency.

Anyway, I'll probably write more on what I'm learning about visual display of complex information from the workflow tool I've been developing as a place to hang my modeling hat for the last couple of months. I've got a stable core set of users and I use the tool myself literally daily (usually the development version which varies somewhat from the 'production' version). I've also got some things to say about R and databases and workflow in that environment as well.