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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Adobe Lightroom 6.10.1 - Import, develop...
Adobe Lightroom is available as part of Adobe Creative Cloud for as little as $9.99/month bundled with Photoshop CC as part of the photography package. Lightroom 6 is also available for purchase as a... Read more
iPhoto Library Manager 4.2.7 - Manage mu...
iPhoto Library Manager allows you to organize your photos among multiple iPhoto libraries, rather than having to store all of your photos in one giant library. You can browse the photos in all your... Read more
Smultron 9.4 - Easy-to-use, powerful tex...
Smultron 9 is an elegant and powerful text editor that is easy to use. Use it to create or edit any text document. Everything from a web page, a note or a script to any single piece of text or code.... Read more
TextSoap 8.4 - Automate tedious text doc...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more
Merlin Project 4.2.3 - $349.00
Merlin Project is the leading professional project management software for OS X. If you plan complex projects on your Mac, you won’t get far with a simple list of tasks. Good planning raises... Read more
QuarkXPress 13.0.0.0 - Desktop publishin...
QuarkXPress 2017 is the new version that raises the bar for design and productivity. With non-destructive graphics and image editing directly within your layout, you no longer have to choose between... Read more
Path Finder 7.5 - Powerful, award-winnin...
Path Finder makes you a master of file management. Take full control over your file system. Save your time: compare and synchronize folders, view hidden files, use Dual Pane and full keyboard... Read more
Path Finder 7.5 - Powerful, award-winnin...
Path Finder makes you a master of file management. Take full control over your file system. Save your time: compare and synchronize folders, view hidden files, use Dual Pane and full keyboard... Read more
Merlin Project 4.2.3 - $349.00
Merlin Project is the leading professional project management software for OS X. If you plan complex projects on your Mac, you won’t get far with a simple list of tasks. Good planning raises... Read more
TextSoap 8.4 - Automate tedious text doc...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more

Latest Forum Discussions

See All

Magikarp Jump splashes onto Android worl...
If you're tired ofPokĂ©mon GObut still want something to satisfy your mobilePokĂ©mon fix,Magikarp Jumpmay just do the trick. It's out now on Android devices the world over. While it looks like a simple arcade jumper, there's quite a bit more to it... | Read more »
Purrfectly charming open-world RPG Cat Q...
Cat Quest, an expansive open-world RPG from former Koei-Tecmo developers, got a new gameplay trailer today. The video showcases the combat and exploration features of this feline-themed RPG. Cat puns abound as you travel across a large map in a... | Read more »
Jaipur: A Card Game of Duels (Games)
Jaipur: A Card Game of Duels 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: ** WARNING: iPad 2, iPad Mini 1 & iPhone 4S are NOT compatible. ** *** Special Launch Price for a limited... | Read more »
Subdivision Infinity (Games)
Subdivision Infinity 1.03 Device: iOS Universal Category: Games Price: $2.99, Version: 1.03 (iTunes) Description: Launch sale! 40% Off! Subdivision Infinity is an immersive and pulse pounding sci-fi 3D space shooter. https://www.... | Read more »
Clash of Clans' gets a huge new upd...
Clash of Clans just got a massive new update, and that's not hyperbole. The update easily tacks on a whole new game's worth of content to the hit base building game. In the update, that mysterious boat on the edge of the map has been repaired and... | Read more »
Thimbleweed Park officially headed to iO...
Welp, it's official. Thimbleweed Park will be getting a mobile version. After lots of wondering and speculation, the developers confirmed it today. Thimbleweed Park will be available on both iOS and Android sometime in the near future. There's no... | Read more »
Pokémon GO might be getting legendaries...
The long-awaited legendary PokĂ©mon may soon be coming to PokĂ©mon GO at long last. Data miners have already discovered that the legendary birds, Articuno, Moltres, and Zapdos are already in the game, it’s just a matter of time. [Read more] | Read more »
The best deals on the App Store this wee...
If you’ve got the Monday blues we have just the thing to cheer you up. The week is shaping up to be a spectacular one for sales. We’ve got a bunch of well-loved indie games at discounted prices this week along with a few that are a little more... | Read more »
Honor 8 Pro, a great choice for gamers
Honor is making strides to bring its brand to the forefront of mobile gaming with its latest phone, the Honor 8 Pro. The Pro sets itself apart from its predecessor, the Honor 8, with a host of premium updates that boost the device’s graphical and... | Read more »
The 4 best outdoor adventure apps
Now that we're well into the pleasant, warmer months, it's time to start making the most of the great outdoors. Spring and summer are ideal times for a bit of trekking or exploration. You don't have to go it alone, though. There are plenty of... | Read more »

Price Scanner via MacPrices.net

Sale! 15-inch 2.6GHz Silver Touch Bar MacBook...
DataVision has the 15″ 2.6GHz Silver Touch Bar MacBook Pro (MLW72LL/A) on sale for $2199 including free shipping. Their price is $200 off MSRP, and it’s the lowest price available for this model (... Read more
A Kaby Lake Processor Upgrade For The MacBook...
Now they tell me! Well, actually Apple hasn’t said anything official on the subject, but last week Bloomberg News’s Mark Gurman and Alex Webb cited unnamed “people familiar with the matter”... Read more
Kodak’s Camera-First Smartphone EKTRA Launche...
The Eastman Kodak Company and Bullitt Group have announced the availability of a U.S. GSM version of the KODAK EKTRA Smartphone. The U.S. launch coincides with a software update addressing requests... Read more
Apple Launches App Development Curriculum for...
Apple today launched a new app development curriculum designed for students who want to pursue careers in the fast-growing app economy. The curriculum is available as a free download today from Apple... Read more
Check Apple prices on any device with the iTr...
MacPrices is proud to offer readers a free iOS app (iPhones, iPads, & iPod touch) and Android app (Google Play and Amazon App Store) called iTracx, which allows you to glance at today’s lowest... Read more
9.7-inch 2017 iPad available for $298, save $...
Sams Club has 32GB 9.7″ Apple iPads available for $298 for Sams Club members. That’s $21 off MSRP. Order online and choose free local store pickup (if available) or free shipping. Read more
touchbyte Releases PhotoSync 3.2 for iOS With...
Hamburg, Germany based touchbyte has announced the release of PhotoSync 3.2 for iOS, a major upgrade to the versatile and powerful app to transfer, backup and share photos and videos over the air.... Read more
Emerson Adds Touchscreen Display and Apple Ho...
Emerson has announced the next evolution of its nationally recognized smart thermostat. The new Sensi Touch Wi-Fi Thermostat combines proven smarthome technology with a color touchscreen display and... Read more
SurfPro VPN for Mac Protects Data While Offer...
XwaveSoft has announced announce the release and immediate availability of SurfPro VPN 1.0, their secure VPN client for macOS. SurfPro VPN allows Mac users to protect their internet traffic from... Read more
13-inch Touch Bar MacBook Pros on sale for $1...
B&H Photo has 13″ MacBook Pros in stock today for up to $150 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 13″ 2.9GHz/512GB Touch Bar MacBook Pro Space Gray (... Read more

Jobs Board

Data Engineer - *Apple* Media Products - Ap...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
*Apple* Store Leader Program - Apple, Inc (U...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on Read more
*Apple* Integration Specialist - A3 Solution...
Apple Integration Specialist Contract-To-HireWe are searching for dedicated, well-experienced and energetic individuals for an information technology corporation Read more
Sr. Software Engineer, *Apple* Online Store...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
Senior Engineering Project Manager, *Apple*...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.