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: wwdc2001-120
$eventId
ID of event: wwdc2001
$eventContentId
ID of session without event part: 120
$eventShortId
Shortened ID of event: wwdc01
$year
Year of session: 2001
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC01 • Session 120

Carbon Controls & Appearance II

Mac OS • 1:01:10

This session builds on the material presented in Session 119, providing details on Dock icon animation, DataBrowser, Help Tags, improved direct manipulation of data with Drag & Drop, and much more. Learn how to make your application "go the extra mile."

Speakers: Guy Fullerton, David McLeod

Unlisted on Apple Developer site

Transcript

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

Alright, so this session we're going to carry on with the topics that we're, or not with the topics, but we're going to carry on the track that we were talking about earlier, which is how do you make a great application on Mac OS X. And if you remember in Session 111 yesterday, I had at the beginning the good, better, best scenarios for adopting Aqua.

And in the best scenario, or even in the better, we started to talk about things like Help Tags, and doing lists, making lists appear properly on Mac OS X. And doing the right thing with the Doc. So one of the very basic things that every application should do is respect the Doc's position, right? And not size windows behind that. We're going to talk about that in this session.

Some of the other things you might do, some applications might do with the Doc, is animate the Doc icon or the application icon. And we're going to talk about some of that material here. So to go in depth on this stuff, I'd like to bring Guy Fullerton back on stage. from the High-Level Toolbox team. Guy, go for it. All right. Well, thankfully, I'm not given the demo for this one, so I can't screw another one up.

That's me! John mentioned I'm going to talk about a lot of things. Yesterday at Ed's session, we talked about new features in the Window and Menu Manager, and I'm going to talk about a bunch of new features in the Control Manager, as well as sort of a toolbox potpourri. We're going to touch on Doc issues, DataBrowser, Help Tags, and a few other things.

So to dive right in, like I said, the Control Manager is moving forward. We've added a bunch of new functionality to it. The most obvious piece of functionality is Aqua widget animation. When you create a push-button control, it flashes. When you create a progress bar control, the fill moves to let the user know that the progress is really happening. Some other controls that we've had on previous systems that animate are the Chasing Arrows Control and the Edit Text Control. Those animated on previous systems. They animate today on Mac OS X. But the difference is that all of these controls animate via a Carbon event timer.

What this means is the controls will animate automatically behind your back whenever you're sort of in the event loop sleeping. If you got a chance to see Ed's Carbon Event session the other day, you'll know what I'm talking about. If you didn't get a chance to see it, it's important that you try to track down the video for that later. It's got some key concepts with respect to use of timers.

But fundamentally what this means is that you no longer need to call idle controls for the system controls under Aqua on Mac OS X. They're going to do their animation automatically via a timer, so you don't need to be calling WaitNextEvent with some small timeout just so you can call idle controls. In fact, that's going to be bad because your app's going to be using up a lot of the CPU and you don't want to do that.

Now all of the controls will begin to animate as soon as they've drawn once. We do this so that we can get the proper look as soon as a window appears. We don't want to begin animating a push button, for instance, right after the window appears before you get the update event, because you'll see the button show up before the rest of the window paints. So what we do is we actually delay the beginning of the animation until after the control draws normally once.

And for some of the controls, particularly the progress bar and the chasing arrows, we offer a way to start and stop the animation, if that's useful to your users. You might want to say that a download's paused for some reason, or there's a network lag, and so you want to stop spinning the arrows for that reason. And you can do that via the Set Control Data API.

Set Control Data takes a control and a tag for the type of data you want to set, and in these particular cases you pass in the KControlProgressBarAnimatingTag or KControlChasingArrowsAnimatingTag, as well as a Boolean value for whether the control is set or not. So you can set the control to be set, and then you can set the control to be set. value for whether or not you want it to animate. So you can start and stop it that way.

Push Buttons - the way you start and stop them from animating is by marking them as default. There's a bunch of different ways you can do that. The preferred way is Set Window Default Button. This is a new API for Carbon. Essentially, what it does for you is it marks a particular control as the default control for a given window.

And the coolest thing about this is if that default control is a push button, the push button will begin animating. The other benefit of Set Window Default Button is that it also tells the Carbon Event Manager which control to hit when the user presses the Return or Enter key. So if you can use Set Window Default Button, by all means, go ahead and do it. It's going to get you the most free functionality now and in the future. Now, an API that you're probably familiar with already is Set Dialog Default Item.

If you use the Dialog Manager a lot, you use this API to tell the Dialog Manager which the default button is. And of course, when you call this API, we will start the default button animating appropriately. And if neither of those really serve your needs, if you want more fine-grained control over whether or not a button is animating, you can use Set Control Data to toggle on or off the animation as you wish with the K-Control Push Button default tag.

We've added a number of new widgets for Aqua, and you can see all of them up on the screen. I'll go through them sort of one by one and tell you what they're useful for. We have a rounded bevel button. You can see those down at the bottom of the window snapshot there. It's just a different look for a bevel button.

By default, your bevel buttons will come up with the square look that you're used to, and that's so you can continue using the same sort of grouped bevel button APIs where they're real close together. But you might have some needs where you really want to draw a rounded bevel button.

And the way you go about that is by using a cool feature we added to the Mac OS X bevel button, whereby you can change the appearance of the bevel button and essentially tell it which Appearance Manager theme button to draw itself as. So you can create a bevel button control and say, "Draw in a rounded fashion," or "Draw in a normal push button fashion," or "Draw in some other fashion." So the bevel button is sort of visually polymorphic in that way.

We also offer a round button that you can see up in the top left-hand corner of the screenshot. This is used for a number of reasons. Most prominently, it's the new Help button that we want in our Aqua user interface guidelines. I'm going to talk specifically about how to get that a little bit later in some gotchas with respect to Mac OS X and Mac OS 9. We also use that for the Back button and the Home button in things like Sherlock and the Help Viewer.

