TweetFollow Us on Twitter

May 02 QT Toolkit

Volume Number: 18 (2002)
Issue Number: 05
Column Tag: QuickTime Toolkit

Event Horizon

by Tim Monroe

Using Carbon Events in a QuickTime Application

Introduction

Over the past couple of years, we've used a simple application, called QTShell, as the basis for our explorations into the QuickTime APIs. QTShell provides the basic Macintosh or Windows application services (start-up, shut-down, event or message handling, menu and window management, and so forth), and it provides stub routines that we use for our application–specific code. For instance, in the previous QuickTime Toolkit article ("Big", in MacTech April 2002), we saw how to add code to QTShell to allow it to play movies fullscreen.

The main problem with QTShell as it stands today is that its Macintosh code uses an event-handling model that is almost 20 years old and sorely in need of a tune-up. Our Macintosh code is indeed already fully Carbonized; that is, it uses only Carbon APIs and hence can run both under "classic" Macintosh systems that support CarbonLib (Mac OS 8 and 9) and under Mac OS X. But it still uses a couple of APIs that result in less than optimal performance, especially under Mac OS X. In particular, it calls WaitNextEvent to retrieve events for the application, and it uses ModalDialog to handle events while its About box is displayed to the user.

What's wrong with WaitNextEvent and ModalDialog is that they continually poll the operating system to see whether any events have arrived for the application. If our application isn't doing anything (for instance, no movies are playing and the user has gone to get a cup of coffee), we are wasting valuable processor time that could be used by other open applications. It would be better if the operating system would just inform us when an event arrives for our application. This is the fundamental idea behind the Carbon Event Manager.

In this article, we're going to learn how to replace our calls to the "classic" Event Manager with calls to the Carbon Event Manager. As we'll see, this mostly involves writing a few event callback functions (or event handlers) that are executed when specific types of events occur. In effect, our application is transformed from a nagging kid ("are we there yet?", "are we there yet?"...) into a polite child who waits patiently for instructions. We'll also need to write a Carbon event loop timer callback function, to allow us to call MCIdle periodically to task our open movies. (To task or idle a movie is to grant its data handlers and media handlers some time to do their work.) Now in fact this obviates some of the good work we did in moving away from WaitNextEvent, since the Carbon event loop timer is going to fire periodically even if our application has no work to do. (The nagging child has been replaced by a nagging parent: "clean your room!", "clean your room!"....) We can work around this by using the movie tasking interval functions — new to QuickTime 6 — to determine when our application should next call MCIdle; we can then reset the event timer accordingly. Toward the end of this article, we'll also see how to work with the Carbon movie control, a custom control introduced in QuickTime 6 that we can use in Carbon applications on Mac OS X to display and control movies. The Carbon movie control automatically handles all the event and tasking interval management associated with displaying a movie in a window and hence frees us from having to write callback functions or timer routines for this. Very nice.

A few words of caution before we begin: the Carbon movie control provides an elegant way to put a QuickTime movie into a window, but it's available only under Mac OS X and only under QuickTime 6 or later; as a result, we'll retain our existing code for managing movies and isolate the new code with the USE_CARBON_MOVIE_CONTROL compiler flag. Similarly, we'll use the USE_TASK_MGMT compiler flag to set off the code that uses QuickTime's new tasking interval management APIs. In both cases, we really ought to replace the compiler flags with run-time checking of the system software and QuickTime versions and just do the right thing. I'll leave that refinement as an exercise for the reader.

More importantly, we still want to be able to build non-Carbon versions of QTShell-based applications and versions that don't use Carbon events. (These can sometimes be useful, for instance, in tracking down bugs.) So we'll introduce yet a third new compiler flag, USE_CARBON_EVENTS, to enclose the code that handles Carbon events. We run the risk of ending up with unreadable code with all these flags, but in fact the damage is fairly minor. Indeed, seeing the Carbon Event Manager code side-by-side with the corresponding "classic" Event Manager code can be instructive, as we'll soon see.

Carbon Events Overview

Let's think back to 1984. The Macintosh operating system popularized the idea of event-driven programming, where an application is structured so that it is guided by events reporting the user's actions with the mouse and keyboard (and other occurrences in the computer). An application is driven by its event loop, a section of code that retrieves events from the Event Manager and dispatches them to the appropriate event-handling routine. Listing 1 shows a typical event loop.

Listing 1: Retrieving and dispatching events

static void QTFrame_MainEventLoop (void)
{
   EventRecord      myEvent;
   long                  myDuration = kWNEMinimumSleep;

   while (!gShuttingDown) {
      // get the next event in the queue
      WaitNextEvent(everyEvent, &myEvent, myDuration, NULL);

      // handle the event
      QTFrame_HandleEvent(&myEvent);
   }
}

We call WaitNextEvent to retrieve an event from the Event Manager and then QTFrame_HandleEvent to handle the event. The myDuration parameter to WaitNextEvent specifies the number of ticks our application is willing to suspend processing if no events (other than null events) are pending for it. This allows other applications and services to use the processor while we don't need it. If an event arrives for our application before the specified duration elapses, WaitNextEvent returns immediately with that event. Otherwise, when the duration elapses, WaitNextEvent returns with a null event. We can use this steady stream of null events as a trigger to perform periodic actions.

The QTFrame_HandleEvent function is essentially a big switch statement that branches on the type of the event:

switch (theEvent->what) {
   case mouseDown:
      // handle mouse-down events here
      break;

   case updateEvt:
      // handle update events here
      break;

   // cases for other event types

   case nullEvent:
      // handle null events here
      break;
}

When we throw QuickTime into the mix, we need to make a very simple adjustment to QTFrame_HandleEvent: before stepping into the switch statement, we'll see whether the event should be handled by a movie controller attached to one of our open movie windows. We do this by calling QTFrame_CheckMovieControllers, which loops though all our application's open movie windows and calls MCIsPlayerEvent until it finds a movie controller that handles the event. So, in outline, our event handling function now looks like this:

isEventHandled = QTFrame_CheckMovieControllers(theEvent);
if (isEventHandled)
   return;

switch (theEvent->what) {

}

