[nexuiz-commits] r8633 - in trunk/misc/tools/NexuizDemoRecorder: . src src/main src/main/java src/main/java/com src/main/java/com/nexuiz src/main/java/com/nexuiz/demorecorder src/main/java/com/nexuiz/demorecorder/application src/main/java/com/nexuiz/demorecorder/application/democutter src/main/java/com/nexuiz/demorecorder/application/jobs src/main/java/com/nexuiz/demorecorder/application/plugins src/main/java/com/nexuiz/demorecorder/main src/main/java/com/nexuiz/demorecorder/ui src/main/java/com/nexuiz/demorecorder/ui/swinggui src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils src/main/resources src/main/resources/help src/main/resources/help/JavaHelpSearch src/main/resources/help/html src/main/resources/help/html/images src/main/resources/icons src/test
DONOTREPLY at icculus.org
DONOTREPLY at icculus.org
Thu Feb 11 10:54:58 EST 2010
Author: greenmarine
Date: 2010-02-11 10:54:58 -0500 (Thu, 11 Feb 2010)
New Revision: 8633
Added:
trunk/misc/tools/NexuizDemoRecorder/pom.xml
trunk/misc/tools/NexuizDemoRecorder/src/
trunk/misc/tools/NexuizDemoRecorder/src/main/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java
trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/about.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelp.hs
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpIndex.xml
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpTOC.xml
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JHelpDev Project.xml
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS.TAB
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/OFFSETS
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/POSITIONS
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/SCHEMA
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/TMAP
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/Map.jhm
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-how-it-works.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-prelim-stop.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-table-settings.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-topics.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/basic_tutorial.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/changelog.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/compat-limitations.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/credits.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/faq.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_job.gif
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_template.gif
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/customize_tables.gif
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/edit_job_vdub.gif
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/main_window.gif
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_dialog.gif
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_global_vdub.gif
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/introduction.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/license.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/open-save.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-architecture.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-virtualdub.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/preferences.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/templates.html
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/advanced.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit_add.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editclear.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editcopy.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editdelete.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/exit.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/fileopen.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/filesave.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/help.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/info.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/package.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_pause.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_play.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart_blue.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/status_unknown.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/view_right_p.png
trunk/misc/tools/NexuizDemoRecorder/src/main/resources/jsmooth exe project.jsmooth
trunk/misc/tools/NexuizDemoRecorder/src/test/
trunk/misc/tools/NexuizDemoRecorder/src/test/java/
Log:
commit v0.2 of Nexuiz Demo Recorder
Added: trunk/misc/tools/NexuizDemoRecorder/pom.xml
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/pom.xml (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/pom.xml 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,83 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>NexuizDemoRecorder</groupId>
+ <artifactId>NexuizDemoRecorder</artifactId>
+ <packaging>jar</packaging>
+ <version>0.2</version>
+ <name>NexuizDemoRecorder</name>
+ <url>http://maven.apache.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>com.miglayout</groupId>
+ <artifactId>miglayout</artifactId>
+ <version>3.7.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.swinglabs</groupId>
+ <artifactId>swingx</artifactId>
+ <version>1.6</version>
+ <exclusions>
+ <!-- Exclude unneeded libs that have been transitively resolved for SwingX -->
+ <exclusion>
+ <groupId>org.swinglabs</groupId>
+ <artifactId>swing-worker</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.jhlabs</groupId>
+ <artifactId>filters</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>javax.help</groupId>
+ <artifactId>javahelp</artifactId>
+ <version>2.0.02</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.0.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <addClasspath>true</addClasspath>
+ <classpathPrefix>lib/</classpathPrefix>
+ <mainClass>com.nexuiz.demorecorder.main.Driver</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/lib</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,444 @@
+package com.nexuiz.demorecorder.application;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.ServiceLoader;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.nexuiz.demorecorder.application.jobs.EncoderJob;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.jobs.RecordsDoneJob;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.DemoRecorderUI;
+
+public class DemoRecorderApplication {
+
+ public static class Preferences {
+ public static final String OVERWRITE_VIDEO_FILE = "Overwrite final video destination file if it exists";
+ public static final String DISABLE_RENDERING = "Disable rendering while fast-forwarding";
+ public static final String DISABLE_SOUND = "Disable sound while fast-forwarding";
+ public static final String FFW_SPEED_FIRST_STAGE = "Fast-forward speed (first stage)";
+ public static final String FFW_SPEED_SECOND_STAGE = "Fast-forward speed (second stage)";
+ public static final String DO_NOT_DELETE_CUT_DEMOS = "Do not delete cut demos";
+ public static final String JOB_NAME_APPEND_DUPLICATE = "Append this suffix to job-name when duplicating jobs";
+
+ public static final String[] PREFERENCES_ORDER = {
+ OVERWRITE_VIDEO_FILE,
+ DISABLE_RENDERING,
+ DISABLE_SOUND,
+ FFW_SPEED_FIRST_STAGE,
+ FFW_SPEED_SECOND_STAGE,
+ DO_NOT_DELETE_CUT_DEMOS,
+ JOB_NAME_APPEND_DUPLICATE
+ };
+ }
+
+ public static final String PREFERENCES_DIRNAME = "settings";
+ public static final String LOGS_DIRNAME = "logs";
+ public static final String PLUGINS_DIRNAME = "plugins";
+ public static final String APP_PREFERENCES_FILENAME = "app_preferences.xml";
+ public static final String JOBQUEUE_FILENAME = "jobs.dat";
+
+ public static final int STATE_WORKING = 0;
+ public static final int STATE_IDLE = 1;
+
+ private RecorderJobPoolExecutor poolExecutor;
+ private List<RecordJob> jobs;
+ private NDRPreferences preferences = null;
+ private List<DemoRecorderUI> registeredUserInterfaces;
+ private List<EncoderPlugin> encoderPlugins;
+ private int state = STATE_IDLE;
+
+ public DemoRecorderApplication() {
+ poolExecutor = new RecorderJobPoolExecutor();
+ jobs = new CopyOnWriteArrayList<RecordJob>();
+ this.registeredUserInterfaces = new ArrayList<DemoRecorderUI>();
+ this.encoderPlugins = new ArrayList<EncoderPlugin>();
+ this.getPreferences();
+ this.loadPlugins();
+ this.configurePlugins();
+ this.loadJobQueue();
+ }
+
+ public void setPreference(String category, String preference, boolean value) {
+ this.preferences.setProperty(category, preference, String.valueOf(value));
+ }
+
+ public void setPreference(String category, String preference, int value) {
+ this.preferences.setProperty(category, preference, String.valueOf(value));
+ }
+
+ public void setPreference(String category, String preference, String value) {
+ this.preferences.setProperty(category, preference, value);
+ }
+
+ public NDRPreferences getPreferences() {
+ if (this.preferences == null) {
+ this.preferences = new NDRPreferences();
+ this.createPreferenceDefaultValues();
+ File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);
+ if (preferencesFile.exists()) {
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(preferencesFile);
+ this.preferences.loadFromXML(fis);
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the application preferences file!", e, true);
+ }
+ }
+ }
+
+ return this.preferences;
+ }
+
+ private void createPreferenceDefaultValues() {
+ this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE, "false");
+ this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING, "true");
+ this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND, "true");
+ this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE, "100");
+ this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE, "10");
+ this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS, "false");
+ this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE, " duplicate");
+ }
+
+ public void savePreferences() {
+ File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);
+ if (!preferencesFile.exists()) {
+ try {
+ preferencesFile.createNewFile();
+ } catch (IOException e) {
+ File parentDir = preferencesFile.getParentFile();
+ if (!parentDir.exists()) {
+ try {
+ if (parentDir.mkdirs() == true) {
+ try {
+ preferencesFile.createNewFile();
+ } catch (Exception ex) {}
+ }
+ } catch (Exception ex) {}
+ }
+ }
+ }
+
+ if (!preferencesFile.exists()) {
+ DemoRecorderException ex = new DemoRecorderException("Could not create the preferences file " + preferencesFile.getAbsolutePath());
+ DemoRecorderUtils.showNonCriticalErrorDialog(ex);
+ return;
+ }
+
+ FileOutputStream fos;
+ try {
+ fos = new FileOutputStream(preferencesFile);
+ } catch (FileNotFoundException e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath() + ". Unsufficient rights?", e, true);
+ return;
+ }
+ try {
+ this.preferences.storeToXML(fos, null);
+ } catch (IOException e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath(), e, true);
+ }
+ }
+
+ public List<RecordJob> getRecordJobs() {
+ return new ArrayList<RecordJob>(this.jobs);
+ }
+
+ public void startRecording() {
+ if (this.state != STATE_WORKING) {
+ this.state = STATE_WORKING;
+
+ for (RecordJob currentJob : this.jobs) {
+ if (currentJob.getState() == RecordJob.State.WAITING) {
+ this.poolExecutor.runJob(currentJob);
+ }
+ }
+
+ //notify ourself when job is done
+ this.poolExecutor.runJob(new RecordsDoneJob(this));
+ }
+ }
+
+ public void recordSelectedJobs(List<RecordJob> jobList) {
+ if (this.state == STATE_IDLE) {
+ this.state = STATE_WORKING;
+ for (RecordJob currentJob : jobList) {
+ if (currentJob.getState() == RecordJob.State.WAITING) {
+ this.poolExecutor.runJob(currentJob);
+ }
+ }
+
+ //notify ourself when job is done
+ this.poolExecutor.runJob(new RecordsDoneJob(this));
+ }
+ }
+
+ public void executePluginForSelectedJobs(EncoderPlugin plugin, List<RecordJob> jobList) {
+ if (this.state == STATE_IDLE) {
+ this.state = STATE_WORKING;
+ for (RecordJob currentJob : jobList) {
+ if (currentJob.getState() == RecordJob.State.DONE) {
+ this.poolExecutor.runJob(new EncoderJob(currentJob, plugin));
+ }
+ }
+
+ //notify ourself when job is done
+ this.poolExecutor.runJob(new RecordsDoneJob(this));
+ }
+ }
+
+ public void notifyAllJobsDone() {
+ this.state = STATE_IDLE;
+
+ //notify all UIs
+ for (DemoRecorderUI currentUI : this.registeredUserInterfaces) {
+ currentUI.recordingFinished();
+ }
+ }
+
+ public synchronized void stopRecording() {
+ if (this.state == STATE_WORKING) {
+ //clear the queue of the threadpoolexecutor and add the GUI/applayer notify job again
+ this.poolExecutor.clearUnfinishedJobs();
+ this.poolExecutor.runJob(new RecordsDoneJob(this));
+ }
+ }
+
+ public RecordJob createRecordJob(
+ String name,
+ File enginePath,
+ String engineParameters,
+ File demoFile,
+ String relativeDemoPath,
+ File dpVideoPath,
+ File videoDestination,
+ String executeBeforeCap,
+ String executeAfterCap,
+ float startSecond,
+ float endSecond
+ ) {
+ int jobIndex = -1;
+ if (name == null || name.equals("")) {
+ //we don't have a name, so use a generic one
+ jobIndex = this.getNewJobIndex();
+ name = "Job " + jobIndex;
+ } else {
+ //just use the name and keep jobIndex at -1. Jobs with real names don't need an index
+ }
+
+
+
+ RecordJob newJob = new RecordJob(
+ this,
+ name,
+ jobIndex,
+ enginePath,
+ engineParameters,
+ demoFile,
+ relativeDemoPath,
+ dpVideoPath,
+ videoDestination,
+ executeBeforeCap,
+ executeAfterCap,
+ startSecond,
+ endSecond
+ );
+ this.jobs.add(newJob);
+ this.fireUserInterfaceUpdate(newJob);
+
+ return newJob;
+ }
+
+ public synchronized boolean deleteRecordJob(RecordJob job) {
+ if (!this.jobs.contains(job)) {
+ return false;
+ }
+
+ //don't delete jobs that are scheduled for execution
+ if (this.poolExecutor.getJobList().contains(job)) {
+ return false;
+ }
+
+ this.jobs.remove(job);
+ return true;
+ }
+
+ public void addUserInterfaceListener(DemoRecorderUI ui) {
+ this.registeredUserInterfaces.add(ui);
+ }
+
+ /**
+ * Makes sure that all registered user interfaces can update their view/display.
+ * @param job either a job that's new to the UI, or one the UI already knows but of which details changed
+ */
+ public void fireUserInterfaceUpdate(RecordJob job) {
+ for (DemoRecorderUI ui : this.registeredUserInterfaces) {
+ ui.RecordJobPropertiesChange(job);
+ }
+ }
+
+ public int getNewJobIndex() {
+ int jobIndex;
+ if (this.jobs.size() == 0) {
+ jobIndex = 1;
+ } else {
+ int greatestIndex = -1;
+ for (RecordJob j : this.jobs) {
+ if (j.getJobIndex() > greatestIndex) {
+ greatestIndex = j.getJobIndex();
+ }
+ }
+ if (greatestIndex == -1) {
+ jobIndex = 1;
+ } else {
+ jobIndex = greatestIndex + 1;
+ }
+ }
+
+ return jobIndex;
+ }
+
+ private void loadJobQueue() {
+ File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);
+ this.loadJobQueue(defaultFile);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void loadJobQueue(File path) {
+ if (!path.exists()) {
+ return;
+ }
+
+ try {
+ FileInputStream fin = new FileInputStream(path);
+ ObjectInputStream ois = new ObjectInputStream(fin);
+ this.jobs = (List<RecordJob>) ois.readObject();
+ for (RecordJob currentJob : this.jobs) {
+ currentJob.setAppLayer(this);
+ }
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the job queue file " + path.getAbsolutePath(), e, true);
+ }
+
+ }
+
+ public void saveJobQueue() {
+ File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);
+ this.saveJobQueue(defaultFile);
+ }
+
+ public void saveJobQueue(File path) {
+ if (!path.exists()) {
+ try {
+ path.createNewFile();
+ } catch (IOException e) {
+ File parentDir = path.getParentFile();
+ if (!parentDir.exists()) {
+ try {
+ if (parentDir.mkdirs() == true) {
+ try {
+ path.createNewFile();
+ } catch (Exception ex) {}
+ }
+ } catch (Exception ex) {}
+ }
+ }
+ }
+
+ String exceptionMessage = "Could not save the job queue file " + path.getAbsolutePath();
+
+ if (!path.exists()) {
+ DemoRecorderException ex = new DemoRecorderException(exceptionMessage);
+ DemoRecorderUtils.showNonCriticalErrorDialog(ex);
+ return;
+ }
+
+ //make sure that for the next start of the program the state is set to waiting again
+ for (RecordJob job : this.jobs) {
+ if (job.getState() == RecordJob.State.PROCESSING) {
+ job.setState(RecordJob.State.WAITING);
+ }
+ job.setAppLayer(null); //we don't want to serialize the app layer!
+ }
+
+ try {
+ FileOutputStream fout = new FileOutputStream(path);
+ ObjectOutputStream oos = new ObjectOutputStream(fout);
+ oos.writeObject(this.jobs);
+ oos.close();
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+ }
+
+ //we sometimes also save the jobqueue and don't exit the program, so restore the applayer again
+ for (RecordJob job : this.jobs) {
+ job.setAppLayer(this);
+ }
+ }
+
+ public void shutDown() {
+ this.poolExecutor.shutDown();
+ this.savePreferences();
+ this.saveJobQueue();
+ }
+
+ public int getState() {
+ return this.state;
+ }
+
+ private void loadPlugins() {
+ File pluginDir = DemoRecorderUtils.computeLocalFile(PLUGINS_DIRNAME, "");
+
+ if (!pluginDir.exists()) {
+ pluginDir.mkdir();
+ }
+
+ File[] jarFiles = pluginDir.listFiles();
+
+ List<URL> urlList = new ArrayList<URL>();
+ for (File f : jarFiles) {
+ try {
+ urlList.add(f.toURI().toURL());
+ } catch (MalformedURLException ex) {}
+ }
+ ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
+ URL[] urls = new URL[urlList.size()];
+ urls = urlList.toArray(urls);
+ URLClassLoader classLoader = new URLClassLoader(urls, parentLoader);
+
+ ServiceLoader<EncoderPlugin> loader = ServiceLoader.load(EncoderPlugin.class, classLoader);
+ for (EncoderPlugin implementation : loader) {
+ this.encoderPlugins.add(implementation);
+ }
+ }
+
+ private void configurePlugins() {
+ for (EncoderPlugin plugin : this.encoderPlugins) {
+ plugin.setApplicationLayer(this);
+ Properties pluginPreferences = plugin.getGlobalPreferences();
+ for (Object preference : pluginPreferences.keySet()) {
+ String preferenceString = (String) preference;
+
+ if (this.preferences.getProperty(plugin.getName(), preferenceString) == null) {
+ String defaultValue = pluginPreferences.getProperty(preferenceString);
+ this.preferences.setProperty(plugin.getName(), preferenceString, defaultValue);
+ }
+ }
+ }
+ }
+
+ public List<EncoderPlugin> getEncoderPlugins() {
+ return encoderPlugins;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,13 @@
+package com.nexuiz.demorecorder.application;
+
+public class DemoRecorderException extends RuntimeException {
+
+ private static final long serialVersionUID = 965053013957793155L;
+ public DemoRecorderException(String message) {
+ super(message);
+ }
+ public DemoRecorderException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,95 @@
+package com.nexuiz.demorecorder.application;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jdesktop.swingx.JXErrorPane;
+import org.jdesktop.swingx.error.ErrorInfo;
+
+public class DemoRecorderUtils {
+
+ public static void showNonCriticalErrorDialog(Throwable e) {
+ if (!(e instanceof DemoRecorderException)) {
+ e = new DemoRecorderException("Internal error", e);
+ }
+ ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);
+ JXErrorPane.showDialog(null, info);
+ }
+
+ /**
+ * Shows an error dialog that contains the stack trace, catching the exception so that the program flow
+ * won't be interrupted.
+ * This method will maybe wrap e in a DemoRecorderException with the given message.
+ * @param customMessage
+ * @param e
+ * @param wrapException set to true if Exception should be wrapped into a DemoRecorderException
+ */
+ public static void showNonCriticalErrorDialog(String customMessage, Throwable e, boolean wrapException) {
+ Throwable ex = e;
+ if (wrapException && !(e instanceof DemoRecorderException)) {
+ ex = new DemoRecorderException(customMessage, e);
+ }
+
+ ErrorInfo info = new ErrorInfo("Error occurred", ex.getMessage(), null, null, ex, null, null);
+ JXErrorPane.showDialog(null, info);
+ }
+
+ public static File computeLocalFile(String subDir, String fileName) {
+ String path = System.getProperty("user.dir");
+ if (subDir != null && !subDir.equals("")) {
+ path += File.separator + subDir;
+ }
+ path += File.separator + fileName;
+ return new File(path);
+ }
+
+ /**
+ * Returns just the name of the file for a given File. E.g. if the File points to
+ * /home/someuser/somedir/somefile.end the function will return "somefile.end"
+ * @param file
+ * @return just the name of the file
+ */
+ public static String getJustFileNameOfPath(File file) {
+ String fileString = file.getAbsolutePath();
+ int lastIndex = fileString.lastIndexOf(File.separator);
+ String newString = fileString.substring(lastIndex+1, fileString.length());
+ return newString;
+ }
+
+ /**
+ * Attempts to create an empty file (unless it already exists), including the creation
+ * of parent directories. If it succeeds to do so (or if the file already existed), true
+ * will be returned. Otherwise false will be returned
+ * @param file the file to be created
+ * @return true if file already existed or could successfully created, false otherwise
+ */
+ public static boolean attemptFileCreation(File file) {
+ if (!file.exists()) {
+ try {
+ file.createNewFile();
+ return true;
+ } catch (IOException e) {
+ File parentDir = file.getParentFile();
+ if (!parentDir.exists()) {
+ try {
+ if (parentDir.mkdirs() == true) {
+ try {
+ file.createNewFile();
+ return true;
+ } catch (Exception ex) {}
+ }
+ } catch (Exception ex) {}
+ }
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ public static final String getFileExtension(File file) {
+ String fileName = file.getAbsolutePath();
+ String ext = (fileName.lastIndexOf(".") == -1) ? "" : fileName.substring(fileName.lastIndexOf(".") + 1,fileName.length());
+ return ext;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,66 @@
+package com.nexuiz.demorecorder.application;
+
+import java.util.Properties;
+
+/**
+ * Class that stores the application and global plug-in preferences of the Nexuiz
+ * Demo Recorder application. Set and Get property methods have been modified to
+ * now supply a category.
+ */
+public class NDRPreferences extends Properties {
+
+ private static final long serialVersionUID = 4363913054294979418L;
+ private static final String CONCATENATOR = ".";
+ /**
+ * Category that defines a setting to be a setting of the NDR application itself
+ * (and not of one of the plugins).
+ */
+ public static final String MAIN_APPLICATION = "NDR";
+
+ /**
+ * Searches for the property with the specified key in this property list.
+ * If the key is not found in this property list, the default property list,
+ * and its defaults, recursively, are then checked. The method returns
+ * <code>null</code> if the property is not found.
+ *
+ * @param category the category of the setting
+ * @param key the property key.
+ * @return the value in this property list with the specified category+key value.
+ */
+ public String getProperty(String category, String key) {
+ return getProperty(getConcatenatedKey(category, key));
+ }
+
+ /**
+ * Calls the <tt>Hashtable</tt> method <code>put</code>. Provided for
+ * parallelism with the <tt>getProperty</tt> method. Enforces use of
+ * strings for property keys and values. The value returned is the
+ * result of the <tt>Hashtable</tt> call to <code>put</code>.
+ *
+ * @param category the category of the setting
+ * @param key the key to be placed into this property list.
+ * @param value the value corresponding to <tt>key</tt>.
+ * @return the previous value of the specified key in this property
+ * list, or <code>null</code> if it did not have one.
+ */
+ public void setProperty(String category, String key, String value) {
+ setProperty(getConcatenatedKey(category, key), value);
+ }
+
+ /**
+ * Returns only the category of a key that is a concatenated string of category and key.
+ * @param concatenatedString
+ * @return
+ */
+ public static String getCategory(String concatenatedString) {
+ return concatenatedString.substring(0, concatenatedString.indexOf(CONCATENATOR));
+ }
+
+ public static String getKey(String concatenatedString) {
+ return concatenatedString.substring(concatenatedString.indexOf(CONCATENATOR) + 1, concatenatedString.length());
+ }
+
+ public static String getConcatenatedKey(String category, String key) {
+ return category + CONCATENATOR + key;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,48 @@
+package com.nexuiz.demorecorder.application;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public class RecorderJobPoolExecutor {
+
+ private int poolSize = 1;
+ private int maxPoolSize = 1;
+ private long keepAliveTime = 10;
+ private ThreadPoolExecutor threadPool = null;
+ private ArrayBlockingQueue<Runnable> queue = null;
+
+ public RecorderJobPoolExecutor() {
+ queue = new ArrayBlockingQueue<Runnable>(99999);
+ threadPool = new ThreadPoolExecutor(poolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, queue);
+ }
+
+ public void runJob(Runnable task) {
+ threadPool.execute(task);
+ }
+
+ public void clearUnfinishedJobs() {
+ threadPool.getQueue().clear();
+ }
+
+ public void shutDown() {
+ threadPool.shutdownNow();
+ }
+
+ public synchronized List<RecordJob> getJobList() {
+ List<RecordJob> list = new ArrayList<RecordJob>();
+ for (Runnable job : this.queue) {
+ try {
+ RecordJob j = (RecordJob)job;
+ list.add(j);
+ } catch (ClassCastException e) {}
+ }
+
+ return list;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,220 @@
+package com.nexuiz.demorecorder.application.democutter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+public class DemoCutter {
+
+ private static final byte CDTRACK_SEPARATOR = 0x0A;
+
+ private DataInputStream inStream;
+ private DataOutputStream outStream;
+ private File inFile;
+ private File outFile;
+
+ /**
+ * Calls the cutDemo method with reasonable default values for the second and first fast-forward stage.
+ * @param inFile @see other cutDemo method
+ * @param outFile @see other cutDemo method
+ * @param startTime @see other cutDemo method
+ * @param endTime @see other cutDemo method
+ * @param injectAtStart @see other cutDemo method
+ * @param injectBeforeCap @see other cutDemo method
+ * @param injectAfterCap @see other cutDemo method
+ */
+ public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap) {
+ this.cutDemo(inFile, outFile, startTime, endTime, injectAtStart, injectBeforeCap, injectAfterCap, 100, 10);
+ }
+
+ /**
+ * Cuts the demo by injecting a 2-phase fast forward command until startTime is reached, then injects the cl_capturevideo 1 command
+ * and once endTime is reached the cl_capturevideo 0 command is injected.
+ * @param inFile the original demo file
+ * @param outFile the new cut demo file
+ * @param startTime when to start capturing (use the gametime in seconds)
+ * @param endTime when to stop capturing
+ * @param injectAtStart a String that will be injected right at the beginning of the demo
+ * can be anything that would make sense and can be parsed by DP's console
+ * @param injectBeforeCap a String that will be injected 5 seconds before capturing starts
+ * @param injectAfterCap a String that will be injected shortly after capturing ended
+ * @param ffwSpeedFirstStage fast-forward speed at first stage, when the startTime is still about a minute away (use high values, e.g. 100)
+ * @param ffwSpeedSecondStage fast-forward speed when coming a few seconds close to startTime, use lower values e.g. 5 or 10
+ */
+ public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap, int ffwSpeedFirstStage, int ffwSpeedSecondStage) {
+ this.inFile = inFile;
+ this.outFile = outFile;
+ this.prepareStreams();
+ this.readCDTrack();
+ injectAfterCap = this.checkInjectString(injectAfterCap);
+ injectAtStart = this.checkInjectString(injectAtStart);
+ injectBeforeCap = this.checkInjectString(injectBeforeCap);
+
+ byte[] data;
+ float svctime = -1;
+ boolean firstLoop = true;
+ String injectBuffer = "";
+ int demoStarted = 0;
+ boolean endIsReached = false;
+ boolean finalInjectionDone = false;
+ boolean disconnectIssued = false;
+ int svcLoops = 0;
+ float firstSvcTime = -1;
+ float lastSvcTime = -1;
+
+ try {
+ while (true) {
+ DemoPacket demoPacket = new DemoPacket(this.inStream);
+ if (demoPacket.isEndOfFile()) {
+ break;
+ }
+
+ if (demoPacket.isClientToServerPacket()) {
+ try {
+ this.outStream.write(demoPacket.getOriginalLengthAsByte());
+ this.outStream.write(demoPacket.getAngles());
+ this.outStream.write(demoPacket.getOriginalData());
+ } catch (IOException e) {
+ throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);
+ }
+
+ continue;
+ }
+
+ if (demoPacket.getSvcTime() != -1) {
+ svctime = demoPacket.getSvcTime();
+ }
+
+ if (svctime != -1) {
+ if (firstSvcTime == -1) {
+ firstSvcTime = svctime;
+ }
+ lastSvcTime = svctime;
+
+ if (firstLoop) {
+ injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedFirstStage + "\n\000";
+ firstLoop = false;
+ }
+ if (demoStarted < 1 && svctime > (startTime - 50)) {
+ if (svcLoops == 0) {
+ //make sure that for short demos (duration less than 50 sec)
+ //the injectAtStart is still honored
+ injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedSecondStage + "\n\000";
+ } else {
+ injectBuffer = "\011\nslowmo " + ffwSpeedSecondStage + "\n\000";
+ }
+
+ demoStarted = 1;
+ }
+ if (demoStarted < 2 && svctime > (startTime - 5)) {
+ injectBuffer = "\011\nslowmo 1;" + injectBeforeCap +"\n\000";
+ demoStarted = 2;
+ }
+ if (demoStarted < 3 && svctime > startTime) {
+ injectBuffer = "\011\ncl_capturevideo 1\n\000";
+ demoStarted = 3;
+ }
+ if (!endIsReached && svctime > endTime) {
+ injectBuffer = "\011\ncl_capturevideo 0\n\000";
+ endIsReached = true;
+ }
+ if (endIsReached && !finalInjectionDone && svctime > (endTime + 1)) {
+ injectBuffer = "\011\n" + injectAfterCap + "\n\000";
+ finalInjectionDone = true;
+ }
+ if (finalInjectionDone && !disconnectIssued && svctime > (endTime + 2)) {
+ injectBuffer = "\011\ndisconnect\n\000";
+ disconnectIssued = true;
+ }
+ svcLoops++;
+ }
+
+ byte[] injectBufferAsBytes = null;
+ try {
+ injectBufferAsBytes = injectBuffer.getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new DemoCutterException("Could not convert String to bytes using US-ASCII charset!", e);
+ }
+
+ data = demoPacket.getOriginalData();
+ if ((injectBufferAsBytes.length + data.length) < 65536) {
+ data = DemoCutterUtils.mergeByteArrays(injectBufferAsBytes, data);
+ injectBuffer = "";
+ }
+
+ byte[] newLengthLittleEndian = DemoCutterUtils.convertLittleEndian(data.length);
+ try {
+ this.outStream.write(newLengthLittleEndian);
+ this.outStream.write(demoPacket.getAngles());
+ this.outStream.write(data);
+ } catch (IOException e) {
+ throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);
+ }
+
+ }
+
+ if (startTime < firstSvcTime) {
+ throw new DemoCutterException("Start time for the demo is " + startTime + ", but demo doesn't start before " + firstSvcTime);
+ }
+ if (endTime > lastSvcTime) {
+ throw new DemoCutterException("End time for the demo is " + endTime + ", but demo already stops at " + lastSvcTime);
+ }
+ } catch (DemoCutterException e) {
+ throw e;
+ } catch (Throwable e) {
+ throw new DemoCutterException("Internal error in demo cutter sub-route (invalid demo file?)", e);
+ } finally {
+ try {
+ this.outStream.close();
+ this.inStream.close();
+ } catch (IOException e) {}
+ }
+ }
+
+
+
+ /**
+ * Seeks forward in the inStream until CDTRACK_SEPARATOR byte was reached.
+ * All the content is copied to the outStream.
+ */
+ private void readCDTrack() {
+ byte lastByte;
+ try {
+ while ((lastByte = inStream.readByte()) != CDTRACK_SEPARATOR) {
+ this.outStream.write(lastByte);
+ }
+ this.outStream.write(CDTRACK_SEPARATOR);
+ } catch (EOFException e) {
+ throw new DemoCutterException("Unexpected EOF occurred when reading CD track of demo " + inFile.getPath(), e);
+ }
+ catch (IOException e) {
+ throw new DemoCutterException("Unexpected I/O Exception occurred when reading CD track of demo " + inFile.getPath(), e);
+ }
+ }
+
+ private void prepareStreams() {
+ try {
+ this.inStream = new DataInputStream(new FileInputStream(this.inFile));
+ } catch (FileNotFoundException e) {
+ throw new DemoCutterException("Could not open demo file " + inFile.getPath(), e);
+ }
+
+ try {
+ this.outStream = new DataOutputStream(new FileOutputStream(this.outFile));
+ } catch (FileNotFoundException e) {
+ throw new DemoCutterException("Could not open demo file " + outFile.getPath(), e);
+ }
+ }
+
+ private String checkInjectString(String injectionString) {
+ while (injectionString.endsWith(";") || injectionString.endsWith("\n")) {
+ injectionString = injectionString.substring(0, injectionString.length()-1);
+ }
+ return injectionString;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,14 @@
+package com.nexuiz.demorecorder.application.democutter;
+
+public class DemoCutterException extends RuntimeException {
+
+ private static final long serialVersionUID = -1419472153834762285L;
+
+ public DemoCutterException(String message) {
+ super(message);
+ }
+ public DemoCutterException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,40 @@
+package com.nexuiz.demorecorder.application.democutter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+public class DemoCutterUtils {
+
+ public static float byteArrayToFloat(byte[] array) {
+ byte[] tmp = new byte[4];
+ System.arraycopy(array, 0, tmp, 0, 4);
+ int accum = 0;
+ int i = 0;
+ for (int shiftBy = 0; shiftBy < 32; shiftBy += 8) {
+ accum |= ((long) (tmp[i++] & 0xff)) << shiftBy;
+ }
+ return Float.intBitsToFloat(accum);
+ }
+
+ public static byte[] convertLittleEndian(int i) {
+ ByteBuffer bb = ByteBuffer.allocate(4);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ bb.putInt(i);
+ return bb.array();
+ }
+
+ public static byte[] mergeByteArrays(byte[] array1, byte[] array2) {
+ ByteBuffer bb = ByteBuffer.allocate(array1.length + array2.length);
+ bb.put(array1);
+ bb.put(array2);
+ return bb.array();
+ }
+
+ public static int convertLittleEndian(byte[] b) {
+ ByteBuffer bb = ByteBuffer.allocate(4);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ bb.put(b);
+ bb.position(0);
+ return bb.getInt();
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,102 @@
+package com.nexuiz.demorecorder.application.democutter;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+
+public class DemoPacket {
+
+ private static final int DEMOMSG_CLIENT_TO_SERVER = 0x80000000;
+
+ private DataInputStream inStream = null;
+ private boolean isEndOfFile = false;
+ private byte[] buffer = new byte[4]; //contains packet length
+ private byte[] angles = new byte[12];
+ private byte[] data;
+ private int packetLength;
+ private boolean isClientToServer = false;
+ private float svcTime = -1;
+
+ public DemoPacket(DataInputStream inStream) {
+ this.inStream = inStream;
+
+ try {
+ inStream.readFully(buffer);
+ } catch (EOFException e) {
+ this.isEndOfFile = true;
+ return;
+ } catch (IOException e) {
+ throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");
+ }
+
+ packetLength = DemoCutterUtils.convertLittleEndian(buffer);
+ if ((packetLength & DEMOMSG_CLIENT_TO_SERVER) != 0) {
+ packetLength = packetLength & ~DEMOMSG_CLIENT_TO_SERVER;
+
+ this.isClientToServer = true;
+ this.readAnglesAndData();
+ return;
+ }
+
+ this.readAnglesAndData();
+
+ // extract svc_time
+ this.readSvcTime();
+
+ }
+
+ public boolean isEndOfFile() {
+ return this.isEndOfFile;
+ }
+
+ public boolean isClientToServerPacket() {
+ return this.isClientToServer;
+ }
+
+ public byte[] getOriginalLengthAsByte() {
+ return this.buffer;
+ }
+
+ public byte[] getAngles() {
+ return this.angles;
+ }
+
+ public byte[] getOriginalData() {
+ return this.data;
+ }
+
+ public float getSvcTime() {
+ return this.svcTime;
+ }
+
+ private void readAnglesAndData() {
+ // read angles
+ try {
+ inStream.readFully(angles);
+ } catch (EOFException e) {
+ throw new DemoCutterException("Invalid Demo Packet");
+ } catch (IOException e) {
+ throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");
+ }
+
+ // read data
+ data = new byte[packetLength];
+ try {
+ inStream.readFully(data);
+ } catch (EOFException e) {
+ throw new DemoCutterException("Invalid Demo Packet");
+ } catch (IOException e) {
+ throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");
+ }
+ }
+
+ private void readSvcTime() {
+ if (data[0] == 0x07) {
+ ByteBuffer bb = ByteBuffer.allocate(4);
+ bb.put(data, 1, 4);
+ byte[] array = bb.array();
+ this.svcTime = DemoCutterUtils.byteArrayToFloat(array);
+ }
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,24 @@
+package com.nexuiz.demorecorder.application.jobs;
+
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+
+/**
+ * Job for the ThreadPoolExecutor that will just call the encoder-plugin's execute
+ * method.
+ */
+public class EncoderJob implements Runnable {
+
+ private RecordJob job;
+ private EncoderPlugin plugin;
+
+ public EncoderJob(RecordJob job, EncoderPlugin plugin) {
+ this.job = job;
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void run() {
+ this.job.executePlugin(this.plugin);
+ }
+
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,636 @@
+package com.nexuiz.demorecorder.application.jobs;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;
+import com.nexuiz.demorecorder.application.democutter.DemoCutter;
+import com.nexuiz.demorecorder.application.democutter.DemoCutterException;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;
+
+public class RecordJob implements Runnable, Serializable {
+
+ private static final long serialVersionUID = -4585637490345587912L;
+
+ public enum State {
+ WAITING, PROCESSING, ERROR, ERROR_PLUGIN, DONE
+ }
+
+ public static final String CUT_DEMO_FILE_SUFFIX = "_autocut";
+ public static final String CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE = "autocap";
+ public static final String CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE = "1234567";
+ protected static final String[] VIDEO_FILE_ENDINGS = {"avi", "ogv"};
+
+ private DemoRecorderApplication appLayer;
+ protected String jobName;
+ private int jobIndex;
+ protected File enginePath;
+ protected String engineParameters;
+ protected File demoFile;
+ protected String relativeDemoPath;
+ protected File dpVideoPath;
+ protected File videoDestination;
+ protected String executeBeforeCap;
+ protected String executeAfterCap;
+ protected float startSecond;
+ protected float endSecond;
+ protected State state = State.WAITING;
+ protected DemoRecorderException lastException = null;
+
+ /**
+ * Points to the actual final file, including possible suffixes, e.g. _copy1, and the actualy ending
+ */
+ protected File actualVideoDestination = null;
+ /**
+ * Map that identifies the plug-in by its name (String) and maps to the plug-in's job-specific settings
+ */
+ protected Map<String, Properties> encoderPluginSettings = new HashMap<String, Properties>();
+
+ private List<File> cleanUpFiles = null;
+
+ public RecordJob(
+ DemoRecorderApplication appLayer,
+ String jobName,
+ int jobIndex,
+ File enginePath,
+ String engineParameters,
+ File demoFile,
+ String relativeDemoPath,
+ File dpVideoPath,
+ File videoDestination,
+ String executeBeforeCap,
+ String executeAfterCap,
+ float startSecond,
+ float endSecond
+ ) {
+ this.appLayer = appLayer;
+ this.jobName = jobName;
+ this.jobIndex = jobIndex;
+
+ this.setEnginePath(enginePath);
+ this.setEngineParameters(engineParameters);
+ this.setDemoFile(demoFile);
+ this.setRelativeDemoPath(relativeDemoPath);
+ this.setDpVideoPath(dpVideoPath);
+ this.setVideoDestination(videoDestination);
+ this.setExecuteBeforeCap(executeBeforeCap);
+ this.setExecuteAfterCap(executeAfterCap);
+ this.setStartSecond(startSecond);
+ this.setEndSecond(endSecond);
+ }
+
+ public RecordJob(){}
+
+ /**
+ * Constructor that can be used by other classes such as job templates. Won't throw exceptions
+ * as it won't check the input for validity.
+ */
+ protected RecordJob(
+ File enginePath,
+ String engineParameters,
+ File demoFile,
+ String relativeDemoPath,
+ File dpVideoPath,
+ File videoDestination,
+ String executeBeforeCap,
+ String executeAfterCap,
+ float startSecond,
+ float endSecond
+ ) {
+ this.jobIndex = -1;
+ this.enginePath = enginePath;
+ this.engineParameters = engineParameters;
+ this.demoFile = demoFile;
+ this.relativeDemoPath = relativeDemoPath;
+ this.dpVideoPath = dpVideoPath;
+ this.videoDestination = videoDestination;
+ this.executeBeforeCap = executeBeforeCap;
+ this.executeAfterCap = executeAfterCap;
+ this.startSecond = startSecond;
+ this.endSecond = endSecond;
+ }
+
+ public void execute() {
+ if (this.state == State.PROCESSING) {
+ return;
+ }
+ boolean errorOccurred = false;
+ this.setState(State.PROCESSING);
+ this.appLayer.fireUserInterfaceUpdate(this);
+ cleanUpFiles = new ArrayList<File>();
+
+ File cutDemo = computeCutDemoFile();
+ cutDemo.delete(); //delete possibly old cutDemoFile
+
+ EncoderPlugin recentEncoder = null;
+
+ try {
+ this.cutDemo(cutDemo);
+ this.removeOldAutocaps();
+ this.recordClip(cutDemo);
+ this.moveRecordedClip();
+ for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
+ recentEncoder = plugin;
+ plugin.executeEncoder(this);
+ }
+ } catch (DemoRecorderException e) {
+ errorOccurred = true;
+ this.lastException = e;
+ this.setState(State.ERROR);
+ } catch (EncoderPluginException e) {
+ errorOccurred = true;
+ this.lastException = new DemoRecorderException("Encoder plug-in " + recentEncoder.getName() + " failed: "
+ + e.getMessage(), e);
+ this.setState(State.ERROR_PLUGIN);
+ } catch (Exception e) {
+ errorOccurred = true;
+ this.lastException = new DemoRecorderException("Executing job failed, click on details for more info", e);
+ } finally {
+ NDRPreferences preferences = this.appLayer.getPreferences();
+ if (!Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS))) {
+ cleanUpFiles.add(cutDemo);
+ }
+ if (!errorOccurred) {
+ this.setState(State.DONE);
+ }
+ this.cleanUpFiles();
+ this.appLayer.fireUserInterfaceUpdate(this);
+ this.appLayer.saveJobQueue();
+ }
+ }
+
+ /**
+ * Will execute just the specified encoder plug-in on an already "done" job.
+ * @param pluginName
+ */
+ public void executePlugin(EncoderPlugin plugin) {
+ if (this.getState() != State.DONE) {
+ return;
+ }
+ this.setState(State.PROCESSING);
+ this.appLayer.fireUserInterfaceUpdate(this);
+
+ try {
+ plugin.executeEncoder(this);
+ this.setState(State.DONE);
+ } catch (EncoderPluginException e) {
+ this.lastException = new DemoRecorderException("Encoder plug-in " + plugin.getName() + " failed: "
+ + e.getMessage(), e);
+ this.setState(State.ERROR_PLUGIN);
+ }
+
+ this.appLayer.fireUserInterfaceUpdate(this);
+ }
+
+ private void cleanUpFiles() {
+ try {
+ for (File f : this.cleanUpFiles) {
+ f.delete();
+ }
+ } catch (Exception e) {}
+
+ }
+
+ private void moveRecordedClip() {
+ //1. Figure out whether the file is .avi or .ogv
+ File sourceFile = null;
+ for (String videoExtension : VIDEO_FILE_ENDINGS) {
+ String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
+ + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
+ File videoFile = new File(fileString);
+ if (videoFile.exists()) {
+ sourceFile = videoFile;
+ break;
+ }
+ }
+
+ if (sourceFile == null) {
+ String p = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
+ + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE;
+ throw new DemoRecorderException("Could not locate the expected video file being generated by Nexuiz (should have been at "
+ + p + ".avi/.ogv");
+ }
+ cleanUpFiles.add(sourceFile);
+
+ File destinationFile = null;
+ NDRPreferences preferences = this.appLayer.getPreferences();
+ String sourceFileExtension = DemoRecorderUtils.getFileExtension(sourceFile);
+ String destinationFilePath = this.videoDestination + "." + sourceFileExtension;
+ destinationFile = new File(destinationFilePath);
+ if (destinationFile.exists()) {
+ if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE))) {
+ if (!destinationFile.delete()) {
+ throw new DemoRecorderException("Could not delete the existing video destinatin file " + destinationFile.getAbsolutePath()
+ + " (application setting to overwrite existing video files is enabled!)");
+ }
+ } else {
+ destinationFilePath = this.videoDestination + "_copy" + this.getVideoDestinationCopyNr(sourceFileExtension) + "." + sourceFileExtension;
+ destinationFile = new File(destinationFilePath);
+ }
+ }
+
+ //finally move the file
+ if (!sourceFile.renameTo(destinationFile)) {
+ cleanUpFiles.add(destinationFile);
+ throw new DemoRecorderException("Could not move the video file from " + sourceFile.getAbsolutePath()
+ + " to " + destinationFile.getAbsolutePath());
+ }
+
+ this.actualVideoDestination = destinationFile;
+ }
+
+ /**
+ * As destination video files, e.g. "test"[.avi] can already exist, we have to save the
+ * the video file to a file name such as test_copy1 or test_copy2.
+ * This function will figure out what the number (1, 2....) is.
+ * @return
+ */
+ private int getVideoDestinationCopyNr(String sourceFileExtension) {
+ int i = 1;
+ File lastFile;
+ while (true) {
+ lastFile = new File(this.videoDestination + "_copy" + i + "." + sourceFileExtension);
+ if (!lastFile.exists()) {
+ break;
+ }
+
+ i++;
+ }
+ return i;
+ }
+
+ private File computeCutDemoFile() {
+ String origFileString = this.demoFile.getAbsolutePath();
+ int lastIndex = origFileString.lastIndexOf(File.separator);
+ String autoDemoFileName = origFileString.substring(lastIndex+1, origFileString.length());
+ //strip .dem ending
+ autoDemoFileName = autoDemoFileName.substring(0, autoDemoFileName.length()-4);
+ autoDemoFileName = autoDemoFileName + CUT_DEMO_FILE_SUFFIX + ".dem";
+ String finalString = origFileString.substring(0, lastIndex) + File.separator + autoDemoFileName;
+ File f = new File(finalString);
+
+ return f;
+ }
+
+ private void cutDemo(File cutDemo) {
+ String injectAtStart = "";
+ String injectBeforeCap = "";
+ String injectAfterCap = "";
+
+ NDRPreferences preferences = this.appLayer.getPreferences();
+ if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING))) {
+ injectAtStart += "r_render 0;";
+ injectBeforeCap += "r_render 1;";
+ }
+ if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND))) {
+ injectAtStart += "set _volume $volume;volume 0;";
+ injectBeforeCap += "set volume $_volume;";
+ }
+ injectBeforeCap += this.executeBeforeCap + "\n";
+ injectBeforeCap += "set _cl_capturevideo_nameformat $cl_capturevideo_nameformat;set _cl_capturevideo_number $cl_capturevideo_number;";
+ injectBeforeCap += "cl_capturevideo_nameformat " + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE + ";";
+ injectBeforeCap += "cl_capturevideo_number " + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + ";";
+
+ injectAfterCap += this.executeAfterCap + "\n";
+ injectAfterCap += "cl_capturevideo_nameformat $_cl_capturevideo_nameformat;cl_capturevideo_number $_cl_capturevideo_number;";
+
+
+ DemoCutter cutter = new DemoCutter();
+ int fwdSpeedFirstStage, fwdSpeedSecondStage;
+ try {
+ fwdSpeedFirstStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE));
+ fwdSpeedSecondStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE));
+ } catch (NumberFormatException e) {
+ throw new DemoRecorderException("Make sure that you specified valid numbers for the settings "
+ + Preferences.FFW_SPEED_FIRST_STAGE + " and " + Preferences.FFW_SPEED_SECOND_STAGE, e);
+ }
+
+ try {
+ cutter.cutDemo(
+ this.demoFile,
+ cutDemo,
+ this.startSecond,
+ this.endSecond,
+ injectAtStart,
+ injectBeforeCap,
+ injectAfterCap,
+ fwdSpeedFirstStage,
+ fwdSpeedSecondStage
+ );
+ } catch (DemoCutterException e) {
+ throw new DemoRecorderException("Error occurred while trying to cut the demo: " + e.getMessage(), e);
+ }
+
+ }
+
+ private void removeOldAutocaps() {
+ for (String videoExtension : VIDEO_FILE_ENDINGS) {
+ String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
+ + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
+ File videoFile = new File(fileString);
+ cleanUpFiles.add(videoFile);
+ if (videoFile.exists()) {
+ if (!videoFile.delete()) {
+ throw new DemoRecorderException("Could not delete old obsolete video file " + fileString);
+ }
+ }
+ }
+ }
+
+ private void recordClip(File cutDemo) {
+ Process nexProc;
+ String demoFileName = DemoRecorderUtils.getJustFileNameOfPath(cutDemo);
+ String execPath = this.enginePath.getAbsolutePath() + " " + this.engineParameters + " -demo "
+ + this.relativeDemoPath + "/" + demoFileName;
+ File engineDir = this.enginePath.getParentFile();
+ try {
+ nexProc = Runtime.getRuntime().exec(execPath, null, engineDir);
+ nexProc.getErrorStream();
+ nexProc.getOutputStream();
+ InputStream is = nexProc.getInputStream();
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader br = new BufferedReader(isr);
+ while (br.readLine() != null) {
+ //System.out.println(line);
+ }
+ } catch (IOException e) {
+ throw new DemoRecorderException("I/O Exception occurred when trying to execute the Nexuiz binary", e);
+ }
+ }
+
+ public void run() {
+ this.execute();
+ }
+
+ public void setAppLayer(DemoRecorderApplication appLayer) {
+ this.appLayer = appLayer;
+ }
+
+ public int getJobIndex() {
+ return jobIndex;
+ }
+
+ public File getEnginePath() {
+ return enginePath;
+ }
+
+ public void setEnginePath(File enginePath) {
+ this.checkForProcessingState();
+ if (enginePath == null || !enginePath.exists()) {
+ throw new DemoRecorderException("Could not locate engine binary!");
+ }
+ if (!enginePath.canExecute()) {
+ throw new DemoRecorderException("The file you specified is not executable!");
+ }
+ this.enginePath = enginePath.getAbsoluteFile();
+ }
+
+ public String getEngineParameters() {
+ return engineParameters;
+ }
+
+ public void setEngineParameters(String engineParameters) {
+ this.checkForProcessingState();
+ if (engineParameters == null) {
+ engineParameters = "";
+ }
+ this.engineParameters = engineParameters.trim();
+ }
+
+ public File getDemoFile() {
+ return demoFile;
+ }
+
+ public void setDemoFile(File demoFile) {
+ this.checkForProcessingState();
+ if (demoFile == null || !demoFile.exists()) {
+ throw new DemoRecorderException("Could not locate demo file!");
+ }
+ if (!doReadWriteTest(demoFile.getParentFile())) {
+ throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");
+ }
+ if (!demoFile.getAbsolutePath().endsWith(".dem")) {
+ throw new DemoRecorderException("The demo file you specified must have the ending .dem");
+ }
+
+ this.demoFile = demoFile.getAbsoluteFile();
+ }
+
+ public String getRelativeDemoPath() {
+ return relativeDemoPath;
+ }
+
+ public void setRelativeDemoPath(String relativeDemoPath) {
+ this.checkForProcessingState();
+ if (relativeDemoPath == null) {
+ relativeDemoPath = "";
+ }
+
+ //get rid of possible slashes
+ while (relativeDemoPath.startsWith("/") || relativeDemoPath.startsWith("\\")) {
+ relativeDemoPath = relativeDemoPath.substring(1, relativeDemoPath.length());
+ }
+ while (relativeDemoPath.endsWith("/") || relativeDemoPath.endsWith("\\")) {
+ relativeDemoPath = relativeDemoPath.substring(0, relativeDemoPath.length() - 1);
+ }
+
+ this.relativeDemoPath = relativeDemoPath.trim();
+ }
+
+ public File getDpVideoPath() {
+ return dpVideoPath;
+ }
+
+ public void setDpVideoPath(File dpVideoPath) {
+ this.checkForProcessingState();
+ if (dpVideoPath == null || !dpVideoPath.isDirectory()) {
+ throw new DemoRecorderException("Could not locate the specified DPVideo directory!");
+ }
+
+ if (!this.doReadWriteTest(dpVideoPath)) {
+ throw new DemoRecorderException("The DPVideo directory is not writable! It needs to be writable so that the file can be moved to its new location");
+ }
+ this.dpVideoPath = dpVideoPath.getAbsoluteFile();
+ }
+
+ public File getVideoDestination() {
+ return videoDestination;
+ }
+
+ public void setVideoDestination(File videoDestination) {
+ this.checkForProcessingState();
+ //keep in mind, the parameter videoDestination points to the final avi/ogg file w/o extension!
+ if (videoDestination == null || !videoDestination.getParentFile().isDirectory()) {
+ throw new DemoRecorderException("Could not locate the specified video destination");
+ }
+
+ if (!this.doReadWriteTest(videoDestination.getParentFile())) {
+ throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");
+ }
+
+ this.videoDestination = videoDestination.getAbsoluteFile();
+ }
+
+ public String getExecuteBeforeCap() {
+ return executeBeforeCap;
+ }
+
+ public void setExecuteBeforeCap(String executeBeforeCap) {
+ this.checkForProcessingState();
+ if (executeBeforeCap == null) {
+ executeBeforeCap = "";
+ }
+ executeBeforeCap = executeBeforeCap.trim();
+ while (executeBeforeCap.endsWith(";")) {
+ executeBeforeCap = executeBeforeCap.substring(0, executeBeforeCap.length()-1);
+ }
+ this.executeBeforeCap = executeBeforeCap;
+ }
+
+ public String getExecuteAfterCap() {
+ return executeAfterCap;
+ }
+
+ public void setExecuteAfterCap(String executeAfterCap) {
+ this.checkForProcessingState();
+ if (executeAfterCap == null) {
+ executeAfterCap = "";
+ }
+ executeAfterCap = executeAfterCap.trim();
+ while (executeAfterCap.endsWith(";")) {
+ executeAfterCap = executeAfterCap.substring(0, executeAfterCap.length()-1);
+ }
+ if (executeAfterCap.contains("cl_capturevideo_number") || executeAfterCap.contains("cl_capturevideo_nameformat")) {
+ throw new DemoRecorderException("Execute after String cannot contain cl_capturevideo_number or _nameformat changes!");
+ }
+ this.executeAfterCap = executeAfterCap;
+ }
+
+ public float getStartSecond() {
+ return startSecond;
+ }
+
+ public void setStartSecond(float startSecond) {
+ this.checkForProcessingState();
+ if (startSecond < 0) {
+ throw new DemoRecorderException("Start second cannot be < 0");
+ }
+ this.startSecond = startSecond;
+ }
+
+ public float getEndSecond() {
+ return endSecond;
+ }
+
+ public void setEndSecond(float endSecond) {
+ this.checkForProcessingState();
+ if (endSecond < this.startSecond) {
+ throw new DemoRecorderException("End second cannot be < start second");
+ }
+ this.endSecond = endSecond;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public void setState(State state) {
+ this.state = state;
+ this.appLayer.fireUserInterfaceUpdate(this);
+ }
+
+ public String getJobName() {
+ if (this.jobName == null || this.jobName.equals("")) {
+ return "Job " + this.jobIndex;
+ }
+ return this.jobName;
+ }
+
+ public void setJobName(String jobName) {
+ if (jobName == null || jobName.equals("")) {
+ this.jobIndex = appLayer.getNewJobIndex();
+ this.jobName = "Job " + this.jobIndex;
+ } else {
+ this.jobName = jobName;
+ }
+ }
+
+ public DemoRecorderException getLastException() {
+ return lastException;
+ }
+
+ /**
+ * Tests whether the given directory is writable by creating a file in there and deleting
+ * it again.
+ * @param directory
+ * @return true if directory is writable
+ */
+ protected boolean doReadWriteTest(File directory) {
+ boolean writable = false;
+ String fileName = "tmp." + Math.random()*10000 + ".dat";
+ File tempFile = new File(directory, fileName);
+ try {
+ writable = tempFile.createNewFile();
+ if (writable) {
+ tempFile.delete();
+ }
+ } catch (IOException e) {
+ writable = false;
+ }
+ return writable;
+ }
+
+ private void checkForProcessingState() {
+ if (this.state == State.PROCESSING) {
+ throw new DemoRecorderException("Cannot modify this job while it is processing!");
+ }
+ }
+
+ public Properties getEncoderPluginSettings(EncoderPlugin plugin) {
+ if (this.encoderPluginSettings.containsKey(plugin.getName())) {
+ return this.encoderPluginSettings.get(plugin.getName());
+ } else {
+ return new Properties();
+ }
+ }
+
+ public void setEncoderPluginSetting(String pluginName, String pluginSettingKey, String value) {
+ Properties p = this.encoderPluginSettings.get(pluginName);
+ if (p == null) {
+ p = new Properties();
+ this.encoderPluginSettings.put(pluginName, p);
+ }
+
+ p.put(pluginSettingKey, value);
+ }
+
+ public Map<String, Properties> getEncoderPluginSettings() {
+ return encoderPluginSettings;
+ }
+
+ public void setEncoderPluginSettings(Map<String, Properties> encoderPluginSettings) {
+ this.encoderPluginSettings = encoderPluginSettings;
+ }
+
+ public File getActualVideoDestination() {
+ return actualVideoDestination;
+ }
+
+ public void setActualVideoDestination(File actualVideoDestination) {
+ this.actualVideoDestination = actualVideoDestination;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,17 @@
+package com.nexuiz.demorecorder.application.jobs;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+
+public class RecordsDoneJob implements Runnable {
+
+ private DemoRecorderApplication appLayer;
+
+ public RecordsDoneJob(DemoRecorderApplication appLayer) {
+ this.appLayer = appLayer;
+ }
+
+ public void run() {
+ this.appLayer.notifyAllJobsDone();
+ }
+
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,69 @@
+package com.nexuiz.demorecorder.application.plugins;
+
+import java.util.Properties;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public interface EncoderPlugin {
+
+ /**
+ * Makes the application layer known to the plug-in, which is required so that the plug-in
+ * can access the preferences of the application. Call this method first before using any
+ * of the others.
+ */
+ public void setApplicationLayer(DemoRecorderApplication appLayer);
+
+ /**
+ * Returns the name of the plug-in. Must not contain a "."
+ */
+ public String getName();
+
+ /**
+ * Returns true if the plug-in is enabled (checked from the preferences of the app layer)
+ * @return true if the plug-in is enabled
+ */
+ public boolean isEnabled();
+
+ /**
+ * Global preferences are preferences of a plug-in that are application-wide and not job-
+ * specific. They should be shown in a global preferences dialog.
+ * Use this method in order to tell the application layer and GUI which global settings your
+ * encoder plug-in offers, and set a reasonable default. Note that for the default-values being
+ * set you can either set to "true" or "false", any String (can be empty), or "filechooser" if
+ * you want the user to select a file.
+ * @return
+ */
+ public Properties getGlobalPreferences();
+
+ /**
+ * In order to influence the order of settings being displayed to the user in a UI, return an array
+ * of all keys used in the Properties object returned in getGlobalPreferences(), with your desired
+ * order.
+ * @return
+ */
+ public String[] getGlobalPreferencesOrder();
+
+ /**
+ * Here you can return a Properties object that contains keys for values that can be specific to each
+ * individual RecordJob.
+ * @return
+ */
+ public Properties getJobSpecificPreferences();
+
+ /**
+ * In order to influence the order of job-specific settings being displayed to the user in a UI,
+ * return an array of all keys used in the Properties object returned in getJobSpecificPreferences(), with
+ * your desired order.
+ * @return
+ */
+ public String[] getJobSpecificPreferencesOrder();
+
+ /**
+ * Will be called by the application layer when a job has been successfully recorded and moved to its
+ * final destination. This method has to perform the specific tasks your plug-in is supposed to do.
+ * @param job
+ * @throws EncoderPluginException
+ */
+ public void executeEncoder(RecordJob job) throws EncoderPluginException;
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,14 @@
+package com.nexuiz.demorecorder.application.plugins;
+
+public class EncoderPluginException extends Exception {
+
+ private static final long serialVersionUID = 2200737027476726978L;
+
+ public EncoderPluginException(String message) {
+ super(message);
+ }
+
+ public EncoderPluginException(String message, Throwable t) {
+ super(message, t);
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,18 @@
+package com.nexuiz.demorecorder.main;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;
+import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;
+
+public class Driver {
+
+ public static void main(String[] args) {
+ SwingGUI.setSystemLAF();
+ Thread.setDefaultUncaughtExceptionHandler(new ShowErrorDialogExceptionHandler());
+ DemoRecorderApplication appLayer = new DemoRecorderApplication();
+
+ SwingGUI gui = new SwingGUI(appLayer);
+ appLayer.addUserInterfaceListener(gui);
+
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,20 @@
+package com.nexuiz.demorecorder.ui;
+
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public interface DemoRecorderUI {
+
+ /**
+ * Called by the application layer to inform the GUI about the fact that
+ * one or more properties of the given job changed (most likely the status).
+ * The given job might also be new to the GUI.
+ * @param job the affected job
+ */
+ public void RecordJobPropertiesChange(RecordJob job);
+
+ /**
+ * Called by the application layer to inform the GUI that it finished
+ * recording all assigned jobs.
+ */
+ public void recordingFinished();
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,737 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.border.EmptyBorder;
+import javax.swing.filechooser.FileFilter;
+
+import net.miginfocom.swing.MigLayout;
+
+import org.jdesktop.swingx.JXTable;
+import org.jdesktop.swingx.JXTitledSeparator;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;
+import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;
+
+/**
+ * Shows the dialog that allows to create a new job, create one from a template
+ * or edit an existing job.
+ */
+
+public class JobDialog extends JDialog implements ActionListener {
+ private static final long serialVersionUID = 6926246716804560522L;
+ public static final int CREATE_NEW_JOB = 0;
+ public static final int EDIT_JOB = 1;
+ public static final int CREATE_NEW_TEMPLATE = 2;
+ public static final int EDIT_TEMPLATE = 3;
+ public static final int CREATE_JOB_FROM_TEMPLATE = 4;
+
+ private DemoRecorderApplication appLayer;
+ private RecordJobTemplatesTableModel tableModel;
+// private JXTable templatesTable;
+ private Frame parentFrame;
+ private int dialogType;
+ private RecordJob job = null;
+ private JPanel inputPanel;
+ private JPanel buttonPanel;
+
+ private JTextField templateNameField;
+ private JTextField templateSummaryField;
+ private JTextField enginePathField;
+ private JButton enginePathChooserButton;
+ private JTextField engineParameterField;
+ private JTextField dpVideoDirField;
+ private JButton dpVideoDirChooserButton;
+ private JTextField relativeDemoPathField;
+ private JTextField jobNameField;
+ private JTextField demoFileField;
+ private JButton demoFileChooserButton;
+ private JTextField startSecondField;
+ private JTextField endSecondField;
+ private JTextArea execBeforeField;
+ private JTextArea execAfterField;
+ private JTextField videoDestinationField;
+ private JButton videoDestinationChooserButton;
+
+ private JButton createButton;
+ private JButton cancelButton;
+
+ //file choosers
+ private JFileChooser enginePathFC;
+ private JFileChooser dpVideoDirFC;
+ private JFileChooser demoFileFC;
+ private JFileChooser videoDestinationFC;
+
+ private FileFilter userDirFilter = new NexuizUserDirFilter();
+
+ private Map<String, JComponent> pluginDialogSettings = new HashMap<String, JComponent>();
+
+ /**
+ * Constructor to create a dialog when creating a new job.
+ * @param owner
+ * @param appLayer
+ */
+ public JobDialog(Frame owner, DemoRecorderApplication appLayer) {
+ super(owner, true);
+ this.parentFrame = owner;
+ this.dialogType = CREATE_NEW_JOB;
+ this.appLayer = appLayer;
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+ setTitle("Create new job");
+
+ this.setupLayout();
+ }
+
+ /**
+ * Constructor to create a dialog when creating a new template.
+ * @param owner
+ * @param dialogType
+ * @param appLayer
+ */
+ public JobDialog(Frame owner, RecordJobTemplatesTableModel tableModel, JXTable templatesTable, DemoRecorderApplication appLayer) {
+ super(owner, true);
+ this.parentFrame = owner;
+ this.dialogType = CREATE_NEW_TEMPLATE;
+ this.tableModel = tableModel;
+ this.appLayer = appLayer;
+// this.templatesTable = templatesTable; seems we don't need it
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ setTitle("Create new template");
+
+ this.setupLayout();
+ }
+
+ /**
+ * Constructor to use when creating a new job from a template, or when editing a template.
+ * @param owner
+ * @param template
+ * @param type either CREATE_JOB_FROM_TEMPLATE or EDIT_TEMPLATE
+ */
+ public JobDialog(Frame owner, RecordJobTemplate template, DemoRecorderApplication appLayer, int type) {
+ super(owner, true);
+ this.parentFrame = owner;
+
+ this.job = template;
+ this.appLayer = appLayer;
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+ if (type != CREATE_JOB_FROM_TEMPLATE && type != EDIT_TEMPLATE) {
+ throw new RuntimeException("Illegal paraameter \"type\"");
+ }
+
+ this.dialogType = type;
+ if (type == CREATE_JOB_FROM_TEMPLATE) {
+ setTitle("Create job from template");
+ } else {
+ setTitle("Edit template");
+ }
+
+ this.setupLayout();
+ }
+
+ /**
+ * Constructor to create a dialog to be used when editing an existing job.
+ * @param owner
+ * @param job
+ */
+ public JobDialog(Frame owner, RecordJob job, DemoRecorderApplication appLayer) {
+ super(owner, true);
+ this.parentFrame = owner;
+ this.dialogType = EDIT_JOB;
+ this.appLayer = appLayer;
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+ setTitle("Edit job");
+ this.job = job;
+
+ this.setupLayout();
+ }
+
+
+
+ public void showDialog() {
+ this.pack();
+ Toolkit t = Toolkit.getDefaultToolkit();
+ Dimension screenSize = t.getScreenSize();
+ if (getHeight() > screenSize.height) {
+ Dimension newPreferredSize = getPreferredSize();
+ newPreferredSize.height = screenSize.height - 100;
+ setPreferredSize(newPreferredSize);
+ this.pack();
+ }
+ this.setLocationRelativeTo(this.parentFrame);
+ this.setVisible(true);
+ }
+
+ private void setupLayout() {
+// setLayout(new MigLayout("wrap 1", "[grow,fill]", "[]20[]"));
+ setLayout(new MigLayout("wrap 1", "[grow,fill]", "[][]"));
+ this.setupInputMask();
+ this.setupButtonPart();
+
+ }
+
+ private void setupInputMask() {
+ inputPanel = new JPanel(new MigLayout("insets 0,wrap 3", "[][250::,grow,fill][30::]"));
+ JScrollPane inputScrollPane = new JScrollPane(inputPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ inputScrollPane.setBorder(new EmptyBorder(0,0,0,0));
+
+ JXTitledSeparator environmentHeading = new JXTitledSeparator("Environment settings");
+ inputPanel.add(environmentHeading, "span 3,grow");
+
+ this.setupTemplateNameAndSummary();
+ this.setupEnginePath();
+ this.setupEngineParameters();
+ this.setupDPVideoDir();
+ this.setupRelativeDemoPath();
+
+ JXTitledSeparator jobSettingsHeading = new JXTitledSeparator("Job settings");
+ inputPanel.add(jobSettingsHeading, "span 3,grow");
+
+ this.setupJobName();
+ this.setupDemoFile();
+ this.setupStartSecond();
+ this.setupEndSecond();
+ this.setupExecBefore();
+ this.setupExecAfter();
+ this.setupVideoDestination();
+
+ this.setupPluginPreferences();
+
+ getContentPane().add(inputScrollPane);
+ }
+
+ private void setupTemplateNameAndSummary() {
+ if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != EDIT_TEMPLATE) {
+ return;
+ }
+
+ //layout stuff
+ inputPanel.add(new JLabel("Template name:"));
+ templateNameField = new JTextField();
+ inputPanel.add(templateNameField, "wrap");
+
+ inputPanel.add(new JLabel("Summary:"));
+ templateSummaryField = new JTextField();
+ inputPanel.add(templateSummaryField, "wrap");
+
+ //UI logic stuff
+ if (this.dialogType == EDIT_TEMPLATE) {
+ RecordJobTemplate template = (RecordJobTemplate) this.job;
+ templateNameField.setText(template.getName());
+ templateSummaryField.setText(template.getSummary());
+ }
+ }
+
+ private void setupEnginePath() {
+ //layout stuff
+ inputPanel.add(new JLabel("Engine:"));
+ enginePathField = new JTextField();
+ enginePathField.setEditable(false);
+ inputPanel.add(enginePathField);
+ enginePathChooserButton = new FileChooserButton();
+ inputPanel.add(enginePathChooserButton);
+
+ //UI logic stuff
+ this.enginePathFC = createConfiguredFileChooser();
+ enginePathChooserButton.addActionListener(this);
+ if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ this.enginePathFC.setSelectedFile(this.job.getEnginePath());
+ this.enginePathField.setText(this.job.getEnginePath().getAbsolutePath());
+ }
+ }
+
+ private void setupEngineParameters() {
+ //layout stuff
+ inputPanel.add(new JLabel("Engine parameters:"));
+ engineParameterField = new JTextField();
+ inputPanel.add(engineParameterField, "wrap");
+
+ //UI logic stuff
+ if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ engineParameterField.setText(this.job.getEngineParameters());
+ }
+ }
+
+ private void setupDPVideoDir() {
+ //layout stuff
+ inputPanel.add(new JLabel("DPVideo directory:"));
+ dpVideoDirField = new JTextField();
+ dpVideoDirField.setEditable(false);
+ inputPanel.add(dpVideoDirField);
+ dpVideoDirChooserButton = new FileChooserButton();
+ inputPanel.add(dpVideoDirChooserButton);
+
+ //UI logic stuff
+ dpVideoDirChooserButton.addActionListener(this);
+ this.dpVideoDirFC = createConfiguredFileChooser();
+ this.dpVideoDirFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ this.dpVideoDirFC.setSelectedFile(this.job.getDpVideoPath());
+ this.dpVideoDirField.setText(this.job.getDpVideoPath().getAbsolutePath());
+ }
+ }
+
+ private void setupRelativeDemoPath() {
+ //layout stuff
+ inputPanel.add(new JLabel("Relative demo path:"));
+ relativeDemoPathField = new JTextField();
+ inputPanel.add(relativeDemoPathField, "wrap 20");
+
+ //UI logic stuff
+ if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE) {
+ relativeDemoPathField.setText("demos");
+ }
+ if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ relativeDemoPathField.setText(this.job.getRelativeDemoPath());
+ }
+ }
+
+ private void setupJobName() {
+ inputPanel.add(new JLabel("Job name:"));
+
+ jobNameField = new JTextField();
+ inputPanel.add(jobNameField, "wrap");
+
+ //UI logic stuff
+ if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != CREATE_NEW_JOB) {
+ jobNameField.setText(this.job.getJobName());
+ }
+ }
+
+ private void setupDemoFile() {
+ String label;
+ if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ label = "Demo file:";
+ } else {
+ label = "Demo directory:";
+ }
+
+ //layout stuff
+ inputPanel.add(new JLabel(label));
+ demoFileField = new JTextField();
+ demoFileField.setEditable(false);
+ inputPanel.add(demoFileField);
+ demoFileChooserButton = new FileChooserButton();
+ inputPanel.add(demoFileChooserButton);
+
+ //UI logic stuff
+ this.demoFileFC = createConfiguredFileChooser();
+ demoFileChooserButton.addActionListener(this);
+ if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ this.demoFileFC.setCurrentDirectory(this.job.getDemoFile());
+ } else {
+ this.demoFileFC.setSelectedFile(this.job.getDemoFile());
+ }
+
+ this.demoFileField.setText(this.job.getDemoFile().getAbsolutePath());
+ }
+
+ //only specify directories for templates
+ if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {
+ this.demoFileFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ }
+ }
+
+ private void setupStartSecond() {
+ //only exists for jobs, not for templates
+ if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {
+ return;
+ }
+
+ //layout stuff
+ inputPanel.add(new JLabel("Start second:"));
+ startSecondField = new JTextField();
+ inputPanel.add(startSecondField, "wrap");
+
+ //UI logic stuff
+ if (this.dialogType == EDIT_JOB) {
+ startSecondField.setText(String.valueOf( this.job.getStartSecond() ));
+ }
+ }
+
+ private void setupEndSecond() {
+ //only exists for jobs, not for templates
+ if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {
+ return;
+ }
+
+ //layout stuff
+ inputPanel.add(new JLabel("End second:"));
+ endSecondField = new JTextField();
+ inputPanel.add(endSecondField, "wrap");
+
+ //UI logic stuff
+ if (this.dialogType == EDIT_JOB) {
+ endSecondField.setText(String.valueOf( this.job.getEndSecond() ));
+ }
+ }
+
+ private void setupExecBefore() {
+ //layout stuff
+ inputPanel.add(new JLabel("Exec before capture:"));
+ execBeforeField = new JTextArea(3, 1);
+ inputPanel.add(new JScrollPane(execBeforeField), "wrap");
+
+ //UI logic stuff
+ if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ execBeforeField.setText(this.job.getExecuteBeforeCap());
+ }
+ }
+
+ private void setupExecAfter() {
+ //layout stuff
+ inputPanel.add(new JLabel("Exec after capture:"));
+ execAfterField = new JTextArea(3, 1);
+ inputPanel.add(new JScrollPane(execAfterField), "wrap");
+
+ //UI logic stuff
+ if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ execAfterField.setText(this.job.getExecuteAfterCap());
+ }
+ }
+
+ private void setupVideoDestination() {
+ //layout stuff
+ inputPanel.add(new JLabel("Video destination:"));
+ videoDestinationField = new JTextField();
+ videoDestinationField.setEditable(false);
+ inputPanel.add(videoDestinationField);
+ videoDestinationChooserButton = new FileChooserButton();
+ inputPanel.add(videoDestinationChooserButton, "wrap 20");
+
+ //UI logic stuff
+ videoDestinationChooserButton.addActionListener(this);
+ this.videoDestinationFC = createConfiguredFileChooser();
+ if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ this.videoDestinationFC.setCurrentDirectory(this.job.getVideoDestination());
+ } else {
+ this.videoDestinationFC.setSelectedFile(this.job.getVideoDestination());
+ }
+
+ this.videoDestinationField.setText(this.job.getVideoDestination().getAbsolutePath());
+ }
+ if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {
+ this.videoDestinationFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ }
+ }
+
+ private void setupPluginPreferences() {
+ for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
+ String pluginName = plugin.getName();
+ //only display settings if the plugin actually has any...
+ Properties jobSpecificDefaultPluginPreferences = plugin.getJobSpecificPreferences();
+ Properties jobPluginPreferences = null;
+ if (this.job != null) {
+ jobPluginPreferences = this.job.getEncoderPluginSettings(plugin);
+ }
+ if (jobSpecificDefaultPluginPreferences.size() > 0 && plugin.isEnabled()) {
+ //add heading
+ JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");
+ inputPanel.add(pluginHeading, "span 3,grow");
+
+ for (String pluginPreferenceKey : plugin.getJobSpecificPreferencesOrder()) {
+ String value = jobSpecificDefaultPluginPreferences.getProperty(pluginPreferenceKey);
+ if (this.job != null) {
+ if (jobPluginPreferences.containsKey(pluginPreferenceKey)) {
+ value = jobPluginPreferences.getProperty(pluginPreferenceKey);
+ }
+ }
+
+ this.setupSinglePluginSetting(plugin, pluginPreferenceKey, value);
+ }
+ }
+ }
+ }
+
+ private void setupSinglePluginSetting(EncoderPlugin plugin, String key, String value) {
+ inputPanel.add(new JLabel(key + ":"));
+
+ if (SwingGUIUtils.isBooleanValue(value)) {
+ JCheckBox checkbox = new JCheckBox();
+ checkbox.setSelected(Boolean.valueOf(value));
+ inputPanel.add(checkbox, "wrap");
+ this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), checkbox);
+ } else if (SwingGUIUtils.isFileChooser(value)) {
+ final JFileChooser fc = new JFileChooser();
+ fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+ JButton fcButton = new JButton("...");
+ final JTextField filePathField = new JTextField();
+ filePathField.setEditable(false);
+ inputPanel.add(filePathField);
+ fcButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int returnValue = fc.showOpenDialog(JobDialog.this);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ File selectedFile = fc.getSelectedFile();
+ filePathField.setText(selectedFile.getAbsolutePath());
+ }
+ }
+ });
+
+ try {
+ File selectedFile = new File(value);
+ if (selectedFile.exists()) {
+ fc.setSelectedFile(selectedFile);
+ filePathField.setText(selectedFile.getAbsolutePath());
+ }
+ } catch (Throwable e) {}
+ this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), fc);
+ inputPanel.add(fcButton);
+ } else {
+ //textfield
+ JTextField textField = new JTextField();
+ textField.setText(value);
+ this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), textField);
+ inputPanel.add(textField, "wrap");
+ }
+ }
+
+ private void setupButtonPart() {
+ String createButtonText;
+ if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ createButtonText = "Create";
+ } else {
+ createButtonText = "Save";
+ }
+ buttonPanel = new JPanel(new MigLayout("insets 0"));
+ createButton = new JButton(createButtonText);
+ createButton.addActionListener(this);
+ cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(this);
+
+ buttonPanel.add(createButton);
+ buttonPanel.add(cancelButton);
+
+ getContentPane().add(buttonPanel);
+ }
+
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == enginePathChooserButton) {
+ int returnValue = this.enginePathFC.showOpenDialog(this);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ File selectedFile = this.enginePathFC.getSelectedFile();
+ this.enginePathField.setText(selectedFile.getAbsolutePath());
+ }
+ } else if (e.getSource() == dpVideoDirChooserButton) {
+ int returnValue = this.dpVideoDirFC.showOpenDialog(this);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ File selectedFile = this.dpVideoDirFC.getSelectedFile();
+ this.dpVideoDirField.setText(selectedFile.getAbsolutePath());
+ }
+ } else if (e.getSource() == demoFileChooserButton) {
+ int returnValue = this.demoFileFC.showOpenDialog(this);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ File selectedFile = this.demoFileFC.getSelectedFile();
+ if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+ this.demoFileField.setText(DemoRecorderUtils.getJustFileNameOfPath(selectedFile));
+ } else {
+ //template, show full path of directory
+ this.demoFileField.setText(selectedFile.getAbsolutePath());
+ }
+
+ }
+ } else if (e.getSource() == videoDestinationChooserButton) {
+ int returnValue = this.videoDestinationFC.showSaveDialog(this);
+ if (returnValue == JFileChooser.APPROVE_OPTION) {
+ File selectedFile = this.videoDestinationFC.getSelectedFile();
+ this.videoDestinationField.setText(selectedFile.getAbsolutePath());
+ }
+ } else if (e.getSource() == createButton) {
+ switch (this.dialogType) {
+ case CREATE_NEW_JOB:
+ case CREATE_JOB_FROM_TEMPLATE:
+ this.requestNewRecordJob(); break;
+ case CREATE_NEW_TEMPLATE:
+ this.createNewTemplate();
+ break;
+ case EDIT_JOB:
+ this.editJob();
+ break;
+ case EDIT_TEMPLATE:
+ this.editTemplate();
+ break;
+ }
+ } else if (e.getSource() == cancelButton) {
+ dispose();
+ }
+ }
+
+ private void requestNewRecordJob() {
+ float startSecond, endSecond = -1;
+ try {
+ startSecond = Float.valueOf(this.startSecondField.getText());
+ endSecond = Float.valueOf(this.endSecondField.getText());
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);
+ return;
+ }
+
+ try {
+ RecordJob j = this.appLayer.createRecordJob(
+ this.jobNameField.getText(),
+ this.enginePathFC.getSelectedFile(),
+ this.engineParameterField.getText(),
+ this.demoFileFC.getSelectedFile(),
+ this.relativeDemoPathField.getText(),
+ this.dpVideoDirFC.getSelectedFile(),
+ this.videoDestinationFC.getSelectedFile(),
+ this.execBeforeField.getText(),
+ this.execAfterField.getText(),
+ startSecond,
+ endSecond
+ );
+ this.saveEncoderPluginSettings(j);
+ dispose();
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog(e);
+ return;
+ }
+
+ }
+
+ private void editJob() {
+ float startSecond, endSecond = -1;
+ try {
+ startSecond = Float.valueOf(this.startSecondField.getText());
+ endSecond = Float.valueOf(this.endSecondField.getText());
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);
+ return;
+ }
+
+ try {
+ this.job.setJobName(this.jobNameField.getText());
+ this.job.setEnginePath(this.enginePathFC.getSelectedFile());
+ this.job.setEngineParameters(this.engineParameterField.getText());
+ this.job.setDemoFile(this.demoFileFC.getSelectedFile());
+ this.job.setRelativeDemoPath(this.relativeDemoPathField.getText());
+ this.job.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());
+ this.job.setVideoDestination(this.videoDestinationFC.getSelectedFile());
+ this.job.setExecuteBeforeCap(this.execBeforeField.getText());
+ this.job.setExecuteAfterCap(this.execAfterField.getText());
+ this.job.setStartSecond(startSecond);
+ this.job.setEndSecond(endSecond);
+ this.saveEncoderPluginSettings(this.job);
+ this.appLayer.fireUserInterfaceUpdate(this.job);
+ dispose();
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog(e);
+ return;
+ }
+
+ }
+
+ private void createNewTemplate() {
+ try {
+ RecordJobTemplate templ = new RecordJobTemplate(
+ this.templateNameField.getText(),
+ this.templateSummaryField.getText(),
+ this.jobNameField.getText(),
+ this.enginePathFC.getSelectedFile(),
+ this.engineParameterField.getText(),
+ this.demoFileFC.getSelectedFile(),
+ this.relativeDemoPathField.getText(),
+ this.dpVideoDirFC.getSelectedFile(),
+ this.videoDestinationFC.getSelectedFile(),
+ this.execBeforeField.getText(),
+ this.execAfterField.getText()
+ );
+ this.saveEncoderPluginSettings(templ);
+ this.tableModel.addRecordJobTemplate(templ);
+ dispose();
+ } catch (NullPointerException e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that you chose a file/directory in each case!", e, true);
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog(e);
+ return;
+ }
+ }
+
+ private void editTemplate() {
+ try {
+ RecordJobTemplate template = (RecordJobTemplate) this.job;
+ template.setName(this.templateNameField.getText());
+ template.setSummary(this.templateSummaryField.getText());
+ template.setJobName(this.jobNameField.getText());
+ template.setEnginePath(this.enginePathFC.getSelectedFile());
+ template.setEngineParameters(this.engineParameterField.getText());
+ template.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());
+ template.setRelativeDemoPath(this.relativeDemoPathField.getText());
+ template.setDemoFile(this.demoFileFC.getSelectedFile());
+ template.setExecuteBeforeCap(this.execBeforeField.getText());
+ template.setExecuteAfterCap(this.execAfterField.getText());
+ template.setVideoDestination(this.videoDestinationFC.getSelectedFile());
+ this.saveEncoderPluginSettings(template);
+ dispose();
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog(e);
+ return;
+ }
+ }
+
+ private void saveEncoderPluginSettings(RecordJob job) {
+ Set<String> keys = this.pluginDialogSettings.keySet();
+ //remember, the keys are concatenated, containing both the category and actual key
+ for (String key : keys) {
+ JComponent component = this.pluginDialogSettings.get(key);
+ if (component instanceof JCheckBox) {
+ JCheckBox checkbox = (JCheckBox) component;
+ job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), String.valueOf(checkbox.isSelected()));
+ } else if (component instanceof JFileChooser) {
+ JFileChooser fileChooser = (JFileChooser) component;
+ if (fileChooser.getSelectedFile() != null) {
+ String path = fileChooser.getSelectedFile().getAbsolutePath();
+ job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);
+ }
+ } else if (component instanceof JTextField) {
+ JTextField textField = (JTextField) component;
+ job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());
+ }
+ }
+ }
+
+ private static class FileChooserButton extends JButton {
+ private static final long serialVersionUID = 1335571540372856959L;
+ public FileChooserButton() {
+ super("...");
+ }
+ }
+
+ private JFileChooser createConfiguredFileChooser() {
+ JFileChooser fc = new JFileChooser();
+ fc.setFileHidingEnabled(false);
+ fc.setFileFilter(userDirFilter);
+ return fc;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,31 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+
+/**
+ * File filter that makes sure that the hidden .nexuiz directory is being shown in the
+ * file dialog, but other hidden directories are not.
+ */
+public class NexuizUserDirFilter extends FileFilter {
+
+ @Override
+ public boolean accept(File f) {
+ if (f.isHidden()) {
+ if (f.isDirectory() && DemoRecorderUtils.getJustFileNameOfPath(f).equals(".nexuiz")) {
+ return true;
+ }
+ return false; //don't show other hidden directories/files
+ }
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return null;
+ }
+
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,195 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import net.miginfocom.swing.MigLayout;
+
+import org.jdesktop.swingx.JXTitledSeparator;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;
+
+public class PreferencesDialog extends JDialog implements ActionListener {
+
+ private static final long serialVersionUID = 7328399646538571333L;
+ private Frame parentFrame;
+ private DemoRecorderApplication appLayer;
+ private NDRPreferences preferences;
+ private Map<String, JComponent> dialogSettings;
+
+ private JButton saveButton = new JButton("Save");
+ private JButton cancelButton = new JButton("Cancel");
+
+ public PreferencesDialog(Frame owner, DemoRecorderApplication appLayer) {
+ super(owner, true);
+ this.parentFrame = owner;
+ this.appLayer = appLayer;
+ this.preferences = appLayer.getPreferences();
+ this.dialogSettings = new HashMap<String, JComponent>();
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+ setTitle("Preferences");
+
+ this.setupLayout();
+ }
+
+ private void setupLayout() {
+ setLayout(new MigLayout("wrap 2", "[][::150,fill]"));
+
+ //add heading
+ JXTitledSeparator applicationHeading = new JXTitledSeparator("Application settings");
+ getContentPane().add(applicationHeading, "span 2,grow");
+
+ for (int i = 0; i < DemoRecorderApplication.Preferences.PREFERENCES_ORDER.length; i++) {
+ String currentSetting = DemoRecorderApplication.Preferences.PREFERENCES_ORDER[i];
+ if (this.preferences.getProperty(NDRPreferences.MAIN_APPLICATION, currentSetting) != null) {
+ this.setupSingleSetting(NDRPreferences.MAIN_APPLICATION, currentSetting);
+ }
+ }
+
+ //add plugin settings
+ for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
+ String pluginName = plugin.getName();
+ //only display settings if the plugin actually has any...
+ Properties pluginPreferences = plugin.getGlobalPreferences();
+ if (pluginPreferences.size() > 0) {
+ //add heading
+ JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");
+ getContentPane().add(pluginHeading, "span 2,grow");
+
+ for (String pluginKey : plugin.getGlobalPreferencesOrder()) {
+ if (this.preferences.getProperty(pluginName, pluginKey) != null) {
+ this.setupSingleSetting(pluginName, pluginKey);
+ }
+ }
+ }
+ }
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.add(saveButton);
+ buttonPanel.add(cancelButton);
+ saveButton.addActionListener(this);
+ cancelButton.addActionListener(this);
+ getContentPane().add(buttonPanel, "span 2");
+ }
+
+ private void setupSingleSetting(String category, String setting) {
+ getContentPane().add(new JLabel(setting + ":"));
+
+ String value = this.preferences.getProperty(category, setting);
+ if (SwingGUIUtils.isBooleanValue(value)) {
+ JCheckBox checkbox = new JCheckBox();
+ this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), checkbox);
+ getContentPane().add(checkbox);
+ } else if (SwingGUIUtils.isFileChooser(value)) {
+ final JFileChooser fc = new JFileChooser();
+ fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+ JButton fcButton = new JButton("...");
+ fcButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ fc.showOpenDialog(PreferencesDialog.this);
+ }
+ });
+ this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), fc);
+ getContentPane().add(fcButton);
+ } else {
+ JTextField textField = new JTextField();
+ this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), textField);
+ getContentPane().add(textField);
+ }
+ }
+
+
+
+ public void showDialog() {
+ this.loadSettings();
+ this.pack();
+ this.setLocationRelativeTo(this.parentFrame);
+ setResizable(false);
+ this.setVisible(true);
+ }
+
+ /**
+ * Loads the settings from the application layer (and global plug-in settings) to the form.
+ */
+ private void loadSettings() {
+ Set<Object> keys = this.preferences.keySet();
+ for (Object keyObj : keys) {
+ String concatenatedKey = (String) keyObj;
+ String value;
+ JComponent component = null;
+ if ((value = this.preferences.getProperty(concatenatedKey)) != null) {
+ if (SwingGUIUtils.isBooleanValue(value)) {
+ component = this.dialogSettings.get(concatenatedKey);
+ if (component != null) {
+ ((JCheckBox) component).setSelected(Boolean.valueOf(value));
+ }
+ } else if (SwingGUIUtils.isFileChooser(value)) {
+ component = this.dialogSettings.get(concatenatedKey);
+ try {
+ File selectedFile = new File(value);
+ if (selectedFile.exists() && component != null) {
+ ((JFileChooser) component).setSelectedFile(selectedFile);
+ }
+ } catch (Throwable e) {}
+
+ } else {
+ component = this.dialogSettings.get(concatenatedKey);
+ if (component != null) {
+ ((JTextField) component).setText(value);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == cancelButton) {
+ this.setVisible(false);
+ } else if (e.getSource() == saveButton) {
+ this.saveSettings();
+ }
+ }
+
+ private void saveSettings() {
+ Set<String> keys = this.dialogSettings.keySet();
+ //remember, the keys are concatenated, containing both the category and actual key
+ for (String key : keys) {
+ JComponent component = this.dialogSettings.get(key);
+ if (component instanceof JCheckBox) {
+ JCheckBox checkbox = (JCheckBox) component;
+ this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), checkbox.isSelected());
+ } else if (component instanceof JFileChooser) {
+ JFileChooser fileChooser = (JFileChooser) component;
+ if (fileChooser.getSelectedFile() != null) {
+ String path = fileChooser.getSelectedFile().getAbsolutePath();
+ this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);
+ }
+ } else if (component instanceof JTextField) {
+ JTextField textField = (JTextField) component;
+ this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());
+ }
+ }
+ this.setVisible(false);
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,113 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.io.File;
+
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public class RecordJobTemplate extends RecordJob {
+
+ private static final long serialVersionUID = 8311386509410161395L;
+ private String templateName;
+ private String summary;
+
+ public RecordJobTemplate(
+ String templateName,
+ String summary,
+ String jobName,
+ File enginePath,
+ String engineParameters,
+ File demoFile,
+ String relativeDemoPath,
+ File dpVideoPath,
+ File videoDestination,
+ String executeBeforeCap,
+ String executeAfterCap
+ ) {
+ super();
+
+ /*
+ * Differences to jobs:
+ * - name and summary exist
+ * - "Demo file:" -> "Demo directory:"
+ * - no start/end second
+ */
+
+ if (templateName == null || summary == null || jobName == null || enginePath == null || engineParameters == null ||
+ demoFile == null || relativeDemoPath == null || dpVideoPath == null || videoDestination == null
+ || executeBeforeCap == null || executeAfterCap == null) {
+ throw new DemoRecorderException("Error: Make sure that you filled the necessary fields! (file choosers!)");
+ }
+
+ this.templateName = templateName;
+ this.summary = summary;
+ this.jobName = jobName;
+ this.enginePath = enginePath;
+ this.engineParameters = engineParameters;
+ this.demoFile = demoFile;
+ this.relativeDemoPath = relativeDemoPath;
+ this.dpVideoPath = dpVideoPath;
+ this.videoDestination = videoDestination;
+ this.executeBeforeCap = executeBeforeCap;
+ this.executeAfterCap = executeAfterCap;
+ }
+
+ public String getName() {
+ return templateName;
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ public void setName(String name) {
+ this.templateName = name;
+ }
+
+ public void setSummary(String summary) {
+ this.summary = summary;
+ }
+
+ /*
+ * (non-Javadoc)
+ * Overwrite this method because here we want to do the read/write test for the path directly
+ * (as this one already is the directory), and not its parent directory.
+ * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setDemoFile(java.io.File)
+ */
+ public void setDemoFile(File demoFile) {
+ if (demoFile == null || !demoFile.exists()) {
+ throw new DemoRecorderException("Could not locate demo file!");
+ }
+ if (!doReadWriteTest(demoFile)) {
+ throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");
+ }
+ this.demoFile = demoFile.getAbsoluteFile();
+ }
+
+ /*
+ * (non-Javadoc)
+ * Overwrite this method because here we want to do the read/write test for the path directly
+ * (as this one already is the directory), and not its parent directory.
+ * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setVideoDestination(java.io.File)
+ */
+ public void setVideoDestination(File videoDestination) {
+ //keep in mind, here videoDestination points to the destination directory, not the destination file
+ if (videoDestination == null || !videoDestination.isDirectory()) {
+ throw new DemoRecorderException("Could not locate the specified video destination directory");
+ }
+
+ if (!this.doReadWriteTest(videoDestination)) {
+ throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");
+ }
+
+ this.videoDestination = videoDestination.getAbsoluteFile();
+ }
+
+ public String getJobName() {
+ return this.jobName;
+ }
+
+ public void setJobName(String jobName) {
+ this.jobName = jobName;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,86 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.SystemColor;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+public class StatusBar extends JPanel {
+
+ private static final long serialVersionUID = -1471757496863555741L;
+ private JLabel currentActivity = null;
+
+ private static final String STATE_IDLE = "Idle";
+ private static final String STATE_WORKING = "Working";
+
+ public StatusBar() {
+ BorderLayout borderLayout = new BorderLayout(0, 0);
+ setLayout(borderLayout);
+ JPanel rightPanel = new JPanel(new BorderLayout());
+ rightPanel.add(new JLabel(new AngledLinesWindowsCornerIcon()), BorderLayout.SOUTH);
+ rightPanel.setOpaque(false);
+
+ add(rightPanel, BorderLayout.EAST);
+
+ this.currentActivity = new JLabel("Idle");
+ add(this.currentActivity, BorderLayout.WEST);
+ setBackground(SystemColor.control);
+ setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.black));
+ }
+
+ /**
+ * Sets the state/display of the status bar to "idle" (false) or "working" (true).
+ * @param state
+ */
+ public void showState(boolean state) {
+ if (state) {
+ currentActivity.setText(STATE_WORKING);
+ } else {
+ currentActivity.setText(STATE_IDLE);
+ }
+ }
+
+ private static class AngledLinesWindowsCornerIcon implements Icon {
+ private static final Color WHITE_LINE_COLOR = new Color(255, 255, 255);
+
+ private static final Color GRAY_LINE_COLOR = new Color(172, 168, 153);
+ private static final int WIDTH = 13;
+
+ private static final int HEIGHT = 13;
+
+ public int getIconHeight() {
+ return HEIGHT;
+ }
+
+ public int getIconWidth() {
+ return WIDTH;
+ }
+
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+
+ g.setColor(WHITE_LINE_COLOR);
+ g.drawLine(0, 12, 12, 0);
+ g.drawLine(5, 12, 12, 5);
+ g.drawLine(10, 12, 12, 10);
+
+ g.setColor(GRAY_LINE_COLOR);
+ g.drawLine(1, 12, 12, 1);
+ g.drawLine(2, 12, 12, 2);
+ g.drawLine(3, 12, 12, 3);
+
+ g.drawLine(6, 12, 12, 6);
+ g.drawLine(7, 12, 12, 7);
+ g.drawLine(8, 12, 12, 8);
+
+ g.drawLine(11, 12, 12, 11);
+ g.drawLine(12, 12, 12, 12);
+
+ }
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,1109 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.help.HelpBroker;
+import javax.help.HelpSet;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.UIManager;
+import javax.swing.border.TitledBorder;
+
+import net.miginfocom.swing.MigLayout;
+
+import org.jdesktop.swingx.JXTable;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.jobs.RecordJob.State;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.DemoRecorderUI;
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobsTableModel;
+import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;
+import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties;
+import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties.XTableState;
+
+public class SwingGUI extends JFrame implements WindowListener, DemoRecorderUI {
+
+ private static final long serialVersionUID = -7287303462488231068L;
+ public static final String JOB_TABLE_PREFERENCES_FILENAME = "jobsTable.pref";
+ public static final String TEMPLATE_TABLE_PREFERENCES_FILENAME = "templatesTable.pref";
+ public static final String TEMPLATE_TABLE_CONTENT_FILENAME = "templates.dat";
+
+ private DemoRecorderApplication appLayer;
+ private PreferencesDialog preferencesDialog;
+
+ private JXTable jobsTable = null;
+ private JPopupMenu jobsTablePopupMenu;
+ private ActionListener jobButtonActionListener = new JobButtonActionListener();
+ private MouseListener jobsTableMouseListener = new JobsTableMouseListener();
+
+ private JXTable templatesTable = null;
+ private JPopupMenu templatesTablePopupMenu;
+ private ActionListener templateButtonActionListener = new TemplateButtonActionListener();
+ private MouseListener templatesTableMouseListener = new TemplatesTableMouseListener();
+
+ private ActionListener recordButtonActionListener = new RecordButtonActionListener();
+
+ private static final String LABEL_JOB_CREATE = "Create";
+ private static final String LABEL_JOB_CREATE_FROM_TEMPL = "Create from template";
+ private static final String LABEL_JOB_DELETE = "Delete";
+ private static final String LABEL_JOB_CLEAR = "Clear";
+ private static final String LABEL_JOB_EDIT = "Edit job";
+ private static final String LABEL_JOB_DUPLICATE = "Duplicate job";
+ private static final String LABEL_JOB_START = "Start job";
+ private static final String LABEL_JOB_SHOWERROR = "Show error message";
+ private static final String LABEL_JOB_RESET_STATE_WAITING = "Reset job status to 'waiting'";
+ private static final String LABEL_JOB_RESET_STATE_DONE = "Reset job status to 'done'";
+
+ private static final String LABEL_TEMPL_CREATE = "Create";
+ private static final String LABEL_TEMPL_CREATE_FROM_JOB = "Create from job";
+ private static final String LABEL_TEMPL_DELETE = "Delete";
+ private static final String LABEL_TEMPL_CLEAR = "Clear";
+ private static final String LABEL_TEMPL_EDIT = "Edit template";
+ private static final String LABEL_TEMPL_DUPLICATE = "Duplicate template";
+
+ private ActionListener menuButtonActionListener = new MenuButtonActionListener();
+ private JMenuItem fileLoadQueue = new JMenuItem("Load job queue", getIcon("fileopen.png"));
+ private JMenuItem fileSaveQueue = new JMenuItem("Save job queue", getIcon("filesave.png"));
+ private JMenuItem filePreferences = new JMenuItem("Preferences", getIcon("advanced.png"));
+ private JMenuItem fileExit = new JMenuItem("Exit", getIcon("exit.png"));
+ private JMenuItem helpHelp = new JMenuItem("Show help", getIcon("help.png"));
+ private JMenuItem helpAbout = new JMenuItem("About", getIcon("info.png"));
+ private JFileChooser jobQueueSaveAsFC = new JFileChooser();
+
+ private JButton jobs_create = new JButton(LABEL_JOB_CREATE, getIcon("edit_add.png"));
+ private JButton jobs_createFromTempl = new JButton(LABEL_JOB_CREATE_FROM_TEMPL, getIcon("view_right_p.png"));
+ private JButton jobs_delete = new JButton(LABEL_JOB_DELETE, getIcon("editdelete.png"));
+ private JButton jobs_clear = new JButton(LABEL_JOB_CLEAR, getIcon("editclear.png"));
+ private JMenuItem jobs_contextmenu_edit = new JMenuItem(LABEL_JOB_EDIT, getIcon("edit.png"));
+ private JMenuItem jobs_contextmenu_duplicate = new JMenuItem(LABEL_JOB_DUPLICATE, getIcon("editcopy.png"));
+ private JMenuItem jobs_contextmenu_delete = new JMenuItem(LABEL_JOB_DELETE, getIcon("editdelete.png"));
+ private JMenuItem jobs_contextmenu_start = new JMenuItem(LABEL_JOB_START, getIcon("player_play.png"));
+ private JMenuItem jobs_contextmenu_showerror = new JMenuItem(LABEL_JOB_SHOWERROR, getIcon("status_unknown.png"));
+ private JMenuItem jobs_contextmenu_resetstate_waiting = new JMenuItem(LABEL_JOB_RESET_STATE_WAITING, getIcon("quick_restart.png"));
+ private JMenuItem jobs_contextmenu_resetstate_done = new JMenuItem(LABEL_JOB_RESET_STATE_DONE, getIcon("quick_restart_blue.png"));
+ private List<JMenuItem> jobs_contextmenu_runPluginMenuItems = new ArrayList<JMenuItem>();
+
+ private JButton templ_create = new JButton(LABEL_TEMPL_CREATE, getIcon("edit_add.png"));
+ private JButton templ_createFromJob = new JButton(LABEL_TEMPL_CREATE_FROM_JOB, getIcon("view_right_p.png"));
+ private JButton templ_delete = new JButton(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));
+ private JButton templ_clear = new JButton(LABEL_TEMPL_CLEAR, getIcon("editclear.png"));
+ private JMenuItem templ_contextmenu_edit = new JMenuItem(LABEL_TEMPL_EDIT, getIcon("edit.png"));
+ private JMenuItem templ_contextmenu_duplicate = new JMenuItem(LABEL_TEMPL_DUPLICATE, getIcon("editcopy.png"));
+ private JMenuItem templ_contextmenu_delete = new JMenuItem(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));
+
+ private static final String PROCESSING_START = "Start processing";
+ private static final String PROCESSING_STOP_NOW = "Stop processing";
+ private static final String PROCESSING_STOP_LATER = "Processing will stop after current job finished";
+ private JButton processing_start = new JButton(PROCESSING_START, getIcon("player_play.png"));
+ private JButton processing_stop = new JButton(PROCESSING_STOP_NOW, getIcon("player_pause.png"));
+
+ private StatusBar statusBar = new StatusBar();
+
+ private static HelpBroker mainHelpBroker = null;
+ private static final String mainHelpSetName = "help/DemoRecorderHelp.hs";
+
+ public SwingGUI(DemoRecorderApplication appLayer) {
+ super("Nexuiz Demo Recorder v0.2");
+ addWindowListener(this);
+
+ this.appLayer = appLayer;
+
+ this.setupLayout();
+ this.setupHelp();
+ this.preferencesDialog = new PreferencesDialog(this, appLayer);
+
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ // Display the window.
+ pack();
+ setVisible(true);
+ //now that we have the GUI we can set the parent window for the error dialog
+ ShowErrorDialogExceptionHandler.setParentWindow(this);
+ }
+
+ private void setupHelp() {
+ if (mainHelpBroker == null){
+ HelpSet mainHelpSet = null;
+
+ try {
+ URL hsURL = HelpSet.findHelpSet(null, mainHelpSetName);
+ mainHelpSet = new HelpSet(null, hsURL);
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog("Could not properly create the help", e, true);
+ }
+
+ if (mainHelpSet != null)
+ mainHelpBroker = mainHelpSet.createHelpBroker();
+ }
+ }
+
+ private void setupLayout() {
+ setLayout(new MigLayout("wrap 1,insets 10", "[400:700:,grow,fill]",
+ "[grow,fill][grow,fill][][]"));
+ Container contentPane = getContentPane();
+ setJMenuBar(this.buildMenu());
+
+ this.setupTemplatePanel();
+ this.setupJobPanel();
+ this.setupRecordPanel();
+
+ contentPane.add(statusBar, "south,height 23::");
+ }
+
+ private void setupTemplatePanel() {
+ JPanel templatePanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));
+ TitledBorder templatePanelTitle = BorderFactory.createTitledBorder("Templates");
+ templatePanel.setBorder(templatePanelTitle);
+ getContentPane().add(templatePanel);
+
+ this.setupTemplatesTable();
+ this.loadTableStates(this.templatesTable);
+ JScrollPane templateScrollPane = new JScrollPane(templatesTable);
+ templatePanel.add(templateScrollPane);
+
+ this.templ_create.addActionListener(this.templateButtonActionListener);
+ this.templ_createFromJob.addActionListener(this.templateButtonActionListener);
+ this.templ_delete.addActionListener(this.templateButtonActionListener);
+ this.templ_clear.addActionListener(this.templateButtonActionListener);
+
+ this.templ_contextmenu_edit.addActionListener(this.templateButtonActionListener);
+ this.templ_contextmenu_duplicate.addActionListener(this.templateButtonActionListener);
+ this.templ_contextmenu_delete.addActionListener(this.templateButtonActionListener);
+
+ this.configureTableButtons();
+
+ JPanel templateControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));
+ templateControlButtonPanel.add(this.templ_create);
+ templateControlButtonPanel.add(this.templ_createFromJob);
+ templateControlButtonPanel.add(this.templ_delete);
+ templateControlButtonPanel.add(this.templ_clear);
+ templatePanel.add(templateControlButtonPanel);
+ }
+
+ private void setupJobPanel() {
+ JPanel jobPanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));
+ TitledBorder jobPanelTitle = BorderFactory.createTitledBorder("Jobs");
+ jobPanel.setBorder(jobPanelTitle);
+ getContentPane().add(jobPanel);
+
+ this.setupJobsTable();
+ this.loadTableStates(this.jobsTable);
+
+ JScrollPane jobScrollPane = new JScrollPane(jobsTable);
+ jobPanel.add(jobScrollPane);
+
+ this.jobs_create.addActionListener(this.jobButtonActionListener);
+ this.jobs_createFromTempl.addActionListener(this.jobButtonActionListener);
+ this.jobs_delete.addActionListener(this.jobButtonActionListener);
+ this.jobs_clear.addActionListener(this.jobButtonActionListener);
+
+ this.jobs_contextmenu_edit.addActionListener(this.jobButtonActionListener);
+ this.jobs_contextmenu_duplicate.addActionListener(this.jobButtonActionListener);
+ this.jobs_contextmenu_delete.addActionListener(this.jobButtonActionListener);
+ this.jobs_contextmenu_start.addActionListener(this.jobButtonActionListener);
+ this.jobs_contextmenu_showerror.addActionListener(this.jobButtonActionListener);
+ this.jobs_contextmenu_resetstate_waiting.addActionListener(this.jobButtonActionListener);
+ this.jobs_contextmenu_resetstate_done.addActionListener(this.jobButtonActionListener);
+
+ //initialize button states
+ configureTableButtons();
+
+ JPanel jobControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));
+ jobControlButtonPanel.add(this.jobs_create);
+ jobControlButtonPanel.add(this.jobs_createFromTempl);
+ jobControlButtonPanel.add(this.jobs_delete);
+ jobControlButtonPanel.add(this.jobs_clear);
+ jobPanel.add(jobControlButtonPanel);
+ }
+
+ private void setupJobsTable() {
+ RecordJobsTableModel tableModel = new RecordJobsTableModel(this.appLayer);
+ jobsTable = new JXTable(tableModel);
+ jobsTable.setColumnControlVisible(true);
+ jobsTable.setPreferredScrollableViewportSize(new Dimension(400, 100));
+ jobsTable.addMouseListener(this.jobsTableMouseListener);
+ }
+
+ private void setupTemplatesTable() {
+ RecordJobTemplatesTableModel tableModel = new RecordJobTemplatesTableModel();
+ templatesTable = new JXTable(tableModel);
+ templatesTable.setColumnControlVisible(true);
+ templatesTable.setPreferredScrollableViewportSize(new Dimension(400, 100));
+ templatesTable.addMouseListener(this.templatesTableMouseListener);
+ }
+
+ private void setupRecordPanel() {
+ JPanel recButtonPanel = new JPanel(new MigLayout());
+ recButtonPanel.add(processing_start);
+ recButtonPanel.add(processing_stop);
+ processing_stop.setEnabled(false);
+ processing_start.addActionListener(recordButtonActionListener);
+ processing_stop.addActionListener(recordButtonActionListener);
+ getContentPane().add(recButtonPanel);
+ }
+
+ public static void setSystemLAF() {
+ try {
+ // Set System L&F
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception e) {
+ }
+ }
+
+ public void RecordJobPropertiesChange(RecordJob job) {
+ RecordJobsTableModel jobsTableModel = (RecordJobsTableModel) this.jobsTable.getModel();
+ List<RecordJob> recordJobs = jobsTableModel.getRecordJobs();
+ int jobIndex = recordJobs.indexOf(job);
+ if (jobIndex == -1) {
+ //new job
+ recordJobs.add(job);
+ //add job at the end of the table:
+ int position = jobsTableModel.getRowCount() - 1;
+ jobsTableModel.fireTableRowsInserted(position, position);
+ } else {
+ //job already existed
+ jobIndex = this.jobsTable.convertRowIndexToView(jobIndex); //convert due to possible view sorting
+ jobsTableModel.fireTableRowsUpdated(jobIndex, jobIndex);
+ }
+ }
+
+ public void recordingFinished() {
+ JOptionPane.showMessageDialog(SwingGUI.this, "Finished recording all jobs", "Recording done", JOptionPane.INFORMATION_MESSAGE);
+ statusBar.showState(false);
+ processing_start.setEnabled(true);
+ processing_stop.setEnabled(false);
+ processing_stop.setText(PROCESSING_STOP_NOW);
+ }
+
+ private JMenuBar buildMenu() {
+ JMenuBar menuBar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.add(fileLoadQueue);
+ fileMenu.add(fileSaveQueue);
+ fileMenu.add(filePreferences);
+ fileMenu.add(fileExit);
+ menuBar.add(fileMenu);
+
+ fileLoadQueue.addActionListener(menuButtonActionListener);
+ fileSaveQueue.addActionListener(menuButtonActionListener);
+ filePreferences.addActionListener(menuButtonActionListener);
+ fileExit.addActionListener(menuButtonActionListener);
+
+ JMenu helpMenu = new JMenu("Help");
+ helpMenu.add(helpHelp);
+ helpMenu.add(helpAbout);
+ menuBar.add(helpMenu);
+
+ helpHelp.addActionListener(menuButtonActionListener);
+ helpAbout.addActionListener(menuButtonActionListener);
+
+ this.setupEncoderPluginButtons();
+
+ this.jobsTablePopupMenu = new JPopupMenu();
+ this.jobsTablePopupMenu.add(jobs_contextmenu_edit);
+ this.jobsTablePopupMenu.add(jobs_contextmenu_duplicate);
+ this.jobsTablePopupMenu.add(jobs_contextmenu_delete);
+ this.jobsTablePopupMenu.add(jobs_contextmenu_start);
+ //add JMenus for plugins
+ for (JMenuItem menuItem : jobs_contextmenu_runPluginMenuItems) {
+ this.jobsTablePopupMenu.add(menuItem);
+ }
+ this.jobsTablePopupMenu.add(jobs_contextmenu_showerror);
+ this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_waiting);
+ this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_done);
+
+
+
+
+ this.templatesTablePopupMenu = new JPopupMenu();
+ this.templatesTablePopupMenu.add(templ_contextmenu_edit);
+ this.templatesTablePopupMenu.add(templ_contextmenu_duplicate);
+ this.templatesTablePopupMenu.add(templ_contextmenu_delete);
+
+ return menuBar;
+ }
+
+ private void setupEncoderPluginButtons() {
+ for (EncoderPlugin plugin : appLayer.getEncoderPlugins()) {
+ JMenuItem pluginMenuItem = new JMenuItem("Just run " + plugin.getName() + " plugin", getIcon("package.png"));
+ pluginMenuItem.addActionListener(jobButtonActionListener);
+ this.jobs_contextmenu_runPluginMenuItems.add(pluginMenuItem);
+ }
+ }
+
+ private void saveTableStates(JXTable table) {
+ String fileName;
+ if (table == jobsTable) {
+ fileName = JOB_TABLE_PREFERENCES_FILENAME;
+ } else {
+ fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;
+ }
+ String exceptionMessage = "An error occurred while trying to save the table state file " + fileName;
+
+ XProperties.XTableProperty t = new XProperties.XTableProperty();
+ XTableState tableState;
+ try {
+ tableState = (XTableState) t.getSessionState(table);
+ } catch (Exception e) { //most likely ClassCastException
+ DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+ return;
+ }
+
+ File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);
+ DemoRecorderUtils.attemptFileCreation(tableStateFile);
+
+ try {
+ FileOutputStream fout = new FileOutputStream(tableStateFile);
+ ObjectOutputStream oos = new ObjectOutputStream(fout);
+ oos.writeObject(tableState);
+ oos.close();
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+ }
+ }
+
+ private void loadTableStates(JXTable table) {
+ String fileName;
+ if (table == jobsTable) {
+ fileName = JOB_TABLE_PREFERENCES_FILENAME;
+ } else {
+ fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;
+ }
+
+ XProperties.XTableProperty t = new XProperties.XTableProperty();
+
+ File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);
+
+ XTableState tableState;
+
+ try {
+ FileInputStream fin = new FileInputStream(tableStateFile);
+ ObjectInputStream ois = new ObjectInputStream(fin);
+ tableState = (XTableState) ois.readObject();
+ t.setSessionState(table, tableState);
+ } catch (Exception e) {
+ //manually hide columns
+ if (table == jobsTable) {
+ //re-create table to be sure
+ this.setupJobsTable();
+ //manually hide some columns
+ jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_AFTER_CAP).setVisible(false);
+ jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_BEFORE_CAP).setVisible(false);
+ jobsTable.getColumnExt(RecordJobsTableModel.VIDEO_DESTINATION_PATH).setVisible(false);
+ jobsTable.getColumnExt(RecordJobsTableModel.DPVIDEO_PATH).setVisible(false);
+ jobsTable.getColumnExt(RecordJobsTableModel.RELATIVE_DEMO_PATH).setVisible(false);
+ jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PARAMETERS).setVisible(false);
+ jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PATH).setVisible(false);
+ } else {
+ //re-create table to be sure
+ this.setupTemplatesTable();
+ //manually hide some columns
+ templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_AFTER_CAP).setVisible(false);
+ templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_BEFORE_CAP).setVisible(false);
+ templatesTable.getColumnExt(RecordJobTemplatesTableModel.VIDEO_DESTINATION_PATH).setVisible(false);
+ templatesTable.getColumnExt(RecordJobTemplatesTableModel.DPVIDEO_PATH).setVisible(false);
+ templatesTable.getColumnExt(RecordJobTemplatesTableModel.RELATIVE_DEMO_PATH).setVisible(false);
+ templatesTable.getColumnExt(RecordJobTemplatesTableModel.DEMO_FILE_PATH).setVisible(false);
+ templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PARAMETERS).setVisible(false);
+ templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PATH).setVisible(false);
+ templatesTable.getColumnExt(RecordJobTemplatesTableModel.JOB_NAME).setVisible(false);
+ }
+ }
+ }
+
+ private class MenuButtonActionListener implements ActionListener {
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == fileLoadQueue) {
+ int result = jobQueueSaveAsFC.showOpenDialog(SwingGUI.this);
+ if (result == JFileChooser.APPROVE_OPTION) {
+ File selectedFile = jobQueueSaveAsFC.getSelectedFile();
+ if (selectedFile.isFile()) {
+ RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();
+ tableModel.loadNewJobQueue(selectedFile);
+ configureTableButtons();
+ }
+ }
+
+ } else if (e.getSource() == fileSaveQueue) {
+ int result = jobQueueSaveAsFC.showSaveDialog(SwingGUI.this);
+ if (result == JFileChooser.APPROVE_OPTION) {
+ File selectedFile = jobQueueSaveAsFC.getSelectedFile();
+ if (!DemoRecorderUtils.getFileExtension(selectedFile).equals("queue")) {
+ //if file is not a .queue file, make it one
+ selectedFile = new File(selectedFile.getAbsoluteFile() + ".queue");
+ }
+ if (selectedFile.exists()) {
+ int confirm = JOptionPane.showConfirmDialog(SwingGUI.this, "File already exists. Are you sure you want to overwrite it?", "Confirm overwrite", JOptionPane.YES_NO_OPTION);
+ if (confirm == JOptionPane.NO_OPTION) {
+ return;
+ }
+ }
+ appLayer.saveJobQueue(selectedFile);
+ }
+ } else if (e.getSource() == filePreferences) {
+ preferencesDialog.showDialog();
+ } else if (e.getSource() == fileExit) {
+ shutDown();
+ } else if (e.getSource() == helpHelp) {
+ if (mainHelpBroker != null) {
+ mainHelpBroker.setDisplayed(true);
+ }
+ } else if (e.getSource() == helpAbout) {
+ showAboutBox();
+ }
+ }
+
+ }
+
+ /**
+ * Listens to the clicks on buttons that are in the job panel (next to the jobs table)
+ * or its context menu.
+ */
+ private class JobButtonActionListener implements ActionListener {
+
+ public void actionPerformed(ActionEvent e) {
+ List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+ List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);
+ if (e.getSource() == jobs_create) {
+ JobDialog jobDialog = new JobDialog(SwingGUI.this, appLayer);
+ jobDialog.showDialog();
+ configureTableButtons();
+ }
+ else if (e.getSource() == jobs_createFromTempl) {
+ if (selectedTemplates.size() != 1) {
+ return;
+ }
+ RecordJobTemplate template = (RecordJobTemplate) selectedTemplates.get(0);
+// JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer);
+ JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer, JobDialog.CREATE_JOB_FROM_TEMPLATE);
+ jobDialog.showDialog();
+ configureTableButtons();
+ }
+ else if (e.getSource() == jobs_delete || e.getSource() == jobs_contextmenu_delete) {
+ int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected job(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);
+ if (result == JOptionPane.YES_OPTION) {
+ deleteSelectedJobs(false);
+ configureTableButtons();
+ }
+ }
+ else if (e.getSource() == jobs_clear) {
+ int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the job list?", "Confirm clear", JOptionPane.YES_NO_OPTION);
+ if (result == JOptionPane.YES_OPTION) {
+ deleteSelectedJobs(true);
+ configureTableButtons();
+ }
+ } else if (e.getSource() == jobs_contextmenu_edit) {
+ if (selectedJobs.size() == 1) {
+ RecordJob selectedJob = selectedJobs.get(0);
+ JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);
+ jobDialog.showDialog();
+ configureTableButtons();
+ }
+ } else if (e.getSource() == jobs_contextmenu_showerror) {
+ if (selectedJobs.size() == 1) {
+ RecordJob selectedJob = selectedJobs.get(0);
+ DemoRecorderUtils.showNonCriticalErrorDialog(selectedJob.getLastException());
+ }
+ } else if (e.getSource() == jobs_contextmenu_resetstate_waiting) {
+ for (RecordJob job : selectedJobs) {
+ job.setState(RecordJob.State.WAITING);
+ }
+ } else if (e.getSource() == jobs_contextmenu_resetstate_done) {
+ for (RecordJob job : selectedJobs) {
+ if (job.getState() == State.ERROR_PLUGIN) {
+ job.setState(RecordJob.State.DONE);
+ }
+ }
+ } else if (e.getSource() == jobs_contextmenu_start) {
+ appLayer.recordSelectedJobs(selectedJobs);
+ if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+ processing_start.setEnabled(false);
+ processing_stop.setEnabled(true);
+ statusBar.showState(true);
+ }
+ } else if (e.getSource() == jobs_contextmenu_duplicate) {
+ if (selectedJobs.size() > 0) {
+ this.duplicateRecordJobs(selectedJobs);
+ //select all new duplicates in the table automatically
+ jobsTable.setRowSelectionInterval(jobsTable.getRowCount() - selectedJobs.size(), jobsTable.getRowCount() - 1);
+ configureTableButtons();
+ }
+ } else if (jobs_contextmenu_runPluginMenuItems.contains(e.getSource())) {
+ int index = jobs_contextmenu_runPluginMenuItems.indexOf(e.getSource());
+ EncoderPlugin selectedPlugin = appLayer.getEncoderPlugins().get(index);
+
+ appLayer.executePluginForSelectedJobs(selectedPlugin, selectedJobs);
+ if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+ processing_start.setEnabled(false);
+ processing_stop.setEnabled(true);
+ statusBar.showState(true);
+ }
+ }
+ }
+
+ private void duplicateRecordJobs(List<RecordJob> jobs) {
+ String nameSuffix = appLayer.getPreferences().getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE);
+ for (RecordJob job : jobs) {
+ RecordJob newJob = appLayer.createRecordJob(
+ job.getJobName() + nameSuffix,
+ job.getEnginePath(),
+ job.getEngineParameters(),
+ job.getDemoFile(),
+ job.getRelativeDemoPath(),
+ job.getDpVideoPath(),
+ job.getVideoDestination(),
+ job.getExecuteBeforeCap(),
+ job.getExecuteAfterCap(),
+ job.getStartSecond(),
+ job.getEndSecond()
+ );
+ newJob.setEncoderPluginSettings(job.getEncoderPluginSettings());
+ }
+ }
+
+ }
+
+ private class TemplateButtonActionListener implements ActionListener {
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == templ_create) {
+ RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+ JobDialog jobDialog = new JobDialog(SwingGUI.this, tableModel, templatesTable, appLayer);
+ jobDialog.showDialog();
+ configureTableButtons();
+ }
+ else if (e.getSource() == templ_createFromJob) {
+ this.createTemplateFromJob();
+ configureTableButtons();
+ }
+ else if (e.getSource() == templ_delete || e.getSource() == templ_contextmenu_delete) {
+ int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected template(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);
+ if (result == JOptionPane.YES_OPTION) {
+ deleteSelectedTemplates(false);
+ }
+ configureTableButtons();
+ }
+ else if (e.getSource() == templ_clear) {
+ int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the template list?", "Confirm clear", JOptionPane.YES_NO_OPTION);
+ if (result == JOptionPane.YES_OPTION) {
+ deleteSelectedTemplates(true);
+ }
+ configureTableButtons();
+ }
+ else if (e.getSource() == templ_contextmenu_edit) {
+ List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);
+ if (selectedTemplates.size() == 1) {
+ RecordJobTemplate selectedTemplate = (RecordJobTemplate) selectedTemplates.get(0);
+ JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedTemplate, appLayer, JobDialog.EDIT_TEMPLATE);
+ jobDialog.showDialog();
+ configureTableButtons();
+ }
+ }
+ else if (e.getSource() == templ_contextmenu_duplicate) {
+ List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);
+ if (selectedTemplates.size() > 0) {
+ this.duplicateTemplates(selectedTemplates);
+ //select all new duplicates in the table automatically
+ templatesTable.setRowSelectionInterval(templatesTable.getRowCount() - selectedTemplates.size(), templatesTable.getRowCount() - 1);
+ configureTableButtons();
+ }
+ }
+ }
+
+ private void createTemplateFromJob() {
+ List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+ if (selectedJobs.size() == 1) {
+ RecordJob job = selectedJobs.get(0);
+ RecordJobTemplate templ = new RecordJobTemplate(
+ "Generated from job",
+ "Generated from job",
+ job.getJobName(),
+ job.getEnginePath(),
+ job.getEngineParameters(),
+ job.getDemoFile().getParentFile(),
+ job.getRelativeDemoPath(),
+ job.getDpVideoPath(),
+ job.getVideoDestination().getParentFile(),
+ job.getExecuteBeforeCap(),
+ job.getExecuteAfterCap()
+ );
+ templ.setEncoderPluginSettings(job.getEncoderPluginSettings());
+
+ RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+ tableModel.addRecordJobTemplate(templ);
+ }
+ }
+
+ private void duplicateTemplates(List<RecordJob> selectedTemplates) {
+ for (RecordJob job : selectedTemplates) {
+ RecordJobTemplate template = (RecordJobTemplate) job;
+ RecordJobTemplate templ = new RecordJobTemplate(
+ template.getName(),
+ template.getSummary(),
+ template.getJobName(),
+ template.getEnginePath(),
+ template.getEngineParameters(),
+ template.getDemoFile(),
+ template.getRelativeDemoPath(),
+ template.getDpVideoPath(),
+ template.getVideoDestination(),
+ template.getExecuteBeforeCap(),
+ template.getExecuteAfterCap()
+ );
+ templ.setEncoderPluginSettings(template.getEncoderPluginSettings());
+
+ RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+ tableModel.addRecordJobTemplate(templ);
+ }
+ }
+ }
+
+ private class RecordButtonActionListener implements ActionListener {
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == processing_start) {
+ appLayer.startRecording();
+ if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+ processing_start.setEnabled(false);
+ processing_stop.setEnabled(true);
+ statusBar.showState(true);
+ }
+ } else if (e.getSource() == processing_stop) {
+ if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+ appLayer.stopRecording();
+ processing_stop.setEnabled(false);
+ processing_stop.setText(PROCESSING_STOP_LATER);
+ }
+ }
+ }
+ }
+
+ private void deleteSelectedJobs(boolean deleteAllJobs) {
+ RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();
+ if (deleteAllJobs) {
+ int rowCount = jobsTable.getRowCount();
+ for (int i = rowCount - 1; i >= 0; i--) {
+ int modelRowIndex = jobsTable.convertRowIndexToModel(i);
+ tableModel.deleteRecordJob(modelRowIndex, i);
+ }
+ } else {
+ int[] selectedRows = jobsTable.getSelectedRows();
+ for (int i = selectedRows.length - 1; i >= 0; i--) {
+ int modelRowIndex = jobsTable.convertRowIndexToModel(selectedRows[i]);
+ tableModel.deleteRecordJob(modelRowIndex, selectedRows[i]);
+ }
+ }
+ }
+
+ private void deleteSelectedTemplates(boolean deleteAllTemplates) {
+ RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+ if (deleteAllTemplates) {
+ int rowCount = templatesTable.getRowCount();
+ for (int i = rowCount - 1; i >= 0; i--) {
+ int modelRowIndex = templatesTable.convertRowIndexToModel(i);
+ tableModel.deleteRecordJobTemplate(modelRowIndex, i);
+ }
+ } else {
+ int[] selectedRows = templatesTable.getSelectedRows();
+ for (int i = selectedRows.length - 1; i >= 0; i--) {
+ int modelRowIndex = templatesTable.convertRowIndexToModel(selectedRows[i]);
+ tableModel.deleteRecordJobTemplate(modelRowIndex, selectedRows[i]);
+ }
+ }
+ //update the button state of buttons dealing with jobs
+ this.configureTableButtons();
+ }
+
+ /**
+ * Iterates through all RecordJob objects (or just the selected ones) and returns true
+ * if at least one of them has one or more has the given state(s).
+ * @param state
+ * @param justSelectedJobs
+ * @return
+ */
+ private boolean checkJobStates(RecordJob.State[] state, boolean justSelectedJobs) {
+ boolean foundState = false;
+ List<RecordJob> jobsToLookAt = null;
+ if (!justSelectedJobs) {
+ jobsToLookAt = this.appLayer.getRecordJobs();
+ } else {
+ jobsToLookAt = getSelectedRecordJobs(jobsTable);
+ }
+
+ for (RecordJob currentJob : jobsToLookAt) {
+ for (int i = 0; i < state.length; i++) {
+ if (currentJob.getState() == state[i]) {
+ foundState = true;
+ break;
+ }
+ }
+ }
+ return foundState;
+ }
+
+ /**
+ * Returns the list of selected RecordJobs or RecordJobTemplates.
+ * @param table jobsTable or templatesTable
+ * @return list of selected RecordJobs or RecordJobTemplates
+ */
+ private List<RecordJob> getSelectedRecordJobs(JXTable table) {
+ List<RecordJob> list = new ArrayList<RecordJob>();
+ if (table.getSelectedRowCount() > 0) {
+ int[] selectedRows = table.getSelectedRows();
+ for (int i = 0; i < selectedRows.length; i++) {
+ int modelRowIndex = table.convertRowIndexToModel(selectedRows[i]);
+ if (table == jobsTable) {
+ RecordJobsTableModel tableModel = (RecordJobsTableModel) table.getModel();
+ RecordJob job = tableModel.getRecordJob(modelRowIndex);
+ if (job != null) {
+ list.add(job);
+ }
+ } else {
+ RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) table.getModel();
+ RecordJobTemplate template = tableModel.getRecordJobTemplate(modelRowIndex);
+ if (template != null) {
+ list.add(template);
+ }
+ }
+ }
+ }
+
+ return list;
+ }
+
+ private void configureTableButtons() {
+ if (jobsTable != null) {
+ if (jobsTable.getRowCount() == 0) {
+ jobs_clear.setEnabled(false);
+ jobs_delete.setEnabled(false);
+ } else {
+ jobs_clear.setEnabled(true);
+ jobs_delete.setEnabled(true);
+ if (jobsTable.getSelectedRowCount() == 0) {
+ jobs_delete.setEnabled(false);
+ } else {
+ //go through all elements and check for attributes PROCESSING
+ RecordJob.State[] lookForState = {RecordJob.State.PROCESSING};
+ boolean foundState = checkJobStates(lookForState, false);
+ if (foundState) {
+ //we have to disable the clear and delete button
+ jobs_delete.setEnabled(false);
+ }
+ }
+ }
+ if (templatesTable.getSelectedRowCount() == 1) {
+ jobs_createFromTempl.setEnabled(true);
+ } else {
+ jobs_createFromTempl.setEnabled(false);
+ }
+ }
+
+ if (templatesTable != null) {
+ templ_createFromJob.setEnabled(false);
+ templ_delete.setEnabled(false);
+ templ_clear.setEnabled(false);
+
+ if (jobsTable != null && jobsTable.getSelectedRowCount() == 1) {
+ templ_createFromJob.setEnabled(true);
+ }
+
+ if (templatesTable.getSelectedRowCount() > 0) {
+ templ_delete.setEnabled(true);
+ }
+
+ if (templatesTable.getRowCount() > 0) {
+ templ_clear.setEnabled(true);
+ }
+ }
+ }
+
+ private class JobsTableMouseListener implements MouseListener {
+
+ public void mouseClicked(MouseEvent e) {
+ if (e != null && e.getClickCount() == 2) {
+ List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+ if (selectedJobs.size() == 1) {
+ RecordJob selectedJob = selectedJobs.get(0);
+ if (selectedJob.getState() != RecordJob.State.PROCESSING) {
+ JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);
+ jobDialog.showDialog();
+ }
+ }
+ } else {
+ configureTableButtons();
+ }
+ }
+
+ public void mouseEntered(MouseEvent e) {}
+
+ public void mouseExited(MouseEvent e) {}
+
+ public void mousePressed(MouseEvent e) {
+ this.showPopupMenu(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ this.showPopupMenu(e);
+ }
+
+ private void showPopupMenu(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ JTable table = (JTable)(e.getSource());
+ Point p = e.getPoint();
+ int row = table.rowAtPoint(p);
+ int[] selectedRows = table.getSelectedRows();
+ //figure out whether we have to reselect the current row under the pointer,
+ //which is only the case if the already selected rows don't include the one under
+ //the pointer yet
+ boolean reSelect = true;
+ for (int i = 0; i < selectedRows.length; i++) {
+ if (row == selectedRows[i]) {
+ reSelect = false;
+ break;
+ }
+ }
+
+ if (row != -1 && reSelect) {
+ table.setRowSelectionInterval(row, row);
+ }
+
+ this.configurePopupMenu();
+ configureTableButtons();
+ jobsTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ private void configurePopupMenu() {
+ //Disable all buttons first
+ jobs_contextmenu_edit.setEnabled(false);
+ jobs_contextmenu_duplicate.setEnabled(false);
+ jobs_contextmenu_delete.setEnabled(false);
+ jobs_contextmenu_resetstate_waiting.setEnabled(false);
+ jobs_contextmenu_resetstate_done.setEnabled(false);
+ jobs_contextmenu_showerror.setEnabled(false);
+ jobs_contextmenu_start.setEnabled(false);
+ for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {
+ pluginItem.setEnabled(false);
+ }
+
+ //edit, duplicate, and show error buttons
+ if (jobsTable.getSelectedRowCount() == 1) {
+ jobs_contextmenu_edit.setEnabled(true);
+
+ //Show error button
+ List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+ RecordJob selectedJob = selectedJobs.get(0);
+ if (selectedJob.getState() == RecordJob.State.ERROR || selectedJob.getState() == RecordJob.State.ERROR_PLUGIN) {
+ jobs_contextmenu_showerror.setEnabled(true);
+ }
+ }
+
+ if (jobsTable.getSelectedRowCount() > 0) {
+ jobs_contextmenu_duplicate.setEnabled(true);
+ //Delete button
+ RecordJob.State[] states = {RecordJob.State.PROCESSING};
+ if (!checkJobStates(states, false)) {
+ //none of the jobs is processing
+ jobs_contextmenu_delete.setEnabled(true);
+ jobs_contextmenu_resetstate_waiting.setEnabled(true);
+ } else {
+ jobs_contextmenu_edit.setEnabled(false);
+ jobs_contextmenu_duplicate.setEnabled(false);
+ }
+
+ //Start button
+ RecordJob.State[] states2 = {RecordJob.State.ERROR, RecordJob.State.DONE, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};
+ if (!checkJobStates(states2, true)) {
+ //only enable start if none of the selected jobs as any of the States above
+ //as the only job State that is not listed is "waiting", we only enable the button if all jobs are waiting
+ jobs_contextmenu_start.setEnabled(true);
+ }
+
+ //reset to 'done' button
+ RecordJob.State[] states3 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING};
+ if (!checkJobStates(states3, true)) {
+ //only enable the "reset to done" button when processes have the state DONE or ERROR_PLUGIN
+ jobs_contextmenu_resetstate_done.setEnabled(true);
+ }
+
+ //plugin buttons, enable only when state of the job is DONE
+ RecordJob.State[] states4 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};
+ if (!checkJobStates(states4, true)) {
+ int counter = 0;
+ for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {
+ if (appLayer.getEncoderPlugins().get(counter).isEnabled()) {
+ pluginItem.setEnabled(true);
+ }
+ counter++;
+ }
+ }
+ }
+ }
+
+ }
+
+ private class TemplatesTableMouseListener implements MouseListener {
+
+ public void mouseClicked(MouseEvent e) {
+ if (e != null && e.getClickCount() == 2) {
+ List<RecordJob> selectedJobs = getSelectedRecordJobs(templatesTable);
+ if (selectedJobs.size() == 1) {
+ RecordJobTemplate selectedJob = (RecordJobTemplate) selectedJobs.get(0);
+ JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer, JobDialog.EDIT_TEMPLATE);
+ jobDialog.showDialog();
+ configureTableButtons();
+ }
+ } else {
+ configureTableButtons();
+ }
+ }
+
+ public void mouseEntered(MouseEvent e) {}
+
+ public void mouseExited(MouseEvent e) {}
+
+ public void mousePressed(MouseEvent e) {
+ this.showPopupMenu(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ this.showPopupMenu(e);
+ }
+
+ private void showPopupMenu(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ JTable table = (JTable)(e.getSource());
+ Point p = e.getPoint();
+ int row = table.rowAtPoint(p);
+ int[] selectedRows = table.getSelectedRows();
+ //figure out whether we have to reselect the current row under the pointer,
+ //which is only the case if the already selected rows don't include the one under
+ //the pointer yet
+ boolean reSelect = true;
+ for (int i = 0; i < selectedRows.length; i++) {
+ if (row == selectedRows[i]) {
+ reSelect = false;
+ break;
+ }
+ }
+
+ if (row != -1 && reSelect) {
+ table.setRowSelectionInterval(row, row);
+ }
+
+ this.configurePopupMenu();
+ configureTableButtons();
+ templatesTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());
+ }
+ }
+
+ private void configurePopupMenu() {
+ //Various buttons
+ templ_contextmenu_edit.setEnabled(false);
+ templ_contextmenu_duplicate.setEnabled(false);
+ templ_contextmenu_delete.setEnabled(false);
+
+ //Edit button
+ if (templatesTable.getSelectedRowCount() == 1) {
+ templ_contextmenu_edit.setEnabled(true);
+ }
+
+ //Delete and duplicate button
+ if (templatesTable.getSelectedRowCount() > 0) {
+ templ_contextmenu_delete.setEnabled(true);
+ templ_contextmenu_duplicate.setEnabled(true);
+ }
+ }
+ }
+
+ private void showAboutBox() {
+ try {
+ InputStream inStream = ClassLoader.getSystemResourceAsStream("about.html");
+ StringBuffer out = new StringBuffer();
+ byte[] b = new byte[4096];
+ for (int n; (n = inStream.read(b)) != -1;) {
+ out.append(new String(b, 0, n));
+ }
+ String htmlString = out.toString();
+ htmlString = htmlString.replaceAll("[\\r\\n]", "");
+ JOptionPane.showMessageDialog(this, htmlString, "About", JOptionPane.PLAIN_MESSAGE);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public void windowActivated(WindowEvent e) {}
+ public void windowClosed(WindowEvent e) {}
+ public void windowDeactivated(WindowEvent e) {}
+ public void windowDeiconified(WindowEvent e) {}
+ public void windowIconified(WindowEvent e) {}
+ public void windowOpened(WindowEvent e) {}
+
+ public void windowClosing(WindowEvent e) {
+ this.shutDown();
+ }
+
+ private void shutDown() {
+ if (this.appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+ int result = JOptionPane.showConfirmDialog(this, "There are still jobs being recorded. Are you sure you want to exit?", "Confirm close", JOptionPane.YES_NO_OPTION);
+ if (result == JOptionPane.NO_OPTION) {
+ return;
+ }
+ }
+ saveTableStates(jobsTable);
+ saveTableStates(templatesTable);
+ saveTemplateTableContent();
+ this.appLayer.shutDown();
+ this.dispose();
+ System.exit(0);
+ }
+
+ private void saveTemplateTableContent() {
+ File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, TEMPLATE_TABLE_CONTENT_FILENAME);
+ RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+ tableModel.saveTemplateListToFile(path);
+ }
+
+ private Icon getIcon(String iconString) {
+ URL url = ClassLoader.getSystemResource("icons/" + iconString);
+ Icon i = new ImageIcon(url);
+ return i;
+ }
+
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,221 @@
+package com.nexuiz.demorecorder.ui.swinggui.tablemodels;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.ui.swinggui.RecordJobTemplate;
+import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;
+
+/**
+ * Columns:
+ * - Job Name
+ * - Engine path
+ * - Engine parameters
+ * - Demo file
+ * - Relative demo path
+ * - dpvideo path
+ * - video destination
+ * - execute before cap
+ * - execute after cap
+ * - start second
+ * - end second
+ * - status
+ * @author Marius
+ *
+ */
+public class RecordJobTemplatesTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 6541517890817708306L;
+
+ public static final int TEMPLATE_NAME = 0;
+ public static final int TEMPLATE_SUMMARY = 1;
+ public static final int JOB_NAME = 2;
+ public static final int ENGINE_PATH = 3;
+ public static final int ENGINE_PARAMETERS = 4;
+ public static final int DEMO_FILE_PATH = 5;
+ public static final int RELATIVE_DEMO_PATH = 6;
+ public static final int DPVIDEO_PATH = 7;
+ public static final int VIDEO_DESTINATION_PATH = 8;
+ public static final int EXECUTE_BEFORE_CAP = 9;
+ public static final int EXECUTE_AFTER_CAP = 10;
+
+ private static final int columns[] = {
+ TEMPLATE_NAME,
+ TEMPLATE_SUMMARY,
+ JOB_NAME,
+ ENGINE_PATH,
+ ENGINE_PARAMETERS,
+ DEMO_FILE_PATH,
+ RELATIVE_DEMO_PATH,
+ DPVIDEO_PATH,
+ VIDEO_DESTINATION_PATH,
+ EXECUTE_BEFORE_CAP,
+ EXECUTE_AFTER_CAP
+ };
+
+ private List<RecordJobTemplate> templates;
+
+ public RecordJobTemplatesTableModel() {
+ templates = new ArrayList<RecordJobTemplate>();
+
+ //load table content
+ File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, SwingGUI.TEMPLATE_TABLE_CONTENT_FILENAME);
+ this.loadTemplateListFromFile(path);
+ }
+
+ public void deleteRecordJobTemplate(int modelRowIndex, int viewRowIndex) {
+ try {
+ this.templates.remove(modelRowIndex);
+ fireTableRowsDeleted(viewRowIndex, viewRowIndex);
+ } catch (IndexOutOfBoundsException e) {
+ throw new DemoRecorderException("Couldn't find correspondig template for modelRowIndex " + modelRowIndex
+ + " and viewRowIndex " + viewRowIndex, e);
+ }
+ }
+
+ public void addRecordJobTemplate(RecordJobTemplate template) {
+ this.templates.add(template);
+ int position = this.templates.size() - 1;
+ fireTableRowsInserted(position, position);
+ }
+
+ public RecordJobTemplate getRecordJobTemplate(int modelRowIndex) {
+ return this.templates.get(modelRowIndex);
+ }
+
+ public int getColumnCount() {
+ return columns.length;
+ }
+
+ public int getRowCount() {
+ return this.templates.size();
+ }
+
+ public void saveTemplateListToFile(File path) {
+ DemoRecorderUtils.attemptFileCreation(path);
+
+ String exceptionMessage = "Could not save the templates to file " + path.getAbsolutePath();
+
+ if (!path.exists()) {
+ DemoRecorderException ex = new DemoRecorderException(exceptionMessage);
+ DemoRecorderUtils.showNonCriticalErrorDialog(ex);
+ return;
+ }
+
+ try {
+ FileOutputStream fout = new FileOutputStream(path);
+ ObjectOutputStream oos = new ObjectOutputStream(fout);
+ oos.writeObject(this.templates);
+ oos.close();
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void loadTemplateListFromFile(File path) {
+ if (!path.exists()) {
+ return;
+ }
+
+ List<RecordJobTemplate> newTemplateList;
+ try {
+ FileInputStream fin = new FileInputStream(path);
+ ObjectInputStream ois = new ObjectInputStream(fin);
+ newTemplateList = (List<RecordJobTemplate>) ois.readObject();
+ } catch (Exception e) {
+ DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the templates from file " + path.getAbsolutePath(), e, true);
+ return;
+ }
+ this.templates = newTemplateList;
+// fireTableRowsInserted(0, this.templates.size());
+ }
+
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ RecordJobTemplate template = this.templates.get(rowIndex);
+ if (template == null) {
+ return null;
+ }
+
+ if (columnIndex < 0 || columnIndex >= columns.length) {
+ return null;
+ }
+
+ String cellData = "UNDEF";
+ switch (columnIndex) {
+ case TEMPLATE_NAME:
+ cellData = template.getName(); break;
+ case TEMPLATE_SUMMARY:
+ cellData = template.getSummary(); break;
+ case JOB_NAME:
+ cellData = template.getJobName(); break;
+ case ENGINE_PATH:
+ cellData = template.getEnginePath().getAbsolutePath(); break;
+ case ENGINE_PARAMETERS:
+ cellData = template.getEngineParameters(); break;
+ case DEMO_FILE_PATH:
+ cellData = DemoRecorderUtils.getJustFileNameOfPath(template.getDemoFile()); break;
+ case RELATIVE_DEMO_PATH:
+ cellData = template.getRelativeDemoPath(); break;
+ case DPVIDEO_PATH:
+ cellData = template.getDpVideoPath().getAbsolutePath(); break;
+ case VIDEO_DESTINATION_PATH:
+ cellData = template.getVideoDestination().getAbsolutePath(); break;
+ case EXECUTE_BEFORE_CAP:
+ cellData = template.getExecuteBeforeCap(); break;
+ case EXECUTE_AFTER_CAP:
+ cellData = template.getExecuteAfterCap(); break;
+ }
+
+ return cellData;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ if (column < 0 || column >= columns.length) {
+ return "";
+ }
+
+ String columnName = "UNDEFINED";
+ switch (column) {
+ case TEMPLATE_NAME:
+ columnName = "Name"; break;
+ case TEMPLATE_SUMMARY:
+ columnName = "Summary"; break;
+ case JOB_NAME:
+ columnName = "Job name"; break;
+ case ENGINE_PATH:
+ columnName = "Engine path"; break;
+ case ENGINE_PARAMETERS:
+ columnName = "Engine parameters"; break;
+ case DEMO_FILE_PATH:
+ columnName = "Demo directory"; break;
+ case RELATIVE_DEMO_PATH:
+ columnName = "Relative demo path"; break;
+ case DPVIDEO_PATH:
+ columnName = "DPVideo path"; break;
+ case VIDEO_DESTINATION_PATH:
+ columnName = "Video destination"; break;
+ case EXECUTE_BEFORE_CAP:
+ columnName = "Exec before"; break;
+ case EXECUTE_AFTER_CAP:
+ columnName = "Exec after"; break;
+ }
+
+ return columnName;
+ }
+
+ public List<RecordJobTemplate> getRecordJobTemplates() {
+ return this.templates;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,192 @@
+package com.nexuiz.demorecorder.ui.swinggui.tablemodels;
+
+import java.io.File;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+/**
+ * Columns:
+ * - Job Name
+ * - Engine path
+ * - Engine parameters
+ * - Demo file
+ * - Relative demo path
+ * - dpvideo path
+ * - video destination
+ * - execute before cap
+ * - execute after cap
+ * - start second
+ * - end second
+ * - status
+ * @author Marius
+ *
+ */
+public class RecordJobsTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 5024144640874313910L;
+
+ public static final int JOB_NAME = 0;
+ public static final int ENGINE_PATH = 1;
+ public static final int ENGINE_PARAMETERS = 2;
+ public static final int DEMO_FILE_PATH = 3;
+ public static final int RELATIVE_DEMO_PATH = 4;
+ public static final int DPVIDEO_PATH = 5;
+ public static final int VIDEO_DESTINATION_PATH = 6;
+ public static final int EXECUTE_BEFORE_CAP = 7;
+ public static final int EXECUTE_AFTER_CAP = 8;
+ public static final int START_SECOND = 9;
+ public static final int END_SECOND = 10;
+ public static final int STATUS = 11;
+
+ private static final int columns[] = {
+ JOB_NAME,
+ ENGINE_PATH,
+ ENGINE_PARAMETERS,
+ DEMO_FILE_PATH,
+ RELATIVE_DEMO_PATH,
+ DPVIDEO_PATH,
+ VIDEO_DESTINATION_PATH,
+ EXECUTE_BEFORE_CAP,
+ EXECUTE_AFTER_CAP,
+ START_SECOND,
+ END_SECOND,
+ STATUS
+ };
+
+ private DemoRecorderApplication appLayer;
+ private List<RecordJob> jobList = null;
+
+ public RecordJobsTableModel(DemoRecorderApplication appLayer) {
+ this.appLayer = appLayer;
+ this.jobList = this.appLayer.getRecordJobs();
+ }
+
+ public void deleteRecordJob(int modelRowIndex, int viewRowIndex) {
+ try {
+ RecordJob job = this.jobList.get(modelRowIndex);
+ if (this.appLayer.deleteRecordJob(job)) {
+ this.jobList.remove(job);
+ fireTableRowsDeleted(viewRowIndex, viewRowIndex);
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new DemoRecorderException("Couldn't find correspondig job for modelRowIndex " + modelRowIndex
+ + " and viewRowIndex " + viewRowIndex, e);
+ }
+ }
+
+ public void loadNewJobQueue(File path) {
+ this.appLayer.loadJobQueue(path);
+ this.jobList = this.appLayer.getRecordJobs();
+ fireTableDataChanged();
+ }
+
+ public RecordJob getRecordJob(int modelRowIndex) {
+ return this.jobList.get(modelRowIndex);
+ }
+
+ public int getColumnCount() {
+ return columns.length;
+ }
+
+ public int getRowCount() {
+ return this.jobList.size();
+ }
+
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ RecordJob job = this.jobList.get(rowIndex);
+ if (job == null) {
+ return null;
+ }
+
+ if (columnIndex < 0 || columnIndex >= columns.length) {
+ return null;
+ }
+
+ String cellData = "UNDEF";
+ switch (columnIndex) {
+ case JOB_NAME:
+ cellData = job.getJobName(); break;
+ case ENGINE_PATH:
+ cellData = job.getEnginePath().getAbsolutePath(); break;
+ case ENGINE_PARAMETERS:
+ cellData = job.getEngineParameters(); break;
+ case DEMO_FILE_PATH:
+ cellData = DemoRecorderUtils.getJustFileNameOfPath(job.getDemoFile()); break;
+ case RELATIVE_DEMO_PATH:
+ cellData = job.getRelativeDemoPath(); break;
+ case DPVIDEO_PATH:
+ cellData = job.getDpVideoPath().getAbsolutePath(); break;
+ case VIDEO_DESTINATION_PATH:
+ cellData = job.getVideoDestination().getAbsolutePath(); break;
+ case EXECUTE_BEFORE_CAP:
+ cellData = job.getExecuteBeforeCap(); break;
+ case EXECUTE_AFTER_CAP:
+ cellData = job.getExecuteAfterCap(); break;
+ case START_SECOND:
+ cellData = String.valueOf(job.getStartSecond()); break;
+ case END_SECOND:
+ cellData = String.valueOf(job.getEndSecond()); break;
+ case STATUS:
+ if (job.getState() == RecordJob.State.DONE) {
+ cellData = "done";
+ } else if (job.getState() == RecordJob.State.ERROR) {
+ cellData = "error";
+ } else if (job.getState() == RecordJob.State.ERROR_PLUGIN) {
+ cellData = "plug-in error";
+ } else if (job.getState() == RecordJob.State.PROCESSING) {
+ cellData = "processing";
+ } else if (job.getState() == RecordJob.State.WAITING) {
+ cellData = "waiting";
+ }
+ }
+
+ return cellData;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ if (column < 0 || column >= columns.length) {
+ return "";
+ }
+
+ String columnName = "UNDEFINED";
+ switch (column) {
+ case JOB_NAME:
+ columnName = "Name"; break;
+ case ENGINE_PATH:
+ columnName = "Engine path"; break;
+ case ENGINE_PARAMETERS:
+ columnName = "Engine parameters"; break;
+ case DEMO_FILE_PATH:
+ columnName = "Demo name"; break;
+ case RELATIVE_DEMO_PATH:
+ columnName = "Relative demo path"; break;
+ case DPVIDEO_PATH:
+ columnName = "DPVideo path"; break;
+ case VIDEO_DESTINATION_PATH:
+ columnName = "Video destination"; break;
+ case EXECUTE_BEFORE_CAP:
+ columnName = "Exec before"; break;
+ case EXECUTE_AFTER_CAP:
+ columnName = "Exec after"; break;
+ case START_SECOND:
+ columnName = "Start"; break;
+ case END_SECOND:
+ columnName = "End"; break;
+ case STATUS:
+ columnName = "Status"; break;
+ }
+
+ return columnName;
+ }
+
+ public List<RecordJob> getRecordJobs() {
+ return this.jobList;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,21 @@
+package com.nexuiz.demorecorder.ui.swinggui.utils;
+
+import java.awt.Component;
+import java.lang.Thread.UncaughtExceptionHandler;
+
+import org.jdesktop.swingx.JXErrorPane;
+import org.jdesktop.swingx.error.ErrorInfo;
+
+public class ShowErrorDialogExceptionHandler implements UncaughtExceptionHandler {
+
+ private static Component parentWindow = null;
+
+ public void uncaughtException(Thread t, Throwable e) {
+ ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);
+ JXErrorPane.showDialog(parentWindow, info);
+ }
+
+ public static void setParentWindow(Component c) {
+ parentWindow = c;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,26 @@
+package com.nexuiz.demorecorder.ui.swinggui.utils;
+
+import java.io.File;
+
+public class SwingGUIUtils {
+ public static boolean isBooleanValue(String value) {
+ if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isFileChooser(String value) {
+ if (value.equalsIgnoreCase("filechooser")) {
+ return true;
+ }
+ try {
+ File file = new File(value);
+ if (file.exists()) {
+ return true;
+ }
+ } catch (Throwable e) {
+ }
+ return false;
+ }
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,432 @@
+/*
+ * Created on 08.02.2007
+ *
+ */
+package com.nexuiz.demorecorder.ui.swinggui.utils;
+
+import java.awt.Component;
+import java.beans.DefaultPersistenceDelegate;
+import java.beans.XMLEncoder;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.swing.SortOrder;
+import javax.swing.RowSorter.SortKey;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+
+import org.jdesktop.swingx.JXTable;
+import org.jdesktop.swingx.JXTaskPane;
+import org.jdesktop.swingx.sort.SortUtils;
+import org.jdesktop.swingx.table.TableColumnExt;
+
+/**
+ * Container class for SwingX specific SessionStorage Properties. Is Factory for
+ * custom PersistanceDelegates
+ */
+public class XProperties {
+
+ /**
+ *
+ * Registers all custom PersistenceDelegates needed by contained Property
+ * classes.
+ * <p>
+ *
+ * PersistenceDelegates are effectively static properties shared by all
+ * encoders. In other words: Register once on an arbitrary encoder makes
+ * them available for all. Example usage:
+ *
+ * <pre>
+ * <code>
+ * new XProperties.registerPersistenceDelegates();
+ * </code>
+ * </pre>
+ *
+ * PENDING JW: cleanup for 1.6 sorting/filtering incomplete. Missing storage
+ * - multiple sort keys
+ *
+ * PENDING JW: except for comparators: didn't before and is not state that's
+ * configurable by users ... so probably won't, not sure, need to revisit -
+ * comparator (?) - filters (?) - renderers/stringvalues (?) - enhanced
+ * sort-related table state (?)
+ */
+ public void registerPersistenceDelegates() {
+ XMLEncoder encoder = new XMLEncoder(System.out);
+ encoder.setPersistenceDelegate(SortKeyState.class, new DefaultPersistenceDelegate(
+ new String[] { "ascending", "modelIndex" }));
+ encoder.setPersistenceDelegate(ColumnState.class, new DefaultPersistenceDelegate(
+ new String[] { "width", "preferredWidth", "modelIndex", "visible", "viewIndex" }));
+ encoder.setPersistenceDelegate(XTableState.class, new DefaultPersistenceDelegate(
+ new String[] { "columnStates", "sortKeyState", "horizontalScrollEnabled" }));
+ }
+
+ /**
+ * Session storage support for JXTaskPane.
+ */
+ public static class XTaskPaneProperty implements Serializable {
+
+ private static final long serialVersionUID = -4069436038178318216L;
+
+ public Object getSessionState(Component c) {
+ checkComponent(c);
+ return new XTaskPaneState(((JXTaskPane) c).isCollapsed());
+ }
+
+ public void setSessionState(Component c, Object state) {
+ checkComponent(c);
+ if ((state != null) && !(state instanceof XTaskPaneState)) {
+ throw new IllegalArgumentException("invalid state");
+ }
+ ((JXTaskPane) c).setCollapsed(((XTaskPaneState) state).isCollapsed());
+ }
+
+ private void checkComponent(Component component) {
+ if (component == null) {
+ throw new IllegalArgumentException("null component");
+ }
+ if (!(component instanceof JXTaskPane)) {
+ throw new IllegalArgumentException("invalid component");
+ }
+ }
+
+ }
+
+ public static class XTaskPaneState implements Serializable {
+ private static final long serialVersionUID = 3363688961112031969L;
+ private boolean collapsed;
+
+ public XTaskPaneState() {
+ this(false);
+ }
+
+ /**
+ * @param b
+ */
+ public XTaskPaneState(boolean collapsed) {
+ this.setCollapsed(collapsed);
+ }
+
+ /**
+ * @param collapsed
+ * the collapsed to set
+ */
+ public void setCollapsed(boolean collapsed) {
+ this.collapsed = collapsed;
+ }
+
+ /**
+ * @return the collapsed
+ */
+ public boolean isCollapsed() {
+ return collapsed;
+ }
+
+ }
+
+ /**
+ * Session storage support for JXTable.
+ */
+ public static class XTableProperty implements Serializable {
+
+ private static final long serialVersionUID = -5064142292091374301L;
+
+ public Object getSessionState(Component c) {
+ checkComponent(c);
+ JXTable table = (JXTable) c;
+ List<ColumnState> columnStates = new ArrayList<ColumnState>();
+ List<TableColumn> columns = table.getColumns(true);
+ List<TableColumn> visibleColumns = table.getColumns();
+ for (TableColumn column : columns) {
+ columnStates.add(new ColumnState((TableColumnExt) column, visibleColumns
+ .indexOf(column)));
+ }
+ XTableState tableState = new XTableState(columnStates
+ .toArray(new ColumnState[columnStates.size()]));
+ tableState.setHorizontalScrollEnabled(table.isHorizontalScrollEnabled());
+ List<? extends SortKey> sortKeys = null;
+ if (table.getRowSorter() != null) {
+ sortKeys = table.getRowSorter().getSortKeys();
+ }
+ // PENDING: store all!
+ if ((sortKeys != null) && (sortKeys.size() > 0)) {
+ tableState.setSortKey(sortKeys.get(0));
+ }
+ return tableState;
+ }
+
+ public void setSessionState(Component c, Object state) {
+ checkComponent(c);
+ JXTable table = (JXTable) c;
+ XTableState tableState = ((XTableState) state);
+ ColumnState[] columnState = tableState.getColumnStates();
+ List<TableColumn> columns = table.getColumns(true);
+ if (canRestore(columnState, columns)) {
+ for (int i = 0; i < columnState.length; i++) {
+ columnState[i].configureColumn((TableColumnExt) columns.get(i));
+ }
+ restoreVisibleSequence(columnState, table.getColumnModel());
+ }
+ table.setHorizontalScrollEnabled(tableState.getHorizontalScrollEnabled());
+ if (tableState.getSortKey() != null) {
+ table.getRowSorter()
+ .setSortKeys(Collections.singletonList(tableState.getSortKey()));
+ }
+ }
+
+ private void restoreVisibleSequence(ColumnState[] columnStates, TableColumnModel model) {
+ List<ColumnState> visibleStates = getSortedVisibleColumnStates(columnStates);
+ for (int i = 0; i < visibleStates.size(); i++) {
+ TableColumn column = model.getColumn(i);
+ int modelIndex = visibleStates.get(i).getModelIndex();
+ if (modelIndex != column.getModelIndex()) {
+ int currentIndex = -1;
+ for (int j = i + 1; j < model.getColumnCount(); j++) {
+ TableColumn current = model.getColumn(j);
+ if (current.getModelIndex() == modelIndex) {
+ currentIndex = j;
+ break;
+ }
+ }
+ model.moveColumn(currentIndex, i);
+ }
+ }
+
+ }
+
+ private List<ColumnState> getSortedVisibleColumnStates(ColumnState[] columnStates) {
+ List<ColumnState> visibleStates = new ArrayList<ColumnState>();
+ for (ColumnState columnState : columnStates) {
+ if (columnState.getVisible()) {
+ visibleStates.add(columnState);
+ }
+ }
+ Collections.sort(visibleStates, new VisibleColumnIndexComparator());
+ return visibleStates;
+ }
+
+ /**
+ * Returns a boolean to indicate if it's reasonably safe to restore the
+ * properties of columns in the list from the columnStates. Here:
+ * returns true if the length of both are the same and the modelIndex of
+ * the items at the same position are the same, otherwise returns false.
+ *
+ * @param columnState
+ * @param columns
+ * @return
+ */
+ private boolean canRestore(ColumnState[] columnState, List<TableColumn> columns) {
+ if ((columnState == null) || (columnState.length != columns.size()))
+ return false;
+ for (int i = 0; i < columnState.length; i++) {
+ if (columnState[i].getModelIndex() != columns.get(i).getModelIndex()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void checkComponent(Component component) {
+ if (component == null) {
+ throw new IllegalArgumentException("null component");
+ }
+ if (!(component instanceof JXTable)) {
+ throw new IllegalArgumentException("invalid component - expected JXTable");
+ }
+ }
+
+ }
+
+ public static class XTableState implements Serializable {
+ private static final long serialVersionUID = -3566913244872587438L;
+ ColumnState[] columnStates = new ColumnState[0];
+ boolean horizontalScrollEnabled;
+ SortKeyState sortKeyState;
+
+ public XTableState(ColumnState[] columnStates, SortKeyState sortKeyState,
+ boolean horizontalScrollEnabled) {
+ this.columnStates = copyColumnStates(columnStates);
+ this.sortKeyState = sortKeyState;
+ setHorizontalScrollEnabled(horizontalScrollEnabled);
+
+ }
+
+ public void setSortKey(SortKey sortKey) {
+ this.sortKeyState = new SortKeyState(sortKey);
+
+ }
+
+ private SortKey getSortKey() {
+ if (sortKeyState != null) {
+ return sortKeyState.getSortKey();
+ }
+ return null;
+ }
+
+ public XTableState(ColumnState[] columnStates) {
+ this.columnStates = copyColumnStates(columnStates);
+ }
+
+ public ColumnState[] getColumnStates() {
+ return copyColumnStates(this.columnStates);
+ }
+
+ public boolean getHorizontalScrollEnabled() {
+ return horizontalScrollEnabled;
+ }
+
+ public void setHorizontalScrollEnabled(boolean horizontalScrollEnabled) {
+ this.horizontalScrollEnabled = horizontalScrollEnabled;
+ }
+
+ private ColumnState[] copyColumnStates(ColumnState[] states) {
+ if (states == null) {
+ throw new IllegalArgumentException("invalid columnWidths");
+ }
+ ColumnState[] copy = new ColumnState[states.length];
+ System.arraycopy(states, 0, copy, 0, states.length);
+ return copy;
+ }
+
+ public SortKeyState getSortKeyState() {
+ return sortKeyState;
+ }
+ }
+
+ /**
+ * Quick hack to make SortKey encodable. How to write a PersistenceDelegate
+ * for a SortKey? Boils down to how to write a delegate for the
+ * uninstantiable class (SwingX) SortOrder which does enum-mimickry (defines
+ * privately intantiated constants)
+ *
+ */
+ public static class SortKeyState implements Serializable {
+ private static final long serialVersionUID = 5819342622261460894L;
+
+ int modelIndex;
+
+ boolean ascending;
+
+ /**
+ * Constructor used by the custom PersistenceDelegate.
+ *
+ * @param ascending
+ * @param modelIndex
+ * @param comparator
+ */
+ public SortKeyState(boolean ascending, int modelIndex) {
+ this.ascending = ascending;
+ this.modelIndex = modelIndex;
+ }
+
+ /**
+ * Constructor used by property.
+ *
+ * @param sortKey
+ */
+ public SortKeyState(SortKey sortKey) {
+ this(SortUtils.isAscending(sortKey.getSortOrder()), sortKey.getColumn());
+ }
+
+ protected SortKey getSortKey() {
+ SortOrder sortOrder = getAscending() ? SortOrder.ASCENDING : SortOrder.DESCENDING;
+ return new SortKey(getModelIndex(), sortOrder);
+ }
+
+ public boolean getAscending() {
+ return ascending;
+ }
+
+ public int getModelIndex() {
+ return modelIndex;
+ }
+ }
+
+ public static class ColumnState implements Serializable {
+ private static final long serialVersionUID = 6037947151025126049L;
+ private int width;
+ private int preferredWidth;
+ private int modelIndex;
+ private boolean visible;
+ private int viewIndex;
+
+ /**
+ * Constructor used by the custom PersistenceDelegate.
+ *
+ * @param width
+ * @param preferredWidth
+ * @param modelColumn
+ * @param visible
+ * @param viewIndex
+ */
+ public ColumnState(int width, int preferredWidth, int modelColumn, boolean visible,
+ int viewIndex) {
+ this.width = width;
+ this.preferredWidth = preferredWidth;
+ this.modelIndex = modelColumn;
+ this.visible = visible;
+ this.viewIndex = viewIndex;
+ }
+
+ /**
+ * Constructor used by the Property.
+ *
+ * @param columnExt
+ * @param viewIndex
+ */
+ public ColumnState(TableColumnExt columnExt, int viewIndex) {
+ this(columnExt.getWidth(), columnExt.getPreferredWidth(), columnExt.getModelIndex(),
+ columnExt.isVisible(), viewIndex);
+ }
+
+ /**
+ * Restores column properties if the model index is the same as the
+ * column's model index. Does nothing otherwise.
+ * <p>
+ *
+ * Here the properties are: width, preferredWidth, visible.
+ *
+ * @param columnExt
+ * the column to configure
+ */
+ public void configureColumn(TableColumnExt columnExt) {
+ if (modelIndex != columnExt.getModelIndex())
+ return;
+ columnExt.setPreferredWidth(preferredWidth);
+ columnExt.setWidth(width);
+ columnExt.setVisible(visible);
+ }
+
+ public int getModelIndex() {
+ return modelIndex;
+ }
+
+ public int getViewIndex() {
+ return viewIndex;
+ }
+
+ public boolean getVisible() {
+ return visible;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getPreferredWidth() {
+ return preferredWidth;
+ }
+
+ }
+
+ public static class VisibleColumnIndexComparator implements Comparator<Object> {
+
+ public int compare(Object o1, Object o2) {
+ return ((ColumnState) o1).getViewIndex() - ((ColumnState) o2).getViewIndex();
+ }
+
+ }
+
+}
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/about.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/about.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/about.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,21 @@
+<html>
+ <h1>Nexuiz Demo Recorder v0.2</h1>
+ Written by GreEn`mArine<br>
+ <h2>Credits</h2>
+ <ul>
+ <li>
+ Thanks for the used icons goes to<br>
+ <a href="http://www.everaldo.com/crystal/?action=downloads">http://www.everaldo.com/crystal/?action=downloads</a>
+ </li>
+ <li>
+ Thanks to divVerent for the demotc.pl script without which<br>
+ this project would have been impossible.
+ </li>
+ </ul>
+
+ <h2>Usage</h2>
+ Open the help in order to find out how to use this software.
+
+ <h2>License</h2>
+ GPL v2
+</html>
\ No newline at end of file
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelp.hs
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelp.hs (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelp.hs 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
+<!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net--><!DOCTYPE helpset PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN" "http://java.sun.com/products/javahelp/helpset_2_0.dtd">
+
+<helpset version="1.0">
+ <title>Demo Recorder Help </title>
+ <maps>
+ <homeID>
+top
+ </homeID>
+ <mapref location="Map.jhm"/>
+
+ </maps>
+ <view>
+ <name>
+TOC
+ </name>
+ <label>
+TOC
+ </label>
+ <type>
+javax.help.TOCView
+ </type>
+ <data>
+DemoRecorderHelpTOC.xml
+ </data>
+ </view>
+
+ <view>
+ <name>
+Search
+ </name>
+ <label>
+Search
+ </label>
+ <type>
+javax.help.SearchView
+ </type>
+ <data engine="com.sun.java.help.search.DefaultSearchEngine">
+JavaHelpSearch
+ </data>
+ </view>
+</helpset>
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpIndex.xml
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpIndex.xml (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpIndex.xml 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE index PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Index Version 1.0//EN" "http://java.sun.com/products/javahelp/index_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->
+
+<index version="1.0"/>
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpTOC.xml
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpTOC.xml (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpTOC.xml 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE toc PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp TOC Version 1.0//EN" "http://java.sun.com/products/javahelp/toc_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->
+
+<toc version="1.0">
+ <tocitem text="Introduction " target="html.introduction"/>
+
+ <tocitem text="Basic tutorial " target="html.basic_tutorial"/>
+
+ <tocitem text="Templates " target="html.templates"/>
+
+ <tocitem text="Open/Save Jobs and Templates " target="html.open-save"/>
+
+ <tocitem text="Preferences dialog " target="html.preferences"/>
+
+ <tocitem text="Compatibility and limitations " target="html.compat-limitations"/>
+
+ <tocitem text="FAQ " target="html.faq"/>
+
+ <tocitem text="Advanced user topics " target="html.advanced-topics">
+ <tocitem text="How it works " target="html.advanced-how-it-works"/>
+
+ <tocitem text="Table settings " target="html.advanced-table-settings"/>
+
+ <tocitem text="Preliminary stop " target="html.advanced-prelim-stop"/>
+
+ </tocitem>
+ <tocitem text="Plug-in architecture " target="html.plugin-architecture">
+ <tocitem text="VirtualDub plug-in " target="html.plugin-virtualdub"/>
+
+ </tocitem>
+ <tocitem text="Credits " target="html.credits"/>
+
+ <tocitem text="License " target="html.license"/>
+
+ <tocitem text="Changelog " target="html.changelog"/>
+
+</toc>
\ No newline at end of file
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JHelpDev Project.xml
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JHelpDev Project.xml (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JHelpDev Project.xml 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE jhelpdev PUBLIC "-//jhelpdev.sourcefore.net//JHelpDev Configuration Settings 1.2" "http://jhelpdev.sourceforge.net/dtd/jhelpdev_1_2.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.org-->
+
+<jhelpdev version="1.2">
+ <config>
+ <project>
+DemoRecorderHelp
+ </project>
+ <projectdir>
+D:\Daten\Eclipse Projects\NexuizDemoRecorder\src\main\resources\help
+ </projectdir>
+ <startpage>
+html/introduction.html
+ </startpage>
+ <popupicon>
+
+ </popupicon>
+ </config>
+ <view>
+ <helptitle>
+Demo Recorder Help
+ </helptitle>
+ <toc showing="yes" label="Table of Contents"/>
+
+ <index showing="no" label="Index"/>
+
+ <search showing="yes" label="Search"/>
+
+ </view>
+ <encoding value="ISO-8859-1"/>
+
+</jhelpdev>
\ No newline at end of file
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS.TAB
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS.TAB (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS.TAB 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,14 @@
+eÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝÿÿÿÿÿ÷_÷_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝÿÿÿÿÿÿÿÝÿÿÿÿÿÿÿuÿÿuÿÿÿÿÿÿÿÿ÷_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝÿÿÿÿý×ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷_ÿÿÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý×ÿÿÿÿÿÿÿÿ÷_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@7û*ÌebøÖBìå(â¦Ob³J6,Ù©
+³vRꯪ/X³r(Í13,Ϫ8³J4³6/4³<£J8¨Ò¨â¯3þ*0³K¬É£J³4º¾+ÿ4³h£"(Ì£J4®º+9ªÌê/.»Ì/*7¢.9"*6Ë0¿üÈ£(£Jî£
+Ì*4£;*1¤,⮣K0£
+0³"£ºþ¨ÞʸÒó6ªÊ»³2(ªªªªªºªª®0¸Â£
+£"º®3â*͸ª£
+4®4£Kªº0ª¨Â¨Ê(ȳ
+ºªª®þÌ.¯®0£
+0º2¢ºªìª³
+Ì*0¾0³:.6(Ê(È£"+þêº*2.몣
+ªªº0ªªº2.£
+0ªª³
+0ª£
+ªª£
+«¢Ì*ª*0ª«ªê«*0ªÌ,ÂÿÌ,ÂÌ,ÂÿÿÌ/0¿ÿÿÿÿÿÿÌ/ó0³"Ì/ÿüÂÿÿÌ,È¿ÿüÂÿÌ/ÿ0¿Ì/þ«ªªªªªêªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¦
\ No newline at end of file
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/OFFSETS
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/OFFSETS
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/POSITIONS
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/POSITIONS
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/SCHEMA
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/SCHEMA (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/SCHEMA 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,2 @@
+JavaSearch 1.0
+TMAP bs=2048 rt=1 fl=-1 id1=1038 id2=1
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/TMAP
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/TMAP
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/Map.jhm
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/Map.jhm (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/Map.jhm 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE map PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 1.0//EN" "http://java.sun.com/products/javahelp/map_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->
+
+<map version="1.0">
+<mapID target="top" url="html/introduction.html "/>
+
+<mapID target="html.preferences" url="html/preferences.html"/>
+
+<mapID target="html.advanced-prelim-stop" url="html/advanced-prelim-stop.html"/>
+
+<mapID target="html.license" url="html/license.html"/>
+
+<mapID target="html.compat-limitations" url="html/compat-limitations.html"/>
+
+<mapID target="html.open-save" url="html/open-save.html"/>
+
+<mapID target="html.introduction" url="html/introduction.html"/>
+
+<mapID target="html.advanced-topics" url="html/advanced-topics.html"/>
+
+<mapID target="html.credits" url="html/credits.html"/>
+
+<mapID target="html.advanced-how-it-works" url="html/advanced-how-it-works.html"/>
+
+<mapID target="html.plugin-architecture" url="html/plugin-architecture.html"/>
+
+<mapID target="html.changelog" url="html/changelog.html"/>
+
+<mapID target="html.faq" url="html/faq.html"/>
+
+<mapID target="html.advanced-table-settings" url="html/advanced-table-settings.html"/>
+
+<mapID target="html.basic_tutorial_jobs_create" url="html/basic_tutorial.html#jobs_create"/>
+
+<mapID target="html.templates" url="html/templates.html"/>
+
+<mapID target="html.plugin-virtualdub" url="html/plugin-virtualdub.html"/>
+
+<mapID target="html.basic_tutorial" url="html/basic_tutorial.html"/>
+
+</map>
\ No newline at end of file
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-how-it-works.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-how-it-works.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-how-it-works.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,77 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>How it works</H1>
+<P>The following is a description of how
+the program processes a single job:</P>
+<UL>
+ <LI><P>The program takes your original
+ demo, e.g. “C:\Nexuiz\data\demos\test.dem” and creates a
+ copy of it, the <I>cut demo</I>. This file has the name
+ <original_demo_name><B>_autocut</B>.dem. The cut demo is
+ different to the original demo in a way that console commands have
+ been <I>injected</I>, so basically the engine thinks that you had
+ entered them yourself (e.g. cl_capturevideo 1 to start recording).</P>
+ <UL>
+ <LI><P>Since it is possible to inject
+ any possible console command, the first command that is injected is
+ to disable rendering (r_render 0), save the value of your “volume”
+ setting and then setting it to 0 (as long as you have sound and
+ rendering disabled while fast-forwarding in the preferences of the
+ Demo Recorder). Then a slowmo 100 command (first stage value from
+ the Demo Recorder preferences) is injected in order to fast-forward
+ the demo.</P>
+ <LI><P>Then, when the game time in the
+ demo is about 1 minute less than your specified start time of the
+ job, slowmo is reduced to 10 (second stage value from the
+ preferences)</P>
+ <LI><P>Then, when the game time in the
+ demo is about 5 seconds less than your specified start time, <B>slowmo</B>
+ is set to <B>1</B>, rendering and sound is enabled again, and
+ whatever your put into the <B>exec before capture</B> field is
+ being injected, too. Then, the values of cl_capturevideo_nameformat
+ and _number are being saved to a temporary variable and are
+ overwritten with defined values (<B>autorec</B> and <B>1234567</B>),
+ so that the Nexuiz Demo Recorder will know the exact name of the
+ output file (which is necessary so that it can move that file to
+ your desired video destination/location)</P>
+ <LI><P>When the the start time is
+ reached, cl_capturevideo 1 is injected, once the end time is
+ reached, cl_capturevideo 0 is injected.</P>
+ <LI><P>Shortly after, whatever you put
+ into the exec after capture field is executed, and then the
+ original values of cl_capturevideo_nameformat and _number are being
+ restored.</P>
+ <LI><P>Then a disconnect command is
+ injected</P>
+ </UL>
+ <LI><P>Next, your specified Nexuiz
+ engine binary is launched. The parameters given to the binary are:</P>
+ <UL>
+ <LI><P>The content of the <B>engine
+ parameters</B> field of the job, and</P>
+ <LI><P><B>-demo
+ <relative-demo-path>/<demo-file-name></B> (this will
+ start Nexuiz, e.g. <B>-demo demos/test.dem</B>, launch the <B>test.dem</B>
+ demo, and the engine will play the complete demo until a disconnect
+ is issued (which we have injected above), and then Nexuiz will
+ close automatically. This -demo parameter exists since Nexuiz 1.0</P>
+ </UL>
+ <LI><P>Once the Nexuiz Demo Recorder
+ notices that your Nexuiz engine binary closed, it will look for the
+ recorded video file in <DPVideo-directory>/autorec1234567.<avi/ogv>
+ and move it (rename it) to your desired <video-destination>,
+ keeping the original extension of the file. In case the preferences
+ are setup to not overwrite an existing video file, a file with
+ ending _copy1 (2, 3, …) will be created.</P>
+</UL>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-prelim-stop.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-prelim-stop.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-prelim-stop.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>Preliminary stop</H1>
+<P>If you want to preliminarily stop the
+recording process, the only thing you can do is to switch to the
+Nexuiz Demo Recorder window and click on <B>stop processing</B>.
+However, if Nexuiz is still open and processing a demo, you have to
+end it manually of wait for it to end. This means that clicking on
+<B>stop processing</B> button will simply remove all jobs that
+haven't been started yet from the internal queue of jobs that would
+normally be processed (of course they <B>won't</B> be removed from
+your jobs table).</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-table-settings.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-table-settings.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-table-settings.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>Table settings</H1>
+<P>Both the jobs and templates table can
+be customized. You can define whether to sort the jobs/templates
+according to a particular column, you can change the order of
+columns, and you can add and remove (hide) additional columns by
+clicking the small icon as shown here on this image:</P>
+<P><img src="images/customize_tables.gif" /></P>
+<P>These settings are being saved and
+automatically restored whenever you close and open the program. In
+case you messed the columns and want them to be reset to default, go
+into the settings sub-folder of the Nexuiz Demo Recorder and delete
+the jobsTable.pref or templatesTable.pref file (.pref for
+“preferences”)</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-topics.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-topics.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-topics.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,18 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>Advanced user topics</H1>
+<P>Here you find additional information
+that is not mandatory for you to know, but might help you
+understanding what and how the tool does stuff, or what you can do
+with it.</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/basic_tutorial.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/basic_tutorial.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/basic_tutorial.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,169 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>Basic tutorial</H1>
+<P>The following images shows the main window:</P>
+<img src="images/main_window.gif" />
+<P>You will notice that the main window is separated into
+4 areas:</P>
+<UL>
+ <LI><P>The menu bar (File, Help)</P>
+ <LI><P>The "Templates" area which has a table
+ and 4 buttons on the right</P>
+ <LI><P>The "Jobs" area which also has a table
+ and 4 buttons on the right</P>
+ <LI><P>The Start/stop processing buttons</P>
+</UL>
+<H2>Step 1</H2>
+<P>Minimize this demo recorder application for a moment
+and figure out which demo(s) you actually want to record. Once you
+know the names of the demo files, you have to figure out the start
+time and end time (when you want the recording process to start and
+end). You have to be aware that the Nexuiz demo recorder needs a
+value in seconds. The problem is that there are 2 different kinds of
+values.
+</P>
+<UL>
+ <LI><P>The first one is the one you see in your HUD in
+ the top right corner. This value is not useful for the Nexuiz demo
+ recorder, because the time is shown in the <minutes:seconds>
+ format. Also this value will be changed/reset to 0 in case the
+ ready-restart feature was using in the game, which resets the time.</P>
+ <LI><P>On the other hand there is the <B>absolute time
+ value</B>. It represents the amount of time that passed on the
+ server since the map was loaded. This value will always increase
+ each second, and won't be reset to 0 after a ready-restart call
+ either. This is the value you need for this tool. You can obtain it
+ the following ways</P>
+ <UL>
+ <LI><P>1: There is a sub-folder "tools" in
+ this package which contains a .patch file that allows you to patch
+ the source-code of the Nexuiz 2.5.2 engine. What this patch does is
+ to add a new variable "showgametime". Once you patched
+ your source-code and compiled the engine, you can set this variable
+ to 1, and then in the lower right corner you will see the current
+ game time. Please note that I cannot help you with patching the
+ sources and compiling the engine. For Microsoft Windows users I put
+ the Nexuiz binary (darkplaces.exe) into the package as well, so you
+ can use this one (copy it into your Nexuiz directory)</P>
+ <LI><P>2: If the demo was recorded with Nexuiz 2.5 or
+ newer you can obtain the time values by opening the console and
+ entering this: prvm_global client time</P>
+ <LI><P>3: If the demo was recorded with a Nexuiz version
+ older than 2.5, just pause the demo at the desired moment, open the
+ scoreboard (by default: hold tabulator key). The scoreboard will
+ show you the current time in <minutes>:<seconds>.
+ Calculate the time using a calculator (time = minutes * 60 +
+ seconds)</P>
+ </UL>
+</UL>
+<P>Make sure that you know the demo file name, start and
+end time, then open the Nexuiz demo recorder tool again</P>
+<H2>Step 2</H2>
+<P><a name="jobs_create">In the "Jobs" panel</a>, click on the +Create
+button. A new dialog will open which asks you for all different kinds
+of things:</P>
+<img src="images/create_job.gif" />
+<UL>
+ <LI><P><b>Engine:</b> click on the ... button and specify the
+ Nexuiz engine that should be used for recording</P>
+ <LI><P><b>Engine parameters:</b> Only fill this out if you already know what it
+ means. Examples include to execute a particular config (+exec
+ someConfig.cfg), or set a special directory (-userdir option) so
+ that your Engine will use a different set of configs</P>
+ <LI><P><b>DPVideo directory:</b> here you have to specify the
+ path where your Nexuiz usually creates new .avi or .ogv files. This
+ is one of the items where you will see that it is required that you
+ already have recorded demos previously. Usually the path will be
+ Nexuiz/data/video (on Windows) or ~/.nexuiz/data/video</P>
+ <LI><P><b>Relative demo path:</b> This is the path that is being
+ used within the virtual file-system of Nexuiz in order to find your
+ demo. Normally, when starting a demo by hand, you would enter
+ something like "playdemo demos/stormkeep_demo.dem",
+ because usually demos are stored in the directory "demos".
+ In case you changed that (e.g. If the demo is in a sub-directory of
+ the demos directory), make sure to put this into the field. But
+ usually you <B>won't</B> have to change the value (demos) to
+ anything else. Just leave it as it is.</P>
+ <LI><P><b>Job name:</b> Here you can specify a name for this job. If
+ you don't specify any name, a name will be automatically chosen, using
+ the format "Job x", where <b>x</b> is a index number.</P>
+ <LI><P><b>Demo file:</b> obviously you have to specify the demo
+ file here</P>
+ <LI><P><b>Start second</b> and <b>end second</b> are self-explanatory</P>
+ <LI><P><b>Exec before capture:</b> Here you have the chance to
+ enter console commands (that is, commands that you could otherwise
+ also enter into the console of the game). One example could be to
+ execute a config here that will make sure that the HUD is hidden. Or
+ you might want to change the FPS for the rendered video by setting
+ cl_capturevideo_fps to another value. If you want to put several
+ commands in here you can either separate them by a new-line, or by ;
+ (semi-colon)</P>
+ <LI><P><b>Exec after capture:</b> works like the "exec
+ before capture" field. Can be used for anything, e.g. Restoring
+ settings that you altered in the exec before field.</P>
+ <LI><P><b>Video destination:</b> Here you have to specify where
+ you want the recorded video file to be moved once the recording
+ process is done. Please note that you have to enter a name for the
+ file, however you should not enter the extension (.avi or .ogv). You
+ don't have to, because the Nexuiz demo recorder will automatically
+ figure out whether the Nexuiz engine generated an avi or ogv file,
+ and will keep its ending when moving the file to its destination.</P>
+</UL>
+<P>Once you filled out all fields, click on create. In
+case you did something wrong with one of the fields, you will
+hopefully get an error message immediately, which allows you to
+correct the mistake. If nothing was filled out incorrectly the dialog
+should close and you should now see a "Job 1" in the job
+queue.</P>
+<P>In order to edit a job, either double-click on it, or
+right-click it and click on Edit</P>
+<H2>Step 3</H2>
+<P>Now that you have setup your first job, click on the
+<B>Start processing</B> button. After a short moment you should see
+Nexuiz opening. Don't be surprised if the "Loading" image
+appears for a long time. What you don't see is that Nexuiz is
+fast-forwarding your demo to the start time you specified, which can
+take some time. After that, once Nexuiz started and finished
+recording the stuff you wanted it to record, it should close
+automatically. The demo recorder should show you a new message dialog
+that it finished recording all jobs. The "Status" column of
+your first job should now be showing "done".</P>
+<H2>Trouble-shooting:</H2>
+<P>In case something went wrong, the status column of your job will
+show "error" (in the jobs table). In order to find out what the problem was,
+right-click on the job and click on "Show error message".
+The message dialog that pops up will hopefully help you to figure out
+what went wrong. If it is something that you think you can fix, do so
+(by editing the job settings), then right-click the job again and click on
+"Reset job status to waiting". This will set the status of the job to
+"waiting" again.</P>
+<H2>Job queue processing</H2>
+<P>You need to be aware that once you click on the <b>Start
+processing</b> button only the jobs with status "waiting" will be
+put into a queue and will be executed one after another. All other jobs
+(with status "error" or "done" will not be started.
+If an error occurs while processing one of the jobs this job will set its state to
+"error" and the next job in queue will be executed. That is, the behavior
+of the Nexuiz demo recorder is to continue working even if one or more individual
+jobs failed.</P>
+<P>In case you already have a job list with several
+jobs but you want to just record one particular job (even though all
+other jobs also have the state "waiting"), just right-click
+on the particular job and click on "Start job". Note that this only
+works if the Nexuiz demo recorder is currently not working on any other jobs.</P>
+<H2>Further reading</H2>
+<P>Congratulations, you managed your
+first steps using this program. You should read the other help
+chapters as well in order to be able to fully utilize the
+functionality of the program.</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/changelog.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/changelog.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/changelog.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+<H1>Changelog</H1>
+<H2>v0.1 -> v0.2</H2>
+<H3>Compatibility changes</H3>
+<UL>
+ <LI><P>Table preferences, job queues and template lists are not compatible
+ between v0.1 and 0.2! Make sure you install v0.2 in a new direcory!</P>
+ <LI><P>NDR v0.2 requires Java SE 6 now (instead of Java SE 5 which was
+ required for v0.1)</P>
+</UL>
+<H3>Functionality changes and fixes</H3>
+<UL>
+ <LI><P><a href="plugin-architecture.html">Plugin architecture</a> that allows other Java developers to include
+ their own plug-ins that can encode the recorded job to
+ another format (e.g. H.264)</P>
+ <LI><P>Ships with <a href="plugin-virtualdub.html">encoder plug-in for VirtualDub</a> (MS Windows,
+ www.virtualdub.org)</P>
+ <LI><P>Duplicates can now be created with <b>more than one job</b> selected,
+ duplicating <b>all</b> selected ones at once</P>
+ <LI><P><b>Templates</b> can now be duplicated</P>
+ <LI><P>Fixed bug where deleting selected rows in the tables caused
+ a weird exception</P>
+ <LI><P>Jobs can be given individual names (don't have to be "Job 1",
+ "Job 2", ... anymore)</P>
+</UL>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/compat-limitations.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/compat-limitations.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/compat-limitations.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>Compatibility and limitations</H1>
+<P><BR><BR>
+</P>
+<H1>Compatibility</H1>
+<H2>Operating system</H2>
+<P>Since this tool is written in Java
+you can use it on Windows, Linux and Mac OS. This means that it can
+be used on all platforms supported by Nexuiz. You do need Java 6.</P>
+<H2>Nexuiz engine version</H2>
+<P>In order to use this tool you will
+need to use a Nexuiz engine of version 2.5 or newer. The reason is
+that the engine needs to have the variables
+cl_capturevideo_nameformat and cl_capturevideo_number. If you use
+older engines, such as the 2.3 engine, recording will work, but the
+tool will be unable to move the avi clip rendered by the engine to
+your desired location. However, you can manually move it and simply
+ignore the fact that the job finished with an “error”.</P>
+<H2>Demo file version</H2>
+<P>There is not really any limitation
+about the Nexuiz version that was used when recording the demo. That
+means that you should be able to use the Nexuiz demo recorder with
+demos from previous Nexuiz versions (they can be older than 2.5). You
+just have to make sure that the engine binary you use for recording
+them is v2.5 and newer. Keep in mind that using a newer-generation
+engine such as the 2.5 one for older demos should usually work
+without problems. You might have to move the dataXXXXXX.pk3 files
+from the Nexuiz version that corresponds to the demo version into the
+data directory.</P>
+<H2>Nexuiz Demo Recorder job queues</H2>
+<P>Table preferences, job queues and template lists are not compatible
+between v0.1 and newer versions! Make sure you install this in a different
+direcory than v0.1!</P>
+<H1>Limitations</H1>
+<P>This tool only works with 1-map demos
+(such as the one being automatically recorded by Nexuiz when using
+the cl_autodemo feature). If you manually recorded a demo that
+contains several maps, you can only specify a start- and endtime for
+the first map. However, you can try to use the demosplit.pl script
+from svn (trunk/misc/tools/demosplit.pl) to split demos having
+several maps into several 1-map demos. However, note that this tool
+will probably only work properly for older demos (up to Nexuiz v2.4.2
+demos).</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/credits.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/credits.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/credits.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>Credits</H1>
+<P>Credits to the <A HREF="http://www.everaldo.com/crystal/">the
+Crystal project</A> (<A HREF="http://www.everaldo.com/crystal/">http://www.everaldo.com/crystal/</A>)
+for the icons. Thanks to the development teams of
+<A HREF="https://javahelp.dev.java.net/" target="_blank">JavaHelp</A>
+(<A HREF="https://javahelp.dev.java.net/" target="_blank">https://javahelp.dev.java.net/</A>,
+<A HREF="https://swingx.dev.java.net/">SwingX</A> (<A HREF="https://swingx.dev.java.net/">https://swingx.dev.java.net/</A>)
+and <A HREF="http://www.miglayout.com/">MigLayout</A> (<A HREF="http://www.miglayout.com/">http://www.miglayout.com/</A>).
+Thanks to the <A HREF="http://jhelpdev.sourceforge.net/">JHelpDev</A> team
+(<A HREF="http://jhelpdev.sourceforge.net/">http://jhelpdev.sourceforge.net/</A>) for developing the
+tool that allowed me to create this documentation without much hassle.
+Thanks to divverent (div0) for the <b>demotc</b> perl script. Thanks to merlijn for
+some suggestions and hints. Thanks to Dave who offered some "useless" patches :P.
+Thank <B>you</B> for using the Nexuiz Demo Recorder.</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/faq.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/faq.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/faq.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,100 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>FAQ</H1>
+<P><I>Question: Is it possible to have a
+job being recorded with one of the Nexuiz presets (Low, medium,
+normal, high, ultra), without affecting my Nexuiz installation in
+general (so that this configuration is just active during capturing
+the current demo?</I></P>
+<P>Yes it is. In Nexuiz there are config
+files for this, which are called:</P>
+<UL>
+ <LI><P>effects-low.cfg</P>
+ <LI><P>effects-med.cfg</P>
+ <LI><P>effects-normal.cfg</P>
+ <LI><P>effects-high.cfg</P>
+ <LI><P>effects-ultra.cfg</P>
+ <LI><P>effects-ultimate.cfg</P>
+ <LI><P>effects-omg.cfg</P>
+</UL>
+<P>In order to use them, there are 2
+approaches</P>
+<P><B>Approach 1:</B> You should
+consider to use a new directory for each new set of effect configs
+you want to try out. This will simply help you to avoid loss of your
+original config, or any unwanted modification to it. For example,
+setup Nexuiz manually by starting it with some userdir, e.g. <B>-userdir
+effectTestUltimate</B>, start Nexuiz, go into the menu and set the
+effects to ultimate (e<I>xec effects-ultimate.cfg</I> in the
+console). Maybe fine-tune settings as you like. Then put the <B>-userdir
+effectTestUltimate</B> directive into the <B>engine parameters</B>
+field of the job.</P>
+<P><b>Approach 2:</b> For this approach it is
+recommended that you do a backup of your config somewhere else, even
+though everything should work fine and your original config should
+not be modified at all after the demo was recorded.</P>
+<P>Add these 4 lines to the <b>exec before
+capture</b> field:</P>
+<P><B>saveconfig backup.cfg</B></P>
+<P><B>cvar_resettodefaults_all</B></P>
+<P><B>exec effects-ultimate.cfg</B></P>
+<P><B>r_restart</B></P>
+<P>The first line will backup your
+current config to “backup.cfg”. The second line resets
+all variables (you can remove this if you want, but sometimes these
+effect-xyz.cfg config files will not set every single variable to the
+new value, but assume that some values have their default value,
+which might not be the case in your config. So resetting everything
+to defaults is recommended. Of course you can choose to use another
+effects config than <B>effects-ultimate.cfg</B>. After executing that
+effects config a restart of the renderer (<B>r_restart</B>) is
+mandatory, because of some of the changes of the effects config not
+being applied without a renderer-restart. Also, keep in mind that the
+cvar_resettodefaults_all will have changed your Nexuiz screen
+resolution (vid_height, vid_width). The r_restart command however
+won't take these changes into account (it will still use your old resolution),
+only <B>vid_restart</B> would.
+This just means that you do not have to change these 2 variables
+manually to “keep” your old screen resolution, as long as
+you don't use vid_restart.</P>
+<P>Also add this to your <b>exec after
+capture</b> field:</P>
+<P><B>cvar_resettodefaults_all</B></P>
+<P><B>exec backup.cfg</B></P>
+<P>Please note: in case you want to
+setup other special things, e.g. whether to record in the AVI instead
+of OGV format, the video FPS, etc, you'd have to put these settings
+some place <B>after</B> the <B>cvar_resettodefaults_all</B> command
+in the <B>exec before capture</B> field.</P>
+<P><BR><BR>
+</P>
+<P><I>Question: Is it possible to record
+a job in a particular screen resolution?</I></P>
+<P>Yes it is. The height and width are
+configured by setting the value vid_height and vid_width to your
+desired value. For example to record a demo in the HD resolution,
+which is 1280x720, put this into the <B>exec before capture</B>
+field:</P>
+<P ALIGN=LEFT><B>vid_width 1280</B></P>
+<P ALIGN=LEFT><B>vid_height 720</B></P>
+<P ALIGN=LEFT><B>vid_restart</B></P>
+<P><BR><BR>
+</P>
+<P><I>Question: I have found a bug, or I
+encountered some other problem, what do I do? </I>
+</P>
+<P>Well, truth to be told, I cannot
+guarantee for any support of this tool. You can put questions or
+comments into the forum thread of the AlienTrap forums where you
+downloaded the tool from.</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_job.gif
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_job.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_template.gif
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_template.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/customize_tables.gif
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/customize_tables.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/edit_job_vdub.gif
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/edit_job_vdub.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/main_window.gif
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/main_window.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_dialog.gif
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_dialog.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_global_vdub.gif
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_global_vdub.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/introduction.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/introduction.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/introduction.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>Introduction</H1>
+<P>Welcome to the Nexuiz demo recorder. This tool is made
+for people <B>who are already familiar</B> with the manual demo
+recording process and are looking for a way to automate this process.
+In simple words, this tool allows you to create jobs that are being
+put into a job queue, so that they can be executed one after another,
+at a point in time when you don't need to access your computer
+anymore. For each job you have to specify the demo you want to
+record, the second when the record is supposed to start and end, as
+well as the location where to save the final video file to. You can
+also use one or more of the available plug-ins to encode the resulting
+video file to another format.</P>
+<P>To get started, take a look at the <a href="basic_tutorial.html">basic tutorial</a></P>
+<P>Click <a href="changelog.html">here</a> to learn about the new features of
+this release.</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/license.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/license.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/license.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,15 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>License</H1>
+<P>This tool is released under the <b>GPL v2</b> (same license as Nexuiz).</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/open-save.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/open-save.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/open-save.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,29 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>Open/Save Jobs and Templates</H1>
+<P>First of all, all your templates and
+jobs will automatically be saved when closing the program (and
+automatically loaded when starting it again). You can find these
+files in the sub-directory “settings”of the program, the
+files are called <B>templates.dat</B> and <B>jobs.dat</B>. Please
+note that the job queue will also be saved each time a job finished
+its execution (so that you don't lose the progress of your jobs in
+case your computer crashes while processing).</P>
+<P>You have the possibility to manually
+save and load other job queues (this might be handy if you are
+working on different projects). For this simply use the <B>File menu</B>
+and click on <B>Load job queue</B> or <B>Save job queue</B>
+respectively. Please be aware that <B>Load job queue</B> will clear
+your current job queue and it cannot be recovered, so you better save
+your original job queue before loading a new one.</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-architecture.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-architecture.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-architecture.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,81 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+<H1>Plug-in architecture</H1>
+<H2>Introduction</H2>
+<P>While the Nexuiz Demo Recorder (NDR)
+saves you a lot of time, what many video editors still need to do is
+to transcode/convert/encode/you-name-it the resulting huge avi clip
+to another (lossless) format. Or, if you are not an editor but just a
+player who wants to distribute the recorded clip, unless you had
+Nexuiz encode the clip in the Ogg Theora codec (cl_capturevideo_ogg
+1), you might want to encode it in XviD, H.264 or some other lossy
+codec. For encoding people use all different kinds of tools. Some
+have a GUI, some don't. Some are available on just one platform, some
+are cross-platform to some extent. However, since this release the
+NDR is now able to also encode clips to whatever format you like by
+the use of <B>encoder plug-ins</B>. For this the NDR has been
+extended with a plug-in architecture that will look for plug-ins in
+the “plugins” folder. NDR plug-ins are basically just a
+bridge between the NDR application and an actual encoder. Examples
+for actual encoders are <B>VirtualDub</B> for MS Windows, or <B>mencoder</B>
+for Linux. What a plug-in will do is to assemble a command line that
+will execute this encoder and hand it the required parameters.</P>
+<H2>Extension is easy</H2>
+<P>In this release the NDR ships with
+just one plug-in: the VirtualDub plug-in. However, if you are a Java
+developer it is really easy for you to write your own plug-ins that
+will be able to handle whatever encoder you want to use. If you are
+interested, have a look into the <B>src</B> folder at the <B>Sample
+Plugin</B>. All you need to do is to implement an interface, edit
+some meta-information files and build a jar file that is being put
+into the <B>plugins</B> directory of the NDR.</P>
+<H2>Plug-in settings</H2>
+<P>Each plug-in allows the user to
+interact with it, by offering settings (or “preferences”).
+There are 2 kinds of settings:</P>
+<UL>
+ <LI><P>Global settings, which will be
+ shown in the File → Preferences dialog</P>
+ <LI><P>Job/template-specific settings,
+ which will be shown in the dialog you use when creating or editing
+ jobs or templates</P>
+</UL>
+<H2>Use plug-ins later on</H2>
+<P>In order to invoke the functionality
+of the plug-in you are not required to set up everything at the very
+beginning (before the clip has actually been recorded). Of course,
+you can do so, and in this case the job would be recorded and, right
+after that, be encoded as well. But you can also choose to have jobs
+recorded first, and then later when you see the need to encode them,
+edit the job settings (add your plug-in settings there), and then
+right-click the job and select “just run the xyz plug-in”.
+Basically this menu item will work on any job with status = <B>done</B>.</P>
+<P>Of course you may not move the
+recorded clip from the destination that you set up, because otherwise
+the plug-in would not find the file to encode it.</P>
+<H2>Error handling</H2>
+<P>In case something went wrong during
+the execution of the plug-in itself, you will see a “plug-in
+error” in the <B>status</B> column of the jobs table. Like for
+normal “errors”, you can right-click the job and click on
+“show error message” in order to see the error message of
+the plug-in. In case you can repair whatever went wrong, do so, then
+right-click the job and click on “reset job status to <B>done</B>”.
+Then you can run the plug-in again.</P>
+<P>If a plug-in appeared to do its job
+just fine but you suspect that something went wrong, you might want
+to have a look at the <B>logs</B> directory in the NDR folder.
+Depending on whether the one who created the plug-in implemented it,
+you might see log files which are basically text files containing the
+output of the encoder itself.</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-virtualdub.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-virtualdub.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-virtualdub.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,163 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+<H1>VirtualDub plug-in</H1>
+<H2>Introduction</H2>
+<P><B><U>Important:</U> Make sure that
+you read this documentation carefully in order to avoid mistakes!</B></P>
+<P>The prerequisite for using this
+plug-in is that you already familiar with the “manual”
+process of encoding clips with VirtualDub (<A HREF="http://www.virtualdub.org/">www.virtualdub.org</A>).
+The great thing about VirtualDub is that it offers both a graphical
+user interface (GUI), accessible via <B>virtualdub.exe</B>, and a
+command line interface (CLI), accessible via <B>vdub.exe</B>.</P>
+<H2>Step 1: create VDub configuration
+files (VCF)</H2>
+<P>Let's say we had a clip that we
+wanted to encode not only once, but twice – into different
+formats. Let's say that the first goal is to encode it to a loss-less
+Huffyuv clip, and the second goal is to encode it to a quality-based
+1-pass XviD clip using MP3 sound.</P>
+<P>What you need to do first is to open
+VirtualDub, set up the settings for one of the goals (by going into
+the video compression and audio compression dialogs) and then click
+on File → Save processing settings (CTRL+S). You will end up
+with saving a .vcf file somewhere on your hard drive …
+remember where you put it! After that, you'd set up the settings for
+your second goal and save these processing settings as well, as a new
+.vcf file.</P>
+<H2>Step 2: Enable and set up the VDub
+plug-in</H2>
+<P>First we need to set up the global
+settings of the plug-in. Go into the preferences dialog and enable
+the plug-in.</P>
+<P><img src="images/preferences_global_vdub.gif"/></P>
+<P>Use the “...” button to
+specify the path to the vdub.exe file. The setting <B>max number of
+VCFs per job</B> is important, because the number you put in there
+will influence how many VCF files you will be able to select in the
+<B>job dialog</B> (the dialog where you create or edit a
+job/template). For our example we need to set it to 2, but it doesn't
+matter if you choose a higher value. Since VDub will encode <B>two</B>
+files for us, we will have to tell VDub under which filenames to save
+them. You can select which approach you want to use by configuring
+the setting <B>output as suffix(0) or file(1)</B>. If you <B>don't</B>
+check the checkbox this means that you will enter a small part of the
+file name, the <B>suffix</B>, that will be put at the end of the
+original file name. E.g. if your original filename was
+“C:\render\myVideoFile.avi”, and the suffix is
+“_huffyuv”, the resulting file would be
+“C:\render\myVideoFile_huffyuv.avi”. However, if you
+checked that checkbox, you would instead get a “...”
+button that allows you to specify the filename for the resulting file
+manually (this is handy if you wanted to save the file to another
+folder for example).</P>
+<P>The <B>show extra options</B> setting
+will be explained at the end of this page.</P>
+<P>Let's now have a look at the dialog
+when editing a job</P>
+<H2>Step 3: Set up the job-specific
+settings</H2>
+<P>Open the job dialog.</P>
+<P><img src="images/edit_job_vdub.gif" />/P>
+<P>Let's ignore the 2 checkboxes at the
+top for a moment. What you will need to do is to specify the path to
+the 2 .vcf files you created previously in VirtualDub in step 1. Then
+you need to specify the suffix (or the file using the file-chooser)
+so that VDub knows where to save the encoded videos.</P>
+<H2>Step 4: Run the plug-in</H2>
+<UL>
+ <LI><P>If you had your job recorded
+ (without using the plug-in) then the status of the job will already
+ be “<B>done”</B> and you can right-click the job and run
+ the VirtualDub plug-in</P>
+ <LI><P>If the job has <B>not</B> been
+ recorded yet, just click on Start processing and wait for the result</P>
+</UL>
+<H2>Additional information</H2>
+<H3>Empty VCF paths</H3>
+<P>As you know you can specify any
+number in the <B>preferences dialog</B> for the setting <B>max number
+of VCFs per job</B>. In case of our example that consists of 2 jobs,
+if you selected 3 (or more) as max. number, and then opened the <B>job
+dialog</B>, you would see 3 fields each for the <B>path to VCF file</B>
+and <B>output file</B> setting. This, however, is not a problem. The
+plug-in will only execute those VCF jobs for which both the path to
+the VCF and the output file fields are filled with proper
+information.</P>
+<H3>VDub job control</H3>
+<P>You still don't know what the 2
+checkboxes in the <B>job dialog</B> at the top of the VDub settings
+mean. What you need to know is that VirtualDub is having its own “job
+queue”, which, however, is called <B>“job control”</B>.</P>
+<P>The following scenarios can make
+sense:</P>
+<UL>
+ <LI><P>Having <B>both</B> checkboxes
+ <B>enabled</B>: Each time a new NDR job is executed which has
+ VirtualDub jobs, the <B>job control</B> of VirtualDub will be
+ cleared. This means that if you had other, old things in there
+ (possibly when working with VirtualDub outside of the NDR) that
+ these will be deleted from VirtualDub's job control, before the new
+ jobs are added there. Checking the second checkbox means that after
+ adding one of the VirtualDub jobs from within the the NDR,
+ VirtualDub will actually encode them right away (which you will
+ want, at least under normal circumstances).</P>
+ <LI><P>Having <B>both</B> checkboxes
+ <B>disabled</B>: In this case the only thing the NDR would do would
+ be to add the two jobs to the job control of VirtualDub, but
+ VirtualDub won't be encoding them. They are just put into the queue.
+ This means that if you were to start the the graphical user
+ interface of VirtualDub after the NDR completed processing, and open
+ the Job Control (F4), you'd see these jobs “waiting”,
+ and you could render them by pressing the Start button in Virtual
+ Dub.</P>
+</UL>
+<P>The reason why these checkboxes exist
+is flexibility. I often want the NDR to record stuff over night,
+because during the recording process my PC is virtually unusable.
+However, encoding clips in VirtualDub is a task that, depending on
+the codec, can be done in the background and allows me to use the PC
+for other things. In this case I'd disable both checkboxes, have the
+NDR record all clips and add them to VirtualDub's job control, and
+then on the next day, I decide when and which jobs to encode into
+different formats from within the VirtualDub GUI.</P>
+<P>And btw, just to be clear, the <B>second</B>
+checkbox means that, <B>when enabled</B>, VirtualDub would actually
+start encoding <U><B>all</B></U> clips that have already been in
+VirtualDub's job control and that have not been encoded yet.</P>
+<H3>Extra options</H3>
+<P>If you selected the <B>show extra
+options</B> checkbox in the <B>preferences dialog</B>, you will
+notice that, in the <B>job dialog</B>, you got 2 additional
+settings/checkboxes for each VCF file. Their names are pretty much
+self-explanatory. I added these checkboxes because, after a scene was
+rendered, I usually have the clip encoded into a loss-less format
+(e.g. Huffyuv) right away. Since it is no problem to encode further
+things (e.g. compressed XviD movies) based on the Huffyuv clip
+(instead of always using the original avi file), enabling both these
+checkboxes for VCF 1 would mean that the 2<SUP>nd</SUP> (3<SUP>rd</SUP>,
+4<SUP>th</SUP> …) VCF jobs will be based on the
+huffyuv-encoded file, and I got rid of original, big video as well,
+saving hard disk space.</P>
+<H3>Trouble shooting</H3>
+<P>In case you suspect that the
+expected, encoded file(s) is somehow incorrect (or even missing),
+have a look at the <B>logs</B> directory in the NDR folder. You will
+see log-files with the following format:</P>
+<P>VirtualDub_NameOfTheJob_vcf1.log</P>
+<P>Instead of “vcf1” you
+might also find “vcf2”, “vcf3”... it
+specifies for which VCF file of that job the log file stands for.</P>
+<P><BR><BR>
+</P>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/preferences.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/preferences.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/preferences.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,64 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+ <H1>Preferences dialog</H1>
+<P>The preferences dialog allows you to
+change the behavior of the program in certain situaitons.</P>
+<P><img src="images/preferences_dialog.gif" /></P>
+<UL>
+ <LI><P><b>Overwrite final video
+ destination file if it exists:</b> This checkbox allows you to determine
+ whether the final destination video file (e.g.
+ <B>C:\render\fragOfTheMonth.avi</B>) should be overwritten by a job
+ if that file already existed from your harddrive. By default this
+ option is disabled. The program will, in this case, not overwrite
+ any existing files, but save the video file with a “copyX”
+ suffix, for example <B>C:\render\fragOfTheMonth_copy1.avi</B></P>
+ <LI><P><b>Disable rendering while
+ fast-forwarding:</b> As the Nexuiz demo recorder creates a cutted demo
+ that has slowmo-commands in it to fast-forward it to the desired
+ start time, the fast-forwarding process is often slowed down due to
+ the action taking place on your screen (and your graphic card
+ choking with the mere speed of the demo, e.g. during a “slowmo
+ 100” period). However, there is a variable called r_render in
+ the engine which can be set to 0, which will disable any render
+ updates. This will speed up the fast-forwarding process massively.
+ Of course, r_render is being set to 1 a few seconds before start
+ time is reached.</P>
+ <LI><P><b>Disable sound while
+ fast-forwarding:</b> The value of the variable volume is saved and the
+ set to 0 right after the demo was loaded. The reason is that it can
+ be annoying to hear the in-game sounds while fast-forwarding.</P>
+ <LI><P><b>Fast-forward speed (first stage
+ or second stage):</b> The demo is fast-forwarded using two different
+ fast-forward speeds. In the first stage, when the start time is
+ still about a minute away, the “first stage” value is
+ used. Then, until 5 seconds before the start time, the “second
+ stage” value is used. You will only need to manipulate these
+ values if you discover that the final recorded video is inaccurate
+ when it comes to the time when the recording starts (e.g. when then
+ video recording starts too late, reduce these 2 values for first and
+ second stage a bit – you will have to experiment)</P>
+ <LI><P><b>Do not delete cut demos:</b> The
+ file name of the automatically created cut demo is like the original
+ name of your demo, but ends with “_autocut.dem”.
+ Normally this demo is created, being recorded from, and then deleted
+ again. If you enable this option the demo file is not deleted, and
+ you can inspect it with a Hex editor or whatever else you want to do
+ with it (maybe send it to a friend to have him record it?)</P>
+ <LI><P><b>Append this suffix to job-name when duplicating jobs:</b>
+ As you now have customizable job names it would probably be a bad idea to
+ give duplicated jobs the same name as the original jobs. This is why you can
+ set up a suffix that will be appended to the original job name.</P>
+
+</UL>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/templates.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/templates.html (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/templates.html 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,69 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+ <title></title>
+ </head>
+
+ <body>
+<H1>Templates</H1>
+<P>The Nexuiz demo recorder offers you
+templates that you can use to create new jobs (which are based on a
+template) more quickly and efficiently. Imagine these "templates"
+didn't exist ... you would always have to specify the complete path
+to the engine, the video file, the demo file, and the final video
+file – each single time when creating a job, from scratch. This
+would take a long time and would be inefficient.</P>
+<P>Instead you are encouraged to create
+templates. Templates can be created either from scratch (click on the
+<B>+ Create</B> button next to the template table) or from an
+existing job (select the job, then click on the button <B>Create from
+job</B>).</P>
+<P>The latter will just add a new entry
+in the templates table, without prompting you for any further
+information. The template's name and summary will be "Generated
+from job", and you can change this by double-clicking this
+generated template, renaming it and giving it a meaningful summary,
+then click save. You will notice that all other values have been
+taken from the job and don't need to be filled out by you anymore.</P>
+<P>Here you can see the template dialog
+when creating a new template:</P>
+<P><img src="images/create_template.gif" /></P>
+<P>The dialog looks very much alike the
+"create job" dialog presented to you in the <a href="basic_tutorial.html#jobs_create">Basic
+tutorial</a>. There are a few differences, however:</P>
+<UL>
+ <LI><P>The dialog shows you a <B>template
+ name</B> and <B>description</B></P>
+ <LI><P>Instead of specifying a <B>demo
+ file</B> you are now specifying a <B>demo directory</B></P>
+ <LI><P>Instead of specifying a <B>video
+ destination file</B>, you now just specify the <B>directory</B></P>
+ <LI><P>You
+ don't specify a <B>start time</B> or <B>end time</B> for templates,
+ because these 2 input parameters are specific for each job and don't
+ make sense to be saved for a template</P>
+</UL>
+<P>Once you have a template you can
+create new jobs easily by selecting a template in the templates table
+and then click on the <B>Create from template</B> button in the jobs
+panel.
+</P>
+<H2>What could templates also be good
+for?</H2>
+<UL>
+ <LI><P>Using different configs for
+ recording (you can set a config to be used either in the “engine
+ parameters” field, e.g. <B>+exec myconfig.cfg</B>, or you
+ could put the <B>exec myconfig.cfg</B> into the “exec before”
+ field.</P>
+ <LI><P>Recording with different Nexuiz
+ engines to compare the visual quality</P>
+ <LI><P>Inspire me … what do you
+ use templates for? Post it to the forum!</P>
+</UL>
+ </body>
+</html>
+
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/advanced.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/advanced.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit_add.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit_add.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editclear.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editclear.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editcopy.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editcopy.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editdelete.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editdelete.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/exit.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/exit.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/fileopen.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/fileopen.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/filesave.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/filesave.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/help.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/help.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/info.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/info.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/package.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/package.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_pause.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_pause.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_play.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_play.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart_blue.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart_blue.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/status_unknown.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/status_unknown.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/view_right_p.png
===================================================================
(Binary files differ)
Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/view_right_p.png
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/jsmooth exe project.jsmooth
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/jsmooth exe project.jsmooth (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/jsmooth exe project.jsmooth 2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<arguments></arguments>
+<classPath>..\..\..\..\..\Nexuiz Demo Recorder\Build v0.2\NexuizDemoRecorder-0.2.jar</classPath>
+<currentDirectory>${EXECUTABLEPATH}</currentDirectory>
+<embeddedJar>false</embeddedJar>
+<executableName>..\..\..\..\..\Nexuiz Demo Recorder\Build v0.2\NexuizDemoRecorder.exe</executableName>
+<iconLocation>..\..\..\..\..\..\temp\128x128\filesystems\folder_video.png</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<mainClassName>com.nexuiz.demorecorder.main.Driver</mainClassName>
+<maximumMemoryHeap>-1</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.6</minimumVersion>
+<skeletonName>Windowed Wrapper</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>URL</key>
+<value>http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleProcess</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleInstance</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>JniSmooth</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>0</value>
+</skeletonProperties>
+</jsmoothproject>
More information about the nexuiz-commits
mailing list