TweetFollow Us on Twitter

The Matrix Revolutions

Volume Number: 19 (2003)
Issue Number: 12
Column Tag: Programming

QuickTime Toolkit

The Matrix Revolutions

by Tim Monroe

Handling Movie File Operations with Revolution

Introduction

In the previous two QuickTime Toolkit articles (in MacTech, September and October 2003), we've taken a look at Revolution, a rapid application development tool published by Runtime Revolution Ltd. So far, we've developed a fairly complete application -- called RunRevVeez -- that can open and display QuickTime movies. Our application also supports basic editing operations on those movies (cut, copy, paste, and so forth). In this article, we'll finish up our investigation of Revolution by implementing the basic file-handling operations. We'll see how to keep track of whether a movie has been edited, how to adjust the items in the File menu accordingly, and how to save an edited movie into a new file. And we'll see how, when the user decides to quit RunRevVeez, to iterate through all open movie windows and give the user the chance to save or discard any unsaved changes to those movies. Figure 1 shows the File menu of RunRevVeez when at least one movie window is open and the movie in the frontmost window has been edited.


Figure 1: The File menu of RunRevVeez

It's worth noting that these techniques might be of interest to any Revolution developers who need to create documents and store the information in them, as this issue does not appear to be well documented.

Toward the end of this article, we'll take a look at building and packaging RunRevVeez for distribution to users. The main hurdle here involves combining into a single item the application built by Revolution and the external plug-in module built by Project Builder. This isn't terribly difficult, but once again it's not well documented. So it's worth touching on the topic.

A few final notes before we begin. First, I have upgraded my development environment to Revolution version 2.1, which is the current version as of the time I'm writing this. (The two previous articles used version 2.0.2.) So you may notice some differences in the interface if you're still using an earlier version. I encountered no problems when upgrading to version 2.1; the existing version 2.0.2 RunRevVeez project could be opened and modified using version 2.1. Moreover, by the time this article makes it to press, the current version will be at least 2.1.1 (more on that later). The folks at Runtime Revolution seem to be committed to making their Mac OS X product as solid and stable as they can, and improved versions seem to be appearing fairly often. This is a good thing.

Also, you may notice that the movie windows and dialog boxes have a slightly different appearance from those in the two previous articles. That's because the screen shots for this article were taken with RunRevVeez running on Mac OS X version 10.3 (also known as "Panther"). I encountered no problems running Revolution or the applications it creates under Panther. This also is a good thing.

File Manipulation

So our goal right now is to finish RunRevVeez by implementing the basic file-handling operations (aside from the Open menu item, which we covered in the earlier articles). In particular, we want to be able to track changes to a movie and allow the user to save those changes. In the previous article, you'll recall, we saw how to implement the standard editing operations. To do this, we needed to use an external code module that called QuickTime's movie controller editing APIs (for instance, MCCut and MCPaste). We also defined a custom property for each movie window (which we called movieChanged) to keep track of whether the movie in that window has been edited.

Enabling and Disabling the File Menu Items

We can use that custom property to help us enable and disable the items in the File menu according to the state of the movie in the frontmost window. We want the New and Open menu items to be enabled always, and we want the Close and "Save As..." menu items to be enabled only if the frontmost window is a movie window. Finally, we want the Save menu item to be enabled only if the frontmost window is a movie window that has been changed since it was last opened or saved. Listing 1 shows the code we add to the mouseDown handler of the main menu item group.

Listing 1: Adjusting the File menu

mouseDown
on mouseDown
   constant kNewItemIndex = 1
  constant kOpenItemIndex = 2
  constant kCloseItemIndex = 3
  constant kSaveItemIndex = 5
  constant kSaveAsItemIndex = 6
   put first line of the openStacks into theTopStack
   put exists(player "MoviePlayer" of stack theTopStack) \
                  into gotPlayer
  enable menuItem kNewItemIndex of menu "File"
  enable menuItem kOpenItemIndex of menu "File"
  
  disable menuItem kCloseItemIndex of menu "File"
  disable menuItem kSaveItemIndex of menu "File"
  disable menuItem kSaveAsItemIndex of menu "File"
  
  if gotPlayer then
    enable menuItem kCloseItemIndex of menu "File"
    enable menuItem kSaveAsItemIndex of menu "File"
    
    if the movieChanged of stack theTopStack is true then
      enable menuItem kSaveItemIndex of menu "File"
    end if
    
  end if
