TweetFollow Us on Twitter

Animal Crackers

Volume Number: 19 (2003)
Issue Number: 1
Column Tag: QuickTime Toolkit

Animal Crackers

Enhancing Cocoa QuickTime Applications

by Tim Monroe

Introduction

In the previous QuickTime Toolkit article ("The Cocoanuts" in MacTech, December 2002), we took a look at using Cocoa, Apple's object-oriented application framework, to develop QuickTime applications. We saw how to put together a basic multi-window application that can open, play, and edit QuickTime movies, and we also investigated a few ways to extend that application by subclassing Cocoa's built-in QuickTime classes or by adding additional methods to those classes with categories.

In this article, I want to continue investigating Cocoa and QuickTime. Primarily, I want to tie up a few loose ends that we didn't have time to address in the previous article. For instance, we need to do a little bit more work to get all the items in the Edit and File menus working as expected, and we need to fix a few minor cosmetic problems with the application. Consider this article then to be some fine tuning to get our Cocoa application, MooVeez, to provide all the functionality of our sample application QTShell. Along the way, however, I also want to take time to investigate a few new topics, including the wired action introduced in QuickTime 6 that allows us to set a movie's scale. And, believe it or not, I'm going to slip in a few tweaks to QTShell itself. So don't be surprised if you see some Carbon code along the way.

Before we begin, I should note that in this article (and the previous one), we're relying on the features of QuickTime and Cocoa that shipped with the so-called Jaguar release: Mac OS X version 10.2 or later, and QuickTime 6.0 or later. Earlier versions of the QuickTime Cocoa classes, NSMovie and NSMovieView, have some noticeable problems in a few key areas. The Jaguar versions of these classes are much more reliable and efficient, so that's what I'll assume we're working with.

Displayable Paths

Let's begin with an easy but important update to our existing MooVeez code. Recall that our application can open a QuickTime movie in a window, and that this window provides a pop-out drawer listing some basic information about the movie (Figure 1).


Figure 1: A movie information drawer

The "Source" is the full path of the QuickTime movie file. In the previous article, I offered the setSource: method shown in Listing 1, which breaks the pathname into its components, tosses out the string "Volumes", and then rebuilds a path using the colon (":") as the path separator.

Listing 1: Displaying the movie's source

setSource
- (void)setSource: (NSString *)name
{
   NSArray *pathComponents = [name 
            componentsSeparatedByString:@"/"];
   NSEnumerator *pathEnumerator = [pathComponents 
            objectEnumerator];
   NSString *component = [pathEnumerator nextObject];
   NSMutableString *massagedPath = [NSMutableString string];
   while (component != nil) {
      if (([component length] > 0) && 
               (strcmp([component cString], "Volumes") != 0)) {
         [massagedPath appendString:component];
         component = [pathEnumerator nextObject];
         if (component != nil)
                [massagedPath appendString:@":"];
         } else {
            component = [pathEnumerator nextObject];
      }
   }
   [_sourceName setStringValue: massagedPath];
}

This isn't quite right, however, since it will toss out a path component named "Volumes" wherever it occurs in the full pathname (not just at the beginning).

I have subsequently learned that there is a Cocoa method that can assist us here; the NSFileManager class provides the componentsToDisplayForPath: method, which returns an array containing the components of a file's displayable name (that is, the name that should be displayed to the user). So if, as above, name is the full UNIX pathname of a file, we can get the array of displayable components like this:

NSArray *pathComponents = [[NSFileManager defaultManager] 
            componentsToDisplayForPath:name];

For instance, the full UNIX pathname of the movie file shown in Figure 1 is:

/Volumes/Meditations/Applications/QuickTime/Sample Movie

The componentsToDisplayForPath: method returns an array whose 4 elements are these:

Meditations
Applications
QuickTime
Sample Movie

Then we just need to reassemble these components with the appropriate path separator. Listing 2 shows our revised version of the setSource: method.

Listing 2: Displaying the movie's source (revised)

setSource
- (void)setSource:(NSString *)name
{
   NSArray *pathComponents = [[NSFileManager defaultManager] 
            componentsToDisplayForPath:name];
   NSEnumerator *pathEnumerator = [pathComponents 
            objectEnumerator];
   NSString *component = [pathEnumerator nextObject];
   NSMutableString *displayablePath = 
            [NSMutableString string];
   while (component != nil) {
      if ([component length] > 0) {
         [displayablePath appendString:component];
         component = [pathEnumerator nextObject];
         if (component != nil)
            [displayablePath appendString:@":"];
      } else {
         component = [pathEnumerator nextObject];
      }
   }
   [_sourceName setStringValue:displayablePath];
}

