January 19, 2006

Pimp My Code, Part 7: Pimplette?

Here's a little code-McNugget. Recently someone wrote:
Delicious Library is a great program and I admire the ways you've customized a lot of the controls. It's always the little things that obsess me, specifically the way NSTextField in the details view in EDIT mode loses focus/committs the edit when the user mouses down anywhere it's bounding box. I've managed to produce a similar effect by adding this mouseDown method to the NSBox my NSTextField is held in.

How did you solve it? Here's my code for reference:
-mouseDown:, Original Code
- (void)mouseDown:(NSEvent *)theEvent {

[self acceptsFirstResponder];
[[self window] makeFirstResponder:self]; // I can make myself accept firstResponder
but I have to get the parent window to make it so.
// Not sure why this works but it made sense when I typed it.
[super mouseDown:theEvent];
Ok, so, restated, the question is, what code did I add to the view that contains my NSTextFields so that, when I click just outside the NSTextField (and in the custom view around them) the NSTextFields stop any editing they may be doing?

Now, I don't get off on being mean, but sometimes I feel like Lisa Simpson as Sacagawea in Margical History Tour, speaking to the new American settlers who are eating berries, rubbing leaves on themselves, and trying to tie their belts: "These berries are poisonous, these leaves are poisonous, and your belt is a snake... which is also poisonous."

To be fair, this person did write me about this method asking for help, but, seriously, whenever I see a comment that says, "Not sure why this works..." I die a little inside. It happens a lot, which explains why I can't move most of the left side of my body any more (and why I'm still single).

ANYways, there's nothing in the original code that can be saved. First off, don't call [self acceptsFirstResponder]. It doesn't have any side-effects, and it just returns 'YES' or 'NO'. So, essentially this method as written is:

-mouseDown:, Original Code, translated
- (void)mouseDown:(NSEvent *)theEvent {

[[self window] makeFirstResponder:self]; // I can make myself accept firstResponder
but I have to get the parent window to make it so.
// Not sure why this works but it made sense when I typed it.
[super mouseDown:theEvent];

Also, don't call [super mouseDown:], because it's documented in NSResponder thusly: NSResponder’s implementation simply passes this message to the next responder. You don't actually WANT this message to be passed on, because you've already acted on it. In this case, it'll do nothing, but you're wasting time and lines of code for no reason.

So, that leaves us with only one line, the middle one, which is a snake, and poisonous. Ok, it's not a snake. But it's not really correct. What you want to do if you want to de-focus an actively editing NSTextView is tell the NSWindow to set the first responder to nil, not self. In this case, you're just a simple NSView subclass, so it may not really hurt anything for you to be responding to events as they come out, but it's confusing and less efficient and it sure doesn't help. And, it could screw up the focus ring and tab-to-next field editing to set yourself as the first responder.

The other problem with the middle line is that you don't actually check to make sure the first responder IS something you want to de-focus before you steal the focus, which isn't too friendly of you. I mean, if the first responder is just a popup (keyboard navigation is on), it's kind of mean to steal it when it wasn't hurting anyone.

So, here we have the pimped-out method, straight from Delicious Library. Yup, I'm giving away my production code, folks! Get it while it's hot! This crap won awards, you know!

-mouseDown:, Pimped
// NSResponder subclass

- (void)mouseDown:(NSEvent *)theEvent;
NSWindow *window = [self window];
if ([[window firstResponder] isKindOfClass:[NSText class]])
[window makeFirstResponder:nil];



Anonymous Anonymous said...

I have no idea why this works...

...but it does.

-(NSString *)pimpThis:(NSString *)code
HelpEmail *email=[[HelpEmail alloc] initWithCode:code];
[email sendTo: wilShipley];

return [email waitForResponse:HE_PIMPED_CODE];

Any clues? :-)

January 19, 2006 7:36 PM

Anonymous Anonymous said...

P.s. - yes, there is a memory leak in that...

January 19, 2006 7:38 PM

Anonymous Anonymous said...

How about a new segment called "Debug My Code" where you can figure why the crap NSTask locks up when it launches?! ARGH!


January 19, 2006 8:00 PM

Blogger Wil Shipley said...

If you mail me your code I might be able to debug it. I certainly can't just guess. NSTask has worked for the applications in which I've used it.

January 19, 2006 8:07 PM