We have a Relevance Bar as well, which is essentially just a variant of the Progress Bar Control. It lets you do relevance ranking. Sherlock uses this. If you were using the Progress Bar Control in Mac OS 9 in Carbon to do a relevance rank, that's not going to give you the right appearance under Aqua on Mac OS X. So you should really begin using the Relevance Bar under Mac OS X.

We also have a Disclosure Button, which is not on the screenshot. That's most prominently used in a Navigation Services dialog. It's useful for controlling whether a window resizes or shrinks back to show you more details. Something that's not exactly new for Aqua, but that I wanted to point out, is the fact that we still have four-sided tabs. Platinum has supported four-sided tabs. You can have tabs on the top, right, bottom, and left of your control. But earlier releases of Mac OS X didn't support the full functionality.

I think we only supported top tabs for a while. Well, they're all back into the GM release of Mac OS X, so you can continue using them on all four sides. Of course, we have appearance primitives to draw all of these widgets. So if you don't feel comfortable using the controls, or you have very specialized needs, you can call the Appearance Manager to draw any of these for you.

Another request we got when we came out with the Aqua User Interface Guidelines was we need smaller controls or differently sized controls. Now the normal controls are basically platinum sized, but there are certain cases in your user interface where you've got a small palette or some other tiny piece of screen real estate where you need to draw a bunch of small check boxes or small push buttons or something like that.

And the Aqua widgets are just too big for those cases. So what we did is we implemented some small versions of various system controls so that you could get the right look under Aqua. A lot of applications out there had small controls already on Mac OS 9. That's because they implemented them themselves.

They wrote custom C-Defs or maybe they weren't C-Defs. They just wrote custom code to draw these things. And of course those are going to look really ugly under Aqua. So if you're using your own hand rolled small controls, you want to begin using the system supported smaller controls.

There are four basic sizes of controls now. Like I mentioned before, the Platinum metrics are the normal sizes. This allows you to have a window that's laid out and have it look just fine on Platinum and just fine on Mac OS X. Some controls, like the checkbox and the radio button, support a smaller size. Others, like the progress bar, support a larger size.

The progress bar is an interesting case, actually, because the default progress bar size, being the same size as it was in Platinum, is good for your layout purposes. But unfortunately, the default size for the progress bar does not match the Acquia User Interface guidelines. The guidelines say you should use a larger one. So in order to match the guidelines, you need to set the size of your progress bar control to be the large size.

The fourth size we support is actually an automatic size. And we've actually supported this for a while, just not a case. We've also supported this explicitly. Certain controls, like the scroll bar, vary how they draw depending on how wide or narrow you make the control rectangle themselves. If your scroll bar is about 15 or 16 pixels wide, we're going to draw the normal size scroll bar.

But as soon as you drop it below a certain threshold, which I think is about 12 pixels, we begin drawing a narrower, small scroll bar. So the scroll bar is one of the ones that can support an automatic size. Now, if you don't like the automatic sizing for scroll bars, you can explicitly request either the normal or small size. So that's one of the things we're going to be looking at. And the other thing is that you can use the small size if you wish.

But in general, you need to make sure that you don't just use these because they're available. You need to have good reasons for them. The larger size controls are much more reasonable. And if you can fit them into your interface, please do so, even if it means making your windows a little bit larger. But if you do have specialized needs, like to fit a whole bunch of widgets on a pallet or some other small bit of screen real estate, go ahead and use the small controls that we provide.

Now, to change the size of a control, it's really straightforward. You can use the set control data API. I mentioned it this session, the previous session. You pass it a control. You pass it the tag for the data type you want to change. In this case, you say change the control size, K control size tag, and then you pass the size that you're interested in.

K control size, normal, small, large, or auto. Not all controls support all sizes, so be ready to handle errors. I believe we have comments in the header which say which controls support which sizes. But in general, if you're good about handling errors, you'll be just fine. The other important thing to note is right now these are only available on Mac OS X. CarbonLib 1.3 does not support any of the control sizing stuff. We're investigating what we would need to do to bring that into CarbonLib, but I can't make any promises about it.

This got brought up last session in the Q&As, but it bears repeating. The Control Manager supports the notion of an embedding hierarchy, and we've done that since Mac OS 8.0. It's been optional on the traditional operating system, including CarbonLib, but now on Mac OS X, embedding is always on. And this has certain interaction side effects for your application. Essentially, hit testing and draw order might not be exactly what you'd expect on Mac OS X if your application does not use the embedding hierarchy on Mac OS 9.

So, my suggestion to everyone is always explicitly request the embedding hierarchy. You do it on 9, just so you can get the embedding hierarchy, and you do it on 10, it's effectively a no-op because the embedding hierarchy's already there, and it's still totally legal to do that. But make sure you explicitly request the embedding hierarchy and run your app on 9, and get your behavior straightened out on there, and you should be able to take that app then and put it on 10 and see good behavior.

Create root control is generally the way you turn on embedding. Creating a root control for a window switches on the embedding hierarchy. That's not the only way you necessarily can turn on the embedding hierarchy. If you use the dialogue manager, you can specify a DLGX resource, which corresponds to your DLOG resource, and there is a feature bit in that DLGX resource that says, "Please turn on the embedding hierarchy for me." You can do the same thing with alert resources.

Now, the reason we turned this on automatically on Mac OS X is that it not only allows us to really simplify the Mac OS X code base, and we basically had divergent code on the traditional OS. We got rid of one whole side of the divergent code and divergent behaviors, so that makes our maintenance life a lot easier.

But it also allows us to add a whole bunch of future enhancements. Yesterday at the Windows and menu session, we talked about how transparent sheets for Carbon were going to be coming in a future release. We wouldn't even be able to do that unless the control embedding was mandatory. So future control management enhancements are also going to be sort of dependent on the embedding hierarchy.

Now, Carbon removed access to a Windows control list. The way controls were maintained in a window on traditional OS was that there was literally just a link list that hung off a window where we kept each control. We eliminated that for Carbon in favor of people just embedding the controls where they wanted them to.

