/*
	File:		MovieTextFinder.java
	
	Description:	This sample demonstrates two different techniques for searching for text strings 
                        in movie text media - the quicktime.std.movies.searchText method, and the 
                        quicktime.std.movies.media.TextMediaHandler.findNextText method.
	
        Author:		Apple Computer, Inc.
	
        Copyright: 	© Copyright 2002 Apple Computer, Inc. All rights reserved.
	
	Disclaimer:	IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
				("Apple") in consideration of your agreement to the following terms, and your
				use, installation, modification or redistribution of this Apple software
				constitutes acceptance of these terms.  If you do not agree with these terms,
				please do not use, install, modify or redistribute this Apple software.
				In consideration of your agreement to abide by the following terms, and subject
				to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
				copyrights in this original Apple software (the "Apple Software"), to use,
				reproduce, modify and redistribute the Apple Software, with or without
				modifications, in source and/or binary forms; provided that if you redistribute
				the Apple Software in its entirety and without modifications, you must retain
				this notice and the following text and disclaimers in all such redistributions of
				the Apple Software.  Neither the name, trademarks, service marks or logos of
				Apple Computer, Inc. may be used to endorse or promote products derived from the
				Apple Software without specific prior written permission from Apple.  Except as
				expressly stated in this notice, no other rights or licenses, express or implied,
				are granted by Apple herein, including but not limited to any patent rights that
				may be infringed by your derivative works or by other works in which the Apple
				Software may be incorporated.
				The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
				WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
				WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
				PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
				COMBINATION WITH YOUR PRODUCTS.
				IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
				CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
				GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
				ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
				OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
				(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
				ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
				
	Change History (most recent first):
            11/22/2002	md	new SampleCode revisions
*/

import java.awt.*;
import java.io.*;
import javax.swing.*;
import java.awt.event.*;
import java.applet.*;
import java.io.IOException;

import quicktime.*;
import quicktime.app.*;
import quicktime.app.display.*;
import quicktime.app.players.*;
import quicktime.io.*;
import quicktime.qd.*;
import quicktime.std.*;
import quicktime.std.movies.*;
import quicktime.std.clocks.*;
import quicktime.std.movies.media.*;
import quicktime.util.*;

/**
 * MovieTextFinder class
 *
 * This class contains code to display and play a quicktime movie in a JFrame.
 * First a QTCanvas is created. The QTCanvas will be use for displaying our
 * QuickTime movie. Next, a movie file is opened and a MovieController
 * object created for the movie. The MovieController object is used to control/play
 * the movie. Finally, a QTDrawable object is created for the
 * MovieController, and the QTDrawable object is set as the drawing client for the
 * QTCanvas.
 */
public class MovieTextFinder extends JFrame implements Errors {	

	JTextField searchTextField;
	JCheckBox wrapSearch;
	JCheckBox caseSensitiveSearch;
	JRadioButton searchForwardButton;
	JRadioButton searchBackwardButton;

	Movie theMovie;
		
	static final private String theTextMovieName = "TextOnly.mov";
	
		/* this flag will determine which technique we
		 use to perform our search */
	static final private boolean kUseMovieSearchTextMethod = true;
	
		// current offset into search text 
	private int currentOffset = 0;
	
	private JDialog errorDialog;
	
	public static void main (String args[]) {
		try { 
			QTSession.open();
			// make a window and show it - we only have one window/one movie at a time
			MovieTextFinder pm = new MovieTextFinder("QT in Java");
			pm.show();
			pm.toFront();
		} catch (QTException e) {
			// at this point we close down QT if an exception is generated because it means
			// there was a problem with the initialization of QT>
			e.printStackTrace();
			QTSession.close ();
		}
	}