end mouseDown

There's nothing here that we haven't seen before, except the use of the constant command to define some symbolic constants. This of course makes our code more readable and (in theory) more maintainable.

Creating a New Movie

In the previous two articles, we saw how to handle the Open command in the File menu to open an existing movie file and to display the movie it contains in a window on the screen. Revolution provides easy access to the standard file-opening dialog box, and it returns the full pathname of a selected movie file, which we can assign to the movie player object.

But how would we handle the New menu item, which should open a new movie window that contains an empty movie not associated with any existing file? Here things get a bit tricky. We can open an empty document window and leave the filename of the player object in that window set to the empty string "". But in that case the movieControllerID property of the player object will be 0, and this renders the new player object pretty much useless. We won't be able to paste any movie data cut or copied from some other movie into it (which is usually what we want to do with empty movies).

One solution to this problem would be to create a new empty movie and an associated movie controller ourselves (in our QuickTime external module, of course) and then assign the movie controller ID to the player object's movieControllerID property. The Revolution documentation states unequivocally that "the movieControllerID property is read-only and cannot be set". However, I'm told that this is in fact not true and that we can assign a value to that property. Because I learned about this fairly late in the process of writing this article, I'll have to leave the implementation of this feature as an exercise for the reader. In the meantime, RunRevVeez will simply display a dialog box, shown in Figure 2, when the user selects the New menu item.


Figure 2: The New warning

Closing a Movie Window

When the user clicks the close button of a stack, Revolution does not immediately close the stack. Instead, it sends a closeStackRequest message to the current card on that stack. If the closeStackRequest message handler passes that message further along the message path, the stack will actually be closed; otherwise, the stack remains open. This mechanism gives us a chance to prompt the user to save or discard changes to the movie in a window, or to cancel the close operation altogether.

We also want to make use of this mechanism when the user selects the Close menu item in the File menu. Accordingly, we'll have the menuPick handler for that item simply send a closeStackRequest message to the frontmost movie window. Listing 2 shows our code that handles the Close menu item.

Listing 2: Handling the Close menu item

menuPick
case "Close"
   put first line of the openStacks into theTopStack
   if exists(player "MoviePlayer" of stack theTopStack) then
      send closeStackRequest to stack theTopStack
   end if
   break

So we need to add a closeStackRequest handler to the script associated with a movie window. This handler needs to check the movieChanged property of the movie window to see if the movie has been edited since it was last opened or saved. If it has been, we want to ask the user to save those changes, discard them, or cancel the close operation. We can do that with this lengthy line of script:

answer warning "Do you want to save the changes \
      you made to the document " & quote & the title of me \
      & quote & "?" \
      with "Don't Save" or "Cancel" or "Save" \
      titled "Save changes before " & theAction \
      as sheet

This line displays the sheet shown in Figure 3.


Figure 3: The "Save changes" sheet

A couple of comments are in order here. First, notice that we use the special keyword quote to embed a pair of quotation marks in the string displayed as the message in the sheet. Also, we use the "as sheet" qualifier to have the dialog box drop down from the movie window as a sheet. On operating systems other than Mac OS X, this qualifier is ignored and the warning is displayed as a standard modal dialog box. In that case, the dialog box will have the specified title, which is either "Save changes before closing" or "Save changes before quitting". We look at the custom property isQuitting of the mainstack to determine which action is appropriate:

if the isQuitting of stack "RunRevVeez" is true then
   put "quitting" into theAction
else
   put "closing" into theAction
end if

(We'll see how that property is set in a little while.)

The user must click one of the three buttons in the sheet (or dialog box) to dismiss it; when that happens, control returns to our closeStackRequest handler and the it variable is set to the text of the selected button. If the user selects "Don't Save", we can simply pass the closeStackRequest message along the message path, thereby allowing the window to be closed. If the user selects "Save", we call the saveAs function, which is defined in our external code module. (See the following two sections for more on saving edited movies.) Finally, if the user selects "Cancel", we want to set the mainstack's isQuitting property to false so that RunRevVeez does not quit. (If we weren't already quitting, this is harmless.) Also, we want to execute the "exit to top" control statement to jump out of the closeStackRequest message handler and all other pending handlers. As we'll see in more detail soon, this will halt a shutDownRequest handler that might be executing.