Unfortunately, we heard a lot of feedback from developers when they said, "You know what? We need the control list because we're doing cool things with it. We have an interface that we literally want to move on the fly from one window to another, maybe via drag & drop. It's got great performance impact for us if you make us create both hierarchies at once, as we destroy one window and put up another.

We'd really like a way to move a control between windows, so we gave it to you." Essentially, we don't allow you to hack the control list anymore, but we allow an existing API, the Embed Control API, to take a control out of one window and just embed it someplace else. The only gotcha with respect to all this is that you cannot actually move the root control. The root control, once it's associated with a window, is always stuck to that window. But you can go ahead and move any sub-controls you're interested in.

We've mentioned this a ton of times again. Carbon events permeate the toolbox. The controls themselves are actually implemented in terms of Carbon events right now. In fact, the The messaging model for control definitions is inherently Carbon event based. Now, we obviously still support message based defprocs, but we do this through a compatibility layer where we intercept a Carbon event coming towards a particular defproc. We see it doesn't handle it, we turn it into a control message and route it off to the defproc.

But, we don't necessarily handle direct messaging in the old school way to the system controls. What that means is if you have any application code which is calling send control message and trying to make the system controls do things, they probably won't work on Mac OS X. You might get lucky for the short term, because I know we still support a few messages this way, but ultimately we plan on removing all support for send control message to the system control definitions.

Now, generally speaking, you shouldn't even need send control message anyway. We have high level control manager APIs that let you do things like drawing and hit testing and whatever at a very high level. So, you could probably just eliminate your use of send control message in favor of these other APIs.

Now, if you have custom controls, you may not be planning on revving them to support Carbon events anytime soon, and you're using send control message with them, obviously, now. You can continue to do so. We're not going to eliminate the send control message API. We're just going to prevent it from working with the system control definitions. But your custom CDEFs will still work just fine.

So I want to go into a few of the future enhancements we're adding to the control manager. For performance reasons, drawing with Quick Draw right now in the toolbox is a little bit slow, because ultimately when we render a particular widget, we're going to draw with quarts. And drawing with quarts means we need to build the proper quarts port equivalent, which is a CG context ref. Building one of these is a little time consuming and a little memory intensive.

And ideally, what we'd like to do for a given draw cycle, we would like to just create one context and pass it to all the controls when they draw such that each control now no longer needs to create its own context. This will save us a lot on time and memory overhead.

Drawing with Quartz will also allow you to get around one major missing feature in the Appearance Manager. Right now, the Appearance Manager does not let you draw pulsing default buttons. There's no way to do it via the APIs. Ultimately, when we allow drawing through Quartz, you will be able to draw pulsing default buttons through the Appearance Manager.

Now yesterday we gave you a little taste of what control drawing modes were. Essentially, we want to build some functionality into the control manager that makes it a lot more reasonable to work with. Right now when you call a control manager API, you don't necessarily know whether or not it's going to draw. If the control is visible, it draws for some APIs. And on Mac OS X, you notice that it draws if you call set control bounds, but on CarbonLib it doesn't draw. And there's all sorts of weird little nuances like that that we've had historically.

And we want to offer a way that lets you have more explicit control of exactly when a control draws. In fact, we want to get to a point where controls don't really draw by default. You can go configure a control five times with five control manager APIs and not draw until you've done all that configuration.

And finally, at some later point, the control manager should be able to say, "Well, you know what? I know what controls are dirty, so why don't I just go and redraw them?" And this was all done initially to allow us to support transparent sheets. Which will be coming up in the future. But it gave us a whole bunch of these cool side effects, and in the future we're going to allow you to turn this stuff on for your Windows and get all kinds of cool performance benefits.

I'm going to switch gears a little bit and start talking about the Doc. A lot of applications Need a new icon. Right now, your icons are probably 32 by 32, and they look pretty miserable. And with the Dock being so prominent in the interface, and the fact that the Dock can display 128 by 128 icons, you need to spend a lot of time making your Dock icon look great.

But it may not be enough to just put a great-looking icon in the Dock. Some applications need to put status in the Dock. They need to--the best example is Mail application. Mail puts a little marker in the Dock that says how many messages you've got unread. It's a really cool feature, but at the same time, you don't want to overdo it. You shouldn't just animate your application icon or badge something on your application icon just because the APIs are there.

You need to not be intrusive into the user's visual space unless it's going to be meaningful to them. If you find an instance in your application where animating your icon would be useful, there's a number of different ways you can do it. Probably the most straightforward way, if you're used to doing this, is to use the Dock application. The most common way to do programming for traditional Mac OS is to use Quick Draw. And the way we allow this is through the use of the begin and end QD context for application Dock tile APIs.

These APIs give you a color graph pointer that you can draw into, and when you call end, it ends up putting the image into the Dock for you. Now, Quick Draw, as you know, does not support transparently in the alpha channel properly. And if you want your application icon to have transparency, you need to draw with quartz, and you do that with a similarly named begin and end C. And then you can use CG context for application Dock tile. You call those, you draw with quartz, you end the context, it draws to the screen.

You can also do badging, which is what Mail does. And there's only, unfortunately, a Quartz way of doing that. But in order for badging to work properly, it needs to use alpha, so that's why it's Quartz only. We've got a couple APIs for that as well. Now, all of these APIs have pretty good documentation in the header itself. They're in MacApplication.h. We show you some ways you use them, some of the pitfalls in there, so I suggest you check those out.

Your minimized windows also show up in the doc, and therefore you might want to change the icon that's used to represent them. When a window is first minimized and put into the Dock, the Window Manager automatically takes a snapshot of the entire window, its structure, its content, and everything, scales it down to 128x128, and puts that in the Dock.

If your application has fairly simple needs, and you just want to update the image in the Dock based on your window's new image contents while it's minimized, you can do that with the Update Collapse Window Dock tile. If your application has a minimized window, and it's busy processing, and you put some more status in your window, or you change some image slightly, and you want it to be reflected in the Dock, just call Update Collapse Window Dock tile on your window, and the Window Manager will just do the right thing for you automatically.

