TweetFollow Us on Twitter

Swing Shift

Volume Number: 20 (2004)
Issue Number: 2
Column Tag: Programming

QuickTime ToolKit

by Tim Monroe

Swing Shift

Editing QuickTime Movies with Java

Introduction

In the previous QuickTime Toolkit article ("Krakatoa, East of Java" in MacTech, January 2004), we saw how to use QuickTime for Java to open and display QuickTime movies in a Java-based application. We saw how to use AWT components to elicit movie files from the user (albeit indirectly, by a call to the QTFile method standardGetFilePreview) and to display and manage movies in a window (using the Frame and QTComponent components). We also saw how to display and handle the application's menus and menu items (using the Menu, MenuItem, and Action classes).

In this article, we're going to extend the application we built in the previous article -- called JaVeez -- to handle movie editing. We'll see how to do standard cut, copy, and paste editing, and we'll see how to save an edited movie into its original file or into a new file. We also need to implement the standard document behaviors, such as prompting the user to save or discard any changes when an edited movie window is about to be closed. In particular, we'll see how to display and manage the "Save Changes" dialog box shown in Figure 1.


Figure 1: The Save Changes dialog box of JaVeez

By sheer coincidence, the dialog boxes we'll use to perform these remaining tasks are best displayed using Swing, not AWT. The dialog box in Figure 1 can be displayed with a single line of code. We get the warning icon essentially for free, by passing the appropriate constant. We also get with the standard behavior where the Save button is highlighted as the default button and is activated by hitting the Return or Enter key on the keyboard. To my knowledge, it's not possible to designate a default button when constructing a dialog box using AWT.

We'll also take a look at handling several of the items in the Mac OS X Application menu. We'll see how to display an About box when the user chooses the "About JaVeez" menu item, and we'll see how to handle the Quit menu item correctly. We'll use a class introduced in J2SE 1.4.1 for Mac OS X, Application, to handle these operations. This class also makes it easy for us to handle basic Apple events, such as the OpenDocuments event that is sent to our application when the user drops some movie files onto the application icon in the Finder. By the end of this article, JaVeez will be virtually indistinguishable from the C-language Carbon-based application QTShell it's modeled upon.

Movie Editing

It's extremely easy to add support for movie editing to our application, because the MovieController class in QuickTime for Java provides pretty much all the methods we need. For instance, to cut the current movie selection, we can execute the cut method on the movie controller object associated with a movie window. As we'll see in a moment, we also need to place the cut segment onto the system scrap so that it's available for subsequent pasting (either by JaVeez itself or by any other QuickTime-savvy application).

In order for these editing methods to work properly, we need to explicitly enable editing. You may recall that in the previous article, we executed this code in the createNewMovieFromFile method:

if ((mc.getControllerInfo() & 
                  StdQTConstants.mcInfoMovieIsInteractive) == 0)
   mc.enableEditing(true);

This code looks to see whether the movie is an interactive movie (such as a QuickTime VR movie or a Flash movie); if it's interactive, it is not editable and therefore we do not want to enable editing. (If we did try to enable editing on an interactive movie, an exception would be raised.)

Handling Editing Operations

In JaVeez, the user performs editing operations using either the menu items in the Edit menu or their standard keyboard shortcuts. In either case, one of our action listeners will be invoked. For instance, Listing 1 shows our declaration of the UndoActionClass, an instance of which is attached as a listener to the Undo menu item and its keyboard equivalent. As you can see, we simply call the movie controller's undo method and then call our own method updateEditedWindow.

Listing 1: Undoing the previous edit

UndoActionClass
public class UndoActionClass extends AbstractAction {
   public UndoActionClass (String text, KeyStroke shortcut) {
      super(text);
      putValue(ACCELERATOR_KEY, shortcut);
   }
   public void actionPerformed (ActionEvent e) {
      try {
         mc.undo();
         updateEditedWindow();
      } catch (QTException err) {
         err.printStackTrace();
      }
   }
}

