Cover | Table of Contents | Colophon
http://java.sun.com/j2se/1.4.2/download.html.
Now you must install and/or update QuickTime.http://quicktime.apple.com/.
What's perhaps more common is that you have
QuickTime, but you don't have QuickTime for Java,
which is not installed by default.
http://java.sun.com/j2se/1.4.2/download.html.
Now you must install and/or update QuickTime.http://quicktime.apple.com/.
What's perhaps more common is that you have
QuickTime, but you don't have QuickTime for Java,
which is not installed by default.
http://lists.apple.com/ are a great source of
information, particularly quicktime-java,
quicktime-users (authoring), and
quicktime-api (native programming).
java-dev is also helpful for figuring out issues
with Mac OS X's Java implementation.<object>
tag, which wraps an
<embed>, as shown in Example 1-1.<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" width="160" height="136" codebase="http://www.apple.com/qtactivex/qtplugin.cab"> <param name="src" VALUE="buhbuhbuh.mov"/> <param name="autoplay" VALUE="true"/> <param name="loop" VALUE="true"/> <param name="controller" value="true"/> <embed src="buhbuhbuh.mov" width="160" height="136" scale="tofit" controller="true" autoplay="true" loop="true" pluginspage="http://www.apple.com/quicktime/download/"/> </object>
height, width, and
src are the only ones that are actually required.
Because I've chosen to include a controller widget,
I add 16 to the height parameter and use the
scale parameter with the value
tofit.
<?xml version="1.0"?> <?quicktime type="application/x-qtpreflight"?> <qtpreflight> <component type="null" subtype="qtj "/> </qtpreflight>
imdc" (short for
image decompressor) and
subtype
"mp4v". QuickTime for Java is something of a
special case, so it gets type "package com.oreilly.qtjnotebook.ch01;
import quicktime.QTSession;
import quicktime.util.QTBuild;
public class QTVersionCheck {
public static void main (String[ ] args) {
try {
QTSession.open( );
System.out.println ("QT version: " +
QTSession.getMajorVersion( ) +
"." +
QTSession.getMinorVersion( ));
System.out.println ("QTJ version: " +
QTBuild.getVersion( ) +
"." +
QTBuild.getSubVersion( ));
QTSession.close( );
} catch (Exception e) {
e.printStackTrace( );
}
}
}
javac, bad things happen:cadamson% javac src/com/oreilly/qtjnotebook/ch01/QTVersionCheck.java
src/com/oreilly/qtjnotebook/ch01/QTVersionCheck.java:3:
package quicktime does not exist
import quicktime.QTSession;
^
src/com/oreilly/qtjnotebook/ch01/QTVersionCheck.java:4:
package quicktime.util does not exist
import quicktime.util.QTBuild;
^
src/com/oreilly/qtjnotebook/ch01/QTVersionCheck.java:10:
cannot resolve symbol
symbol : variable QTSession
location: class com.oreilly.qtjnotebook.ch01.QTVersionCheck
QTSession.open( );
^
QTSession.open( ) gives QuickTime an opportunity
to initialize itself, and it must be made before any other QTJ call,
or you'll get an exception. Similarly, you must call
QTSession.close( ) when you're
done with QuickTime to give it a chance to clean up.QTSession.open( ) as early as possible and
QTSession.close( ) as late as possible. The former
is easy enough to do: just put it in your
application's entry point or even in a static
initializer so that it precedes main( ). On the
other hand, ensuring that you call QTSession.close(
) gracefully is trickier, because your user could quit your
application with a menu item you provide, a Ctrl-C, a Cmd-Q (on Mac),
or (heaven forbid) a kill -9
your-pid from the command line. Ideally,
you'd like to have a fighting chance of properly
closing QuickTime in as many cases as possible.QTSession.close() in a Java shutdown hook, which will get called as
the JVM goes away. There are no guarantees, but it's
better than nothing.QTSession beyond calling this class.package com.oreilly.qtjnotebook.ch01;
import quicktime.*;
public class QTSessionCheck {
private Thread shutdownHook;
private static QTSessionCheck instance;
private QTSessionCheck( ) throws QTException {
super( );
// init
QTSession.open( );
// create shutdown handler
shutdownHook = new Thread( ) {
public void run( ) {
QTSession.close( );
}
};
Runtime.getRuntime( ).addShutdownHook(shutdownHook);
}
private static QTSessionCheck getInstance( ) throws QTException {
if (instance = = null)
instance = new QTSessionCheck( );
return instance;
}
public static void check( ) throws QTException {
// gets instance. if a new one needs to be created,
// it calls QTSession.open( ) and creates a shutdown hook
// to call QTSession.close( )
getInstance( );
}
}package com.oreilly.qtjnotebook.ch01;
import quicktime.*;
import quicktime.app.time.*;
import quicktime.io.*;
import quicktime.std.*;
import quicktime.std.movies.*;
import java.io.*;
public class TrivialAudioPlayer {
public static void main (String[ ] args) {
if (args.length != 1) {
System.out.println (
"Usage: TrivialAudioPlayer <file>");
return;
}
try {
QTSessionCheck.check( );
QTFile f = new QTFile (new File(args[0]));
OpenMovieFile omf = OpenMovieFile.asRead(f);
Movie movie = Movie.fromFile (omf);
TaskAllMovies.addMovieAndStart( );
movie.start( );
} catch (QTException e) {
e.printStackTrace( );
}
}
}
ant buildfile, the classes
will be in the classes directory, so
you'll need to extend your classpath into there:cadamson% java -classpath classes com.oreilly.qtjnotebook.ch01.TrivialAudioPlayer ~/mp3testing/Breakaway.mp3
QTSessionCheck from the previous task to set up
the QuickTime session.Movie
object, getting it into the Java display space, and adding more
sophisticated controls so that your user (or just your code) can know
what's happening inside a playing movie and take
control.Frame.package com.oreilly.qtjnotebook.ch02;
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
import java.awt.*;
public class BasicQTPlayer extends Frame {
public BasicQTPlayer (Movie m) throws QTException {
super ("Basic QT Player");
QTComponent qc = QTFactory.makeQTComponent (m);
Component c = qc.asComponent( );
add (c);
}
public static void main (String[ ] args) {
try {
QTSessionCheck.check( );
QTFile file =
QTFile.standardGetFilePreview (
QTFile.kStandardQTFileTypes);
OpenMovieFile omFile = OpenMovieFile.asRead (file);
Movie m = Movie.fromFile (omFile);
Frame f = new BasicQTPlayer (m);
f.pack( );
f.setVisible(true);
m.start( );
} catch (Exception e) {
e.printStackTrace( );
}
}
}
QTJava.zip
in the classpath; this is the Mac OS X
version):Frame.package com.oreilly.qtjnotebook.ch02;
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
import java.awt.*;
public class BasicQTPlayer extends Frame {
public BasicQTPlayer (Movie m) throws QTException {
super ("Basic QT Player");
QTComponent qc = QTFactory.makeQTComponent (m);
Component c = qc.asComponent( );
add (c);
}
public static void main (String[ ] args) {
try {
QTSessionCheck.check( );
QTFile file =
QTFile.standardGetFilePreview (
QTFile.kStandardQTFileTypes);
OpenMovieFile omFile = OpenMovieFile.asRead (file);
Movie m = Movie.fromFile (omFile);
Frame f = new BasicQTPlayer (m);
f.pack( );
f.setVisible(true);
m.start( );
} catch (Exception e) {
e.printStackTrace( );
}
}
}
QTJava.zip
in the classpath; this is the Mac OS X
version):cadamson% javac -d classes -classpath src:/System/Library/Java/Extensions/QTJava.zip src/com/oreilly/qtjnotebook/ch02/BasicQTPlayer.java
cadamson% java -classpath classes com.oreilly.qtjnotebook.ch02.BasicQTPlayer
MovieController
to get the standard QuickTime controller
bar, an on-screen control widget that provides a play/pause button, a
volume control, and the movie position control (typically called a
"scrubber" in QuickTime parlance).main() is exactly the same as before, while the constructor adds
one new line and changes another, as seen in Example 2-2.public class BasicQTController extends Frame {
public BasicQTController (Movie m) throws QTException {
super ("Basic QT Controller");
MovieController mc = new MovieController(m);
QTComponent qc = QTFactory.makeQTComponent (mc);
Component c = qc.asComponent( );
add (c);
pack( );
}
QTFactory to make
a QTComponent from the Movie,
the program creates a MovieController object from
the Movie and asks the
QTFactory to make a QTComponent
from that. This eliminates the need for main( ) to
call start( ), because the user can start and stop
the movie from the play/pause button on the control bar.JComponent. QuickTime for Java can provide one.BasicQTPlayer that does everything with
Swing equivalents
(JFrame instead of Frame,
JComponent instead of
Component, etc.).package com.oreilly.qtjnotebook.ch02;
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
import java.awt.*;
import javax.swing.*;
public class BasicSwingQTPlayer extends JFrame {
public BasicSwingQTPlayer (Movie m) throws QTException {
super ("Basic Swing QT Player");
MoviePlayer mp = new MoviePlayer (m);
QTJComponent qc = QTFactory.makeQTJComponent (mp);
JComponent jc = qc.asJComponent( );
getContentPane( ).add (jc);
pack( );
}
public static void main (String[ ] args) {
try {
QTSessionCheck.check( );
QTFile file =
QTFile.standardGetFilePreview (
QTFile.kStandardQTFileTypes);
OpenMovieFile omFile = OpenMovieFile.asRead (file);
Movie m = Movie.fromFile (omFile);
JFrame f = new BasicSwingQTPlayer (m);
f.pack( );
f.setVisible(true);
m.start( );
} catch (Exception e) {
e.printStackTrace( );
}
}
}
MovieController. One example of this is
Movie.start( ). You can programmatically issue
many more commands, some of which you can't issue
with the default control.BasicQTButtons.java. The main() is exactly the same as BasicQTPlayer,
but the constructor has extra work to create some control buttons,
and an actionPerformed( ) method implements
AWT's ActionListener.package com.oreilly.qtjnotebook.ch02;
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
import java.awt.*;
import java.awt.event.*;
public class BasicQTButtons extends Frame
implements ActionListener {
Button revButton,
stopButton,
startButton,
fwdButton;
Movie theMovie;
public BasicQTButtons (Movie m) throws QTException {
super ("Basic QT Player");
theMovie = m;
QTComponent qc = QTFactory.makeQTComponent (m);
Component c = qc.asComponent( );
setLayout (new BorderLayout( ));
add (c, BorderLayout.CENTER);
Panel buttons = new Panel( );
revButton = new Button("<");
revButton.addActionListener (this);
stopButton = new Button ("0");
stopButton.addActionListener (this);
startButton = new Button ("1");
startButton.addActionListener (this);
fwdButton = new Button (">");
fwdButton.addActionListener (this);
buttons.add (revButton);
buttons.add (stopButton);
buttons.add (startButton);
buttons.add (fwdButton);
add (buttons, BorderLayout.SOUTH);
pack( );
}
public void actionPerformed (ActionEvent e) {
try {
if (e.getSource( ) = = revButton)
theMovie.setRate (theMovie.getRate( ) - 0.5f);
else if (e.getSource( ) = = stopButton)
theMovie.stop( );
else if (e.getSource( ) = = startButton)
theMovie.start( );
else if (e.getSource( ) = = fwdButton)
theMovie.setRate (theMovie.getRate( ) + 0.5f);
} catch (QTException qte) {
qte.printStackTrace( );
}
}
public static void main (String[ ] args) {
try {
QTSessionCheck.check( );
QTFile file =
QTFile.standardGetFilePreview (
QTFile.kStandardQTFileTypes);
OpenMovieFile omFile = OpenMovieFile.asRead (file);
Movie m = Movie.fromFile (omFile);
Frame f = new BasicQTButtons (m);
f.pack( );
f.setVisible(true);
m.start( );
} catch (Exception e) {
e.printStackTrace( );
}
}
}BasicQTTimeDisplay code extends the
BasicQTController by adding a
Label
to the bottom of the
Frame
. A
Swing Timer calls
actionPerformed( ) every 250 milliseconds, and
this method checks the current time of the movie and resets the
label.package com.oreilly.qtjnotebook.ch02;
import quicktime.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.io.*;
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
import java.awt.*;
import java.awt.event.*;
public class BasicQTTimeDisplay extends Frame
implements ActionListener {
Movie theMovie;
Label timeLabel;
public BasicQTTimeDisplay (Movie m) throws QTException {
super ("Basic QT Controller");
theMovie = m;
MovieController mc = new MovieController(m);
QTComponent qc = QTFactory.makeQTComponent (mc);
Component c = qc.asComponent( );
setLayout (new BorderLayout( ));
add (c, BorderLayout.CENTER);
timeLabel = new Label ("-:--", Label.CENTER);
add (timeLabel, BorderLayout.SOUTH);
javax.swing.Timer timer =
new javax.swing.Timer (250, this);
timer.start( );
pack( );
}
public void actionPerformed (ActionEvent e) {
if (theMovie = = null)
return;
try {
int totalSeconds = theMovie.getTime( ) /
theMovie.getTimeScale( );
int seconds = totalSeconds % 60;
int minutes = totalSeconds / 60;
String secString = (seconds > 9) ?
Integer.toString (seconds) :
("0" + Integer.toString (seconds));
String minString = Integer.toString (minutes);
timeLabel.setText (minString + ":" + secString);
} catch (QTException qte) {
qte.printStackTrace( );
}
}
public static void main (String[ ] args) {
try {
QTSessionCheck.check( );
QTFile file =
QTFile.standardGetFilePreview (
QTFile.kStandardQTFileTypes);
OpenMovieFile omFile = OpenMovieFile.asRead (file);
Movie m = Movie.fromFile (omFile);
Frame f = new BasicQTTimeDisplay (m);
f.pack( );
f.setVisible(true);
m.start( );
} catch (Exception e) {
e.printStackTrace( );
}
}
}BasicQTButtons
program. The new version,
BasicQTCallback, asks to be notified when the rate
changes. When the rate is 0, it will disable the stop button (labeled
"0"), and when the rate is 1, it
disables the play button (labeled
"1"). For space,
I'll list only the lines that have changed from
BasicQTButtons.quicktime.std.clocks, which is where callbacks are
defined, and quicktime.std, whose
StdQTConstants
provides constants to specify the
callbacks' behavior:import quicktime.std.*; import quicktime.std.clocks.*;
Movie
to an inner class' constructor:MyQTCallback myCallback = new MyQTCallback (m);
Movie argument and an
execute( ) method:class MyQTCallback extends RateCallBack {
public MyQTCallback (Movie m) throws QTException {
super (m.getTimeBase( ),
0,
StdQTConstants.triggerRateChange);
callMeWhen( );
}
public void execute( ) {
if (rateWhenCalled = = 0.0) {
startButton.setEnabled (true);
stopButton.setEnabled (false);
} else if (rateWhenCalled = = 1.0) {
startButton.setEnabled (false);
stopButton.setEnabled (true);
}
// indicate that we want to be called again
try {
callMeWhen( );
} catch (QTException qte) {
qte.printStackTrace( );
} } }
Movie.nextFrame( )
method. Indeed, a Movie might not have a video
track at all, if it represents an MP3 or some other audio-only media.
So, finding the next frame requires being a little smarter about
looking inside the Movie's
structure.BasicQTButtons
code. In this example, the implementations of the forward and back
buttons are altered so that instead of changing the play rate, they
change the current time to be the next frame before or after the
current time. For space, this example shows only the changes from the
original BasicQTButtons.quicktime.std to use
StdQTConstants
, and
quicktime.std.clocks for some time-related
classes. It also adds an instance variable
visualTrack, which is found with the following
call:theMovie = m;
// find video track
visualTrack =
m.getIndTrackType (1,
StdQTConstants.visualMediaCharacteristic,
StdQTConstants.movieTrackCharacteristic);
revButton and fwdButton are
disabled later in the constructor.actionPerformed() does the frame-step logic when the
revButton or fwdButton is
clicked:if (e.getSource( ) = = revButton) {
TimeInfo ti =
visualTrack.getNextInterestingTime (
StdQTConstants.nextTimeMediaSample,
theMovie.getTime( ),
-1);
theMovie.setTime (new TimeRecord (theMovie.getTimeScale( ),
ti.time));
}
else if (e.getSource( ) = = stopButton)
theMovie.stop( );
else if (e.getSource( ) = = startButton)
theMovie.start( );
else if (e.getSource( ) = = fwdButton) {
TimeInfo ti =
visualTrack.getNextInterestingTime (
StdQTConstants.nextTimeMediaSample,
theMovie.getTime( ),
1);
theMovie.setTime (new TimeRecord (theMovie.getTimeScale( ),
ti.time));
}BasicQTURLController.java. This is a significant
rethinking of the earlier BasicQTController class.
This example creates a GUI from an empty
"dummy" movie, then asks the user
for a URL, gets a Movie from that, and replaces
the dummy movie. By getting the movie last, it tempts fate to see how
well QTJ can deal with loading a movie over the network.package com.oreilly.qtjnotebook.ch02;
import quicktime.*;
import quicktime.std.*;
import quicktime.app.view.*;
import quicktime.std.movies.*;
import quicktime.std.movies.media.*;
import quicktime.io.*;
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
import java.awt.*;
public class BasicQTURLController extends Frame {
QTComponent qc;
public BasicQTURLController ( ) throws QTException {
super ("Basic QT DataRef/Controller");
Movie dummyMovie = new Movie( );
qc = QTFactory.makeQTComponent (dummyMovie);
Component c = qc.asComponent( );
add (c);
pack( );
}
public static void main (String[ ] args) {
try {
QTSessionCheck.check( );
BasicQTURLController f =
new BasicQTURLController ( );
String url =
javax.swing.JOptionPane.showInputDialog (f,
"Enter URL");
DataRef dr = new DataRef (url);
Movie m = Movie.fromDataRef (dr,
StdQTConstants.newMovieActive);
MovieController mc = new MovieController (m);
f.qc.setMovieController (mc);
f.setVisible(true);
f.pack( );
m.prePreroll(0, 1.0f);
m.preroll(0, 1.0f);
m.start( );
} catch (Exception e) {
e.printStackTrace( );
}
}
}
Movie, and plays it, without getting any
kind of GUI.package com.oreilly.qtjnotebook.ch01;
import quicktime.*;
import quicktime.app.time.*;
import quicktime.io.*;
import quicktime.std.*;
import quicktime.std.movies.*;
import java.io.*;
public class BasicAudioPlayer {
public static void main (String[ ] args) {
if (args.length != 1) {
System.out.println (
"Usage: BasicAudioPlayer <file>");
return;
}
try {
QTSessionCheck.check( );
QTFile f = new QTFile (new File(args[0]));
OpenMovieFile omf = OpenMovieFile.asRead(f);
Movie movie = Movie.fromFile (omf);
TaskAllMovies.addMovieAndStart( );
movie.start( );
} catch (QTException e) {
e.printStackTrace( );
}
}
}
Movie
class has a
task( ) method that could be called to give time
to a specific movie, and a static taskAll(
)
method
that tasks all active movies.MovieController) is used to indicate
where copies and pastes should occur. By shift-clicking, a user can
select a time-range from the current time (indicated by the play
head) to wherever the user shift-clicks (or, if he is dragging,
wherever the mouse is released).BasicQTEditor, shown in Example 3-1, will be the basis for the examples in this
chapter. It offers a single empty movie window (with the ability to
open movies from disk in new windows or to create new empty movie
windows), and an Edit menu with cut, copy, and paste
options.
package com.oreilly.qtjnotebook.ch03;
import quicktime.*;
import quicktime.qd.QDRect;
import quicktime.std.*;
import quicktime.std.movies.*;
import quicktime.app.view.*;
import quicktime.io.*;
import java.awt.*;
import java.awt.event.*;
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
public class BasicQTEditor extends Frame
implements ActionListener {
Component comp;
Movie movie;
MovieController controller;
Menu fileMenu, editMenu;
MenuItem openItem, closeItem, newItem, quitItem;
MenuItem copyItem, cutItem, pasteItem;
static int newFrameX = -1;
static int newFrameY = -1;
static int windowCount = 0;
/** no-arg constructor for "new" movie
*/
public BasicQTEditor ( ) throws QTException {
super ("BasicQTEditor");
setLayout (new BorderLayout( ));
QTSessionCheck.check( );
movie = new Movie(StdQTConstants.newMovieActive);
controller = new MovieController (movie);
controller.enableEditing(true);
doMyLayout( );
}
/** file-based constructor for opening movies
*/
public BasicQTEditor (QTFile file) throws QTException {
super ("BasicQTEditor");
setLayout (new BorderLayout( ));
QTSessionCheck.check( );
OpenMovieFile omf = OpenMovieFile.asRead (file);
movie = Movie.fromFile (omf);
controller = new MovieController (movie);
controller.enableEditing(true);
doMyLayout( );
}
/** gets component from controller, makes menus
*/
private void doMyLayout( ) throws QTException {
// add movie component
QTComponent qtc =
QTFactory.makeQTComponent (controller);
comp = qtc.asComponent( );
add (comp, BorderLayout.CENTER);
// file menu
fileMenu = new Menu ("File");
newItem = new MenuItem ("New Movie");
newItem.addActionListener (this);
fileMenu.add (newItem);
openItem = new MenuItem ("Open Movie...");
openItem.addActionListener (this);
fileMenu.add (openItem);
closeItem = new MenuItem ("Close");
closeItem.addActionListener (this);
fileMenu.add (closeItem);
fileMenu.addSeparator( );
quitItem = new MenuItem ("Quit");
quitItem.addActionListener (this);
fileMenu.add(quitItem);
// edit menu
editMenu = new Menu ("Edit");
copyItem = new MenuItem ("Copy");
copyItem.addActionListener(this);
editMenu.add(copyItem);
cutItem = new MenuItem ("Cut");
cutItem.addActionListener(this);
editMenu.add(cutItem);
pasteItem = new MenuItem ("Paste");
pasteItem.addActionListener(this);
editMenu.add(pasteItem);
// make menu bar
MenuBar bar = new MenuBar( );
bar.add (fileMenu);
bar.add (editMenu);
setMenuBar (bar);
// add close-button handling
addWindowListener (new WindowAdapter( ) {
public void windowClosing (WindowEvent e) {
doClose( );
}
});
}
/** handles menu actions
*/
public void actionPerformed (ActionEvent e) {
Object source = e.getSource( );
try {
if (source = = quitItem) doQuit( );
else if (source = = openItem) doOpen( );
else if (source = = closeItem) doClose( );
else if (source = = newItem) doNew( );
else if (source = = copyItem) doCopy( );
else if (source = = cutItem) doCut( );
else if (source = = pasteItem) doPaste( );
} catch (QTException qte) {
qte.printStackTrace( );
}
}
public void doQuit( ) {
System.exit(0);
}
public void doNew( ) throws QTException {
makeNewAndShow( );
}
public void doOpen( ) throws QTException {
QTFile file =
QTFile.standardGetFilePreview (QTFile.kStandardQTFileTypes);
Frame f = new BasicQTEditor (file);
f.pack( );
if (newFrameX >= 0)
f.setLocation (newFrameX+=16, newFrameY+=16);
f.setVisible(true);
windowCount++;
}
public void doClose( ) {
setVisible(false);
dispose( );
// quit if no windows now showing
if (--windowCount = = 0)
doQuit( );
}
public void doCopy( ) throws QTException {
Movie copied = controller.copy( );
copied.putOnScrap(0);
}
public void doCut( ) throws QTException {
Movie cut = controller.cut( );
cut.putOnScrap(0);
}
public void doPaste( ) throws QTException {
controller.paste( );
pack( );
}
/** Force frame's size to respect movie size
*/
public Dimension getPreferredSize( ) {
System.out.println ("getPreferredSize");
if (controller = = null)
return new Dimension (0,0);
try {
QDRect contRect = controller.getBounds( );
Dimension compDim = comp.getPreferredSize( );
if (contRect.getHeight( ) > compDim.height) {
return new Dimension (contRect.getWidth( ) +
getInsets( ).left +
getInsets( ).right,
contRect.getHeight( ) +
getInsets( ).top +
getInsets( ).bottom);
} else {
return new Dimension (compDim.width +
getInsets( ).left +
getInsets( ).right,
compDim.height +
getInsets( ).top +
getInsets( ).bottom);
}
} catch (QTException qte) {
return new Dimension (0,0);
}
}
/** opens a single new movie window
*/
public static void main (String[ ] args) {
try {
Frame f = makeNewAndShow( );
// note its x, y for future calls
newFrameX = f.getLocation( ).x;
newFrameY = f.getLocation( ).y;
} catch (Exception e) {
e.printStackTrace( );
}
}
/** creates "new" movie frame, packs and shows.
used by main( ) and "new"
*/
private static Frame makeNewAndShow( )
throws QTException {
Frame f = new BasicQTEditor( );
f.pack( );
if (newFrameX >= 0)
f.setLocation (newFrameX+=16, newFrameY+=16);
f.setVisible(true);
windowCount++;
return f;
}
}MovieController) is used to indicate
where copies and pastes should occur. By shift-clicking, a user can
select a time-range from the current time (indicated by the play
head) to wherever the user shift-clicks (or, if he is dragging,
wherever the mouse is released).BasicQTEditor, shown in Example 3-1, will be the basis for the examples in this
chapter. It offers a single empty movie window (with the ability to
open movies from disk in new windows or to create new empty movie
windows), and an Edit menu with cut, copy, and paste
options.
package com.oreilly.qtjnotebook.ch03;
import quicktime.*;
import quicktime.qd.QDRect;
import quicktime.std.*;
import quicktime.std.movies.*;
import quicktime.app.view.*;
import quicktime.io.*;
import java.awt.*;
import java.awt.event.*;
import com.oreilly.qtjnotebook.ch01.QTSessionCheck;
public class BasicQTEditor extends Frame
implements ActionListener {
Component comp;
Movie movie;
MovieController controller;
Menu fileMenu, editMenu;
MenuItem openItem, closeItem, newItem, quitItem;
MenuItem copyItem, cutItem, pasteItem;
static int newFrameX = -1;
static int newFrameY = -1;
static int windowCount = 0;
/** no-arg constructor for "new" movie
*/
public BasicQTEditor ( ) throws QTException {
super ("BasicQTEditor");
setLayout (new BorderLayout( ));
QTSessionCheck.check( );
movie = new Movie(StdQTConstants.newMovieActive);
controller = new MovieController (movie);
controller.enableEditing(true);
doMyLayout( );
}
/** file-based constructor for opening movies
*/
public BasicQTEditor (QTFile file) throws QTException {
super ("BasicQTEditor");
setLayout (new BorderLayout( ));
QTSessionCheck.check( );
OpenMovieFile omf = OpenMovieFile.asRead (file);
movie = Movie.fromFile (omf);
controller = new MovieController (movie);
controller.enableEditing(true);
doMyLayout( );
}
/** gets component from controller, makes menus
*/
private void doMyLayout( ) throws QTException {
// add movie component
QTComponent qtc =
QTFactory.makeQTComponent (controller);
comp = qtc.asComponent( );
add (comp, BorderLayout.CENTER);
// file menu
fileMenu = new Menu ("File");
newItem = new MenuItem ("New Movie");
newItem.addActionListener (this);
fileMenu.add (newItem);
openItem = new MenuItem ("Open Movie...");
openItem.addActionListener (this);
fileMenu.add (openItem);
closeItem = new MenuItem ("Close");
closeItem.addActionListener (this);
fileMenu.add (closeItem);
fileMenu.addSeparator( );
quitItem = new MenuItem ("Quit");
quitItem.addActionListener (this);
fileMenu.add(quitItem);
// edit menu
editMenu = new Menu ("Edit");
copyItem = new MenuItem ("Copy");
copyItem.addActionListener(this);
editMenu.add(copyItem);
cutItem = new MenuItem ("Cut");
cutItem.addActionListener(this);
editMenu.add(cutItem);
pasteItem = new MenuItem ("Paste");
pasteItem.addActionListener(this);
editMenu.add(pasteItem);
// make menu bar
MenuBar bar = new MenuBar( );
bar.add (fileMenu);
bar.add (editMenu);
setMenuBar (bar);
// add close-button handling
addWindowListener (new WindowAdapter( ) {
public void windowClosing (WindowEvent e) {
doClose( );
}
});
}
/** handles menu actions
*/
public void actionPerformed (ActionEvent e) {
Object source = e.getSource( );
try {
if (source = = quitItem) doQuit( );
else if (source = = openItem) doOpen( );
else if (source = = closeItem) doClose( );
else if (source = = newItem) doNew( );
else if (source = = copyItem) doCopy( );
else if (source = = cutItem) doCut( );
else if (source = = pasteItem) doPaste( );
} catch (QTException qte) {
qte.printStackTrace( );
}
}
public void doQuit( ) {
System.exit(0);
}
public void doNew( ) throws QTException {
makeNewAndShow( );
}
public void doOpen( ) throws QTException {
QTFile file =
QTFile.standardGetFilePreview (QTFile.kStandardQTFileTypes);
Frame f = new BasicQTEditor (file);
f.pack( );
if (newFrameX >= 0)
f.setLocation (newFrameX+=16, newFrameY+=16);
f.setVisible(true);
windowCount++;
}
public void doClose( ) {
setVisible(false);
dispose( );
// quit if no windows now showing
if (--windowCount = = 0)
doQuit( );
}
public void doCopy( ) throws QTException {
Movie copied = controller.copy( );
copied.putOnScrap(0);
}
public void doCut( ) throws QTException {
Movie cut = controller.cut( );
cut.putOnScrap(0);
}
public void doPaste( ) throws QTException {
controller.paste( );
pack( );
}
/** Force frame's size to respect movie size
*/
public Dimension getPreferredSize( ) {
System.out.println ("getPreferredSize");
if (controller = = null)
return new Dimension (0,0);
try {
QDRect contRect = controller.getBounds( );
Dimension compDim = comp.getPreferredSize( );
if (contRect.getHeight( ) > compDim.height) {
return new Dimension (contRect.getWidth( ) +
getInsets( ).left +
getInsets( ).right,
contRect.getHeight( ) +
getInsets( ).top +
getInsets( ).bottom);
} else {
return new Dimension (compDim.width +
getInsets( ).left +
getInsets( ).right,
compDim.height +
getInsets( ).top +
getInsets( ).bottom);
}
} catch (QTException qte) {
return new Dimension (0,0);
}
}
/** opens a single new movie window
*/
public static void main (String[ ] args) {
try {
Frame f = makeNewAndShow( );
// note its x, y for future calls
newFrameX = f.getLocation( ).x;
newFrameY = f.getLocation( ).y;
} catch (Exception e) {
e.printStackTrace( );
}
}
/** creates "new" movie frame, packs and shows.
used by main( ) and "new"
*/
private static Frame makeNewAndShow( )
throws QTException {
Frame f = new BasicQTEditor( );
f.pack( );
if (newFrameX >= 0)
f.setLocation (newFrameX+=16, newFrameY+=16);
f.setVisible(true);
windowCount++;
return f;
}
}doCopy( ),
doCut( ), and doPaste( ) to use
low-level editing calls on the Movie instead of
cut/copy/paste-type calls on the MovieController.LowLevelQTEditor needs a static
Movie, called copiedMovie, to
keep track of what's on its virtual
"clipboard" so that it can be
shared across the new doCopy( ), doCut(), and doPaste( ) methods:public void doCopy( ) throws QTException {
copiedMovie = new Movie( );
TimeInfo selection = movie.getSelection( );
movie.insertSegment (copiedMovie,
selection.time,
selection.duration,
0);
}
public void doCut( ) throws QTException {
copiedMovie = new Movie( );
TimeInfo selection = movie.getSelection( );
movie.insertSegment (copiedMovie,
selection.time,
selection.duration,
0);
movie.deleteSegment (selection.time,
selection.duration);
controller.movieChanged( );
}
public void doPaste( ) throws QTException {
if (copiedMovie = = null)
return;
copiedMovie.insertSegment (movie,
0,
copiedMovie.getDuration( ),
movie.getSelection( ).time);
controller.movieChanged( );
pack( );
}
UndoableQTEditor builds on the original
BasicQTEditor by adding an
"undo" menu item. The
doUndo( ) method i