One For The Money, Two For The Show,…

WWDC is just around the corner, featuring some 10.8 (dubbed “Mountain Lion”) excitement, maybe some iOS 6 news, and possibly some hardware upgrades, although I have my doubts about cluttering the developer conference with hardware announcements.

But since it’s coming I decided to have a glance at Mountain Lion, to be at least able to follow the discussions. Now, it’s true I haven’t changed my setup in a while: my traveling companion is a late 2008 black MacBook (boosted in RAM and hard drive as time passed) that’s way more than a match for its current counterparts in terms of development. And to my mind, once it’s equipped with a SSD drive (which I can swap in very easily, by the way), it’s going to be somewhere between 20 and 40% slower than its 4-years-younger rivals. Yep.

The only thing that dramatically improved these past few years are the graphics and the core redundancy. Since I don’t play on my macbook, and can wait the extra 20s it will take to finish compiling my biggest project (I tested), I feel confident this puppy will follow me a little bit more.

But! Not so fast! 10.8 won’t run on it. Wait, what? For a frickin 20% penalty, I get to buy a new laptop in which I can’t change the hard disk, upgrade its RAM, or get an extra battery? Apparently so.

The official reason is that it won’t run in 64 bits. Wait, what? It does too! It runs 64bits programs like a charm.

“No no no, you don’t get it, it won’t boot in 64 bits. That’s why we won’t support it”. Wait, what? Windows 7 boots it in 64 bits. So does Linux. What’s the game here?

So, for my laptop, not only do I have to shell out 2k euros, but for features that I don’t care about (I have a console for gaming purposes, thank you very much), and at the expense of features I actually need (given that I work a lot with video, my hard drive has a life expectancy of a couple of years, tops). OK, well… For my laptop, I might actually get convinced, given the fact that it is pretty banged up. But that’s vanity, not technical.

And it gets worse with my trusty Mac Pro. 4 cores, 8GB of RAM and some pretty good video card (for gaming… sold that way, anyway), but still no go. The same “it won’t boot in 64 bits” shenanigans.

Except, we are absolutely not in the same game, price-wise. I can’t replace my Mac Pro with an iMac. I have 4x2TB of storage in there, plus a boatload of things connected to the myriad of available ports. So I would have to replace it with a new Mac Pro. If it ever gets announced, the thing I will need will cost something like 4k to 6k. That’s a hell of a lot for a tiny teenie booting issue that got fixed on both b1 and b2 beta releases of the OS, but that got closed on the b3 for no obvious reason.

I get that Apple is a hardware company, and needs to sell hardware. And in the past, every time my computer slowed to unbearable speeds, I upgraded my hardware gladly. But this is not it. If someone forces you to do something for no other reason than “because we say so”, there’s a good chance of a backlash.

Oh and by the way? VMWare Fusion allows me to run 10.8 in a virtual machine… on these two computers. And the speed is decent too. So I hope Apple continues behaving like the good guy, and does not start using wrong tactics for commercial reasons. They have the money (that I gave freely and abundantly over the years), they can afford it.

  

[AppleScript] Batch saving to PDF

I had to re-print a batch of bills that are saved in (sorry) xls format.

Now my problem is, I need to send the PDFs, obviously. Exporting xls files to pdf is not exactly built-in Office X and 2004, so I had to go with AppleScript.

I have been a long time fan of that technology. The language itself might be weird, but the concept of something that can glue any piece of software together is a pretty smart one. Problem is, most of the applications or standard functions are not really “applescriptable”, meaning they don’t respond to precise functions. So that leaves UI scripting.

UI scripting is phenomenal, if a little hard to use. Basically, what it does is simulate keystrokes and clicks. That allows you to do anything a user would be able to do.

Without further ado, the AppleScript to batch sve to pdf a bunch of excel files using the print dialog!

on open some_items
    repeat with this_item in some_items
        try
            tell application "Microsoft Excel"
                activate
                open this_item
                tell application "System Events" to tell process "Microsoft Excel"
                    --Bring up print window
                    keystroke "p" using command down
                    --Choose "PDF" > "Save as PDF"
                    click (menu button "PDF" of window 1)
                    click (menu item 1 of menu of menu button "PDF" of window 1)
                    delay 2
                    -- Choose the desktop as save locaton (Command-D)
                    keystroke "d" using command down
                    --Save
                    keystroke "s" using command down
                    keystroke return
 
                    -- wait and close
                    delay 3
                    keystroke "w" using command down
                end tell
            end tell
        end try
    end repeat
 
    tell application "Finder"
        open desktop
    end tell
end open
  

The Modal Panel

I’m still in the wonderful world of export plugins for Aperture/iPhoto.

There is one thing that took some time to figure out: the Modal Panel Conundrum.

Basically, here’s the problem: when you make a plugin for these applications, you don’t own the window. The application expects you to hand over a view and will integrate it in the window. That means it’s awfully limited in terms of the amount of data/effects/nifty things you can do, graphically.

So the decision was taken to add the optional stuff in a side “panel” (HUD window or regular panel, depending on the situation).

Problem number one: the export window is modal (normal). That means any new panel you setup and present a/ is going to be in the background and b/ won’t receive mouse/keyboard events.

Not to worry, my fellow developers! NSPanel has a method setWorksWhenModal: . If you set it to YES, the mouse and keyboard events will be forwarded to the panel. As for the background thing, it’s a window level problem. Use setLevel: to any level above NSModalPanelWindowLevel.

You’d think that’s it, right? Well unfortunately, it isn’t. While most controls do work as expected, there is ONE thing that doesn’t work. You can’t have any IBAction called in your code from the controls on the panel.