The updateEditedWindow method performs any operations that might be required when that the movie in the specified frame has been altered by an edit operation. For instance, the size of the movie may have changed, so we'll want to call our method sizeWindowToMovie to ensure that the movie window is sized to exactly contain the movie and the movie controller bar (if it's visible). Also, we'll want to adjust the items in the File and Edit menus appropriately. Listing 2 shows the updateEditedWindow method.

Listing 2: Updating the state of an edited window

updateEditedWindow public void updateEditedWindow () { try { mc.movieChanged(); mc.movieEdited(); mc.controllerSizeChanged(); adjustMenuItems(); sizeWindowToMovie(); } catch (QTException err) { err.printStackTrace(); } }

The actions associated with pasting and clearing can be handled by code that's exactly analogous to that in Listing 1 (just change "undo" to "paste" or "clear" in the actionPerformed method). In the two remaining cases, cutting and copying, we also need to make sure to move the cut or copied segment to the scrap, by calling the putOnScrap method. Listing 3 gives our implementation of the cut operation. The movie controller's cut method returns a QuickTime movie that holds the segment cut from the movie in the window; we copy it to the scrap and then dispose of that movie segment by calling disposeQTObject.

Listing 3: Cutting the movie selection

CutActionClass
public class CutActionClass extends AbstractAction {
      public CutActionClass (String text, KeyStroke shortcut) {
      super(text);
      putValue(ACCELERATOR_KEY, shortcut);
   }
   public void actionPerformed (ActionEvent e) {
      try {
         Movie editMovie = mc.cut();
         editMovie.putOnScrap(0);
         editMovie.disposeQTObject();
         updateEditedWindow();
      } catch (QTException err) {
         err.printStackTrace();
      }
   }
}

Our action handler for the copy operation is even simpler, since we don't need to call updateEditedWindow (because copying the current selection from a movie does not change the original movie).

Selecting All or None of a Movie

As usual, our Edit menu contains two further items, "Select All" and "Select None", which are quite easy to implement. In earlier articles, we've seen how to handle these items by calling MCDoAction with the mcActionSetSelectionDuration selector. Listing 4 shows how JaVeez handles the "Select All" command.

Listing 4: Selecting all of a movie

SelectAllActionClass
public class SelectAllActionClass extends AbstractAction {
   public SelectAllActionClass (String text, 
                                                   KeyStroke shortcut) {
      super(text);
      putValue(ACCELERATOR_KEY, shortcut);
   }
   public void actionPerformed (ActionEvent e) {
      try {
         TimeRecord tr = new TimeRecord(m.getTimeScale(), 0);
      
         mc.setSelectionBegin(tr);
         tr.setValue(m.getDuration());
         mc.setSelectionDuration(tr);
      } catch (QTException err) {
         err.printStackTrace();
      }
   }
}

The interesting thing here is that a TimeRecord is an object (which must be explicitly constructed by a call to new), not a structure as in our C code. In general, Java does not support either struct or union; instead, we need to build these sorts of composite types using classes or interfaces. The package quicktime.std.clocks provides access to TimeRecord objects and the methods to get and set their properties.

Listing 5 shows how JaVeez handles the "Select None" command.

Listing 5: Selecting none of a movie

SelectNoneActionClass
public class SelectNoneActionClass extends AbstractAction {
   public SelectNoneActionClass (String text, 
                                                   KeyStroke shortcut) {
      super(text);
      putValue(ACCELERATOR_KEY, shortcut);
   }
   public void actionPerformed (ActionEvent e) {
      try {
         TimeRecord tr = new TimeRecord(m.getTimeScale(), 0);
      
         mc.setSelectionDuration(tr);
      } catch (QTException err) {
         err.printStackTrace();
      }
   }
}

Enabling and Disabling Menu Items