Blogger Abhi Beckert said...

Hmm, I would have thought creating the window variable is a waste of code by your standards.

January 19, 2006 8:14 PM

Anonymous Anonymous said...

Yeah, I wondered about that too. Why declare the "window" variable instead of simply saying [self window] twice? I wouldn't expect it to be any slower, since this is only called once per mouse click, and the [self window] message only gets sent twice if a text field was first responder, right?

January 19, 2006 9:01 PM

Blogger Wil Shipley said...

I just felt that calling [self window] in the if () made the nesting a bit deep, which made it harder to read.

It's totally your call; there's no real efficiency to be won here.

January 19, 2006 11:22 PM

Blogger Abhi Beckert said...

Just trying to pick your brains. ;)

Next question, I seem to recal a post by you about never using generic variable names like window...

I guess your "rules" (for lack of a better term) aren't as strict as you make them out to be.:p

January 20, 2006 12:12 AM

Anonymous Tomas Jogin said...

I really like your writing style, Wil. Not the coding style I mean, the writing style. I get a vague impression that you know your shit when it comes to Obj-C and Cocoa, but I'm a Ruby-programmer myself so I wouldn't know if or when you fucked that up. Anyways, love the writing style; like, the Simpsons reference, that's just pure genious right there. If Jerry Holkins ever gets the flu (or gets his hands cut off, or dies in a horrible traffic accident involving heaps of kittens and an old but very lovely lady), Mike Krahulik should give you a call.

January 20, 2006 2:19 AM

Blogger Wil Shipley said...

tomas: Thanks! Jerry's a cool guy; we gave him a 2-foot mall-special Christmas sausage at the Child's Play dinner. I'd hate for anything to happen to him before he finishes eating it.

abhi: Generic variable names like "index" are not useful. However, when a view has a window, the most concise and descriptive name for that is "window". Some people use "myWindow" but I think that convention is lame beyond belief.

My convention inside a method is usually if I am caching the value of a method I call on myself, I just name the variable the same as the method. It's very clear that way. bounds = [self bounds]. No questions.

January 20, 2006 2:25 AM

Anonymous Anonymous said...

Sweet, now about that book cover....

January 20, 2006 3:20 AM

Anonymous Anonymous said...

You forgot to mention Drew Hamlin left Delicious Monster...

January 20, 2006 6:11 AM

Anonymous Jonathan said...

OMG! Is Drew going to Apple too?

If so what will happen to Wil, after release of Delicious Library is he going to Google? :)

P.S. Your pimping entries are so cool. I've learned me a lot.

January 20, 2006 8:42 AM

Anonymous Piet said...

And so the open sourcingness of Delicious Libary begins

(one can dream...)

January 20, 2006 9:19 AM

Anonymous Anonymous said...

I too agree with tomas. wil, you have an execellent writing style. I started reading your blog to gain incite from an successful cocoa developer, but I keep coming back and reading every article because I enojy it!

Keep those sarcastic, tounge-in-cheek comments and the simple, powerful code coming!


January 20, 2006 2:48 PM

Blogger Wil Shipley said...

I didn't actually forget to mention Drew left, I just didn't think one of my customer support guys quitting was germane to a discussion of mouseDown:. Silly me!

I don't know if Drew's going to Apple; I assume he's going to finish college. We've already hired his replacement (two days before, interestingly), so it shouldn't hurt our customer service any.

January 20, 2006 4:04 PM

Anonymous Anonymous said...

myWindow, theirWindow, somebodyElsesWindow... Or, just plain, ordinary, ol' window.

Like Charlie Brown, simple, but suprisingly insightful...


January 21, 2006 12:48 AM

Anonymous Andrew Hamann said...

So tell us the truth Wil. Is pimpin' easy?

January 21, 2006 6:58 PM

Anonymous Anonymous said...

Maybe you should write a book.

January 23, 2006 6:47 AM

Anonymous Anonymous said...

I have this insider information: nothing is done for version 2.0. Wil is procrastinating.

January 23, 2006 3:25 PM

Blogger Mark Stultz said...

This comment has been removed by a blog administrator.

January 23, 2006 3:53 PM

Blogger Mark Stultz said...

I saw the same code in iMediaMan.

January 23, 2006 3:56 PM


Post a Comment

<< Home