It's perhaps worth mentioning that Carbon applications can call the Launch Services functions LSCopyDisplayNameForRef or LSCopyDisplayNameForURL to get displayable path names.

Edit Menu

There is one outright bug in the Jaguar implementation of NSMovieView that rears its head when we try to undo a change to a movie. Do this: open a movie, select some of the movie data using the controller bar, and then cut the selected data. As expected, the Paste menu item is now enabled; the problem is that the Undo menu item is not enabled. (See Figure 2.) We ought to be able to undo the cut, and NSMovieView should be handling this automatically for us, but it's not.


Figure 2: The Edit menu of MooVeez

The Cocoa engineers are aware of this problem, and I believe that a fix has been found and will be incorporated into a future version of NSMovieView. In the meantime, there appears to be a fairly simple workaround to this problem. The workaround involves subclassing NSMovieView so that we can override the validateMenuItem: method. Listing 3 shows the override method.

Listing 3: Enabling and disabling the Undo menu item

validateMenuItem
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
   BOOL isValid = NO;
   SEL action = [item action];
   // handle the Undo menu item
   if (action == @selector(undo:)) {
      MovieController mc = [self movieController];
      if (mc) {
         long flags = 0L;
         MCGetControllerInfo(mc, &flags);
         isValid = flags & mcInfoUndoAvailable;
      }
   } else {
      isValid = [super validateMenuItem:item];
   }
   return isValid;
}

Here we're just calling MCGetControllerInfo and checking the returned set of flags to see whether the mcInfoUndoAvailable flag is set. If it is, we enable the Undo menu item; otherwise we disable the menu item. Notice that we pass all other menu items to the superclass (that is, to NSMovieView).

Once we've added this code to our subclass of NSMovieView, the Undo and Redo menu items are enabled and disabled as expected, as shown in Figure 3. In this case, the user has already cut some of the movie data and then undone the cut (that's why the Redo menu item is enabled).


Figure 3: The Edit menu of MooVeez (revised)

In addition, the Undo and Redo menu items appear to function correctly now. Take this new code for a spin: undo some cuts and pastes and see if everything works as expected. My preliminary tests do not reveal any problems, but I'm a bit worried by the following console message that appears the first time the user performs an undo operation:

Warning: -[NSMovieView undo:] is obsolete.

I recommend employing this workaround only after thoroughly testing it yourself.

File Menu

Now that we've got the Edit menu working pretty much as expected, let's take a quick look back at the File menu. In the previous article, we explicitly postponed adding support for creating new, empty movie windows -- that is, supporting the New menu item. To disable the New menu item, we added a validateMenuItem: method to our application controller class (defined in the file AppController.m). And we overrode the newDocument: method to act as a no-op. Since we want to add support for creating new empty movies, we need to remove these methods from our application controller class entirely. Once we do that, the default behaviors of the NSDocument class will kick in and we'll be able to create new empty documents.

For this to work correctly, however, we need to make a few simple fixes to the code that is called by the windowControllerDidLoadNib: method. Hitherto we have assumed that the user had opened a movie file by selecting the Open or "Open Recent" menu item and choosing a movie file, or by dropping a movie file onto the application's icon. In either case, we know that the movie is associated with an existing movie file. But once we allow the user to create a new movie, we have to make sure not to rely on NSDocument's fileName method returning a non-nil value. We'll revise the code in our initializeMovieWindowFromFile: method, as shown in Listing 4.

Listing 4: Creating a new movie

initializeMovieWindowFromFile
if ([self fileName]) {
   // open the movie file with read/write permission and load the movie from it
   err = FSPathMakeRef([[self fileName] 
            fileSystemRepresentation], &fileRef, NULL);
   if (err == noErr)
      err = FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, 
            NULL, &fileSpec, NULL);
   if (err == noErr)
      err = OpenMovieFile(&fileSpec, &fileRefNum, 
            fsRdWrPerm);
   if (err == noErr)
      err = NewMovieFromFile(&qtMovie, fileRefNum, 
            &fileResNum, NULL, 0, NULL);
} else {
   qtMovie = NewMovie(newMovieActive);
}

As you can see, we've conditionalized the existing code so that it is called only if the document already has a file associated with it; otherwise, we simply call NewMovie to create a new empty movie.

We also need to make sure to set the window title to the last component of the file name only if the file actually has a name:

if ([self fileName])
   [[_movieView window] setTitle:[self 
            lastComponentOfFileName]];

If we do not explicitly set the window title, NSDocument will generate one automatically. Figure 4 shows a new document window with an empty movie.