Editing a movie usually entails that some of the items in the Edit and File menus need to be adjusted. For instance, cutting a segment out of a movie should be undoable, so we want to make sure that the Undo menu item is enabled. Similarly, once we've made any edit operation whatsoever to a movie, we want to make sure that the Save menu item is enabled. Listing 6 shows our implementation of the adjustMenuItems method. Notice that we disable the entire Edit menu if editing is not enabled for the movie. Also, we can use a movie's hasChanged method to determine whether to enable or disable the Save and Undo menu items. (As you would guess, we clear the movie's changed state -- by calling the clearChanged method -- when the user saves a movie.)

Listing 6: Adjusting the menu items

adjustMenuItems
public void adjustMenuItems () {
   try {
      if ((mc.getControllerInfo() & 
                  StdQTConstants.mcInfoEditingEnabled) == 0)
         editMenu.setEnabled(false);
      else
         editMenu.setEnabled(true);
        
      if (m.hasChanged()) {
         miSave.setEnabled(true);
         miUndo.setEnabled(true);
      } else {
         miSave.setEnabled(false);
         miUndo.setEnabled(false);
      }
       
      if (mc.getVisible())
      miShowController.setLabel
               (resBundle.getString("hideControllerItem"));
      else
         miShowController.setLabel
                  (resBundle.getString("showControllerItem"));
       
   } catch (QTException err) {
      err.printStackTrace();
   }
}

It's worth remarking that this menu-enabling logic is quite a bit simpler than that contained in our C-language applications, primarily because in JaVeez, a menu is always associated with a movie window. This means that the Close and "Save As..." items should always be enabled.

File Manipulation

Now let's consider how to handle the Close and "Save As..." menu items, along with the Save and Quit menu items. When handling the Close and Quit items, we need to check to see whether the user has made any changes to the frontmost movie window or to any of the open movie windows. If so, we need to give the user an opportunity to save or discard those changes. We also need to allow the user to cancel the close or quit operation altogether.

Closing a Movie Window

We can do all this by adding a window adapter to our application. A window adapter is an object that listens for specific events involving a window and executes a method when it receives a notification that one of those events has occurred. Currently, we can listen for window activation or deactivation, iconification or deiconification, and opening or closing. There are two flavors of window closing events; we can be notified that a window is in the process of being closed, or we can be notified that a window actually has closed. Clearly, we want to listen for the first kind of window closing event so that we can cancel it if necessary.

We'll define a class that extends the WindowAdapter class; this means that our class needs to implement some but not necessarily all of the methods in the WindowAdapter class. As just mentioned, we want to implement only the windowClosing method. Listing 7 shows our definition of the WindowAdpt class.

Listing 7: Handling a request to close a window

windowClosing
class WindowAdpt extends java.awt.event.WindowAdapter {
   public void windowClosing (java.awt.event.WindowEvent event) {
      try {
         if (m.hasChanged())
            askSaveChanges(resBundle.getString("closeText"));
         else
            dispose();
      } catch (QTException err) {
         err.printStackTrace();
      }
   }
}

This is simple enough: if the movie in the window has changed since it was opened or last saved, ask the user to save or discard those changes; otherwise, call dispose to close the window. (The dispose method is implemented by AWT's Window class.)

In the JaVeez constructor, we create an instance of this class and set it as the window listener like this:

WindowAdpt WAdapter = new WindowAdpt();
addWindowListener(WAdapter);

All that remains is to write the askSaveChanges method. This method needs to display the Save Changes dialog box (see Figure 1 again) and respond to the user's selecting one of the three buttons in that box. Happily, Swing provides the JOptionPane class that is tailor-made for this purpose. Listing 8 shows our implementation of askSaveChanges. We pass in a string object that indicates whether the user is closing a particular window or quitting the application.

Listing 8: Prompting the user to save a changed movie

askSaveChanges
public boolean askSaveChanges (String actionString) {
   Object[] options = {"Save", "Cancel", "Don\u00A9t Save"};
   boolean windowClosed = false;
   
   int result = JOptionPane.showOptionDialog(this,
                        "Do you want to save the changes you made "
                        + " to the document"
                        + "\n\u201C" + baseName + "\u201D?",
                        "Save changes before " + actionString,
                        JOptionPane.YES_NO_CANCEL_OPTION,
                        JOptionPane.WARNING_MESSAGE,
                        null,
                        options,
                        options[0]);

   if (result == SAVE_RESULT) {
      // save the changes and close the window
      save();
      windowClosed = true;
   } else if (result == CANCEL_RESULT) {
      // don't save changes and don't close the window
      windowClosed = false;
   } else if (result == DONTSAVE_RESULT) {
      // don't save changes but do close the window
      windowClosed = true;
   }
   
   if (windowClosed)
      dispose();
   
   return(windowClosed);
}

The showOptionDialog method returns the index in the options array of the selected button. JaVeez defines these constants to make our code easier to read:

private static final int SAVE_RESULT       = 0;
private static final int CANCEL_RESULT     = 1;
private static final int DONTSAVE_RESULT   = 2;

When it exits, the askSaveChanges method returns a boolean value that indicates whether it actually closed the window it was asked to close. Our windowClosing override method ignores that value, but we'll need to use it when we call askSaveChanges during the process of quitting the application. More on that later.

One final point: what's with the gibberish in a couple of the strings in askSaveChanges? Strings in Java are Unicode strings, and using the standard keyboard entries for the apostrophe and the left and right double quotation marks would yield a less satisfactory dialog box. Compare Figure 1 to Figure 2:


Figure 2: The Save Changes dialog box of JaVeez (bad characters)

The Unicode value "\u00A9" gives us the nicer-looking apostrophe in the word "Don't", and the values "\u201C" and "\u201D" give us the nicer-looking quotation marks around the filename (though it's hard to tell that in the font used in this dialog box).

Saving a Changed Movie

The askSaveChanges method (Listing 8) calls JaVeez' save method if the user elects to save the changes to the window. It's easy to implement that method, using the movie's updateResource method. Listing 9 shows our save method.

Listing 9: Saving a movie

save
public void save () {
   try {
      if (omf == null) {
         saveAs();
      } else {
         m.updateResource(omf, 
                        StdQTConstants.movieInDataForkResID, null);
         m.clearChanged();
      }
   } catch (QTException err) {
      err.printStackTrace();
   }
}

If no movie file is yet associated with the movie window, we call the saveAs method instead of updateResource. This has the effect of displaying the file-saving dialog box, which allows the user to specify a filename for the movie.

Saving a Movie into a New File

When the user selects the "Save As..." menu item in the File menu (or, as we just saw, elects to save a movie that has not yet been associated with a movie file), we need to elicit a location for the new movie file and then save the movie into that file. In JaVeez, we'll execute this line of code:

fd = new FileDialog(this, "Save: JaVeez", FileDialog.SAVE);

The AWT FileDialog class, when passed the FileDialog.SAVE parameter, displays a dialog box like the one shown in Figure 3.


Figure 3: The file-saving dialog box displayed by AWT

This is one of the few cases where the AWT dialog box is preferable to the corresponding Swing dialog box, which is shown in Figure 4.


Figure 4: The file-saving dialog box displayed by Swing

As you can see, the AWT box more closely resembles the native file-saving dialog box displayed by Carbon or Cocoa applications running on Mac OS X.

Once the user has selected a new location and file name, we can save the movie into that file by executing the flatten method on the movie, as shown in Listing 10.

Listing 10: Saving a movie into a new file

saveAs
public void saveAs () {
   try {
      if (fd == null)
         fd = new FileDialog(this, "Save: JaVeez", 
                                                      FileDialog.SAVE);
            
         fd.setFile(null);     // clear out the file name
         fd.show();
            
         String dirName = fd.getDirectory();
         String fileName = fd.getFile();
            
         if ((dirName != null) && (fileName != null)) {
            QTFile qtf = new QTFile(dirName + fileName);
      
            m.flatten(
StdQTConstants.flattenForceMovieResourceBeforeMovieData,
               qtf, 
               StdQTConstants.kMoviePlayer,
               IOConstants.smSystemScript, 
               StdQTConstants.createMovieFileDeleteCurFile, 
               StdQTConstants.movieInDataForkResID, 
               qtf.getName());
      
            // close the connection to the current movie file
            if (omf != null) {
               omf.close();
               omf = null;
            }
      
            // now open the new file in the current window
            createNewMovieFromFile(qtf.getPath(), true);
         }
       
      } catch (QTException err) {
         if (err.errorCode() != Errors.userCanceledErr)
            err.printStackTrace();
      }
}

The standard behavior of a "Save As..." operation is that the new movie will replace the existing movie in the existing movie window. Accordingly, saveAs closes the connection to the existing movie file (by calling its close method) and calls createNewMovieFromFile with the full pathname of the new movie file. By passing true as the second parameter, we instruct createNewMovieFromFile not to reposition the window.

Quitting the Application

When the user decides to quit JaVeez (typically by selecting the Quit item in the Application menu or -- on Windows -- the Exit item in the File menu), we want to loop through all open movie windows and call the askSaveChanges method on each window that's been edited since it was opened or last saved. To my knowledge, AWT does not provide a way to find just the open movie windows. It does provide the getFrames method in the Frame class, but my experience is that the array returned by getFrames includes an entry for all frames ever created by the application (not just the ones that are currently open and visible). Nonetheless, we can use the getFrames method to good effect by simply ignoring any frames that are not visible or do not belong to the JaVeez class. Listing 11 shows our implementation of attemptQuit.

Listing 11: Handling a request to quit the application

attemptQuit
public boolean attemptQuit () {
   // try to close all open document windows; if none is kept open, quit
   Frame[] frames = Frame.getFrames();
   boolean didClose = true;
   String quitString = resBundle.getString("quitText");
   
   // try all open movie windows other than the current one
   for (int i = 0; i < frames.length; i++) {
      if ((frames[i] != this) && (frames[i] != null) && 
            (frames[i].getClass().getName() == "JaVeez") && 
            (frames[i].isVisible())) {
         try {
            JaVeez jvz = (JaVeez)(frames[i]);
          
            if (jvz == null)
               continue;

         if (jvz.isDirty())
               didClose = jvz.askSaveChanges(quitString);
          
            if (!didClose)
               return(false);
         } catch (Exception err) {
             err.printStackTrace();
         }
      }
   }
   
   if (this.isDirty())
      didClose = this.askSaveChanges(quitString);
   
   if (didClose)
      goAway();
   
   return(didClose);
}

You'll notice that we postpone handling the frontmost window until all other movie windows have been handled. I suspect it's not a good thing to dispose of an object while it's still executing one of its methods.

If none of the open movie windows has been edited or the user does not cancel the closing of any of them, then attemptQuit executes the goAway method. This method simply closes the application's connection to QuickTime and then exits (Listing 12).

Listing 12: Quitting the application

goAway
public void goAway () {
   try {
      if (qtc != null)
         qtc.setMovieController(null);
   } catch (QTException err) {
   }
        
   QTSession.close();
   System.exit(0);   
}

Application Objects

Let's finish up by considering how to integrate our application with the native Mac OS X operating environment so that it acts as much as possible like a well-written Carbon or Cocoa application. Java 1.4.1 for Mac OS X includes the new package com.apple.eawt, which contains the Application class. This class provides methods that allow us to fine-tune the appearance and behavior of the Application menu, respond to items in that menu, and handle the basic application-related Apple events. By creating an application object and attaching to it an application listener, we can (for instance) respond appropriately when the user drags some movie files onto our application's icon in the Finder.

To take advantage of these enhancements, we need to import the appropriate packages:

import com.apple.eawt.*;

Creating an Application Object

You'll recall from the previous article that JaVeez declares the static variable fApplication:

private static Application fApplication = null;

In its constructor method, JaVeez calls the createApplicationObject method, which is defined in Listing 13. After ensuring that fApplication hasn't already been set to a non-null value and that the application is running on Mac OS X, createApplicationObject retrieves the application object, disables the Preference menu item, and installs an application listener. The listener implements methods that handle some of the items in the Application menu (About, Preferences, and Quit); these methods are also called when certain Apple events are targeted at the application.

Listing 13: Creating an Application object

createApplicationObject
pubic void createApplicationObject () {       
   if ((fApplication == null) && 
                     QTSession.isCurrentOS(QTSession.kMacOSX))) {
      fApplication = Application.getApplication();
      fApplication.setEnabledPreferencesMenu(false);
      fApplication.addApplicationListener(new 
                              com.apple.eawt.ApplicationAdapter() {

         public void handleAbout (ApplicationEvent e) {
            about();
            e.setHandled(true);
         }
      
         public void handleOpenApplication (ApplicationEvent e) {
         }
      
         public void handleOpenFile (ApplicationEvent e) {
          launchedFromDrop = true;
          
          JaVeez jvz = new JaVeez(e.getFilename());   
          jvz.createNewMovieFromFile(e.getFilename(), false);
          jvz.toFront();
         }
      
         public void handlePreferences (ApplicationEvent e) {
          preferences();
          e.setHandled(true);
         }
      
         public void handlePrintFile (ApplicationEvent e) {
         }
      
         public void handleQuit (ApplicationEvent e) {
          boolean allWindowsClosed;
          
          allWindowsClosed = attemptQuit();
          e.setHandled(allWindowsClosed);
         }
      });
   }
}

These handlers can call the setHandled method to indicate whether the event was handled. The most interesting case is the handleQuit method, which (as you can see) returns the value it gets from the attemptQuit method. This allows our application to prevent itself from quitting if the user cancels the save-or-discard-changes query.

Handling Dragged Files

In order for the user to be able to drop QuickTime movie files onto our application's icon, we need to set the document types supported by JaVeez. We do this by selecting the JaVeez target in the project window and then choosing the "Get Info" item in the Project menu. Click the "+" button at the lower-left corner of the Info window to add the desired file types. As you can see in Figure 5, JaVeez supports QuickTime movie files and Flash files.


Figure 5: The supported document types

Showing the About Box

We have to tackle one final task, namely displaying our application's About box. We want this to look as much as possible like the About box of QTShell, shown in Figure 6.


Figure 6: The About box of QTShell

Once again, Swing provides a ridiculously easy way to do this, using the JOptionPane class (which we used earlier to display the "Save Changes" dialog box). Here, we want to use the showMessageDialog method, as shown in Listing 14.

Listing 14: Displaying the application About box

about
public void about () {
   ImageIcon penguinIcon = new ImageIcon
                        (JaVeez.class.getResource("penguin.jpeg"));
   
   JOptionPane.showMessageDialog((Component)null,
                     "A QuickTime movie player application"
                     + "\nbuilt with QuickTime for Java. \n \n" 
                     + "\u00A92003 by Tim Monroe",
                     "About JaVeez",
                     JOptionPane.INFORMATION_MESSAGE,
                     penguinIcon);
}

When we call the about method, showMessageDialog displays the dialog box shown in Figure 7. This is remarkably close to the target About box shown in Figure 6.


Figure 7: The About box of JaVeez

The parameters passed to showMessageDialog should be fairly obvious. The first parameter is the parent frame; in this case we pass null so that the message pane is embedded in a default frame that is centered on the main screen. The second parameter is the message string; notice that we can embed the newline escape character "\n" into the message string, as well as the escape sequence "\u00A9" (which is the Unicode value of the copyright symbol "(c)"). The third parameter is the title of the enclosing frame. The fourth parameter is ignored since we are passing an icon as the fifth parameter. Here we are using the standard penguin image, which we added to our project. We need to call the getResource method to load that image from the application bundle.

Conclusion

In this article, we've seen how to extend the basic Java application we built last time to handle movie editing and document-related operations. The Movie and MovieController classes in QuickTime for Java provide nearly all the methods we need to handle these tasks. And the Application class added in J2SE 1.4.1 for Mac OS X makes it particularly easy to open files dropped onto the application icon, display an About box, and handle application quitting.

JaVeez is now pretty much feature-complete. You may be puzzled, however, that we've focused almost entirely on building an application tailored for Mac OS X. After all, a large part of the allure of using Java to build applications and applets is their ability to run unchanged on multiple platforms. (The Java programmer's mantra is of course "write once, run anywhere".) So what about Windows? Will JaVeez run unchanged on Windows computers that have QuickTime installed? In a word: no. But the changes required to get JaVeez to run on Windows are indeed quite minimal. We'll take a look at those changes, and perhaps also at a few simple enhancements to JaVeez, in the next article.

Acknowledgements

Thanks are due once again to Anant Sonone, Tom Maremaa, Chris Adamson, and Daniel H. Steinberg for reviewing this article and providing some helpful comments.


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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Parallels Desktop 13.2.0 - Run Windows a...
Parallels allows you to run Windows and Mac applications side by side. Choose your view to make Windows invisible while still using its applications, or keep the familiar Windows background and... Read more
VueScan 9.5.92 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
iFinance 4.3.4 - Comprehensively manage...
iFinance allows you to keep track of your income and spending -- from your lunchbreak coffee to your new car -- in the most convenient and fastest way. Clearly arranged transaction lists of all your... Read more
jAlbum Pro 15.0 - Organize your digital...
jAlbum Pro has all the features you love in jAlbum, but comes with a commercial license. You can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly... Read more
jAlbum 15.0 - Create custom photo galler...
With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly, with pro results - Simply drag and drop photos into groups, choose a design... Read more
Duet 1.6.9.3 - Use your iPad as an exter...
Duet is the first app that allows you to use your iDevice as an extra display for your Mac using the Lightning or 30-pin cable. Note: This app requires a $14.99 iOS companion app. Version 1.6.9.3:... Read more
Duet 1.6.9.3 - Use your iPad as an exter...
Duet is the first app that allows you to use your iDevice as an extra display for your Mac using the Lightning or 30-pin cable. Note: This app requires a $14.99 iOS companion app. Version 1.6.9.3:... Read more
iExplorer 4.1.10 - View and transfer fil...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
iExplorer 4.1.10 - View and transfer fil...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
Adobe InCopy CC 2018 13.0.1.207 - Create...
InCopy CC 2018 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous InCopy customer). Adobe InCopy CC 2018, ideal for large team projects... Read more