If you need more custom control, we offer APIs for those as well. Create and release QD context for collapsed window doc tile. And these APIs are all documented in MacWindows.h. And we're working with the documentation people to get other forms of documentation available. But right now, I took a look at these headers the other day, and they're really, really good. They explain a lot of what you need to know. Now to give you a more concrete example of all this, I would like to bring up David McLeod. He can show you some of the details.

David? Thanks. Morning. I wrote a small demo application, if you could bring up-- right. This session builds on the material presented in Session 119, providing details on Dock icon animation, DataBrowser, Help Tags, improved direct manipulation of data with Drag & Drop, and much more. Learn how to make your application "go the extra mile." That's a little demo application called Tyler. And keep an eye on this guy down here. That's the guy that's going to get animated. First, I'm going to draw on it with Quick Draw.

I'm not a graphic artist or anything. I didn't have anybody make fancy art for me. You can see that I had to erase "I would have left behind the image, otherwise I would have left behind some remnants of the other

[Transcript missing]

Next, I'll show Quartz. You can see it's the same drawing.

If you can see really up close on it, it's all anti-aliased and stuff, because Quartz is really nice. That way you get it all for free. There's also a transparency on here. You can't really see it unless I drag a window behind it. You can see that that, all that drawing is transparent. You can see right through it, see the window up there.

The next thing I'll show you here is I'm going to add a tile to the badge. I'm going to add a badge to the tile, excuse me. It's just my WWDC badge. Again, I think that might break some HI guidelines. I just made it myself. It's fairly simple. You just make an image with an alpha channel and you blast it on there with an API call. One gotcha here though is that you need to clean the tile before you draw on it.

Because if you don't, the badge will just overcomposite on top of itself. I'll just do that a few times and draw. You can see that its shadow has just overcomposited over and over. It's not much of a shadow anymore. It's kind of a black blob, which is ugly. The second last thing I'll show you is Restore Tile. Just bring it back to what the application sets the icon in there.

The last thing that I'm going to show you is something Guy talked about with the collapsed window tile animation. So I have a little demo window here. Got a beautiful picture of someone. It's a little dark though. So we have some processing that can happen to it to lighten it so we can see his pretty mug. We can actually minimize this into the doc, and you can see that the tile continues to update itself, so you can see how far along the processing is. Let's do it one more time, just for fun. Let's do a whole bunch of them.

Good thing my head's not really that big. Yay. He's so pretty. So you can see they're all down there animating. And you can also see that there's a whole bunch of things down there animating, and it's a little ugly. So don't get crazy doing the animating. It's just fun sometimes. Next, I'll just show you some of the code I used. It was all very simple and straightforward to do. First, you saw me do the QuickDraw animation.

You call in and you get a QD Context for the application Doctile. You keep a hold of the port and you get to use it for a while. I happen to do some erasing here. You really do have to erase. You can get the same overcompositing problems with Quartz. And if you don't erase with the Quick Draw, you'll end up with remnants of your previous tile drawing on there. I do a little bit of drawing using QuickDraw. Everybody knows how to do that, probably better than I do.

And here's a gotcha. When you request a context, you always have to flush the context before you finish with it because the tile won't update until you get some other update queue later on. So don't forget to flush. It's really important. And lastly, you've got to tell the doc that you're finished with that context and you're not going to be drawing in there anymore. That's the quick draw way, and you saw how it made everything opaque and it just killed the transparency. That's just no fun.

Very similar code to do the Quartz. I begin the CG context right here. I clear it out. This particular clear though, "maintains the transparency." So I start out with something completely transparent, and I can start drawing on that. I do the happy face drawing again, and I happen to do a little bit of transparent yellow in there, and that's how you can see through it.

"CG Contexts are just like the QD Contexts you get in the first one. Don't forget to flush, or else it won't update properly." "Can't forget to flush." "And lastly, you gotta tell it, 'Hey, I'm finished with the context.'" The badge is a little bit more complicated, and you require a CG image. You say, "I have a CG image, and I'm going to be blasting it onto the tile.

I'm going to blit it on there." There's a bit of a pitfall, like I mentioned earlier, where I showed you where the badge overcomposited on itself and its shadow was darkened. So I actually restore the tile just before I draw on it, so I have a nice clean tile to start with.

I make some PixMaps, and there's a really nice call available called "Create CG Image from PixMaps." So you can take a PixMap that is an image that you want to put on there, another PixMap that is the alpha mask that you want to use. It makes a nice CG image for you to use. Very convenient.

And lastly, just want to overlay that image on top of the tile. It's really straightforward. The next part is restoring the Doctile. It's very complicated, so Guy wanted me to go through the code line by line for you. Here it is here, the one line. Restore application Dock tile image. Very straightforward, no parameters. I think we can all manage to cut and paste that one out of the header.

And the last one was the collapsed Dock tile animation. It was very straightforward. All I did is I updated the window so that you could see the processing happening. And you just have to tell, you just have to call the API "update collapsed window Dock tile" with a window pointer, and it does all the work for you. It just takes the image of the window and sticks it into the tile.

So I do some image processing before and after, and just make that one call and it does the animation for you. The only gotcha there is you want to make sure you update it one final time when you're finished. So there's some tricky code hiding up here where I update it one last time. But that's about it. It was really easy to do tile animation. Oh, one last thing. This code is going to be up on the website. It's just going to be sample source code for everybody to try out. Thanks, David.

So that was cool. One final note about the Doc stuff. Again, I know you've probably heard this about a dozen times. Just because you can do this doesn't mean you should do this. Make sure that your application and window icon animation is subtle and use it only when necessary. The last thing I want to see personally is about ten applications each spinning some little random animation just because they can. Not only is it visually distracting, but it's just going to wreck performance for the machine. So do it only when it's necessary.

[Transcript missing]