Listing 3 shows our complete closeStackRequest message handler.

Listing 3: Handling a request to close a window

closeStackRequest
on closeStackRequest
   if the movieChanged of this stack is true then
    
          -- figure out whether we are quitting or closing
    if the isQuitting of stack "RunRevVeez" is true then
      put "quitting" into theAction
    else
      put "closing" into theAction
    end if
     
          -- ask the user to save or discard changes, or to cancel the close operation
    answer warning "Do you want to save the changes \
      you made to the document " & quote & the title of me \
      & quote & "?" \
      with "Don't Save" or "Cancel" or "Save" \
      titled "Save changes before " & theAction \
      as sheet
     
          -- the button chosen by the user is now in the "it" variable; handle the three cases
          -- Cancel:
    if it is "Cancel" then
      set the isQuitting of stack "RunRevVeez" to false
      exit to top
    end if
    
          -- Save: save the changes and close window
    if it is "Save" then
      put the movieControllerID of player "MoviePlayer" \ 
               of this stack into mc
      get saveAs(mc)
    end if
    
       -- Don't Save: toss the changes and close window
       -- (no actual code needed here)
    
  end if
  
     -- if we get to here, we want to close the window
  close me
  
end closeStackRequest

In theory (at least as I understand it), we should use the command "pass closeStackRequest" instead of "close me" near the end of this handler, to pass the message along the message chain. But I couldn't get things working correctly if I did that. At any rate, this handler appears to do the right thing.

Saving a Changed Movie

Once the user has edited a movie, he or she is likely to want to save those changes. Unfortunately, the current version of Revolution (version 2.1) makes it impossible for us to save an edited movie into the file the movie was opened from. That's because, when the Revolution runtime engine calls OpenMovieFile to open a movie file, it passes the value fsRdPerm as the desired file permission. That is, Revolution always opens movie files with read-only permission. So, even though RunRevVeez is able to actually edit a movie, it can't save the edited movie back into its original file.

The engineers at Runtime Revolution are aware of this limitation and have indicated to me that version 2.1.1 will be able to open movie files with read/write permission. In addition, Revolution would need to provide accessor methods that return a movie's file reference number (which QuickTime returns to OpenMovieFile) and its resource ID (which QuickTime returns to NewMovieFromFile), since we would need both those values when we call UpdateMovieResource. Because version 2.1.1 is not yet available, I cannot verify that it provides the capabilities we need to save an edited movie into its original file. As a result, RunRevVeez simply displays a warning when the user selects the Save menu item. It executes this line of script to do so:

answer warning "Save is not supported by RunRevVeez. Use \
                                       Save As...."

Figure 4 shows the resulting warning.


Figure 4: The Save warning

Saving a Movie into a New File

The best we can do right now, therefore, is allow the user to save an edited movie into a new file -- that is, implement the "Save As..." menu item. Our menu handler for the "Save As..." item begins as usual by finding the frontmost window (that is, stack) and ensuring that it contains a player object. Then it calls the custom function saveAs:

put the movieControllerID of player "MoviePlayer" \
                           of stack theTopStack into mc
get saveAs(mc)

The code for the saveAs function is contained in our QuickTime external; Listing 4 shows the corresponding external code, XCMD_SaveAs. The central core of this function is borrowed wholesale from our existing C-language sample code application QTShell, as are the utility functions QTFrame_PutFile and QTUtils_ConvertCToPascalString that are called by XCMD_SaveAs.

Listing 4: Saving a movie in a new file