Figure 4: A new, empty document window

We can cut or copy data in other movie windows and then paste it into this new movie. And we can save the new movie into a file, just as we would expect.

Listing 4 is interesting also because it illustrates how to convert a string of type NSString into a file system specification (of type FSSpec): get a C string representation of the NSString, create an FSRef, and then call FSGetCatalogInfo to get an FSSpec record from the FSRef. Listing 5 shows this technique packaged into a nice utility method.

Listing 5: Converting a pathname string into a file system specification

NSStringToFSSpec
(void)NSStringToFSSpec:(NSString *)theFilePath 
            fsSpec:(FSSpec *)theFSSpecPtr
{
   FSRef fsRef;
   Boolean isDirectory = false;
   OSStatus err = noErr;
   // create an FSRef for the specified target file
   if (theFilePath) {
      err = FSPathMakeRef([theFilePath 
            fileSystemRepresentation], &fsRef, &isDirectory);
      // create an FSSpec record from the FSRef
      if (!err && !isDirectory) {
         err = FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL, 
            NULL, theFSSpecPtr, NULL);
      }
   }
}

This is useful because Cocoa likes to work with full pathnames, while the QuickTime APIs tend to prefer FSSpec records. Note, however, that the NSStringToFSSpec function works only with pathnames that describe existing files; if we pass in a path to a nonexistent file, FSPathMakeRef will return fnfErr (file not found) and fail to create an FSRef.

If we want to create an FSSpec record for a file that does not exist, we need to be a bit more creative. Listing 6 defines the writeToFile: method, which might be called (for instance) when the user selects the "Save As" menu item. As you can see, if FSPathMakeRef returns fnfErr, we call the standard UNIX file system functions open, write, and close to create a new file. Then we call FSPathMakeRef and FSGetCatalogInfo (as above) to get an FSSpec record, which we pass to FlattenMovie.

Listing 6: Writing a movie into a file

writeToFile
(BOOL)writeToFile:(NSString *)path 
            ofType:(NSString *)type
{
   FSRef fsRef;
   FSSpec fsSpec;
   NSString *newPath;
   OSStatus err = noErr;
   newPath = [NSString stringWithFormat:@"%@~", path];
   // create an FSRef for the specified target file
   err = FSPathMakeRef([newPath fileSystemRepresentation], 
            &fsRef, NULL);
   if (err == fnfErr) {
      // if the file does not yet exist, then let's create the file
      int fd;
      fd = open([newPath fileSystemRepresentation], 
            O_CREAT | O_RDWR, 0600);
      if (fd < 0)
         return NO;
      write(fd, " ", 1);
      close(fd);
      err = FSPathMakeRef([newPath fileSystemRepresentation], 
            &fsRef, NULL);
   }
   if (err == noErr) {
      // create an FSSpec record from the FSRef
      err = FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL, 
            NULL, &fsSpec, NULL);
      if (err == noErr) {
         short resId;
         // flatten the movie data into the specified target file
         FlattenMovie([[_movieView movie] QTMovie],
            flattenAddMovieToDataFork | 
            flattenForceMovieResourceBeforeMovieData,
            &fsSpec,
            'TVOD',
            smSystemScript,
            createMovieFileDeleteCurFile | 
            createMovieFileDontCreateResFile,
            &resId,
            nil);
         CloseMovieFile(resId);
      }
      rename([newPath fileSystemRepresentation], 
            [path fileSystemRepresentation]);
      return YES;
   }
   // clean up
   unlink([newPath fileSystemRepresentation]);
   return NO;
}

Window Resizing

Let's now take a look at a couple of issues related to resizing our movie windows. Typically our document windows will be resized directly by the user, by dragging the resize box in the lower right corner of the document window. In Interface Builder, we've set the Size attributes of the movie view as shown in Figure 5.


Figure 5: The size attributes of the movie view

The autosizing springs within the movie view and the rigid connections between the movie view and its superview (the document window) indicate that the movie view should grow or shrink to maintain a constant distance from all its edges to the edges of the document window. That is, when the user resizes the document window, the movie view will automatically be resized to maintain its border.

Occasionally, however, we need to adjust the size of the document window based on the desired size of the movie view. We did this previously when setting the size of the window for a newly-opened QuickTime movie. We'll also want to adjust the size of the document window when we receive the mcActionControllerSizeChanged movie controller action. So the first thing I want to do is rework our existing code a bit, to factor out the code that sets the movie size. Listing 7 shows our new method windowContentSizeForMovie:.

Listing 7: Getting a window size from a movie size