All right, so switching gears back to the Control Manager. Control Manager supports a lot of Carbon Events. They're all documented in CarbonEvents.h. We tell you what they're used for, we tell you when they're sent, we tell you what parameters come along with them, we tell you what the responsibility of someone who handles them is.

This is a great way to write custom def procs now. This is only available on Mac OS X right now, but we're looking at making Carbon event def procs available in a future version of CarbonLib. The best part about Control Carbon Events is it also allows application clients of controls, whether they be custom controls that you wrote or system controls that Apple wrote, it allows those application clients to sort of override or augment the behavior of a particular control definition.

All of the Control Carbon Events, except for two that I'm going to be mentioning today, are of the KEVENT class control. Just take a look in carbonevents.h and it'll tell you all the details there. Let's say you want to write a Carbon event based control definition. It's your first time you're doing this. I'm going to take you through step by step.

In order to do a Carbon Event-based Toolbox object, you need to register your Toolbox object class. And a Toolbox object class is just an opaque wrapper around the notion of a def proc. You can think of it that way. The way you create an object class is by calling RegisterToolboxObjectClass. You give it a name, and you give it the Carbon event handler for that defproc, and you give it the list of events that you're interested in handling.

The API hands you back a Toolbox Object Class Ref. Now, when you call createCustomControl, you can pass in that Toolbox Object Class Ref, actually pass it in as part of a control-def-spec structure. But you pass that in to createCustomControl, and therefore the control manager says, "Oh, you know what? I know what defproc to create now. I'm going to go create it." When you call Create Custom Control, you can also pass in an initialization collection with all sorts of initialization information that your control definition might want.

Totally free form, totally up to you what you want to put in there. The Control Manager does support some default things in this initialization collection. It will look for things like a starting value, a starting minimum, whether or not the control is visible, things like that. This is all documented in the header as well.

Let's say, however, that you do all of your control creation either via New Control or Get New Control. Or perhaps you want to write a shared library version of a control that needs to be used by third-party applications, and all they use are New Control and Get New Control. In order to support New Control and Get New Control, you need to use the Register Control Definition API. I should probably go into a little bit of background to make this make more sense.

When you call Create Custom Control, you're essentially saying, "Create this control with this entry point. I already know what the entry point is." New Control and Get New Control operate off this PROC ID model. If you've done any toolbox programming in the past, you know what a PROC ID is. Essentially, it's just sort of a mangled resource ID of where a defproc would have lived on the traditional OS.

And what Register Control Definition lets you do is it lets you take a proc pointer-based defproc, whether it's a toolbox object type, or whether it's just a raw proc pointer, and say, "Associate it with this sort of virtual resource ID." So now your clients can call New Control and Get New Control and actually instantiate versions of your Carbon event-based defproc.

There's a couple basic Carbon Events that just about every DefProc is going to want to support. And of course, those have to do with initialization and disposal. We send two different Carbon Events: KEventControlInitialize. This is the one where you initialize. It, in the Carbon Event, has the initialization collection that you passed in to create Custom Control. So you can extract that out, start digging through there, looking for your bits of information that are important to you. Your only responsibility for handling of KEventControlInitialize is to make sure you report your control's features back out in the KEventParamsControlFeatures.

You do this so the control manager knows what kind of things you support, like whether or not you're an embedder or various other things. And of course, also while handling initialization, you want to allocate any instance data or do any prep work that your control definition might need to do. And you can imagine how the dispose work. You get the dispose event, you dispose your stuff, and you're done. It's pretty easy, right? Now, if you want to be a full-featured control definition, you want to allow your clients to make use of certain Control Manager APIs.

For instance, if your clients want to determine what the proper size of your control is, given its current settings, like, let's say an application creates a static text control, and they start out creating one that's just 10 pixels wide and 10 pixels long, just because it's the easiest way for them to do it, and then they shove some text in there.

Then they want to ask the Control Manager, "Hey, how big should you really be to display all your text?" You would use the GetControlBestRect API to do so. In order for a control definition to report the right thing, it needs to listen to the KEventControlGetOptimalBounds Carbon event. You handle this Carbon event by examining your current state, figuring out how big you should be, and you can even include a baseline offset.

This baseline offset allows your clients to do things like line up the text baselines for two totally different widgets, make things look right according to the Aqua Human Interface guidelines. Now, another API that you might want to allow your clients to support is GetControlRegion. GetControlRegion allows your clients to ask for any region relative to a particular part code of your control, as well as a couple sort of meta part codes, like the structure and the content part.

When the application calls GetControlRegion, we send your control definition, a K-event control GetPartRegion. And well, guess what? It has the part in it that the client is interested in, and it's your responsibility to figure out what the region is, stuff that in the Carbon event, say you handled it, and then you're done.

You'll probably want to handle the K-control structure metapart and the K-control content metapart in addition to all the sort of unique parts of your control. And these will allow your clients to determine the total draw area of your control, as well as the sort of embeddable area of your control.

And likewise, get and set control data is a free-form way that clients can ask your control for a certain piece of information. It basically enables you to provide information to clients that the control manager does not already have explicit support for. For instance, the disclosure triangle--that's a bad example--of the static text control. The static text control supports a one-line mode or a two-line mode.

Well, it doesn't really make sense to add a control manager API that's called "set control one-line mode" with a Boolean, right? You can just use set control data to say, "Hey, static text, I want you to be one-line or I want you to be two-line." So this is a very powerful thing for configuration of complex custom controls. And if you want to allow your clients to use it, you listen to the k-event control get set data Carbon events.

Any control worth its salt is probably going to draw, right? To do that, you listen to the kevent control draw Carbon event. And normally for message-based CDEFs you always draw in the current port. But for this Carbon event, we actually pass you the port that we want you to draw into. And in fact, we only optionally pass you this port. If you see a port inside the kevent control draw Carbon event, draw into that port. If you don't, go ahead and draw into the current port.

