Solipsism Gradient

Rainer Brockerhoff’s blog

Browsing Posts tagged Cocoa

Flipr out

No comments

Searching for some cool eye candy to add to my applications, I thought of having the main window flip around to present the preferences window – an effect pioneered by Dashboard widgets. Not finding a ready-made solution I set out to do it myself, and learn how to use CoreImage from Cocoa.

So, here’s some new source code: a category on NSWindow to flip from some window to another window. Please read the “ReadMe” file for details and caveats. The code should work on any PowerPC Macs with AltiVec, and any Intel Macs.

That said, it’s not as fast as I thought it would be, and I don’t want to spend more time on it right now. I’ll be looking into Quartz Composer to do something similar, although the upcoming CoreAnimation (for Leopard) will probably make this completely obsolete very shortly.

Re: Update: XRay

No comments

Rainer Brockerhoff wrote:

…Imagine my surprise when I learned that Peter had just published the complete Hex Fiend source code, and also started up a Wiki to explain details…

That said, I haven’t had time to look at details of his data backend yet, but that too looks like it will save me at least a month of tinkering.

Well, I’ve now had a little time to look at the Hex Fiend code.

As I expected, it builds a tree of referenced/changed byte ranges for an edited file. There’s a generic “ByteSlice” class with concrete subclasses that represent either a range of bytes inside a file or a range of bytes in memory – the latter would be the result of an editing operation like typing or pasting in stuff. That much I’m already doing myself, albeit with different names.

The interesting part comes when an edited file is saved. Hex Fiend goes to great lengths in optimizing writing time, allocated RAM, and disk space; it uses threaded AVL trees and lots of neuron grease, and while I understood very generally what’s supposed to be going on, the details are extremely daunting. My hat’s off to the wizard. And he spent similar care on optimized searching, too.

Now, just lifting all that code and plopping it into XRay II just wouldn’t be cost-effective. Yes, the result is that you can open a 240GB file on a 250GB disk, swap huge chunks of it around, and insert random bytes in the middle, and save it with no problem. Do I see this situation arising frequently for XRay II users? Frankly, no. Remember, the idea is to do structured editing of file contents, not necessarily pure hex editing… and Hex Fiend is already terrific at this (not to mention, free). I see the hex editing panels in XRay II more suited to editing small amounts of data and as a convenience to view raw file contents without necessarily changing them.

So, falling back on the old method of saving an edited file to a temporary file, then swapping it with the original if creation succeeded, means that complexity will go way down at the expense of speed (noticeable only for really huge files) and of the necessity of extra free space (same).

A second problem is that, for Hex Fiend, a file is just a sequence of bytes – no structure. For me, on the contrary, changing a byte in one place – meaning editing a representation of (say) an ID3 tag in a music file, or a QuickTime atom in a movie file – will usually mean that elsewhere in the file one (or even several) count or length fields will also have to change, preserving the file’s integrity. So my data representation tree also needs to reflect a particular file’s format as decoded by a plugin – and there may be several plugins seeing the file in different ways – and the nodes need to be more intelligent, notifying each other when necessary.

Still, seeing the Hex Fiend code has given me assurance that I can do it myself, so that’s good… icon_biggrin.gif

Update: XRay

No comments

As expected, I’ve worked a lot on XRay II during the trip. I’d hoped to get the raw data updating backend working, but unfortunately debugging the plugin interface took much more time than I had anticipated. In particular, I ran into edge cases on my automatic view resizing algorithms and had to refactor them completely; it turned out that I had 3 different cases – for NSTextFields, for my outline views, and for plugin views – which were fighting each other and thus had to be folded into a single model. Anyway, the new scheme seems conceptually sound and the only thing missing are some optimizations.

