Solipsism Gradient

Rainer Brockerhoff’s blog

Browsing Posts tagged Cocoa

This will take some explaining, and may not make much sense unless you’re a Cocoa developer. So feel free to skip this.

In early February – about a month and a half ago – someone asked an obscure question on the developer lists and, by chance, I had a code sample that did almost exactly what that guy wanted. So I sent it to him. So far so good… warm fuzzy feelings all around…

…it turned out that this gave me an idea for a new contextual menu for the Finder. So I thought, the new CM will be just that code sample with some padding, so let me start first on the application to configure stuff for the CM. Shouldn’t take more than a week or two.

As usual, I started out by designing the application icon first, which sidetracked me into a long investigation about the habits and aspect of a certain mammal. That done, I decided the user interface should consist only of a list of items. Fixed-size items each consisting of a few icons and a name. Of course, the first choice was using a NSTableView.

It turns out that the icons were too dissimilarly sized, so a table with one row per item showed too few items, and the UI looked ugly. So my next idea was to use a NSMatrix of cells, each cell with the subitems artfully arranged. This looked better, but…

I soon noticed that a NSMatrix operates with rows and columns of cells (duh!). I wanted a view that adapted itself to the window size the user wanted, but having to scroll both horizontally and vertically was ugly as soon as there were more than a dozen items. So I messed around for a couple of days with adapting the number of columns to the window width, and couldn’t get it to work reliably.

So I decided that I wanted something like iPhoto’s browser window, where there’s only a vertical scrollbar, and the view adapts instantly to width variations. So I started to write a custom view that would be inserted into a NSScrollView and do automatic layout of as many items fit into the current width.

After a week of this it was almost working… however, I was getting some strange behavior in certain conditions; when the window was resized very rapidly, or when the scroller had to appear during insertions, or, and so forth. It appeared that I wasn’t managing the communications between the NSScrollView and my own view correctly under all circumstances.

So I was trying to trace when NSTextViews communicate with their NSScrollViews, when it hit me that I could simply use a NSTextView and insert the item as specialized NSTextAttachmentCells. Bliss! This would give me all the required scrolling and resizing behavior automatically, and I would just have to write the specialized cell code and disable some of the NSTextView’s functions… it wouldn’t do to allow the user to insert actual text there, for instance.

There I went off into another two-week-long detour of writing and debugging the cell code. My cells consist of several subentities which are actually NSViews themselves – images and text – and they in turn have to be editable, selectable, and so forth. I’ll leave the saga of how I finally solved this for another post – it involves adding a subview to the NSTextView, something not normally done.

So after that I had to decide which text behaviors I wanted to leave in the NSTextView. No editing, OK. Selecting multiple items with the text cursor? Hmm… it looked interesting. The user could select several items and drag them somewhere! Perhaps for backing up the items into a Finder window – or to make a list of his items into a text windows. I happily set to work implementing routines for copying and dragging the current selection in various formats, which in turned allowed me to exercise my code under unforeseen conditions and decide how the items would be stored and saved.

Then when it was nearly finished I started to run into snags again. Suddenly I wanted to select two non-adjacent items, which NSTextView doesn’t allow. I changed my system-wide selection color (for a non-related reason) and suddenly selections in my NSTextView began to look ugly – worse, the way selections were made called the user’s attention to the fact that this was a NSTextView with much reduced functionality. This is never a good idea; it leads the user to expect certain things to work, and when they won’t work, it’s a negative experience.

So today I disabled all text selections wholesale, as well as all related functionality. This enabled me to throw out several chunks of code and someday might allow me to implement discontinuous item selections, too. So the NSTextView really just handles reflowing of my item list, and all the remaining functionality is shuffled off into my specialized NSTextAttachmentCell class.

So far so good; I expected to finally freeze my UI and get to work on the shareware related aspects – registration, trial expiration, and so forth. However, I still have to extirpate the final clue that I’m using a text view, namely the I-beam (text insertion) cursor. So far I’ve hammered away at this for the whole day without finding a solution that works all of the time.

As usual. 😥

Stay tuned…

Re: One more…

No comments

Well, the details caught up with me very fast. By late afternoon, one user e-mailed me a crash report for Zingg! 1.4. After dinner, another user e-mailed me with a simple way to reproduce the crash. 30 minutes later it was fixed, so 1.4.1 has just been published.

This sort of thing is very embarrassing… neither I nor my handful of beta-testers thought of doing the particular choreography that causes the crash. Namely, one has to:

  • set either DropStuff or Disk Copy to “always/override”;
  • set the “Show” popup to either “No comment” or “Styled name”;
  • open the contextual menu and the Zingg! submenu;
  • without releasing the mouse, go outside the submenu to close it;
  • still without releasing the mouse, go back and reopen the submenu. Boom, as Steve Jobs is fond of saying.