The Carbon Event Manager changes this model in several key ways. First, as we've seen, we don't continually poll for events by calling WaitNextEvent. Rather, we install one or more event callback functions (we'll see how to do that shortly); the Carbon Event Manager sends particular events directly to those callback functions, which then handle the events as appropriate. We enable this dispatching by calling RunApplicationEventLoop. Listing 2 shows our revised version of QTFrame_MainEventLoop.

Listing 2: Retrieving and dispatching events (revised)

static void QTFrame_MainEventLoop (void)
{
#if USE_CARBON_EVENTS
   RunApplicationEventLoop();
#else
   EventRecord      myEvent;
   long                  myDuration = kWNEMinimumSleep;

   while (!gShuttingDown) {
      // get the next event in the queue
      WaitNextEvent(everyEvent, &myEvent, myDuration, NULL);

      // handle the event
      QTFrame_HandleEvent(&myEvent);

   } // while (!gShuttingDown)
#endif
}

RunApplicationEventLoop processes and dispatches events until we call QuitApplicationEventLoop. As you would guess, we call QuitApplicationEventLoop in our application shutdown function QTFrame_QuitFramework.

The second main change is that the Carbon Event Manager redefines the kinds of events our application can receive. The original or "classic" Event Manager can handle only a limited number of event types:

enum {
   nullEvent                        = 0,
   mouseDown                        = 1,
   mouseUp                           = 2,
   keyDown                           = 3,
   keyUp                              = 4,
   autoKey                           = 5,
   updateEvt                        = 6,
   diskEvt                           = 7,
   activateEvt                        = 8,
   osEvt                              = 15,
   kHighLevelEvent                  = 23
};

The Macintosh system software folks could of course add new event types to this list, but they would be limited to 16 total event types by the size of the event mask (a 16-bit value that determines which events our application receives when it calls WaitNextEvent).

The Carbon Event Manager uses a much larger set of events, called Carbon events. A Carbon event is uniquely specified by its event class and its event kind. For instance, the Carbon event that corresponds to the classic event keyDown is of class kEventClassKeyboard and kind kEventRawKeyDown. And the Carbon event that corresponds to the classic event updateEvt is of class kEventClassWindow and kind kEventWindowUpdate. Here are the event classes we shall be concerned with in QTShell:

enum {
   kEventClassKeyboard                     = FOUR_CHAR_CODE(‘keyb'),
   kEventClassApplication               = FOUR_CHAR_CODE(‘appl'),
   kEventClassMenu                           = FOUR_CHAR_CODE(‘menu'),
   kEventClassWindow                        = FOUR_CHAR_CODE(‘wind'),
   kEventClassControl                     = FOUR_CHAR_CODE(‘cntl'),
   kEventClassCommand                     = FOUR_CHAR_CODE(‘cmds')
};

But the Carbon Event Manager doesn't just reshuffle the classic events into new classes; in addition, it defines a large number of new kinds of events. For instance, when the user clicks in a window's close box, our application can receive an event of class kEventClassWindow and kind kEventWindowClose. Gone are the days when we just got a mouse click and had to determine where the click occurred and perhaps then track the click until the user released the mouse button; instead, we get higher-level indications of what the user is trying to accomplish. We really don't care about the mouse-down event; rather, we care that the user clicked in the window's close box.

This ties in with yet another significant advantage of the Carbon Event Manager: it provides default event handlers for these event classes. The default handler for window events, for instance, knows how to handle clicks in the close box and the zoom box, as well as drag-clicks in the window's title bar. And the default handler for controls knows how to handle control clicks and tracking. Our application needs to get involved only when we want to override or augment the behaviors provided by the default handlers. For instance, before we allow the user to close a movie window, we need to check to see whether any of the data in the window has changed and give the user the opportunity to save any changes. So we'll install an event handler that receives window close events.

We install an event handler by calling InstallEventHandler. In theory, we could write one big event handler for all the event classes and kinds we care about, but in practice it's better to write one event handler for each type of event target. An event target is an opaque object that corresponds to an object in the application that can receive events, such as a control, a window, or the application itself. The Carbon Event Manager provides a handful of routines for obtaining event targets; for example, we can call GetWindowEventTarget to get the event target associated with a particular window.

In summary, we implement support for the Carbon Event Manager by defining and installing an event handler for each event target our application cares about. Depending on the type of target, we may need to explicitly install the default (or standard) event handler. And we make it all go by calling RunApplicationEventLoop, as we saw in Listing 2.

Document Windows

As you know, QTShell can open movies and images in standard document windows. We'll take advantage of the default behaviors provided by the Carbon Event Manager's standard window event handler and restrict our window event handler to those events that have special meaning for QuickTime or for our application.

Specifying Events

We specify one or more events by creating an array of event type specifications, defined by the EventTypeSpec data type:

struct EventTypeSpec {
   UInt32               eventClass;
   UInt32               eventKind;
};

If a window contains a movie, we need to pass to the associated movie controller any key events that are not command-key events (which might therefore be menu item shortcuts); we also need to pass the movie controller any mouse clicks inside the movie rectangle. In addition, our application needs to know when the window is being closed, activated or deactivated, and when it needs to be redrawn. We can specify the events that should be sent to our window event handler like this:

EventTypeSpec      myEventSpec[] = { 
   {kEventClassKeyboard, kEventRawKeyDown},
   {kEventClassKeyboard, kEventRawKeyRepeat},
   {kEventClassKeyboard, kEventRawKeyUp},
   {kEventClassWindow, kEventWindowUpdate},
   {kEventClassWindow, kEventWindowDrawContent},
   {kEventClassWindow, kEventWindowActivated},
   {kEventClassWindow, kEventWindowDeactivated},
   {kEventClassWindow, kEventWindowHandleContentClick},
   {kEventClassWindow, kEventWindowClose}
};

Notice that a single event target can receive more than one class of event; in this case, our document window event handler is to be sent both keyboard events and window events.

Installing Event Handlers

In our Macintosh code, we create a movie or image window by calling NewCWindow. We can attach the standard event handler to that new window by calling the InstallStandardEventHandler function, like this:

InstallStandardEventHandler(GetWindowEventTarget(myWindow));

The GetWindowEventTarget function returns an event target associated with the specified window. It's worth noting that if we had called CreateNewWindow to create the window, then we should install the standard event handler by setting a window attribute, like this:

ChangeWindowAttributes(myWindow, 
               kWindowStandardHandlerAttribute, 0);

We use the InstallEventHandler function to install our custom window event handler:

if (gWinEventHandlerUPP != NULL)
   InstallEventHandler(GetWindowEventTarget(myWindow), 
            gWinEventHandlerUPP, GetEventTypeCount(myEventSpec), 
            myEventSpec, 
            QTFrame_GetWindowObjectFromWindow(myWindow), NULL);

The first parameter is of course the event target — which corresponds to our new window. The second parameter is a universal procedure pointer to the event callback function, which we can create by calling NewEventHandlerUPP:

EventHandlerUPP         gWinEventHandlerUPP = NULL;

gWinEventHandlerUPP = NewEventHandlerUPP
            (QTFrame_CarbonEventWindowHandler);

The third parameter is the number of event type specifications contained in the fourth parameter. As you can see, we use the Carbon Event Manager function GetEventTypeCount to get that number. The fifth parameter is a reference constant that is passed to the event handler when it is called. In this case, we want to pass a handle to the application-specific data attached to the window. The last parameter to InstallEventHandler is the address of a variable of type EventHandlerRef; this is an opaque type that refers to the newly-installed event handler. We don't need that reference, so we pass NULL.

Listing 3 shows our revised version of QTFrame_CreateMovieWindow.

Listing 3: Creating a movie window (revised)

WindowReference QTFrame_CreateMovieWindow (void)
{
   WindowReference      myWindow = NULL;

   // create a new window to display the movie in
   myWindow = NewCWindow(NULL, &gWindowRect, gWindowTitle, 
            false, noGrowDocProc, (WindowPtr)-1L, true, 0);

   // create a new window object associated with the new window
   QTFrame_CreateWindowObject(myWindow);

#if USE_CARBON_EVENTS
{
   EventTypeSpec      myEventSpec[] = { 
      {kEventClassKeyboard, kEventRawKeyDown},
      {kEventClassKeyboard, kEventRawKeyRepeat},
      {kEventClassKeyboard, kEventRawKeyUp},
      {kEventClassWindow, kEventWindowUpdate},
      {kEventClassWindow, kEventWindowDrawContent},
      {kEventClassWindow, kEventWindowActivated},
      {kEventClassWindow, kEventWindowDeactivated},
      {kEventClassWindow, kEventWindowHandleContentClick},
      {kEventClassWindow, kEventWindowClose}
   };

   // install Carbon event handlers for this window
   InstallStandardEventHandler
            (GetWindowEventTarget(myWindow));
   if (gWinEventHandlerUPP != NULL)
      InstallEventHandler(GetWindowEventTarget(myWindow), 
            gWinEventHandlerUPP, GetEventTypeCount(myEventSpec), 
            myEventSpec, 
            QTFrame_GetWindowObjectFromWindow(myWindow), NULL);
}
#endif

   return(myWindow);
}

Handling Window Events

Now, when the Carbon Event Manager receives an event of a kind we want to handle for a document window, it calls QTFrame_CarbonEventWindowHandler, which is declared like this:

PASCAL_RTN OSStatus QTFrame_CarbonEventWindowHandler 
            (EventHandlerCallRef theCallRef, EventRef theEvent, 
            void *theRefCon)

The event handler is passed the reference constant we specified when we called InstallEventHandler, as well as an event reference and an event handler call reference. The event reference is an opaque data structure that contains information about the event, such as its class, its kind, and any additional parameters for the event. We can use the GetEventClass and GetEventKind functions to extract the event class and kind from the event reference:

myClass = GetEventClass(theEvent);
myKind = GetEventKind(theEvent);

And we can use GetEventParameter to retrieve any additional event parameters. For instance, when the event class is kEventClassWindow and the event kind is kEventWindowHandleContentClick, then the direct object parameter is a window pointer for the target window; we can retrieve that information like this:

WindowRef         myWindow = NULL;
myErr = GetEventParameter(theEvent, kEventParamDirectObject, 
            typeWindowRef, NULL, sizeof(myWindow), NULL, 
            &myWindow);

Recall that with the "classic" Event Manager, the amount of information associated with an event was limited to what could be stuffed into an event record; with the Carbon Event Manager, any number of event parameters can be associated with a particular event type.

The theCallRef parameter passed to QTFrame_CarbonEventWindowHandler is a reference to the next event handler in the event handler sequence for the associated event target. The Carbon Event Manager arranges handlers into a sequence (technically, a stack) and calls each handler in turn until one of them handles the event. This scheme allows us to override certain behaviors of a standard event handler but perhaps not all. For example, when we receive the kEventWindowClose event, we can prompt the user to save or discard any changes to the movie data in a window and then fall through to the standard event handler to actually close the window. (We can avoid this "fall through" by returning any result code other than eventNotHandledErr.) And if we want to invoke the next event handler in the stack before we do our custom processing, we can call CallNextEventHandler with the event handler call reference passed to our own event handler. In QTShell we won't use this event handler call reference, since we shall completely handle any events we are registered to receive or else fall through to the standard handlers.

Let's take a look at how we handle a few of the events targeted at a document window. It turns out that most of these events can be directly translated into classic events and then processed using our existing function QTFrame_HandleEvent. We can use the function ConvertEventRefToEventRecord to convert a Carbon event into its corresponding classic event:

EventRecord      myEvent;
ConvertEventRefToEventRecord(theEvent, &myEvent);

ConvertEventRefToEventRecord returns a Boolean value that indicates whether it successfully converted the Carbon event into a classic event; so we can process our keyboard events like this:

case kEventClassKeyboard:
   switch (myKind) {
      case kEventRawKeyDown:
      case kEventRawKeyRepeat:
      case kEventRawKeyUp:
         if (ConvertEventRefToEventRecord(theEvent, &myEvent))
            QTFrame_HandleEvent(&myEvent);
         myErr = noErr;
         break;
   }
   break;

For some kinds of Carbon events, ConvertEventRefToEventRecord doesn't seem to work; in those cases, we need to construct an event record ourselves. A good example here is the kEventWindowHandleContentClick event. We can handle it by explicitly retrieving the relevant event parameters and assigning them to the fields of the event record, as shown in Listing 4.

Listing 4: Handling document window events

PASCAL_RTN OSStatus QTFrame_CarbonEventWindowHandler 
            (EventHandlerCallRef theCallRef, EventRef theEvent, 
             void *theRefCon)
{
#pragma unused(theCallRef)
   UInt32               myClass, myKind;
   UInt32               myModifiers;
   WindowRef         myWindow = NULL;
   EventRecord      myEvent;
   WindowObject      myWindowObject = (WindowObject)theRefCon;
   OSStatus            myErr = eventNotHandledErr;

   myClass = GetEventClass(theEvent);
   myKind = GetEventKind(theEvent);

   switch (myClass) {
      case kEventClassKeyboard:
         switch (myKind) {
            case kEventRawKeyDown:
            case kEventRawKeyRepeat:
            case kEventRawKeyUp:
               if (ConvertEventRefToEventRecord(theEvent, 
                                                         &myEvent))
                  QTFrame_HandleEvent(&myEvent);
               myErr = noErr;
               break;
         }
         break;

      case kEventClassWindow:
         switch (myKind) {
            case kEventWindowUpdate:
            case kEventWindowDrawContent:
            case kEventWindowActivated:
            case kEventWindowDeactivated:
               if (ConvertEventRefToEventRecord(theEvent, 
                                                         &myEvent))
                  QTFrame_HandleEvent(&myEvent);
               myErr = noErr;
               break;
            case kEventWindowHandleContentClick:
               myErr = GetEventParameter(theEvent, 
                     kEventParamDirectObject, typeWindowRef, NULL, 
                     sizeof(myWindow), NULL, &myWindow);
               if (myErr == noErr) {

                  GetEventParameter(theEvent, 
                     kEventParamKeyModifiers, typeUInt32, NULL, 
                     sizeof(myModifiers), NULL, &myModifiers);
                  GetEventParameter(theEvent, 
                     kEventParamMouseLocation, typeQDPoint, NULL, 
                     sizeof(Point), NULL, &myEvent.where);

                  myEvent.what = mouseDown;
                  myEvent.message = (long)myWindow;
                  myEvent.modifiers = myModifiers;
                  myEvent.when = EventTimeToTicks(
                     GetCurrentEventTime());

                  QTFrame_HandleEvent(&myEvent);
                  myErr = noErr;
               }
               break;

            case kEventWindowClose:
               if (myWindowObject != NULL) {
                  QTFrame_DestroyMovieWindow(
                                       (**myWindowObject).fWindow);
                  myErr = noErr;
               }
               break;

            default:
               break;
         }
         break;
   }

   return(myErr);
}

Notice that we set the when field of the event record by calling GetCurrentEventTime and converting the value it returns into ticks (using the utility macro EventTimeToTicks); this is the Carbon event replacement for good old TickCount.

Menus

Currently, we handle Macintosh menu selections in the standard "classic" fashion: we call MenuSelect when we get a mouse-down event in the menu bar. MenuSelect drops down the menus and tracks events in them until the user releases the mouse button; then it returns a 32-bit long integer whose high-order 16-bit word is the ID of the menu and whose low-order 16-bit word is the menu item index. Our function QTFrame_HandleMenuCommand inspects that long integer and reacts appropriately.

On Windows, a menu item is specified by a single 16-bit "menu item identifier", which is an arbitrary value that we associate with the menu item. Because the value on Windows is arbitrary, we've constructed it by setting the high-order 8 bits to the Macintosh menu ID and the low-order 8-bits to the index of the menu item in the menu. Here are the values we use for the File menu items:

#define IDM_FILENEW                           33025
#define IDM_FILEOPEN                        33026
#define IDM_FILECLOSE                        33027
#define IDM_FILESAVE                        33028
#define IDM_FILESAVEAS                        33029
#define IDM_EXIT                              33031

We've designed our menu-handling functions (QTFrame_HandleFileMenuItem, QTFrame_HandleEditMenuItem, and QTApp_HandleMenu) to accept these 16-bit values, and we've defined several macros to help us construct and deconstruct a menu item identifier:

#define MENU_IDENTIFIER(menuID,menuItem)
                                       ((menuID<<8)+(menuItem))
#define MENU_ID(menuIdentifier)
                                       ((menuIdentifier&0xff00)>>8)
#define MENU_ITEM(menuIdentifier)
                                       ((menuIdentifier&0x00ff))

The Carbon Event Manager greatly simplifies our menu handling. The default application event handler processes all events in the menu bar and in menus; our application is called only when a menu state needs to be adjusted or when the user actually selects a menu item. Notifications of menu selections are sent to an application's event callback function that handles command events. The event class is kEventClassCommand and the event kind is kEventCommandProcess.

Defining Command IDs

When our menu event callback function is called, we determine which menu item was selected by looking at the command ID associated with the event. A command ID is a 32-bit value; we are free to associate any value with any menu item, but the Carbon Event Manager defines a set of IDs for some of the standard menu items. For instance, it defines these constants for the Edit menu items:

enum {
   kHICommandUndo                  = FOUR_CHAR_CODE(‘undo'),
   kHICommandRedo                  = FOUR_CHAR_CODE(‘redo'),
   kHICommandCut                     = FOUR_CHAR_CODE(‘cut ‘),
   kHICommandCopy                  = FOUR_CHAR_CODE(‘copy'),
   kHICommandPaste                  = FOUR_CHAR_CODE(‘past'),
   kHICommandClear                  = FOUR_CHAR_CODE(‘clea'),
   kHICommandSelectAll            = FOUR_CHAR_CODE(‘sall')
};

And the Carbon Event Manager defines this constant for the Quit item in the File menu (on Mac OS 8 and 9) or the Application menu (on Mac OS X):

enum {
   kHICommandQuit                  = FOUR_CHAR_CODE(‘quit')
};

We're going to keep using our custom menu item identifiers, but we also need to assign command IDs to the menu items when we are using the Carbon Event Manager. We'll define the COMMAND_ID macro to convert a 16-bit menu item identifier into a 32-bit command ID:

#define COMMAND_ID(menuIdentifier)
   ((MENU_ID(menuIdentifier)<<16)+(MENU_ITEM(menuIdentifier)))

When QTShell starts up, we need to call SetMenuItemCommandID to associate command IDs with menu items. Listing 5 shows the code we'll call.

Listing 5: Defining the framework command IDs

#if USE_CARBON_EVENTS
   myMenu = GetMenuHandle(kAppleMenuResID);
   SetMenuItemCommandID(myMenu, kAboutMenuItem, COMMAND_ID(
            MENU_IDENTIFIER(kAppleMenuResID,kAboutMenuItem)));

   myMenu = GetMenuHandle(kFileMenuResID);
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_FILENEW), 
                                             COMMAND_ID(IDM_FILENEW));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_FILEOPEN), 
                                             COMMAND_ID(IDM_FILEOPEN));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_FILECLOSE), 
                                             COMMAND_ID(IDM_FILECLOSE));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_FILESAVE), 
                                             COMMAND_ID(IDM_FILESAVE));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_FILESAVEAS), 
                                             COMMAND_ID(IDM_FILESAVEAS));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_EXIT), 
                                             COMMAND_ID(IDM_EXIT));

   myMenu = GetMenuHandle(kEditMenuResID);
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_EDITUNDO), 
                                             COMMAND_ID(IDM_EDITUNDO));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_EDITCUT), 
                                             COMMAND_ID(IDM_EDITCUT));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_EDITCOPY), 
                                             COMMAND_ID(IDM_EDITCOPY));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_EDITPASTE), 
                                             COMMAND_ID(IDM_EDITPASTE));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_EDITCLEAR), 
                                             COMMAND_ID(IDM_EDITCLEAR));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_EDITSELECTALL), 
                                       COMMAND_ID(IDM_EDITSELECTALL));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_EDITSELECTNONE), 
                                       COMMAND_ID(IDM_EDITSELECTNONE));
