Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2006-120
$eventId
ID of event: wwdc2006
$eventContentId
ID of session without event part: 120
$eventShortId
Shortened ID of event: wwdc06
$year
Year of session: 2006
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC06 • Session 120

Beyond Buttons and Sliders: Complex Controls in Cocoa

Application Technologies • 56:36

Learn how you can quickly and efficiently create sophisticated Cocoa user interface elements for your application. Get practical guidance on how to extend Cocoa control objects like the table view and outline view using your own custom cells, and how to customize menus in a variety of ways.

Speakers: Corbin Dunn, Peter Ammon

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Welcome to Beyond Buttons and Sliders: Complex Controls in Cocoa. My name's Corbin Dunn. I work in AppKit at Apple. I'm a software engineer. And this is a hands-on talk, but unfortunately the sample code isn't available to download yet. There's been a little snafu with it. This is a great talk, and you can follow along with me and you'll check it out. We'll get the sample code out for you, and you can come to the lab. They made a typo there. I should say AppKit, not Apple Lab.

A little bit too much of a spell checking going on. But tomorrow at 2 o'clock, come to the lab. You can find me or any of our other developers. We can get you the source if you don't have it. Keep going to the session 120 page and hitting reload. And eventually, hopefully, it'll appear up there. Okay, complex controls in Cocoa. So what does that mean? What I'm going to talk about is how to create a custom cell. And I'm going to go over four basic operations with the cell.

I'm sorry about this. I'm going to talk about custom drawing, custom tracking in the cell, custom hit testing, which is something new on Leopard that you need to do, and custom editing of a cell. In addition, what's a complex cell without a complex control? So I'm going to take the complex control in this outline view, subclass it, and we're going to add some cool rollover highlighting to it.

In addition, we have a cool new control in Leopard called Innis Rule Editor and Innis Predicate Editor. And we're going to go over how to create custom searches using Innis Predicate Editor and give an overview of Innis Rule Editor. So let's just dump right into this source code and start taking a look at the demo. Let's go to demo.

This is the photo search sample which you will eventually have. I'm going to go ahead and do a build, a debug, or sorry, build, build and go breakpoints off. And this is going to run the app, and I'm going to show you what it is. So what we see here I'm going to go ahead and hide everything else.

This is our photo search sample application. What you see at the top is this new Innes rule editor control, which we're going to talk about. It's also Innes predicate editor as a subclass of it. I'm going to go ahead and do a search. I'm going to search for Corbin, just because I like that name.

And what you see in here are search results inside of an outline view. The outline view is using some new leopard features. It's got this group row look and kind of a spotlight-like look and spans across all the columns. We have a custom cell here, which we're going to talk about how to create. Custom cell is an image, some title, a little subtitle. This outline view has rollover highlighting.

It's going to be easy to go ahead and add this rollover highlighting with a custom subclass of outline view. I'm going to show you how to do that. You can double click on things. And it will go ahead and open up that image in preview. If you hit the little eye when you roll over it, it's going to do a finder info thing on it.

We're going to discuss this rule editor, how it creates these new rules and rows. In addition, some other things that you see in here. At the bottom, we have a new, in Leopard, NSPathControl. I'm not going to talk about it, but you can just take a look at it and see that source code and how to use it.

Another thing is you may have seen the ability to add any type of view to a menu. And in here, you can just put about a game, a Quartz Composer window, anything in a menu there. We aren't going to discuss it, but you'll have the source code to see how to do it. All right, let's go back to slides.

So this cell, there's a class called ImagePreviewCell, and inside the cell we're going to take this nice big rectangle and just break it up into smaller pieces. That image there, we're going to override the standard NSL method called ImageRecForBounds and return that custom location for our image. Another NSL method that's already there is TitleRecForBounds. We're going to override that to return just that little title location that we want to draw the title out.

We want to add some extra things to this cell, so we'll have another method called Subtitle Rect for Bounds, which will return just the subtitle area. Finally, that cool little info button where you mouse over and it does the rolling highlight thing, we'll have another method called Info Button for Bounds that returns that rect for the info button. All this code is in the imagepreviewcell.in file, which hopefully you'll take a look at after the session. Why are we going to do this? It's going to make drawing really easy, because we know all the locations to draw at.

Hit testing is going to be very easy because we have all the portions broken up. Editing is going to be easy because we know we want to edit just that little title area and nothing else. Tracking is going to be easy. So when you hit that little iInfo button, it's going to act like a button and track, and only in that portion. Let's go back over to the demo and dive into some of this code.

All right, inside of Xcode, if you are following along and maybe one of the three people who have the source code, you can open up the Photo Search demo app and you can expand the classes. And in here there's a custom cells and controls, and there's ImagePreviewCell.m. If you are following along, hopefully you have it open.