windowContentSizeForMovie
- (NSSize)windowContentSizeForMovie:(Movie)qtMovie
{
   NSSize size;
   Rect rect;
   GetMovieBox(qtMovie, &rect);
   size.width = (float)(rect.right - rect.left);
   size.height = (float)(rect.bottom - rect.top);
   // enforce a minimum width (important for sound-only movies)
   if (size.width == 0)
      size.width = (float)240;
   size.width += 2 * kMoviePaneOffset;
   size.height += 2 * kMoviePaneOffset;
   if ([_movieView isControllerVisible])
      size.height += kMovieControllerBarHeight;
   return size;
}

When the movie window is awaked from the nib file, we set the movie window size like this:

[[_movieView window] setContentSize:
            [self windowContentSizeForMovie:qtMovie]];

And when we receive the mcActionControllerSizeChanged movie controller action, we can set the movie window size as shown in Listing 8.

Listing 8: Setting a window size from a movie size

MyActionFilter
case mcActionControllerSizeChanged: 
   [[[doc movieView] window] setContentSize:
            [doc windowContentSizeForMovie:[[[doc movieView] 
            movie] QTMovie]]];
   break;

Now, when do we receive the mcActionControllerSizeChanged action? We'll receive it whenever the user manually resizes our document window, since NSMovieView informs the movie controller -- probably by calling MCSetControllerBoundsRect -- whenever the movie view is resized. (In this case, we really don't need to call setContentSize:, but it doesn't hurt to do so.) We'll also receive the mcActionControllerSizeChanged action when certain wired actions change the movie size. For instance, QuickTime 6 introduced the kActionMovieSetScale action, which sets the target movie's scale (or magnification). And earlier versions of QuickTime included the kActionTrackSetMatrix wired action, which sets a track's matrix. When the movie controller processes either of these actions, it will eventually inform our application of the size change by sending the mcActionControllerSizeChanged action to our filter procedure.

It turns out that the code in Listing 8 can lead to some drawing glitches when triggered by these wired actions. When a movie receives the kActionMovieSetScale action, our window can end up looking like the one in Figure 6. As you can see, the controller bar was not correctly erased at its previous position or redrawn fully in the new position.


Figure 6: Drawing problems after a set scale action

I haven't investigated this behavior enough to know whether it's a problem in QuickTime's action-handling code or in our own code. But there is at least one easy workaround: just inform the movie controller that the movie has changed, by calling MCMovieChanged. Listing 9 shows our updated code for handling the mcActionControllerSizeChanged action.

Listing 9: Setting a window size from a movie size (revised)

MyActionFilter
case mcActionControllerSizeChanged:
   [[[doc movieView] window] setContentSize:
            [doc windowContentSizeForMovie:[[[doc movieView] 
            movie] QTMovie]]];
   MCMovieChanged([[doc movieView] movieController], 
            [[[doc movieView] movie] QTMovie]);
   break;

By the way, this is not a Cocoa-specific problem. It will also affect our Carbon-based QTShell application. Accordingly, we should add a call to MCMovieChanged in QTShell's movie controller action filter procedure as well, as shown in Listing 10.

Listing 10: Setting a window size from a movie size (QTShell)

QTApp_MCActionFilterProc
case mcActionControllerSizeChanged:
   QTFrame_SizeWindowToMovie(myWindowObject);
   if (theMC && (**myWindowObject).fMovie)
      MCMovieChanged(theMC, (**myWindowObject).fMovie);
   isHandled = true;
   break;

There is at least one other occasion when the movie controller might send us the mcActionControllerSizeChanged action, namely when a streamed movie changes its size dynamically during playback. We don't need any additional code to handle this, but we do need to tell the Movie Toolbox that we want to be informed of any changes in the size of streamed movies. We do this by setting a movie playback hint when we open the movie, as follows:

SetMoviePlayHints(qtMovie, hintsAllowDynamicResize, 
            hintsAllowDynamicResize);

We are informed of movie size changes triggered by wired actions even if we don't set this play hint, but not those triggered by a dynamic size change of a streamed movie.

Cursor Adjustment

As you know, some media types change the cursor image as it moves over various regions in a track. Flash tracks and wired sprite tracks often do this, and QuickTime VR does this as a matter of course. (And with a vengeance: QuickTime VR defines nearly 80 different cursors that can be displayed as the user performs operations within a panoramic movie, and over 100 within an object movie.) Figure 7 shows a QuickTime VR movie with the mouse lying on top of a link hot spot (that is, a hot spot that, when clicked, moves the user to a new node).


Figure 7: A QuickTime VR node with the mouse over a link hot spot

