Carbon • 58:25
This session features a variety of upcoming features in the High Level Toolbox that will help Carbon developers deliver the best possible Aqua experience in their applications. Discusses how to improve applications with the combo box control, the toolbar control, services, drawers, and keyboard focus.
Speaker: Guy Fullerton
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Ladies and gentlemen, please welcome HI Toolbox Engineer Guy Fullerton. I'm Guy, but he just told you that, so don't need to be told again. I got two things to say. The first is no food comas. I know you're just coming back from lunch and you're probably sitting there with a big, you know, meal in your stomach. So if you need to, get up, jump around, get some energy, because I got a long session.
Second thing I need to tell you is that if you saw Ed's session before lunch, you know that I have no demos. And right now some of you are thinking, no demos. No, that's right. I have no demos. That's because I've got tons of content to talk about. Ed talked about a lot of stuff in a high level overview sort of way.
And I'm going to talk about stuff from sort of a low level technical perspective and show you how to implement a lot of the things he showed. So before I go into details on the new stuff in Jaguar, first thing I want to point out is some cool stuff we worked on in the Appearance Manager and that we're currently working on in the Appearance Manager.
The first thing is a radical improvement in theme text drawing speed in the Appearance Manager. If you've been using the theme text drawing APIs in 10.0 and 10.1, you know they're pretty horrendously slow. So for Jaguar, we improved our caching just tremendously. We started making more efficient use of the Atsui text drawing APIs. And we basically got a two times performance increase in Jaguar. The green bars are Jaguar. Obviously the blue bars are 10.1.
So another thing that we're currently working on is some improvements to the Appearance Manager. If you've used the Appearance Manager drawing primitives, you've probably run into a couple of problem areas. If you use a lot of theme drawing primitives to draw your widgets, you probably notice that the performance is kind of crummy. And the main reason for that is the Appearance Manager APIs are inherently Quick Draw based.
When you call one of these APIs, they assume you've got a Quick Draw port around. They assume that port's configured with the right clip region, right pen settings, and all that stuff. But internally, our implementation is implemented in terms of core graphics and cores. And that means before we actually do the render, we have to do a fairly expensive sync from the Quick Draw port to the CG context.
Also, the Appearance Manager APIs are full of other Quick Draw data structures. If you pass a rectangle into the Appearance Manager, it's a classic Quick Draw rectangle. It's a 16-bit coordinate system. It's not exactly the most modern coordinate system. Every framework out there works around this by implementing their own bigger rectangle type. So the fact that the Appearance Manager does this is kind of crummy.
And the third problem area is the use of theme erase UPPs. A lot of the drawing primitives require you to pass in an erase proc so that you can erase behind whatever image you're ultimately going to draw. And this ultimately ends up giving you this sort of Aqua misaligned pattern problem that I'm sure you've all seen when you ported your apps to Mac OS X.
So what we're currently working on is eliminating all those problems. We've got a new suite of Appearance Manager APIs that are still in development. I don't know if these are going to make Jaguar or not, but we're working hard to try to pull that off. We work entirely in terms of core graphics.
Instead of assuming that you've got a Quick Draw port around, you must pass in a core graphics context ref so we can do our render. And the ultimate benefit here is that we can just take that context, go right to our low-level rendering routines, and blit really, really fast. So these are much, much faster.
Instead of taking Quick Draw types, they all take floating point coordinate types. We've got a hi-rect and an hi-point definition. I think you can find those in Carbonevents core.h. But fundamentally, these are just rectangles and points that use a floating point parameter. So we get rid of the 16-point coordinate space issue.
And they also do not have theme erase procs. These are set up to work with the HIView model. If you had a chance to see that earlier today, you know that in HIView, there is no erase. You only draw. Therefore, our new Appearance Manager APIs just draw. So you never have to worry about the pattern misalignment problems or providing erase procs. And the fact that we're putting some new APIs in place allows us to solve some of the other nagging problems, like right now, there's no way to draw a pulsing button with the appearance primitives, whereas these new APIs will let you do that.
So, jumping into the new features for Jaguar. We've got a toolbar control. Really, really full-featured, handles a lot of stuff for you. It's going to handle all the event handling stuff, drag and drop, all those things. Puts up a configuration sheet automatically, lets the user rearrange all the toolbar items. All of that is automatic. Does the animation when appropriate. It's totally customizable. You can add your own toolbar items. You can add your own toolbar item views and all that kind of stuff. And perhaps one of the best things about it is it's got an auto-save feature.
If you create a toolbar and you want it to auto-save, you can just configure it up that way, and I'll show you how to do that in a second. You configure it up that way. And any time the user makes a change to the toolbar, the toolbar will automatically save those changes to your application's preferences. And the next time your application launches and you create another one of the same toolbar, the toolbar will go to the preference, fetch the settings, and you'll have the user's settings automatically. You don't have to make changes. manage that yourself.
So the toolbar isn't really a control. So it was a little bit of a misnomer on the other slide. But it's got its own header. It's hitoolbar.h. And the reason I say it's not really a control is because the toolbar is implemented with the model view controller sort of architecture. So the toolbar that you generate via the APIs is really the controller.
The control or the view will get created dynamically as it's needed and put into the window. And I'll talk a little bit more about that later. One other thing to mention, like I said, you can create custom toolbar item views to represent wholly custom items in your toolbar. All of your custom toolbar item views must be HIV savvy. So if you need to, get the DVDs, check out the HIV session. Ed talked an awful lot about that.
So creating a toolbar is really, really simple. Let me run through how to do that. We've got an HI Toolbar Create API, really creative, but, you know, gets the point across, right? The first parameter you pass to that is the toolbar's identifier. This identifier uniquely represents that toolbar within your application space. This is important for two reasons.
The first reason is that you might actually have two different kinds of toolbars in your application, one for document windows and some other kind of toolbar for some other aspect of your app, you know, maybe your preferences dialog or something like that. And these identifiers can keep those toolbars distinct. The second reason is that when a toolbar is auto-save, is set to auto-save, we use this identifier as part of the key to which we save the data in your preferences.
So as you can see, I've set up this toolbar to autosave. The next parameter is actually a list of attributes for you to configure the toolbar. In this particular case, I've said, you know, hey, toolbar, please autosave for me, and please allow the user to configure the toolbar.
There's a whole bunch of different options you can pass in, but these are probably the most common ones. And when you call it, you get an HIToolbarRef. And HIToolbarRef is just an HI object subclass that represents the toolbar, and it sort of floats off in limbo. This is not necessarily a view--it is not a view. And at this point, it's not even associated with any window yet.
Now, the next step I'm just going to have to hand wave over. I'm going to talk more about toolbar delegates in a few minutes, but fundamentally what I'm doing here is I know I've already created a toolbar delegate, and I'm just going to associate that delegate with the toolbar.
So once you create a toolbar, like I said, it's off in limbo someplace. Now you need to put it in a window. You do that with setWindowToolbar. So in this case, I'm taking one of my document windows and slamming the toolbar into it. Now, setWindowToolbar has two kind of cool points about it.
One is that an individual toolbar can actually be associated with lots of windows. So you can call setWindowToolbar repeatedly, and the toolbar will make sure that the view representation of that toolbar will stay in sync amongst all those windows that share that toolbar reference. The other important thing to note about setWindowToolbar is that since an HI toolbar is an HI object, which also implies that it's a CF type ref, setWindowToolbar will retain the toolbar. And that becomes important in just a second.
So after you've associated a toolbar with a window, you probably want the toolbar widget in your window structure. You can do that with changeWindow attributes. And we've implemented a new kwindowToolbar button that's an attribute for you to turn on. We don't do this automatically for you because we know there might be some cases when you don't want that toolbar to be toggleable. A preferences dialog, for instance, might always need to have the toolbar. So in that case, you wouldn't want to change window attributes to put the widget on. It would be misleading the user that they could turn the toolbar off.
Now, most toolbars allow the user to either configure it or add items to it via drag and drop. And the toolbar will only do its drag and drop work if the control manager's standard drag and drop handling is turned on. So to turn that on, you call the longest name in the toolbox. And I'm not gonna say it, 'cause I'll say it wrong. And turn on standard control manager drag handling.
And since I'm kind of in a toolbar creation, window creation kind of mode, I want to make sure this toolbar is shown by default in my particular window here. So I call this Show/Hide Window Toolbar API. Not only can that programmatically hide or show the toolbar, but the third parameter to the API-- in this case, I'm passing false-- represents whether or not I want the toolbar to animate as it comes down from the top of the window. So in this particular case, which is kind of example code of creating a window the first time, I just want to show the toolbar and be done with it. It doesn't need to animate.
And finally, I'm going to release my toolbar instance. And this is okay, like I said, because when you call setWindowToolbar, the window had retained the toolbar. And my call of CF release here just says, okay, I'm done with my local variable, but that toolbar still exists in the window. You might not want to release it. Maybe you want to keep a global around with a reference to your toolbar, but you really don't need to because you can actually get any toolbar that's associated with any window if you want to.
So now I've got to come back to toolbar delegates. So in the model view controller architecture, the toolbar instance is pretty much like the controller. And it's going to create views as necessary and put them in your window. And the delegate is kind of the boss of the toolbar in that it decides what exactly can go in that toolbar. So it's kind of the bridge that allows you to plug the model into the controller.
Now, as the boss of the toolbar, it gets to do a couple different things. It gets to define the default set of toolbar items that can be in that toolbar. And that default set is what would appear in a toolbar the very first time one of those toolbars is ever created by a user that hasn't saved off the auto-save configuration stuff in their preferences.
And it's also represented in the toolbar configuration sheet. Down at the bottom of the configuration sheet, there's usually a default set item that the user can drag as a whole and drop it in the toolbar to configure it. Well, the delegate gets to decide what that default set is.
The delegate also gets to decide what the whole allowable set of items are for a given toolbar. And that allowable set is also reflected in the configuration sheet because each of the allowable items will be shown in the configuration sheet so the user can drag them out and add them to the toolbar. And finally, the delegate's third responsibility is to actually create the toolbar items.
And we do this via a delegate simply for efficiency. There may be times when a user has just decided to not show any toolbars anywhere in your application, even though you've associated them with your various windows. And therefore, you don't want the overhead of having all these toolbar items around in memory because they're not being shown. So the fact that we've got a delegate that is specifically asked to create toolbar items when they're necessary means you're not going to have any of that overhead.
So Delegate does its job by handling four different Carbon events. The first two, getDefaultIdentifiers and getAllowedIdentifiers, lets the delegate get to decide what shows up in that config sheet, what's allowed, what's the default set. And the delegate simply responds to this by passing back a list of item identifiers, which are kind of like toolbar identifiers. They're just these unique strings which uniquely identify the types of items. Oh, actually, before I go any further, one thing I forgot, let me see if it's on the previous slide.
Yeah, the toolbar delegate can be any HI object. It really doesn't matter what it is. The toolbar could care less. The toolbar just needs an HI object so it can send these events to the delegate. So you might decide that the delegate is a window, or it could be your application HI object, or it could just be any other HI object you feel like devising, or it could even be the toolbar itself, just so long as the appropriate handlers are on that HI object.
Okay, so coming back to the delegate. The other two events the delegate must handle are createItemWithIdentifier and createItemFromDrag. And these are the hooks that allow a delegate to decide, give the delegate the chance to create the various sorts of toolbar items. And you handle it simply by creating a toolbar item, putting it in the Carbon event, and returning so that it gets passed back to the toolbar. The toolbar will then take that item and add it to the view.
So what exactly are these toolbar items? Well, fundamentally, they are things that are represented in the toolbar, but they're not the views themselves. They're just these, I don't want to say ephemeral, but they're just these things out in space that represent what a toolbar item could be, could be visually represented as.
And every toolbar item has four different pieces of data associated with it. The first is the identifier, the toolbar identifier. Like I mentioned before, this is just a unique string that uniquely identifies that toolbar item's purpose. That string might get saved out to your application's preferences file if your toolbar is set up to autosave. And you, the delegate will receive that string when asked to create a particular toolbar item.
Toolbar items also have labels and images. I mean, obviously, you want all of your toolbar items to show something in the toolbar. And they also have a command ID associated with them. Now, this command ID is sent out in much the same way as a push button sends out a command ID.
When you click on a toolbar item, it sends that command out. It propagates up the view hierarchy, and somebody's going to handle it. Actually, something Ed showed in the previous session is also in here. When you click on a toolbar item, before we send out the command ID, we send out another Carbon event. And I forget what it is specifically, but it's something like K toolbar item pressed or something like that, which gives you a chance to hook in to a toolbar item getting clicked before this command gets sent out.
Toolbar items can also have submenus associated with them. And if you've got a submenu associated with the toolbar, you can also send out a toolbar item. And that's something that's really important. So, let's look at this. It's going to show up two places. We will display it and track it automatically when you click on the toolbar item. And in the overflow menu that sometimes shows up if your window is too narrow to fit all your toolbar items, your items submenu will show up as a submenu of the menu item that represents that toolbar item in the overflow menu. I said that right. Trust me.
Every toolbar item also has a set of attribute bits to allow you to configure its behavior. And these are-- I think all of them, if not the majority of them. The first is whether or not you want to allow duplicates of that toolbar item. Now, most toolbar items only need to show up once in a given toolbar.
For instance, if you're in the Finder, you really only want one trash can in the Finder's toolbar. But there are some sorts of toolbar items, such as the URL item that Ed added in his demo, that you want to be able to show up multiple times, because it's not the item itself that is the important part. It's the function that happens within the item, and that function might be different, even though it's the same toolbar item, at least by ID.
Another attribute is the Cannot Remove attribute. The best example of this is probably in the System Preferences. The Show All icon in the top left-hand corner of System Preferences is always there. You never want users to be able to remove it, because if they remove it, there's no way to show all. So one way to do that would be to set the Cannot Remove Toolbar Item attribute.
And likewise, that Show All button is also all the way on the left-hand side of the toolbar, so that the user always has a very consistent place to go to to look for it. We've got an attribute called Anchor to the Left Side that you can use to get that same sort of behavior. Now, some toolbar items are separators, and that has two basic meanings. One, it allows the toolbar item a little bit more flexibility when it draws. But two, it will show up as a separator in the Overflow menu if that needs to be displayed.
And finally, kind of a subtle point, like I was saying before, when a toolbar item is clicked, it's going to send out a command to your control hierarchy. But it can generally do that in one of two ways. By default, the command will first be sent to the toolbar item view that was clicked, and then that view's parent, and then that view's grandparent, and so on up the containment hierarchy. And if nobody handles it, it'll eventually get to the application. But another very popular way to send commands is through the user focus chain.
So you can set a toolbar item to please send your command to the user focus. And when you click on it, instead of going through the view containment hierarchy, it's actually going to go to the user focus, which is, you know, an edit field or some other control that's got focus.
One good example of the use of this might be a cut toolbar item that's going to do a cut copy paste sort of operation. You don't want that to go to the normal view containment hierarchy. You want to make sure that hits the user focus so it actually performs the right cut operation.
And of course, the system is going to provide the standard sorts of toolbar items for you. You know, we have all the basics. We've got a space separator line, the flexible space, which allows things to show up on the right-hand side of the toolbar, and a configuration button, which, when clicked, will automatically bring up the configuration sheet to let the user rearrange everything.
And each one of these has a unique identifier string that's in the header that you can use to specify that. Perhaps in your delegate, you know, you want to make sure that the default set of items has maybe one toolbar item off on the left, and then a separator, and then a space, maybe another item in a flexible space. You can do that with our standard identifiers.
But toolbar items are more interesting when they actually have your application content in them. So let me show you quickly how you would create a toolbar item with your own custom icon and label. It's pretty straightforward. You call HIToolbarItemCreate. And the first piece of information you need to pass to it is the unique identifier. So before I go any further, this sort of code might be something you would write in your delegate to handle the item creation Carbon events, for instance. So you give it its unique identifier.
And then you configure the various attributes you want. In this case, I'm creating a sort toolbar item. And I want to make sure that goes to the user focus, so I set that attribute. And it hands me back an HIToolbarItemRef, which is an HI object like any other.
And then I go and configure it a little bit more. I want to say, "Okay, hey, your command ID is this." And I've got my sort command already defined in one of my headers. So I assign that to the toolbar item. And then I give it a label, which is simply a CFString. And this is probably a bad coding example, because generally, since this is a string displayed in your interface, you would not want to use a constant like I'm doing here. You want to make sure you get a localized string from your bundle and all that.
And then you want to set the image for the toolbar item. So in this case, I've gone off and rendered my image, perhaps with-- I think the API name is, you know, CGCreateImage from Pixmaps or something like that. You might have another way that you want to create your image. But you associate your image with the toolbar, and that's going to get rendered automatically.
But you may have even bigger needs than that. Not all toolbar items are just simple icons with labels underneath them. Some might need to be more complex, such as edit fields or the finder's view style switcher, or who knows what you're going to dream up. We allow that as well. And the way you achieve that is through subclassing each toolbar item.
So by default, every toolbar item--well, step back one bit. When a toolbar item needs to be represented by a view, that toolbar item will be sent a Carbon event asking it to please create the view for me. And by default, the view that's created is simply one that draws an image and a label underneath it.
If you want to change that behavior, you subclass HIToolbarItem to override the KEventToolbarItemCreateCustomView Carbon event. And now you can create whatever you want. The only condition we've got is that it must be an HIView. It must be HIViewsavvy, be ready to draw with core graphics, be ready to call the HIView bounds and frame getting APIs as necessary, and do all that stuff.
And the other requirement is it needs to support KEventControlGetSizeConstraints. That's a Carbon event we added to the control suite in Jaguar, which is used by the toolbar when it's dynamically repositioning things. You know, the toolbar needs to know how big a given item is so it can shove stuff over.
Figure out when to display the overflow menu and things like that. So you need to make sure your view supports that Carbon event. One important thing to note, on the Jaguar WWDC seed that we gave you, I don't think any of our standard views support this Carbon event yet. So if you're going to try to take one of our existing views and slap it in a toolbar and you see weird things happening, it's probably because you need to support this Carbon event. So wire that up too when you're playing around with the seed.
So in general, the toolbar is going to handle item creation for you basically by sending Carbon events to the delegate. And that will probably serve 95% of your needs. But there may be some cases where you find that you need to add some items on the fly, or perhaps you don't want to use the toolbar's auto-saving feature. You want to load your toolbar items from someplace else, and so you need to build them up on the fly. You can do that with a set of very simple APIs. You can append items, insert them anywhere in the toolbar, or remove them if you need to.
And to make your life even easier, the window is going to take care of some of the toolbar management for you automatically. So, for instance, if you want to support a hide/show toolbar item in your menu bar, or maybe even a configure toolbar item in your menu bar, you can put standard commands behind those menu items in your menu bar, and when the user chooses those menu items, the window will receive the event and automatically bring up the configuration sheet in the toolbar, or hide or show it as appropriate. And you're not limited to just putting these commands in the menus. You can put those commands in other controls if you want.
So another cool new control we've introduced for Jaguar is the combo box. Totally full featured. We looked at many other combo box APIs. We tried to get the best of every world we possibly could, talk to our HI designers, figure out the right way to display the list. And what we found out was there's about 10 zillion permutations of how to do these things.
So we allow you to do just about all those. You can auto complete the text. You can have the list sorted. You can control the list dimensions. You can control when the list pops up and all these kinds of things. All through a very simple API. Now fundamentally, you create one of these combo boxes through HIComboBoxCreate.
Again, a very boring name, but it gets the job done. So one important thing to note about this API before I describe it anymore is the fact that it's going to create an HI view. And by default--well, not by default. When you create an HI view, it's not associated with any window. It's out in limbo someplace.
And it's not going to be displayed. It's not going to be displayed or even be able to be tracked or anything like that until you put it in a window. And you simply put it in a window by embedding it into the appropriate parent view in whatever window you want to put it into.
So when you call HIComboBoxCreate, you need to supply a couple things. You give it some default text for the edit field, if you feel like giving it default text. And you give it an array of items to populate the list. And that's pretty much about it. You can pass in attributes to vary behaviors and stuff like that.
But we've also got a suite of APIs that let you customize the behavior on the fly, alter list items on the fly, add and remove them, and do various things. It's really, really easy to use. This screenshot, I think I threw something together in about two minutes to make the screenshot work. So it's really, really simple.
So another view that I talked about is the scroll view. We finally got a scroll view in Carbon. It's really, really cool. It does all kinds of stuff for you. It makes your life really, really easy. I'm probably going to change data browser to start using the scroll view to simplify some stuff, and that will allow me to fix data browser bugs a little bit more quickly, too. So that will be good for everybody.
So simply speaking, the scroll view is a way to allow the toolbox to scroll some larger canvas, some larger image, or maybe a text flow or something like that. Within a small space. And the toolbox is going to manage the scroll bars for you automatically. It'll deal with everything if the scroll view gets smaller.
It's just really, really easy to use. You create one of these scroll views with hiscrollviewcreate. And again, because this is an HI view, it's not bound to any window. So then you would embed it. You would, yeah, I guess embed's the right word. It's any parent view that you wanted.
Uh, let's see. All right, so-- Oh, right. And after you've created this scroll view, You need to embed your canvas or your content into the scroll view. And your content is any other view, so long as that view supports a certain protocol. Now, what do I mean by a content or a canvas? This kind of gives you an idea of what I'm talking about.
The scroll view is what I showed on that first slide. It's just the fully opaque part of the picture here with the image in it and the scroll bars. That's the whole scroll view. Whereas your canvas is the larger flow, i.e., the image that we're showing behind that. And the way this all works is simply by making sure your Canvas view supports the scroll view protocol. So the ScrollView protocol is a set of three events, and the first of which is, "Hey, Canvas, get me your info," and that's the KEVENT_SCROLLable_GetInfo Carbon event.
So when your canvas receives that event, you need to supply a set of information that describes how big the canvas is. You're going to tell it literally the pixels wide and pixels high, or in fact it doesn't even have to be pixels. It can be in whatever unit you want.
You need to tell where you're scrolled to right now, how much you want to scroll, if the user clicks in the arrows in the page up and page down parts of the scroll bar and stuff like that, as well as how much are you showing right now. And that allows us to use proportional scroll bar thumbs and things like that.
And so you'll receive this event any time the scroll view needs to rebuild its scroll bars or rethink the way it's laying out and stuff like that. The next event your canvas is going to receive is the KEventsScrollableScrollToCarbon event. And simply speaking, this Carbon event is sent to your canvas as a request to scroll now, do it now, right? The user has just clicked the thumb and dragged down.
And the scroll view knows that because it owns the scroll bar and has an action proc associated with it. So then it sends your canvas the ScrollToCarbon event. It knows how many units you need to scroll and whatever your unit space is. And so your job is just to move your content.
And so that might be as simple as redrawing or to make your life even easier, we have this HIViewScrollRect API. Ed showed off the power of this API in one of his demos, but the subtlety might have not quite been apparent. Essentially what this API lets you do is take any view and say scroll it by this amount.
And the HIView mechanism will do the right thing with respect to the amount of scrolls that you need to scroll. So it will do the scroll rect to speed and efficiency as best it can. It will kind of do a scroll rect like operation on all the bits that are easily scrollable.
But then it takes all the revealed areas and makes sure to invalidate those. As well as making sure that any content that happened to be on top of you, like maybe a peer view that was above you in z order. Or, you know, a peer of your parent, anybody that's above you in the HIView z order. And invalidate that area because that needs to get redrawn. So in an optimal case, it's going to do a really flas-- really fast blit. But if it needs to, it'll jump through a few more hoops to make sure invalidation happens.
So the last Carbon event is the k-event scrollable info changed Carbon event. And this is not one that your canvas is going to receive, but it's one that your canvas must send. There's occasionally times when a canvas changes in some fashion. For instance, your canvas might be a huge text editing engine of some type. And as the user's typing, the height of your total pay--or all of your text is getting longer and longer and longer or maybe the user goes and changes the font setting and that completely causes re-layouts.
Now your document's twice as long, twice as wide, who knows what. But now you need to make sure the scroll view scroll bars are updated to reflect reality. And you do that by sending the k-event scrollable info changed Carbon event to the scroll view. The scroll view will see that and say, "Ah, okay, I know you've changed." So it immediately turns around and says, "Hey, give me your info." And in fact, it might not even immediately turn around and ask for your info. It might defer that until later if it needs to. One important thing to note-- So, we haven't implemented this in the Jaguar seed.
In fact, we just found out about the need for it last week--or earlier this week, I guess, as we were getting ready for one of the demos. So we'll have this in by the time we ship. So another really, really cool thing I want to talk about is drawers. Yeah, we've got drawers in Carbon. It's really cool. You know what they are. You've seen them in mail. You want to use them, and now you can. And it's really, really simple.
There's not a whole lot of new APIs in the drawer API. In fact, you create a drawer like you create any other window. You call create new window. And we've got a brand new window class called kDrawerWindowClass that defines, you know, the right frame and all the right look and stuff like that. So you create it. And then in all likelihood, you want your drawer to have the appropriate theme background. And we've got an appearance manager theme brush called kThemeBrushDrawerBackground that you can associate with your window and it'll do the right stripey pattern.
And then after you've created the drawer, you need to bind it to a parent window. And that parent window is the one out of which the drawer slides. In this case, I've got a document window in my app, and I want to associate the drawer with it. So it's just a really quick and easy step.
And after that, you're pretty much done. Now, you might want to configure it a little bit, and we've got a couple configuration APIs if you need to use them. But generally speaking, just creating the drawer and binding it to the window is enough. So the first configuration API is setDrawerPreferredEdge.
By default, when a drawer slides out of a parent window, the edge it slides out from is based on two things. It's based on the system direction, whether it's a left-to-right language or a right-to-left language, and it's also based on the amount of screen real estate, right? If a drawer would normally try to slide out of the right side, but there's not enough room on the right side, it'll shoot out the left side or whatever. But if you want to change that behavior, or even make the drawer slide out of the bottom or even the top of your window if you have that need, you can use the SetDrawerPreferredEdge API.
Now, the other configuration API that's specific to drawers is called setDrawerOffsets. Left to its own devices, a drawer will appear with its top edge aligned with the top edge of your window's content and the bottom edge aligned with the bottom edge of your window's content. But if you need to change this behavior, you can do that with set drawer offsets.
You know, for whatever reason, if you have 50 pixels down at the bottom of your window that you don't want the drawer to be sort of overlapping linearly with, you don't have to. You can just say, "Hey, have your bottom edge be 50 pixels up from the bottom." And also by default, when you resize the parent window, a drawer will resize with the parent window subject to the offsets you've supplied. If you want to constrain the drawer with respect to its resizing, you can do it one of two different ways.
If you call setWindowResizeLimits, which is just a normal window manager API, you can set whatever resize limits you want, min and a max, and the drawer will respect that. And likewise, we send out a pair of Carbon events, getMinimumSize and getMaximumSize Carbon events, so that if you need to determine your drawer's minimum and maximum size dynamically, you can just attach a Carbon event handler to your drawer window, listen to those Carbon events, respond with the right values, and the toolbox will do all the right stuff for you.
So drawer event handling is kind of a three part story. The best version of the story comes when you use the standard window handlers and as much of Carbon events as possible. Because when you use the standard window handlers, all the interaction happens for free. But if you need to use more wait next event driven Event flow. You can do that too. You just need to make sure you handle it like any other window. You know, you call handle control click, find control, you know, do all those various things that you're used to doing.
But one thing to keep in mind is that the K event window draw content Carbon event is required to make the drawer work properly. But that's got kind of an asterisk on it. So first some background. When a drawer first slides out of its parent window, it needs to have content in it. It's not like you're going to receive an update event as it's sliding out and get a chance to paint each stripe of each frame of the animation as it slides out. That'd be kind of crazy.
So we send this Carbon event to the drawer in an effort to pre-render the drawer so that it can slide out in one fell swoop and be nice and smooth and have a pretty image there already. Now the big asterisk I was talking about with respect to this is that if you're using a full-blown compositing window, you've turned HIView on for that window, you don't need to worry about the draw content Carbon event. Carbon event, because HIV is going to take care of all the drawing for you.
And in fact, draw content is not even required if you're a standard window handler--sorry, if you're using the standard window handler on that drawer and all you have is controls in that window, the standard window handler will make sure that the draw content Carbon event gets serviced automatically. So that'll just happen for free.
And so you've created a drawer, you've bound it to the window, and in some fashion you need to make it accessible to the user. You need to slide it in and out. There's two different ways to do that. We've got a high level API and a low level API.
The high level API is ToggleDrawer. Really straightforward. You just pass in your drawer and that's it. The drawer already knows its state. It knows if it's closed, it knows if it's open, and it does the reverse. It's going to animate and slide out. The important note about ToggleDrawer is that it operates completely asynchronously. So as soon as you call ToggleDrawer, that API is going to return, control goes back to your application, and the drawer is basically going to slide out on a timer.
So in order for the drawer to actually slide out, you need to make sure you go back into your normal event processing. You know, call wait next event or run application-- well, if you called run application event loop, you don't have to worry about this. But call wait next event is appropriate to make sure timers fire and things like that happen so the drawer slides out.
The lower level APIs give you not only configurability, but they also allow you to slide the drawer synchronously if you need to. OpenDrawer and ClosedDrawer obviously do what they say. You just pass the drawer and you pass some configuration information. You know, you can make it synchronous if you need to, if you've got that need.
So sometimes it's not enough to just open or close the drawer. Parts of your application may need to update its interface based on whether the drawer is open or closed. You might want to change the state of a menu item in the menu bar to switch from open drawer to closed drawer, or who knows what. And to that end, we've added a set of four Carbon events that you can listen to to basically be notified when something's happening to the drawer.
When you call an API like toggleDrawer when a drawer is closed, or if you just call openDrawer, we're immediately going to send a K event window drawer opening Carbon event to the drawer window, and I think to the application as well. And if you're listening for that, you can do whatever updating you need to do. And likewise, once the drawer has completely finished opening, we're going to send the K event window drawer opened Carbon event, past tense. So you can do whatever you need to do there. And likewise, we've got two other Carbon events for drawer closing and drawer closed as well.
So the Services menu is really, really cool. It's not specifically a new Jaguar feature per se because we shipped it in Mac OS X1. But it's cool for two main reasons. It's really cool for users because they may want some functionality in your application, but for whatever reason you couldn't get it out in this release. But there might be a service out there that can provide that functionality for them.
So it kind of in a way gives users, you know, free functionality in your application. And it's really cool from a developer perspective because it's really easy to support. And at the same time, it gets this free functionality out there. You know, you can now say, "Hey, I'm services savvy. I'm integrated with this app or that app." Or in this case, you know, you're integrated with mail. You can now mail text directly out of some word processing document or something like that.
So there's two pieces to services. The service clients, which are your applications, it's your productivity app. And there's also service providers. First I want to talk about service clients. Service clients do their work via a set of three different Carbon events that seem very much copy/paste like. Well, because they're basically copy and paste operations.
But there's one other Carbon event that's important and isn't exactly copy and paste like, and that's kEventServiceGetTypes. Your application will be sent this GetTypes Carbon event when a service is considering looking at your interface. Essentially when the user pulls down the service menu and the service's engine needs to figure out which menu item should be enabled and disabled and stuff like that, they'll send this Carbon event to your application.
So your application has two different responsibilities when it receives this Carbon event. You need to explain what data you can immediately export now. And that's fundamentally asking the question, what does the user have selected right now? So if you're a basic text editing application and the user's got some text selected, you might say, hey, I can give you text and I can give you Unicode text and styled text and who knows how many other varieties of text you want to offer.
You can hand that over. But if you're a really, really rich, full-featured text editor and maybe you support QuickTime movies, and in fact the user's got a QuickTime movie in your document and it's selected right now, and you received this Carbon event, you'd not only want to return text if that's what's selected, but you'll also want to return movie. And likewise, the other responsibility you have is a definition of what flavors you can receive right now.
So in this case of a really, really full-featured text editor, you might say these three varieties of text and you might say movie and JPEG and PICT and various other image types and maybe sound or whatever, but it's just up to you to say, hey, these are the things I could accept in sort of a paste-like operation. And the way you report this information are via arrays of strings. This event comes in with two mutable arrays that you can look at and manipulate. And it's your job to just fill that up with the appropriate strings.
So what are the appropriate strings? Well, traditionally, Carbon applications are not necessarily the same as the other types of text. So you can see that the two options have dealt with the notion of flavors in terms of OS types, right? PICT, M-O-O-V-T-E-X-T or whatever. But in order to be compatible with the existing services implementation, which was originally born from Cocoa and whatnot, they use these NSStrings to represent the flavor types, you need to convert those OS types into the appropriate string.
And you do that with create type string with OS type. So if you can accept as paste text, you would simply call create type string with OS type, paste text, it's going to hand you back a string, stick that string in the array, and you return from your Carbon event handler.
So then there's two events that get sent to you when a service is actually invoked. The first is KEventServiceCopy. And this may not always get sent to you, but I'll explain more why later when I talk about service providers. KEventServiceCopy is a request for you to package up the selection and hand it off to the service.
So in this Carbon event, you'll find a scrap ref. And just like using the normal Scrap Manager APIs in Carbon events, you just package up all your data, put it in that scrap ref, and return. And then the system goes off and talks to the service. And then you might also be sent a K event service paste Carbon event, which is a request to, hey, I've just modified this data for you, or actually the service has just modified this data for you. Please, please put it into your interface. So you simply handle this event by taking the scrap ref out of the Carbon event, extracting whatever data you want out of it, and replacing the current selection with that data.
Service providers can also be Carbon applications, and they take maybe a little bit more work to put together, but only because you need to modify your Info.plist. There's two things you need to do to your Info.plist. One is something you probably have already done if you have an Info.plist, and that's sort of make sure you've got a CFBundle identifier. The CFBundle identifier is simply the bundle packaged app replacement for the notion of an application signature. You probably have one already, but if you don't, you need to make sure you add one in order to be a service provider.
The second thing you need to do is describe the set of services your service can perform. And you do that by adding the NSServices key to your Info.plist. The data in this key is an array of dictionaries where every item in the array is one service that your overall provider can perform. Every dictionary has four pieces of data.
It's got a message identifier. That's simply an identifier string that uniquely identifies that service to your provider, right? I mean, if you're a service that can do four different things, you need to have four different unique ID strings because you're going to be sent this identifier when asked to perform that service. So it allows you to sort of uniquely tell what thing the user's picked.
The next thing you need to put in the dictionary is the menu item text. Obviously, it's going to show up in the menu. And then you have two different lists. The list of send types and return types. Send types are the types of data that you can accept into your service.
You know, in the case of the example I was showing before, mail text, it would say, "Hey, I can take text and Unicode text and various other types." The return types are the list of data types that you can actually hand back after you've performed whatever your service is. And it's possible that you might not even have any send types or might not even have any return types, but you'll definitely have one or the other.
Some services are only data returners. For instance, there might be a service which is "Paste my signature into this whatever it is," right? "Paste my signature into your document." Whereas you might have the inverse, which is the mail text service item, which is, "Hey, the user's got something selected. Just go do something with it.
I'm not going to hand them anything back. I'm just going to do something with it." So once you've created your service provider application and built up its Info.plist, you need to make sure it's put in the right place. Otherwise, the service engine won't be able to find it.
It can pretty much go in any of the standard system paths that end in /applications or into system library services. And that just simply makes sure that the engine knows where to look. The engine doesn't want to scan every single place on your hard drive looking because that would just take too long.
So when your service has one of its menu items chosen, your service app will receive the KEVENT service perform Carbon event. It's got two pieces of data in it. The message identifier, which is that string that uniquely identifies that particular service that your provider can perform. And a scrap ref containing the data from the application, the front most application that the user was interacting with. And that gives your service a chance to do its work.
If you're interested in processing the incoming data, you can do that. If you have outgoing data, you can put your outgoing data into the scrap ref. But like any good scrap ref client, before you modify a scrap ref, you need to call clear scrap first. So you clear scrap, modify the scrap ref to have the new data, then you return, your service is done. The front most application's been affected in whatever way you wanted them to.
And like I said, services is nothing new for Jaguar. It's been out there since 10.1. We try to put HeaderDoc in all of our headers as much as we can to make it really easy on you guys. And services is really, really well documented in Carbonevents.h. And we've got some documentation online on the developer Applecom website.
It's in the menu manager someplace, and I'll blab a few more seconds in case you're copying down this URL. Alright, so the next really cool thing for Jaguar is our expanded keyboard navigation. And it's kind of a big topic. It goes beyond just controls, even though that's what I'm showing up here.
So keyboard navigation is sort of born out of Section 508 compliance. If you haven't heard that term yet, Section 508 compliance is a set of requirements in order to make system software and applications accessible to users that need alternative means to either manipulate or use a computer and stuff like that.
And Section 508 says, hey, you can't force people to always use the mouse. So we try to make it so that users can use the keyboard if that's all they want to do. And this isn't just for accessibility purposes. This is just a really widely requested feature, right? I mean, I'm a huge keyboard user myself, and I hate the fact that I've got to take the hand off the keyboard and go use the mouse and do stuff like that. And I know a lot of you have the same opinion. So this just gives, you know, everybody a cool feature that they can use.
And you can do a bunch of things with the keyboard now in Jaguar. You can navigate the menu bar, bring up the menus, switch between them, go up and down items, choose items, stuff like that. You can switch between windows in two different ways. You can switch between just the windows in an application, or you can switch between all windows on the system in the appropriate Z order.
And most importantly, you can interact with virtually every control on the system via the keyboard. The reason I say virtually is that there's some controls that just don't warrant interaction, like the progress bar spinning arrows. You know, you can't click on those anyway, so of course those don't support keyboard now.
So one of the cool bits about the whole keyboard nav story is that most of it's for free. The menu stuff is gonna happen basically behind your back. Your application's never gonna need to worry about it. The menus will just pop up in your application. You can navigate them without any work.
The only people that will need work are those that do custom menu definitions. And I know there's not a lot of you out there, but we've got some documentation in menus.h. One of the existing menu manager Carbon events, and, you know, I'm spacing on it right now, but if you have this need come up after me, I can tell you exactly which Carbon event it is. It's one we've been sending out for quite a while. We repurposed that to allow us to keyboard nav even in custom menu devs, so you can do that too.
Window keyboard navigation is also mostly for free. We tried to make this as free as possible. And in fact, it will work in your application, unless you're doing something so amazingly obscure that it trips up our code. But if you need to customize its behavior because you're seeing strange things go on, like, okay, the right window didn't come forward because I'm doing some weird things with window groups or something like that, and you find that you need to customize our behavior, you can do that with a pair of Carbon events. We have a focus next document window and a focus next floating window event that gets sent to the application.
There are some fairly strict requirements to how you can respond or what you can do when you respond to one of these Carbon events. And we've got that outlined in carbonevents.h by each of these events in the header. So take a look at that. Most of you will never need to worry about this, because the window switching is just going to happen for free.
So now that we have toolbars in Windows, we need a way to key nav up to the toolbar. And we don't want the toolbar to be part of the normal focus chain of controls, because generally users just use it to click on and whatnot. But we've got a hotkey now that can transfer the focus from the content area of your window up to the toolbar, and another hotkey which transfers it from the toolbar down to the content area. And in fact, you can see them right there.
K event window focus toolbar moves it to the toolbar, and content moves it to the content. This also is going to happen behind your back, and you may find you need to customize the behavior, because our implementation that we do for you automatically does not support any focusing you might do in your totally custom content areas of your application. We can only deal with controls and views.
So we will, of course, take focus away from a text field in your content area and move it up to the toolbar. But if you're rolling your own text editor for your own text fields, or, you know, who knows, you've got your own list box that can take care of that, you've got your own list box that can take focus, if that's not implemented as a control, we have no way to tell that thing, "Hey, stop focusing." So it's your responsibility to listen to the K event window focus toolbar Carbon event and say, "Oh, okay, the window's about to focus the toolbar, so I need to make sure to relinquish the focus on my content area." Or vice versa, right? The focus is in the toolbar, and the user presses the hotkey to bring it back to the content.
All we know how to focus are controls and views, and if there are none of those in your window, well, we're not really going to focus anything. So you need to make sure to listen to the K event window focus content Carbon event to focus any totally custom stuff you do.
Now, like I said, lots of controls are keyboard navigable, and we try to make this as easy to adopt in your own custom controls as possible. And since HIView, we're trying to make HIView a lot more prevalent, we think a lot of you will need to adopt this functionality. Let me give you a little bit of background first. Before everybody freaks out thinking that, "Oh my gosh, I'm going to be tabbing everywhere, and I'm not going to be able to get to my text fields." No, there's actually two modes in Jaguar.
There's the traditional keyboard focusing mode, which only focuses traditionally focusable controls, like, you know, edit fields, list boxes, and things like that. And then there's this full keyboard nav mode, which says, "Focus everything. Try to focus everything possible, because I don't want to use the mouse." And in that case, we're going to focus everything: push buttons, check boxes, radio buttons, pop-ups, you know, what have you, tab controls, all that stuff. And you can toggle the setting two different ways. You can toggle it globally by going to the system prefs, and I think it's in the keyboard pane of the system prefs.
There's a full keyboard nav checkbox. You check that. Now all of a sudden, all the windows and all the applications that come up will be fully keyboard navigable. Or, you can actually toggle that setting kind of transiently on a window-by-window basis. There's a hotkey, Control-F7, that when a window is frontmost, and you press Control-F7, it toggles the global setting for the lifetime of that window. And if you want to toggle it back, you just hit Control-F7 again. in.
Okay, so now the nitty gritty. If you've got an existing C-Def and you want to make it keyboard focusable, that's pretty straightforward. We already have a control set focus part Carbon event that's sent to your control and we want it to try to focus. We've been doing this for a while, since 10.0, which is one of the standard control Carbon events we send out, but we've added a new parameter for Jaguar and that's the focus everything parameter.
Basically, that's an indication of what the focus everything full keyboard nav mode is with respect to the window that your control is in. So if you're a traditionally focusable control, like an edit field or something like that, you don't even have to look at the focus everything parameter, right? You're always going to accept the focus regardless of whether full keyboard nav is on. But if you're not one of these traditionally focusable controls, like some sort of custom button, you want to look at the state of the focus everything parameter. And if you're gaining focus, you want to make sure you only gain focus if the focus everything parameter is true.
However, if you're being asked to lose focus, perhaps because somebody's asking you to focus to the next part and you have no next part, you don't want to care about the focus everything parameter. You want to make sure you can always lose focus regardless of whether full keyboard nav is on.
So once you're focused, there's two things you should probably do. Obviously, the whole point of being focused is so you can respond to keyboard events. So the event you generally want to listen to if you're only interested in, say for instance, the push button is really only interested in the space bar because that's the equivalent of a mouse click. You would want to listen to K-Event Keyboard Raw Key Down. This just tells you at the lowest level, hey, this key was pressed. It gives you a chance to act on that.
And generally, for simple controls, you can act simply by calling HIV simulate click. HIV simulate click is pretty cool. You can think of it sort of as a wrapper around highlight control, right? Because to simulate a click on a push button, we want to say, hey, button, highlight your button part and then wait a short time and then unhighlight your button part and then pretend like it was clicked.
So in that way, it's kind of a wrapper around highlight control. But the best part about it is this API behaves the same as, say, handle control click, but it's not the same as, say, handle control click. So if you call HIV simulate click on a push button that has a command ID associated with it, that command ID will be sent out in the appropriate way for the button.
So since we're now focusing more, you might find when we actually ship Jaguar, I've got to get to a side note here, you might find that the focus order is a little off. In Jaguar, what we intend to do is a spatial keyboard navigation focus order determination stuff. And I've got an algorithm that mostly worked until I found an infinite loop.
So for the seed, yeah, for the seed that's turned off, basically now we do a really stupid focus order, which is based on peer order in the control hierarchy. And in fact, it doesn't even support backward focus, because I just had to submit the bug fix for the build. But when we actually ship, it's going to be a spatial navigation order. And we're going to determine that as best we can.
But you might find that our best determination isn't exactly perfect for your needs. So we want to allow you to customize the focus order. And you can do that two different ways. A parent is actually--sorry, a parent view, a parent control, is actually in charge of the focus order. And we have a focus order for the children within it.
To that end, we send a control get next focused candidate to the parent view whenever we're trying to move away from one control to another control. So it lets the parent sort of decide what's going on. So if you are, you know, like a user pain sort of thing, where you're organizing a group of other controls, and you want to determine their focus order, you can respond to that Carbon event. Another higher level way to do this is actually with a set of APIs. Given any view, you can say, "This view is your next view." And then the focusing subsystem will be overridden, and we'll make sure to go to the order you specify.
And likewise, you can say, "Given any parent view, please focus this child first." So, you know, if you have a control that you want to make sure is always focused in your window, you're probably going to get one of the routes and say, "Hey, this is your first child." And we'll make sure to start the order with that one.
So Carbon is far from dead. Carbon is still way alive, and Carbon will be alive for a long time. We're trying to throw all these new cool features out there for you. We want you to adopt them as best you can, because they're going to make great Aqua applications.
And besides, you've got a week before Episode Two comes out, and you need something to do between going to Toys R Us and buying toys, or at least I need something to do between those trips. And likewise, once you get there and you're standing in line, bring your power books and you can code this stuff too.
It's really simple. I swear to you, if you're waiting two hours for the movie, you can probably get drawers implemented in your application. We're making it as easy as we possibly can, so I want you to get out there and implement this stuff, and it'll make really, really great applications.
And we've already got documentation out there. On the--I don't know if this stuff is on the CD or not, or if it's on the site. Anyway, we've got preliminary documentation on HI objects. It talks about how to manage them, how to subclass them, and stuff like that. An HI view reference, which talks about some of the subtleties of using our new HI view and compositing mode and stuff like that.
And finally, we've got a reference on HI toolbar. And a lot of this stuff also, like I said, has HeaderDoc documentations in the relevant headers. So look there, you know. In all likelihood, you might find your questions answered smack dab in the middle of the headers. Now, some of the sessions on the roadmap already happened. But if you need a refresher course on either HI view or HI object, get the DVDs and take a quick ponder at those sessions. And we can't stress enough how much HI view and HI object and Carbon events are the future of Carbon.
All the new features we're doing are going to have hooks in terms of Carbon events. They're probably going to be HI objects. And if you want to alter their behaviors, you're going to need more than one. And you don't even know how to subclass these things. So, you know, I suggest you get on the ball there and start checking out those technologies.
We already also had a migrating to Carbon events session, which is a good refresher course if you need just the basics on how to install Carbon event handlers, how to deal with adding parameters to events, getting parameters out of events. And immediately after my session, we've got an improving performance with Carbon events session.
It's a good refresher course on, you know, okay, my app is still eating a lot of time. When I run top, oh, I see it's using 89% of the CPU. This session will show you how to eliminate those kinds of problems and be a really, really good citizen with respect to the processor. And finally, tomorrow, I'm going to talk an awful lot about accessibility. I probably should have put another session on here on the roadmap.
Before my session tomorrow at 3:30, there's the accessibility overview at 2:00 p.m. If you're planning on coming to my session at 3:30, please go to the 2:00 p.m. one first, because it's going to lay a lot of foundations and talk about the concepts. But then stay for my accessibility in Carbon session, because it's really, really cool.
You can now inspect interfaces of various applications on the system, whether or not they're Carbon or Cocoa. And you're probably going to want your application to be wired up to this, because, well, you might be limited from selling your application in only certain areas if you're not accessibility compliant. So please check that out.