And I'm going to go ahead and collapse the big Apple header because I really don't want to look at it. And up here at the start of the file, we see a bunch of defines. These turn on individual features in this file. So I'm going to go ahead and turn these off by making it pound hit define hit test zero. And it's going to just turn off a bunch of features.

And we're going to turn them back on one at a time and see what they do and how they work. I'm going to hit Command F and we're going to search for draw int. And if you're following along make sure that ignore case is checked and hit next. And what that will do is it will take you to the method draw interior with frame.

At the start of this, we see a bunch of code that determines if we should be highlighted or not inside the table view. Take a look at that on your own and you can figure out how to draw with the blue background and the white text. But below that, what we see, If I went a little too far. We have Image Rect for bounds.

So we took that big rectangle and broke it up, and that gives us that Image Rect. And then what are we going to do? We're going to take our Image IVAR and just draw it in that Rect. So the advantage of breaking that rectangle up into little pieces made drawing really easy.

Below that we have Title Rect for bounds. So we took the Title Rect and we're going to do the same thing: Draw in Rect. Below that, we grab our attributed subtitle string. We grab the attribute subtitle rect. And what are we going to do? It's going to be another draw on rect. This is all very simple stuff that you can do in Tiger today.

[Transcript missing]

a little click happy, and finally at the bottom InfoRect button for bounds where it grabs that little info button, and then all that is, is an image. It's not any fancy button or anything. And so we grab the info button image and do a draw and rect of that, too.

So let's take a look at this application with all the drawing stuff enabled. So I'm going to go ahead and do debug or build. Build and go breakpoints off inside Xcode, and this will build the project and quickly run it and launch it. I'm going to do a search for Corbin again and hit enter.

And so this did a search here. Now all the drawing is happening and working correctly, but let's take a look at what happens when we don't implement some of the other events and methods correctly. Like let's say I want to just single click right there on that image. What's going to happen? So I single click on it, and it starts an editing session.

Now notice the editing session is way too big, and that's not what we want. We also don't want to start the editing session when you click on the title. And let me step back for a second. On Leopard, we enhanced the in-as-table view. You no longer have to do a double click to start editing a cell's contents. You can just single click right on the title, just like in Finder, and it'll start the editing session.

See, what else is wrong with this? Well, when I roll over that eye, it was doing that little highlight. That's not working anymore. And if I click the eye, well, it should start the little Show Info thing, but it's doing an editing session. So those are some problems which we want to fix. So I'm going to go ahead and quit that app.

And right below that drawing method, you will see this defined, which we turned off, #if hit test. I'm going to go ahead and turn it back on by commenting it to be #if1 to enable the code. Hit test for event. What is this method? It's new in Leopard, so let me go ahead and open up the header for NSL and take a look at it. If you are following along, you don't have to do this. I'm just going to pop up and open up NSL.h.

and look for hit test in this file. So inside of NSL.h, we have a new category for the hit test method. We have hit test for event. So we're passed in an event of where the hit testing should happen. An event has a location in the window. It has a particular rect, which is the cell frame that you're in, because in a table your frame might be different.

And it also has the control view that happened in past to it. So what can you return for this, and why is this important? Well, above it are the things you can return. These things you can OR together to return more than one result. So first thing, if you didn't hit anything, you can just return NSCellHit: None. So you didn't hit any content or nothing at all.

If you hit a content area, you'll return NSCellHit: Content Area. But that's not too interesting. Where it starts to get interesting is when you want to begin editing text. So if you hit text, you want to return NSCellHit: Editable Text Area. And that will start the editing session.

In addition, when we hit the little iInfo button, we want to be tracking right then, because you want to do mouse tracking to make it actually work like a button. So for that little portion, you want to return NSCellHitTrackableArea. And all these are keys to the table view and outline view to do certain things with your complex cell. So I'm going to go ahead and close that file, and let's take a look at that method.

If I can find it again. So it's under the draw interior with frame. We see the hit test for event. So what are we doing here? The first thing we do is we take that event's location, and we want to convert into coordinates that we know about. So I call convert point from a view of nil. So it's converting from the window's coordinates to our coordinates of the control view. And that gives us a point that's in our coordinate system.

Then again, we took that big rect, and we broke it up into those small little pieces for the purpose of hit testing and drawing. So we can just do a NSMouse in rect to see if that points in our rect. And if it is in the title rect, well, that's a content area. And we're going to go ahead and or that with the hit editable text area. So that way, it'll start an editing session when you click on that and only that area.

The image rect, well, we grab that image rect. And that's just a content area. It won't be anything fancy. Below that, we grab the attribute subtitle, grab the rect for it, and then again do a mouse in rect. Return that to content area. Simple enough. But what about that little info button? We grab the info button rect, do a mouse in rect.

It's also a content area. But more importantly, it's a cell hit trackable area. So when you click on that little info button, it's going to tell the cell that it needs to start tracking. And so the table view will call you back and start a tracking in the side of the cell.