The trouble is that some media types don't change the cursor back to the default arrow cursor when it moves outside of the movie box. The result is that our application ends up displaying an incorrect cursor, as shown in Figure 8. The mouse is just to the right of the movie box (toward the top of the movie). Note that its image is still one supplied by QuickTime VR, not the standard arrow cursor. We should fix that.


Figure 8: A QuickTime VR cursor displayed outside the movie box

Adjusting the Cursor in Carbon

Before we see how to adjust the cursor in our Cocoa application, let's digress briefly to consider how we already do this in our Carbon application QTShell. Listing 11 shows the definition of the QTApp_Idle function, which we call whenever we receive an idle event. As you can see, we simply check whether the current mouse position (which we get by calling GetMouse) is outside of the front movie window or front movie window's visible region; if the mouse is indeed outside of these regions, we call MacSetCursor to reset the cursor to the arrow cursor.

Listing 11: Adjusting the cursor in an idle procedure

QTApp_Idle
void QTApp_Idle (WindowReference theWindow)
{
   WindowObject         myWindowObject = NULL;
   GrafPtr               mySavedPort;
   GetPort(&mySavedPort);
   MacSetPort(QTFrame_GetPortFromWindowReference(theWindow));
   myWindowObject = QTFrame_GetWindowObjectFromWindow
            (theWindow);
   if (myWindowObject != NULL) {
      MovieController      myMC = NULL;
      myMC = (**myWindowObject).fController;
      if (myMC != NULL) {
#if TARGET_OS_MAC
         // restore the cursor to the arrow
         // if it's outside the front movie window or outside the window's visible region
         if (theWindow == QTFrame_GetFrontMovieWindow()) {
            Rect            myRect;
            Point         myPoint;
            RgnHandle   myVisRegion;
            Cursor         myArrow;
            GetMouse(&myPoint);
            myVisRegion = NewRgn();
            GetPortVisibleRegion
               (QTFrame_GetPortFromWindowReference(theWindow), 
                     myVisRegion);
            GetWindowPortBounds(theWindow, &myRect);
            if (!MacPtInRect(myPoint, &myRect) || 
                     !PtInRgn(myPoint, myVisRegion))
               MacSetCursor(GetQDGlobalsArrow(&myArrow));
            DisposeRgn(myVisRegion);
         }
#endif
      }
   }
   MacSetPort(mySavedPort);
}

Frankly, this is old-style Mac programming. It works well enough, but it's likely to eat up CPU cycles unnecessarily since it's still stuck in that old polling mindset. Let's upgrade QTShell by reimplementing cursor adjusting using Carbon events. (For a general discussion of Carbon events and QuickTime, see "Event Horizon" in MacTech, May 2002.)

My first attempt to use Carbon events here was to have the window event handler register for events of class kEventClassMouse and type kEventMouseMoved. After all, we need to check to see if the cursor needs to change only if it's been moved. The trouble is that a movie in QTShell completely fills the content region of a movie window (except for the space occupied by the controller bar at the bottom of a movie window). Once the cursor moves out of the movie to the left or right, it's no longer over the movie window and hence subsequent mouse movements will not trigger kEventMouseMoved events for that movie window.

So I ended by having the application event handler register for kEventMouseMoved events, as shown in Listing 12. When it receives a mouse-moved event, it retrieves the position of the mouse and then checks to see if the mouse is currently outside the frontmost movie window. If it is, the cursor is reset to the default arrow cursor.

Listing 12: Adjusting the cursor in a Carbon event handler

QTFrame_CarbonEventAppHandler
case kEventClassMouse:
   switch (myKind) {
      case kEventMouseMoved:
         myErr = GetEventParameter(theEvent, 
                  kEventParamMouseLocation, typeQDPoint, NULL, 
                  sizeof(Point), NULL, &myPoint);
         if (myErr == noErr) {
            WindowRef      myWindow = NULL;
            Rect               myRect;
            Cursor            myArrow;
         
            GlobalToLocal(&myPoint);
            
            // get the front movie window
            myWindow = QTFrame_GetFrontMovieWindow();
            if (myWindow != NULL) {
               GetWindowPortBounds(myWindow, &myRect);
               if (!PtInRect(myPoint, &myRect))
                  MacSetCursor(GetQDGlobalsArrow(&myArrow));
            }
         }
         break;
   }
   break;

This strategy appears to work quite nicely, and it avoids the polling behavior of our original code. Strictly speaking, we need to adjust the cursor only when a movie contains interactive tracks (that is, Flash, wired sprite, wired text, or QuickTime VR), since they are the only kinds of tracks that are likely to change the cursor from the default arrow cursor. I doubt, however, that there is much to be gained by checking for interactive track types here. It's probably better just to reset the cursor to the arrow for every kind of QuickTime movie, as we do here.

