Carbon • 54:31
The Carbon Event model is a simple, flexible, and efficient model for handling events in Mac OS X. This session provides developers with step-by-step instructions for migrating WaitNextEvent-based applications to the new Carbon Event model.
Speakers: Curt Rothert, Bryan Prusha
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it may have transcription errors.
OS X evangelist, Xavier Legros. Hi, everybody. Welcome to session 203, "Migrating to Carbon Events." This session is very important for all Carbon developers, not because Carbon events are very important technology in Mac OS X, but because it's the start of the future. This session is the beginning where we're going to be introducing brand new features for Carbon developers. And for that, for you to take advantage of actually these new features we're going to be bringing, you're going to have to use Carbon events.
Today we're going to be talking about a lot of things. We're going to help you understand all the different steps for you to move to Carbon events. And you have to understand this session as the base for you to move forward. Thank you. and to talk about the future and to talk about how you're going to be developing and are going to be migrating to Carbon events. I'd like to invite on stage Kurt Rothart, who actually has been working on the toolbox and will take you on a step-by-step approach for migrating your application. Kurt?
So good afternoon, ladies and gentlemen. It is a pleasure to be here with you to talk about Carbon events. Carbon Events, in a nutshell, is the event dispatching system in the high-level toolbox. It permeates every part of the toolbox, and it has been designed specifically for performance, extensibility, and modularity.
So what is this session about? First, I'm going to give you an overview of the Carbon event model so we can become familiar with it. and be comfortable with using it to migrate your WaitNext event-based application to Carbon Events. Who is this designed for? Well, if you have an application that you want to add new features to, if you're running--existing running on OS 9 or OS 10 using Wait Next Event, and you want to take advantage of new features or new technologies that are available on OS 10, this is for you.
It's also if you want to migrate your current event processing over to Carbon Events, and also to be a good Mac OS X citizen in terms of performance and functionality. Bye. So you may be asking yourself, "What benefits will I get by moving to Carbon Events?" Well, importantly, it encourages a modular design in your application.
That is, that you can incorporate your event processing with your UI of your application. So if you work with different groups, they can incorporate different modules, and you can put them together with very few, if any, integration problems whatsoever. It's also compatible back to Mac OS 8.6 with the CarbonLib shared library. It's important because the toolbox can add new features for free. You benefit from standard features that are available in the toolbox, And as new features are added, you get those for free.
It's also helpful because this predictable behavior defined by Apple. Now, traditionally, Apple has provided human interface guidelines that you would have had to react to and put together into your application, which makes your application's event processing part of your app. Well, using Carbon Events, we have the responsibility of taking care of the event processing.
And new features in Mac OS X and in the high-level toolbox are Carbon Event only. There's also a number of performance improvements. You may be asking yourself, what kind of performance improvements? Well, it encourages no polling. Using WaitNextEvent, you're sitting on one entry point polling, trying to figure out if an event has happened. With Carbon Events, you no longer have to do that. It's a direct dispatching model. So when an event happens, it's dispatched to your event handler, and you do the processing that you need to do. In this way, there's no dead-end event processing. And what I mean by that is let's say the user clicks in the menu bar to track down a menu, and then they decide they don't want to select a menu item, so they track off and release. Using WaitNextEvent, you would be getting that mouse down, you'd be asking the toolbox where was that mouse down, finding that it was in the menu bar, and then telling the toolbox to track that. Well, the user drags off. They don't select any menu item. And your application was involved with dispatching an event that did nothing. So Carbon Events gets away from that. And there are APIs that are specifically designed for performance. And for a lot more information on that, please go see session 207, Performance with Carbon Events, which is tomorrow in this hall at 3.30.
So an important thing to know about Carbon Events is you can migrate at your own pace. You can make incremental changes in your application that help move off of "wait next event" and go over to Carbon Events. Like I mentioned earlier, it encourages a modular design in your application. So you can add new modules to your application and not have your wait-next-event code affected by those changes.
At this point, I think it's important to look at the relationship between the look and feel of a Mac OS application. Traditionally, the look is presented, you know, by the appearance manager. The UI is drawn by that. And it's important to use the Appearance Manager because it adds a layer of abstraction for the drawing. If you were always drawing your custom controls every time, like the migration from OS 9 and OS 8 using Platinum over to Aquon 10 would have been very difficult. So it was facilitated by using the Appearance Manager. Well, similarly, the event processing using WaitNextEvent is limited in the sense that we can't move forward, add new behaviors, add new functionality.
So the toolbox has provided that, just tools for you to add and make your own application, such as different types of controls, push buttons, radio buttons, check boxes. You put all of those into your application, and you're responsible for taking care of what happens with those controls. Well, it's useful to think of Carbon events as a full framework. The framework for your house is already built. All you need to do is plug into the functionality that you're interested in benefiting from. So what does this mean for an event-driven app?
Well, with WaitNextEvent, as I mentioned earlier, you have to process each event and tell the toolbox what to do. Now, it's important to understand that WaitNextEvent is based on top of Carbon Events. So any new functionality that you add using Carbon Events won't affect your WaitNextEvent code. In fact, it will bypass-- if you handle certain events, it won't even be returned to WaitNextEvent. So you can take care of some event processing on your own by bypassing that. And with Carbon Events, the toolbox processes each event and then tells you what to do.
So graphically, what's going on here? Is with WaitNextEvent, your application, Gets an event from the event loop. It figures out what to do with many switch case statements. It tries to figure out where should I dispatch this to. Dispatches it to your own handlers. And then you tell the toolbox what to do.
With Carbon Events, you define a set of events that you're interested in receiving. You tell the toolbox about that. You get into the event loop, which blocks, and the toolbox will dispatch events, and if it's an event that you're interested in, it will call back into your application's handlers, and you take care of the event.
So because of this, it encourages the event handling of your application to be part of the design. Like I mentioned earlier, you can design all the functionality in a module that deals with the UI. For example, if you wanted to add a new inspector window, all of the event handling for that window could be in the file or module that deals with the UI of that window. So you can take new modules, plug them in, very few integration problems. And a lot of standard behaviors are provided for free by the toolbox. But those behaviors can be overrided by you to enhance it with your own functionality.
So how are events dispatched in the toolbox? As you can see in this image, an event can go-- like, let's say, for example, that the user clicks in a control. if the control does not handle the event, it gets propagated to its parent if it's in an embedded-- in an embedding situation. If its parent doesn't handle the event, it gets dispatched and falls down to the window. If the window doesn't handle it, it propagates to the application. At that point, if it's not handled, it gets returned via wait_next_event.
So at any point along this, you can install a handler, handle the event, take care of your functionality. If you've handled the event, the propagation stops processing. If you don't handle the event, it drops down to the application and then out via waitNextEvent if it makes sense for waitNextEvent to return that event.
So it's a hierarchical dispatching model. And because of that, we can have standard handling, which comes for free. We have our handlers. The toolbox has its handlers installed on particular targets, and it can take care of actions. You can override those. Or if it's not handled, it falls down out through WaitNextEvent. But you can modify the standard behavior because handlers are stackable. Certain handlers are provided by the toolbox, and you can stack your handler on top of that. If you don't handle it, it falls into the toolbox's domain to take care of.
So first let's go through a primer, figure out what is this Carbon events. Let's become comfortable with using it. So I want to talk about the data types and the APIs that are specific to Carbon Events. Now, all of these are in the carbonevents.h header. So if you have your laptop, you can pop it out and read it as we go.
So I'll be talking about the event type spec data structure, which indicates in the toolbox what events you're interested in. I'll talk about the event ref type, which is the main type of notification, how you are notified that an event has happened. We'll talk about the event target ref, and this is where you install your handler and where events are sent to. and also an event handler ref, which is a reference to your installed event handler.
So the event type spec is a very simple data structure, which includes the event class and the event kind. The event class is the high-level class of the type of event. For example, if an event happened in a control, it's K event class control. Or--no, yes. If it's in a window, it's a K event class window.
And then the event kind is a constant indicating what kind of event that was. So if there was a mouse down, for instance, that would be K event class mouse, K event mouse down. And usually you're interested in more than one type of event. So when you tell the toolbox what events you're interested in, you pass it in as an array of this type.
The event ref is an opaque data type. So when you need to get information in or out of an event ref, you have to use accessors. And it can contain an arbitrary amount of data in terms of the number of data types, in terms of the size of the data, and in terms of the type of data. Now, this is important because it's extremely extensible. In the future, if we decide we want to add different data to a particular event, we can go ahead and do that.
And the data is referenced by a parameter and a type pair. And all of this is in the carbonevents.h header. It has a listing of what types of parameters are in each particular event and what type of data that is that you can get out. And also, it's a two-way street. Information can come in with an event ref, and then you can set information in that event, and when the toolbox gets that, it can act on that as well.
Each UI element has an event target associated with it. So that's in terms of windows, controls, menus, the application, even the user focus, and the event dispatcher itself. This is where you install your event handler. So if you're interested in getting an event in a control, you would install event on that control target. And the events are then sent to that target and then to your handler.
The event handler ref is a reference to your event handler. You get this when you call install event handler. You don't have to get this return. You can pass in null if you aren't interested. But it's useful if you get into a modal situation or a certain particular mode where you want to add event types to that handler or remove event types from that handler because you want a broader range of events being dispatched to your handler, for instance.
So that's the basic data types to get going on a Carbon Event-based application. But how do I get notifications? Well, you have to define your Carbon Event handler, and I'll describe how that looks next. And then you install your handler on an event target. And then when events get dispatched, they will get-- your callback will get called.
So this is what your event handler would look like. When you install on a particular target and an event comes in, it will call your handler with an event handler call ref, which is useful for manual propagation, which I'll talk about later. It'll also give you the event ref, what is the type of event that has just occurred. And it will also return to you some user data that you specified when you installed the handler on that target.
And the propagation, whether it's dispatched along the hierarchy, is controlled by your return value. If you handle the event, you'll return no error. And that tells the toolbox that the event has been handled. Stop propagation. If the error "event not handled error" is returned, that will tell the toolbox that the event has not been handled. Continue propagation.
So finally, to install the event handler, you call install event handler. Now this takes the target that you're interested in the event array that you're interested in and the number of events that are in that array. Well, there's a macro available for your convenience called getEventTypeCount, which will get the number of events that are in your array. Now, if you get the event handler ref, you can also remove the event handler using remove event handler. I'd like to bring out Brian Prusa to give a quick demo of getting a quick Carbon Event app running. Brian? Thank you very much, Kurt.
to just give a small walk-through of installing a Carbon Event and then handling that Carbon Event elsewhere in the application. So this is pretty much the default code generated by Project Builder for a Carbon Nib-based application. Now, the first thing I'd like to do is show you the Nib. So this is an Interface Builder document. And so here's our default window. I'm just going to add a couple buttons and show off some of the features of Interface Builder. So you notice the lines appearing show you how to line up your UI according to the Aqua Human Interface guidelines.
So I'm going to make this a quit button, and we'll make this a beat button. Now, here, I can show the information, all the attributes on these buttons. And I'm going to add what's called a command ID. So we'll make this the quit command. There are a whole series of default commands here. I'll just choose quit. So command IDs are things that can be passed around as a Carbon event that you can intercept later. So if I click this button, this quit command ID will be sent out. I can intercept that and do what I want with it. Now for the beep button, I'll just give it a beep command.
which is my own type here. And also, because command IDs are high-level events, or getting the command event process is a high-level event, rather than having to worry about tracking this button or things like that, I will just get this event and deal with it. It doesn't matter where it comes from in the application. And so, for instance-- Let me bring that back.
in the menu bar here, we've created another menu, the Beat menu. And in this case, we also have-- there I go again. We also have... we also have the beep command installed here. And so this command ID can be sent out from the menus, from buttons, and in fact, it can also be sent out from keyboard events. So if I bring the window back up, and here's the beep control, I'm gonna set this to the default button. So when I hit return or enter, this button will be activated, and the command will also be sent out. So let me save that.
come back to the code here. Now here is just grabbing all the information out of the nib, grabbing the menu bar, the window that we were just dealing with. And then what I want to do special is install this command event handler. So the type of command -- or type of Carbon event I want to deal with is the k event class command and k event command process kind.
So here I'll create the universal procedure proc, UPP, for my event handler. And then I want to install that event handler. So I call the aptly named installEventHandler. And I want to install this on the application event target. Like I said, this is a high-level event, not anything like a mouse down. I don't have to--I don't care whether it's in my control or a window or a menu specifically. In this case, I want to get it no matter where it comes from in the application. So I'll install my event handler. I'll count up the number of events that I'm registering for. And I'll register for these particular notifications.
called runApplicationEventLoop, which drops us down into the HIToolbox's event handling routines. And this is all--this is where all the standard handling, things like that happen for you. And after we're done running the application, we want to dispose of that event handler. So let's look at the event handler itself.
Here I want to look at the HICommand, which is what I registered for. And HICommand is a data structure which contains the command ID that I passed in. So I want to get this particular event parameter. In this case, it's the kventparam direct object. And it's of type HICommand. And I'll just grab that command right out.
here a little lower, we're going to switch off the command ID. And in the case where it's beep, which is my private type, which I set up, I want to just call sys beep. And in that case, I want to return no error because I don't want this to propagate anywhere else. Nothing in the toolbox cares about the beep ID. It's only important to me. So I don't want anybody else to know about this. But I also want to make sure that in every other case, so the default case, I return event not handled error. And this will allow the the application to go on processing every other type of command that comes through. In the case of this demo, where I created the quit beat-- I'm sorry, the quit button, I want to make sure that that quit command ID is processed through so the toolbox can intercept it later and quit your application on your behalf in a standard way. So I'll go ahead and run this application. And we'll see how it behaves.
Now, there will be a lot of things that happen for us here because the standard event handlers are all being used. So we'll get window dragging and menu activation. Oh, this is my worst nightmare. Just compile it again. Hmm. Great. Well, we're gonna go a little hardcore on you here, so give me just a second. I'm sorry? All right. Let me clean this, make sure everything's all cleaned out here. Build from scratch.
All right, here we go. Whew. Now we have our window. It's dragging around. We get that for free because it's a default behavior. We get the resize. We get menu tracking. This is great. I didn't have to do anything specifically to handle this. But what we're here to look at is the command ID. So here's my beep button. And I'm gonna click, and then the command ID beep will be sent out, and I'll intercept it in my handler and beep. So just as you would imagine, this would happen. And again, because this is a high-level event, I can hit Enter... It tracks for me automatically, and we get the same behavior. And again from the menu.
If I wanted to, I could quit this application through the default quit menu item. But in this case, I'm gonna handle it-- or send this off somewhere else. I want to quit with my button. So that quit command ID is sent off and handled in a standard manner by the IHI toolbox on your behalf. All right. Thank you very much. Back to you, Kurt.
Thanks, Brian. So you can see there was very little additional code that needed to go in in order to make a simple application that actually did something. There wasn't any boilerplate code required using waitnext-event in order to track the event and process it. Pretty cool. So now that we understand how to build a basic Carbon application, Carbon event-based application, let's talk about how to migrate your existing application. So I've listed five steps for migration. The first two steps I think you could do today.
That is, first, replace your tracking loops. If you need to track where the mouse is, for instance, the user clicks in a certain area of your screen, and you need to track where the mouse location is while the mouse is down, traditionally you'd sit in a loop and pull the mouse location. Well, we have API specifically designed to get away from that, and I'll describe that in a moment. Second, get rid of null event idling with waitNextEvent. WaitNextEvent, as you know, returns null events if no events have come in, and you've asked for null events. Or it'll just return false. And then you can do some particular idling. Well, there's ways to deal with that as well.
And those two things you can do today. Next, get your feet wet with Carbon Events. Design some new features using Carbon Events. So it'll allow you to become familiar with the data types, the event handling, how to install handlers and handle those events. Next. With your existing event processing, migrate that over to using Carbon Events. I'll describe that in a moment as well. And lastly, when you've taken care of everything, get rid of Wait Next Event altogether. It's been a good friend, but we want to move forward.
So step one, replace your tracking loops. Like I mentioned, traditionally, when the user clicks on the mouse button and you want to track that, you would call while still down, getMouse. Now what this does is it sits in a tight loop, pulling the hardware to see if, where the mouse location is.
It's bad because it's sitting in a tight loop even though maybe nothing has happened. Maybe the user hasn't actually done anything with the mouse in terms of moving it or releasing it or doing anything interesting. Now, it's doubly bad because this requires inter-process communication. We have to ask the Windows server for the state of the mouse. So instead, use the API track mouse location. What this does is it gets in the event loop and it blocks until the user actually moves the mouse or does something interesting with the mouse.
This frees the CPU up for other processes. You're not starving the processor or starving other applications that need to do things. And also, since it's blocked on the event loop, if you have any timers installed, they will still be called and your application can continue processing even while the mouse is down.
In addition, there's a new mouse tracking region API. This is new in Jaguar. This is as a replacement for the mouse region parameter in wait next event. The advantages of this are using the mouse region parameter in wait_next_event limited you to one mouse region per application. While using the mouse tracking region API, you can have many regions per window that you want to track. And this allows you to get entered notifications and exited notifications.
So if the user enters a particular region of your window, you'll get a notification to do something. This is useful if you need to do cursor switching. If you have a text field that when you drag the cursor over, where you want to turn to an I-beam, for example. And this is also how the window widgets work. So when you drag the mouse over the close, minimize, or maximize widget, we'll highlight them, and this is exactly how it's done. Step two, get rid of your null event idle processing. This is important because you're just spinning. You're sitting on waitNextEvent waiting for nothing to happen in order for you to go ahead and do something.
So what is the problem? What are you trying to solve with ILEVENT processing? Well, perhaps you need to do some processing at periodic times to animate an image, for instance, or blink the cursor. Or let's say you have a text editor and you need to do repagination. Well, you don't want to interfere with the user while they're typing, so you want to wait a certain amount of time. Repaginate while they're not doing anything. Or do some searching or sorting in the background.
What are the problems with using wait_next_event? As I mentioned, it wastes CPU cycles. Sometimes you're sitting there and you're not interested in any particular event at this one time. You want to wait for a certain amount of time before something happens. But you're still returning from wait_next_event and have to do some calculations. It's also not modular.
If you have a new module that needs some idle processing, you need to incorporate that, include that into your existing code with WaitNextEvent to handle some processing. And it requires internal logic to maintain timers. And what I mean by that is, let's say you have two animations in your document, one that needs to be animated four times a second and one five times a second. Well, since WaitNextEvent is based on ticks, the easiest solution is to call yourself back 20 times a second, do some calculations to figure out which image needs to be animated particular time.
Solution? Use event loop timers. It's a very simple API, and it allows you to install one timer per task. In my example, if you had two images that you needed to update, you would just install one timer for one image, a different timer for another image, and each of those would only be updated when they need to be updated. There's also three classes of timers. There's one shot for singular events. That is, let's say you build up a cache, you want to keep it around for a particular amount of time, and then flush it. You'd use a one-shot timer for that.
There's periodic timers for recurring events, like an animation, cursor blanking, et cetera. And new in Jaguar, there's idle timers. Now, this is a type of timer that you can install that will be called when the user is idle in terms of moving the mouse, keyboard input, et cetera. There's more information on this in session 207, which is performance of Carbon events, which, again, is tomorrow here in this hall at 3:30.
Like I mentioned, the Event Loop Timer API is very simple. There's Install Event Loop Timer. This takes the event loop that you're interested in installing this timer on, so you can install per preemptive thread. you can set the event loop timer's next firing time. Now, that's useful if you have a one-shot timer, you use it, but you want to reset it again in the future. You don't want to get rid of it, necessarily. So you use this API to actually reset the firing time. and then the toolbox will never get rid of a timer for you, specifically for if you want to reset the time. So you'll always have to call removeEventLoopTimer when you're done using it.
I'm going to talk about idle timers and show how easy it is to get up on them and get off of wait next event. I'd like to bring Brian Prusher back up. Thank you again, Kurt. Thank you. All right, for this demo, I have a WaitNext event-based app, which I will convert over to event loop timers.
So here we have... here we have our main.cp. And I initialized some things from our NID. This is, again, a NID-based app. And I'm going to be running an animation of "The Game of Life." You may have seen it on some screen savers. So I initialize my animation, and then I run that nasty boilerplate event loop code wrapped around waitnext event that everybody has to write. And then when I'm done with the application, I'm going to go ahead and dispose of my animation.
So let's look at that boilerplate event code. Here we have wait_next_event. And then when an event comes through, we have to decide, okay, is this a mouse down? Is this a key down? What are we gonna do with each one of those different types of events? Oh, if it's a mouse down, is it gonna be in the menu bar? Is it gonna be in the drag part of the menu? Or, I'm sorry, the drag part of the window? And the go away box, the grow box, all these things. So all this code that you have to handle manually. and it becomes a lot of code.
So here we come back again to waitNextEvent. And I'm calling this back to every six ticks, which is every ten times a second, to update my animation. Now, as Kurt described, we have to wait until something doesn't happen. So when waitNextEvent returns false, we're gonna go ahead and come down to this switch statement, handle our null event, and this is where I evolve the animation.
And so now that we've looked at this, let's go ahead and run the app and see what it does. So here's the nice animation running along, updating every tenth of a second. But what happens when an event does happen and we can't process on all events? So, I'm dragging around, but my animation isn't updating. Okay, then. All right. So if I go to resize my window, again, everything stops. Or if I just go to access the menu, everything grinds to a halt.
this isn't necessarily the greatest thing in the world. So I'm gonna go ahead and quit this. And I'm gonna go over to my animation code. Part of what Carbon Events does for you is it allows you to bring the event handling over to those parts of the application where it really matters. I want to keep my animation code together with the event processing for it.
So here's my initialization routine, and I grabbed some more information from the nib. And what I want to do in this initialization now is install an event loop timer. So here I'm creating the event loop timer UPP. And I want to install that on the current event loop. So in the multi-threaded case, this would be on whatever thread I'm running in. In this case, it's single thread. I'm running on the main event loop.
Now I'm gonna fire this off immediately because I don't want to wait for my animation to start. I just want to get right in there and going. And again, like the waitNextEvent case, I'm gonna go ahead and have this call back every ten times-- or ten times a second. And I'll pass through my UPP, and I'll be notified whenever this time--timer fires. So here in my timer, all I'm doing is calling EvolveLife just like I was back in the WaitNextEvent case. Now that I install that timer, I can go ahead and remove this-- this waitnext-event null idle processing. It's no longer necessary. So I'm gonna go ahead and rebuild.
One other thing that's very important is once you move over to null event processing-- or from null event processing to event loop timers, you want to give your wait_next event a very long timeout so it's not being needlessly called back. So we'll give it a very long timeout.
So now that I'm running on timers, you'll see there are some basic changes in the application. For one, as soon as I do have events, things don't stop processing. This is great. My animation keeps going. And while I resize, the animation continues. And we can even watch the animation underneath these wonderful translucent menus.
So there we've converted the wait_next_event, idle_event_processing over to carbon_event_timers. So thank you very much. Back to you, Kurt. Thanks, Brian. So that looked pretty easy, didn't it? You could get off of WaitNextEvent for your event processing and use timers. So those two steps I think you could do today. Get off of tracking the mouse, using while still down getMouse. and also using null event processing with wait_next_event. Step three.
Do your new development with Carbon Events. It helps you become familiar with Carbon Events concepts, data types, APIs, when your event handlers are called, et cetera. It won't impact your existing code. Like I mentioned, it encourages modularity. So as you create a new module, you can just plug it in. Won't impact your existing WaitNext event code.
So do this when you have to add new application features, such as if you're gonna do a toolbar or an inspector window, or even new window definitions. They can be completely Carbon event-based. Also become familiar with using nibs. As you saw in Brian's demo, it was very easy to construct an interface in Interface Builder. And it generates what's called a nib.
Also do this when you want to adopt new toolbox features. Most new features will and are implemented using Carbon Events, such as the services menu, the mouse tracking regions, which I mentioned, even HIObject, which is this cool new philosophy that we're really excited about. It'll be talked more about tomorrow at 9:00 in this hall in the overview of the HI Toolbox architecture. All Carbon Event based. It's very cool.
So step four. Migrate your existing event processing over to using Carbon events. You don't have to deal with all the event dispatching yourself. Give that responsibility to us. We're good at it. You have two solutions for doing this. You could use standard handlers, and that's behavior that's provided by a toolbox for free that you can take advantage of. And you can also override some default behavior and take care of events yourself.
So what's the problem with handling events the old way? What's wrong with it? Well, it's limited to 16 types of events. And that's simply because WaitNextEvent's event mask is a 16-bit integer. Since it has the limitation of only returning 16 types of events, it gives you the low-level event. A mouse down has occurred. A keyboard down has occurred. It's not--it doesn't have a full, rich sense of events, like actions that have happened. You also see that it's the main bottleneck for idle event processing and mouse tracking, which I already mentioned. It makes your architecture very difficult and hard to maintain.
and you are required to do the proper dispatching. So if we ever want to change behavior, you have to go maintain your code. Change it. Now, an example of that is when the proxy icon was added to the window title bar. If you wanted to take advantage of that, you were required to go into your WaitNext event-based code and deal with that accordingly.
We want to give you an extensible solution. So the easiest thing to do is use the standard handlers. You get standard behaviors for free. For example, with the window, you saw in Brian's first example that you could drag the window around, resize it. All that was provided for free by the toolbox. And now if we include any future technologies, you'll also get that for free. And it allows you to migrate off of WaitNextEvent.
So for a window, if you want to take advantage of standard handlers, you would set the kWindowStandardHandler attribute. Now, this you could do in creation with the API. It takes attributes. There's also an API change window attributes. Or if you're using Interface Builder, there's a checkbox that says "Standard Handler," and you click it, and you get all this for free. Resizing, dragging, tracking the widgets.
Very cool. Then you can also customize the behavior. If you're not satisfied with the standard behavior provided by the toolbox, you can go ahead and install handlers for the events that you're interested in. Let's say you want to know when the user has dragged a window close to the edge of the screen, so you could snap the window to the edge of the screen. Well, you could install a handler to find out when the window has moved and then change data in that event ref accordingly, and the toolbox will listen to that and snap the window. Or let's say you want to grow the window and you want to constrain the size so it doesn't just have odd dimensions. You can also do that. Install a handler for that particular event, a bounds changing event.
and do some calculations to generate what you-- how big you want the window to be, and Toolbox will respect that. What this allows you to do is handle the high-level action, not the low-level event. You're not dealing with the mouse down. You're dealing with the user actually move the window, and I want to do something about that.
I mentioned earlier that you can control the propagation of an event as it's dispatched through the hierarchy. Well, for standard handling, you would set the standard handler attribute and then do nothing. The toolbox takes care of everything for you. Now let's say you want to override some of the standard behavior. Well, you would install a handler on a target for the particular event you're interested in. You'd handle that event, and then you return no error. Processing stops-- it doesn't propagate any further.
But let's say you want to modify the standard behavior. You want to get the standard behavior, but you want to do something in addition. Well, you still can do that. If you want to pre-process, you install your handler on a target for that particular event. When your handler's called, you can do something and return event not handled there. The toolbox will continue propagation. Or you could post-process it. And you would explicitly continue propagation by calling callNextEventHandler. Or you could do a combination of both.
Now let's go on to some advanced event processing. So that's kind of the basics. Now, it can also be useful to create your own events. Let's say you have a command handler for the close event, like for a window. What you've seen is that the user has tracked over the close box of a window, and you want to generate a close command. So you can use the API createEvent and direct that towards the application so all your event handling goes through the same bottleneck, if you will, the same handling.
You can also use Carbon Events for internal communication. Go ahead and define your own event classes, and then you can use-- create event, send the event to a target. Let's say you created a module for an inspector window, and all the event handling's dealt with that, and it defines an interface using event classes. A different part of your application can create an event of that class, send it to the window, and it can react to that. It's also useful for communicating between preemptive threads.
So you've done all of that. And you don't want to stay so long to wait next event. What are the steps to doing that? So follow all four steps I mentioned earlier. When you're done with that, maximize the sleep, which you saw Brian do. And what that does is it makes sure that your timers are doing what you would expect them to do, that your application continues to process events the way you would expect. Then you remove WaitNextEvent and replace it with runApplicationEventLoop. I'll describe that API next. And then finally, you would remove all of the WaitNextEvent artifacts. All of that switch case stuff just should go away.
Run application event loop, the replacement for wait next event. When you call it, it enters the event loop. And then it will block until any events occur. That means that your application is not processing events, it's not doing anything until something interesting happens. So this provides a good user experience to the user because your application does not consume resources when it's not doing anything.
While it's in this function, it will dispatch events to your handlers as they come in, and it will only return when quit application event loop is called. Now, that can either be called explicitly by you in one of your event handlers or implicitly by the toolbox. For example, it will take care of the H-I command quit and call this function, so it will return from run application event loop and...and... come out of your code. And so to go through these advanced things and actually show a very cool demo, I'd like to call Brian back up.
All right, thank you again, Kurt. Now this time I'm gonna knock on wood here. All right, I'm gonna come back to this animation demo. And what I'm gonna do first is go open the nib. So there's some things I want to do there. I want to start using standard event handlers. I want to kind of get myself off of waitNextEvent. So here's the main window. And so down here, as Kurt mentioned, there's the standard handler checkbox. Now I'm gonna click it. Now that I'm using standard handlers, I'm gonna click live resize, and I'll get live resize in the standard behavior. I can save off that nib.
I'm gonna come back to the code. I'll go again to the animation code. And here there is more code to uncomment. So I'm going to install another command event process handler. And so this looks just like it did before. I'm installing this on the application event target. I have my handler. And the same K event class command, K event command process types for the event.
So I'll come down here and look at the handler. And like before, I'll get event parameter, get the direct object, which is the command, and I'll switch off the command ID. But I want to handle specifically the khicmdnew, which is outlined in carbon-events.h. And I'm going to initialize my animation here.
And again, return no error, because I want to be the only thing that handles the new command. I'm also going to handle khicmdquit and dispose of all the pieces of my animation. But this I do want to go ahead and continue, so I'm going to return event not handled error. because I want to have the standard-- standard handler call quitApplicationEventLoop on my behalf.
Now I can go back to my main.cp. Because I'm handling the quit command, I no longer have to dispose of my... dispose of my animation here. And in fact, because I'm using standard event handlers, I no longer need my boilerplate event loop. Instead, this is gonna be replaced by run application event loop.
And since I'm no longer using my boilerplate event code, I can simply delete it. It certainly makes that file a lot shorter, which is very nice. Good benefit. Okay, now I'm gonna go ahead and run this. And we'll see that I can actually remove code and using the standard event handlers I don't have-- the application still behaves very similar to what it did before. I can still drag it around. The widgets still update. And now I even get benefits for free, like live resizing.
And again, the menus still track. I can close this window and then choose "new," and it comes back. So I'm now handling all my event processing in my animation code itself. So everything is all localized and modularized. Now, just to really belabor this point, I'm gonna go ahead and add a completely new module to this application.
I have a new module set up right here. I'm just gonna drag it into Project Builder, into my sources. I'll add it. And so I have just some code, a header file, and a nib part of my module. I'm gonna go back to my main code and include module.h.
and then I'll initialize my module just like I initialized my life animation. Now, in my module, I handle the MODL command ID. So I'm going to go back to my main nib. and add a new menu item. I'll just duplicate the new item here. And I'll say new module. I'll give it a new command key equivalent.
And then in this menu item, I want to handle the MODL. and of the MODL command. Now, when I choose this menu item, it'll send out this command, be intercepted by the event handler that was installed in my module during the initialization, and it'll create the new module for me. Save that off. Go ahead and rerun.
And now with very little effort, I'm able to add a completely new module to my application. Now, this module also has standard events, so I can go ahead and just drag this around. It's completely separate from this. I didn't have to-- didn't have to handle anything specifically. I can resize live just like I could before.
And if I close this and choose new module from the file menu, it'll pop up and just start going again. So I can dynamically dispose and recreate my module. So here it is, complete new functionality, completely wrapped up, so if there are two groups working on the development of this application, they can work independently and with very little work integrate them together into their app. So thank you very much. Back to Kurt.
Thanks, Brian. So you can see, it was very little effort to take a new module. You don't have to know anything about the module other than the interface to initialize it. Maybe some Carbon events, event class or two that the new module requires. And the integration was very easy. Plug it into Project Builder, and it just built and worked.
So some tips for using Carbon events. Sometimes you may not know what events are coming in, going out, what I should be installing a handler on. Well, you can find out what events are going on by setting this environment variable, event trace. Now, there's a number of ways to do this, but on Mac OS X in terminal, what you would do is you would type in setenv event trace 1, and that sets the environment variable. And then you would launch your application from that terminal session. Better make sure your terminal buffer's pretty high because there's a lot of spewage that comes out telling you which events are being sent and what's going on. There's also some very valuable information on the web in a tips and tricks page for Carbon events. It's on Apple's developer website.
So I hope I've been able to impress upon you that migration is easy. I mean, it's an incremental process. I don't expect you to do all of it today. There's some that can be done today. but it's useful to do any new development using Carbon Events. In fact, you should be doing all new development with Carbon Events.
Carbon events are the future of the toolbox. This is the direction that we're going. If you want to take advantage of new features, you'll have to be Carbon event-based. So it's useful to get up and on there now. Amen. And it's a must-have for being good OS X citizens in terms of performance and functionality. Like I mentioned, there's a lot of new functionality that's Carbon Event only. If you want to take advantage of that, you have to be up on Carbon Events.
There's some excellent documentation available. There's a Carbon porting guide. There's a book on handling Carbon events, Carbon event manager reference. There's some documentation on your Jaguar CD. which is listed at the bottom of the slide. And there's also a lot of information on Apple's developer website about getting up on Carbon events.
Five steps of migration. It's pretty straightforward. And you saw Brian go through everything today. Replace the track and loops. Get rid of waitNextEventHidling. Design all of your new features using Carbon Events. Migrate your event processing over to Carbon Events. And finally, get rid of WaitNextEvent and all the cruft associated with that.
Our Level Toolbox team is completely monopolizing this room tomorrow, so I'd encourage you to go see all of the sessions that are available. There's an architectural overview, which will discuss new features coming into the toolbox, a HIView session, excellent session, new controls and services to go in more depth of what's going on in the toolbox, new stuff that's available. And I highly recommend seeing improving performance with Carbon Events, and that's at 3:30. And that will teach you how to use -- to be a better citizen for performance. you