And then the default's going to be NSL hit none, meaning you didn't hit anything else. So I'm going to go ahead and save this file. And I'm going to do build and go breakpoints off to compile that with that hit test for event method. And let's see how well that works. So I do another search.

And now before, if I single clicked on that image, it started an editing session. So I'm going to go ahead and quit that. And I'm going to go ahead and do a single click on that. And it's not doing an editing session. So that's working like we want to. Now if I single click on the title, it starts an editing session. That's excellent. It's still too big.

We'll fix that next. And then what about the info button? Well, the rollover highlight isn't working yet. But if I click on the single eye, it should start tracking. We didn't implement tracking code yet, so it's not doing anything. that. Now below that, we see another method, another defined #ifeditframe. I'm going to turn this back on by making it #if1. And if you happen to be following along, hopefully everyone has this change to being #if1 and commenting out the edit frame portion.

Edit with Frame, In View, Editor, Delegate, Event is a method that's been around for a while, and it lets you begin an editing session. So what we're going to do is we only want to edit the title rect. So again, we take that big broken up cell, grab that title rect, and we slightly tweak it to kind of make it look nicer. And this was just something that was done by experimentation. And then we call a super edit with frame passing in that title rect. So what this will do, it'll force the editing session just to be that smaller title rect portion and nothing else.

Now below that is select with frame, which is implemented exactly like edit with frame, calling the super, passing in the title rect. Now why do we have to implement both of these? Well, in this table view and in this outline view, we'll call select with frame or edit with frame depending on what type of cell you are. So if you are an in this text field cell or a subclass thereof, like what this cell is, then it will call select with frame.

And what's that telling you to do is it'll start the editing session and it'll select the text all at once. So you have to implement both to make it work properly. So I'm going to go ahead and do build, build and go breakpoints off, and we'll take a look and see what happens when we properly implement that.

So I search for Corbin or something again. So what this application is actually doing, I forgot to mention that, it's using Spotlight and it's generating a Spotlight query search to do a search for images only on my system. So now, if I single click right on the title there, it begins an editing session and it's editing exactly the portion we want to edit. Super.

Get practical guidance on how to extend Cocoa to your own custom cells, and how to customize menus in a variety of ways. The first method is a class method: prefers tracking until mouse up. So if your cell wants to track when you mouse down up until you mouse up, you need to return yes from this class method.

If you don't, you're going to have problems, which I can mention to you later offline. Feel free to find me in the lab. But the bottom line is if you track mouse until it's up, return yes from this method. By default, in this cell, return is no. So if you don't override this, you may have some subtle bugs in your cell subclass.

Now for tracking, there are several points that you can override tracking. We're going to override it on the highest level inside of NSL. We're going to override track mouse, in-rect, of view, until mouse up. So this is called by the table view or outline view, or even a regular control, when you should begin tracking inside of your cell. And what are we going to do in this? Well, we only want to track in that Info Button. So we go and grab the Info Button Rack for the bounds, and we're going to do a big while loop.

And what are we going to loop for? Well, we're going to do a while loop until we get the mouse up. So we're looking for the event that is in this left mouse up. Now this is a very simple event tracking mechanism. It does the same taking of the point, converting it to our local coordinates for the cell and the control. It does an NSMouse and REC to find if that point that you're in right then with the mouse is in that info button.

And if it is, it sets this internal IVAR called Mouse Down In Info Button. And it calls a control view to re-display in that cell. So what this is going to do is it's going to make that little eye go from the normal looking to the down press look by telling the IVAR used to draw that image to draw with a darker image.

Then it also checks for a mouse entered and mouse exited events and uses NSApp SendEvent to dispatch out some extra events that we want to process. Finally, the key to the whole event loop portion, tracking portion, is grabbing the Controls window and doing Next Event Matching Mask for the, well, we want to know if the left mouse has been up, the left mouse has been dragged, or a mouse was entered or exited from our little racks, which I'll talk about in a bit too.

So what do we do at the end of this? Well, if that mouse was down in the Info button, we want to send a special action out to do some special event. In this particular app's case, it opens up that little info window in Finder. So if the mouse was down, we reset the flag, and then we call NSApp send action to send an internally set action for it.

Key portion from TrackMouse is what you have to return from this, yes or no. It's kind of confusing if you don't know at first. And from TrackMouse, you have to return yes if you process the mouse up. If you don't return yes, you will have more subtle bugs where the table view or outline view will think that it should still track because you didn't return yes. Normally it will stop when you return yes.

All right, so let's go ahead and take a look at this by doing another build, build and go breakpoints off, and see it working and see if that info button can then be clicked on. So I'm going to do another search. So I clicked the little iInfo button, and it tracked, and it went and sent the action, and showed the little info window. All right, super. So let's go back to slides.