#endif

Of course, applications built on top of QTShell may have additional menus, and we need to define command IDs for items in those menus are well. Listing 6 shows how we can set up command IDs for items in the Test menu.

Listing 6: Defining the application-specific command IDs

#if USE_CARBON_EVENTS
   MenuRef      myMenu = NULL;

   myMenu = GetMenuHandle(kTestMenuResID);
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_CONTROLLER), 
                                    COMMAND_ID(IDM_CONTROLLER));
   SetMenuItemCommandID(myMenu, MENU_ITEM(IDM_SPEAKER_BUTTON), 
                                    COMMAND_ID(IDM_SPEAKER_BUTTON));
#endif

It's worth noting that we could instead embed command IDs in our application's resources. The ‘xmnu' resource type contains extended menu information, including a command ID to be associated with a particular menu item. In QTShell, we'll use SetMenuItemCommandID, as shown above. (It's also worth noting that we could specify command IDs in a .nib file, if we were using .nib files instead of resources to define our application's user interface.)

Adjusting Menus

Our application receives an event of class kEventClassCommand and kind kEventCommandUpdateStatus when the menus need to be adjusted. We can handle that event by calling our function QTFrame_AdjustMenus:

case kEventCommandUpdateStatus:
   myErr = GetEventParameter(theEvent, 
                     kEventParamDirectObject, typeHICommand, NULL, 
                     sizeof(myCommand), NULL, &myCommand);
   if ((myErr == noErr) && 
            ((myCommand.attributes & kHICommandFromMenu) != 0))
      QTFrame_AdjustMenus(FrontWindow(), NULL, 0L);
   break;