The run loop is in modal mode, therefore any event that should generate an IBAction generates a NSBeep instead. That means no button, no slider that updates a value in your code, no popup, no nothing. It should work, since it’s set to work when modal, and it does, to a point.

But fear not, you actually have a couple of possible solutions.

For sliders, popups, etc, bindings still work. Therefore if you bind the value in the control to a value in your code (and you have a custom accessor, for instance), you will detect the changes and have the correct value. A little tricky, but nothing ugly there.

For controls that have them, notifications work as well. So instead of having IBActions called in your code, you can setup notifications and react to them instead.

The only thing these two solutions doesn’t work for is unfortunately the most common control out there… the button. A button calls an IBAction. There’s nothing else it can do. So what can you do? Well you could “not use buttons”, but I agree it’s sometimes quite a tedious gymnastic.

So the solution is to subclass NSPanel, and to make your “modal panels” use that class instead. Here’s the code. As usual, feel free to comment and/or use it!

@interface NZModalPanel : NSPanel {
}
@end
 
@implementation NZModalPanel
 
- (BOOL) worksWhenModal {
  return YES;
} 
 
- (void)sendEvent:(NSEvent *)event {
  id clickedItem = [[self contentView] hitTest:[event locationInWindow]];
  if([clickedItem isKindOfClass:[NSButton class]]) {
    NSButton *clickedButton = (NSButton*) clickedItem;
    if([clickedButton target] != nil && [clickedButton action] != nil) {
      [[clickedButton target] performSelector:[clickedButton action] withObject:clickedButton];
    }
  } else {
    [super sendEvent:event];
  }
}
 
@end
  

Highlight 1.5

It’s been a long time since I’ve updated Highlight, but since I’ve added feature now and again upon requests (including arrows on lines, Snow Leopard support, and a bunch of other things that I forget), I have decided to release the 1.5 version with all these goodies in.

Check it out, and comment, if you like it. If you don’t, tell me why by email ;)

  

Aperture SDK and NSTableView

Problem: Making a plugin for Aperture in which I have a table containing a series of on/off values
Problem: The On/Off values are actually not “On” and “Off” but vary from line to line
Problem: Although I’m probably the last person running an OS X 10.4 as operational system, the Aperture SDK is 10.4
Problem: The function that would allow me to have a different cell for each line ( dataCellForTableColum: ) is 10.5+

Banging my head against the wall did not produce good results (in that department anyway). Looking up mailing lists questions and forums have yielded a lot of interesting solutions, none of which actually worked in my particular case (among other things, setting up an NSMenuDelegate — the problem with that is does not change the menu in its collapsed state, only when selecting items from it –, subclassing NSPopupButtonCell, with all the headache of passing data to and fro — the problem with that is a massive flux of memory allocations and reallocations which yields in most cases to a crash, even when I don’t autorelease anything –, or duplicating the menu over and over again — which doesn’t work, because on 10.4 the cells are not copies… they are all the same, refreshed for every line –, etc… ).

In the end, the solution is not particularly elegant for me, but it works, and is within the boundaries of whatever philosophy holds the NSTableView of 10.4 together : fresh menu, every time.

Basic setup:
An NSTableView with a column in which the datacell is an NSPopupButtonCell. Mine has the identifer “value” (as opposed to “name”).
A table view delegate that knows what the values for this line should be (for On and Off), and capable of producing an NSArray with the two values (here called “bitsValues”) using a function names “getBitValuesForRow:(int)”

The code goes in the function willDisplayCell:

- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
  if([@"value" compare:[tableColumn identifier]] == NSOrderedSame) { // right column
    if ([cell isKindOfClass:[NSPopUpButtonCell class]]) { // right type of cell -- you never know
      NSArray *bitsValues = [self getBitValuesForRow:row];
 
      if(bitsValues != nil && [bitsValues count] == 2) {
        // don't forget that, otherwise, your menus will snap back to item 0
        int indexOfSelection = [cell indexOfSelectedItem];
        [cell setMenu:[[[NSMenu alloc] init] autorelease]];
        NSMenuItem *item = [[NSMenuItem alloc]
                initWithTitle: [bitsValues objectAtIndex:0]
                action:nil
                keyEquivalent:@""];
        [item setTarget:nil];
        [[cell menu] addItem:[item autorelease]];
        item = [[NSMenuItem alloc]
                initWithTitle: [bitsValues objectAtIndex:1]
                action:nil
                keyEquivalent:@""];
        [item setTarget:nil];
        [[cell menu] addItem:[item autorelease]];
        [cell selectItemAtIndex:indexOfSelection]; // see above
    }
  }
 }
}

As far as I know it works well, without any leak and with the intended results.

Enjoy, and as usual, let me know if it’s useful to any of you fellow coders out there.

  

The joy of hacking : screensaver’s prefs

It so happens that I need to change the settings of the System Preferences’ Screen Saver pane, programmatically. I need to make the slide show one non-random.

Armed by nothing more than my trusty Cocoa Documentation and my guile, I try to see where the preference is stored. It’s in the ByHost section of the defaults, and changing it apparently does nothing.

Programmatically, changing the default for another application generally means using

ScreenSaverDefaults *prefs = [ScreenSaverDefaults defaultsForModuleWithName:@"com.apple.screensaver.slideshow"];
[prefs setBool:NO forKey:@"Random"];
[prefs synchronize];

But it doesn’t work. So I scratch my head long and hard. I try subclassing, I try changing it through gdb. I try a lot of things.

Finally, it comes in a flash : the slide show preferences, for some obscure reason, seem to be attached (at least on a temporary basis) to [ScreenSaverDefaults defaultsForModuleWithName:[NSString stringWithFormat:@"com.apple.screensaver.%@", [directory lastPathComponent]]] !

Illogical? Maybe. But it works :)