XCMD_SaveAs
#define kSavePrompt                  "Save Movie as:"
#define kSaveFileName               "untitled.mov"
void XCMD_SaveAs (char *args[], int nargs, 
            char **retstring, Bool *pass, Bool *error)
{
   MovieController mc = NULL;
   Movie movie = NULL;
  char *nufilename = NULL;
   OSErr result = userCanceledErr;
   char *retstr = NULL;
   *pass = false;
   *error = false;
   if (nargs == 1) {
      mc = (MovieController)atol(args[0]);
      if (mc != NULL) {
         movie = MCGetMovie(mc);
         if (movie != NULL) {
            FSSpec mfile;
            Boolean isSelected = false;
            Boolean isReplacing = false;   
            StringPtr prompt = 
                  QTUtils_ConvertCToPascalString(kSavePrompt);
            StringPtr fileName = 
                  QTUtils_ConvertCToPascalString(kSaveFileName);
            QTFrame_PutFile(prompt, fileName, &mfile, 
                                       &isSelected, &isReplacing);
                        
            free(prompt);
            free(fileName);
                        
            // save the movie in the selected location
            if (isSelected) {
               Movie         newMovie = NULL;
               short         refNum = -1;
               FSRef         fsRef;
                                
               // delete any existing file of that name
               if (isReplacing) {
                  result = DeleteMovieFile(&mfile);
                  if (result != noErr)
                     goto bail;
               }
                                
               newMovie = FlattenMovieData(movie, 
                           flattenAddMovieToDataFork | 
                           flattenForceMovieResourceBeforeMovieData,
                           &mfile,
                   FOUR_CHAR_CODE('TVOD'),
                   smSystemScript,
                           createMovieFileDeleteCurFile | 
                           createMovieFileDontCreateResFile);
               result = GetMoviesError();
               if ((newMovie == NULL) || (result != noErr))
                  goto bail;
                
               // FlattenMovieData creates a new movie file and returns the movie to us; 
               // since we want to let Revolution open the new file, we'll dump the 
               // movie returned by FlattenMovieData
               DisposeMovie(newMovie);
                
               // also, on MacOS, FlattenMovieData *always* creates a resource fork; 
               // delete the resource fork now....
#if TARGET_OS_MAC
               result = FSpOpenRF(&mfile, fsRdWrPerm, &refNum);
               if (result == noErr) {
                  SetEOF(refNum, 0L);
                  FSClose(refNum);
               }
#endif
               // get the full pathname of the new file (to pass back to caller)
               result = FSpMakeFSRef(&mfile, &fsRef);
               if (result == noErr) {
                  retstr = malloc(kMaxPathSize);
                  result = FSRefMakePath(&fsRef, retstr, 
                                                      kMaxPathSize);
                  if (result == noErr) {
                     *retstring = retstr;
                     return;
                  }
               }
            } 
         }
      }
   }
        
bail:
   retstr = calloc(1, 2);
   if (retstr != NULL)
      retstr[0] = (result == noErr) ? '0': '1';
      
   *retstring = retstr;
 }

The typical behavior of the "Save As..." menu item is to create (or overwrite) the target movie file and then to open the movie in that file in the existing movie window. That means that we need to get RunRevVeez to open that new (or overwritten) file and load its movie into the movie window. To do this, we need to know the full pathname of that file. If you look carefully, you'll see that XCMD_SaveAs calls FSpMakeFSRef and FSRefMakePath to convert the file system specification of the target file into a full pathname, and that this pathname is returned (in retstring) to the caller. In other words, if the call to saveAs completes successfully, the built-in variable it will contain the full pathname of the new (or overwritten) file; if that call fails for any reason, then it will be the empty string or set to "1".

If the call to saveAs succeeds, we need to open the movie in the target file, as shown in Listing 5.

Listing 5: Loading the target movie into a movie window

menuPick
-- get the base name of the file
set the itemDelimiter to "/"
put the last item of it into newStackName
        
set the filename of player "MoviePlayer" \
                  of stack theTopStack to it
set the title of stack theTopStack to newStackName
        
set the movieChanged of stack theTopStack to false
get windowSetModified(windowID of stack theTopStack, 0)

We've seen much of this code before, since we needed to perform these operations when we opened a movie file into a new window. Notice that we reset the movieChanged property to false and call windowSetModified to clear the window modification state.

Quitting the Application

As you know, on Mac OS X the Quit menu item is contained in the Application menu, as shown in Figure 5.


Figure 5: The Application menu