We adjust the menus only if the command ID is being sent to us as the result of some event involving a menu (that is, if the kHICommandFromMenu bit is set in the command attributes).

Handling Menu Selections

When the user selects one of our application's menu items, the Carbon Event Manager sends us an event of class kEventClassCommand and kind kEventCommandProcess. Once again, we check that the command arises from a menu selection; then we call our existing function QTFrame_HandleMenuCommand:

case kEventCommandProcess:
   myErr = GetEventParameter(theEvent, 
                     kEventParamDirectObject, typeHICommand, NULL, 
                     sizeof(myCommand), NULL, &myCommand);
   if ((myErr == noErr) && 
            ((myCommand.attributes & kHICommandFromMenu) != 0))
      myErr = QTFrame_HandleMenuCommand(myCommand.commandID) ? 
                                          noErr : eventNotHandledErr;
   break;

Remember that an event handler should return eventNotHandledErr when we want the event to be propagated to other handlers in the call chain. I've reworked QTFrame_HandleMenuCommand (and the menu handlers it calls) so that it returns a Boolean value indicating whether the menu command was handled by the application. We return noErr if we handle the event.

Listing 7 shows our complete application event handler QTFrame_CarbonEventAppHandler, which handles command events.

Listing 7: Handling commands

PASCAL_RTN OSStatus QTFrame_CarbonEventAppHandler 
            (EventHandlerCallRef theCallRef, EventRef theEvent, 
            void *theRefCon)
{
#pragma unused(theCallRef, theRefCon)
   UInt32            myClass, myKind;
   HICommand      myCommand;
   OSStatus         myErr = eventNotHandledErr;

   myClass = GetEventClass(theEvent);
   myKind = GetEventKind(theEvent);

   switch (myClass) {
      case kEventClassCommand:
         switch (myKind) {
            case kEventCommandProcess:
               myErr = GetEventParameter(theEvent, 
                     kEventParamDirectObject, typeHICommand, NULL, 
                     sizeof(myCommand), NULL, &myCommand);
               if ((myErr == noErr) && 
            ((myCommand.attributes & kHICommandFromMenu) != 0))
                  myErr = 
                     QTFrame_HandleMenuCommand(myCommand.commandID) 
                     ? noErr : eventNotHandledErr;
               break;

            case kEventCommandUpdateStatus:
               myErr = GetEventParameter(theEvent, 
                     kEventParamDirectObject, typeHICommand, NULL, 
                     sizeof(myCommand), NULL, &myCommand);
               if ((myErr == noErr) && 
            ((myCommand.attributes & kHICommandFromMenu) != 0))
                  QTFrame_AdjustMenus(FrontWindow(), NULL, 0L);
               break;
         }
         break;

      case kEventClassMenu:
         switch (myKind) {
            case kEventMenuBeginTracking:
               gMenuIsTracking = true;
               break;

            case kEventMenuEndTracking:
               gMenuIsTracking = false;
               break;
         }
         break;
   }

   return(myErr);
}

