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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Whitethorn Games combines two completely...
If you have ever gone fishing then you know that it is a lesson in patience, sitting around waiting for a bite that may never come. Well, that's because you have been doing it wrong, since as Whitehorn Games now demonstrates in new release Skate... | Read more »
Call of Duty Warzone is a Waiting Simula...
It's always fun when a splashy multiplayer game comes to mobile because they are few and far between, so I was excited to see the notification about Call of Duty: Warzone Mobile (finally) launching last week and wanted to try it out. As someone who... | Read more »
Albion Online introduces some massive ne...
Sandbox Interactive has announced an upcoming update to its flagship MMORPG Albion Online, containing massive updates to its existing guild Vs guild systems. Someone clearly rewatched the Helms Deep battle in Lord of the Rings and spent the next... | Read more »
Chucklefish announces launch date of the...
Chucklefish, the indie London-based team we probably all know from developing Terraria or their stint publishing Stardew Valley, has revealed the mobile release date for roguelike deck-builder Wildfrost. Developed by Gaziter and Deadpan Games, the... | Read more »
Netmarble opens pre-registration for act...
It has been close to three years since Netmarble announced they would be adapting the smash series Solo Leveling into a video game, and at last, they have announced the opening of pre-orders for Solo Leveling: Arise. [Read more] | Read more »
PUBG Mobile celebrates sixth anniversary...
For the past six years, PUBG Mobile has been one of the most popular shooters you can play in the palm of your hand, and Krafton is celebrating this milestone and many years of ups by teaming up with hit music man JVKE to create a special song for... | Read more »
ASTRA: Knights of Veda refuse to pump th...
In perhaps the most recent example of being incredibly eager, ASTRA: Knights of Veda has dropped its second collaboration with South Korean boyband Seventeen, named so as it consists of exactly thirteen members and a video collaboration with Lee... | Read more »
Collect all your cats and caterpillars a...
If you are growing tired of trying to build a town with your phone by using it as a tiny, ineffectual shover then fear no longer, as Independent Arts Software has announced the upcoming release of Construction Simulator 4, from the critically... | Read more »
Backbone complete its lineup of 2nd Gene...
With all the ports of big AAA games that have been coming to mobile, it is becoming more convenient than ever to own a good controller, and to help with this Backbone has announced the completion of their 2nd generation product lineup with their... | Read more »
Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »

Price Scanner via MacPrices.net

Deal Alert! B&H Photo has Apple’s 14-inch...
B&H Photo has new Gray and Black 14″ M3, M3 Pro, and M3 Max MacBook Pros on sale for $200-$300 off MSRP, starting at only $1399. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8... Read more
Department Of Justice Sets Sights On Apple In...
NEWS – The ball has finally dropped on the big Apple. The ball (metaphorically speaking) — an antitrust lawsuit filed in the U.S. on March 21 by the Department of Justice (DOJ) — came down following... Read more
New 13-inch M3 MacBook Air on sale for $999,...
Amazon has Apple’s new 13″ M3 MacBook Air on sale for $100 off MSRP for the first time, now just $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD/Space Gray): $999 $100 off MSRP... Read more
Amazon has Apple’s 9th-generation WiFi iPads...
Amazon has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80-$100 off MSRP, starting only $249. Their prices are the lowest available for new iPads anywhere: – 10″ 64GB WiFi iPad (Space Gray or... Read more
Discounted 14-inch M3 MacBook Pros with 16GB...
Apple retailer Expercom has 14″ MacBook Pros with M3 CPUs and 16GB of standard memory discounted by up to $120 off Apple’s MSRP: – 14″ M3 MacBook Pro (16GB RAM/256GB SSD): $1691.06 $108 off MSRP – 14... Read more
Clearance 15-inch M2 MacBook Airs on sale for...
B&H Photo has Apple’s 15″ MacBook Airs with M2 CPUs (8GB RAM/256GB SSD) in stock today and on clearance sale for $999 in all four colors. Free 1-2 delivery is available to most US addresses.... Read more
Clearance 13-inch M1 MacBook Airs drop to onl...
B&H has Apple’s base 13″ M1 MacBook Air (Space Gray, Silver, & Gold) in stock and on clearance sale today for $300 off MSRP, only $699. Free 1-2 day shipping is available to most addresses in... Read more
New promo at Visible: Buy a new iPhone, get $...
Switch to Visible, and buy a new iPhone, and Visible will take $10 off their monthly Visible+ service for 24 months. Visible+ is normally $45 per month. With this promotion, the cost of Visible+ is... Read more
B&H has Apple’s 13-inch M2 MacBook Airs o...
B&H Photo has 13″ MacBook Airs with M2 CPUs and 256GB of storage in stock and on sale for $100 off Apple’s new MSRP, only $899. Free 1-2 day delivery is available to most US addresses. Their... Read more
Take advantage of Apple’s steep discounts on...
Apple has a full line of 16″ M3 Pro and M3 Max MacBook Pros available, Certified Refurbished, starting at $2119 and ranging up to $600 off MSRP. Each model features a new outer case, shipping is free... Read more

Jobs Board

Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Business Analyst | *Apple* Pay - Banco Popu...
Business Analyst | Apple PayApply now " Apply now + Apply Now + Start applying with LinkedIn Start + Please wait Date:Mar 19, 2024 Location: San Juan-Cupey, PR Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.