TweetFollow Us on Twitter

Aug 01 QT Toolkit

Volume Number: 17 (2001)
Issue Number: 08
Column Tag: QuickTime Toolkit

The Skin Game

by Tim Monroe

Working with QuickTime Skins

Introduction

QuickTime 5 introduced support for displaying movies inside of arbitrarily shaped windows. These windows are called skinned movie windows, and the custom shape of one of those windows is called its skin. Up to now, our sample applications have always displayed QuickTime movies inside a standard document window, which occupies a rectangular area on the screen. Even QuickTime Player, which uses a snazzy brushed-metal window frame with rounded edges, always shows a movie inside a rectangular pane inside the frame. Skins give us a way to break out of this rectangular mold. For instance, Figure 1 shows a QuickTime movie with a skin that's shaped like the QuickTime logo.


Figure 1: A QuickTime movie with a skin

This movie contains two video tracks, one for the grainy, grayscale video showing in the center of the logo, and one for the logo image itself. (The second video track contains a single sample that extends for the entire duration of the first track.) The user can start and stop the movie by pressing the spacebar or by clicking in the visible portion of the grayscale video. And the user can move the window around on the screen by clicking anywhere on the blue logo and dragging.

Figure 2 shows another possibility. Here is our penguin sprite movie once again, but this time as a skinned movie. It's still got a tween track that changes the sprite's graphics mode from total transparency to total opacity. But now I've set the looping mode to palindrome looping so that the penguin fades in and out as the movie plays.


Figure 2: Another QuickTime movie with a skin

Figure 3 shows yet another skinned movie window. Most of what you see here, including all the buttons and draggable handles, is provided by a Flash track. The grayscale image is once again a frame of a video track, which we can start, stop, pause, and play in slow motion using the tools palette on the right side of the movie window.


Figure 3: Yet another QuickTime movie with a skin

In this article, we're going to learn how to create skinned movies. More importantly, we're going to learn how to open a skinned movie file and display the movie to the user in a window of the appropriate shape. That is to say, we're going to learn how to make our applications skin savvy. Currently there are very few applications that can open skinned movies. The only widely-available application that can do this, to my knowledge, is QuickTime Player.

Our sample application in this article, which can both create skinned movies and play them back, is called QTSkins. The Test menu of QTSkins is shown in Figure 4; as you can see, it has only one menu item, which allows us to add a skin track to a movie.


Figure 4: The Test menu of QTSkins

Skins

Perhaps the best way to think of QuickTime skins is like this: a skinned movie is just a QuickTime movie with a custom window shape. A skin provides a way of selecting some portion of an existing movie and having that portion be all that's displayed to the user when the movie is opened. Skinned movies don't have title bars or window frames, and they don't display a controller bar. As a result, if we want the user to be able to interact with the movie, we'll need to supply our own controls. We can use wired sprite tracks or Flash tracks for this, or perhaps even wired text tracks (which we encountered in the previous article).

The data that defines the custom window shape is contained in the skinned movie file itself. This fact has some very important consequences. For one thing, it means that we can select on a per-movie basis whether a movie is displayed in a normal document window or in a custom-shaped skinned window. We're not modifying the general appearance of the playback application (which is perhaps the typical use of the term ‘skins'). Rather, we're modifying the specific appearance of what's being played back. In a nutshell, we're changing the movie, not the movie player. Previously, the movie data represented some content that plays back inside a document window or pane, usually under the supervision of a movie controller and controller bar. Now the movie data can represent the content and the window and the controller. For the first time, really, the movie author has complete control over the user's playback experience.

So what kind of data do we use to construct a skinned movie? The first thing we need is some way of specifying which portion of the movie rectangle we want to appear as the content region of the skinned movie window. The content region of a window is the portion of the window in which an application displays the contents of a document; in our case, it's where the movie data and any movie controls are displayed. We specify the skinned movie's content region by providing a 1-bit (that is, black and white) mask that's the same size as the movie rectangle. If a pixel in the mask is black, then the corresponding pixel in the movie rectangle is displayed; otherwise, the corresponding pixel is not displayed. Let's call this mask the content region mask. Figure 5 shows the content region mask for the skinned movie shown in Figure 1.


Figure 5: A content region mask

We also need some way to move the skinned movie window around on the screen. Typically, of course, we move a window by grabbing its title bar or window frame and then dragging. Because skinned movie windows don't have title bars or frames, however, we need to explicitly indicate the portion of the skinned movie window that the user can grab and drag. We do this by specifying a second mask, the drag region mask. (This is also a 1-bit mask.) A user can click anywhere in this region and drag the window around. Figure 6 shows the drag region mask for the skinned movie shown in Figure 1.


Figure 6: A drag region mask

You'll notice that the drag region mask is entirely contained within the content region mask, so that the user can grab only in some visible portion of the movie window. In addition, the drag region mask should exclude any areas of the movie rectangle that you want to be interactive. It won't do any good, for instance, to have a skinned movie's drag region overlap any wired sprites, since a click in that area will be interpreted as the beginning of a drag operation.

So we need three ingredients to create a skinned movie. We need the movie data itself. We need a content region mask, to indicate the portion of the movie rectangle that is displayed to the user. And we need a drag region mask to indicate the portion of the movie rectangle that can be grabbed.

Creating Skinned Movies

The typical way to create a skinned movie is to add a skin track to an existing movie. The skin track contains data that specifies the content region and the drag region of the movie window. In this section, we'll investigate two different ways to add a skin track to a movie. First, though, we'll take a brief moment to learn about media characteristics. This will help us see that skin data can in fact be contained in other kinds of tracks as well.

Searching Media Characteristics

Let's begin by considering a utility function we'll call several times in our application, QTSkin_IsSkinnedMovie (defined in Listing 1). This function returns a Boolean value that indicates whether the specified movie contains skin data.

Listing 1: Determining whether a movie is a skinned movie