So a quick review of what we just talked about. By taking that big wreck and breaking it up, it made drawing to be really easy. Hit testing was really easy, editing was easy, and tracking was easy. Now, something else new to Leopard: Tracking Areas, which are on NSView. NSView has a new method called UpdateTrackingAreas to add an NSTrackingArea. NSTrackingAreas, they are basically the replacement for TrackingRex on Leopard, and they're a lot easier to use.

So here's a snippet of some of the code from Leopard for NSView. And what you see here is you see Add Tracking Area, where it's going to add an NSTrackingArea, Remove Tracking Area, and a Tracking Areas array. So this is an array of all the tracking areas inside the view.

In addition, there's Update Tracking Areas. The only method that you will probably override here is Update Tracking Areas. Before, you'd have to know when your reqs were moved, if you should add, if you should re-add the tracking reqs, if your frame moved. It was really a little bit difficult inside of Tiger to figure out when to do this and to do it correctly. Now you have one override point to do it, and it makes it very easy. So this is in NSView.h.

In this tracking area is this new class to Leopard, so let's take a look at it. Here's a snippet from its header. What you see here is the initializer, init with rect, so you pass in a little tracking area rect that you want to be tracking, some options, which I'll discuss in a minute, an owner, so the owner is the person who's going to get the actual events that happen for your tracking area, and a user info dictionary to pass in some state information that you want to keep track of.

So what options are we going to use? We have some NS Tracking Area options. We want to be enabled during mouse drag, so we pass in NS Tracking enabled during mouse drag. We OR it with NS Tracking mouse entered and exited, because the events that we want to get are entered and exited events. And then we want to always track, so we add NS Tracking active always.

If you don't have this flag, and there are other flags, you can control if it tracks when your window is key or when it's not key, etc. You can take a look at those extra flags. So having these options causes the owner to give it mouse entered and mouse exited.

This code is in its tracking area, dot h. It's another typo there, copy and paste error. And all right, tracking areas, great. How are we going to actually use that in our sample code in our demo app? We want to automatically add tracking areas for cells. So we're going to define a contract for the cell to adhere to. And here is a snippet of that contract that we are defining. We're going to have this category, add tracking areas for view, interact with user info at a particular mouse location, and then the cell will want to get mouse entered and mouse exited events.

So this is a contract which we're defining for any cell to implement. This is in cell tracking rec, dot h, in the sample code, and image preview cell, dot m, for an implementation. So again, this is a hands-on talk, so let's dive back into the sample code and take a look at this.

So underneath that last method that we just took a look at, there's another defined, Pound If Tracking Area. So let's do the same thing of turning it back on by making it #f1 and calling out the other portion. So this, we will show the code for the outline view subclass, but it will automatically call us, telling us to add our own tracking areas. So we implemented this contract we defined, add tracking areas for view.

What do we want to track? Well, when you mouse over that info button, we want to highlight. So we grab that info button rect, and we want it to be, well, we discussed the options, we enabled during drag, enabled during, we want to get the mouse entered and mouse exited events, and we want to always be active. In addition, we do an NSMouseInRect to see if that mouse location is in the info button rect, and if it is, we add NSTrackingAssume inside. So that means that we'll start out in our tracking rect at that point, tracking area, sorry.

Then what do we do? We call NSTrackingAreaAlloc and use that initWithRect method, passing in the info button Rect, passing in the options. The owner who's going to get the events isn't going to be the cell, but it needs to be the control. So the control is going to get that event, and we're passing in some user info that was kind of just passed through to us. Then we call ControlView, add tracking area to add the tracking area, and then release it.

So that means the control is going to call our mouse entered and mouse exit events. And what we're going to do is we're just going to set an IVAR indicating that we're in that hovered info button and that we'll draw with a different color rect at that point, or different info button image.

Now, if you know how table view and outline view works, if you set this flag-- well, table view uses one cell and kind of just stamps it out on each row over and over again. So if you think about it, if you set this flag, won't it stamp out that same cell with that same flag on each row? Normally, yes. But we'll talk about how to actually make that work properly by subclassing outline view. Mouse exited just undoes what mouse entered did.

So let's go ahead and do build, build and go, breakpoints off, and take a look at what happens when we implement this event, or method. Let me do another search. And now if I hover over the Info button there, I wave the mouse over it, it turns to a different image, kind of a grayish eye Info button, which is what we want. Super. Okay, let's go back to slides.

So how are we going to finish up adding these automatic tracking areas for the cells? Well, the control has to do some work. So we're going to subclass in this outline view and automatically add tracking areas for each cell that we can see. What are we going to do? We're going to override that new NSView method in Leopard called UpdateTrackingAreas. So it's the one portion where we need to actually add our tracking areas.

Now, any tracking area that was added, we have to explicitly remove. So we'll loop through and remove our existing tracking areas. And now what you see here in the first line is 4 in its tracking area and in its self-tracking areas. That's using the new for-in syntax there to kind of do a quick looping.