Adjusting the Cursor in Cocoa

Let's return to the more pressing concern of adjusting the cursor in our Cocoa application. There are two principal ways we can return the cursor to its default arrow shape when it moves outside of the movie rectangle. First, we can add a tracking rectangle to the NSMovieView view that holds our movie, by executing the addTrackingRect: method. For instance, inside of windowControllerDidLoadNib:, we can execute this line of code:

[_movieView addTrackingRect:[_movieView bounds] owner:self 
            userData:nil assumeInside:NO];

The addTrackingRect: method causes the specified owner to receive mouseEntered: and mouseExited: messages whenever the mouse is moved into and out of the specified rectangle inside the target view. The message recipient is often the same view whose rectangle will be tracked, but it need not be. In the present case, indeed, we are setting the document instance as the owner, so that these messages are sent to it and not to the movie view. This allows us to define mouseEntered: and mouseExited: within our custom document class, so that we do not need to subclass NSMovieView.

Actually, since we are only interested in knowing when the mouse leaves the movie view, we shall implement only the mouseExited: method (Listing 13).

Listing 13: Handling mouse-exited messages

mouseExited
- (void) mouseExited:(NSEvent*)event
{
   // set cursor to the default cursor
   [[NSCursor arrowCursor] set];
}

Here we send the factory method arrowCursor to the NSCursor class and then set the resulting cursor.

It turns out that windowControllerDidLoadNib: is not really the best place to call addTrackingRect:. The main reason for this is that the tracking rectangle is not automatically resized when the target view is resized. Rather, we need to explicitly remove any current tracking rectangle and then add a new one each time the movie view changes size. So let's make the call to addTrackingRect: inside of our movie controller action filter procedure, when we handle the mcActionControllerSizeChanged action. Listing 14 shows our revised code for handling this action.

Listing 14: Resetting the tracking rectangle

MyActionFilter
case mcActionControllerSizeChanged:
   [[[doc movieView] window] setContentSize:
            [doc windowContentSizeForMovie:
            [[[doc movieView] movie] QTMovie]]];
   [[doc movieView] removeTrackingRect:
            [doc trackingRectTag]];
   [doc setTrackingRectTag:[[doc movieView] 
            addTrackingRect:[[doc movieView] bounds] owner:doc 
            userData:nil assumeInside:NO]];
   MCMovieChanged([[doc movieView] movieController], 
            [[[doc movieView] movie] QTMovie]);
   break;

First, we send the removeTrackingRect: message to the movie view to remove any existing tracking rectangle. Notice that removeTrackingRect: takes a parameter, which specifies a tracking rectangle tag (of the scalar type NSTrackingRectTag). This tag is returned to us by addTrackingRect:, and we are storing it in the new instance variable _trackingRectTag in our document class. Since we are calling removeTrackingRect: and addTrackingRect: within our movie controller action filter procedure, the document class needs to provide accessor methods for us to get and set the tag. Listings 15 and 16 show our definitions of these two methods. Nothing earthshaking here.

Listing 15: Getting the tracking rectangle tag

trackingRectTag
- (NSTrackingRectTag)trackingRectTag
{
   return _trackingRectTag;
}
Listing 16: Setting the tracking rectangle tag
setTrackingRectTag
- (void)setTrackingRectTag:(NSTrackingRectTag)rectTag
{
   _trackingRectTag = rectTag;
}

The second way of adjusting our cursor is to define a cursor rectangle for the movie view. A cursor rectangle is a special kind of tracking rectangle. What's special about it is that NSView knows that the rectangle is likely to change if the associated view is resized or moved. Accordingly, NSView calls the view's resetCursorRects method whenever that happens, to give the view a chance to resize or move the cursor rectangle. Listing 17 shows how we might define the resetCursorRects method.

Listing 17: Resetting the cursor rectangle

resetCursorRects
- (void)resetCursorRects
{
   NSCursor      *newCursor;
   [super resetCursorRects];
   newCursor = [NSCursor arrowCursor];
   [self addCursorRect:[self bounds] cursor:newCursor];
   [newCursor setOnMouseExited:YES];
}

Here we create a new arrow cursor and call addCursorRect: to attach it to a new cursor rectangle that is as large as the target view (that is, the movie view). Then we call setOnMouseExited: to configure the cursor to set itself as the current cursor when the mouse moves outside of the cursor rectangle.