Boolean QTSkin_IsSkinnedMovie (Movie theMovie) 
{
   return(GetMovieIndTrackType(theMovie, 1, 
               FOUR_CHAR_CODE(‘skin'), movieTrackCharacteristic) 
               != NULL);
}

We've worked with GetMovieIndTrackType a handful of times previously, but only using the movieTrackMediaType flag as the last parameter, to search for a track of a given index that has a specific type. Here, you'll notice, we use the movieTrackCharacteristic flag instead, which tells GetMovieIndTrackType to search for a track of a given index that has a specific media characteristic. A media characteristic is a feature that can be shared by two or more track types, such as the ability to draw data. Originally, in QuickTime version 2.0, there were two supported media characteristics, indicating whether the track has video or audio data in it:

enum {
   VisualMediaCharacteristic            = FOUR_CHAR_CODE(‘eyes'),
   AudioMediaCharacteristic             = FOUR_CHAR_CODE(‘ears')
};

Any track that displays visible data to the user has the VisualMediaCharacteristic media characteristic; some examples are video tracks, sprite tracks, text tracks, MPEG tracks, and timecode tracks. Similarly, any track that plays audible data to the user has the AudioMediaCharacteristic media characteristic; some examples are sound tracks and music tracks. QuickTime has subsequently added a few other searchable media characteristics, including kCharacteristicProvidesActions for tracks that contain wired actions.

In Listing 1, we're looking to see whether any track in the movie contains skin data. (There is as yet no publicly-defined constant for the skin media characteristic, so we've hard-coded the value FOUR_CHAR_CODE(‘skin').) Skin tracks certainly contain skin data, so they have this characteristic. But other kinds of tracks may very well contain skin data, and so they too would have this characteristic. (If we are interested in knowing whether a specific track has a given characteristic, we can call the MediaHasCharacteristic function.) By searching for the skin media characteristic instead of the skin media type, we allow our applications to work with any movie tracks that contain skin data. Right now, to be sure, there are no track types with that characteristic aside from skin tracks; but we are equipped to deal with them when they come along.

Using the QuickTime XML Importer

By far the easiest way to create a movie with a skin track is to use a QuickTime XML importer, introduced in QuickTime 5. XML (for Extensible Markup Language) is a textual description of a document that contains structured information. It's similar in flavor to HTML, but differs significantly in that XML does not have a predefined set of markup tags. Rather, XML is more of a metalanguage for describing structured information. A QuickTime XML importer is a movie importer that knows how to parse certain kinds of XML files. QuickTime provides an importer that knows how to parse XML files that contain tags describing a skinned movie. Listing 2 shows the file used to construct the skinned movie shown in Figure 1. As you can see, this XML file specifies three other files, which contain the original movie data, a mask for the content region of the window, and a mask for the drag region of the window.

Listing 2: An XML file that specifies a skinned movie

<?xml version=”1.0”?>
<?quicktime type=”application/x-qtskin”?>
<skin>
   <movie src=”QTLogo.mov”/>
   <contentregion src=”contentmask.pct”/>
   <dragregion src=”dragmask.pct”/>
</skin>

If we open this file using QuickTime Player or any other skin-savvy application, we'll see the skinned movie shown in Figure 1. The application probably calls NewMovieFromFile or NewMovieFromDataRef to open the XML file. QuickTime will see that the file doesn't contain a movie atom and then go looking for a suitable movie importer. (See "In and Out" in MacTech, May 2000, for a more in-depth discussion of how this works.) In the present case, QuickTime will invoke the XML importer to import the movie data and return a movie to the calling application. Note that some importers, including the QuickTime XML importer, seem to ignore the newMovieActive flag passed to NewMovieFromFile. So we'll add the following line of code to the QTFrame_OpenMovieInWindow function, after we call NewMovieFromFile:

SetMovieActive(myMovie, true);

We can create a self-contained skinned movie file by calling FlattenMovieData on the open skinned movie. Our sample applications make this call when the user selects the "Save As..." menu item. The self-contained movie file is easier to move around and to transport from machine to machine. It's also preferable for web-based movie delivery.

Creating Skin Tracks Programmatically

Using the XML importer is fine and dandy, but we'd also like to be able to create skinned movies directly, using the QuickTime APIs. Once again, we'll do this by adding a skin track to an existing movie. We've created many kinds of tracks in QuickTime movies, so we've got the drill down. You'll recall that it goes basically like this:

  • Create a new track and media (NewMovieTrack and NewTrackMedia).
  • Create a new sample description (NewHandle).
  • Start a media-editing session (BeginMediaEdits).
  • Add media data to the new media (AddMediaSample).
  • End the media-editing session (EndMediaEdits).
  • Insert the new media data into the track (InsertMediaIntoTrack).

It turns out, however, that we need to use a slightly different method for constructing a skin track. When we build (for instance) a video track or a sprite track, we need to know the exact structure of the media sample data, and we need to fill out a sample description that describes that data (its size, its compression type, and so forth). Moreover, when we call AddMediaSample, we need to specify the duration of the media sample. But with skin media data, the notion of duration doesn't really apply. After all, we're just specifying a couple a masks for a window shape, not any time-based data.

To simplify our handling of media data that isn't time based, QuickTime 5 introduced public media information, which can be any data associated with a media that does not need to be pegged to a specific time in a track. Currently, to my knowledge, only the skin media handler supports public media information, to maintain the content and drag region masks.

QuickTime 5 includes two new functions for working with public media information, MediaSetPublicInfo and MediaGetPublicInfo. MediaSetPublicInfo is declared essentially like this:

ComponentResult MediaSetPublicInfo(MediaHandler mh,
         OSType infoSelector, void *infoDataPtr, Size dataSize);

The mh parameter specifies the media handler we're giving the information to; in the present case, 
it's the skin media handler. The infoSelector parameter specifies the kind of public information 
we're setting. The skin media handler currently understands two selectors, ‘skcr' 
(for the content region mask) and ‘skdr' (for the drag region mask). The parameters infoDataPtr 
and dataSize specify the memory location and size of the public media information data. With the skin 
media handler, however, dataSize should be 0 and infoDataPtr should be a picture handle (of type 
PicHandle). For instance, here's how we'll set the content region mask:

myErr = MediaSetPublicInfo(myHandler, FOUR_CHAR_CODE(‘skcr'), 
                        (void *)myContentPic, 0);

Our work really boils down to this: have the user select two pictures, one for the content region mask and another for the drag region mask; then create a new track and media (of type ‘skin'), call MediaSetPublicInfo for each of the pictures selected by the user, and finish up by calling InsertMediaIntoTrack. Once we've got the two picture handles, the 6-step sequence listed above reduces to this:

  • Create a new track and media (NewMovieTrack and NewTrackMedia).
  • Add media data to the new media (MediaSetPublicInfo).
  • Insert the new media data into the track (InsertMediaIntoTrack).

Let's consider, then, how to get the two picture handles. Ideally, we'd like to allow the user to work with any kind of image file that QuickTime can open (just like the XML importer does). MediaSetPublicInfo expects the data we pass it to be a PicHandle, so we need some way to convert the image data in a file selected by the user into a PicHandle. Happily, there is a graphics importer function, GraphicsImportGetAsPicture, that does precisely this. Listing 3 defines the QTSkin_GetPicHandleFromFile function, which we use (in Listing 4) to prompt the user for the two images we need. (For more information about graphics importers, see "Quick on the Draw" in MacTech, April 2000.)

Listing 3: Getting a picture handle from an image file

PicHandle QTSkin_GetPicHandleFromFile (void)
{
   OSType                            myTypeList = 
                                                  kQTFileTypeQuickTimeImage;
   short                             myNumTypes = 1;
   FSSpec                            myPictSpec;
   QTFrameFileFilterUPP             myFilterUPP = NULL;
   GraphicsImportComponent           myImporter = NULL;
   PicHandle                          myPicture = NULL;
   OSErr                                  myErr = noErr;

#if TARGET_OS_MAC
   myNumTypes = 0;
#endif

   // have the user select an image file
   myFilterUPP = QTFrame_GetFileFilterUPP(
                  (ProcPtr)QTSkin_FileFilterFunction);

   myErr = QTFrame_GetOneFileWithPreview(myNumTypes, 
                  (QTFrameTypeListPtr)&myTypeList, &myPictSpec, 
                  myFilterUPP);
   if (myErr != noErr)
      goto bail;

   // get a graphics importer for the image file
   myErr = GetGraphicsImporterForFile(&myPictSpec, 
                           &myImporter);
   if (myErr != noErr)
      goto bail;

   // convert the image into a PicHandle
   myErr = GraphicsImportGetAsPicture(myImporter, &myPicture);
   
bail:
   if (myFilterUPP != NULL)
      DisposeNavObjectFilterUPP(myFilterUPP);

   if (myImporter != NULL)
      CloseComponent(myImporter);

   return(myPicture);
}

We are finally ready to put this all together. When the user selects the "Add Skin Track..." menu item, we execute the QTSkin_AddSkinTrack function defined in Listing 4.

Listing 4: Adding a skin track to a movie

OSErr QTSkin_AddSkinTrack (Movie theMovie)
{
   Track               myTrack = NULL;         // the movie track
   Media               myMedia = NULL;         // the movie track's media
   Rect                myRect;
   MediaHandler        myHandler = NULL;
   PicHandle           myContentPic = NULL;
   PicHandle           myDragPic = NULL;
   OSErr               myErr = paramErr;

   if (theMovie == NULL)
      goto bail;

   // elicit the two pictures we need from the user
   myContentPic = QTSkin_GetPicHandleFromFile();
   if (myContentPic == NULL)
      goto bail;

   myDragPic = QTSkin_GetPicHandleFromFile();
   if (myDragPic == NULL)
      goto bail;

   // get the movie's dimensions
   GetMovieBox(theMovie, &myRect);
   MacOffsetRect(&myRect, -myRect.left, -myRect.top);

   // create the skin track and media
   myTrack = NewMovieTrack(theMovie, 
                        FixRatio(myRect.right, 1), 
                        FixRatio(myRect.bottom, 1), kNoVolume);
   if (myTrack == NULL)
      goto bail;

   myMedia = NewTrackMedia(myTrack, FOUR_CHAR_CODE(‘skin'), 
                        GetMovieTimeScale(theMovie), NULL, 0);
   if (myMedia == NULL)
      goto bail;

   myHandler = GetMediaHandler(myMedia);
   if (myHandler == NULL)
      goto bail;

   // add the skin content picture as public media information
   myErr = MediaSetPublicInfo(myHandler, 
                        FOUR_CHAR_CODE(‘skcr'), 
                        (void *)myContentPic, 0);
   if (myErr != noErr)
      goto bail;

   // add the skin drag picture as public media information
   myErr = MediaSetPublicInfo(myHandler, 
                        FOUR_CHAR_CODE(‘skdr'), 
                        (void *)myDragPic, 0);
   if (myErr != noErr)
      goto bail;

   // add the media to the track
   myErr = InsertMediaIntoTrack(myTrack, 0, 0, 
                        GetMediaDuration(myMedia), fixed1);

bail:
   if (myContentPic != NULL)
      KillPicture(myContentPic);

   if (myDragPic != NULL)
      KillPicture(myDragPic);

   return(myErr);
}

As you can see, using MediaSetPublicInfo greatly simplifies creating a skin track. We don't have to create a sample description, and we don't need to call BeginMediaEdits or AddMediaSample or EndMediaEdits. The skin media handler takes care of all the details of storing the content and drag region masks in the skin media.

Skinned Movie Playback

So now we know how to build a skinned movie, using either the QuickTime XML importer or our own application code. As mentioned earlier, we also want our application to be able to open and play back skinned movies. This turns out to be significantly more complicated, however, since we need to be able to assign a custom window shape to a movie window and window shapes are handled by the application, not by QuickTime. So, we're going to have to get acquainted with some of the low-level window-handling capabilities of our host operating systems if we want to be able to open and manipulate skinned movies.

On Macintosh operating systems, we assign a custom shape to a movie window by writing a custom window definition procedure. Under Carbon, the code for a custom window definition procedure is contained in the application itself, not in a code resource of type ‘WDEF' (as in the pre-Carbon Mac world). Once we've defined our custom procedure, we can call the CreateCustomWindow function to create a skinned movie window. Whenever the Window Manager needs to draw our custom window or handle clicks on it, it calls our custom window definition procedure.

On Windows operating systems, it's even easier to assign a custom shape to a window: we can call the SetWindowRgn function when opening the movie window to assign an arbitrary region as the window shape. We'll also add a little code to our basic movie window procedure QTFrame_MovieWndProc to handle skinned window dragging.

Before we can do any of this, however, we need to get ahold of the skin data that determines the window's appearance and drag behavior. That is, we need to read the content and drag region masks out of the skinned movie file. As you might guess, we'll use GetMediaPublicInfo to get the picture data stored in the skin track. Then we'll need to convert that picture into a region, which we'll pass to the operating system window handlers.

Setting Up the Application Data

For each skinned movie file we open, we need to maintain some application-specific data. Basically, we need a place to keep track of the various regions describing the geometry of the skinned movie window. In the file ComApplication.h, we'll declare the ApplicationDataRecord data structure like this:

typedef struct ApplicationDataRecord {
   RgnHandle      fContentRegion;         // content region of window
   RgnHandle      fDragRegion;            // drag region of window
   RgnHandle      fStructRegion;          // structure region of window
#if TARGET_OS_WIN32
   HRGN               fWinHRGN;           // window region
#endif
} ApplicationDataRecord, *ApplicationDataPtr, 
                                       **ApplicationDataHdl;

The fContentRegion and fDragRegion fields hold handles to the content region and the drag region of the window, which we retrieve from the skin track as we're opening a skinned movie. The fStructRegion field holds the structure region of the skinned window. A window's structure region is the entire screen area occupied by the window, including the window's content region and its window frame. For skinned movies, the structure region is usually identical to its content region. The fWinHRGN field holds the window content region as an object of type HRGN. This is the object we'll to pass to SetWindowRgn when we set the window's shape on Windows.

Recall that the data stored in a skin track is of type PicHandle. We can retrieve that data by calling GetMediaPublicInfo, passing it a selector for the type of information we want. For instance, to retrieve the content region mask from a skin track, we can execute this code:

myPicture = (PicHandle)NewHandle(0);
if (myPicture == NULL)
   goto bail;

myErr = MediaGetPublicInfo(myHandler, FOUR_CHAR_CODE(‘skcr'), 
                  myPicture, NULL);

If GetMediaPublicInfo completes successfully, myPicture will contain a handle to the picture data. We 
then need to convert this picture data into a region, since that's the kind of data we'll need to have
available in our custom window definition procedure. We make this conversion by calling the application 
function QTSkin_ConvertPictureToRegion:

myErr = QTSkin_ConvertPictureToRegion(myPicture, 
                  &(**myAppData).fContentRegion);

QTSkin_ConvertPictureToRegion creates a region that contains every non-white pixel in the specified picture. The key step is using QuickDraw's BitmapToRegion function to convert a bitmap or a pixel map into a region. So we need to create a pixel map from our picture data. But this is very easy: we simply create a new offscreen graphics world and draw the picture data into it. We can then use the GetGWorldPixMap function to get the pixel map associated with that graphics world. Listing 5 shows our definition of QTSkin_ConvertPictureToRegion.

Listing 5: Converting a picture into a region

OSErr QTSkin_ConvertPictureToRegion (PicHandle thePicture, 
                  RgnHandle *theRegionPtr)
{
   Rect                     myRect;
   GWorldPtr            myGWorld = NULL;
   PixMapHandle         myPixMap = NULL;
   CGrafPtr               mySavedPort = NULL;
   GDHandle               mySavedDevice = NULL;
   RgnHandle            myRegion = NULL;
   OSErr                  myErr = noErr;

   if ((thePicture == NULL) || (theRegionPtr == NULL))
      return(paramErr);

   // get the current graphics port and device
   GetGWorld(&mySavedPort, &mySavedDevice);
   
   // get the bounding box of the picture
   myRect = (**thePicture).picFrame;
   myRect.bottom = EndianS16_BtoN(myRect.bottom);
   myRect.right = EndianS16_BtoN(myRect.right);

   // create a new GWorld and draw the picture into it
   myErr = QTNewGWorld(&myGWorld, k1MonochromePixelFormat, 
                     &myRect, NULL, NULL, kICMTempThenAppMemory);
   if (myGWorld == NULL)
      goto bail;

   SetGWorld(myGWorld, NULL);

   myPixMap = GetGWorldPixMap(myGWorld);
   if (myPixMap == NULL)
      goto bail;

   LockPixels(myPixMap);
   HLock((Handle)myPixMap);

   EraseRect(&myRect);
   DrawPicture(thePicture, &myRect);

   // create a new region and convert the pixmap into a region
   myRegion = NewRgn();
   myErr = MemError();
   if (myErr != noErr)
      goto bail;

   myErr = BitMapToRegion(myRegion, (BitMap *)*myPixMap);

bail:
   if (myErr != noErr) {
      if (myRegion != NULL) {
         DisposeRgn(myRegion);
         myRegion = NULL;
      }
   }

   if (myGWorld != NULL)
      DisposeGWorld(myGWorld);

   // restore the original graphics port and device
   SetGWorld(mySavedPort, mySavedDevice);

   *theRegionPtr = myRegion;

   return(myErr);
}
For our Windows applications, we need to take one further step and convert the Macintosh region (of type RgnHandle) into a Windows region (of type HRGN). The QuickTime Media Layer provides a function that will do this for us:
(**myAppData).fWinHRGN = MacRegionToNativeRegion
                                          ((**myAppData).fContentRegion);

All of this start-up code will go into the function QTSkin_InitWindowData, which is called by QTApp_SetupWindowObject to perform any application-specific initialization of the movie window and its associated data. QTApp_SetupWindowObject contains this code to handle skinned movies:

if (QTSkin_IsSkinnedMovie(myMovie)) {
   // hide the controller bar
   MCSetVisible(myMC, false);

   // detach the controller
   MCSetControllerAttached(myMC, false);

   // initialize the window data for a skins movie
   (**theWindowObject).fAppData = 
                  (Handle)QTSkin_InitWindowData(theWindowObject);
}

When QTApp_SetupWindowObject is called, the skinned movie window has already been created but it has not yet been displayed to the user. On Windows, we used our standard function QTFrame_CreateMovieWindow to create the movie window. So at this point, on Windows, we can already call SetWindowRgn to set the shape of the skinned movie window:

if ((**myAppData).fWinHRGN != NULL) {
   RECT         myRect;
   int         myResult;

   GetRgnBox((**myAppData).fWinHRGN, &myRect);

   OffsetRgn((**myAppData).fWinHRGN, 
      -myRect.left + GetSystemMetrics(SM_CXFRAME), 
      -myRect.top + GetSystemMetrics(SM_CYCAPTION) + 
                           GetSystemMetrics(SM_CYFRAME));
   myResult = SetWindowRgn((**theWindowObject).fWindow, 
                  (**myAppData).fWinHRGN, true);
}

SetWindowRgn sets the visible region of a window; it expects the origin of the window region we pass it to be relative to the upper-left corner of the window, not relative to the client area of the window. So we need to offset the stored window region (**myAppData).fWinHRGN horizontally by the width of the window frame and vertically by the height of the window frame and the height of the title bar (or caption). Figure 7 shows the penguin window with these offsets.


Figure 7: The client region offsets

As far as Windows is concerned, the window frame and window controls still exist — they are just not visible on the screen. The window is a full-fledged MDI child window, just like any of our other (non-skinned) movie windows. The only difference is that the skinned movie window has a special visible region.

Listing 6 shows the full version of our skinned movie window initialization code.

Listing 6: Initializing the application data for a skinned movie

ApplicationDataHdl QTSkin_InitWindowData 
                                       (WindowObject theWindowObject)
{
   ApplicationDataHdl         myAppData = NULL;
   Track                        myTrack = NULL;
   MediaHandler               myHandler = NULL;
   PicHandle                  myPicture = NULL;
   MatrixRecord               myMatrix;
   OSErr                          myErr = noErr;

   // if we already have some window data, dump it
   myAppData = 
      (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject
               (theWindowObject);
   if (myAppData != NULL)
      QTSkin_DumpWindowData(theWindowObject);     // see Listing 14

   myAppData = 
      (ApplicationDataHdl)NewHandleClear
               (sizeof(ApplicationDataRecord));
   if (myAppData != NULL) {

      myTrack = GetMovieIndTrackType
         ((**theWindowObject).fMovie, 1, FOUR_CHAR_CODE(‘skin'), 
         movieTrackCharacteristic);
      if (myTrack != NULL) {
         myHandler = GetMediaHandler(GetTrackMedia(myTrack));
         if (myHandler != NULL) {

            // get the current movie matrix
            GetMovieMatrix((**theWindowObject).fMovie, 
                                                &myMatrix);

            myPicture = (PicHandle)NewHandle(0);
            if (myPicture == NULL)
               goto bail;

            // get the content region picture
            myErr = MediaGetPublicInfo(myHandler, 
                           FOUR_CHAR_CODE(‘skcr'), myPicture, NULL);
            if (myErr != noErr)
               goto bail;

            // convert it to a region
            myErr = QTSkin_ConvertPictureToRegion(myPicture, 
                           &(**myAppData).fContentRegion);
            if (myErr != noErr)
               goto bail;

            // scale that region so the window scales with the movie
            myErr = TransformRgn(&myMatrix, 
                           (**myAppData).fContentRegion);
            if (myErr != noErr)
               goto bail;

#if TARGET_OS_WIN32
            (**myAppData).fWinHRGN = MacRegionToNativeRegion(
                           (**myAppData).fContentRegion);
            if ((**myAppData).fWinHRGN != NULL) {
               RECT         myRect;
               int         myResult;

               GetRgnBox((**myAppData).fWinHRGN, &myRect);
               // the coordinates of a window region are relative to the upper-left corner 
               // of the window (not to the client area of the window)
               OffsetRgn((**myAppData).fWinHRGN, 
                  -myRect.left + GetSystemMetrics(SM_CXFRAME), 
                  -myRect.top + GetSystemMetrics(SM_CYCAPTION) + 
                  GetSystemMetrics(SM_CYFRAME));
               myResult = SetWindowRgn(
                  (**theWindowObject).fWindow, 
                  (**myAppData).fWinHRGN, true);
               if (myResult == 0) {
                  // SetWindowRgn failed
                  DeleteObject((**myAppData).fWinHRGN);
                  (**myAppData).fWinHRGN = NULL;
                  goto bail;
               }
            }
#endif

            // repeat with drag region picture
            myErr = MediaGetPublicInfo(myHandler, 
                           FOUR_CHAR_CODE(‘skdr'), myPicture, NULL);
            if (myErr != noErr)
               goto bail;

            // convert it to a region
            myErr = QTSkin_ConvertPictureToRegion(myPicture, 
                           &(**myAppData).fDragRegion);
            if (myErr != noErr)
               goto bail;

            // scale that region so the window scales with the movie
            myErr = TransformRgn(&myMatrix, 
                           (**myAppData).fDragRegion);
            if (myErr != noErr)
               goto bail;

            // copy the content region into the structure region
            (**myAppData).fStructRegion = NewRgn();
            MacCopyRgn((**myAppData).fContentRegion, 
                           (**myAppData).fStructRegion);
         }
      }
   }

bail:
   if (myPicture != NULL)
      DisposeHandle((Handle)myPicture);

   return(myAppData);
}

Specifying a Custom Window Shape

As we've seen, it's child's play on Windows operating systems to specify a custom window shape: just pass the shape (as an HRGN) to SetWindowRgn. On the Mac, it's quite a bit more complicated. We need to write a custom window definition procedure and attach it to any skinned movies that the user opens. In our framework function QTFrame_OpenMovieInWindow, we'll add a few Mac-specific lines before the existing call to QTFrame_CreateMovieWindow:

#if TARGET_OS_MAC
   // create a new window to display the movie in
   if (QTSkin_IsSkinnedMovie(myMovie))
      myWindow = QTSkin_CreateSkinsWindow();
   else
#endif
      myWindow = QTFrame_CreateMovieWindow();

On Macintosh computers, QTFrame_CreateMovieWindow calls the Window Manager function NewCWindow to create a standard document window. For skinned windows, we need to call CreateCustomWindow, as shown in Listing 7.

Listing 7: Opening a window with a custom shape

WindowReference QTSkin_CreateSkinsWindow (void)
{
   WindowPtr                  myWindow = NULL;
   WindowReference         myWindowRef = NULL;
   Rect                         myRect = {10, 60, 200, 200};

   // call CreateCustomWindow to create a window using our custom window defproc
   CreateCustomWindow(&gDefSpec, kDocumentWindowClass, 
                        kWindowNoAttributes, &myRect, &myWindow);
   if (myWindow != NULL) {
      // get the "window reference" for this window
      myWindowRef = 
         QTFrame_GetWindowReferenceFromWindow(myWindow);

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

   return(myWindowRef);
}

This call to CreateCustomWindow asks for a document window with no special attributes. (The rectangle parameter is arbitrary, since we'll change the window size later.) The window definition procedure to be used to handle the custom window is specified by the &gDefSpec parameter, which is a pointer to a window definition specification, declared like this:

struct WindowDefSpec {
   WindowDefType               defType;
   union {
      WindowDefUPP             defProc;
      Void                     *classRef;
      Short                    procID;
   } u;
};

The defType field specifies which member of the union u we want to use. In the present case, we want to use the defProc member, so we set defType to kWindowDefProcPtr. And we'll set the defProc member to a universal procedure pointer to our custom window definition procedure. We initialize the gDefSpec global variable in the application start-up code for QTSkins, by calling the QTSkin_Init function defined in Listing 8.

Listing 8: Setting up a window definition specification

void QTSkin_Init (void)
{
   // set up the window definition specification structure
   gDefSpec.defType = kWindowDefProcPtr;
   gDefSpec.u.defProc = NewWindowDefUPP(QTSkin_SkinWindowDef);
}

Writing a Custom Window Definition Procedure

On Macintosh operating systems, the appearance and behavior of our skinned movie windows are determined by QTSkin_SkinWindowDef, our custom window definition procedure. QTSkin_SkinWindowDef is declared like this:

static PASCAL_RTN long QTSkin_SkinWindowDef 
                     (short theVarCode, WindowRef theWindow, 
                        short theMessage, long theParam);

Here, theMessage is a window definition message that indicates which task the window definition procedure is to perform. These are the common window definition messages:

enum {
   kWindowMsgDraw                        = 0,
   kWindowMsgHitTest                     = 1,
   kWindowMsgCalculateShape              = 2,
   kWindowMsgInitialize                  = 3,
   kWindowMsgCleanUp                     = 4,
   kWindowMsgDrawGrowOutline             = 5,
   kWindowMsgDrawGrowBox                 = 6,
   kWindowMsgGetFeatures                 = 7,
   kWindowMsgGetRegion                   = 8,
   kWindowMsgDragHilite                  = 9,
   kWindowMsgModified                    = 10,
   kWindowMsgDrawInCurrentPort           = 11,
   kWindowMsgSetupProxyDragImage         = 12,
   kWindowMsgStateChanged                = 13,
   kWindowMsgMeasureTitle                = 14,
   kWindowMsgGetGrowImageRegion          = 19
};

We can ignore most of these messages in our procedure. For instance, our skinned movie windows don't have grow boxes, so we can ignore the kWindowMsgDrawGrowOutline and kWindowMsgDrawGrowBox messages. In fact, we'll need to handle only three of these messages: kWindowMsgHitTest, kWindowMsgGetFeatures, and kWindowMsgGetRegion.

When we receive the kWindowMsgGetFeatures message, we need to return (through theParam) a value that indicates the capabilities of our custom window definition procedure. Really all our custom procedure can do is return information about various window regions. So we'll set the features information like this:

case kWindowMsgGetFeatures:
   if (theParam != 0L)
      *(OptionBits *)theParam = kWindowCanGetWindowRegion;
   return(1);

The meaning of the return value of our custom window definition procedure varies, depending on the message the procedure is handling. In this case, the documentation tells us to return 1.

When we receive the kWindowMsgHitTest message, we need to return one of these values, indicating which region of the movie (if any) was clicked in:

enum {
   wNoHit                                  = 0,
   wInContent                              = 1,
   wInDrag                                 = 2,
   wInGrow                                 = 3,
   wInGoAway                               = 4,
   wInZoomIn                               = 5,
   wInZoomOut                              = 6,
   wInCollapseBox                          = 9,
   wInProxyIcon                            = 10
};

With this message, theParam contains the coordinates of the mouse click, which we can extract like this:

myPoint.v = HiWord(theParam);
myPoint.h = LoWord(theParam);

This point is in global screen coordinates. Our regions, however, are stored with the upper-left corner 
set to (0,0). So we need to map myPoint into the window's local coordinate system, as follows:

GetPort(&myPort);
SetPortWindowPort(theWindow);

myLocal = myPoint;
GlobalToLocal(&myLocal);

MacSetPort(myPort);

The GlobalToLocal function maps the specified point into the coordinate system of the current graphics port, so we need to make sure that our custom window is the current graphics port (taking care to save and restore the previous current port).

Now that we've got a point local to the skinned movie window, we can use the PtInRgn function to do the required hit-testing:

if (PtInRgn(myLocal, (**myAppData).fDragRegion))
   return(wInDrag);

if (PtInRgn(myLocal, (**myAppData).fContentRegion))
   return(wInContent);

return(wNoHit);

We first look to see whether the specified point is in the drag region. If not, we look to see whether it's in the content region. If the point is in neither region, we indicate that no hit occurred.

When we receive the kWindowMsgGetRegion message, theParam is a pointer to a structure of type GetWindowRegionRec:

struct GetWindowRegionRec {
   RgnHandle                winRgn;
   WindowRegionCode         regionCode;
};

The regionCode field indicates which region we are supposed to return (through the winRgn field). Our skinned movie windows have only three interesting regions, the content region, the drag region, and the structure region (which is typically identical to the content region). So we'll respond to only three values for the regionCode field: kWindowContentRgn, kWindowDragRgn, and kWindowStructureRgn.

The region whose handle we return in the winRgn field is supposed to be specified in global screen coordinates. Our stored regions, however, are specified in coordinates local to the client region of the movie window. So we need to offset those regions before we return them from our window definition procedure. First, then, we need to figure out the global coordinates of the top-left corner of the window, like this:

GetPort(&myPort);   
SetPortWindowPort(theWindow);

GetPortBounds(GetWindowPort(theWindow), &myPortBounds);

myTopLeft.h = myPortBounds.left;
myTopLeft.v = myPortBounds.top;
LocalToGlobal(&myTopLeft);

MacSetPort(myPort);

Then we need to offset any of the regions we pass back. For instance, we'll pass back the window's drag region like this:

MacCopyRgn((**myAppData).fDragRegion, myRgnRec->winRgn);
MacOffsetRgn(myRgnRec->winRgn, myTopLeft.h, myTopLeft.v);

Listing 9 shows our complete window definition procedure for skinned movie windows.

Listing 9: Handling skinned movie window messages

static PASCAL_RTN long QTSkin_SkinWindowDef 
                     (short theVarCode, WindowRef theWindow, 
                        short theMessage, long theParam)
{
#pragma unused(theVarCode)

   switch (theMessage) {

      case kWindowMsgInitialize:
      case kWindowMsgCleanUp:
      case kWindowMsgDrawGrowOutline:
      case kWindowMsgDrawGrowBox:
      case kWindowMsgDraw:
         // nothing here
         break;

      case kWindowMsgHitTest: {
         ApplicationDataHdl      myAppData = NULL;
         Point                        myPoint;
         Point                        myLocal;
         GrafPtr                     myPort;

         myAppData = 
               (ApplicationDataHdl)QTFrame_GetAppDataFromWindow
               (QTFrame_GetWindowReferenceFromWindow(theWindow));
         if (myAppData == NULL)
            return(wNoHit);

         // on entry, theParam contains the mouse location in global screen coordinates
         myPoint.v = HiWord(theParam);
         myPoint.h = LoWord(theParam);

         // the content and drag regions are offset relative to the window origin
         GetPort(&myPort);
         SetPortWindowPort(theWindow);

         myLocal = myPoint;
         GlobalToLocal(&myLocal);

         MacSetPort(myPort);

         // look first to see if the mouse event is in the drag region;
         // it takes precedence over the content region
         if (PtInRgn(myLocal, (**myAppData).fDragRegion))
            return(wInDrag);

         if (PtInRgn(myLocal, (**myAppData).fContentRegion))
            return(wInContent);

         return(wNoHit);
      }

      case kWindowMsgGetFeatures:
         if (theParam != 0L)
            *(OptionBits *)theParam = kWindowCanGetWindowRegion;
         return(1);

      case kWindowMsgGetRegion: {
         GetWindowRegionRec      *myRgnRec = 
                                       (GetWindowRegionRec *)theParam;
         ApplicationDataHdl      myAppData = NULL; 
         GrafPtr                     myPort;
         Rect                           myPortBounds;
         Point                        myTopLeft;

         myAppData = 
               (ApplicationDataHdl)QTFrame_GetAppDataFromWindow
               (QTFrame_GetWindowReferenceFromWindow(theWindow));
         if (myAppData == NULL)
            break;

         // get the top-left corner of the window, in global coordinates
         GetPort(&myPort);   
         SetPortWindowPort(theWindow);

#if TARGET_API_MAC_CARBON
         GetPortBounds(GetWindowPort(theWindow), &myPortBounds);
#else
         myPortBounds = theWindow->portRect;
#endif
         myTopLeft.h = myPortBounds.left;
         myTopLeft.v = myPortBounds.top;
         LocalToGlobal(&myTopLeft);

         MacSetPort(myPort);

         switch (myRgnRec->regionCode) {
            case kWindowTitleBarRgn:
            case kWindowCloseBoxRgn:
               break;

            case kWindowDragRgn:
               MacCopyRgn((**myAppData).fDragRegion, 
                                                   myRgnRec->winRgn);
               MacOffsetRgn(myRgnRec->winRgn, myTopLeft.h, 
                                                   myTopLeft.v);
               break;

            case kWindowContentRgn:
               MacCopyRgn((**myAppData).fContentRegion, 
                                                   myRgnRec->winRgn);
               MacOffsetRgn(myRgnRec->winRgn, myTopLeft.h, 
                                                   myTopLeft.v);
               break;

            case kWindowStructureRgn:
               MacCopyRgn((**myAppData).fStructRegion, 
                                                   myRgnRec->winRgn);
               MacOffsetRgn(myRgnRec->winRgn, myTopLeft.h, 
                                                   myTopLeft.v);
               break;

            default:
               break;
         }

         return(noErr);
      }

      default:
         break;
   }

   return(0L);
}

Handling Dragging on Windows Computers

Earlier we saw how to assign a custom window shape to a movie on Windows operating systems, by calling SetWindowRgn. We still need to see how to handle window dragging on Windows. Let's begin by reviewing briefly how our window procedure for movie windows processes the messages it receives. Listing 10 shows a snippet from QTFrame_MovieWndProc. First of all, it fills out an MSG structure and translates the Windows message into a Macintosh event by calling WinEventToMacEvent. Then it passes the Mac event to the application function QTApp_HandleEvent. Then, if QTApp_HandleEvent did not handle the event, QTFrame_MovieWndProc passes the Mac event to MCIsPlayerEvent.

Listing 10: Sending Windows messages to the movie controller

MSG          myMsg = {0};
LONG         myPoints = GetMessagePos();

myMsg.hwnd = theWnd;
myMsg.message = theMessage;
myMsg.wParam = wParam;
myMsg.lParam = lParam;
myMsg.time = GetMessageTime();
myMsg.pt.x = LOWORD(myPoints);
myMsg.pt.y = HIWORD(myPoints);

// translate a Windows event to a Mac event
WinEventToMacEvent(&myMsg, &myMacEvent);

// let the application-specific code have a chance to intercept the event
myIsHandled = QTApp_HandleEvent(&myMacEvent);

// pass the Mac event to the movie controller
if (!myIsHandled)
   if (myMC != NULL)
      if (!IsIconic(theWnd))
         myIsHandled = MCIsPlayerEvent(myMC, 
                                             (EventRecord *)&myMacEvent);

With skinned windows, the drag regions and the content regions virtually always overlap, so we need to prevent the movie controller from getting any mouse clicks that are in the drag region (since it would likely interpret them as clicks in the content region). We can do this quite easily by having QTApp_HandleEvent look to see whether the event it's passed is a mouse click in the drag region and, if it is, return true. Listing 11 shows the QTSkins version of QTApp_HandleEvent. Note that this code is conditionalized for Windows applications only, since on Macintosh the window definition procedure is responsible for finding clicks in the drag region.

Listing 11: Looking for drag region clicks (Windows)

Boolean QTApp_HandleEvent (EventRecord *theEvent)
{
#if TARGET_OS_MAC
#pragma unused(theEvent)
#endif

   Boolean            myIsHandled = false;

#if TARGET_OS_WIN32
   ApplicationDataHdl      myAppData = (ApplicationDataHdl)
                              QTFrame_GetAppDataFromFrontWindow();
   Point                        myPoint;

   if (theEvent == NULL)
      goto bail;

   if (theEvent->what == mouseDown) {
      myPoint = theEvent->where;
      GlobalToLocal(&myPoint);

      if (myAppData != NULL)
         if (PtInRgn(myPoint, (**myAppData).fDragRegion))
            myIsHandled = true;
   }
#endif

bail:
   return(myIsHandled);
}

So far, then, we've managed to prevent the movie controller associated with a movie window from getting clicks in the window's drag region. Now we need to actually handle those clicks. On Windows, we can look for messages of type WM_LBUTTONDOWN and see if they are in the drag region. If they are, we want to trick the default window procedure into thinking that the clicks are on the title bar, so that the default window procedure will handle the dragging for us. We can do this by sending a message of type WM_NCLBUTTONDOWN to the default window procedure, like this:

SendMessage(theWnd, WM_NCLBUTTONDOWN, (WPARAM)HTCAPTION, 
                                          MAKELPARAM(5, 5));

The WM_NCLBUTTONDOWN message reports a button-down event in a non-client area of a window. The first parameter indicates which part of the window is directly under the cursor hot spot at the time of the click. In our case, we want to say that the click occurred in the title bar (indicated by the HTCAPTION constant). The second parameter indicates the location of the cursor hot spot, in coordinates that are relative to the upper-left corner of the screen. As best I can tell, the default window procedure ignores that parameter when the first parameter is set to HTCAPTION. So we'll pass an arbitrary value of (5,5). Our complete left-button click handling is shown in Listing 12.

Listing 12: Handling drag region clicks (Windows)

case WM_LBUTTONDOWN:
   // handle potential clicks in window drag region; 
   // if we get one, map it into a click on the title bar
   if (QTSkin_IsSkinnedMovie(myMovie))
      if (QTSkin_IsDragClick(myWindowObject, lParam)) {
         SendMessage(theWnd, WM_NCLBUTTONDOWN, 
                     (WPARAM)HTCAPTION, MAKELPARAM(5, 5));
         myIsHandled = true;
      }

   // do any application-specific mouse-button handling, 
   // but only if the message hasn't already been handled
   if (!myIsHandled)
      QTApp_HandleContentClick(theWnd, &myMacEvent);

   break;

The only thing left is to consider the definition of QTSkin_IsDragClick, which we call in Listing 12 to determine whether the specified point is in the drag region of the skinned movie window. Here we have several possibilities. We saw above that our version of QTApp_HandleEvent returns true if the specified event is a mouse-down event in the window's drag region. So we could just use that function. Alternatively, we can convert the Mac-style drag region (saved in our application data record) to a Windows region (of type HRGN) and call the Windows function PtInRegion to see whether the specified point is in that region. That's the strategy we'll use here; Listing 13 shows our definition of QTSkin_IsDragClick.

Listing 13: Finding drag region clicks (Windows)

#if TARGET_OS_WIN32
Boolean QTSkin_IsDragClick 
                  (WindowObject theWindowObject, LONG lParam)
{
   WindowObject                   myWindowObject = NULL;
   ApplicationDataHdl             myAppData = NULL;
   HRGN                           myRegion = NULL;
   POINT                          myPoint;
   Boolean                        isDragClick = false;

   myAppData = (ApplicationDataHdl)
            QTFrame_GetAppDataFromWindowObject(theWindowObject);
   if (myAppData != NULL) {
      myPoint.x = LOWORD(lParam);
      myPoint.y = HIWORD(lParam);

      myRegion = MacRegionToNativeRegion
            ((**myAppData).fDragRegion);

      if (PtInRegion(myRegion, myPoint.x, myPoint.y))
         isDragClick = true;

      DeleteObject(myRegion);
   }

   return(isDragClick);
}
#endif

The lParam parameter that was passed to WM_LBUTTONDOWN (which we also pass to QTSkin_IsDragClick) specifies a point in coordinates that are local to the client area of the window. As a result, we don't need to offset the drag region in Listing 13.

So now we've completely handled a click in the drag region of a skinned movie window on Windows.

Shutting Down

When the user closes a skinned movie window, we need to deallocate any memory used for displaying the movie in a skin. In particular, we need to dispose of the window regions that we're storing in the application data record. Listing 14 shows the definition of QTSkin_DumpWindowData, which is called by QTApp_RemoveWindowObject.

Listing 14: Cleaning up when a skinned window is closed

void QTSkin_DumpWindowData (WindowObject theWindowObject)
{
   ApplicationDataHdl      myAppData = NULL;

   myAppData = (ApplicationDataHdl)
            QTFrame_GetAppDataFromWindowObject(theWindowObject);
   if (myAppData != NULL) {
      if ((**myAppData).fContentRegion != NULL)
         DisposeRgn((**myAppData).fContentRegion);

      if ((**myAppData).fDragRegion != NULL)
         DisposeRgn((**myAppData).fDragRegion);

      if ((**myAppData).fStructRegion != NULL)
         DisposeRgn((**myAppData).fStructRegion);

      DisposeHandle((Handle)myAppData);
      (**theWindowObject).fAppData = NULL;
   }
}

You'll notice that we didn't do anything to free up the memory addressed by (**myAppData).fWinHRGN. The documentation for the SetWindowRgn function indicates that the operating system owns the region we pass it; this means that we don't need to call DeleteObject on that region.

When our application shuts down, we need to deallocate the universal procedure pointer contained inside of the gDefSpec structure. Listing 15 shows how we do this.

Listing 15: Cleaning up at application shut-down

void QTSkin_Stop (void)
{
   // dispose of the window procedure UPP
   if (gDefSpec.u.defProc != NULL)
      DisposeWindowDefUPP(gDefSpec.u.defProc);
}

Conclusion

If you've made it this far, you deserve a pat on the back. We've had our usual dose of new QuickTime APIs, but we've also had a big gulp of low-level window management. On the Macintosh, we had to write a custom window definition procedure in order for our application to handle skinned movie windows. And on Windows, we had to tinker with our application's event-handling to support skinned movie window dragging. But the payoff for all this work is tremendous, precisely because skinned movies are such great stuff. As we've noted, the movie author now has virtually complete control over the appearance and behavior of movie windows. The movie interface has become part of the movie content. The medium is now part of the message.

Credits

Special thanks are due to Jim Batson for reviewing this article and providing some helpful comments. Thanks are also due to ici Media, Inc. (http://www.icimediainc.net) for permission to use the picture of the movie in Figure 3.


Tim Monroe is intrigued to discover that his lizards often eat their own skin after they molt. You can explain this to him at monroe@apple.com.

 
AAPL
$98.15
Apple Inc.
-0.23
MSFT
$43.58
Microsoft Corpora
-0.31
GOOG
$587.42
Google Inc.
+1.81

MacTech Search:
Community Search:

Software Updates via MacUpdate

Knock 1.1.7 - Unlock your Mac by knockin...
Knock is a faster, safer way to sign in. You keep your iPhone with you all the time. Now you can use it as a password. You never have to open the app -- just knock on your phone twice, even when it's... Read more
Mellel 3.3.6 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
LibreOffice 4.3.0.4 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
Freeway Pro 7.0 - Drag-and-drop Web desi...
Freeway Pro lets you build websites with speed and precision... without writing a line of code! With it's user-oriented drag-and-drop interface, Freeway Pro helps you piece together the website of... Read more
Drive Genius 3.2.4 - Powerful system uti...
Drive Genius is an OS X utility designed to provide unsurpassed storage management. Featuring an easy-to-use interface, Drive Genius is packed with powerful tools such as a drive optimizer, a... Read more
Vitamin-R 2.15 - Personal productivity t...
Vitamin-R creates the optimal conditions for your brain to work at its best by structuring your work into short bursts of distraction-free, highly focused activity alternating with opportunities for... Read more
Toast Titanium 12.0 - The ultimate media...
Toast Titanium goes way beyond the very basic burning in the Mac OS and iLife software, and sets the standard for burning CDs, DVDs, and now Blu-ray discs on the Mac. Create superior sounding audio... Read more
OS X Yosemite Wallpaper 1.0 - Desktop im...
OS X Yosemite Wallpaper is the gorgeous new background image for Apple's upcoming OS X 10.10 Yosemite. This wallpaper is available for all screen resolutions with a source file that measures 5,418... Read more
Acorn 4.4 - Bitmap image editor. (Demo)
Acorn is a new image editor built with one goal in mind - simplicity. Fast, easy, and fluid, Acorn provides the options you'll need without any overhead. Acorn feels right, and won't drain your bank... Read more
Bartender 1.2.20 - Organize your menu ba...
Bartender lets you organize your menu bar apps. Features: Lets you tidy your menu bar apps how you want. See your menu bar apps when you want. Hide the apps you need to run, but do not need to... Read more

Latest Forum Discussions

See All

Murl the Squirrel (Games)
Murl the Squirrel 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Meet Murl. He is teased by a group of flying squirrels because he can't fly. Determined to show them he's can fly, he meets... | Read more »
Celleste (Games)
Celleste 0.1 Device: iOS Universal Category: Games Price: $2.99, Version: 0.1 (iTunes) Description: Lots of cute action with amazing 3D graphics and a new type of gameplay! Take control over the forces of the universe to help a group... | Read more »
Super Heavy Sword (Games)
Super Heavy Sword 0.0.1 Device: iOS Universal Category: Games Price: $.99, Version: 0.0.1 (iTunes) Description: Get Ready to Get HEAVY! Monster Robot Studios presents SUPER Heavy Sword! The sequel to the smash hit HEAVY sword which... | Read more »
Angels In The Sky (Games)
Angels In The Sky 1.00 Device: iOS Universal Category: Games Price: $6.99, Version: 1.00 (iTunes) Description: - This game is only for the iPhone 5s. please do not use the iPad, iPhone 5 or earlier devices.- Just touch or holding... | Read more »
80 Days (Games)
80 Days 1.0.2 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.2 (iTunes) Description: 1872, with a steampunk twist. Phileas Fogg has wagered he can circumnavigate the world in just eighty days. Choose your own route... | Read more »
Micromon (Games)
Micromon 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: 130+ Animated Monsters to Catch & Battle! No waiting, play at your own pace! Embark on an epic monster capture RPG like none... | Read more »
Empire Manager (Games)
Empire Manager 1.0 Device: iOS iPhone Category: Games Price: $3.99, Version: 1.0 (iTunes) Description: Become ruler of an empire. Manage your economy, develop technology, hire an army and conquer the world in this addictive turn-... | Read more »
Empire Manager HD (Games)
Empire Manager HD 1.0 Device: iOS Universal Category: Games Price: $7.99, Version: 1.0 (iTunes) Description: Become ruler of an empire. Manage your economy, develop technology, hire an army and conquer the world in this addictive... | Read more »
Star Admiral Review
Star Admiral Review By Rob Thomas on July 30th, 2014 Our Rating: :: ADMIRABLE ADMIRALSUniversal App - Designed for iPhone and iPad While this new digital CCG may feel a bit familiar, Star Admiral offers a sci-fi twist and galaxy’s... | Read more »
Zap! Pow! Become a Badass Wizard in Phan...
Zap! Pow! | Read more »

Price Scanner via MacPrices.net

iPad Cannibalization Threat “Overblown”
Seeking Alpha’s Kevin Greenhalgh observes that while many commentators think Apple’s forthcoming 5.5-inch panel iPhone 6 will cannibalize iPad sales, in his estimation, these concerns are being... Read more
Primate Labs Releases July 2014 MacBook Pro P...
Primate Labs’ John Poole has posted Geekbench 3 results for most of the new MacBook Pro models that Apple released on Tuesday. Poole observes that overall performance improvements for the new MacBook... Read more
Apple Re-Releases Bugfixed MacBook Air EFI Fi...
Apple has posted a bugfixed version EFI Firmware Update 2.9 a for MacBook Air (Mid 2011) models. The update addresses an issue where systems may take longer to wake from sleep than expected, and... Read more
Save $50 on the 2.5GHz Mac mini, plus free sh...
B&H Photo has the 2.5GHz Mac mini on sale for $549.99 including free shipping. That’s $50 off MSRP, and B&H will also include a free copy of Parallels Desktop software. NY sales tax only. Read more
Save up to $140 on an iPad Air with Apple ref...
Apple is offering Certified Refurbished iPad Airs for up to $140 off MSRP. Apple’s one-year warranty is included with each model, and shipping is free. Stock tends to come and go with some of these... Read more
$250 price drop on leftover 15-inch Retina Ma...
B&H Photo has dropped prices on 2013 15″ Retina MacBook Pros by $250 off original MSRP. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.3GHz Retina MacBook Pro: $2249, $250 off... Read more
More iPad Upgrade Musings – The ‘Book Mystiqu...
Much discussed recently, what with Apple reporting iPad sales shrinkage over two consecutive quarters, is that it had apparently been widely assumed that tablet users would follow a two-year hardware... Read more
13-inch 2.5GHz MacBook Pro on sale for $999,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $999.99 on their online store. Choose free shipping or free instant local store pickup (if available). Their price is $100 off MSRP. Price is... Read more
Save up to $300 on an iMac with Apple refurbi...
The Apple Store has Apple Certified Refurbished iMacs available for up to $300 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. These are the best prices on... Read more
WaterField Unveils 15″ Outback Solo & 13″...
Hard on the heels of Apple’s refreshed MacBook Pro Retina laptops announcement, WaterField Designs has unveiled a 15-inch version of the waxed-canvas and leather Outback Solo and a 13-inch version of... Read more

Jobs Board

Sr. Product Leader, *Apple* Store Apps - Ap...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Sr. Product Leader, *Apple* Store Apps - Ap...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.