And we have to only add one remove tracking areas that we added. If we remove arbitrary tracking areas, you don't want to do that because it's possible that AppKit at some portion or some time may want to add tracking areas. So we'll show you how to remove just yours. Next is a little for loop, which is kind of some pseudocode there. For each visible row and column, it grabs the cell. Now, what is it going to grab? New in Leopard is a new table view and outline view method called prepared cell at column row.

That's going to give us a fully formed, ready-to-use cell that will be drawn with, that has bindings information automatically filled into it. It can be used for tracking, drawing, and various other things. So we're going to grab that fully formed cell, fully prepared cell, and we're going to call that new method, that new contract that we defined, add tracking areas for view in Rect with the user info at a mouse location. This code is in the subclass trackable_outline_view.m. And again, hands-on talk, so let's dive back into this code and take a look at that.

So I'm going to go ahead and open up trackableoutlineview.m. If you do happen to be following along, go ahead and open up trackableoutlineview.m. And I'm going to collapse the big old header. And if we scroll down a little bit, we see the update tracking areas method that I was just discussing.

It's doing that for loop, and we're going to remove tracking areas where the owner of the tracking area is us, and the user info has a special key which we are going to add, the row key. So if it has a row key in the user info, we know we added it. So we're going to go ahead and remove it.

Next, what are we going to do? We're going to grab the visible rows using table view's method rows and rect, and the visible columns using columns and rect. Another feature on Leopard is the ability to hide table columns. So we have to find out which columns are visible because you might have some hidden there.

We're going to find out that mouse location of the current event, and we're going to enumerate through all the rows. So we're going to go four of the rows in the first visible row and go through all of them. Now I'm going to ignore a little bit of code here where a full width cell, so cells can actually span across all columns now. So I'm going to ignore this bit of code which goes for the full width portion.

And we're going to look at the portion where it iterates through all the columns. It uses that new table view and outline view method, prepared cell at column, to grab the fully prepared cell, respond to selector to see if it actually adheres to our contract, add tracking areas for view.

And if it does, our user info that we're going to pass through to it is a dictionary which has the column, and a column key, and a row, and the row key. So we know what row and column this tracking area is going to be for. And then we call the cells method to actually add its tracking areas.

So if you recall, the owner was this actual view, and it's going to get the mouse entered and mouse exited events. So it's getting mouse entered. And here's the implementation of mouse entered. What it does is we dissect that information that we put inside of the user info to grab the row and column.

And if we do have a row and column, then it's definitely an event that we wanted. And we grab the integer value. Again, we use prepared cell at column to get that fully formed cell. We have an IVAR in this class called underbar mouse cell. We release the old one. We store off the column and the row where it happened at, and we copy that cell. Now, in just a second, you'll see a neat trick that we can do with that copied cell.

So this is going to, or then we call mouse cell mouse entered. So that's giving your cell the entered event where it can set up its own state. Now, right underneath this we see mouse exited, which basically undoes exactly what we did, and I'm not going to discuss that. But the key portion is that new method, preparedCellAtColumn. Well, if everything is going through this inside table view and outline view, not only can we call it, but we can also override it and do some special stuff.

So, if the row that it's asking for is that mouse row, and is that mouse column, we're going to return that copied cell. So that means table view is going to be stamping out these rows one at a time with that same cell, until it gets to the one where you did the mouse entered, and it's going to use your copied version, which maintains its own state, and then it'll go ahead and do the rest of the stuff. In addition, we also override UpdateCell, and we call setNeedsDisplayInRect if the cell it's updating is that particular one cell. So that gives us automatic tracking areas by some simple subclassing. Let's go back to some slides.

Okay, another cool thing in Leopard: Expansion Tooltips. What are expansion tooltips and how do you take advantage of them? Well, these are automatic for tables and browsers. Here's a little screenshot from Mail. You can see it has a title there where it's kind of truncated some text. So if you take your mouse and kind of hover it over there for just a second, now automatically what you will see is pop a cool tooltip right over what you saw will expand the text there. there.

Now we have a new NSL method allowing you developers to override it and customize what you see there. This will work automatically for all our cells which we actually ship and it will work pretty well with everyone else's custom cells, but sometimes you have to customize it. So the way you customize it is overriding ExpansionFrameWithFrame in view. So what you do here is, basically your cell is drawing in this little rect, and you're passing in that cell frame when ExpansionFrameWithFrame is called. Now if the frame is not big enough for you, you can return the frame that you need to draw in.

Then if you return that bigger frame, DrawWithExpansionFrame will then be called for you to draw in that big frame. And that lets you draw expanded. If you don't need to draw expanded, you can just return NSZeroRect and EmptyRect. And it will just not do that expanded stuff. So this is in NSL.h. And again, hands on, so let's just go ahead and dive back on into the code and take a look at that.