To test all this out I started work on a QuickTime plugin. Currently this has a movie preview pane and a QuickTime atoms pane. The preview pane was actually working well when we started out on the trip but it turned out to be surprisingly hard to adapt to the view resizing scheme, so I’ll have to redo it. I’m using Tiger’s new QTMovie view which does a lot of work for you, but at some point it’s unavoidable to dip into the old Carbon QuickTime API which is, to put it mildly, a confusing patchwork.

No doubt there are sound historical reasons for this, but it’s a huge pain to get it all working in a modern Cocoa environment. For instance, the movie inside a QTMovie view doesn’t necessarily obey Cocoa’s clipping rules while it is playing – especially if it’s a QTVR panorama. QTVRs also seem to override Cocoa’s cursors even if the view itself is hidden or clipped out.

The second pane does a hierarchical display of QuickTime atoms. There’s a huge and confusingly documented roster of possible QuickTime atoms and they may be nested in often unintuitive ways. So far, I’ve been just using them to debug my nested container views and the autoresizing scheme, but it’s clear that this will probably be an extreme test case for both; other plugins won’t stress these aspects so much.

With all this, the data updating scheme took a back seat. Briefly, XRay II plugins will interact with the file system over an abstraction class called XRayItem. An item can represent an actual file system item, the entire contents of one such an item’s data or resource forks, or specific subparts of those. The idea is to have plugins chop XRayItems up into smaller pieces and pass them to other plugins to format and display them. Ultimately, once a data portion is changed by some editing action by the user the changed data are cached and the changes are passed upward the chain so other plugins can update their own representations accordingly. Then, when the changes are saved, they have to be collected and written out into a reasonably efficient manner.

Turns out this is not as easy as it sounds. Starting with just the default plugins that show the hex view of a file’s data fork, the user might want to open a 20GB file, select half of it, cut it to the clipboard, past it back at the end of the file (or even another file), go back to the middle and change a single byte, then do a search/replace loop over the result. Since, on Tiger, a typical Cocoa program can allocate just a little over 2GB of RAM (and not necessarily in a single chunk), this becomes a non-trivial memory management problem.

Of course, for the vast majority of files up to a certain limit – let’s say up to 128MB or so – keeping all that in RAM and changing it there would be the simplest and not-to-slow solution, and I’ll certainly have a fallback implementation for that. And if this were a Leopard-only app with a 64-bit version, this limit could be pushed a lot higher – but it has to run on Tiger and on 32-bit systems too.

So I was putting this off while tinkering with the other parts of the app, and seriously considering asking Peter Ammon for more hints, as I knew he’d solved the problem in his excellent HexFiend hex editor. Imagine my surprise when I learned that Peter had just published the complete HexFiend source code, and also started up a Wiki to explain details. Thanks a lot, Peter. Every Cocoa developer should download this gem, there’s lots of cool stuff inside from a Cocoa team insider; I’ve already learned how to filter out all fixed-width fonts, for instance.

That said, I haven’t had time to look at details of his data backend yet, but that too looks like it will save me at least a month of tinkering.

Re: Text editing…

No comments

Yet Another interim progress report on XRay II.

Turns out some of my problems with the hex editor were due to overuse of the idea to have my scrollview’s delegate do everything (including standing in for the First Responder). So I changed back to a simpler model. I now have a single view inside my scroll view as the delegate, and it stays in place – it just checks the scrollbar’s status to decide what to draw.

As this subview is declared as NSView<NSTextInput> – meaning it’s a plain subclass of NSView that obeys the NSTextInput protocol – it’s also a descendant of NSResponder, and now stuff started “just working”. Seems obvious in retrospect, of course. There’s lots of details about implementing that protocol, but it’s mostly working now; I’m still not doing any actual editing, but that’s the top item on my list of things to do next.

Several other issues are being worked on in the meantime. Many things are more complex than I anticipated, but I believe the final result will be worthwhile. The actual plugin interface is still mutating from day to day, mostly because the actual usage patterns are hard to anticipate. I have two plugins partially working: File Metadata and File Forks (which use the basic hex editor to do its work).