And likewise, if your control supports a sort of opaque background that you need sub-controls to be rendered on top of properly, you want to listen to the kevent control apply background and kevent control apply text color Carbon events. These give you a chance to set up the background properly so your sub-controls can erase.

Most controls track the mouse. How do you do that? Well, it's actually really simple if you have a simple control. If your control is something like a push button where it pretty much just has two visual states, you know, the user's tracking on it, the user's tracking off of it, all you need to support is K event control hit test. This Carbon event is sent by the control manager in a number of situations. A client might be asking, "Hey, what part of the control am I over right now?" Well, we'll send you the Carbon event for that so you can tell the client.

But if your tracking needs are simple, this hit test Carbon event allows the control manager to do the majority of work for you. You simply respond with the part code that the mouse currently is over. And if you have simple needs, the control manager will just highlight and unhighlight you, withdraw Carbon events as appropriate.

And if you have more complex tracking needs, you should listen to the K-Event Control Track Carbon Event. This is sort of the one-stop shopping, high-level Carbon event where you can do anything you need to do. This Carbon event is sent to you almost immediately after a client calls handle control click or track control. You've got full rain, you can do custom tracking, weird scroll bar behavior, anything you want.

Now there's actually about a half a dozen more tracking related Carbon events, and they're honestly fairly complex, and I'm not going to go into the details here, but I doubt you'll really need to use them. If you are a widget like a scroll bar and you want the control manager to sort of do its default thumb behaviors and weird things like that, you can actually take advantage of about a half a dozen other Carbon events. Those are all documented in the header pretty well, so take a look there.

If you want to support keyboard input, the first thing you must do is support the notion of focusing. We talked about focusing before. It basically says, "You know what? My control is ready for keyboard input. I'm where the keyboard input should go." So in order to support that, you need to listen to the K-Event Control Set Focus Part Carbon Event. There's a lot of information in this slide, but it's really all important. When you get sent this event, you're going to be told which part to focus.

Now sometimes you might be told to focus no part. That means clear the focus if you have it already. Other times you might be told a specific part to focus. So you should go ahead and focus that specific part. Other times, like when the user hits the Tab key or Shift Tab key as they're tabbing through your user interface, we say focus the next part or focus the previous part. So you want to handle those as appropriate.

Of course, if you're not currently focused and you receive the Carbon event that says focus the next part, you should focus to your first part. And as you keep receiving next part Carbon events, keep focusing next part until you get to the end. And finally, when you're at the last available focusable part and you receive another request to focus the next part, just turn your focus off. Of course, you do the reverse if the user is asking for the previous part.

Now your one responsibility on exit from this Carbon event is to tell the Control Manager what part is really focused. This is so the Control Manager can do certain smart things. Just store your currently focused part in the Kevent Param control part of the Carbon event, and that's all you really need to do.

You might optionally want to support the Kevent Control GetFocused part Carbon event. In fact, this is probably... Most of you will not need to support this if you return the proper part in K-Event Param control part. But if you have very special needs, you might want to take advantage of this Carbon event. It allows the control manager to request sort of lazily what part is focused.

Oh, actually, let's go back one more thing. This becomes especially important in the future. One thing we want to do with Mac OS X is allow keyboard navigability across all the user interface. And it's a little ways off, but The only way to make that happen for your custom controls is to support these Carbon events. You will not be able to make it happen for controls that are message-based.

Now the cool thing is you can actually have a message-based control that just installs a few Carbon event handlers. And this might be one of them you want to take advantage of. So I suggest checking this stuff out, learning how it works, because it will become very important in the future if you want to have a totally keyboard-navigable UI.

So once you've got the focus, you want to accept keyboard input. The best way to do that is to listen to a Carbon Event that's not part of the Control class. This is actually part of the Text Input class, and it's a K-Event class, Text Input. The Carbon Event specifically is K-Event Text Input Unicode for Key Event. It's a little cumbersome of a name, but it's documented well in the header.

And essentially, the Unicode character stream is stored in a parameter of that Carbon Event. You extract it, you slap it into your control, and you're pretty much all set. Now the even cooler bit is that keyboard events are routed directly to the user focus. Once you're focused, if you're doing Carbon Event-savvy defprocs, you don't need to worry about the client calling handle_control_key. It all just gets sent there automatically.

There are several more events you might or may not want to handle depending on how complex your control is. The first is K-Event Control Activate and Deactivate. These are sent when a client calls Activate or Deactivate Control. Now generally speaking, most widgets will not need to respond to these Carbon events.

These are only useful if you need to change your control's internal state as a result of activating or deactivating. You don't need to worry about handling this event if you only change your visual state on activating and deactivating. I guess the best example of this is the Edit Text control.

When it receives this Carbon event, it calls TE Activate, right, because it needs to disable the look of the edit field. And it doesn't just do that to get the visual look. It needs to do that so that the text services manager can make sure to deactivate any TSM documents and stuff of that nature. But in general, other controls like the push button, they don't worry about it because after this Carbon event is sent to your control, the control manager will request to draw anyway, so you'll be redrawn in the proper visual state.

Now a lot of controls are becoming more complex these days. Case in point, the DataBrowser. We'll talk more about the DataBrowser a little bit later, but as controls become more complex, it becomes important to display a proper cursor when you're over a control. We're no longer at the point where we know, "All right, well, we want an arrow over buttons and just about everything.

Oh, except for the Edit Text Control, which needs an insertion point." No, now we're at the situation where, "All right, if I'm over one of the column headers in DataBrowser, I need to display the little Expando arrow cursor, unless I'm all the way expanded to one direction, then I want to show the Expando cursor that only points the other direction." So we have a Handle Control Set Cursor API, which was introduced relatively recently, and in order to take advantage of that, you just listen to the K Event Control Set Cursor Carbon Event. We tell you where the mouse location is, as well as what modifier keys are down, and then you can look at your control's internal data structures and figure out what cursor to put up.