So I'm going to go ahead and open up the image preview cell.m file again. And if you are happening to follow along, hopefully you have image preview cell.m open by now, and you're taking a look at it. And below there, you see another define, which we're going to turn on this feature to look at it. Or actually, let's run with it off and see what it looks like. So if you don't implement this, what happens? So I'm going to do build and go, breakpoints off. And let's take a look and see what happens. So I do a search.

Now I resize that column to be small and hover over it. If I wait a second, ah, so the expansion frame popped up. Now it's drawing incorrectly. It's drawing our image. It's drawing that little info button. We really want to just pop up the bit of text, which you couldn't see.

But notice the rect it's using is actually the right size rect. It's just drawing the wrong stuff. So what InnoCell is going to do by default is it's going to call title rect for bounds and return that title rect. So we need to do some customization to actually make this work right. So, I'm going to go ahead and turn this feature on by making it #F1 and commenting out the expansion frame support portion.

Expansion Frame with Frame. This new method in Leopard, we just call it Supers Expansion Frame with Frame, and we check to see if it's empty, because if it was empty, it had enough space, and we're going to tweak it to make it just a couple pixels wider, or a couple points wider, actually. But the real key portion was the drawing was incorrect.

So inside of the new Leopard method, Draw with Expansion Frame, we're going to go ahead and just grab our Attributed String value for the cell, and we're just going to do Title Draw on Rect, and just draw the title there, because that's all we want to see. We don't want to see the image or anything else. So now if I go ahead and do Build and Go Breakpoints Off, let's see how well it works then. Do a search again.

I hover over it, and then it pops up, and it's the right size. Super. Let's go back to slides. So I discussed a bunch of stuff about NSL and NSOutlineView. I'm going to now turn you over to Peter Ammon, and he's going to talk about NSRuleEditor and NSPredicateEditor.

Hello. I'm Peter Ammon. I'm an engineer on the Cocoa team. I'm going to talk about NSRuleEditor and NSProductedEditor. We're going to talk about what we mean by rules. We're going to talk about some of the new classes we have in Cocoa for using rules. We're going to give a demo of them with the code we just saw. We'll talk about their APIs, and then we'll talk about localization of rules.

So what do we mean by a rule? Rule is when the user tells your application exactly what to find or to act on or to do. For example, if you've ever used Spotlight, sometimes there's really simple rules. You want to search for maybe files that contain rolling stones. You type it in the text field. That's a rule. Sometimes rules are more complicated. On Mac OS X, Mail has a rule called News from Apple for finding all the news, the emails that Apple sends you, and it looks like that.

So sometimes rules are for searching or filtering like these last two examples, but they aren't all. For example, the actions you perform on the messages you find are also a type of rule, even though they're not searching or filtering. And these are getting used more and more in Mac OS X, right? We have it in the Finder, we have it in the Open Panel, and now let's get it into your application. So we have two new classes. We have NSPredicateEditor and NSRuleEditor. And they look the same. They both look like that. It's the part at the top of the PhotoSearch application.

So how are they related? Well, NS Rule Editor subclass is NS Control, and NS Predicate Editor specializes Rule Editor for predicates. But they have the exact same user interface and the exact same localization mechanism. So how are they different? Why do we have two? Well, NSPredicate Editor is for editing NSPredicates. But NSRule Editor is for editing abstract rules. Your application is going to define what it means by a rule. and his Predicate Editor has a simple API which is focused on predicates. But Rule Editor has a more flexible and larger API. It's more difficult.

Predicate Editor has a push model. What we mean by that is you're going to set the available predicates on the Predicate Editor in a certain way. NS Rule Editor has a poll model. It's sort of like NS Toolbar. How does the Toolbar work? Well, the Toolbar has a delegate, and the Toolbar will ask your delegate for the Toolbar items and then arrange them in the view and allow the user to arrange them in the view.

Rule Editor works the same way. It's going to ask the delegate for the rule items and arrange them in the view and allow the user to arrange them in the view. And lastly, Predicate Editor has some support in Interface Builder, new in Leopard. Rule Editor does not have any yet. So let's see this in action. Can we switch over? Can we switch over? Okay, so I'm going to open up the main menu .nib in Interface Builder 3.

Okay, so let's see what we have in here. Let's go over to the outline mode. Within the window, We have a scroll view, another scroll view, and the view at the bottom. The view at the bottom is the path control, which is showing you where the photos are. If we expand the bottom scroll view, we have the predicate editor. Let's select that and just delete that entire view.

We'll recreate it. So in the library, under Tools, Show Library, we'll go to Data Views, and the Predicate Editor is this bubbly type control down here. So we'll select it and we'll drag it out, and we'll drop it there. Let's put it where we want it. "We'll take that and we'll drag it wide. Let's make sure that it's set to resize properly. View Inspector, excuse me.

Okay, so right now it comes preloaded with a couple of demo rules, right? There's age is less than, name is Rick. These are just samples. We want to replace them with our own. So in our tool to show inspector, We'll click on the row, and over here we see left expressions, operators, and right expressions.