	/**
	 * MovieTextFinder constructor
	 *
	 * This class creates a QTCanvas which will be used to display QuickTime content. Next, a 
	 * movie file is opened. This movie contains a text track which we'll use to demonstrate
	 * how to search for individual text strings. Finally, we add the QTCanvas as a component
	 * to the JFrame container.
	 */
	public MovieTextFinder (String title) {
		super (title);

		myQTCanvas = new QTCanvas();

		new FileMenu (this);

		setResizable(false);

		JPanel p1 = new JPanel();
		
		p1.setLayout (new BoxLayout(p1, BoxLayout.Y_AXIS));
		p1.setBorder(BorderFactory.createEmptyBorder(20,20,20,20));

		searchForwardButton = new JRadioButton("Search Forward");
		searchForwardButton.setSelected(true);
		searchBackwardButton = new JRadioButton("Search Backward");
		ButtonGroup buttonGroup = new ButtonGroup();
		buttonGroup.add(searchForwardButton);
		buttonGroup.add(searchBackwardButton);
		
		JPanel radioPanel = new JPanel();
		
		radioPanel.setLayout(new BoxLayout(radioPanel,BoxLayout.Y_AXIS));
		radioPanel.add(searchForwardButton);
		radioPanel.add(searchBackwardButton);
		
		p1.add(radioPanel);

		wrapSearch = new JCheckBox("wrap search");
		caseSensitiveSearch = new JCheckBox("case sensitive");

		p1.add(wrapSearch);
		p1.add(caseSensitiveSearch);

		try {
				/* let's open and display our text movie which is
					located in our project folder */
			createNewMovieFromURL("file://" + QTFactory.findAbsolutePath (theTextMovieName));
		}
		catch (IOException ioe) {
			showErrorDialog(ioe.toString());
			System.exit(0);
		}

        Container C = getContentPane();
        C.setLayout (new FlowLayout(FlowLayout.LEFT));

		JPanel p3 = new JPanel();
        p3.setLayout (new GridLayout(5,1));
		p3.setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
		
		JLabel jl = new JLabel("  Enter Search Text");
		p3.add(jl);

		searchTextField = new JTextField(10);
		searchTextField.setMaximumSize(new Dimension(250,20));
		searchTextField.select(0,0);
		p3.add(searchTextField);

		JButton searchButton = new JButton("       Search      ");
		searchButton.setDefaultCapable(true);

		p3.add(Box.createRigidArea(new Dimension(0,20)));
		p3.add(searchButton);
		
		// trap <return>, <enter> key presses so we can perform our search
		addKeyListener( new KeyAdapter() {
			public void keyReleased(KeyEvent e) {
					// <enter> key pressed? 
				if (e.getKeyCode() == e.VK_ENTER) {
					DoSearch();		// search for current text 
				}
			}
		} );

		searchButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				DoSearch();
			}
		} );

		Container container = getContentPane();
        container.setLayout (new FlowLayout(FlowLayout.LEFT));
		container.add(p1, "West");

		try {
			QDRect qtr = theMovie.getBox();
			myQTCanvas.setSize(qtr.getWidth(),qtr.getHeight());
			
			JPanel p0 = new JPanel();
			p0.setLayout (new FlowLayout(FlowLayout.LEFT));
			p0.setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
			p0.add(myQTCanvas);
			container.add(p0, "Center");		
		}
		catch (QTException e) {
			e.printStackTrace();
		}

		container.add(p3, "East");
		/* set focus on search text box, so user
			can type search text without having
			to first tab to it */
		searchTextField.requestFocus();

		pack();
		
		addWindowListener(new WindowAdapter () {
			public void windowClosing (WindowEvent e) {
				goAway();
			}
			
			public void windowClosed (WindowEvent e) { 
				System.exit(0);
			}
		});			
	}

	/*
	 * This internal method is called to perform a text search in the movie 
	 */
	private void DoSearch() {
		String theText = searchTextField.getText();
		try {
			
			/* set the search features */
			int searchFlags = quicktime.std.StdQTConstants.findTextUseOffset;

				/* backward search? */
			if (searchBackwardButton.isSelected())
				searchFlags |= quicktime.std.StdQTConstants.findTextReverseSearch;
				
				/* wrap search? */
			if (wrapSearch.isSelected())
				searchFlags |= quicktime.std.StdQTConstants.findTextWrapAround;
			
				/* case sensitive search? */
			if (caseSensitiveSearch.isSelected())
				searchFlags |= quicktime.std.StdQTConstants.findTextCaseSensitive;
	
			findTextInMovieTextTrack (theText, searchFlags);
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}

	}
	
	private QTDrawable myPlayer;
	private QTCanvas myQTCanvas;	
	
	/**
	 *
	 * createNewMovieFromURL method
	 *
	 * This class creates a movie and it's associated movie controller from
	 * a url. It then creates a QTPlayer object which is used to display/play 
	 * and control the movie. 
	 *
	 * @param theURL string containing the URL of the movie to open
	 */
	public void createNewMovieFromURL (String theURL) {
		try {
			// create the DataRef that contains the information about where the movie is
			DataRef urlMovie = new DataRef(theURL);
			
			// create the movie 
			theMovie = Movie.fromDataRef (urlMovie,StdQTConstants.newMovieActive);

			currentOffset = 0;
			
			// This shows the steps to use the three different Objects to present a Movie
				// QTPlayer -> presents the MovieController allowing the user to interact with the movie and control its playback/presentation
				// MoviePlayer -> presents the Movie directly to the screen - application could provide its own controls
				// MoviePresenter -> puts the Movie into an offscreen buffer, the buffer is then drawn to the screen - application could provide its own controls
			if (true)  // QTPlayer
				myPlayer = new QTPlayer (new MovieController (theMovie));
			else if (false) 	// make a MoviePlayer version
				myPlayer = new MoviePlayer (theMovie);
			else if (false) 	// make a MoviePresenter out of this
				myPlayer = new MoviePresenter (theMovie);
			
			myQTCanvas.setClient (myPlayer, true);
			
			// this will set the size of the enclosing frame to the size of the incoming movie
			pack();

			//no user control over MoviePlayer or MoviePresenter so set rate
			if (false)
				theMovie.setRate(1);
			
		} catch (QTException err) {
			err.printStackTrace();
		}
	}
	
	/**
	 * Gets the player associated with the movie
	 * 
	 * @return the QTDrawable object playing the movie
	 */
	public QTDrawable getPlayer () { return myPlayer; }
	
	/**
	 * Gets the canvas associated with the movie
	 * 
	 * @return the QTCanvas object containing the movie player
	 */
	public QTCanvas getCanvas () { return myQTCanvas; }
	
	/**
	 * Gets the movie to be searched
	 * 
	 * @return the Movie
	 */
	public Movie getMovie () throws QTException {
		return theMovie;
	}
	
	/** 
	 * This method tears down the QTSession object and quits the application
	 */
	public static void goAway () {
		QTSession.close();
		System.exit(0);	
	}	

	/** 
	 * This method stops the movie (if applicable) from playing
	 */
	void stopPlayer () {
		try {
			if (theMovie != null)
				theMovie.setRate(0);
		} catch (QTException err) {
			err.printStackTrace();
		}
	}
	
	/** 
	 * FindTextInMovieTextTrack method
	 *
	 * This method shows how to use both the quicktime.std.movies.searchText method and the 
	 * quicktime.std.movies.media.TextMediaHandler.findNextText method to search for text in 
	 * a movie text track.
	 * @param searchText the string to search for
	 * @param theFlags search flags- forward, backward, etc.
	 * @return true if the text was found
	 */
	boolean findTextInMovieTextTrack (String searchText, int theFlags) {
			// was the text found? 
		boolean textFound = false;
		
			// we will search in enabled text tracks only 
		theFlags |= StdQTConstants.searchTextEnabledTracksOnly;
		
		if ( kUseMovieSearchTextMethod) {
			//////////
			//
			// METHOD ONE: Use the quicktime.std.movies.searchText method, your one-stop, 
			//				find-the-text-and-do-the-right-thing function.
			//
			//////////
			
			try {
					// get the current movie time - we will start searching
					//	from this point on
				QTPointer searchTextPtr = new QTPointer(searchText.getBytes());	
				SearchResult sResult = theMovie.searchText(searchTextPtr,		// the text to search for
															theFlags,				// flags 					
															theMovie.getTime(),	// current movie time 		
															currentOffset 			// searchOffset 			 
															);
				if (sResult.getFoundTrack() != null) {
					textFound = true;	// found it!!!! 
					currentOffset = sResult.getOffset();	// save off current offset into text 
				}
			}
			catch (QTException e) {
				int theError = e.errorCode();
				if (theError == quicktime.Errors.movieTextNotFoundErr) {
					showErrorDialog("Text not found!");
				}
				else {
					e.printStackTrace();
				}
			}
		} else {
			//////////
			//
			// METHOD TWO: Use quicktime.std.movies.media.TextMediaHandler.findNextText method. Here, 
			//				you need to explicitly go to the found text and select it. 
			//
			//////////

					// the text media handler for our text track 
				MediaHandler trackMediaHandler = null;

				try {					
						// get the first enabled text track from the movie. If there is
						//	more than one, the first one found will be used 
					Track theTextTrack = theMovie.getIndTrackType (1,
																StdQTConstants.textMediaType, 
																StdQTConstants.movieTrackMediaType | 
																StdQTConstants.movieTrackEnabledOnly);
						
					if (theTextTrack != null) {
						// load the entire text track into RAM 
						theTextTrack.loadIntoRam (0, theTextTrack.getDuration(), 0);

						// get the text media handler for the track - we'll use
						//	this media handler to demonstrate how to perform a
						//	text search 
						Media trackMedia 	= Media.getTrackMedia (theTextTrack);
						trackMediaHandler 	= trackMedia.getHandler();
					}
					else {
						showErrorDialog ("Movie does not contain a text track!");
						return false;
					}
			
					// take the generic media handler gotten from our text
					//	track and cast it to a TextMediaHandler, since we know
					//	we are dealing with text media 
					TextMediaHandler textMediaHdlr = (TextMediaHandler)trackMediaHandler;
				
						// we'll need to make a QTPointer object from our search text 
						//	and pass this to the findNextText method below 
					QTPointer searchTextPtr = new QTPointer (searchText.getBytes());	
					
						// search for the specified text
					FoundTextInfo fTextInfo = textMediaHdlr.findNextText (searchTextPtr,			// the text to search for 
																		theFlags,				// search flags 
																		theMovie.getTime()	// time to begin searching from 
																		);	
					if (fTextInfo.time != -1) {
						textFound = true;	// found it!!!! 

							// save current offset into search text 
						currentOffset = fTextInfo.offset;
						
							// go to the time in the movie where the
							//	search text was found 
						theMovie.setTimeValue(fTextInfo.time);
	
							// highlight the text 
						textMediaHdlr.hiliteTextSample(theMovie.getTime(),
														fTextInfo.offset,
														fTextInfo.offset+searchText.length(),
														new QDColor(0x8000,0x8000,0x8000)	// use grey color hilight 
														);
					}
					else 
						showErrorDialog("Text not found!");
				}
				catch (QTException e) {
					int theError = e.errorCode();
					if (theError == quicktime.Errors.movieTextNotFoundErr) 
						showErrorDialog("Text not found!");
					else 
						e.printStackTrace();
				}
			}
		
			// tell the caller whether or not text was found 
		return textFound;
	}
	
	/**
	 * Displays an error dialog reporting any problems encountered
	 * 
	 * @param theError the error string
 	 */
	private void showErrorDialog(String theError) {
		errorDialog = new JDialog();
		errorDialog.setModal(true);
		errorDialog.setResizable(false);
		Container c = errorDialog.getContentPane();
		c.setLayout(new BoxLayout(c,BoxLayout.Y_AXIS));
		
		JPanel jp = new JPanel();
		jp.setLayout(new BoxLayout(jp,BoxLayout.Y_AXIS));
		jp.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));				

		JLabel jl = new JLabel(theError);
		jl.setMaximumSize(new Dimension(270,20));
		
		jp.add(jl);
		
		JPanel jp2 = new JPanel();
		jp2.setLayout(new BoxLayout(jp2,BoxLayout.Y_AXIS));
		jp2.setBorder(BorderFactory.createEmptyBorder(5,125,15,10));

		JButton jb = new JButton("OK");
		jb.setMaximumSize(new Dimension(70,20));

		jp2.add(jb);
		
		errorDialog.getContentPane().add(jp, "South");
		errorDialog.getContentPane().add(jp2, "South");
		jb.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				errorDialog.dispose();
			}
		});
		errorDialog.setBounds(0,0,300,100);
		errorDialog.show();
	}
}