Likewise, contextual menus are becoming more important in the user interface. We offer a Handle Control Contextual Menu Click API. And when the client calls this, we send you a K-Event Control Contextual Menu Click Carbon event. Now the cool bit is these Carbon events will be sent to your control definition automatically if the default window handler is installed on your window. It means if you're very Carbon event savvy, your control definition can just handle these Carbon events. You don't need to worry about the right API calls because the toolbox is going to call them for you.

[Transcript missing]

If you have a large, visually complex CDEF and you resize, right now the control manager says draw everything. And if it's particularly expensive for you to draw, that can take a lot of time, you see the lag, maybe a flash on Mac OS 9, and it just really isn't a good thing. What we're going to start doing in the future is allowing you to tell us what region to update as a result of one of these state changes.

So what will happen is someone will call size control on your control, and you will be able to put a region into a parameter of the K event control bounds change Carbon event that tells the control manager, "Look, just redraw this particular area, that way it's not very expensive." Now this stuff isn't in yet, but it's one of the things that we're sort of throwing in the control compositing mode bucket that we've been talking about a little bit. And finally, if you're using the Embed Control API to move controls from one window to another, we send a K event control owning window change to CarbonEvent. So you might want to react to that appropriately.

There's a few others. And these, actually, DevProcs won't want to handle. These are pretty much reserved for the client of the control. The first is kEventControlHit. After the user clicks on a control and the application calls track control or handle control click, and the click did successfully end on that control, we send out a kEventControlHit Carbon event.

This will give your application a chance to react to the control as appropriate. If that control has a command ID associated with it, and the kEventControlHit Carbon event was not handled, we will send out a kEventCommandProcess Carbon event, first to the control, and then ultimately through its embedding hierarchy down to the window. Now, the really cool thing about this is you can build up an entire user interface with a window and a whole bunch of controls in it, and let the default Carbon event handlers track all the clicks on your controls and let all the default processing happen.

And then at the window level, you can install a kEventControlHit or a kEventCommandProcess Carbon event handler that listens to all the controls. So you can have four control hits and command processes, four controls in that window. So it sort of lets you encapsulate all your logic code into one place in a clean way.

Now, I've mentioned complex controls, and right now there is no more complex control than the DataBrowser. The DataBrowser is our list manager replacement, essentially. And we figured, well, if we're going to replace the list manager, we're going to need a new API. Well, we don't want to make a new API, so let's just leverage an existing API. Well, the Control Manager API was really rich, so we decided to turn the DataBrowser into a control.

And essentially, DataBrowser is the list view that you see in the Finder, and it's the column view that you see in a Navigation Services dialog. It's totally customizable. We support resizable columns. You can change what's displayed in a given column. You get to control the text that's displayed, the icon that's displayed. It supports text editing, drag and drop, all kinds of stuff like that.

Like I said, it's a system supply control definition. You create it with Create Data Browser Control. Gives you back a normal control ref. You can use all the normal control manager APIs. You can size control it, you can move control it, you can hide it, you can make it active, inactive. But of course, a complex list needs a whole new suite of APIs. You can give it the right data, configure it properly. So there's a bunch of new APIs in ControlDefinitions.h to configure the Data Browser.

So we're not planning on leaving the DataBrowser alone. We need to improve it in several ways. The version we shipped in Mac OS X is pretty good. There are some bugs in it, yeah, granted. The version in CarbonLib is lagging behind a little bit. So there's definitely some enhancements we want to make to it. First off is speed. We plan on speeding up the DataBrowser, for one, through the control compositing stuff I've been talking about, and specifically by handling a K event control bounds change Carbon event.

That way, when you're live resizing a Finder window, we don't repaint the entire DataBrowser control. And in fact, that's why live resizing a Finder window is so darn slow. So we're going to make that a little bit faster. I've fixed several critical crashing bugs lately, and they will show up in a future operating system release. There's still a bunch of other bugs we know about. We plan on fixing the major ones. And I want to get as many of those bug fixes into the version of DataBrowser that ships with CarbonLib 1.4 as possible.

And finally, DataBrowser supports most of its customization through proc pointers. Proc pointers are a little bit limiting in that you can't change the parameter list they receive. Once you've got a four parameter proc pointer, you're pretty much always stuck with a four parameter proc pointer. Well, Carbon events are sort of our proc pointer replacements, so we're going to integrate them more with DataBrowser. So you'll be able to do more customization through Carbon events.

So, if you're going to support help properly in your application, there's several steps you need to take. Probably the most basic one is including the proper help button in your application. If you display standard alerts from Create Standard Alert, Standard Alert, Create Standard Sheet, we're going to put the right button in there for you. But if you have custom needs, either in a larger window or in a dialog that you roll yourself, you want to display the proper help button.

Unfortunately, you have to conditionalize your code to make this work on both Mac OS 9 and 10. On Aqua, we want you to use the round button control. Unfortunately, you can only create a round button control on Mac OS X. On Mac OS X, just call Create Round Button Control. On Mac OS 9, however, we don't have support for the round button control. And in fact, the Mac OS 9 Look is just a plain bevel button control. So call Create Bevel Button Control when you're running on CarbonLib.

Now, to get the right help icon, you can just use icon services. Both of these control types support totally custom behavior via icon refs. So you call Get Icon Ref to get the help icon associated with the control, and that's pretty much all you have to worry about.

Another step for supporting help is, of course, Help Tags. We showed it on the screen. We showed it on the last slide. Help Tags, you can think of those as a balloon help replacement, if you're used to the traditional of us. But really, they're just context-sensitive tool tips.

The cool part about Help Tags is that they remain very brief for most cases. As you hover over a particular button, it should give you just a very brief indication of what that button's going to do. But if you hold down the Control key, the Help Tag actually expands to give you a little bit more information. That way, you can get that information if you want it, but it doesn't stand in your way and block out the rest of the interface.

Help Tags also allow customizable positioning relative to the UI element that you're planning on putting help up about. And we have content guidelines in the Aqua User Interface Guidelines, I believe. I believe there's a section in the back that talks expressly about what sort of help content is good help content, and how much is too much, and what is just the right amount. So take a look at that.