I’m now making a separate version of the main XRay II application to be used as the plugin developer base. This will be a stripped-down version of the application: it’ll run only the two basic plugins and a third one, the one being developed. It will also have some debugging infrastructure in place.

Next, I’ll use this to start up projects for at least two more, perhaps more, plugins of different types, both reusing basic UI elements from the main application. Hopefully this will give me enough variations to publish a rich but robust plugin interface. I envision stopping development on the main application as soon as possible – perhaps even this year, if all goes well – and doing all the rest inside plugins.

Text editing…

No comments

Autoresizing NSTextFields are now working quite well, except in one instance – when there are several of them stacked and the window is resized several times very rapidly. I’ve left this alone for now while tackling another problem: text editing. Let me explain this a little.

One of my basic UI elements is a fast hex editor view. Here’s what it looks like at present:

Three columns, hex displacement at the left, hex characters in the center, MacOSRoman (or any other encoding) to the right. All in a scrolling view. That’s where things began to get strange. For normal Cocoa use, you’d normally define an NSTextView inside an NSScrollView. The three columns might be done by NSTextBlocks, as this will be Tiger-only. Or three different NSTextContainers, each one referencing an aspect of the same NSTextStorage…

The main stumbling point here is, I want to be able to edit very large files with no performance penalty. Files tens of gigabytes in size or even larger. All the standard Cocoa text objects above are out; they use 32-bit offsets and ranges for text sizes and selections, meaning things are limited to 4GB (or even 2GB in some cases). Also, NSTextView becomes slow as molasses well before text sizes reach those magnitudes, as the entire text needs to be laid out first to determine line breaks. Also, RAM requirements to generate the hex interpretation go up – remember a standard Mac OS X app can allocate just a little over 2GB of RAM. Finally, NSScrollView uses floats to track the scroll thumb position, meaning that scroll tracking becomes imprecise when you’re trying to track millions of lines.

Well, my fate apparently is to recode most of the NeXT-era UI widgets anyway – see RBSplitView. I tackled the scroller problem first, as I needed it for the file browser anyway, and soon had a special scroll view that tracked scrolling positions with unsigned 64-bit displacements – large enough for the largest file supported by Mac OS X – and faster and simpler than the standard one. It basically consists of the vertical NSScroller and the empty (document) space to its left; no intervening NSClipView. Any subviews are relocated by the offset when the scroller changes and drawing is optimized to the visible portion.

I also put in the option to have the view’s delegate redraw the visible portion directly – this was the idea I had to optimize the hex editor. Indeed, drawing is very fast; I have my own cached CoreGraphics bitmaps of the characters, and blit them to the visible portion of the sscroll view. And only the necessary part of the displayed file needs to be accessed, thanks to mapping only that part to memory with mmap(). And putting in editing later would be easy once the display portion is finished…

Famous last words. Turns out that the Cocoa text system is rather more complex than I expected – the standard NSTextView/NSTextField objects hide that very successfully from the “normal” developer. After several days of reading the very terse docs, and trying to find out which methods are called where, I’m finally at a point where the normal text input methods are working. However, I still can’t figure out how the standard copy/cut/paste menu items are enabled, and the whole process is still too clunky for wider use.

Stay tuned for developments…

So, I needed to autoresize NSTextFields in XRay II (vertically only). Sort of a poor man’s WebView. This had to “just work” on certain NSTextFields used by third-party plugins, though, without any extra code or subclassing by the plugin writer.

There are two problems there. One is finding out the actual optimum vertical size, while editing and while not, for any type of border or bezel. Here’s the code I finally worked out, with the kind help of Daniel Jalkut:

- (NSSize)minSizeForContent {
   NSRect frame = [self frame];
   NSRect newf = frame;
   NSTextView* editor = nil;
   if ((editor = (NSTextView*)[self currentEditor])) {
      newf = [[editor layoutManager] usedRectForTextContainer:[editor textContainer]];
      newf.size.height += frame.size.height-[[self cell] drawingRectForBounds:frame].size.height;
   } else {
      newf.size.height = HUGE_VALF;
      newf.size = [[self cell] cellSizeForBounds:newf];
   }
   frame.size.height = newf.size.height;
   return frame.size;
}

So I put this into a category of NSTextField and did some runtime diddling with implementation pointers, but for other uses it could well be in a subclass.

The second problem is properly pushing down the views below the field when it is resized. The solution I ended up coding is a little too gnarly to post here, and it depends on the field and its sibling views being inside a custom NSView subclass with flipped coordinates… still, it seems to work well enough now, so I’ll leave it there and work on other stuff.

Now and then I read complaints about Xcode on blogs and mailing lists. It’s come a long way but some parts are still slow and cumbersome, granted. One of the complaints – which usually comes from Java or Windows C++ migrants – is that Xcode has no refactoring aids. Some people even publish workarounds.

So what is this refactoring thing anyway? According to Wikipedia:

Refactoring is the process of rewriting a computer program or other material to improve its structure or readability, while explicitly keeping its meaning or behavior…

Refactoring does not fix bugs or add new functionality. Rather it is designed to improve the understandability of the code or change its structure and design, and remove dead code, to make it easier for human maintenance in the future. In particular, adding new behavior to a program might be difficult with the program’s given structure, so a developer might refactor it first to make it easy, and then add the new behavior.

I’d tend to agree with that, up to a point. I usually refactor when I reach a dead end in the software’s structure, that is, when the current structure won’t allow me to proceed implementing what I want to implement. Or – probably the same thing, essentially – when I find myself implementing things I don’t want to implement anymore.

But my tendency (see fractal programming) is to do it in the reverse order; I write some code that does new stuff in a new way. Then I migrate lots of old code into the new scheme, often rewriting it radically if necessary, or throwing entire blocks away. (Well, not literally at first; I prefer to comment such blocks out or move them into a “dead code” file for later reference.)

Now, the aforementioned migrants usually don’t see it that way. Rather, they want some automation to make the process easier:

An automated tool such as a SCIDs to help you do might work like this:

– I have a method which has some code that I would like to pull out into its own method.

– I highlight the offending code.

– I select Extract Method from a popup menu

– The RefactoringBrowser asks me to name the method and automatically creates it and inserts the highlighted code.

– In the current method, the highlighted code is replace by an invocation to the newly created method.

All very nice, but it presumes several things which I don’t see coming to Xcode (at least not to the Objective-C parts):

– You have a very regular, structured style of coding that conforms to standards the “RefactoringBrowser” understands.

– You always use the standard refactoring methods, such as expanding, collapsing, pulling out, pushing in, whatever.

– All source code in your project has been previously parsed and stored in the SCID (source code in database), so the browser and refactoring software have a perfect understanding of your code.

This is perfectly possible (or at least I’m told it is) in Java and perhaps in C++ – though I’m skeptical about the latter. I was astounded when a friend, who was qualifying to some Java certificate or other, asked me to have a look at his source code. A quite trivial program was expanded to several dozen source files, consisting of literally hundreds of small methods that differed from each other only by name and a few characters or line. No doubt everything was set up very logically and hierarchically and according to whatever standards a certified Java programmer must obey, but… it was completely illegible by my (admittedly eccentric) standards. It was code only a SCID could love.

So, suppose my friend decided to refactor his code. Just renaming a few classes must necessarily entail profound changes in all source and project files. Not only must the filenames themselves be changed, but all mentions of this class must also be changed. Wait, don’t we have global search & replace for that…?

But, of course, renaming classes or methods is trivial. Maybe it’s suddenly obvious that you need to push some methods off into a subclass, or pull them up into their superclass. Wouldn’t it be nice to have this done automatically?