Latest Forum Discussions

See All

Mighty Battles guide - how to build a so...
Mighty Battles, the latest title from Hothead Games, is set to take the App Store by storm. The game puts a welcome twist on lane battlers, adding FPS elements to spice things up a bit. You'll collect cards to put your own military unit to gether,... | Read more »
Rules of Survival guide - how to be the...
The PUBG craze makes its way to mobile, with more and more battle royale games debuting on iOS and Android. Rules of Survival joins the ranks of mobile PUBG-likes, offering a classic battle royale experiences that doesn't vary too much from its... | Read more »
The best new games we played this week -...
The weekend is upon us friends, and it's time to take a look back and reflect on all of the wonderful games we've played over the past few days. This week was jam packed with new releases. There were some big, long awaited launches, some fun... | Read more »
Lineage II: Revolution guide - tips and...
At long last, Lineage II: Revolution has now come to western shores, bring Netmarble's sweeping MMORPG to mobile devices. It's an addictive, epic experience, but some of the systems in the game can be a bit overwhelming. Here are a few tips to help... | Read more »
A Boy and His Blob (Games)
A Boy and His Blob 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: | Read more »
Fight terrible monsters and collect epic...
Released on Western markets early last month, Dragon Project, created by Japanese developer COLOPL, brings epic monster hunting action to mobile for the very first time. Collect a huge array of weapons and armor, and join up with friends to fight... | Read more »
I Am The Hero (Games)
I Am The Hero 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: I Am The Hero is a pixel art, beat 'em up, fighting game that tells the story of a "Hero" with a glorious but mysterious past.... | Read more »
Kauldron (Music)
Kauldron 1.0 Device: iOS Universal Category: Music Price: $3.99, Version: 1.0 (iTunes) Description: Kauldron is our warmest sounding, punchiest synth yet! A completely new modeling technology, combined with carefully designed... | Read more »
Lineage II: Revolution is mobile’s bigge...
NCSoft’s hit fantasy MMORPG series has just made the leap to mobile with the help of Netmarble in Lineage II: Revolution. With over 1.5 million players having already pre-registered ahead of the game’s launch, Revolution hit the app stores... | Read more »
Swing skilfully in new physics-based pla...
Sometimes it’s the most difficult of obstacles that can be the most rewarding. One game hoping to prove this is OCMO, the new tough but fair platformer from developers Team Ocmo. Primed to set every speedrunner’s pulse racing, as an otherworldly... | Read more »