This menu is supplied by the operating system, so we cannot attach a menuPick script to it. Instead, when the user selects the Quit menu item, the operating system sends an Apple event to our application. Since we have not installed any Apple event handlers, the Revolution runtime engine handles that event and issues a shutDownRequest message to our application. So we can handle the Quit menu item either by installing an Apple event handler or a shutDownRequest handler. To facilitate moving RunRevVeez to Windows operating systems, let's use a shutDownRequest handler.

We'll begin our shutDownRequest handler by setting a custom property of the mainstack, isQuitting:

set the isQuitting of me to true

We've already seen where we need to inspect this property, in the closeStackRequest handler of the movie window (Listing 3).

The remainder of our shutDownRequest handler simply loops through all open movie windows (using the repeat control structure) and sends the closeStackRequest message to them. If we encounter a window that isn't a movie window, we simply close it. Revolution applications will quit automatically once the last open window is closed. Listing 6 shows our complete shutDownRequest handler.

Listing 6: Handling the Quit menu item

shutDownRequest
on shutDownRequest
  set the isQuitting of me to true
  
     -- check for "dirty" movie windows
  repeat for each line theStack in the openStacks
    
    if exists(player "MoviePlayer" of stack theStack) then
            -- ask the movie window to close
      send closeStackRequest to stack theStack
    else
            -- if it's not a movie window, just close it
      close stack theStack
    end if
  end repeat
  
  pass shutDownRequest
  
end shutDownRequest

Application Distribution

Let's finish up by considering how to link our external code module -- which contains virtually all of our QuickTime-specific code -- to our application RunRevVeez. During application development, we can do this by setting the External References property of the mainstack to the bundle built by Project Builder. Recall that Project Builder builds a module called QTExternal.bundle. I found it most useful to copy that bundle into the folder containing the Revolution IDE. Then I added the full pathname of that bundle to the list of external references, as shown in Figure 6. The easiest way to set this external reference is to click on the little folder icon and then navigate to the desired file.


Figure 6: The external references during application development

Thereafter, whenever RunRevVeez is executed (either as a standalone application or in the Revolution IDE itself), the specified bundle will be used to resolve the external references in RunRevVeez.

Obviously, however, this is not satisfactory as a final distribution method. It would be far better to package the external and the application into a single file that can be installed wherever the user wishes, copied from machine to machine, and so forth. Once again, we want to set the External References property of the mainstack, but this time to a path relative to the compiled application. The easiest way to do that is using the message window (or message box). The message window is a window that allows us to execute one or more lines of script without having to create a message handler or attach the handler to any object. Figure 7 shows the Revolution message window, with the appropriate command typed into it.


Figure 7: The message window

If we inspect the External References property of the mainstack once again, we'll see the desired relative path (Figure 8).


Figure 8: The external references for application distribution

All that remains is to copy the external bundle into the application bundle. Once we've got RunRevVeez working as desired and we've reset the External References property as just indicated, option-click the compiled application in the Finder and choose "Show Package Contents", as shown in Figure 9.


Figure 9: The contextual menu for RunRevVeez

Finally, copy the QTExternal.bundle file into the MacOS folder that's inside the Contents folder of the window that opens up. We're done.

Conclusion

Let's take stock of what we've learned about Revolution and QuickTime in these three articles. We set ourselves a very specific goal: duplicate the functionality of our existing Carbon application QTShell using Revolution. That meant: create an application capable of opening arbitrary QuickTime movie files in windows on the screen, support the standard movie editing operations, and allow the user to save any edited movies as desired. That also meant: have our application look and behave like a standard double-clickable application (using native controls and other widgets provided by the operating system), support an About box, and implement the typical menus and menu items for movie windows and documents.

Revolution was able to provide most of what we were looking for, especially since we can take advantage of its ability to call external code modules. For instance, Revolution does not provide built-in support for movie editing, but it was easy enough for us to add that ability by defining a few routines in an external module. As I said in an earlier article, writing and deploying an external module is so easy that I can't really imagine any serious developer using Revolution not wanting to employ them as a matter of course. In an external, we have access to virtually the entire set of QuickTime APIs, which we can bring into play as needed.