Well, not really. First off, inheritance is much less used in Objective-C – or at least, I use it less than I used to do in my C++ days (I refuse to learn Java). Runtime binding, introspection and categories mean you usually don’t have to subclass more than one or two deep from the standard Cocoa classes. In fact, I believe I just today went to the third subclass level for the first time. So, automating such a superfluous process makes little sense.

Second, remember that Objective-C is just a small superset of C (unlike Java and C++ which are C-like languages). And that Mac OS X is Unix-based, with many headers pulled in from heterogenous sources. This means, of course, that all the old crufty things of the old C days are still there… pointers, pointer casting, weird #defines, other tricks – you name it. And all of this is liable to be #included into your poor unsuspecting code if you do any work at all with Carbon or BSD APIs, as most non-trivial applications need to.

In other words, I don’t believe it’s possible to feed all of this into a SCID and expect it to behave rationally; of course the gcc compiler has to make sense of all this eventually, but I seriously doubt it could be easily refactored to go back and forth from its internal representation to the source code while it’s being edited. It’s still, essentially, a batch processor.

Suppose someone pulled this transformation off. Suppose all the old C headers were tamed to make them compatible. Suppose we had everything in a hierarchical, intelligent, refactoring browser/editor. Now what?

It may be some congenital deficiency in my own neural wiring, but I can’t recall ever refactoring my code twice the same way (except for that trivial class/method renaming). So again, not much for an automated “RefactoringBrowser” to do.

Well. All this to, finally, say that I’ve been stuck refactoring some of my code – specifically, the XRay II file system browser back-end… and of course, no automation would have helped me. Nor would I have trusted it to do anything like what I want.

This is perhaps the third or fourth version of it, and it’s easily the most complex refactoring I’ve ever done. Unfortunately there are no intermediate steps. 20 days ago everything was compiling and running nicely (except, of course, for the problems that led me to this refactoring attempt). Then suddenly it’s like open-heart surgery. Nothing compiles and the number of error messages is so great that gcc throws its metaphorical hands up and goes off to sulk in a corner. I can’t close the patient up again until everything has been put back into place – even if it’s not the same place. And it’s a lot of information to hold in one’s head at the same time. I suppose I must get a second monitor, but that’s not practical at this moment.

And the availability of powerful time-sinks like the Internet means that it’s almost impossible to summon the necessary concentration to do the surgery in a single run. I’ve made serious progress over this weekend by the simple expedient of staying overnight with some friends who don’t have an Internet connection (or, even, a phone line). Still, sometimes it’s necessary to read and write e-mails, chat, even write long posts about refactoring… icon_wink.gif

Even so, I hope to get past this obstacle during the next few days and write a little about actual results. Turns out I learned a lot about in the process. More as soon as possible, then.

Well, here I’m plugging away at XRay II and making reasonably good, if at times uneven, progress.

People ask now and then how I work. Just now I was reflecting on how, sometimes, reams of code get turned out on one day and then almost nothing on another day. Or how, sometimes, many parts are changed or refactored, while at other times updates are confined to a single routine or source file…

It may just be true for my style of programming, but I now see the process resembles very much the way a Mandelbrot Set is plotted. I start out with an empty project and iterate over everything repeatedly, adding a handful of code at a time, zooming in on as much details as is needed, then zooming out again to recheck the broad outlines, then zooming in again elsewhere.

I never manage to plan ahead how it will work, except for having a broad idea of what I want to do, and often I need to change direction radically at some point, either in fine or broad detail. Sometimes I need to take off for hours or days (or even months, as was the case for RBSplitView) to investigate a possible solution. I spent a couple of months learning about WebKit to format my information, only to run into trouble on some details and abandoning it again.

Then, of course, there are phases where it seems necessary to refactor some stuff that’s already done, either to make it smaller, more elegant, or just nice to look at – even if nobody else ever sees this code. It makes for slow progress sometimes, but the results are usually very satisfactory.

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.