Actually, any application which has exactly 8 or 9 letters and which starts with any hexadecimal digit would cause the crash, but only DropStuff (and, for Jaguar users, Disk Copy) fall within these parameters. At least of the applications I have here.

OK. Why can such a weird combination of circumstances trigger a crash? The answer lies within the way Contextual Menu Plugins work. Briefly, the plugin is a loadable bundle with a few precisely defined entry points. When the user control-clicks (or right-clicks) on some item(s), the “Examine Context” entry point is called with a list of AppleEvent records – one for each item. Such a record resolves to a file path or URL. So I have to loop over the list and by a somewhat complex logic return another list containing parameters for the items I wish to have inserted into the Finder’s contextual menu.

These parameters unfortunately are somewhat restricted; I can pass the menu item’s text, a so-called command-ID, and a few attributes for the item. Later on, if the user selects one of my menu items, the “Handle Selection” entry point is called, this time with that item’s command-ID, and the same list of records that was passed to the first call I described. At this point, I decode the command-ID to get back which application I should open, and I re-decode and encode the record list to pass the list of items to be opened to that application.

All very well, but how to attach application icons to each menu item? This demands more complicated hackery. First, I had to register yet another entry point: a “Menu Event Handler”. Unfortunately, this gets called for the main contextual menu and for every submenu – not just for my own little submenu! So first I had to detect my own submenu among the others; not easy when contents are wildly variable. I finally hit upon the trick of setting an invisible first item with a special encoded title.

Also, for some weird reason, at the point this handler gets called the command-IDs haven’t yet been attached to the menu items – so there’s no way of telling directly to which application each item corresponds. So I simply encoded that information into the menu item texts I return from the “Examine Context” entry point, instead of putting in the actual application name. When the handler sees such an encoded text, it decodes it to obtain the application path, and from that gets both the name and the icon, and puts them into the menu item.

What I conveniently forgot is that the handler gets called every time the submenu is opened – not just when it’s built. So the second time around, the handler dutifully tries to re-decode the menu items again – only now they already contain the actual application names! And for certain names, the decoding will actually proceed with invalid data and crash later on. So, a simple first-time flag and it was fixed. For some reason, in my tests I never played around with the mouse, opening and closing the submenu…

Zingg! out…

No comments

Well, Zingg! 1.3 is out.

It took a few days longer than I expected. First there were some more bugs and suggestions; then I tested it on Jaguar and two things didn’t work at all; and finally I had a run-in with configuration problems for my local database server. If you’re wondering about the last item, it’s because Zingg! 1.3 now incorporates the latest version of my online version-checking code… which incidentally feeds me the user’s version of Mac OS X for my statistics.

As I type this, the VersionTracker page counts 387 downloads. If past stats are any indication, that means about 800 or 900 downloads total… many people go directly to my site. My site statistics run every midnight, so I have no exact figures yet. Meanwhile, 206 of those people (let’s say 25%) used the new version-checking code. 204 are on Mac OS X 10.3.2, 2 are on 10.3.1. No earlier versions at all! I’ll have to check whether this is a bug or a reliable statistic…

Then again, this is good news, as I’d like to do Panther-only software in the near future. I’ll try and hurry up the next version of XRay to get stats from those users, too.

The Jaguar incompatibilities were quite puzzling. The Zingg! Configurator relies on a main NSTableView to show a list of applications. I wanted to allow the user to sort the table by any of the 4 table columns. The standard way of doing this, by clicking on the column headers, seemed simple to implement. Since I used NSURL objects to store the application names and paths, I subclassed this to store a complete table row in each object and then used the standard NSArray sortedArrayUsingSelector: method to sort this in different ways. It worked on the first try on my Panther development machine… but then in Jaguar it threw an exception indicating that my subclassing wasn’t working at all.

This was complicated by the fact that I’ve migrated all my projects to Xcode, so I couldn’t use a debugger on the Jaguar machine… but I finally found some hints that the NSURL internal workings had changed significantly from Jaguar to Panther – apparently it used to be a class cluster, but wasn’t anymore. To save time, I changed from a is-a to a has-a pattern for my table row object, and this worked again.

Then I ran into a Jaguar bug: the delegate tableView:didClickTableColumn: method isn’t always called, unlike in Panther. The workaround is to turn on the option to allow column reordering (by drag&drop) – I thought it kind of useless but found no other way.

Then (after already uploading the disk image) I had to go back and rewrite the docs for the changes… c’est la vie. At least it’s out now and no bug report’s arrived so far…

Photos licensed by Creative Commons license. Unless otherwise noted, content © 2002-2024 by Rainer Brockerhoff. Iravan child theme by Rainer Brockerhoff, based on Arjuna-X, a WordPress Theme by SRS Solutions. jQuery UI based on Aristo.