As you can see, toward the end of QTFrame_CarbonEventAppHandler we also handle two commands in the kEventClassMenu event class; these commands are sent to our handler when the user begins and ends tracking a menu. In effect, the gMenuIsTracking global variable indicates whether a menu is currently being displayed. We'll use that global variable a bit later, when we see how to task our QuickTime movies.

Installing the Application Event Handler

Of course, we still need to install our application event handler, by calling InstallEventHandler. The Carbon Event Manager automatically installs a standard application event handler, so we don't need to do that. Listing 8 shows the code we add to QTFrame_InitMacEnvironment to install our application event handler.

Listing 8: Installing an application event handler

EventTypeSpec   myEventSpec[] = { 
                  {kEventClassMenu, kEventMenuBeginTracking},
                  {kEventClassMenu, kEventMenuEndTracking},
                  {kEventClassCommand, kEventCommandProcess},
                  {kEventClassCommand, kEventCommandUpdateStatus}
};

gAppEventHandlerUPP = NewEventHandlerUPP(
            QTFrame_CarbonEventAppHandler);
if (gAppEventHandlerUPP != NULL)
   InstallEventHandler(GetApplicationEventTarget(), 
            gAppEventHandlerUPP, GetEventTypeCount(myEventSpec), 
            myEventSpec, NULL, NULL);

Modal Windows

Now it's time to wean ourselves of ModalDialog. Happily, our application calls it only once, when we display our About box (Figure 1). The Carbon Event Manager provides the function RunAppModalLoopForWindow, which we can use instead of ModalDialog to put our application into a modal state. When the application is in a modal state, only events targeted at a specified window (or at any window in front of it) are processed by the Carbon Event Manager. The application remains in a modal state until it calls QuitAppModalLoopForWindow.


Figure 1: The About box of QTShell

Handling Events for the About Box

Our About box is pretty simple. We'll install the standard window event handler for it, to obtain all the usual window behaviors:

myWindow = GetDialogWindow(myDialog);
InstallStandardEventHandler(GetWindowEventTarget(myWindow));

The only thing our application needs to handle explicitly is a user click on the OK button or a keyboard shortcut for a click on that button. We can call SetWindowDefaultButton to map keyboard events to the OK button:

GetDialogItem(myDialog, kStdOkItemIndex, &myItemKind, 
            &myItemHandle, &myItemRect);
if (myItemHandle != NULL)
   SetWindowDefaultButton(myWindow, 
            (ControlHandle)myItemHandle);

And we handle user clicks on the OK button by installing an event handler that processes events of class kEventClassControl and kind kEventControlHit. Listing 9 shows the main segment of QTFrame_ShowAboutBox. Notice that we also register to receive draw events for the dialog window.

Listing 9: Displaying the application's About box

#if USE_CARBON_EVENTS
   EventTypeSpec      myEventSpec[] = {
                     {kEventClassWindow, kEventWindowDrawContent},
                     {kEventClassControl, kEventControlHit}
};

   // install a window handler for the dialog window
   GetDialogItem(myDialog, kStdOkItemIndex, &myItemKind, 
            &myItemHandle, &myItemRect);
   if (myItemHandle != NULL)
      SetWindowDefaultButton(myWindow, 
            (ControlHandle)myItemHandle);

   InstallStandardEventHandler(GetWindowEventTarget(myWindow));
   if (gWinEventModalHandlerUPP != NULL)
      InstallEventHandler(GetWindowEventTarget(myWindow), 
            gWinEventModalHandlerUPP, 
            GetEventTypeCount(myEventSpec), myEventSpec, 
            (void *)myDialog, NULL);

   // display and handle events in the dialog box until the user clicks OK
   RunAppModalLoopForWindow(myWindow);
#else
   // display and handle events in the dialog box until the user clicks OK
   do {
      ModalDialog(gModalFilterUPP, &myItem);
   } while (myItem != kStdOkItemIndex);
#endif

We specify the dialog pointer of the About box as the reference constant when we call InstallEventHandler. This allows us to know which dialog box to draw when we get the kEventWindowDrawContent event. Listing 10 shows our definition of our modal dialog event handler, QTFrame_CarbonEventModalWindowHandler.

Listing 10: Handling events for the About box

PASCAL_RTN OSStatus QTFrame_CarbonEventModalWindowHandler 
            (EventHandlerCallRef theCallRef, EventRef theEvent, 
            void *theRefCon)
{
#pragma unused(theCallRef)
   UInt32         myClass, myKind;
   DialogPtr   myDialog = (DialogPtr)theRefCon;
   OSStatus      myErr = eventNotHandledErr;

   myClass = GetEventClass(theEvent);
   myKind = GetEventKind(theEvent);

   switch (myClass) {
      case kEventClassWindow:
         switch (myKind) {
            case kEventWindowDrawContent:
               if (myDialog != NULL)
                  DrawDialog(myDialog);
               myErr = noErr;
               break;
         }
         break;

      case kEventClassControl:
         switch (myKind) {
            case kEventControlHit:
               if (myDialog != NULL)
                  myErr = QuitAppModalLoopForWindow(
                                    GetDialogWindow(myDialog));
               break;
         }
         break;
   }

   return(myErr);
}

We call QuitAppModalLoopForWindow, and hence leave the modal state, when the user clicks the OK button.

Handling Non-Document Windows

Let's digress briefly to fix a bug that can arise when QTShell or one of its descendants is running on Mac OS X and we display a help tag. A help tag is a message that can appear when the cursor is left motionless over some interface element (typically a window, a control, or a menu item) for a preset amount of time. Figure 2 shows a help tag associated with our About box.


Figure 2: A help tag

It's fairly simple to add help tags to a Carbon application. Listing 11 shows some code we can add to QTFrame_ShowAboutBox to display the help tag illustrated in Figure 2. (The application's resources include two strings with the specified ‘STR#' resource ID and indices.)

Listing 11: Adding a help tag to the About box

HMHelpContentRec      myContent;

myContent.version = kMacHelpVersion;
myContent.tagSide = kHMAbsoluteCenterAligned;

myContent.content[0].contentType = kHMStringResContent;
myContent.content[0].u.tagStringRes.hmmResID = 128;
myContent.content[0].u.tagStringRes.hmmIndex = 1;