So the left expression, we want to have a key path. We want this to be the file name. And the key path for the file name is something from Spotlight. It's KMDItemFSName. This is defined in the MD Item header. Now, what operators do we want? Well, we don't want less than or greater than because names, that doesn't make sense for names. We'll unclick those.

We want to have equal. We want to have begins with, ends with, and we want to do contains, but a sort of interesting property of Spotlight is you really want to have in. So we're saying the name is in whatever the user types, but that means contains. Now the Write Expressions has a short text field for numbers. We want that to be type of string, so we'll click on that and choose Strings.

And that row is finished. Select the second row. This will be for file kind. So a user can search for JPEGs or PNGs or GIFs. So in that case, the key path is KMDItemContentTypeTree, which is again defined in the MDItem header. We only want to have one operator for file kind. That's the equals. And for constant values, we're going to type in UTIs. So for JPEGs, the UTI is public.JPEG. And we'll have public.GIF and public.PNG.

Okay, well, the user doesn't want to see KMDItemFS name, right? The user wants to see file name, or maybe just name. So we're going to double click on the pop-up button, and we'll just change this to name. Because we had to choose "in" for the operator, it's in, so we're going to make that "contained" because that's what it really means, even though the operator is in. We'll drag that to the top so it's the default.

And for the second row, instead of content type tree, we'll call it kind. Instead of public.jpg, just jpg, gif, and png. Okay, so we're done configuring the predicate editor. All we have to do is hook it up now. So what I'm going to do is right click on it, and we're going to set the action.

I'm going to drag that over to the window controller. And that's going to set every time the Predicate Editor change, it's going to send its action and invoke the Predicate Editor change method. We also have to hook up the IVAR of the window controller, so I'm going to right click on that, choose the predicate editor, and click and point it at our predicate editor. So I'm going to save this. And if we did this right and I didn't mistype the spotlight names, then build and go breakpoints off. It should work just like it did before.

There it is. We'll type Corbin. Oh, well. Not sure what happened there. Likely I mistyped the names. So I'll show you some of the features of the Predicate Editor. You can, by clicking the plus buttons, you can add additional rows. You can, if you hold down Option, it's going to turn into a gear. You can add nested rows. You can drag and click them and rearrange them.

So you can make complicated, sophisticated compound predicates. And we can click and select a bunch and hit delete and they go away. Alright, let's go back to slides. So let's talk about the programmatic APIs for these new controls. So a rule, as we just saw, is made up of rows.

What's in a row? A row contains pop-up buttons. It contains text fields. It can contain any sort of view you want, really. And rows can also contain other rows, sub-rows, nested rows. And you can nest arbitrarily deeply if you want. So how do we get these views into the control? Where do they come from? Well, the answer is different for the two controls.

The Predicate Editor, they're coming from what's called as row templates. What's a row template? It's an instance of a new class, NSPredicateEditorRowTemplate. What's that? It's a mouthful. We're going to call it template for short. So in the screenshot here, A template is something that describes related predicates, in the sense they have the same form and the same views. For example, the middle three rows all have a date property as the left popup and then some operator and then a date picker as the right view.

These are all represented by the same template. But the bottom row is different. It's got three popups instead of two. It's got that extra text field in there. So that's going to be a different template. And even the compound row at the top is another template. So we have three templates for this view.

So how do you make a template? Well, you make a template the same way you make an NMS predicate, in a sense. How do you make a predicate? You're going to pass it an array of left expression, an operator, and a right expression. But with erode templates, you're going to pass it an array of left expressions, an array of operators, and an array of right expressions. So the available predicates are anything you can make from choosing one left expression, one operator, and one right expression.

So, for example, let's say you wanted to make a template that looks like this. Favorite movie is Fried Green Tomatoes. What we're going to do is we're going to start by making the array of left expressions. These are going to be key paths. For example, favorite movie or favorite food. We're going to make an array of operators. These are just NSNumbers. Excuse me. We're going to make the array of right expressions, which are the values the user can pick. And we'll have the array of operators, which are the is or contains for that.

So you're going to pass these all to Predicate Editor row template, and you're going to get a template that looks like that. So you're giving it the array of left expressions, the array of right expressions, a modifier such as direct or too many, just like NSPredicate, the array of operators, and no options here.

So what if you want, instead of a pop-up button on the right, you want a text field like this? How do you make that? Well, you're going to make the array of left expressions the same way you did before, but instead of an array of constant values, you're going to pass it an attribute type. In this case, we have a text field. We want a text field. We're going to pass it a string attribute type, so we get strings. And also the array of operators like before. You're going to call a different init method on NSPredicateEditorRowTemplate.

Go to path to the left expressions, the string attribute type, direct modifier again, the operators, and no options. That gets you a text field. If you pass it maybe date type, you get a date picker. You can also pass it number type to get the short text field.