Price Scanner via MacPrices.net

Save up to $180 with Apple Certified Refurbis...
Apple has Certified Refurbished 2017 13″ MacBook Airs available starting at $849. An Apple one-year warranty is included with each MacBook, and shipping is free: – 13″ 1.8GHz/8GB/128GB MacBook Air (... Read more
Black Friday deals on Apple Macs now live at...
Amazon has MacBook Pros, MacBook Airs, MacBooks, and iMacs on sale for up to $200 off MSRP for Black Friday week. Shipping is free. Note that stock of some Macs may come and go during the week, so... Read more
Black Friday pricing on Macs and iPads now av...
B&H Photo has lowered prices on many Macs, iPads, and iPad Pros as part of their Black Friday week sale. Save up to $200 on MacBooks and iMacs and up to $150 on iPads. B&H charges sales tax... Read more
Best Apple iPad deals this weekend, up to $80...
Apple resellers are offering 9.7″ iPads and 10.5″ iPad Pros for up to $80 off MSRP this weekend as part of their early Holiday and Black Friday sales: Adorama is offering new 2017 9.7″ 32GB WiFi... Read more
Early Black Friday sale: Apple iMacs for up t...
B&H Photo has 27-inch iMacs in stock and on sale for up $130-$150 off MSRP including free shipping. B&H charges sales tax in NY & NJ only: – 27″ 3.8GHz iMac (MNED2LL/A): $2149 $150 off... Read more
Apple restocks refurbished Mac minis starting...
Apple has restocked Certified Refurbished Mac minis starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: – 1.4GHz Mac mini: $419 $80 off MSRP – 2.6GHz Mac... Read more
Save on 12″ MacBooks, Apple refurbished model...
Apple has Certified Refurbished 2017 12″ Retina MacBooks available for $200-$240 off the cost of new models. Apple will include a standard one-year warranty with each MacBook, and shipping is free.... Read more
Early Holiday sale: 12″ iPad Pros for up to $...
B&H Photo has 12″ iPad Pros on sale today for up to $130 off MSRP. Shipping is free, and B&H collects no sales tax outside NY & NJ: – 12″ 64GB WiFi iPad Pro: $749, save $50 – 12″ 256GB... Read more
Holiday sale prices on Apple 13″ MacBook Pros...
B&H Photo has 2017 13″ MacBook Pros in stock today and on sale for $100-$150 off MSRP, each including free shipping plus NY & NJ sales tax only: – 13-inch 2.3GHz/128GB Space Gray MacBook Pro... Read more
Sale: 13″ MacBook Airs starting at $899, $100...
B&H Photo has 2017 13″ MacBook Airs on sale today for $100 off MSRP including free shipping. B&H charges NY & NJ sales tax only: – 13″ 1.8GHz/128GB MacBook Air (MQD32LL/A): $899, $100 off... Read more

Jobs Board

*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Product Manager - *Apple* Pay on the *Appl...
Job Summary Apple is looking for a talented product manager to drive the expansion of Apple Pay on the Apple Online Store. This position includes a unique Read more
*Apple* Pro/Consumer Apps Support Engineer -...
…exemplify AppleCare's expert technical support paired with exceptional customer service for Apple 's software apps. This person is a problem solver, who understands Read more
Partner Marketing Manager, *Apple* Pay - Ap...
Job Summary The Apple Pay partner marketing team is looking for a Marketing Manager to develop and drive US programs. The right candidate will be passionate about Read more
*Apple* Solution Consultant - Apple (United...
# Apple Solution Consultant - Rochester, MN Job Number: 113037950 Rochester, MN, Minnesota, United States Posted: 19-Sep-2017 Weekly Hours: 40.00 **Job Summary** Are Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.