As you can see, there is much less code required when using cursor rectangles than when using tracking rectangles. We don't need to keep track of the tracking rectangle tag, and we don't need any accessor functions to pass that tag to the movie controller action filter procedure. The only downside to using cursor rectangles is that we must subclass NSMovieView (so that we have a class to define the resetCursorRects method). As we saw above, we did not need to subclass NSMovieView when using tracking rectangles.

Conclusion

In this article, we revisited the basic Cocoa movie playing and editing application that we developed in the previous article, with an eye to tying up a few loose ends. We've now got all the items in the File and Edit menus working as expected, and we've cleaned up a few cosmetic glitches in our original version of MooVeez. With these improvements, MooVeez now matches quite closely the capabilities of QTShell, our benchmark Carbon movie playback and editing application. Moreover, the Cocoa framework upon which MooVeez is built provides a number of additional features not found in QTShell, including the "Open Recent" menu item in the File menu and the Window menu for managing all open document windows.

Credits

Listing 6 is based on some code by Vince DeMarco.


Tim Monroe in 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
$501.11
Apple Inc.
+2.43
MSFT
$34.64
Microsoft Corpora
+0.15
GOOG
$898.03
Google Inc.
+16.02

MacTech Search:
Community Search:

Software Updates via MacUpdate

CrossOver 12.5.1 - Run Windows apps on y...
CrossOver can get your Windows productivity applications and PC games up and running on your Mac quickly and easily. CrossOver runs the Windows software that you need on Mac at home, in the office,... Read more
Paperless 2.3.1 - Digital documents mana...
Paperless is a digital documents manager. Remember when everyone talked about how we would soon be a paperless society? Now it seems like we use paper more than ever. Let's face it - we need and we... Read more
Apple HP Printer Drivers 2.16.1 - For OS...
Apple HP Printer Drivers includes the latest HP printing and scanning software for Mac OS X 10.6, 10.7 and 10.8. For information about supported printer models, see this page.Version 2.16.1: This... Read more
Yep 3.5.1 - Organize and manage all your...
Yep is a document organization and management tool. Like iTunes for music or iPhoto for photos, Yep lets you search and view your documents in a comfortable interface, while offering the ability to... Read more
Apple Canon Laser Printer Drivers 2.11 -...
Apple Canon Laser Printer Drivers is the latest Canon Laser printing and scanning software for Mac OS X 10.6, 10.7 and 10.8. For information about supported printer models, see this page.Version 2.11... Read more
Apple Java for Mac OS X 10.6 Update 17 -...
Apple Java for Mac OS X 10.6 delivers improved security, reliability, and compatibility by updating Java SE 6.Version Update 17: Java for Mac OS X 10.6 Update 17 delivers improved security,... Read more
Arq 3.3 - Online backup (requires Amazon...
Arq is online backup for the Mac using Amazon S3 and Amazon Glacier. It backs-up and faithfully restores all the special metadata of Mac files that other products don't, including resource forks,... Read more
Apple Java 2013-005 - For OS X 10.7 and...
Apple Java for OS X 2013-005 delivers improved security, reliability, and compatibility by updating Java SE 6 to 1.6.0_65. On systems that have not already installed Java for OS X 2012-006, this... Read more
DEVONthink Pro 2.7 - Knowledge base, inf...
Save 10% with our exclusive coupon code: MACUPDATE10 DEVONthink Pro is your essential assistant for today's world, where almost everything is digital. From shopping receipts to important research... Read more
VirtualBox 4.3.0 - x86 virtualization so...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more

Briquid Gets Updated with New Undo Butto...
Briquid Gets Updated with New Undo Button, Achievements, and Leaderboards, on Sale for $0.99 Posted by Andrew Stevens on October 16th, 2013 [ | Read more »
Halloween – iLovecraft Brings Frightenin...
Halloween – iLovecraft Brings Frightening Stories From Author H.P. | Read more »
The Blockheads Creator David Frampton Gi...
The Blockheads Creator David Frampton Gives a Postmortem on the Creation Process of the Game Posted by Andrew Stevens on October 16th, 2013 [ permalink ] Hey, a | Read more »
Sorcery! Enhances the Gameplay in Latest...
Sorcery! | Read more »
It Came From Australia: Tiny Death Star
NimbleBit and Disney have teamed up to make Star Wars: Tiny Death Star, a Star Wars take on Tiny Tower. Right now, the game is in testing in Australia (you will never find a more wretched hive of scum and villainy) but we were able to sneak past... | Read more »
FIST OF AWESOME Review
FIST OF AWESOME Review By Rob Rich on October 16th, 2013 Our Rating: :: TALK TO THE FISTUniversal App - Designed for iPhone and iPad A totalitarian society of bears is only the tip of the iceberg in this throwback brawler.   | Read more »
PROVERBidioms Paints English Sayings in...
PROVERBidioms Paints English Sayings in a Picture for Users to Find Posted by Andrew Stevens on October 16th, 2013 [ permalink ] | Read more »
OmniFocus 2 for iPhone Review
OmniFocus 2 for iPhone Review By Carter Dotson on October 16th, 2013 Our Rating: :: OMNIPOTENTiPhone App - Designed for the iPhone, compatible with the iPad OmniFocus 2 for iPhone is a task management app for people who absolutely... | Read more »
Ingress – Google’s Augmented-Reality Gam...
Ingress – Google’s Augmented-Reality Game to Make its Way to iOS Next Year Posted by Andrew Stevens on October 16th, 2013 [ permalink ] | Read more »
CSR Classics is Full of Ridiculously Pre...
CSR Classics is Full of Ridiculously Pretty Classic Automobiles Posted by Rob Rich on October 16th, 2013 [ permalink ] | Read more »

Price Scanner via MacPrices.net

Apple Store Canada offers refurbished 11-inch...
 The Apple Store Canada has Apple Certified Refurbished 2013 11″ MacBook Airs available starting at CDN$ 849. Save up to $180 off the cost of new models. An Apple one-year warranty is included with... Read more
Updated MacBook Price Trackers
We’ve updated our MacBook Price Trackers with the latest information on prices, bundles, and availability on MacBook Airs, MacBook Pros, and the MacBook Pros with Retina Displays from Apple’s... Read more
13-inch Retina MacBook Pros on sale for up to...
B&H Photo has the 13″ 2.5GHz Retina MacBook Pro on sale for $1399 including free shipping. Their price is $100 off MSRP. They have the 13″ 2.6GHz Retina MacBook Pro on sale for $1580 which is $... Read more
AppleCare Protection Plans on sale for up to...
B&H Photo has 3-Year AppleCare Warranties on sale for up to $105 off MSRP including free shipping plus NY sales tax only: - Mac Laptops 15″ and Above: $244 $105 off MSRP - Mac Laptops 13″ and... Read more
Apple’s 64-bit A7 Processor: One Step Closer...
PC Pro’s Darien Graham-Smith reported that Canonical founder and Ubuntu Linux creator Mark Shuttleworth believes Apple intends to follow Ubuntu’s lead and merge its desktop and mobile operating... Read more
MacBook Pro First, Followed By iPad At The En...
French site Info MacG’s Florian Innocente says he has received availability dates and order of arrival for the next MacBook Pro and the iPad from the same contact who had warned hom of the arrival of... Read more
Chart: iPad Value Decline From NextWorth
With every announcement of a new Apple device, serial upgraders begin selling off their previous models – driving down the resale value. So, with the Oct. 22 Apple announcement date approaching,... Read more
SOASTA Survey: What App Do You Check First in...
SOASTA Inc., the leader in cloud and mobile testing announced the results of its recent survey showing which mobile apps are popular with smartphone owners in major American markets. SOASTA’s survey... Read more
Apple, Samsung Reportedly Both Developing 12-...
Digitimes’ Aaron Lee and Joseph Tsai report that Apple and Samsung Electronics are said to both be planning to release 12-inch tablets, and that Apple is currently cooperating with Quanta Computer on... Read more
Apple’s 2011 MacBook Pro Lineup Suffering Fro...
Appleinsider’s Shane Cole says that owners of early-2011 15-inch and 17-inch MacBook Pros are reporting issues with those models’ discrete AMD graphics processors, which in some cases results in the... Read more

Jobs Board

*Apple* Retail - Manager - Apple (United Sta...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, youre a master of them all. In the stores fast-paced, dynamic Read more
*Apple* Support / *Apple* Technician / Mac...
Apple Support / Apple Technician / Mac Support / Mac Set up / Mac TechnicianMac Set up and Apple Support technicianThe person we are looking for will have worked Read more
Senior Mac / *Apple* Systems Engineer - 318...
318 Inc, a top provider of Apple solutions is seeking a new Senior Apple Systems Engineer to be based out of our Santa Monica, California location. We are a Read more
*Apple* Retail - Manager - Apple Inc. (Unite...
Job Summary Keeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you’re a master of them all. In the store’s fast-paced, Read more
*Apple* Solutions Consultant - Apple (United...
**Job Summary** Apple Solutions Consultant (ASC) - Retail Representatives Apple Solutions Consultants are trained by Apple on selling Apple -branded products Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.