myContent.content[1].contentType = kHMStringResContent;
myContent.content[1].u.tagStringRes.hmmResID = 128;
myContent.content[1].u.tagStringRes.hmmIndex = 2;

MacSetRect(&myContent.absHotRect, 0, 0, 0, 0);

HMSetWindowHelpContent(myWindow, &myContent);

The problem arises because a help tag is drawn inside of a small yellow window (as the drop-shadow in Figure 2 indicates) and our existing code is unable to distinguish that window from our movie or image windows. Currently, we determine whether a window is an application window (that is, a movie or image window) by calling QTFrame_IsAppWindow, defined in Listing 12.

Listing 12: Finding application windows

Boolean QTFrame_IsAppWindow (WindowReference theWindow)
{
   if (theWindow == NULL)
      return(false);

#if TARGET_OS_MAC
   return(GetWindowKind(theWindow) >= kApplicationWindowKind);
#endif
#if TARGET_OS_WIN32
   return(true);
#endif
}

The trouble is that — lo and behold — help tag windows also have the window kind kApplicationWindowKind. So the Macintosh code in QTFrame_IsAppWindow will decide that help tag windows are application windows; eventually our framework code will retrieve the window's reference constant and later doubly dereference it to get the associated window object data. On Mac OS X, where the operating system is rather strict about memory accesses, we'll get an access fault.

There are several ways to avoid this problem. Perhaps the best solution is to rework QTFrame_IsAppWindow so that it winnows out help tag windows. We can do this by inspecting the window class of the window passed in, as shown in Listing 13.

Listing 13: Finding application windows (revised)

Boolean QTFrame_IsAppWindow (WindowReference theWindow)
{
#if TARGET_OS_MAC
   UInt32         myClass = 0;
   OSStatus      myErr = noErr;

   if (theWindow == NULL)
      return(false);

   myErr = GetWindowClass(theWindow, &myClass);
   if (myErr != noErr)
      return(false);
   else
      return(myClass == kDocumentWindowClass);
#endif
#if TARGET_OS_WIN32
   return(theWindow != NULL);
#endif
}

Here we look at the window class (which we retrieve by calling GetWindowClass). Our movie and image windows have a window class of kDocumentWindowClass, while help tag windows have a window class of kHelpWindowClass. Problem solved.

It's worth noting that this problem has nothing to do with Carbon events; rather, it arises from the fact that (with our original code) a help tag is considered to be an application window, with a bona fide window object attached to it. It's also worth noting that the problem can arise even if our application doesn't display its own help tags; on Mac OS X, the Open file dialog box will display a help tag if the cursor remains motionless for the appropriate amount of time over a file name that's too long to fit in a column. Compare Figures 3 and 4.


Figure 3: The Open file dialog box with a truncated file name


Figure 4: A help tag in the Open file dialog box

Event Loop Timers

Because our applications play QuickTime movies, we need to call MCIsPlayerEvent periodically to make sure that QuickTime has time to process events for any open movies. When using the classic event model, we rely on the fact that our application will receive a steady stream of null events; since our call to MCIsPlayerEvent is contained inside of QTFrame_HandleEvent, MCIsPlayerEvent will be called often enough to keep any open movies playing smoothly.

The Carbon Event Manager does not issue null events. To ensure that an open QuickTime movie is tasked periodically, we can install an event loop timer. We create a universal procedure pointer to our timer callback routine by calling NewEventLoopTimerUPP:

gWinTimerHandlerUPP = NewEventLoopTimerUPP(
                                       QTFrame_CarbonEventWindowTimer);

Then we create a new timer and attach it to a window by calling InstallEventLoopTimer:

if (gWinTimerHandlerUPP != NULL)
   InstallEventLoopTimer(GetMainEventLoop(), 0, 
                     TicksToEventTime(kWNEMinimumSleep), 
                     gWinTimerHandlerUPP, myWindowObject, 
                     &(**myWindowObject).fTimerRef);

The first parameter specifies the event loop we want to attach the timer to; in QTShell we have only one event loop, which we get by calling GetMainEventLoop. The third parameter indicates the desired period between timer calls. The fifth parameter is a reference constant that's passed to the timer callback when it is executed; here we pass the window object associated with the window, so that we can use the window-specific data contained in it. An event loop timer reference is returned in the last parameter, which here is &(**myWindowObject).fTimerRef. We need to keep track of this reference so that we can later remove the event loop timer when the window is closed:

if ((**myWindowObject).fTimerRef != NULL)
   RemoveEventLoopTimer((**myWindowObject).fTimerRef);

As you can see, we've added a field fTimerRef to the window object record to hold the event loop timer reference.

We'll use our event loop timer callback function, QTFrame_CarbonEventWindowTimer, to call MCIdle on the window's movie controller, as you can see in Listing 14.

Listing 14: Handling event loop timer callbacks

PASCAL_RTN void QTFrame_CarbonEventWindowTimer
            (EventLoopTimerRef theTimer, void *theRefCon)
{
#pragma unused(theTimer)
   WindowObject   myWindowObject = (WindowObject)theRefCon;

   // just pretend a null event has been received....
   if ((myWindowObject != NULL) && 
                        ((**myWindowObject).fController != NULL))
      if (!gMenuIsTracking || gRunningUnderX)
         MCIdle((**myWindowObject).fController);
 }

We don't call MCIdle if the menu is being tracked and we've running on Mac OS 8 or 9. This is to prevent QuickTime from drawing on top of the dropped-down menus.

Notice that we install a separate event loop timer for each open movie window. As an alternative, we could install a single application-wide event loop timer whose callback function loops through all open movie windows and calls MCIdle on each window's movie controller. This alternate strategy would require a small amount of extra code (principally to make sure we don't have an active timer if no movie windows are open), but might result in better efficiency (since we are using only one event loop timer). I'm told, however, that any efficiency gains are likely to be minimal; as a result, I've chosen to use one event loop timer per movie window.

Tasking Interval Management

We use an event loop timer to call MCIdle, to task an open movie. This works great, except that the timer fires at the specified interval even if none of the movie's data handlers or media handlers has any work to do. QuickTime 6 provides several new functions that we can use to manage these tasking intervals. The key new function is QTGetTimeUntilNextTask, which we can use to find out when we next need to call MCIsPlayerEvent or MCIdle. For instance:

QTGetTimeUntilNextTask(&myDuration, 60);

QTGetTimeUntilNextTask returns, in the first parameter, the length of time until a QuickTime movie needs to be tasked next. The second parameter indicates the desired timescale of that duration. Passing 60 means that we want QTGetTimeUntilNextTask to give us a duration in ticks. Passing 1000 would give us a duration in milliseconds.

Adjusting the Classic Event Loop Interval

We can use QTGetTimeUntilNextTask to adjust the interval we pass to WaitNextEvent in our classic event loop. Listing 15 shows our final version of QTFrame_MainEventLoop.

Listing 15: Retrieving and dispatching events (final)

static void QTFrame_MainEventLoop (void)
{
#if USE_CARBON_EVENTS
   RunApplicationEventLoop();
#else
   EventRecord      myEvent;
   long                  myDuration = kWNEMinimumSleep;

   while (!gShuttingDown) {

   #if USE_TASK_MGMT
      // get the number of ticks until QuickTime's next task
      QTGetTimeUntilNextTask(&myDuration, 60);

      if (myDuration == 0)
         myDuration = kWNEMinimumSleep;
   #endif

      // get the next event in the queue
      WaitNextEvent(everyEvent, &myEvent, myDuration, NULL);

      // handle the event
      QTFrame_HandleEvent(&myEvent);

   } // while (!gShuttingDown)
#endif
}