Well, almost. There are a few limitations to using QuickTime APIs within Revolution applications that are worth highlighting. We saw earlier in this article that the Revolution runtime engine opens movie files with read-only permission. This means that we cannot save an edited movie into the file it was originally opened from. As we saw, however, this does not mean that we cannot usefully edit movies using RunRevVeez; rather, we simply have to educate the user to save the edited movie into a new file. The staff at Runtime Revolution has indicated that a fix for this issue is in the works, so happily (by the time you read this) our "Save As..." workaround may no longer be necessary.

A more significant limitation is that the Revolution runtime engine installs a movie controller action filter procedure. I'm guessing that it does this to support the messages that can be sent to a player object when various events concerning the QuickTime movie occur. For example, when the user changes the movie's current time (perhaps by clicking in the time line area of the controller bar, or by dragging the thumb), a currentTimeChanged message is sent to the player object. Similarly, for a QuickTime VR movie, the Revolution runtime engine installs an intercept procedure so that it can issue hotSpotClicked messages at the appropriate time.

The trouble (as we learned in an earlier article on REALbasic) is that this effectively prevents our external code module from installing a movie controller action filter procedure (or QuickTime VR intercept procedure) of its own. This then limits our ability to take advantage of a large number of capabilities that rely on an application processing messages in a movie controller action filter procedure. (We've seen very many examples of this in earlier articles.)

Let's be very clear about this, however: Revolution isn't doing anything wrong by installing and using these callback procedures. Rather, it's providing functionality to its users in exactly the way it should. The problem arises because Apple has as yet provided no means of chaining movie controller action filter procedures or QuickTime VR intercept procedures. One way out of this quandary would be for Apple to do exactly that: provide a way for multiple filter procedures to be called by a single movie controller. It might also be possible for Revolution to support an "expert mode" that allowed an application to selectively turn off the installation of these procedures. That would allow an external module to take full advantage of these various callback procedures without stepping on Revolution's toes.

So let's wrap thing up: RunRevVeez now looks and behaves exactly as it should, with only minor exceptions. It can open and display QuickTime movies, allow the standard editing operations on those movies, and save the edited movies into new files. It gives the user an opportunity to save or discard edited movies when a window is about to close or when the application is about to quit. And all of this was achieved with a couple dozen lines of script and an easy-to-construct external code module.

Credits

Thanks are due once again to Tuviah Snyder at Runtime Revolution Ltd., who was especially helpful this time in figuring out several issues concerning the packaging of our QuickTime external module.


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

 
AAPL
$99.00
Apple Inc.
+1.33
MSFT
$44.07
Microsoft Corpora
-0.43
GOOG
$589.79
Google Inc.
+0.77

MacTech Search:
Community Search:

Software Updates via MacUpdate

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
TotalFinder 1.6.2 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Tab-based... Read more
Vienna 3.0.0 RC 2 :be5265e: - RSS and At...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
VLC Media Player 2.1.5 - Popular multime...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more
Default Folder X 4.6.7 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more
TinkerTool 5.3 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Audio Hijack Pro 2.11.0 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Intermission 1.1.1 - Pause and rewind li...
Intermission allows you to pause and rewind live audio from any application on your Mac. Intermission will buffer up to 3 hours of audio, allowing users to skip through any assortment of audio... Read more
Autopano Giga 3.6 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more

Latest Forum Discussions

See All

Traps n’ Gemstones Review
Traps n’ Gemstones Review By Campbell Bird on July 28th, 2014 Our Rating: :: CASTLEVANIA JONESUniversal App - Designed for iPhone and iPad Fight mummies, dig tunnels, and ride a runaway minecart to discover ancient secrets in this... | Read more »
The Phantom PI Mission Apparition Review
The Phantom PI Mission Apparition Review By Jordan Minor on July 28th, 2014 Our Rating: :: GHOSTS BUSTEDUniversal App - Designed for iPhone and iPad The Phantom PI is an exceedingly clever and well-crafted adventure game.   | Read more »
More Stubies Are Coming Your Way in a Ne...
More Stubies Are Coming Your Way in a New Update Posted by Jessica Fisher on July 28th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
The Great Prank War Review
The Great Prank War Review By Nadia Oxford on July 28th, 2014 Our Rating: :: PRANKING IS SERIOUS BUSINESSUniversal App - Designed for iPhone and iPad Though short, The Great Prank War offers an interesting and fun mix of action and... | Read more »
Marvel Contest of Champions Announced at...
Marvel Contest of Champions Announced at Comic-Con Posted by Jennifer Allen on July 28th, 2014 [ permalink ] Announced over the weekend at San Diego Comic-Con was the fairly exciting looking Marvel Contest of Champions. | Read more »
Teenage Mutant Ninja Turtles Review
Teenage Mutant Ninja Turtles Review By Jennifer Allen on July 28th, 2014 Our Rating: :: DULL SWIPINGUniversal App - Designed for iPhone and iPad The pizza power is weak when it comes to this Teenage Mutant Ninja Turtles game.   | Read more »
Exploration Focused Puzzle Game Beatbudd...
Exploration Focused Puzzle Game Beatbuddy Set to Make Transition from PC to iOS this September Posted by Jennifer Allen on July 28th, 2014 [ permalink ] | Read more »
PlanetHD
PlanetHD By Nadia Oxford on July 28th, 2014 Our Rating: :: SPACE MADNESSUniversal App - Designed for iPhone and iPad PlanetHD will keep players busy for a while, though its unpredictable physics are a handful to deal with.   | Read more »
This Week at 148Apps: July 21-25, 2014
Another Week of Expert App Reviews   At 148Apps, we help you sort through the great ocean of apps to find the ones we think you’ll like and the ones you’ll need. Our top picks become Editor’s Choice, our stamp of approval for apps with that little... | Read more »
Reddme for iPhone - The Reddit Client (...
Reddme for iPhone - The Reddit Client 1.0 Device: iOS iPhone Category: News Price: $.99, Version: 1.0 (iTunes) Description: Reddme for iPhone is an iOS 7-optimized Reddit client that offers a refreshing new way to experience Reddit... | Read more »

Price Scanner via MacPrices.net

13-inch 2.5GHz MacBook Pro on sale for $1099,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $1099.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
Roundup of Apple refurbished MacBook Pros, th...
The Apple Store has Apple Certified Refurbished 13″ and 15″ MacBook Pros available for up to $400 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. Their prices... Read more
Record Mac Shipments In Q2/14 Confound Analys...
A Seeking Alpha Trefis commentary notes that Apple’s fiscal Q3 2014 results released July 22, beat market predictions on earnings, although revenues were slightly lower than anticipated. Apple’s Mac’... Read more
Intel To Launch Core M Silicon For Use In Not...
Digitimes’ Monica Chen and Joseph Tsai, report that Intel will launch 14nm-based Core M series processors specifically for use in fanless notebook/tablet 2-in-1 models in Q4 2014, with many models to... Read more
Apple’s 2014 Back to School promotion: $100 g...
 Apple’s 2014 Back to School promotion includes a free $100 App Store Gift Card with the purchase of any new Mac (Mac mini excluded), or a $50 Gift Card with the purchase of an iPad or iPhone,... Read more
iMacs on sale for $150 off MSRP, $250 off for...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
Mac minis on sale for $100 off MSRP, starting...
Best Buy has Mac minis on sale for $100 off MSRP. Choose free shipping or free instant local store pickup. Prices are for online orders only, in-store prices may vary: 2.5GHz Mac mini: $499.99 2.3GHz... Read more
Global Tablet Market Grows 11% in Q2/14 Notwi...
Worldwide tablet sales grew 11.0 percent year over year in the second quarter of 2014, with shipments reaching 49.3 million units according to preliminary data from the International Data Corporation... Read more
New iPhone 6 Models to Have Staggered Release...
Digitimes’ Cage Chao and Steve Shen report that according to unnamed sources in Apple’s upstream iPhone supply chain, the new 5.5-inch iPhone will be released several months later than the new 4.7-... Read more
New iOS App Helps People Feel Good About thei...
Mobile shoppers looking for big savings at their favorite stores can turn to the Goodshop app, a new iOS app with the latest coupons and deals at more than 5,000 online stores. In addition to being a... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
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
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
WW Sales Program Manager, *Apple* Online St...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.