So, okay, that's nice. What if you want to go beyond that? What if you want to have more than three views? What if you want to have some, handle some sophisticated subquery predicates that we're making available in Leopard? Well, you can do that by subclassing NSPredicate Editor row template. And you're going to override some of its methods.

So what are the interesting methods? Well, the first one is template views. This just returns the array of views that get put into the row. It's pretty simple. The next one is Match Your Predicate. This is how your template is going to indicate which predicates it's able to create and which it's able to display. So, Match Your Predicate returns a floating point value because it sometimes multiple templates can display a particular predicate. So whichever template returns the highest value is the one which is going to end up actually displaying it. It's saying it's best at displaying that predicate.

There's also set predicate, so when you receive the set predicate message, you want to configure your views to represent that predicate. And there's predicate with sub-predicates. When you get this, you want to return the predicate represented by your views and also the sub-predicates which correspond to your sub-rows.

So there's a bit of a problem. The problem is we need to have multiple instances of the same template. For example, in this screenshot, we have the three rows that are all exactly the same template. They all have name, contains, and then some text field. So the way we handle this is we make sure the templates are copyable.

When you want to have multiple instances of a single template, we'll copy it. And if you make a custom template, you have to make sure your templates are copyable as well. One of the easier ways to do this is just load your views in a nib file. That way you can load the nib file over and over and each time you'll get fresh views.

Okay, we'll talk about Rule Editor now. So Rule Editor is, remember, the base class, and it's just about user interface. It doesn't try to put more structure than necessary on your rules. The delegate, as we discussed, is going to be providing the views to the rule editor. So how does it do that? Well, we'll look at an example. Let's say we're trying to identify files like in the photo search application.

How do we do that? Well, the user is going to work from left to right. He's going to see a pop-up button on the left. It's going to have a couple options: kind, name, date, open, last modified. And when he chooses one of these, that's going to affect the views he sees to the right of So let's say he chooses kind. Well, there's only one sub-option of that, only one operator, which is is. We only made one available in Interface Builder. So after he sees "is", what's next? Well, he's going to have different types of files: applications, images, texts, and music.

If he chooses music, let's say, we're going to have different types of music: AAC, MP3. So each sub-choice is a child of the previous choice. So in a sense, the rules form a multi-way tree where every item has child items. My item term we're using is called "criteria." So in this diagram up here, every blue box is a criterion.

So a rule then is just a path through the tree. It's just a selection of criteria at each column. So how do you expose this tree to the rule editor? Well, your delegate is going to provide the criteria, and the criteria could be any object you want. It's up to your application. That's the sense in which the rules are abstract.

To express a tree, you need to know two things. You need to know the number of children of a given criterion, That's rule editor number of children for criterion with row type. This is a net method that your delegate will implement. And you need to know the child at a given index. So for this, you're going to return a new criterion representing the child at that index for the parent criterion.

So what do the criteria look like? Well, it's called display value. That's what you actually see. That's what the views are. So you can return a string, a view, or a menu item as the display value. So in this case, we see three strings that the delegate returned.

Last open is within last and days. And in one case, the delegate returned a view for the display value. The method here is just rule editor display value for criterion in row, and all you do here is return a string, a menu item, or a view depending on what your criterion looks like.

The method here is just rule editor display value for criterion in row, and all you do here is return a string, a menu item, or a view depending on what your criterion looks like. The method here is just rule editor display value for criterion in row, and all you do here is return a string, a menu item, or a view depending on what your criterion looks like. So the way we're going to handle this localization is with a strings file. We have some additional syntax in strings files.

For this particular case, you'd say "kind is rock music" enclosing each pop-up in brackets and the percent at sign. And you're going to make that equal to "Class A.S. Musica de la Roca." Now you can rearrange the text fields with the number dollar sign syntax, which is familiar syntax from strings files. And you can also insert the extra text field, the day, just by putting it in the string.

That can be a lot of strings. If you have a lot of rules, there's a bit of a combinatoric explosion. For example, there's "kind is rock music," "country music," "disco," "opera," "kind is not," all of these. You can get it out of hand pretty quickly. So the solution to this is we have, you can group them with commas.

So because most of the strings will localize the same way, meaning you're going to move the pop-ups to the same position and it will just be translation for the remainder, you can group strings that need to be translated the same way within brackets. So in this case, kind is, is not, and contains can all be just translated to the Spanish equivalents as no S and contiene.

What do we have? We have a common interface for rules. That's Rule Editor and Predicate Editor. It's going to be used throughout Mac OS X. It's localizable, it's accessible, it supports drag and drop as we saw. Predicate Editor is for editing NSPredicates when your data model is in NSPredicate, and Rule Editor is when your data is more abstract and you want to have more control.

[Transcript missing]

So for more information you can contact Derek Horn and we'll have the sample code up as soon as we can. It's up now? Sample code is available now. You can get this and play with it right now.