You can implement Help Tags in a number of ways. The most straightforward way is to use Interface Builder and its NIB support. Any NIB that you build from Interface Builder for a Carbon application will allow you to specify help information directly in that NIB. So not only can you position everything, but you can give it all the help content. And then when you run that interface with the Carbon Event Manager, the Help Tags just come up automatically. So that's the most straightforward way.

We also offer a programmatic interface, and we even added a few APIs to let you leverage existing Balloon Help content that you might have for your dialogues. Now while these APIs are available to let you use the Balloon Help content, I don't recommend using them, not for any sort of performance or technical issue, but just because the guidelines for Balloon content do not match up the guidelines for Help Tag content. In general, Help Tag content is a little bit more concise than the Balloon content. But if you need a component that's more precise, you can use the Help Tag content. crutch for the short term, you can go ahead and use the compatibility APIs.

So if you want to use the programmatic interface, there's actually three different ways you can do it. You can associate static help content with an individual control window or menu by using these HMsetControlWindowOrMenuHelpContent APIs. You can go back and change the help content later, but essentially, once you've set it, the CarbonEvent subsystem, which actually will run for your WaitNextEvent-based application, will put up the help content automatically based on the control's current position. Now if you need a little bit more control over when your content is displayed, you can use a certain callback mechanism.

We allow a callback to be associated for controls and windows and menu items, such that you can display the help tag, or you can generate the help tag's information dynamically on the fly. This might be useful for a lot of frameworks out there. And finally, if that doesn't even serve your needs, you can just put up any arbitrary help tag in any position you want by calling HMDisplayTag. You provide the right hot rectangle and the right help data. And then you just put it up on screen and there you go.

Now unfortunately, tearing down the help tags is a little cumbersome. We forgot the remove tag API. So right now what you have to do--yeah, I know, we're lame sometimes. Right now what you have to do is turn off help tags to make the help tag go away, and then turn help tags back on. Kind of a crummy workaround, but it'll do the trick.

Now on the Drag Manager front, most of the stuff's the same, although there's a few gotchas. The drag highlight color is changed under Aqua. It's no longer the, I think it was a blue for Platinum. We now have a theme brush to allow you to draw the proper Aqua-savvy drag highlight.

And of course, if we ever decide to change the drag highlight and you continue using the Set Theme Background API with the right brush, you'll keep getting the right look. In fact, we might even change the drag highlight depending on whether you're in Aqua or Graphite. So use these APIs to get the right color and you'll always have the right look.

As was pointed out yesterday in one of the sessions, Drop Rocks for Drag Manager don't work anymore on Mac OS X. The way you do this on Mac OS X is actually through the Set Drag Image API. Set Drag Image is actually callable repeatedly during the drag, so you can alter your drag image as the drag is moving along the screen if you want to.

So we actually get questions about cursors quite a bit. The HI guidelines talk about it quite a bit, but there's sort of one main gotcha that you need to worry about. Essentially, the guidelines say this: If you're going to be busy for less than two seconds, just put up a watch cursor. If you're going to be busy for more than ten seconds, put up some kind of progress dialogue or spinning arrows or something to let the user know you're going to be busy. But unfortunately, there's this weird window of opportunity between two seconds and ten seconds.

And the reason it's a weird window is because the system will automatically put up the spinning wait cursor for your application if your application is not responsive to events for two seconds. So if you get into some long processing loop that goes beyond the two-second threshold, up comes the spinning cursor, which we like to call the spinning cursor of death. You don't want to rely on this cursor for your busy needs because of why we call it the spinning cursor of death.

Generally speaking, this cursor should be interpreted by the user as, "Wow, this app is really hung. Maybe I want to try this out." You don't want to force quit it. Or, "This app is really in a bad state." So if you start relying on the system's spinning wait cursor for your busy needs, the user is going to think your application is actually hung and might force quit you prematurely.

So what I would suggest doing is if you're going to do any busy processing that's going to take more than two seconds, make sure you put up some kind of mode-less dialogue or spinning arrows to let the user know it's going to take a while. And continue processing events as normal to allow the user to get access to the rest of your interface.

So get out there. Go ship your applications. I'm sick of using classic apps. Come on. Come on. Carbon events are the way to do this. Carbon events permeate the entire toolbox. You can take advantage of all sorts of new toolbox functionality. We've talked about it in four different sessions. You can use Carbon events to adopt sheets, Carbon events to do cool things with your controls, Carbon events to do cool things with the windows. You can use this as the sort of patching mechanism for Mac OS X to alter the toolbox behavior.

Take advantage of the dock when it makes sense for your applications. Don't go overboard with it, but make sure if you've got a really great way to give the user a little bit more information by changing your dock's icon, great, do it. And as John Galenzi said in his session yesterday, Aqua gives you the chance to go and stand out.

The playing field's leveled. A lot of applications are trying to make the most out of Aqua. This is your chance to go and beat all your competitors and get a great looking Aqua interface and make your application best of class. So with that, to go through some roadmap and Q&A, I'd like to bring back up John Galenzi. Thanks.

Let's see, the roadmap. The first one, 119, we just had that earlier this morning. So if you weren't here, you got to watch it on DVD. Designing Aqua Icons, go to that one on Friday. Tomorrow morning here in this hall at 10:30, it's going to be a great session. Apple Help, again, we've talked through all of these, so I won't go into great detail.

Anything new here? Nope, all this stuff happened. No, no, feedback for him. Friday, tomorrow. Oh yes, feedback on the high-level toolbox, correct. Give us your feedback as to what you want added to the high-level toolbox or Carbon functionality to get a really great user experience with your product on 10.

There's my contact info if you need anything added to the system, or you think we're missing something, or something's not working the way you want it to work, please contact me. Or if you have questions about creating a great application on X, feel free to send me an email and work with me to get your app revised for Mac OS X.