QTGetTimeUntilNextTask can return a duration of 0, which means that QuickTime wants to be called immediately. It's generally a bad idea to pass a sleep time of 0 to WaitNextEvent, so we enforce a minimum sleep of kWNEMinimumSleep ticks. (kWNEMinimumSleep is set to 1.)

Adjusting the Carbon Event Loop Timer Interval

We can also use QTGetTimeUntilNextTask to adjust the period of our event loop timer. Each time our timer callback function is called, we can determine the interval to the next time we need to call MCIdle and then reset the timer interval accordingly. Here, we want to work in milliseconds:

QTGetTimeUntilNextTask(&myDuration, 1000);
if (theTimer != NULL)
   SetEventLoopTimerNextFireTime(theTimer, 
            myDuration * kEventDurationMillisecond);

Listing 16 shows our event loop timer with the tasking management code in place.

Listing 16: Handling event loop timer callbacks (revised)

PASCAL_RTN void QTFrame_CarbonEventWindowTimer 
            (EventLoopTimerRef theTimer, void *theRefCon)
{
   WindowObject   myWindowObject = (WindowObject)theRefCon;

#if USE_TASK_MGMT
   long               myDuration = 0L;

   // get the number of milliseconds until QuickTime's next task
   QTGetTimeUntilNextTask(&myDuration, 1000);

   if (myDuration == 0)
      myDuration = kMinAppTaskInMillisecs;

   // set the timer to fire at that time
   if (theTimer != NULL)
      SetEventLoopTimerNextFireTime(theTimer, 
               myDuration * kEventDurationMillisecond);
#endif

   // just pretend a null event has been received....
   if ((myWindowObject != NULL) && 
                        ((**myWindowObject).fController != NULL))
      if (!gMenuIsTracking || gRunningUnderX)
         MCIdle((**myWindowObject).fController);
}

If QTGetTimeUntilNextTask returns the value 0, we'll once again peg the delay to a predefined minimum to avoid swamping the processor with our QuickTime tasks.

Handling Task-Sooner Notifications

What happens if QuickTime needs to be tasked before the event loop timer fires next? To handle that possibility, we can install a task-sooner callback function. QuickTime calls this function if our application needs to task one of its movies before a specified event loop timer is scheduled to fire. We call QTInstallNextTaskNeededSoonerCallback to install our callback function:

#if USE_TASK_MGMT
gQTTaskSoonerUPP = NewQTNextTaskNeededSoonerCallbackUPP
            (QTFrame_NextTaskNeededSoonerProcedure);
if ((gQTTaskSoonerUPP != NULL) && 
            (gAppEventLoopTimerRef != NULL))
   QTInstallNextTaskNeededSoonerCallback(gQTTaskSoonerUPP, 
            1000, 0, (void *)(**myWindowObject).fTimerRef);
#endif

The callback function is quite simple. It calls SetEventLoopTimerNextFireTime to reset the event loop timer (Listing 17).

Listing 17: Handling task-sooner notifications

PASCAL_RTN void QTFrame_NextTaskNeededSoonerProcedure 
            (TimeValue theDuration, unsigned long theFlags, 
            void *theRefCon)
{
#pragma unused(theFlags)

   EventLoopTimerRef      myTimer = (EventLoopTimerRef)theRefCon;

   if (myTimer != NULL)
      SetEventLoopTimerNextFireTime(myTimer, 
            theDuration * kEventDurationMillisecond);
}

Of course, when our application is quitting, we want to dispose of the callback function UPP:

if (gQTTaskSoonerUPP != NULL)
   DisposeQTNextTaskNeededSoonerCallbackUPP(gQTTaskSoonerUPP);

The Carbon Movie Control

Now, wouldn't it be great if there were a way to display a movie in a window without having to worry about all this tasking and timer interval resetting and event handling? That is precisely what is provided by the Carbon movie control, introduced in QuickTime 6 on Mac OS X. The Carbon movie control is a custom control that displays and manages a QuickTime movie. We create the control by calling CreateMovieControl, like this:

myErr = CreateMovieControl(theWindow, &myRect, theMovie, 
            myFlags, &myControl);

The first parameter is the window that is to contain the new control. The second parameter is the control rectangle (in local coordinates); in our case, we'll use the entire movie window. The third parameter is the movie to be displayed using the movie control, a handle to which is returned in the fifth parameter.

The fourth parameter to CreateMovieControl specifies a set of flags that modify the behavior of the new movie control. Currently these flags are defined:

enum {
   kMovieControlOptionHideController      = (1L << 0),
   kMovieControlOptionLocateTopLeft         = (1L << 1),
   kMovieControlOptionEnableEditing         = (1L << 2),
   kMovieControlOptionHandleEditingHI      = (1L << 3),
   kMovieControlOptionSetKeysEnabled      = (1L << 4),
   kMovieControlOptionManuallyIdled         = (1L << 5)
};

In QTShell, we'll use these flags:

myFlags = kMovieControlOptionSetKeysEnabled | 
                  kMovieControlOptionLocateTopLeft | 
                  kMovieControlOptionEnableEditing;

The kMovieControlOptionManuallyIdled flag indicates that we want to task the movie ourselves (presumably with our own event loop timer). If this flag is clear, the movie control uses its own event loop timer to task the movie.

The movie control created by CreateMovieControl is associated with a movie controller; this movie controller is always attached to the movie and will be initially visible unless we set the kMovieControlOptionHideController flag. If we want to call movie controller functions, we can retrieve the movie controller by calling GetControlData:

myErr = GetControlData(myControl, kControlEntireControl, 
            kMovieControlDataMovieController, sizeof(myMC), 
            (void *)&myMC, NULL);

To appreciate just how cool the Carbon movie control is, try this: take some sample code that uses the Carbon event model but which knows nothing about QuickTime. Add the few lines of code you need to open a movie file and create a movie from that file. Add the line of code above that uses CreateMovieControl in the appropriate spot. Add the necessary QuickTime libraries to your project. Compile and link. Voilà, you now have a QuickTime-savvy application. It can't get any easier than that. (But it can get better: the Carbon movie control supports the basic movie controller editing operations, and it also adjusts the Edit menu items as appropriate to the state of the movie if the kMovieControlOptionHandleEditingHI flag is set.)

Conclusion

Let's wrap things up. We've managed to bring QTShell into the 21st Century by replacing its "classic" Event Manager underpinnings with the more elegant and more efficient machinery of the Carbon Event Manager. To accomplish this, we wrote and installed a few event handlers and a timer callback function, and we set the whole thing in motion by calling RunApplicationEventLoop. The Carbon Event Manager then sends our application events as they become available, and it invokes our timer callback function periodically so that we can call MCIdle to task our open movies.

We also saw how to use the tasking interval management functions introduced in QuickTime 6 to adjust the interval between timer callbacks. It's important to understand that we need to use these functions only in Carbon event-savvy applications that install their own timer callback functions or in any Macintosh applications that use the "classic" event model. On Windows, or in Cocoa applications that use the NSMovie and NSMovieView classes (which we'll investigate in an upcoming article), the tasking intervals are managed internally. Also, if we use the Carbon movie control (available on Mac OS X in QuickTime 6 and later), we don't need to worry about handling events or timer intervals at all.

