package krut;
/*
* Run_KRUT.java
*
* Created on den 29 december 2004, 23:00
*/
/**
* @author jonte
*/
/*
* @(#)Run_KRUT.java
*
* Some general comments on this program.
*
* First of all, the major part of the program is licenced
* under the GPL which you should have recieved a copy of
* along with the program. Some classes come under different
* licences, which are specified in the source code to those
* classes, and copied in the file readme.txt, which you
* should also have recieved a copy of along with this
* program.
*
* Secondly, some discussion. The structure of this program
* is not always optimal. There are a lot of global
* parameters, and you may stumble upon some difficult to
* understand usage of things like access levels, and
* global parameters. This is pretty much always due simply
* to the fact that this program was written as a way of
* learning Java. As it turned out, the program
* was useful, and I decided to share it. Then the program
* also became a little more popular than I had imagined,
* so I decided to do my best to clean up the code, so that
* it at least became readable. That is where I am right now.
* But of course, I realize already that I can not start
* changing things like global parameters, since in some places
* they are necessary, and in some places they aren't, and
* I don't know which is which any longer.
*
* I will leave all parameters that were global global, and
* all parameters that were public public, even if there
* doesn't seem to be a reason for it, because as far as I
* am concerned, no harm is done.
*
* Some times, the remarks in the comments are just notes
* to myself. This would usually be the case if they seem
* very obvious.
*
* Jonas
*/
import java.io.*;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import java.util.logging.Logger;
import krut.KRUT_GUI.*;
import krut.KRUT_Recording.*;
/** This is the main class of the program. When the main() method is
* run, the following things will be done:<BR><BR>
*
* - Create and show the GUI.<BR>
* - Start two new threads for the Sampler and the ScreenGrabber.<BR>
* - Register event and action listeners to all buttons and menus.<BR><BR>
*
* When this is done, the main thread is done, and everything
* that is done after is caused by the event and action listeners.
*/
public class Run_KRUT implements ActionListener, ItemListener {
private final static Logger logger = Logger.getLogger(Run_KRUT.class.getName());
/** Newline String for the OutputWindow. */
protected String newline = "\n";
/** The main frame. */
public JFrame frame;
/** The interface to the user for changing the capture area.
* This used to be a separate object that was initiated by the Run_KRUT object.
* Now the present object is initiated in KrutSettings, but the Run_KRUT object
* still needs access so the code will not break.
*/
public CapSizeQuery capQuery;
/** The interface to the user for changing the fps values for the
* recorded movie.
* This used to be a separate object that was initiated by the Run_KRUT object.
* Now the present object is initiated in KrutSettings, but the Run_KRUT object
* still needs access so the code will not break.
*/
public FPSQuery fpsQuery;
/** The interface to the user for changing the movie encoding quality.
* This used to be a separate object that was initiated by the Run_KRUT object.
* Now the present object is initiated in KrutSettings, but the Run_KRUT object
* still needs access so the code will not break.
*/
public QualitySlider encSlider;
/** The interface to the user for changing the sound recording quality.
* This used to be a separate object that was initiated by the Run_KRUT object.
* Now the present object is initiated in KrutSettings, but the Run_KRUT object
* still needs access so the code will not break.
*/
public SoundQuery soundQuery;
/** The interface to the user for changing the save files.
* This used to be a separate object that was initiated by the Run_KRUT object.
* Now the present object is initiated in KrutSettings, but the Run_KRUT object
* still needs access so the code will not break.
*/
public SaveFileChooser saveQuery;
/** The output window for the program. */
public OutputText outWindow;
/** The timer object. */
public KrutTimer timer;
/** The settings object, containing the interfaces to the user
* for changing the properties of the recorded data.
*/
public KrutSettings krutSettings;
/** The ScreenGrabber is used for all video and
* screenshot functions. */
public ScreenGrabber myGrabber = null;
/** The Sampler is used for all audio recording.
*/
public Sampler mySampler = null;
/** The EncodingProgressBar is used to show and abort encoding. */
public EncodingProgressBar myProgressBar;
/** A class used for showing the snap shot.
* Used in the snapAction() method. */
public SnapShot imageUtils = new SnapShot();
/** The initial name of the snapshot file.
* This is just passed on to the SaveFileChooser
* in the activateGUI() method. */
public File imageFile = new File("image.jpg");
/** The initial name of the movie file.
* This is just passed on to the SaveFileChooser
* in the activateGUI() method. */
public File movieFile = new File("movie.mov");
/** The initial name of the audio file.
* This is just passed on to the SaveFileChooser
* in the activateGUI() method. */
public File audioFile = new File("audio.wav");
/** Flag to keep track of if we should record audio.
* Used in the recordAction() and stopAction(). This flag
* is updated in the checkInited() method, where it is set to
* the value of the nextAudio flag. This means that this
* flag will not change until a new recording is ready to
* start, so there will be no unwanted interruptions of ongoing
* recordings.
*/
protected boolean recAudio = true;
/** Flag to keep track of if we should record Video.
* Used in the recordAction() and stopAction(). This flag
* is updated in the checkInited() method, where it is set to
* the value of the nextVideo flag. This means that this
* flag will not change until a new recording is ready to
* start, so there will be no unwanted interruptions of ongoing
* recordings.
*/
protected boolean recVideo = true;
/** Flag used to keep track of if the NEXT recording
* should record audio. This flag is used in the
* checkInited() method, where its value is copied into
* recAudio. This flag is changed in the
* itemStateChanged() method.
*/
protected boolean nextAudio = true;
/** Flag used to keep track of if the NEXT recording
* should record video. This flag is used in the
* checkInited() method, where its value is copied into
* recVideo. This flag is changed in the
* itemStateChanged() method.
*/
protected boolean nextVideo = true;
/** This button is used to switch between the
* recording button, the stop button and
* the timer button. The active button is the
* button actually showing in the left button
* position in the main window.
* The buttons are added in init(), and used in addButtons()
* and makeNavigationButton and addButtons().
*/
protected JButton activeButton = null;
/** The recording button.
* The buttons are added in init(), and used in addButtons()
* and makeNavigationButton and addButtons().
*/
protected JButton recButton = null;
/** The stop button.
* The buttons are added in init(), and used in addButtons()
* and makeNavigationButton and addButtons().
*/
protected JButton stopButton = null;
/** The snapshot button.
* The buttons are added in init(), and used in addButtons()
* and makeNavigationButton and addButtons().
*/
protected JButton snapshotButton = null;
/** The mouse pointer button.
* The buttons are added in init(), and used in addButtons()
* and makeNavigationButton and addButtons().
*/
protected JButton mouseButton = null;
/** The button used to indicate that the timer is active.
* The buttons are added in init(), and used in addButtons()
* and makeNavigationButton and addButtons().
*/
protected JButton timerButton = null;
/** The button used to indicate that the timer is running.
* The buttons are added in init(), and used in addButtons()
* and makeNavigationButton and addButtons().
*/
protected JButton timerRecButton = null;
/** This JFrame is used to show the SnapShots in.
* Used in the SnapAction() method.
* Needs to be initiated before the GUI shows.
* Is initiated in the createMainFrame() method.
* (If it is initiated earlier, it will not have the correct LookAndFeel)
*/
protected JFrame snapShotFrame;
/** The starting value of the recording and playback fps.
* This is used in the createScreenGrabber() method.
* It is also passed on to the KrutSettings constructur
* from the init() method.
*/
private int startFps = 15;
/** The starting value of the video encoding quality.
* This value is just passed on to KrutSettings via the
* constuctor. This value is only used in the init()
* method. If this value is changed, the initial value
* of the parameter encQuality in ScreenGrabber should
* be changed accordingly.
*
* startEncQuality should be a value between 0 and 100.
* encQuality in ScreenGrabber should be between 0 and 1.
*/
private int startEncQuality = 75;
/** A flag determining if we should start recording audio
* in stereo or not. It is set to no in order to reduce
* the size of the recorded audio file. This flag is
* passed on to KrutSettings via the constructor, in init().
* It is also passed on to the Sampler in createSampler().
*/
private boolean startStereo = false;
/** A flag determining if we should start recording audio
* in 16-bit or not. It is set to no in order to reduce
* the size of the recorded audio file. This flag is
* just passed on to KrutSettings via the constructor, in init().
* It is also passed on to the Sampler in createSampler().
*/
private boolean startSixteen = false;
/** The starting value of the frequency for audio recording.
* This value is just passed on to KrutSettings via the
* constuctor, in init().
* It is also passed on to the Sampler in createSampler().
*/
private int startFrequency = 22050;
/** This is the menu in the main window of the GUI.
* It is defined here because it has to be accessed from
* the method init(), to deactivate and reactivate it
* before and after the KrutSettings and OutputWindow
* objects are initiated.
*/
private JMenu menu;
/** The audio CheckBox from the menu. It is defined here,
* for a simple way to access it from the init() method,
* where it is passed on to the KrutSettings object.
*/
private JCheckBoxMenuItem acbMenuItem;
/** The video CheckBox from the menu. It is defined here,
* for a simple way to access it from the init() method,
* where it is passed on to the KrutSettings object.
*/
private JCheckBoxMenuItem vcbMenuItem;
/** The mouse pointer CheckBox from the menu. It is defined here,
* for a simple way to access it from the init() method,
* where it is passed on to the KrutSettings object.
*/
private JCheckBoxMenuItem mcbMenuItem;
/** Used for the buttons, in addButtons() and actionPerformed(). */
static final private String RECORD = "record";
/** Used for the buttons, in addButtons() and actionPerformed(). */
static final private String SNAPSHOT = "snapshot";
/** Used for the buttons, in addButtons() and actionPerformed(). */
static final private String STOP = "stop";
/** Used for the buttons, in addButtons() and actionPerformed(). */
static final private String TIMER = "timer";
/** Used for the buttons, in addButtons() and actionPerformed(). */
static final private String MPOINTER = "mpointer";
/** Used for the buttons, in addButtons() and actionPerformed(). */
static final private String EMPTY = "empty";
/** A flag used to tell if there is already a thread
* running trying to stop the recording. This flag
* is used to (almost) definitely make sure that two
* STOP - ActionCommands can not be executed in a row.
* This flag is changed to true in the the ActionListener
* and changed back to false in restoreGUI.
*/
protected boolean stopping = false;
/** A flag used to tell if recording is in progress.
* This is used to tell the timer when it should
* record and when it should stop. This flag is
* changed to true in the ActionListener, and
* changed back to false in restoreGUI.
*/
protected boolean recording = false;
/** A class that is used to run the stopAction() method
* in a separate thread. This class is used from the
* actionPerformed() method.
*/
private class StopThread extends Thread {
public void run() {
try {
stopAction();
} catch (Exception e) {
System.out.println("Stop thread cancelled");
System.out.println(e);
} finally {
myProgressBar.dispose();
}
}
}
/***************************************************************************
* First part :
* This part of the code contains methods for the creation of
* and user-interaction with the GUI.
**************************************************************************/
/** Create the menu bar, and add the menu to it. Three
* checkbox items are stored in the global parameters
* vcbMenuItem, acbMenuItem and mcbMenuItem.
*
* @return a JMenuBar object with the menu attached to it.
*/
public JMenuBar createMenuBar() {
JMenuBar menuBar;
JMenuItem menuItem;
JRadioButtonMenuItem rbMenuItem;
/** The "Follow Mouse" CheckBox from the menu. */
JCheckBoxMenuItem fwmMenuItem;
/** The "Preview Window" CheckBox from the menu. */
JCheckBoxMenuItem prwMenuItem;
/** Create the menu bar. */
menuBar = new JMenuBar();
/** Build the menu. */
menu = new JMenu("Menu");
menu.setMnemonic(KeyEvent.VK_M);
menu.getAccessibleContext().setAccessibleDescription(
"Menu");
menuBar.add(menu);
/** Add video checkbox. */
vcbMenuItem = new JCheckBoxMenuItem("Video output", true);
vcbMenuItem.setMnemonic(KeyEvent.VK_O);
vcbMenuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_1, ActionEvent.ALT_MASK));
vcbMenuItem.addItemListener(this);
menu.add(vcbMenuItem);
/** Add audio checkbox. */
acbMenuItem = new JCheckBoxMenuItem("Audio output", true);
acbMenuItem.setMnemonic(KeyEvent.VK_U);
acbMenuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_2, ActionEvent.ALT_MASK));
acbMenuItem.addItemListener(this);
menu.add(acbMenuItem);
/** Add mouse checkbox. */
mcbMenuItem = new JCheckBoxMenuItem("Show Mouse", true);
mcbMenuItem.setMnemonic(KeyEvent.VK_M);
mcbMenuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_3, ActionEvent.ALT_MASK));
mcbMenuItem.addItemListener(this);
menu.add(mcbMenuItem);
/** Add follow mouse checkbox. */
fwmMenuItem = new JCheckBoxMenuItem("Follow Mouse", false);
fwmMenuItem.setMnemonic(KeyEvent.VK_F);
fwmMenuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_4, ActionEvent.ALT_MASK));
fwmMenuItem.addItemListener(this);
menu.add(fwmMenuItem);
/** Add preview window checkbox. */
prwMenuItem = new JCheckBoxMenuItem("Preview Window", false);
prwMenuItem.setMnemonic(KeyEvent.VK_P);
prwMenuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_5, ActionEvent.ALT_MASK));
prwMenuItem.addItemListener(this);
menu.add(prwMenuItem);
/** Add Settings menu item. */
menu.addSeparator();
menuItem = new JMenuItem("Settings/Save Files",
KeyEvent.VK_S);
menuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_6, ActionEvent.ALT_MASK));
menuItem.addActionListener(this);
menu.add(menuItem);
/** Add output window menu item. */
menuItem = new JMenuItem("Show output window",
KeyEvent.VK_H);
menuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_7, ActionEvent.ALT_MASK));
menuItem.addActionListener(this);
menu.add(menuItem);
/** Add output window menu item. */
menuItem = new JMenuItem("Timer",
KeyEvent.VK_T);
menuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_8, ActionEvent.ALT_MASK));
menuItem.addActionListener(this);
menu.add(menuItem);
/** Add speed test menu item. */
menuItem = new JMenuItem("Run speed test",
KeyEvent.VK_R);
menuItem.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_0, ActionEvent.ALT_MASK));
menuItem.addActionListener(this);
menu.add(menuItem);
menu.addSeparator();
menuItem = new JMenuItem("Request Feature");
menuItem.addActionListener(new OpenUrlActionListener("https://sourceforge.net/p/krut/feature-requests/"));
menu.add(menuItem);
menuItem = new JMenuItem("Report Bug");
menuItem.addActionListener(new OpenUrlActionListener("https://sourceforge.net/p/krut/bugs/"));
menu.add(menuItem);
menuItem = new JMenuItem("GitHub Page");
menuItem.addActionListener(new OpenUrlActionListener("https://github.com/jimmyfm/krut"));
menu.add(menuItem);
return menuBar;
}
/** Returns an ImageIcon, or null if the path was invalid.
*
* @param The path to the image.
* @return The ImageIcon.
*/
protected static ImageIcon createImageIcon(String path) {
java.net.URL imgURL = Run_KRUT.class.getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
/** Add 3 buttons to a toolbar.
* Several buttons are created, and then the toolbar
* will switch between the recButton, the
* stopButton, and the timerButton. This is done by adding only the button
* called activeButton to the toolBar, and then copying
* the layout on to that button from one of the
* other two.
*
* @param toolBar The JToolBar that should be used in
* the program.
*/
protected void addButtons(JToolBar toolBar) {
/** Create a recButton. This will not be added to the
* JToolBar, but will serve as a template for the
* activeButton, when it is waiting for recording
* to start.
*/
recButton = makeNavigationButton("mellan7.PNG", RECORD,
"Start recording",
"Rec ");
/** Create the active button. This button will be changed
* when recording stops and starts to either reflect the
* recButton or the stopButton.
*/
activeButton = makeNavigationButton("mellan7.PNG", RECORD,
"Start recording",
"Rec ");
/** Add first button (activeButton). */
toolBar.add(activeButton);
/** Create the snapshotButton and add it to the JToolBar.
*/
snapshotButton = makeNavigationButton("blue6.PNG", SNAPSHOT,
"Take screenshot",
" Snap");
/** Add second button (snapshotButton). */
toolBar.add(snapshotButton);
/** Create a stopButton. This will not be added to the
* JToolBar, but will serve as a template for the
* activeButton, when it is in record mode.
*/
stopButton = makeNavigationButton("stop.PNG", STOP,
"Stop recording",
"Stop");
stopButton.setSize(recButton.getSize());
/** Create a timerButton. This will not be added to the
* JToolBar, but will serve as a template for the
* activeButton, when it is in timer mode.
*/
timerButton = makeNavigationButton("timer.PNG", TIMER,
"The timer is active",
"Timer");
/** Create a timerRecButton. This will not be added to the
* JToolBar, but will serve as a template for the
* activeButton, when it is in timer mode.
*/
timerRecButton = makeNavigationButton("timer_running.PNG", TIMER,
"The timer is recording",
"Timer");
/** Create the mouse pointer button.
*/
mouseButton = makeNavigationButton("mus.PNG", MPOINTER,
"Select capture area with mouse and CTRL button",
"");
/** Add the key listener and the focus listener to the
* mouse pointer button.
*/
mouseButton.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyPressed(java.awt.event.KeyEvent evt) {
mouseButtonKeyPressed(evt);
}
public void keyReleased(java.awt.event.KeyEvent evt) {
mouseButtonKeyReleased(evt);
}
});
mouseButton.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
mouseButtonFocusLost(evt);
}
});
/** Add third button (mouseButton). */
toolBar.add(mouseButton);
}
/** When the mouse-pointer button is pressed, this method
* fires a KeyPressed event for the mouse-pointer
* button in the krutSettings object. Since
* the two buttons behave identically, this will
* be all that needs to be done.
* At the time of writing, the method that will
* execute in the krutSettings object is called
* jButton1KeyPressed().
*
* @param evt The event that triggered this KeyListener.
*/
private void mouseButtonKeyPressed(java.awt.event.KeyEvent evt) {
java.awt.event.KeyListener[] listeners =
krutSettings.jButton1.getKeyListeners();
for (int i = 0; i < listeners.length; i++) {
listeners[i].keyPressed(evt);
}
}
/** When the mouse-pointer button is released (=CTRL is released),
* this method fires a KeyReleased event for the
* mouse-pointer button in the krutSettings object. Since the
* two buttons behave identically, this will be all
* that needs to be done.
* At the time of writing, the method that will
* execute in the krutSettings object is called
* jButton1KeyReleased().
*
* @param evt The event that triggered this KeyListener.
*/
private void mouseButtonKeyReleased(java.awt.event.KeyEvent evt) {
java.awt.event.KeyListener[] listeners =
krutSettings.jButton1.getKeyListeners();
for (int i = 0; i < listeners.length; i++) {
listeners[i].keyReleased(evt);
}
}
/** When the mouse-pointer button looses focus, this method
* fires a FocusLost event for the mouse-pointer button
* in the krutSettings object. Since the
* two buttons behave identically, this will be all
* that needs to be done.
* At the time of writing, the method that will
* execute in the krutSettings object is called
* jButton1FocusLost().
*
* @param evt The event that triggered this FocusListener.
*/
private void mouseButtonFocusLost(java.awt.event.FocusEvent evt) {
java.awt.event.FocusListener[] listeners =
krutSettings.jButton1.getFocusListeners();
for (int i = 0; i < listeners.length; i++) {
listeners[i].focusLost(evt);
}
}
/** Creates a navigation button of the specified
* appearance, and returns it.
*
* @param imageName A String representation of the URL
* to an image that should be displayed
* on this button.
* @param actionCommand The action command for this button.
* This command is listened for in
* the actionPerformed(ActionEvent e)
* method, to determine which button
* was pressed.
* @param toolTipText The tooltip text for this button.
* @param altText The text that should be typed on
* this button, if any.
*
* @return A JButton according to the specifications
* given in the parameters.
*/
protected JButton makeNavigationButton(String imageName,
String actionCommand,
String toolTipText,
String altText) {
/** Attempt to locate the image */
String imgLocation = imageName;
URL imageURL = Run_KRUT.class.getResource(imgLocation);
/** Create and initialize the button. */
JButton button = new JButton();
button.setActionCommand(actionCommand);
button.setToolTipText(toolTipText);
button.addActionListener(this);
/** Add the image if it was succesfully located. */
if (imageURL != null) {
button.setIcon(new ImageIcon(imageURL, altText));
} else {
System.err.println("Resource not found: "
+ imgLocation);
}
/** Set a text on the button. If there is no String
* or an empty String in the parameter altText
* no text will appear on the button.
*/
button.setText(altText);
return button;
}
/** This method delivers the size that the activeButton
* parameter should have. This method is used by
* the switchActiveButton method. To change the size
* of the activeButton, this method can be overridden.
*
* @return A Dimension object containing the
* size of the activeButton.
*/
public Dimension getActiveButtonSize() {
/** Return the size of the record button, minus the size of the
* border, which doesn't seem to show.
* (size - margin + (border - margin))
*/
Dimension returnDim = new Dimension(recButton.getPreferredSize());
returnDim.width += recButton.getInsets().left - 2 * recButton.getMargin().left;
returnDim.width += recButton.getInsets().right - 2 * recButton.getMargin().right;
returnDim.height += recButton.getInsets().top - 2 * recButton.getMargin().top;
returnDim.height += recButton.getInsets().bottom - 2 * recButton.getMargin().bottom;
return returnDim;
}
/** Switches the activeButton so that the button
* displayed is now the one in newButton. The
* size of the new activeButton will be the Dimension
* value taken from the getActiveButtonSize method.
*
* @param newButton The new JButton that should be displayed
* as the activeButton in the GUI.
*/
protected void switchActiveButton(JButton newButton) {
Insets insets = new Insets(0, 0, 0, 0);
Dimension prefSize = getActiveButtonSize();
Dimension sizeDiff =
new Dimension((prefSize.width - newButton.getPreferredSize().width),
(prefSize.height - newButton.getPreferredSize().height));
/** If the button should be smaller, this part will do that. */
activeButton.setMinimumSize(prefSize);
activeButton.setMaximumSize(prefSize);
activeButton.setPreferredSize(prefSize);
/** If the button should be bigger, we will add to the margins. */
if (0 <= sizeDiff.width) {
insets.left += sizeDiff.width / 2;
insets.right += sizeDiff.width / 2 + sizeDiff.width % 2;
}
if (0 <= sizeDiff.height) {
insets.top += sizeDiff.height / 2;
insets.bottom += sizeDiff.height / 2 + sizeDiff.height % 2;
}
/** Change the activeButton to match our new button . */
activeButton.setMargin(insets);
activeButton.setIcon(newButton.getIcon());
activeButton.setText(newButton.getText());
activeButton.setToolTipText(newButton.getToolTipText());
activeButton.setActionCommand(newButton.getActionCommand());
}
/** Change the GUI to display a timer button
* instead of a recording/stop button. There
* will be one button displaying if the timer
* is just active and waiting to start a recording,
* and another button if the timer is recording.
*
* @param recording Should be true if the timer
* is recording, false if it is
* not.
*/
public void setTimerGUI(boolean recording) {
JButton setButton;
if (recording) {
setButton = timerRecButton;
} else {
setButton = timerButton;
}
switchActiveButton(setButton);
}
/** This is where the GUI is restored after encoding of
* a recorded film. This method is called from the
* stopAction() method. The GUI should not be restored from anywhere
* else, since that could mean a potential problem if
* the stopAction() method is still merging a file in the
* background, while another StopThread had been started.
*
* The GUI was changed in the recordAction() method.
*/
public void restoreGUI() {
switchActiveButton(recButton);
activeButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
/** This should be redundant (the mouseTimer is started in stopAction()),
* but this is kept just in case. */
if (!krutSettings.mouseTimer.isRunning())
krutSettings.startMouseTimer();
/** This allows the STOP action to have effect again. */
stopping = false;
/** Allow recording again */
recording = false;
/** Now we may need to change the file names for the next recording
* session. It depends on whether file names should be overwritten
* or not.
*/
saveQuery.imageFile = saveQuery.filterFile(saveQuery.imageFile);
saveQuery.audioFile = saveQuery.filterFile(saveQuery.audioFile);
/** This is a bit of an emergency solution. If we would only
* filterFile here, the file from the previos merge would not
* yet be created, and filterFile would return the same
* file name as we just used. So we need to manually increase
* the file index. This will have the unwanted effect that
* even if we record video-only or audio-only, the movie file name
* will still change. The presumption so far is that this is not
* a serious, or maybe even noticed, problem for the user.
*/
if (!krutSettings.saveEnumCheckbox.isSelected()) {
saveQuery.videoFile = saveQuery.filterFile(saveQuery.getNextFile(saveQuery.videoFile));
}
/** Update displayed file names. */
krutSettings.changeFileNames();
}
/** If an ActionEvent is fired from one of the
* buttons or the menu, this is where we'll end
* up. Since there is only one ActionListener
* (the Run_KRUT objet itself) listening to all
* the different objects, this method will use
* the getActionCommand() method of the incoming
* ActionEvent to separate them. It is therefore
* important that the buttons and menu items are
* given a proper ActionCommand.
*
* @param e The ActionEvent for this Action.
*/
public void actionPerformed(ActionEvent e) {
/** Get the ActiomCommand for this Action. */
String cmd = e.getActionCommand();
/** First we check if one of the buttons has been pressed. */
if (RECORD.equals(cmd)) {
recording = true;
recordAction();
} else if (SNAPSHOT.equals(cmd)) {
snapAction();
} else if (TIMER.equals(cmd)) {
timerAction();
} else if ("Timer active".equals(cmd)) {
timerActivatedAction();
} else if ("Timer recording".equals(cmd)) {
timerStartedAction();
} else if ("Timer stopped".equals(cmd)) {
timerStoppedAction();
} else if ("Special Stop".equals(cmd)) {
timerStoppedByStopButtonAction();
} else if (STOP.equals(cmd) && !stopping) {
/** If the stop button is clicked,
* start by disabling this method to prevent
* an eventual second click from launching
* another StopThread.
*/
stopping = true;
/** Start a new thread to handle the encoding of the movie,
* using low priority. In the course of this thread,
* the GUI is restored, and by the end of the thread
* everything is back to normal.
*/
StopThread stopThread = new StopThread();
stopThread.setPriority(Thread.MIN_PRIORITY);
stopThread.start();
} else if (MPOINTER.equals(cmd)) {
/** This starts the changing of the capture area
* by using the methods for the mouse pointer
* button in krutSettings.
*/
krutSettings.jButton1.doClick();
} else if (EMPTY.equals(cmd)) {
} else {
/** Now we know that the action was a menu event
* (checkboxes events are handled in the method
* itemStateChanged() below).
*/
JMenuItem source = (JMenuItem) (e.getSource());
if (source.getText() == "Show output window") {
outWindow.outFrame.setVisible(true);
return;
}
if (source.getText() == "Settings/Save Files") {
krutSettings.setVisible(true);
return;
}
if (source.getText() == "Timer") {
timer.setVisible(!timer.isVisible());
frame.pack();
return;
}
if (source.getText() == "Run speed test") {
if (!myGrabber.running) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
speedTest();
}
});
}
return;
}
}
}
/** Something has changed in one of the checkboxes.
*
* @param e The ItemEvent that caused the change.
*/
public void itemStateChanged(ItemEvent e) {
JMenuItem source = (JMenuItem) e.getSource();
if (source.getText() == "Video output") {
if (e.getStateChange() == ItemEvent.SELECTED) {
outWindow.out("Video recording enabled.");
outWindow.out("");
nextVideo = true;
krutSettings.setVideoCheckBox(true);
} else {
outWindow.out("Video recording disabled.");
outWindow.out("");
nextVideo = false;
krutSettings.setVideoCheckBox(false);
}
} else if (source.getText() == "Audio output") {
if (e.getStateChange() == ItemEvent.SELECTED) {
outWindow.out("Audio recording enabled.");
outWindow.out("");
nextAudio = true;
krutSettings.setAudioCheckBox(true);
} else {
outWindow.out("Audio recording disabled.");
outWindow.out("");
nextAudio = false;
krutSettings.setAudioCheckBox(false);
}
} else if (source.getText() == "Show Mouse") {
if (e.getStateChange() == ItemEvent.SELECTED) {
outWindow.out("Showing mouse position in video.");
outWindow.out("");
myGrabber.getMouse = true;
krutSettings.setMouseCheckBox(true);
} else {
outWindow.out("Hiding mouse position in video.");
outWindow.out("");
myGrabber.getMouse = false;
krutSettings.setMouseCheckBox(false);
}
} else if (source.getText() == "Follow Mouse") {
if (e.getStateChange() == ItemEvent.SELECTED) {
outWindow.out("Moving the screen recording area to follow the mouse pointer");
outWindow.out("Using the Preview Window is recommended.");
outWindow.out("");
myGrabber.followMouse = true;
} else {
outWindow.out("The screen recording area is fixed.");
outWindow.out("Chosen coordinates are available in the Krut Settings window.");
outWindow.out("");
myGrabber.followMouse = false;
}
} else if (source.getText() == "Preview Window") {
if (e.getStateChange() == ItemEvent.SELECTED) {
outWindow.out("Showing a preview of the film while recording.");
outWindow.out("This can be practical when using a non-fixed recording area.");
outWindow.out("");
// Start the preview window.
previewAction();
myGrabber.preview = true;
} else {
outWindow.out("Showing no preview of the recording.");
outWindow.out("");
/* Stop and remove the preview window, if it was active. */
if (myGrabber.preview) imageUtils.stopPreviewWindow();
myGrabber.preview = false;
}
}
}
/***************************************************************************
* Second part :
* This part of the code contains methods which are needed to perform
* the actions that are launched through the GUI.
**************************************************************************/
/** This method takes the snapshot */
private void snapAction() {
/** This changes video settings and file names. */
checkInited();
/** Take the snapshot */
myGrabber.snapshot();
outWindow.out("Snapshot taken, area : ");
Rectangle capRect = Settings.getCaptureRect();
outWindow.out(" " + capRect.x + ", " + capRect.y +", " + capRect.width + ", " + capRect.height);
outWindow.out("Saved to file: " + myGrabber.screenshotFile.getAbsolutePath());
/** Try to display the snapshot on screen. */
try {
// JFrame tempFrame = new JFrame(myGrabber.screenshotFile.getPath().toString());
snapShotFrame.setTitle(myGrabber.screenshotFile.getPath().toString());
/** imageUtils is a SnapShot object, initiated only once, in the declaration. */
/** load image from file. */
Image pic = imageUtils.loadPic(myGrabber.screenshotFile.getPath(), snapShotFrame);
BufferedImage paintBuff = new BufferedImage(pic.getWidth(snapShotFrame),
pic.getHeight(snapShotFrame), BufferedImage.TYPE_INT_RGB);
Graphics paintGraph = paintBuff.getGraphics();
paintGraph.drawImage(pic, 0, 0, snapShotFrame);
/** Paint image in a new snapshot window. */
imageUtils.createAndShowGUI(snapShotFrame, paintBuff);
} catch (OutOfMemoryError om) {
outWindow.out("Out of memory, could not display image.");
}
outWindow.out("");
/** Save the image. */
saveQuery.imageFile = saveQuery.filterFile(saveQuery.imageFile);
/** Update the file names. */
krutSettings.changeFileNames();
}
/** This method makes sure the preview window is running (if it should).
* This method is called from itemStateChanged */
private void previewAction() {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
/** imageUtils is a SnapShot object, initiated only once, in the declaration. */
Rectangle capRect = Settings.getCaptureRect();
imageUtils.initPreviewWindow(capRect.width, capRect.height);
}
});
}
/** If the timer button is pressed, we make sure that the
* timer window is visible.
*/
private void timerAction() {
if (!timer.isVisible()) {
timer.setVisible(true);
frame.pack();
}
}
/** When the timerToggleButton is toggled on
* this is the action that is performed.
*/
private void timerActivatedAction() {
if (!recording) setTimerGUI(true);
}
/** When the timerToggleButton sends a "Timer started"
* ActionEvent, this is the action that is performed.
*/
private void timerStartedAction() {
if (!recording) recButton.doClick();
else this.outWindow.out(
"Running recording detected, continuing old recording.");
/** When we return here after clicking the
* record button (or even if we didn't),
* a copy of the stop button will be showing in the
* GUI. The only way we can allow it to
* show is if we modify it so that it no
* longer does what it used to.
*/
activeButton.setActionCommand("Special Stop");
}
/** When the timerToggleButton is toggled off
* this is the action that is performed.
*/
private void timerStoppedAction() {
if (recording) stopButton.doClick();
else restoreGUI();
}
/** If recording is stopped by the stop button,
* while the timer is running, the timer will not
* be properly reset. This is a quick fix to this
* issue. The stop button displayed in the GUI
* when the timer is running is not a stop button,
* but a modified stop button that leads to this
* action instead. Here we click the "Stop Timer"
* button in the Timer GUI.
*/
private void timerStoppedByStopButtonAction() {
timer.getTimerButton().doClick();
}
/** This method handles both video and audio recording.
* Video and audio recording are interrupted when the stop
* button is pressed, which leads to the stopAction() method
* being run.
*/
private void recordAction() {
/** Stop the mouseTimer to save a very small
* amount of resources (no other purpose)
* (it is started again in stopAction()).
*/
krutSettings.stopMouseTimer();
/** Change the GUI, switch record and stop buttons. */
switchActiveButton(stopButton);
/** Check for critical parameter changes (filenames, video or
* audio settings). If there are any, they are handled here as well.
*/
checkInited();
/** Start the synchronized recording. */
long syncTime = System.currentTimeMillis();
mySampler.setSyncTime(syncTime);
myGrabber.setSyncTime(syncTime);
if (recAudio) {
/** We have to tell the ScreenGrabber that audio
* is also recording. This will have the effect
* that the ScreenGrabber will wait at the end of
* it's run() method, to achieve maximum sync
* between audio and video.
*/
myGrabber.audioRecording = true;
/** Start audio recording. */
mySampler.stopped = false;
mySampler.wakeUp();
outWindow.out("Recording audio");
}
if (recVideo) {
/** Start video recording. */
myGrabber.notFinished = true;
myGrabber.wakeUp();
outWindow.out("Recording video");
}
outWindow.out("");
/* If the user has closed the preview window without using
* the checkbox, it will be brought back here.
*/
if ((myGrabber.preview) && (imageUtils.previewFrame != null) &&
(!imageUtils.previewFrame.isShowing())) {
previewAction();
}
}
/** Stop recording. If video is recorded, audio
* waits for video to finish, in order to get sync.
* If both were running, the method then waits for both
* output files to be written before calling Merge.
*/
private void stopAction() {
/** Disable the activeButton, just to make
* things look good.
*/
activeButton.setActionCommand(EMPTY);
activeButton.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
/** Create a new EncodingProgressBar, and give it access
* to the outWindow
*/
myProgressBar = new EncodingProgressBar(outWindow);
myGrabber.myProgressBar = myProgressBar;
myProgressBar.setStatus(myProgressBar.ENCODING);
outWindow.out("Stopping recording, please wait ...");
/** Setting this flag tells the ScreenGrabber to stop recording.
*/
myGrabber.notFinished = false;
/** This flag stops the Sampler from recording if
* it shouldn't have started yet. If it is already
* recording (as it usually is), this flag prevents
* the sampler from trying to sync any audio recorded
* past this point. This is pretty important, since
* the last sample is usually very delayed due to the
* ScreenGrabber occupying all available resources. So
* the last sample will be lagging a lot behind.
*
* Note that the Sampler is still recording. This is
* because if the movie is to short, the Merge class
* will sometimes crash. In the ScreenGrabber there
* is a check to prevent this. When everything is
* ok, the ScreenGrabber sets it's recording flag to
* false, and then the Sampler can be stopped.
*
* If all three threads (this one, the Sampler and the
* ScreenGrabber) are running simultaneously, this should
* work ok. If only one of them runs at a time, it should
* work ok as well, as long as there is not another
* active thread filling the sample buffer for sound
* in the mean time.
*/
mySampler.stopped = true;
if (recVideo) {
/** This should not lock, because the ScreenGrabber does not
* do anything extraordinary up until this point.
*/
while (myGrabber.recording && (!myProgressBar.cancelled)) {
myGrabber.hold();
}
outWindow.out("Finished recording video to: " +
myGrabber.tempFile);
}
if (recAudio && (!myProgressBar.cancelled)) {
/** THIS IS A POINT WHERE THINGS CAN STILL CRASH.
* It would be better to put up some sort of system
* where you gave this method a certain amount of time,
* then said that audio recording has failed, and
* then reinitialized the sampler.
*/
mySampler.stopRecording();
/** Tell the ScreenGrabber that audio recording has
* now stopped, and the ScreenGrabber can start
* claiming resources again.
*/
myGrabber.audioRecording = false;
myGrabber.wakeUp();
}
/** This is a good place to start the mouse timer,
* since we once stopped it just because it didn't need to
* run during recording. (It was stopped in recordAction()).
*/
krutSettings.startMouseTimer();
/** Encode video */
if (recVideo && (!myProgressBar.cancelled)) {
myGrabber.encode();
}
/** Merging audio and video */
if (recVideo && recAudio) {
if (myProgressBar.cancelled || myGrabber.unRecoverableError) {
restoreGUI();
outWindow.out("Error creating mov file, aborting");
} else {
if (myGrabber.error) {
outWindow.out("Error recording");
outWindow.out("Attempting to proceed creating interrupted movie");
}
/** Holds for video track to be written. */
while (myGrabber.running && (!myProgressBar.cancelled))
myGrabber.hold();
/** Waiting for the sampler here, because if it
* crashes here, the user will be left with at least
* a complete film, and hopefully complete audio as
* well.
*/
while (mySampler.recording && (!myProgressBar.cancelled))
mySampler.hold();
outWindow.out("Finished recording sound to: " +
mySampler.audioFile.getPath());
myProgressBar.setStatus(myProgressBar.MERGING);
/** First we make sure that we can really write to
* the mov-file. If we can not, we will try a new
* one until we can.
*/
while (saveQuery.videoFile.exists() && !saveQuery.videoFile.delete()) {
saveQuery.videoFile =
saveQuery.filterFile(saveQuery.getNextFile(saveQuery.videoFile));
}
/** Merging here */
try {
/** First we get all the arguments to the Merge command
* stored in local parameters. */
String audioFile = "";
audioFile = mySampler.audioFile.toURL().toString();
String mergeArguments[] = {"-o", saveQuery.videoFile.toURL().toString(),
myGrabber.tempFile, audioFile};
EncodingProgressBar argBar = myProgressBar;
/** At this point it should be perfectly safe to
* reactivate the GUI and let the merging go on
* in the background.
*/
restoreGUI();
if (mergeAudioVideo(mergeArguments, argBar))
outWindow.out("Finished recording to: " +
mergeArguments[1]);
} catch (Exception e) {
restoreGUI();
System.out.println(e);
}
}
} else {
restoreGUI();
}
outWindow.out("");
}
/** Merges audio and video files into one media file.
*
* @param mergeArguments A String array containing in order:
* The String "-o",
* A String representation of the
* desired output file URL,
* A String representation of the URL to a mov-file
* containing the video data,
* A String representation of the URL to a file
* containing the audion data.
*
* myProgressBar An EncodingProgressBar that can be used to manually
* interrupt the merging procedure.
*
* @return true if Merge was completed, false if Merge was interrupted.
*/
private boolean mergeAudioVideo(String[] mergeArguments,
EncodingProgressBar myProgressBar) {
try {
new Merge(mergeArguments, myProgressBar);
if (!myProgressBar.cancelled) return true;
} catch (Exception e) {
outWindow.out("Error merging files");
outWindow.out("" + e);
} catch (OutOfMemoryError o) {
outWindow.out("Error merging files");
outWindow.out("" + o);
}
return false;
}
/** This method is run prior to starting a new recording.
* It is also run before speed tests and snap shots.
* It checks if the parameters for the
* Sampler and the ScreenGrabber has been changed
* since it was last created. If they have, they
* are updated, and if needed, the affected object
* is reinited, before recording starts.
* Changes that force the ScreenGrabber to reinit
* are changes to cap size, and changes to video encoding
* quality. Changes that force the Sampler to reinit
* are changes to audio encoding quality. File name
* changes are also checked here.
*/
private void checkInited() {
/** Used to flag that the ScreenGrabber needs reinitialization */
boolean grabberCriticalChange = false;
/** Used to flag that the Sampler needs reinitialization */
boolean samplerCriticalChange = false;
outWindow.out("Checking if reinitialization is needed");
outWindow.out("");
/** Should we record video & audio (these are flags). */
recVideo = nextVideo;
recAudio = nextAudio;
/** Should we sync audio to the system clock (flag)? */
mySampler.syncAudio = krutSettings.syncCheckbox.isSelected();
/** Calculate the ratio between playback and recording fps,
* and give this ratio to the sampler. This is used to synchronize
* audio to video, even if video is played back at different speeds.
*/
mySampler.speed = fpsQuery.plb / ((float) fpsQuery.fps);
/** Optimize some sampler parameters for the different cases
* of recording speed. These are empirical values (2006), and they may
* not be very good on new systems. There are most certainly better
* parameters than these in either case, but they have on average
* given pretty good audio quality.
*/
if (mySampler.speed < 1) {
mySampler.sleepTime = 900;
outWindow.out("Slow motion");
} else if (1 < mySampler.speed) {
mySampler.sleepTime = 100;
mySampler.maxAhead = (int) (2000 /
(mySampler.sleepTime * (1 - mySampler.speed)));
outWindow.out("Fast motion");
} else {
mySampler.sleepTime = 450;
mySampler.countTimesLag = 2;
outWindow.out("Normal speed mode");
}
outWindow.out("");
/** Check if the capture area has changed.
* If it has, the ScreenGrabber needs to be reinitialized. */
if (capQuery.altered) {
Rectangle capRect = new Rectangle(capQuery.xVal,
capQuery.yVal,
capQuery.widthVal,
capQuery.heightVal);
Settings.setCaptureRect(capRect);
grabberCriticalChange = true;
capQuery.altered = false;
}
/** Check if any of the fps values have changed. */
if (fpsQuery.altered) {
myGrabber.setFps(fpsQuery.fps, fpsQuery.plb);
fpsQuery.altered = false;
}
/** Check if the video encoding quality has changed.
* If it has, the ScreenGrabber needs to be reinitialized. */
if (encSlider.altered) {
myGrabber.encQuality = encSlider.quality / 100f;
grabberCriticalChange = true;
encSlider.altered = false;
}
/** Check if any of the audio parameters have changed.
* If they have, the Sampler needs to be reinitialized. */
if (soundQuery.altered) {
mySampler.frequency = soundQuery.frequency;
mySampler.sampleSize = (soundQuery.sixteenBit ? 16 : 8);
mySampler.channels = (soundQuery.stereo ? 2 : 1);
samplerCriticalChange = true;
soundQuery.altered = false;
}
/** Update all the save file names. */
setAllSaveFiles();
/** Reinitialize the ScreenGrabber if we have to. */
if (grabberCriticalChange) {
outWindow.out("ScreenGrabber critical parameters have been changed");
outWindow.out("Reinitializing...");
try {
myGrabber.init();
} catch (IOException e) {
outWindow.out("" + e);
}
outWindow.out("Done!");
outWindow.out("");
}
/** Reinitialize the Sampler if we have to. */
if (samplerCriticalChange) {
outWindow.out("Sampler critical parameters have been changed");
outWindow.out("Reinitializing...");
mySampler.init();
outWindow.out("Done!");
outWindow.out("");
}
}
/** Runs the speed test. This gives a very rough estimate
* of the maximum fps that can be used for recording without
* frame loss. It does not consider hard drive limitations,
* or delays caused by audio recording.
*/
public void speedTest() {
try {
/** First reinitialize the ScreenGrabber if needed. */
checkInited();
outWindow.out("");
outWindow.out("Running new speed test...");
double avgTime = myGrabber.testCapTime();
outWindow.out("Average frame snapshot time (ms): " + avgTime);
double[] testValues = myGrabber.testEnc();
double avgSize = testValues[0];
double avgEncTime = testValues[1];
outWindow.out("Average frame size (bytes): " + avgSize);
outWindow.out("Average encryption time per frame (ms): "
+ avgEncTime + newline);
myGrabber.init();
outWindow.out("Estimated frame cap time (ms): " + (avgTime + avgEncTime));
outWindow.out("Estimated maximum fps without frameloss:" + (1000 / (avgTime + avgEncTime)));
if (myGrabber.time < avgTime + avgEncTime)
outWindow.out("WARNING! Fps is set higher than recommended");
outWindow.out("");
// outWindow.out("Maximum number of frames: " + myGrabber.maxNOPics);
// outWindow.out("Max total cap time (s): " +
// (myGrabber.time * myGrabber.maxNOPics / 1000));
// outWindow.out("");
outWindow.out("For an accurate estimate of these parameters,");
outWindow.out("make sure there is an average image in the cap area.");
outWindow.out("Hard drive and sound recording will cause additional frame loss.");
outWindow.out("NOTE: This function was primarily used in development.");
outWindow.out("It is now inaccurate, but has been kept for comparative purposes.");
outWindow.out("");
} catch (IOException e) {
System.err.println(e);
}
}
/** Copies the save files from the SaveFileChooser
* to the ScreenGrabber and the Sampler. This
* method is called both from the checkInited()
* method and from the init() method. The saveQuery.altered
* flag which tells those methods to call this method is
* changed in the restoreGUI(), snapAction() and
* checkInited() methods.
*/
private void setAllSaveFiles() {
mySampler.setAudioFile((saveQuery.audioFile.getAbsoluteFile()).toString());
myGrabber.screenshotFile = saveQuery.imageFile;
try {
File path = new File(saveQuery.videoFile.toString());
path = path.getAbsoluteFile();
File tempFile = new File(path.getParentFile().getAbsolutePath() + path.separatorChar + "temp.mov");
/** Get the new tempFile. */
tempFile = saveQuery.filterFile(tempFile);
while (tempFile.exists() && (!tempFile.delete())) {
tempFile = saveQuery.filterFile(saveQuery.getNextFile(tempFile));
}
tempFile = saveQuery.filterFile(tempFile);
myGrabber.tempFile = tempFile.toURL().toString();
} catch (MalformedURLException mue) {
System.err.println(mue);
}
}
/** Wait for a given amount of milliseconds.
*
* @param millis The amount of milliseconds this
* method should hold.
*/
private synchronized void timedHold(int millis) {
try {
wait(millis);
} catch (InterruptedException ie) {
System.err.println(ie);
}
}
/***************************************************************************
* Third part :
* This part of the code contains methods which initialize the class.
**************************************************************************/
/** This method just creates the ScreenGrabber.
* This method is called from the init() method,
* in a separate thread, to try to get some flow in
* the display of the program.
*/
private void createScreenGrabber() {
/** Make the ScreenGrabber. It will start as a seperate
* thread, with highest priority. The constructor to
* ScreenGrabber will just perform a speed test, and
* then it's done. Both capRect and startFps are
* global parameters that are already initiated.
*/
myGrabber = new ScreenGrabber(startFps);
myGrabber.setPriority(Thread.MAX_PRIORITY);
/** Starts the ScreenGrabber thread. This thread will
* basically go through the ScreenGrabber.init() method,
* and then wait, until recording is started. See the
* ScreenGrabber.run() method.
*/
myGrabber.start();
}
/** This method just creates the Sampler.
* This method is called from the init() method,
* in a separate thread, to get some flow in
* the display of the program.
*/
private void createSampler() {
/** Make the Sampler, with the defaultFileName
* (a local parameter to the Sampler class) as the
* name of the file that will be created by the
* sampler. The Sampler is a separate thread, with
* highest priority. The constructor will just
* create the save file, and then it's done.
*/
mySampler = new Sampler();
/** Pass on initial parameters to the sampler */
mySampler.channels = (startStereo ? 2 : 1);
mySampler.sampleSize = (startSixteen ? 16 : 8);
mySampler.frequency = startFrequency;
mySampler.setPriority(Thread.MAX_PRIORITY);
/** Starts the Sampler thread. This thread will
* basically go through the Sampler.init() method,
* and then wait, until recording is started. See the
* Sampler.run() method.
*/
mySampler.start();
}
/** The starting message of KRUT.
*/
private void printStartMessages() {
outWindow.out("Welcome to Krut Computer Recorder.");
outWindow.out("This program is licenced under the GPL, see readme.txt for details.");
outWindow.out("");
outWindow.out("To record audio, you need to enable the \"Wave out-mix\" (or similar),");
outWindow.out("under recording in your sound controls.");
outWindow.out("");
outWindow.out("To record video from a media player you need to disable video acceleration.");
outWindow.out("");
outWindow.out("A tempfile (temp*.mov) without sound is always created");
outWindow.out("in the path of you movie file.");
outWindow.out("");
outWindow.out("You can close this output window at any time (re-open through \"Menu\").");
outWindow.out("");
}
/** Create and show the main JFrame of Krut. */
private void createMainFrame() {
try {
// javax.swing.UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
// javax.swing.UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getCrossPlatformLookAndFeelClassName());
// javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
System.err.println(ex);
}
JFrame.setDefaultLookAndFeelDecorated(true);
/** Create the main frame of the program. */
frame = new JFrame("Krut Computer Recorder");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
/** The old layouts are kept in the code,
* since it is faster to find them this way
* than to try and find an old piece of code
* if they are needed.
*/
//new GridLayout(1, 2));
/** Create the main JPanel in the main frame. */
JPanel converterPanel = new JPanel();
/** Create the JToolBar for the main window. */
JToolBar toolBar = new JToolBar();
/** Add the buttons to toolbar */
addButtons(toolBar);
//frame.setOpaque(true);
toolBar.setFloatable(false);
/** Add the menu bar
* and the toolbar with buttons to the panel.
*/
JMenuBar menuBar = createMenuBar();
menuBar.add(toolBar);
converterPanel.add(menuBar);
/** Display the main window. It consists of
* two parts, the first is the buttons and the
* menu. The second, which is not visible at
* startup, is the timer window. This gets added
* to the frame in the activateGUI method.
*/
frame.getContentPane().setLayout(new java.awt.GridBagLayout());
/** Make the first part of the main window visible,
* buttons and menu. They will remain unactivated
* until the activateGUI has been executed.
*/
java.awt.GridBagConstraints gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
frame.getContentPane().add(converterPanel, gridBagConstraints);
recButton.setEnabled(false);
activeButton.setEnabled(false);
snapshotButton.setEnabled(false);
stopButton.setEnabled(false);
mouseButton.setEnabled(false);
menu.setEnabled(false);
/** This Frame needs to be initiated before the GUI is visible.
*/
snapShotFrame = new JFrame("Snapshot");
/** Finished, pack and display frame */
frame.pack();
/** This is the original place of
* frame.setVisible(true);
* It was moved to the end of the activateGUI method.
*/
frame.setVisible(true);
}
/** Main init function.
* First the ScreenGrabber and the Sampler are created,
* then all classes for the menu bar functions are properly
* inited. After that, the main window is created and made
* visible.
*/
public void init() throws IOException {
System.out.println("Starting, please wait.");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
createMainFrame();
outWindow = new OutputText();
outWindow.init(0, frame.getSize().height + 50);
outWindow.out("Starting, please wait for the GUI to activate.");
createScreenGrabber();
createSampler();
krutSettings = new KrutSettings(startFps, startEncQuality,
startStereo, startSixteen, startFrequency);
timer = new KrutTimer(frame);
activateGUI();
}
/** Start a new thread, that waits for the other threads
* to finish, and then activates the buttons in the GUI.
*/
private void activateGUI() {
/** The Run_KRUT class need to have access to the
* classes making up the krutSettings window because
* they were originally all that there was, and unless the
* program is to be very rewritten, this class, along with
* the Sampler and the ScreenGrabber, still
* sometimes need to have direct access to them.
*/
capQuery = krutSettings.getCapSizeQuery();
fpsQuery = krutSettings.getFPSQuery();
encSlider = krutSettings.getQualitySlider();
soundQuery = krutSettings.getSoundQuery();
saveQuery = krutSettings.getSaveFileChooser();
/** Give classes in the GUI access to their output window */
capQuery.myOutput = outWindow;
fpsQuery.myOutput = outWindow;
encSlider.myOutput = outWindow;
soundQuery.myOutput = outWindow;
saveQuery.myOutput = outWindow;
/** This gives the krutSettings object access to the
* three checkboxes that appear both in the menu and
* the krutSettings window.
*/
krutSettings.setVChkBoxMenuItem(vcbMenuItem);
krutSettings.setAChkBoxMenuItem(acbMenuItem);
krutSettings.setMChkBoxMenuItem(mcbMenuItem);
/** Set the current save files in the SaveQuery */
saveQuery.imageFile = saveQuery.filterFile(imageFile);
saveQuery.audioFile = saveQuery.filterFile(audioFile);
saveQuery.videoFile = saveQuery.filterFile(movieFile);
/** Give the Sampler and the ScreenGrabber a copy
* of the saveQuery, so that they can know whether
* to overwrite files or not.
*/
mySampler.mySaveQuery = saveQuery;
myGrabber.mySaveQuery = saveQuery;
myGrabber.mySnapShot = this.imageUtils;
imageUtils.setFps(startFps);
/** Update the save file names in the text fields in
* krutSettings
*/
krutSettings.changeFileNames();
/** We should listen to events from the
* timer toggle button.
*/
timer.getTimerButton().addActionListener(this);
timer.setOutput(outWindow);
/** Print the start messages in the output window */
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
printStartMessages();
/** Once the timer is created, it is added to the main frame
* of the GUI, although it is not made visible until the
* user chooses "Timer" from the menu.
*/
java.awt.GridBagConstraints gridBagConstraints =
new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
frame.getContentPane().add(timer, gridBagConstraints);
timer.setVisible(false);
/** Activate the buttons and the menu */
recButton.setEnabled(true);
activeButton.setEnabled(true);
snapshotButton.setEnabled(true);
stopButton.setEnabled(true);
mouseButton.setEnabled(true);
menu.setEnabled(true);
}
});
/** This is moved here from the marked position in the
* init method. Originally, the GUI was supposed to
* be showing, with inactive buttons and menu, while
* the rest of the objects were created.
* But in reality, java waited for all the other threads
* creating the different components to finish
* before the drawing of the GUI was completed. This
* looked ugly.
* Rather than restructuring the initialization of the
* program once more, the GUI is for the moment
* just kept invisible until everything is initialized.
*/
// frame.setVisible(true);
}
private static void createAndShowGUI() {
logger.info("Creating and showing GUI");
Run_KRUT newContentPane = new Run_KRUT();
try {
newContentPane.init();
} catch (IOException e) {
}
}
public static void main(String[] args) throws IOException {
createAndShowGUI();
}
}