Friday, July 28
[The very, very distant future.]
• When Bill first announced he was retiring in two years, I was like, "Damn, you'd think he'd at least wait around until after Vista shipped."
• Microsoft just announced Vista will ship "
when it's ready." What, now you're admitting you're
never going to ship it?
• I just found out that the home edition of Vista will come bundled with
Duke Nukem Forever.
• We're talking about an operating system update that is now
six years late. Six years! If I had a girlfriend who was that late, I'd be playing video games with my kid right now. (But not, you know, under Vista.)
• Microsoft now has completely cornered the market on two types of products: "not shipping" and "
not profitable." How do they make money? Volume!
• This subtitle cracks me up: "
In wake of Vista, [Steve Ballmer] says Microsoft will never again wait 5 years to upgrade a major product." Uh, gee, ComputerWorld, have you ever seen a boat? Because, the wake comes AFTER the boat goes by. This boat is still in drydock.
• You've got to admire the temerity of a company that sells you an operating system that will
brand you a pirate if you install too much third-party software or change video cards,
and name this feature "Windows Genuine Advantage." I'm trying to imagine some little guy at Microsoft asking the marketing department, "Uh, hey, guys, people are switching to better, open operating systems. Seriously, all BS aside, what's our message? What's the
genuine advantage for people running Windows?" And marketing is all, "That's it! That's what we'll call our obtrusive new system for making people's computers work
even less reliably in order to make us more money! Genuine Advantage! Genius!" And the original guy is all, "Whaaa... what just happened?"
--
'For better and for worse, there's no other company that would be attempting to get into that business at this time... Nobody else has the optimism, nobody else has the financial resources, and you might say nobody else, you know -- well, let me just leave it at that.-- Steve Ballmer, on the his new iPod look-a-like
Hmm, what's he talking about? I have some guesses... here's my list of other things nobody else has:
• The complete disconnect between their appetite for dominance and their ability to actually ship stuff.
• Billions upon billions of dollars with which to subsidize their new products because there's no way people would pay what it costs Microsoft to develop and manufacture them.
• The ability to invent new 'standards' on almost daily basis and get the press to bite. Everybody COM? DirectThis? Active Stuff? Anything .Net? Playsforsurenoseriouslyunlessyouownthemostcommonplayeronthemarket? Live Everything?
"There really is a Sony that lives inside of us."
-- Steve Ballmer, again
You want to invent
new audio standards nobody uses and make iPod clones that nobody buys, too? You know, I think you're up to the challenge, Mr. B.
--
In other news, the first of a series of
interpretive paintings I'm having done of the Delicious Monster is now up on the intertron. I randomly found
Brian Pike's website and loved his stuff, so I commissioned a monster from him.
I'll be selling a series of posters of the monster done in different artists' styles later in the year, when I collect more of these. If you're an artist, or you know an artist, you (or he or she) can get in on some of this commission action. The basic terms are, "Do a painting, in your style, of the Delicious monster doing what you think Delicious monsters do." [Keep in mind that the monster is a shy vegetarian who lives in a jungle and tries to avoid getting eaten.] Just send me a sketch of an idea (and some of your other work) and if I like it, I'll commission a full work (should be approximately 1' x 2' or so) for $450.
In the end, I get the painting and the copyright for the painting (so I can sell reproductions), but you can use the reproductions in your portfolio. I'll consider other media types, as well, if you have something compelling. The simplest e-mail address for me is wjs at apple's mail server, which is mac to the dot com, my sizzle.
--
And finally,
Delicious Monster is officially looking for an artist who can do icons, interface elements, and, hopefully, gorgeous 3D renderings of real objects. Send your sample work (including a piece of furniture if you can do 3D -- is this a hint of something to come?) to me if you're interested.
Labels: mac community, stories
Monday, July 24
My web host runs a cool little program (Urchin) that tracks how many people come to my website and how they got there. I check it periodically to see which sites are talking about me (yes, I'm vain, big surprise there) and how people are finding out about me.
There are some really strange data, particularly in the search terms with which people find my site.
I mean, sure, lots of people google, say,
etrade (6.73% of all visitors on Friday, which means that they really, REALLY shouldn't have screwed with me), and often people will just google
wil+shipley (3.85%) instead of remembering my non-mnemonic url (wilshipley.com).
But, how about the guy who was looking for
custom+pimp+puppets? My first thought was, why'd this link to my site? And then, "What's a pimp puppet? Is it just a puppet of a pimp? If so, why the hell does someone want one?" (Unless it's named Frank.) The really bizarre part is the person didn't find what he wanted, so he followed up by googling
custom+made+pimp+puppets. Vive la diffrence!
Now, I can see refining your search, but,
why did he follow my link twice? I can just see some guy going through his list of possible hits on google and double-checking each one... "Did I already do wilshipley.com? I can't remember... OH GOB, WHY WON'T SOMEONE MAKE ME A CUSTOM PUPPET OF A PIMP?"
--
Stunningly, the #1 way people found my site in the last month was googling
kyle+orton+drunk. I don't even know who this is or why he's drinking, although I do support the notion of drinking in general. Also, google doesn't actually link to my site with this search any more, so I'm not sure why this was my #1 referrer, but 10% of ALL visitors have this as their search terms, and, hey, welcome to all of you new readers, although you might be a bit disappointed with my site since I've only got 1/3rd of what you're looking for here. But here's a toast to your buddy Kyle!
--
Seven people came to my site after googling
delicious+library+serial+number. Yah, because, uh, my blog is the #1 place to find haxxored codes FOR THE PROGRAM I WROTE! GET A JOB, YOU HIPPIES!
--
There are a lot of strange singleton google searches that I feel are like questions to me, personally. For instance, someone got to my site by asking
what+does+%22dress+for+dancing%22+mean? (I feel I've let you down, sir. If you come back, I'd wear slacks that are comfortable and a nice shirt with buttons. Unless you're a girl - always go with a dress. You know... "dress for dancing.")
And, as for Mr.
which+school+did+michael+faraday+go+to, I honestly have no idea.
Wikipedia might... Ah, yes, here it is: he was self-educated; apprenticed to a book-binder for seven years, where he read many books. Man, there's a romantic past. I'm going to start telling people that when they ask me about my education. Screw the UW, that's LAM0RS!
sex+position+for+bad+knees? Ok, I got this one: ON YOUR BACK, GENIUS. Geez, did that honestly never occur to you? (Note: Wil Shipley is not a real doctor, and his medical advice should not be taken without first consulting your physician. Who will laugh at you. I mean, come on, just get off your knees if they hurt.)
what+do+you+call+the+guy+who+trades+options%3f? Options trader. Total gimme. Next?
write+like+a+pirate? Arrr, tis easy! 'Cept larnin' how ta' hold the pen in yarrr hook!
when+will+the+g5+have+an+intel+processor? Uh, I guess when you can buy an order of fries with a side of fries?
actual+pimp+lessons+and+tips+for+getting+a+bitch? No. First off, pimping ain't easy. Second, it's hard out there for a pimp. Third, you're an idiot. (Although, I can recommend a lovely puppet for you... no?)
--
Some people not clear on the concept of google:
me+creative+thinking+final+project. Hmm. Me assuming you not get good grade on that one.
using+wine+with+autodesk+inventor -- Oh, yah? Well I invented using wine with pimping code. Also, wine with programming, wine with playing Nintendo, and wine with breakfast. Oh, sweet wine.
And a personal note to Mr.
naked+woman+pirates -- I like the way you think! You've pretty much covered your basic food groups there, except for booze.
--
Speaking of booze, yet more on our friend kyle:
orton+drunk -- I hear he is, yes.
google+kyle+orton+drunk.+ -- You don't have to type the word
google into google, sir. It knows it's google.
kyle+orton+and+drunk -- kyle orton is be drunk, kyle orton a drinker be, kyle orton drinky-drank, yes, we get it.
kyle+orton+drunk+pics -- meh, you've seen one kyle orton drunk, you've pretty much seen 'em all.
kyle+orton+out+drunk -- I honestly can't believe anyone can out-drink this guy, considering how famous he is for it.
ray+orton+drunk -- No, no, it's kyle... K-Y-L-E.
--
A lot of people come to my site looking for info on Einstein, the talking-est parrot you ever did meet, and rightly so, because I am a one-stop shop for all your talking parrot needs. However, webcrawler (written by Brian Pinkerton, all-around awesome dude) has the most interesting link of all... if you
search for images of Sister+Brother+Jerk+off+Story on webcrawler.com, the ONLY picture you'll find is from my blog -- it's Stephanie, the zookeeper from the Knoxville Zoo, with Einstein, the parrot that loves her, at TED 2006. Go ahead and follow that link, there's really nothing much going on. I mean, she's kind of making a kissy face, but they're both fully clothed. (Well, the parrot is fully... feathered.)
Now, I'd like to point out that Stephanie is NOT my sister,
and she's happily married. And the parrot isn't related to me, either. And I never laid a hand on the zookeeper OR the parrot. Not that the parrot isn't attractive, but, you know... too bitey for my taste.
So how they HELL did this search term end up pointing to my site? Admittedly, my favorite joke in the whole world starts, "Did you hear about the guy who fucked a parrot?" but I don't think I've ever posted it here.
--
PS: He got a canarial disease: "chirpies". But don't worry, it's tweetable.
Labels: stories
Friday, July 14
Tonight I was thinking about global variables (or, their equivalents -- class variables or class methods that return static values), and how gosh-darned handy they are, except for the fact that you don't often use them because what you want is variables that are global to a particular document, and if you have multiple documents open you don't know WHICH particular document unless you have a backpointer to it, in which case it's not a global any more, is it?
And then I realized that, in the future, each instance of an application will have only one document. Which is to say, if the user opens 10 TextEdit documents, she'll actually have 10 instances of TextEdit running.
The advantage to this is that, if you do something that is slow or modal in one TextEdit document, you don't have to, like, go check your mail or do something else -- you can edit another TextEdit document while you wait. When you think about it from a user's point of view, why is it that when TextEdit is hung (doing something really intensive on one document, or even just hanging for the fun of it), you can access Mail or Safari documents, but not other TextEdit documents? Is there some research that shows you are more likely to want to go send a Mail message than edit another text document? This is an app-centric feature in what
should be a document-centric world.
But imagine if, say, some crazy web page made Safari crash, but it was that only one window, and all your other Safari windows were still open. Imagine if you hung Preview by saving something as a JPEG2000 while playing a movie (true story) and you could just kill off that
document and all your other images you were working with were still around?
So, how would this work? Well, at a lowest level (the OS), it's pretty simple. Mach already will automatically share pages mapped in from the application's resources and object code, between running instances of the app. (In fact, I believe this sharing of resources happens right now if two users use Fast User Switching and run the same app.) The OS
would have to actually launch the application multiple times to create multiple documents, which would mean there'd be an extra memory overhead of a whole application's stack being created (and the objective-c environment being fired up, classes being initialized, etc), and there'd be a speed hit from this as well. However, speed and memory are two things that are getting cheaper, whereas programmer time is not. And startup times are getting faster and faster even without hardware improvements, thanks to cool OS and Cocoa tricks. I certainly don't think those are exhausted (freeze-dried apps, anyone?).
Then, at the Mac workspace (Finder) level, Apple would have to do some work with things like AppleScript and LaunchServices so they are aware that there is no longer just one, say, "TextEdit" to which it can send messages. Some things would work just fine with little reworking -- if you have an AppleScript that says, "Open this document in TextEdit and do blah blah" you already don't know and don't care how many documents TextEdit has open right now, so this change wouldn't matter. But if you look at an AppleScript that says, "Go through TextEdit's open documents and tell the first one..." then that would obviously require some OS glue to work correctly.
On the dock, Apple would only show one icon for any given app, no matter how many instances of it were running. Apple could pretty easily rework the workspace so all documents that were running in the same base application were shown/hidden together, even though they were in different address spaces. The Windows menu wouldn't be hard to modify so that it just brought another app to front, as well, if you selected a different document.
The user wouldn't (necessarily) want inspector windows to move around when she switched documents inside a single application, but that would be easy to handle -- it wouldn't be hard to do some simple messaging between instances of the application telling them all where their "Blah Inspector" should be located and whether it should be hidden, for example. This kind of coordination of the "shared look" of the app would be easily built into AppKit -- you'd simply decide ahead of time what should stay the same between documents (maybe you have a special icon dock that sits at the top and should be there for every document, for instance) and AppKit would make sure that all running instances are in the same state, much as it does right now with toolbars (albeit in a single process, but the extension is not hard). Consider, for instance, the color panel, where swatches are always the same across all running programs, or the font panel, which displays the same fonts and groups no matter the app. This is a solved problem.
The user experience for all this could be just like the current one under OS X, except crashes and hangs and modal operations in a document would be handled MUCH more gracefully, and the task of writing document-based Cocoa programs would get simpler. Imagine, for instance, if you're populating a popup button somewhere with, say, the names of all the objects in a Graffle document. The neat thing would be, you wouldn't need a backpointer! There's only one document, so you can just treat it as a global: "Hey, global document, give me all your object names." You're done. (Sure, you can ask the NSApp for its currently active document right now, but there are instances when you're doing work for a document but
you can't be sure that it's the active document -- it could be one of the background ones. This would eliminate that uncertainty.)
Conceptually, this whole model seems a lot cleaner to me, basically because different documents inside an app almost never talk to each other. There's really no reason have them in the same address space, except for the fact that launching apps was expensive (up to now) and took up memory.
But as we switch to these new Intel machines, we're finding app launches are, like, a fraction of a second. And, history has shown us, whenever we have an excess of speed or memory, we use it to make programming simpler for ourselves, which allows us to make cooler programs for users.
Heck, in the old days, we only had one address space, and if you crashed a program your whole machine was S.O.L. Now we can't even imagine such a barbaric time. I think one day we'll look back to the days when a single application had multiple documents and say, "Why? That's such a pain to deal with! What if one document corrupts memory! They'll all get messed up! That's crazy!"
Labels: interface design, mac community, random ideas
Thursday, July 13
Hey, Bill, you recently went on record saying that
you felt Windows Vista had an 80% chance of shipping by January, 2007. Fine, I'll take that bet. I've got $10,000 that says you do NOT ship by then.
Now, you are (still) the Chairman of Microsoft. You've got inside information on this issue. You can talk to the engineers, you can look at the source code, you can read reports from your managers. You've got people you pay (in aggregate) millions of dollars to estimate this kind of thing. You've spent $8
billion writing this crap.
I, on the other hand, am just some guy. I don't even read the news about Microsoft much, except when you guys get slapped with another antitrust lawsuit or fine, or when you try to buy your way out of hell by giving Windows software to another school or something.
Still, I'm willing to risk $10,000 of my money against $30,000 of yours that you do NOT ship Vista by January. (That's 1 to 3 odds, for bookie-types.) Since your stated "80%" means you think the odds are 4 to 1 against slipping, I'm giving you better odds than you asked for -- you should really be putting up $40,000 to make this fair, but I want to make it more attractive for you. I know you're famous for being
cheap frugal.
You ask why? Because you can't throw money at every problem to solve it, Bill. You can't just keep adding engineers and money to a giant, ugly mess and hope that, eventually, it'll become a polished, tight piece of art. No art was ever created by committee. Nothing great was ever designed by 4,000 engineers. Ever.
You said, "If the feedback from the beta tests shows it is not ready for prime time, I'd be glad to delay it." Glad? Really? Well, get used to happiness, I guess. Because you know that, if you're
lucky, in January you're going to squirm and weasel and release a "limited version" that you "recommend" only for, uh, say, professional IT guys who only have one eye, and suicide kings. Then, when you get a bunch of press on how crappy that version of Vista is, you'll quietly cut more and more features from it until you end up with the "home" version, which will look mysteriously like XP with some new paint.
But what do I know? You're the billionaire, I'm just some dude with a blog and a fistful of design awards.
So put your money where your mouth is, big guy. I'm calling you out. I'm steppin' to you. You just got served. If you really believe what you said, this is a slam-dunk. This is money in the bank, baby. Unless, uh, you know full well there isn't a chance in hell you can ship that giant spaghetti monster to consumers "on time". (Where "on time" means "only six years late.")
--
On a personal and professional note, if you're not Bill Gates but you are
cheap still looking for a deal,
Delicious Library is on sale right now for what is probably the last time ever -- we're going into stores soon, and when we do we're not allowed to undercut them with discounts (like this one). So, strike while the iron is hot, make hay while the sun shines, win one for the gipper, and all that rot, wot wot, and, er, and, uh, well, please give me some money, is what I'm saying.
Because you
know Bill Gates is going to want me to put the $10K in escrow up-front.
Labels: mac community
Monday, July 3
Today's post is a meta-pimp -- this is the summary of some guidance I gave one of my programmers here at Delicious Monster. Most of this isn't new information; it should in fact be standard practice for experienced Cocoa hands. Consider this a remedial pimping; for young pimps and the pimp-at-heart.

Most sheets you'll pop up will be "alert sheets," of the simple "Hey, uh, you're about to do something bad and/or extremely bad, are you sure you want to continue?" variety. (The examples here are all cribbed from
Apple's excellent user interface guidelines, which you should read and re-read.)
Alert sheets are simple to program, although not as simply as alert
panels used to be under Rhapsody (pre-10.0), because panels are application-modal and sheets are document-modal. The relevant difference for users between the two modalities is that with document-modality the user is
still allowed to mess around with other documents, whereas in app-modal mode you put your panel in front of everything and force the user to vote yea or nay before she does anything else, period.

Contrast this with Mac OS 9's largely
system-modal panels, where, for example, if you were printing in one application the print panel would block the user from interacting with any other applications until the print was complete. This, frankly, sucked from the user. Using induction we can deduce that finer-grained modality is preferable, and thus that we should use document-modality over app-modality, even though app-modality still exists in the Cocoa APIs. And, for example, iTunes uses application-modal panels on their info panels instead of, say, document-modal sheets, which is really pretty bletcherous. (Really, they shouldn't use modes at all in an info panel, but that's a separate issue.)
So, before 10.0, you would find yourself asking the user's permission (or forgiveness) using one of the following two panel functions. You can still use them if you come up with a really good excuse to block the entire application -- for example, if the user selects a menu option like, "Delete this application and add my name to a list of people who are never allowed to run it again":
int NSRunInformationalAlertPanel(NSString *title, NSString *msg, NSString *defaultButton, NSString *alternateButton, NSString *otherButton, ...);int NSRunCriticalAlertPanel(NSString *title, NSString *msg, NSString *defaultButton, NSString *alternateButton, NSString *otherButton, ...);Note that there are two nice variants provided here, both stolen from Mac OS 9 by NeXT engineers a long, long time ago -- the "informational" panel and the "critical" panel. For your reference, here's a friendly quote from the currently-current Apple HIG guidelines explaining when to use which: "In rare cases, you may want to display a caution icon in your alert, badged with the application icon as shown in Figure 13-30. A badged alert is appropriate only if the user is performing a task, such as installing software, and a possible side effect of that task would be the inadvertent destruction of data."
And here's an example of a critical alert panel:

Notice that there's a big yellow exclamation point, which is the international symbol for "you are going to lose data." (Not really, I just made that up.) As a programmer, you might not have realized there are two variants of the standard alert panel, because many Cocoa programmers don't use critical alerts when needed (instead they use the generic
NSRunAlertPanel() function, which is admittedly 50% easier to remember). For instance, in TextEdit, when you change a rich-text document to plain-text, and are going to irretrievably lose all your formatting information, you get an
informational sheet, not a modal sheet. Whups.
Update: Mea culpa! I just checked and, in 10.4 at least, TextEdit can undo past a RTF conversion event, and so they were correct to NOT make their sheet be critical. I apologize to the authors of that app for impugning their character in my search for an example.
Invoking either these two panels from your programs is as simple as cake: you just call 'em and check the return code to see which button was pressed. Remember to follow the HIGs when it comes to the "title" and "msg," because a lot of people get this wrong. Specifically, the "title" isn't really so much a
title any more under Aqua, it's more of a
line of text describing the issue succinctly. This is a change from pre-Aqua days that some Cocoa programmers haven't fully grokked, so you'll still see panels with the title "Alert" or with just a title and no text, which
the HIG guys hate.

--
So, after that brief discussion of application-modal panels, we move to document-modal sheets, which you should strive to use whenever it makes sense. First off, let's check out the easy-to-functions for these:
NSBeginCriticalAlertSheet(NSString *title, NSString *defaultButton, NSString *alternateButton, NSString *otherButton, NSWindow *docWindow, id modalDelegate, SEL didEndSelector, SEL didDismissSelector, void *contextInfo, NSString *msg, ...)NSBeginInformationalAlertSheet(NSString *title, NSString *defaultButton, NSString *alternateButton, NSString *otherButton, NSWindow *docWindow, id modalDelegate, SEL didEndSelector, SEL didDismissSelector, void *contextInfo, NSString *msg, ...);Hey, wait a minute... these functions have more parameters! I thought you said sheets were
easier to use? Well yes, they are, for the user. For you: more work. However, the payoff is happy users. And you can't put a price on that. Well, you can, actually: it's about $40, which is pretty sweet.
The big difference in running sheets vs. running panels is that
these sheet functions return immediately, before the user has responded to the panel. Note that they don't return an
int, they return
void: the great unknown. (If only we had Schrödinger's return type, which would represent the all and none of default and alternate and other.)
Now, this immediate return throws a lot of programmers of, and rightly so... I mean, obviously you are throwing up an alert because you want some feedback from the user, and so the _very_ next thing you want to do is going to be based on what the user decides, and yet here you are, unceremoniously dumped back into your code with no decisions in sight. What to do?
Well, there are two choices. One is, just immediately return from whatever method you're in, which should return control flow to the main even loop, and then wait for your
didEndSelector to eventually be called when the user makes up her mind. In that method, you'll be informed of her decision, and can proceed naturally.
The only downside to this is that if you've built up a lot of state information before popping up your alert sheet, you're going to have to regenerate it when you get called back
or you'll have to save it off in your instance variables. Bluck!
One way around this, to be used in extreme cases, is to just process events on your own, such as the following:
|
@implementation ExampleClass
- (IBAction)doSomethingCompletelyAmazingYetKindOfDangerous:(NSButton *)sender; { [some code that has a lot of state setup]
userChoice = INT32_MAX;
NSBeginInformationalAlertSheet(NSLocalizedString(@"I'm Going to do Something Big", @"alert title"), nil, nil, nil, [document windowForSheet], self, selector(_sheetDidEnd:returnCode:contextInfo:), nil, NULL, NSLocalizedString(@"It's going to be awesome. Seriously.", @"alert message"));
while (userChoice == INT32_MAX) { [NSApp sendEvent:[NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] NSModalPanelRunLoopMode dequeue:YES]]; }
if (userChoice == NSAlertDefaultReturn) { [some code that does the cool thing with the state we calculated at great expense] } }
@end
@implementation ExampleClass (Private)
- (void)_sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { userChoice = returnCode; }
@end |
In this example we assume
userChoice is an instance variable (quick quiz: why can't this be a static variable instead?) and is an int. I'm not particularly fond of having a one-use ivar like this, but, hey, not a lot of options - if the alternative is saving off four or five different pieces of state in ivars, this might be cleaner. Note that MOST of the time you're really going to want to do your actual processing in the
-_sheetDidEnd:..., so you can skip all the NSEvent crap and just return from your
-doSomething... method -- this is just an example of how you might work around a situation where you really don't want to return from your action method before you finish what you're doing.
Update: Pierre Lebeaupin (the pretty pin?) correctly pointed out that my contrived example above is, in fact, wrong in the case of document-modal sheets (see the comments page). I tried to be weaselly in this example by saying, "I'd only use this in extreme cases," to express that I've never used [[sendEvent:...]nextEvent:...] hack in this way, but it'd be something I might try (and then, hopefully, discovered the bug Pierre found).
What I was
trying to accomplish was come up with a concise way to introduce sub-event loops, which are actually a useful tool in some extreme situations. In fact, this post was inspired by one of my programmers not knowing about them when trying to work around some problems with printing, so I tried to blend my discussion of sheets and event subloops into one mélange, but, in fact, my example was flawed. Pierre pointed out a fundamental problem that, in fact, early Mac OS X apps all suffered from, because early on Apple hadn't thought of the case where you pop up two sheets in two different documents. Apple added the asynchronous APIs just for this case.
Here's a real example of when you might use your own event subloop, which
may in fact still have problems of its own (we just started doing this in our code), but I assert it does not due to the inherent limitations of the print system.
Right now, the way we print in Delicious Library 2 is to create a WebView (from Apple's WebKit) and populate it with your books &c, then just tell the WebView to print itself. Clever, no? The only problem is that WebViews will NOT load images synchronously (grrrrrr) even if they are local images and even though it would be completely trivial to do so (eg, NSURLConnection has a +sendSynchronousRequest:... method). So, if you're in the middle of running a print sheet for a document, and you create a WebView based on user input, and then want to print it, you need to spin on the event loop letting all the images load asynchronously before you try to print the WebView. Since there appears to only be one possible print operation per application, I believe this to be a valid use of a event subloop. In our case, the code looked like this:
|
- (void)_setupWebViewThumbnailBasedOnUserSettings; { allImagesHaveLoaded = NO;
while (!allImagesHaveLoaded) { NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate dateWithTimeIntervalSinceNow:0.05] inMode:NSModalPanelRunLoopMode dequeue:YES] if (nextEvent) [NSApp sendEvent:nextEvent]; }
// elsewhere, we have code that sets 'allImagesHaveLoaded' when... well, you know } |
Note that we are, in fact, polling in the above loop, which is normally a no-no, but we are forced to because we're not guaranteed an event will be sent
after the last image loads in our WebView, we don't want the user to have to 'bump the mouse' to get the print panel to finish. (This kind of mistake is very common when writing event loops, and in fact you can see it in a lot of shipping apps -- for instance, some older apps force you to keep moving the mouse to keep autoscrolling; if you stop moving, you stop scrolling, because events aren't being processed and they didn't set a separate timer event like the Apple docs said.)
In this case, we don't mind the polling so much because we know we'll stop once we've loaded a very small number of images from a local disk, and because there's really not much else the user is planning to do in this quarter-of-a-second (switch applications and start and a compile? not likely.) Since the end of the polling is NOT determined by user input, we aren't in a situation where the user could wander away from her computer while it is polling while waiting for her and come back to find her MacBook slagged from overheating.
--
O'Reilly has written a whole article about running alert sheets, which you can read for a lot more detail than I would ever provide you with, ever. Finally, you should check out the (relatively) new
NSAlert class, which is slightly more complex to use (you have to create the object and THEN show it) but provides an objective API onto all this.
--
Ok, now we've handled the case of pulling down a simple yes/no/maybe style sheet with two lines of text and an icon. What if you want to pull down a sheet with radio buttons and tables and all kinds of fun widgets, that performs a relatively complex task for the user (like, say, configuring rules for a smart shelf)? Here's an example sheet from the AppKit, of setting up a fax. (Note that if you want to do
exactly what's on this sheet, you should, you know, just send a fax.)

Well, here's what I consider to be the current best practice for sheets that have a ton of logic and interaction. The overview is, you create an NSWindowController subclass to handle loading and unloading the NIB with the sheet in it (remember that a sheet is just a standard window to Interface Builder), and then provide a single, beautiful class method as its API:
|
@interface LIShelfCreationSheetWindowController : NSWindowController { NSDocument *document; // [add some ivars and outlets and stuff] }
// Class API + (void)runModalSheetlToCreateShelfForDocument:(NSDocument *)document; // actions - (IBAction)addShelf:(id)sender; - (IBAction)cancel:(id)sender;
@end
[...new file...]
@implementation LIShelfCreationSheetWindowController
// Class API
+ (void)runModalSheetlToCreateShelfForDocument:(NSDocument *)document; { LIShelfCreationSheetWindowController *shelfCreationSheetWindowController = [[self alloc] _initWithDocument:document];
[NSApp beginSheet:[shelfCreationSheetWindowController window] modalForWindow:[document windowForSheet] modalDelegate:shelfCreationSheetWindowController didEndSelector:@selector(_sheetDidEnd:returnCode:contextInfo:) contextInfo:nil]; [[shelfCreationSheetWindowController window] makeKeyAndOrderFront:nil]; // redundant but cleaner }
// actions
- (IBAction)addShelf:(id)sender; { // // actually create the shelf, based on the user's settings on the sheet, which are bound to our ivars // [NSApp endSheet:[self window] returnCode:0]; // stop the app's modality }
- (IBAction)cancel:(id)sender; { [NSApp endSheet:[self window] returnCode:0]; // stop the app's modality }
@end
@implementation LIShelfCreationSheetWindowController (Private)
// Init and dealloc
- (id)_initWithDocument:(NSDocument *)aDocument; { if (![self initWithWindowNibName:NSStringFromClass([self class])]) return nil; document = [aDocument retain]; return self; }
- (void)dealloc; { [document release]; [super dealloc]; }
// private callbacks
- (void)_sheetDidEnd:(NSWindow *)sheetWindow returnCode:(int)returnCode contextInfo:(void *)contextInfo; // [NSApp beginSheet:...] { [sheetWindow orderOut:nil]; // hide sheet [self autorelease]; }
@end |
Things to note here: this class has a backpointer to your NSDocument, so it can get at any relevant state it needs without you having to passed it in explicitly as parameters to the class method (in real code you'd replace the word "NSDocument" with the name of your NSDocument subclass). For instance, we immediately need to figure out which window will sport our shiny new sheet, and we use NSDocument's built-in and conveniently titled
-windowForSheet method.
In NIB you'd hook up buttons to invoke the
-addShelf: and
-cancel: actions, and you'd using bindings to keep your instance variables in sync with the tables and radio buttons and other widgets in the UI, so you can just access your ivars directly in
-addShelf: when the rubber hits the code.
I prefer having a separate class to run the sheet because I've found there can be a lot of cruft code for configuring the UI and performing some action, and it's nice to offload your NSDocument subclass of that kind of code. In NSDocument, your original action method is going to be very small (one line!), which is going to make it very readable.
--
This shell of a class isn't intended to evoke a "Genius!" reaction from the gentle reader: hopefully, in fact, you are muttering, "That seems pretty obvious." Because the correct way to write code should always seem obvious, in retrospect. Years from now, when you're looking at your code, you should say, "Of course I wrote it that way -- there's no other decent way to do it," and not, "What the hell was I thinking?"
Labels: code