QTShell still contains a few loose ends. In theory, there is no need for our About box to be a modal window. It could just as easily be implemented as a document window with a special window class. This would allow us to keep the About box open while the user operates on an open movie window. I'll leave this as an exercise for the interested reader.

References and Credits

For a more complete discussion of the basic application framework that underlies QTShell and its offspring, see the very first QuickTime Toolkit article, "QuickTime 101" in MacTech, January 2000. For a very good overview of Carbon events (written by the chief architect, Ed Voas), see "Introduction to Carbon Events" in MacTech, July 2001.

Thanks to Alex Beaman and Greg Chapman for reviewing this article and providing some helpful comments. Thanks also to Ed Voas and Eric Schlegel for providing some information about event loop timers.


Tim Monroe is a member of the QuickTime engineering team. You can contact him at monroe@apple.com. The views expressed here are not necessarily shared by his employer.

 
AAPL
$97.35
Apple Inc.
+0.32
MSFT
$44.49
Microsoft Corpora
+0.09
GOOG
$588.08
Google Inc.
-5.27

MacTech Search:
Community Search:

Software Updates via MacUpdate

TinkerTool 5.3 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Audio Hijack Pro 2.11.0 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Intermission 1.1.1 - Pause and rewind li...
Intermission allows you to pause and rewind live audio from any application on your Mac. Intermission will buffer up to 3 hours of audio, allowing users to skip through any assortment of audio... Read more
Autopano Giga 3.6 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more
Airfoil 4.8.7 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
Microsoft Remote Desktop 8.0.8 - Connect...
With Microsoft Remote Desktop, you can connect to a remote PC and your work resources from almost anywhere. Experience the power of Windows with RemoteFX in a Remote Desktop client designed to help... Read more
xACT 2.30 - Audio compression toolkit. (...
xACT stands for X Aaudio Compression Toolkit, an application that encodes and decodes FLAC, SHN, Monkey’s Audio, TTA, Wavpack, and Apple Lossless files. It also can encode these formats to MP3, AAC... Read more
Firefox 31.0 - Fast, safe Web browser. (...
Firefox for Mac offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals... Read more
Little Snitch 3.3.3 - Alerts you to outg...
Little Snitch gives you control over your private outgoing data. Track background activityAs soon as your computer connects to the Internet, applications often have permission to send any... Read more
Thunderbird 31.0 - Email client from Moz...
As of July 2012, Thunderbird has transitioned to a new governance model, with new features being developed by the broader free software and open source community, and security fixes and improvements... Read more

Latest Forum Discussions

See All

Garfield: Survival of the Fattest Coming...
Garfield: Survival of the Fattest Coming to iOS this Fall Posted by Jennifer Allen on July 25th, 2014 [ permalink ] Who loves lasagna? Me. Also everyone’s favorite grumpy fat cat, Garfield. | Read more »
Happy Flock Review
Happy Flock Review By Andrew Fisher on July 25th, 2014 Our Rating: :: HERD IT ALL BEFOREUniversal App - Designed for iPhone and iPad Underneath the gloss of Happy Flock’s visuals is a game of very little substance. It’s cute, but... | Read more »
Square Register Updates Adds Offline Pay...
Square Register Updates Adds Offline Payments Posted by Ellis Spice on July 25th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Looking For Group – Hearthstone’s Curse...
For the first time since its release (which has thankfully been a much shorter window for iPad players than their PC counterparts), Blizzard’s wildly successful Hearthstone: Heroes of Warcraft CCG is sporting some brand new content: the single... | Read more »
Poptile Review
Poptile Review By Jennifer Allen on July 25th, 2014 Our Rating: :: SIMPLY FUNUniversal App - Designed for iPhone and iPad Simple yet a little bit glorious, Poptile is a satisfying entertaining puzzle game with oodles of the ‘one... | Read more »
Modern Combat 5: Blackout Review
Modern Combat 5: Blackout Review By Brittany Vincent on July 25th, 2014 Our Rating: :: LESS QQ, MORE PEW PEWUniversal App - Designed for iPhone and iPad The fifth entry into the blockbuster Modern Combat series is what mobile... | Read more »
Watch and Share Mobile Gameplay Videos W...
Watch and Share Mobile Gameplay Videos With Kamcord Posted by Jennifer Allen on July 25th, 2014 [ permalink ] iPhone App - Designed for the iPhone, compatible with the iPad | Read more »
THE KING OF FIGHTERS '98 (Games)
THE KING OF FIGHTERS '98 1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0 (iTunes) Description: Series’ masterpiece “KOF ’98” finally joins the battle on iPhone! FEATURES:■ The best game balance in the “KOF”... | Read more »
LEX Goes Free For One Day In Honor of Ne...
LEX Goes Free For One Day In Honor of New Update Posted by Jennifer Allen on July 24th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Thomas Was Alone Goes Universal, Slashes...
Thomas Was Alone Goes Universal, Slashes Price to $3.99 Posted by Ellis Spice on July 24th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »

Price Scanner via MacPrices.net

Mac minis on sale for $100 off MSRP, starting...
Best Buy has Mac minis on sale for $100 off MSRP. Choose free shipping or free instant local store pickup. Prices are for online orders only, in-store prices may vary: 2.5GHz Mac mini: $499.99 2.3GHz... Read more
Global Tablet Market Grows 11% in Q2/14 Notwi...
Worldwide tablet sales grew 11.0 percent year over year in the second quarter of 2014, with shipments reaching 49.3 million units according to preliminary data from the International Data Corporation... Read more
New iPhone 6 Models to Have Staggered Release...
Digitimes’ Cage Chao and Steve Shen report that according to unnamed sources in Apple’s upstream iPhone supply chain, the new 5.5-inch iPhone will be released several months later than the new 4.7-... Read more
New iOS App Helps People Feel Good About thei...
Mobile shoppers looking for big savings at their favorite stores can turn to the Goodshop app, a new iOS app with the latest coupons and deals at more than 5,000 online stores. In addition to being a... Read more
Save on 5th generation refurbished iPod touch...
The Apple Store has Apple Certified Refurbished 5th generation iPod touches available starting at $149. Apple’s one-year warranty is included with each model, and shipping is free. Many, but not all... Read more
What Should Apple’s Next MacBook Priority Be;...
Stabley Times’ Phil Moore says that after expanding its iMac lineup with a new low end model, Apple’s next Mac hardware decision will be how it wants to approach expanding its MacBook lineup as well... Read more
ArtRage For iPhone Painting App Free During C...
ArtRage for iPhone is currently being offered for free (regularly $1.99) during Comic-Con San Diego #SDCC, July 24-27, in celebration of the upcoming ArtRage 4.5 and other 64-bit versions of the... Read more
With The Apple/IBM Alliance, Is The iPad Now...
Almost since the iPad was rolled out in 2010, and especially after Apple made a 128 GB storage configuration available in 2012, there’s been debate over whether the iPad is a serious tool for... Read more
MacBook Airs on sale starting at $799, free s...
B&H Photo has the new 2014 MacBook Airs on sale for up to $100 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels... Read more
Apple 27″ Thunderbolt Display (refurbished) a...
The Apple Store has Apple Certified Refurbished 27″ Thunderbolt Displays available for $799 including free shipping. That’s $200 off the cost of new models. Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Sr. Project Manager for *Apple* Campus 2 -...
…the design and construction of one building or building components of the New Apple Campus located in Cupertino, CA. They will provide project management oversight for Read more
WW Sales Program Manager, *Apple* Online St...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.