commit v0.2 of Nexuiz Demo Recorder
authorgreenmarine <greenmarine@f962a42d-fe04-0410-a3ab-8c8b0445ebaa>
Thu, 11 Feb 2010 15:54:58 +0000 (15:54 +0000)
committergreenmarine <greenmarine@f962a42d-fe04-0410-a3ab-8c8b0445ebaa>
Thu, 11 Feb 2010 15:54:58 +0000 (15:54 +0000)
git-svn-id: svn://svn.icculus.org/nexuiz/trunk@8633 f962a42d-fe04-0410-a3ab-8c8b0445ebaa

82 files changed:
misc/tools/NexuizDemoRecorder/pom.xml [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/about.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelp.hs [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpIndex.xml [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpTOC.xml [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/JHelpDev Project.xml [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS.TAB [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/OFFSETS [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/POSITIONS [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/SCHEMA [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/TMAP [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/Map.jhm [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-how-it-works.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-prelim-stop.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-table-settings.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-topics.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/basic_tutorial.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/changelog.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/compat-limitations.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/credits.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/faq.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_job.gif [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_template.gif [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/customize_tables.gif [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/edit_job_vdub.gif [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/main_window.gif [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_dialog.gif [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_global_vdub.gif [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/introduction.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/license.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/open-save.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-architecture.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-virtualdub.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/preferences.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/help/html/templates.html [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/advanced.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit_add.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/editclear.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/editcopy.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/editdelete.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/exit.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/fileopen.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/filesave.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/help.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/info.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/package.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_pause.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_play.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart_blue.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/status_unknown.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/icons/view_right_p.png [new file with mode: 0644]
misc/tools/NexuizDemoRecorder/src/main/resources/jsmooth exe project.jsmooth [new file with mode: 0644]

diff --git a/misc/tools/NexuizDemoRecorder/pom.xml b/misc/tools/NexuizDemoRecorder/pom.xml
new file mode 100644 (file)
index 0000000..712d291
--- /dev/null
@@ -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>
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java
new file mode 100644 (file)
index 0000000..2ca7b3d
--- /dev/null
@@ -0,0 +1,444 @@
+package com.nexuiz.demorecorder.application;\r
+\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileNotFoundException;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.ObjectInputStream;\r
+import java.io.ObjectOutputStream;\r
+import java.net.MalformedURLException;\r
+import java.net.URL;\r
+import java.net.URLClassLoader;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.Properties;\r
+import java.util.ServiceLoader;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+\r
+import com.nexuiz.demorecorder.application.jobs.EncoderJob;\r
+import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
+import com.nexuiz.demorecorder.application.jobs.RecordsDoneJob;\r
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
+import com.nexuiz.demorecorder.ui.DemoRecorderUI;\r
+\r
+public class DemoRecorderApplication {\r
+       \r
+       public static class Preferences {\r
+               public static final String OVERWRITE_VIDEO_FILE = "Overwrite final video destination file if it exists";\r
+               public static final String DISABLE_RENDERING = "Disable rendering while fast-forwarding";\r
+               public static final String DISABLE_SOUND = "Disable sound while fast-forwarding";\r
+               public static final String FFW_SPEED_FIRST_STAGE = "Fast-forward speed (first stage)";\r
+               public static final String FFW_SPEED_SECOND_STAGE = "Fast-forward speed (second stage)";\r
+               public static final String DO_NOT_DELETE_CUT_DEMOS = "Do not delete cut demos";\r
+               public static final String JOB_NAME_APPEND_DUPLICATE = "Append this suffix to job-name when duplicating jobs";\r
+               \r
+               public static final String[] PREFERENCES_ORDER = {\r
+                       OVERWRITE_VIDEO_FILE,\r
+                       DISABLE_RENDERING,\r
+                       DISABLE_SOUND,\r
+                       FFW_SPEED_FIRST_STAGE,\r
+                       FFW_SPEED_SECOND_STAGE,\r
+                       DO_NOT_DELETE_CUT_DEMOS,\r
+                       JOB_NAME_APPEND_DUPLICATE\r
+               };\r
+       }\r
+       \r
+       public static final String PREFERENCES_DIRNAME = "settings";\r
+       public static final String LOGS_DIRNAME = "logs";\r
+       public static final String PLUGINS_DIRNAME = "plugins";\r
+       public static final String APP_PREFERENCES_FILENAME = "app_preferences.xml";\r
+       public static final String JOBQUEUE_FILENAME = "jobs.dat";\r
+       \r
+       public static final int STATE_WORKING = 0;\r
+       public static final int STATE_IDLE = 1;\r
+       \r
+       private RecorderJobPoolExecutor poolExecutor;\r
+       private List<RecordJob> jobs;\r
+       private NDRPreferences preferences = null;\r
+       private List<DemoRecorderUI> registeredUserInterfaces;\r
+       private List<EncoderPlugin> encoderPlugins;\r
+       private int state = STATE_IDLE;\r
+       \r
+       public DemoRecorderApplication() {\r
+               poolExecutor = new RecorderJobPoolExecutor();\r
+               jobs = new CopyOnWriteArrayList<RecordJob>();\r
+               this.registeredUserInterfaces = new ArrayList<DemoRecorderUI>();\r
+               this.encoderPlugins = new ArrayList<EncoderPlugin>();\r
+               this.getPreferences();\r
+               this.loadPlugins();\r
+               this.configurePlugins();\r
+               this.loadJobQueue();\r
+       }\r
+       \r
+       public void setPreference(String category, String preference, boolean value) {\r
+               this.preferences.setProperty(category, preference, String.valueOf(value));\r
+       }\r
+       \r
+       public void setPreference(String category, String preference, int value) {\r
+               this.preferences.setProperty(category, preference, String.valueOf(value));\r
+       }\r
+       \r
+       public void setPreference(String category, String preference, String value) {\r
+               this.preferences.setProperty(category, preference, value);\r
+       }\r
+       \r
+       public NDRPreferences getPreferences() {\r
+               if (this.preferences == null) {\r
+                       this.preferences = new NDRPreferences();\r
+                       this.createPreferenceDefaultValues();\r
+                       File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);\r
+                       if (preferencesFile.exists()) {\r
+                               FileInputStream fis = null;\r
+                               try {\r
+                                       fis = new FileInputStream(preferencesFile);\r
+                                       this.preferences.loadFromXML(fis);\r
+                               } catch (Exception e) {\r
+                                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the application preferences file!", e, true);\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               return this.preferences;\r
+       }\r
+       \r
+       private void createPreferenceDefaultValues() {\r
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE, "false");\r
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING, "true");\r
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND, "true");\r
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE, "100");\r
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE, "10");\r
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS, "false");\r
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE, " duplicate");\r
+       }\r
+       \r
+       public void savePreferences() {\r
+               File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);\r
+               if (!preferencesFile.exists()) {\r
+                       try {\r
+                               preferencesFile.createNewFile();\r
+                       } catch (IOException e) {\r
+                               File parentDir = preferencesFile.getParentFile();\r
+                               if (!parentDir.exists()) {\r
+                                       try {\r
+                                               if (parentDir.mkdirs() == true) {\r
+                                                       try {\r
+                                                               preferencesFile.createNewFile();\r
+                                                       } catch (Exception ex) {}\r
+                                               }\r
+                                       } catch (Exception ex) {}\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               if (!preferencesFile.exists()) {\r
+                       DemoRecorderException ex = new DemoRecorderException("Could not create the preferences file " + preferencesFile.getAbsolutePath());\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(ex);\r
+                       return;\r
+               }\r
+               \r
+               FileOutputStream fos;\r
+               try {\r
+                       fos = new FileOutputStream(preferencesFile);\r
+               } catch (FileNotFoundException e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath() + ". Unsufficient rights?", e, true);\r
+                       return;\r
+               }\r
+               try {\r
+                       this.preferences.storeToXML(fos, null);\r
+               } catch (IOException e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath(), e, true);\r
+               }\r
+       }\r
+       \r
+       public List<RecordJob> getRecordJobs() {\r
+               return new ArrayList<RecordJob>(this.jobs);\r
+       }\r
+       \r
+       public void startRecording() {\r
+               if (this.state != STATE_WORKING) {\r
+                       this.state = STATE_WORKING;\r
+                       \r
+                       for (RecordJob currentJob : this.jobs) {\r
+                               if (currentJob.getState() == RecordJob.State.WAITING) {\r
+                                       this.poolExecutor.runJob(currentJob);\r
+                               }\r
+                       }\r
+                       \r
+                       //notify ourself when job is done\r
+                       this.poolExecutor.runJob(new RecordsDoneJob(this));\r
+               }\r
+       }\r
+       \r
+       public void recordSelectedJobs(List<RecordJob> jobList) {\r
+               if (this.state == STATE_IDLE) {\r
+                       this.state = STATE_WORKING;\r
+                       for (RecordJob currentJob : jobList) {\r
+                               if (currentJob.getState() == RecordJob.State.WAITING) {\r
+                                       this.poolExecutor.runJob(currentJob);\r
+                               }\r
+                       }\r
+                       \r
+                       //notify ourself when job is done\r
+                       this.poolExecutor.runJob(new RecordsDoneJob(this));\r
+               }\r
+       }\r
+       \r
+       public void executePluginForSelectedJobs(EncoderPlugin plugin, List<RecordJob> jobList) {\r
+               if (this.state == STATE_IDLE) {\r
+                       this.state = STATE_WORKING;\r
+                       for (RecordJob currentJob : jobList) {\r
+                               if (currentJob.getState() == RecordJob.State.DONE) {\r
+                                       this.poolExecutor.runJob(new EncoderJob(currentJob, plugin));\r
+                               }\r
+                       }\r
+                       \r
+                       //notify ourself when job is done\r
+                       this.poolExecutor.runJob(new RecordsDoneJob(this));\r
+               }\r
+       }\r
+       \r
+       public void notifyAllJobsDone() {\r
+               this.state = STATE_IDLE;\r
+               \r
+               //notify all UIs\r
+               for (DemoRecorderUI currentUI : this.registeredUserInterfaces) {\r
+                       currentUI.recordingFinished();\r
+               }\r
+       }\r
+       \r
+       public synchronized void stopRecording() {\r
+               if (this.state == STATE_WORKING) {\r
+                       //clear the queue of the threadpoolexecutor and add the GUI/applayer notify job again\r
+                       this.poolExecutor.clearUnfinishedJobs();\r
+                       this.poolExecutor.runJob(new RecordsDoneJob(this));\r
+               }\r
+       }\r
+       \r
+       public RecordJob createRecordJob(\r
+               String name,\r
+               File enginePath,\r
+               String engineParameters,\r
+               File demoFile,\r
+               String relativeDemoPath,\r
+               File dpVideoPath,\r
+               File videoDestination,\r
+               String executeBeforeCap,\r
+               String executeAfterCap,\r
+               float startSecond,\r
+               float endSecond\r
+       ) {\r
+               int jobIndex = -1;\r
+               if (name == null || name.equals("")) {\r
+                       //we don't have a name, so use a generic one \r
+                       jobIndex = this.getNewJobIndex();\r
+                       name = "Job " + jobIndex;\r
+               } else {\r
+                       //just use the name and keep jobIndex at -1. Jobs with real names don't need an index\r
+               }\r
+               \r
+               \r
+               \r
+               RecordJob newJob = new RecordJob(\r
+                       this,\r
+                       name,\r
+                       jobIndex,\r
+                       enginePath,\r
+                       engineParameters,\r
+                       demoFile,\r
+                       relativeDemoPath,\r
+                       dpVideoPath,\r
+                       videoDestination,\r
+                       executeBeforeCap,\r
+                       executeAfterCap,\r
+                       startSecond,\r
+                       endSecond\r
+               );\r
+               this.jobs.add(newJob);\r
+               this.fireUserInterfaceUpdate(newJob);\r
+               \r
+               return newJob;\r
+       }\r
+       \r
+       public synchronized boolean deleteRecordJob(RecordJob job) {\r
+               if (!this.jobs.contains(job)) {\r
+                       return false;\r
+               }\r
+               \r
+               //don't delete jobs that are scheduled for execution\r
+               if (this.poolExecutor.getJobList().contains(job)) {\r
+                       return false;\r
+               }\r
+               \r
+               this.jobs.remove(job);\r
+               return true;\r
+       }\r
+       \r
+       public void addUserInterfaceListener(DemoRecorderUI ui) {\r
+               this.registeredUserInterfaces.add(ui);\r
+       }\r
+       \r
+       /**\r
+        * Makes sure that all registered user interfaces can update their view/display.\r
+        * @param job either a job that's new to the UI, or one the UI already knows but of which details changed\r
+        */\r
+       public void fireUserInterfaceUpdate(RecordJob job) {\r
+               for (DemoRecorderUI ui : this.registeredUserInterfaces) {\r
+                       ui.RecordJobPropertiesChange(job);\r
+               }\r
+       }\r
+       \r
+       public int getNewJobIndex() {\r
+               int jobIndex;\r
+               if (this.jobs.size() == 0) {\r
+                       jobIndex = 1;\r
+               } else {\r
+                       int greatestIndex = -1;\r
+                       for (RecordJob j : this.jobs) {\r
+                               if (j.getJobIndex() > greatestIndex) {\r
+                                       greatestIndex = j.getJobIndex();\r
+                               }\r
+                       }\r
+                       if (greatestIndex == -1) {\r
+                               jobIndex = 1;\r
+                       } else {\r
+                               jobIndex = greatestIndex + 1;\r
+                       }\r
+               }\r
+               \r
+               return jobIndex;\r
+       }\r
+       \r
+       private void loadJobQueue() {\r
+               File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);\r
+               this.loadJobQueue(defaultFile);\r
+       }\r
+       \r
+       @SuppressWarnings("unchecked")\r
+       public void loadJobQueue(File path) {\r
+               if (!path.exists()) {\r
+                       return;\r
+               }\r
+               \r
+               try {\r
+                       FileInputStream fin = new FileInputStream(path);\r
+                       ObjectInputStream ois = new ObjectInputStream(fin);\r
+                       this.jobs = (List<RecordJob>) ois.readObject();\r
+                       for (RecordJob currentJob : this.jobs) {\r
+                               currentJob.setAppLayer(this);\r
+                       }\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the job queue file " + path.getAbsolutePath(), e, true);\r
+               }\r
+               \r
+       }\r
+       \r
+       public void saveJobQueue() {\r
+               File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);\r
+               this.saveJobQueue(defaultFile);\r
+       }\r
+       \r
+       public void saveJobQueue(File path) {\r
+               if (!path.exists()) {\r
+                       try {\r
+                               path.createNewFile();\r
+                       } catch (IOException e) {\r
+                               File parentDir = path.getParentFile();\r
+                               if (!parentDir.exists()) {\r
+                                       try {\r
+                                               if (parentDir.mkdirs() == true) {\r
+                                                       try {\r
+                                                               path.createNewFile();\r
+                                                       } catch (Exception ex) {}\r
+                                               }\r
+                                       } catch (Exception ex) {}\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               String exceptionMessage = "Could not save the job queue file " + path.getAbsolutePath();\r
+               \r
+               if (!path.exists()) {\r
+                       DemoRecorderException ex = new DemoRecorderException(exceptionMessage);\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(ex);\r
+                       return;\r
+               }\r
+               \r
+               //make sure that for the next start of the program the state is set to waiting again\r
+               for (RecordJob job : this.jobs) {\r
+                       if (job.getState() == RecordJob.State.PROCESSING) {\r
+                               job.setState(RecordJob.State.WAITING);\r
+                       }\r
+                       job.setAppLayer(null); //we don't want to serialize the app layer!\r
+               }\r
+               \r
+               try {\r
+                       FileOutputStream fout = new FileOutputStream(path);\r
+                       ObjectOutputStream oos = new ObjectOutputStream(fout);\r
+                       oos.writeObject(this.jobs);\r
+                       oos.close();\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);\r
+               }\r
+               \r
+               //we sometimes also save the jobqueue and don't exit the program, so restore the applayer again\r
+               for (RecordJob job : this.jobs) {\r
+                       job.setAppLayer(this);\r
+               }\r
+       }\r
+       \r
+       public void shutDown() {\r
+               this.poolExecutor.shutDown();\r
+               this.savePreferences();\r
+               this.saveJobQueue();\r
+       }\r
+       \r
+       public int getState() {\r
+               return this.state;\r
+       }\r
+       \r
+       private void loadPlugins() {\r
+               File pluginDir = DemoRecorderUtils.computeLocalFile(PLUGINS_DIRNAME, "");\r
+\r
+               if (!pluginDir.exists()) {\r
+                       pluginDir.mkdir();\r
+               }\r
+\r
+               File[] jarFiles = pluginDir.listFiles();\r
+\r
+               List<URL> urlList = new ArrayList<URL>();\r
+               for (File f : jarFiles) {\r
+                       try {\r
+                               urlList.add(f.toURI().toURL());\r
+                       } catch (MalformedURLException ex) {}\r
+               }\r
+               ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();\r
+               URL[] urls = new URL[urlList.size()];\r
+               urls = urlList.toArray(urls);\r
+               URLClassLoader classLoader = new URLClassLoader(urls, parentLoader);\r
+               \r
+               ServiceLoader<EncoderPlugin> loader = ServiceLoader.load(EncoderPlugin.class, classLoader);\r
+               for (EncoderPlugin implementation : loader) {\r
+                       this.encoderPlugins.add(implementation);\r
+               }\r
+       }\r
+       \r
+       private void configurePlugins() {\r
+               for (EncoderPlugin plugin : this.encoderPlugins) {\r
+                       plugin.setApplicationLayer(this);\r
+                       Properties pluginPreferences = plugin.getGlobalPreferences();\r
+                       for (Object preference : pluginPreferences.keySet()) {\r
+                               String preferenceString = (String) preference;\r
+                               \r
+                               if (this.preferences.getProperty(plugin.getName(), preferenceString) == null) {\r
+                                       String defaultValue = pluginPreferences.getProperty(preferenceString);\r
+                                       this.preferences.setProperty(plugin.getName(), preferenceString, defaultValue);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       public List<EncoderPlugin> getEncoderPlugins() {\r
+               return encoderPlugins;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java
new file mode 100644 (file)
index 0000000..f9318f4
--- /dev/null
@@ -0,0 +1,13 @@
+package com.nexuiz.demorecorder.application;\r
+\r
+public class DemoRecorderException extends RuntimeException {\r
+       \r
+       private static final long serialVersionUID = 965053013957793155L;\r
+       public DemoRecorderException(String message) {\r
+               super(message);\r
+       }\r
+       public DemoRecorderException(String message, Throwable cause) {\r
+               super(message, cause);\r
+       }\r
+\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java
new file mode 100644 (file)
index 0000000..e46e7ed
--- /dev/null
@@ -0,0 +1,95 @@
+package com.nexuiz.demorecorder.application;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+\r
+import org.jdesktop.swingx.JXErrorPane;\r
+import org.jdesktop.swingx.error.ErrorInfo;\r
+\r
+public class DemoRecorderUtils {\r
+       \r
+       public static void showNonCriticalErrorDialog(Throwable e) {\r
+               if (!(e instanceof DemoRecorderException)) {\r
+                       e = new DemoRecorderException("Internal error", e);\r
+               }\r
+               ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);\r
+               JXErrorPane.showDialog(null, info);\r
+       }\r
+       \r
+       /**\r
+        * Shows an error dialog that contains the stack trace, catching the exception so that the program flow\r
+        * won't be interrupted.\r
+        * This method will maybe wrap e in a DemoRecorderException with the given message.\r
+        * @param customMessage\r
+        * @param e\r
+        * @param wrapException set to true if Exception should be wrapped into a DemoRecorderException\r
+        */\r
+       public static void showNonCriticalErrorDialog(String customMessage, Throwable e, boolean wrapException) {\r
+               Throwable ex = e;\r
+               if (wrapException && !(e instanceof DemoRecorderException)) {\r
+                       ex = new DemoRecorderException(customMessage, e);\r
+               }\r
+               \r
+               ErrorInfo info = new ErrorInfo("Error occurred", ex.getMessage(), null, null, ex, null, null);\r
+               JXErrorPane.showDialog(null, info);\r
+       }\r
+       \r
+       public static File computeLocalFile(String subDir, String fileName) {\r
+               String path = System.getProperty("user.dir");\r
+               if (subDir != null && !subDir.equals("")) {\r
+                       path += File.separator + subDir;\r
+               }\r
+               path += File.separator + fileName;\r
+               return new File(path);\r
+       }\r
+       \r
+       /**\r
+        * Returns just the name of the file for a given File. E.g. if the File points to\r
+        * /home/someuser/somedir/somefile.end the function will return "somefile.end"\r
+        * @param file\r
+        * @return just the name of the file\r
+        */\r
+       public static String getJustFileNameOfPath(File file) {\r
+               String fileString = file.getAbsolutePath();\r
+               int lastIndex = fileString.lastIndexOf(File.separator);\r
+               String newString = fileString.substring(lastIndex+1, fileString.length());\r
+               return newString;\r
+       }\r
+       \r
+       /**\r
+        * Attempts to create an empty file (unless it already exists), including the creation\r
+        * of parent directories. If it succeeds to do so (or if the file already existed), true\r
+        * will be returned. Otherwise false will be returned\r
+        * @param file the file to be created\r
+        * @return true if file already existed or could successfully created, false otherwise\r
+        */\r
+       public static boolean attemptFileCreation(File file) {\r
+               if (!file.exists()) {\r
+                       try {\r
+                               file.createNewFile();\r
+                               return true;\r
+                       } catch (IOException e) {\r
+                               File parentDir = file.getParentFile();\r
+                               if (!parentDir.exists()) {\r
+                                       try {\r
+                                               if (parentDir.mkdirs() == true) {\r
+                                                       try {\r
+                                                               file.createNewFile();\r
+                                                               return true;\r
+                                                       } catch (Exception ex) {}\r
+                                               }\r
+                                       } catch (Exception ex) {}\r
+                               }\r
+                               return false;\r
+                       }\r
+               } else {\r
+                       return true;\r
+               }\r
+       }\r
+       \r
+       public static final String getFileExtension(File file) {\r
+               String fileName = file.getAbsolutePath();\r
+               String ext = (fileName.lastIndexOf(".") == -1) ? "" : fileName.substring(fileName.lastIndexOf(".") + 1,fileName.length());\r
+               return ext;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java
new file mode 100644 (file)
index 0000000..b599e4a
--- /dev/null
@@ -0,0 +1,66 @@
+package com.nexuiz.demorecorder.application;\r
+\r
+import java.util.Properties;\r
+\r
+/**\r
+ * Class that stores the application and global plug-in preferences of the Nexuiz\r
+ * Demo Recorder application. Set and Get property methods have been modified to \r
+ * now supply a category.\r
+ */\r
+public class NDRPreferences extends Properties {\r
+       \r
+       private static final long serialVersionUID = 4363913054294979418L;\r
+       private static final String CONCATENATOR = ".";\r
+       /**\r
+        * Category that defines a setting to be a setting of the NDR application itself\r
+        * (and not of one of the plugins).\r
+        */\r
+       public static final String MAIN_APPLICATION = "NDR";\r
+\r
+       /**\r
+     * Searches for the property with the specified key in this property list.\r
+     * If the key is not found in this property list, the default property list,\r
+     * and its defaults, recursively, are then checked. The method returns\r
+     * <code>null</code> if the property is not found.\r
+     *\r
+     * @param   category the category of the setting\r
+     * @param   key   the property key.\r
+     * @return  the value in this property list with the specified category+key value.\r
+     */\r
+       public String getProperty(String category, String key) {\r
+               return getProperty(getConcatenatedKey(category, key));\r
+       }\r
+       \r
+       /**\r
+     * Calls the <tt>Hashtable</tt> method <code>put</code>. Provided for\r
+     * parallelism with the <tt>getProperty</tt> method. Enforces use of\r
+     * strings for property keys and values. The value returned is the\r
+     * result of the <tt>Hashtable</tt> call to <code>put</code>.\r
+     *\r
+     * @param category the category of the setting\r
+     * @param key the key to be placed into this property list.\r
+     * @param value the value corresponding to <tt>key</tt>.\r
+     * @return     the previous value of the specified key in this property\r
+     *             list, or <code>null</code> if it did not have one.\r
+     */\r
+       public void setProperty(String category, String key, String value) {\r
+               setProperty(getConcatenatedKey(category, key), value);\r
+       }\r
+       \r
+       /**\r
+        * Returns only the category of a key that is a concatenated string of category and key.\r
+        * @param concatenatedString\r
+        * @return\r
+        */\r
+       public static String getCategory(String concatenatedString) {\r
+               return concatenatedString.substring(0, concatenatedString.indexOf(CONCATENATOR));\r
+       }\r
+       \r
+       public static String getKey(String concatenatedString) {\r
+               return concatenatedString.substring(concatenatedString.indexOf(CONCATENATOR) + 1, concatenatedString.length());\r
+       }\r
+       \r
+       public static String getConcatenatedKey(String category, String key) {\r
+               return category + CONCATENATOR + key;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java
new file mode 100644 (file)
index 0000000..7f4181f
--- /dev/null
@@ -0,0 +1,48 @@
+package com.nexuiz.demorecorder.application;\r
+\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.concurrent.ArrayBlockingQueue;\r
+import java.util.concurrent.ThreadPoolExecutor;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
+\r
+public class RecorderJobPoolExecutor {\r
+       \r
+       private int poolSize = 1;\r
+       private int maxPoolSize = 1;\r
+       private long keepAliveTime = 10;\r
+       private ThreadPoolExecutor threadPool = null;\r
+       private ArrayBlockingQueue<Runnable> queue = null;\r
+\r
+       public RecorderJobPoolExecutor() {\r
+               queue = new ArrayBlockingQueue<Runnable>(99999);\r
+               threadPool = new ThreadPoolExecutor(poolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, queue);\r
+       }\r
+\r
+       public void runJob(Runnable task) {\r
+               threadPool.execute(task);\r
+       }\r
+       \r
+       public void clearUnfinishedJobs() {\r
+               threadPool.getQueue().clear();\r
+       }\r
+\r
+       public void shutDown() {\r
+               threadPool.shutdownNow();\r
+       }\r
+       \r
+       public synchronized List<RecordJob> getJobList() {\r
+               List<RecordJob> list = new ArrayList<RecordJob>();\r
+               for (Runnable job : this.queue) {\r
+                       try {\r
+                               RecordJob j = (RecordJob)job;\r
+                               list.add(j);\r
+                       } catch (ClassCastException e) {}\r
+               }\r
+               \r
+               return list;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java
new file mode 100644 (file)
index 0000000..0aff553
--- /dev/null
@@ -0,0 +1,220 @@
+package com.nexuiz.demorecorder.application.democutter;\r
+import java.io.DataInputStream;\r
+import java.io.DataOutputStream;\r
+import java.io.EOFException;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileNotFoundException;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.UnsupportedEncodingException;\r
+\r
+public class DemoCutter {\r
+\r
+       private static final byte CDTRACK_SEPARATOR = 0x0A;\r
+\r
+       private DataInputStream inStream;\r
+       private DataOutputStream outStream;\r
+       private File inFile;\r
+       private File outFile;\r
+\r
+       /**\r
+        * Calls the cutDemo method with reasonable default values for the second and first fast-forward stage.\r
+        * @param inFile @see other cutDemo method\r
+        * @param outFile @see other cutDemo method\r
+        * @param startTime @see other cutDemo method\r
+        * @param endTime @see other cutDemo method\r
+        * @param injectAtStart @see other cutDemo method\r
+        * @param injectBeforeCap @see other cutDemo method\r
+        * @param injectAfterCap @see other cutDemo method\r
+        */\r
+       public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap) {\r
+               this.cutDemo(inFile, outFile, startTime, endTime, injectAtStart, injectBeforeCap, injectAfterCap, 100, 10);\r
+       }\r
+       \r
+       /**\r
+        * Cuts the demo by injecting a 2-phase fast forward command until startTime is reached, then injects the cl_capturevideo 1 command\r
+        * and once endTime is reached the cl_capturevideo 0 command is injected.\r
+        * @param inFile the original demo file\r
+        * @param outFile the new cut demo file\r
+        * @param startTime when to start capturing (use the gametime in seconds)\r
+        * @param endTime when to stop capturing\r
+        * @param injectAtStart a String that will be injected right at the beginning of the demo\r
+        *                                              can be anything that would make sense and can be parsed by DP's console\r
+        * @param injectBeforeCap a String that will be injected 5 seconds before capturing starts\r
+        * @param injectAfterCap a String that will be injected shortly after capturing ended\r
+        * @param ffwSpeedFirstStage fast-forward speed at first stage, when the startTime is still about a minute away (use high values, e.g. 100)\r
+        * @param ffwSpeedSecondStage fast-forward speed when coming a few seconds close to startTime, use lower values e.g. 5 or 10\r
+        */\r
+       public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap, int ffwSpeedFirstStage, int ffwSpeedSecondStage) {\r
+               this.inFile = inFile;\r
+               this.outFile = outFile;\r
+               this.prepareStreams();\r
+               this.readCDTrack();\r
+               injectAfterCap = this.checkInjectString(injectAfterCap);\r
+               injectAtStart = this.checkInjectString(injectAtStart);\r
+               injectBeforeCap = this.checkInjectString(injectBeforeCap);\r
+\r
+               byte[] data;\r
+               float svctime = -1;\r
+               boolean firstLoop = true;\r
+               String injectBuffer = "";\r
+               int demoStarted = 0;\r
+               boolean endIsReached = false;\r
+               boolean finalInjectionDone = false;\r
+               boolean disconnectIssued = false;\r
+               int svcLoops = 0;\r
+               float firstSvcTime = -1;\r
+               float lastSvcTime = -1;\r
+               \r
+               try {\r
+                       while (true) {\r
+                               DemoPacket demoPacket = new DemoPacket(this.inStream);\r
+                               if (demoPacket.isEndOfFile()) {\r
+                                       break;\r
+                               }\r
+                               \r
+                               if (demoPacket.isClientToServerPacket()) {\r
+                                       try {\r
+                                               this.outStream.write(demoPacket.getOriginalLengthAsByte());\r
+                                               this.outStream.write(demoPacket.getAngles());\r
+                                               this.outStream.write(demoPacket.getOriginalData());\r
+                                       } catch (IOException e) {\r
+                                               throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);\r
+                                       }\r
+                                       \r
+                                       continue;\r
+                               }\r
+\r
+                               if (demoPacket.getSvcTime() != -1) {\r
+                                       svctime = demoPacket.getSvcTime();\r
+                               }\r
+\r
+                               if (svctime != -1) {\r
+                                       if (firstSvcTime == -1) {\r
+                                               firstSvcTime = svctime;\r
+                                       }\r
+                                       lastSvcTime = svctime;\r
+                                       \r
+                                       if (firstLoop) {\r
+                                               injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedFirstStage + "\n\000";\r
+                                               firstLoop = false;\r
+                                       }\r
+                                       if (demoStarted < 1 && svctime > (startTime - 50)) {\r
+                                               if (svcLoops == 0) {\r
+                                                       //make sure that for short demos (duration less than 50 sec)\r
+                                                       //the injectAtStart is still honored\r
+                                                       injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedSecondStage + "\n\000";\r
+                                               } else {\r
+                                                       injectBuffer = "\011\nslowmo " + ffwSpeedSecondStage + "\n\000";\r
+                                               }\r
+                                               \r
+                                               demoStarted = 1;\r
+                                       }\r
+                                       if (demoStarted < 2 && svctime > (startTime - 5)) {\r
+                                               injectBuffer = "\011\nslowmo 1;" + injectBeforeCap +"\n\000";\r
+                                               demoStarted = 2;\r
+                                       }\r
+                                       if (demoStarted < 3 && svctime > startTime) {\r
+                                               injectBuffer = "\011\ncl_capturevideo 1\n\000";\r
+                                               demoStarted = 3;\r
+                                       }\r
+                                       if (!endIsReached && svctime > endTime) {\r
+                                               injectBuffer = "\011\ncl_capturevideo 0\n\000";\r
+                                               endIsReached = true;\r
+                                       }\r
+                                       if (endIsReached && !finalInjectionDone && svctime > (endTime + 1)) {\r
+                                               injectBuffer = "\011\n" + injectAfterCap + "\n\000";\r
+                                               finalInjectionDone = true;\r
+                                       }\r
+                                       if (finalInjectionDone && !disconnectIssued && svctime > (endTime + 2)) {\r
+                                               injectBuffer = "\011\ndisconnect\n\000";\r
+                                               disconnectIssued = true;\r
+                                       }\r
+                                       svcLoops++;\r
+                               }\r
+\r
+                               byte[] injectBufferAsBytes = null;\r
+                               try {\r
+                                       injectBufferAsBytes = injectBuffer.getBytes("US-ASCII");\r
+                               } catch (UnsupportedEncodingException e) {\r
+                                       throw new DemoCutterException("Could not convert String to bytes using US-ASCII charset!", e);\r
+                               }\r
+\r
+                               data = demoPacket.getOriginalData();\r
+                               if ((injectBufferAsBytes.length + data.length) < 65536) {\r
+                                       data = DemoCutterUtils.mergeByteArrays(injectBufferAsBytes, data);\r
+                                       injectBuffer = "";\r
+                               }\r
+                               \r
+                               byte[] newLengthLittleEndian = DemoCutterUtils.convertLittleEndian(data.length);\r
+                               try {\r
+                                       this.outStream.write(newLengthLittleEndian);\r
+                                       this.outStream.write(demoPacket.getAngles());\r
+                                       this.outStream.write(data);\r
+                               } catch (IOException e) {\r
+                                       throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);\r
+                               }\r
+\r
+                       }\r
+                       \r
+                       if (startTime < firstSvcTime) {\r
+                               throw new DemoCutterException("Start time for the demo is " + startTime + ", but demo doesn't start before " + firstSvcTime);\r
+                       }\r
+                       if (endTime > lastSvcTime) {\r
+                               throw new DemoCutterException("End time for the demo is " + endTime + ", but demo already stops at " + lastSvcTime);\r
+                       }\r
+               } catch (DemoCutterException e) {\r
+                       throw e;\r
+               } catch (Throwable e) {\r
+                       throw new DemoCutterException("Internal error in demo cutter sub-route (invalid demo file?)", e);\r
+               } finally {\r
+                       try {\r
+                               this.outStream.close();\r
+                               this.inStream.close();\r
+                       } catch (IOException e) {}\r
+               }\r
+       }\r
+\r
+       \r
+\r
+       /**\r
+        * Seeks forward in the inStream until CDTRACK_SEPARATOR byte was reached.\r
+        * All the content is copied to the outStream.\r
+        */\r
+       private void readCDTrack() {\r
+               byte lastByte;\r
+               try {\r
+                       while ((lastByte = inStream.readByte()) != CDTRACK_SEPARATOR) {\r
+                               this.outStream.write(lastByte);\r
+                       }\r
+                       this.outStream.write(CDTRACK_SEPARATOR);\r
+               } catch (EOFException e) {\r
+                       throw new DemoCutterException("Unexpected EOF occurred when reading CD track of demo " + inFile.getPath(), e);\r
+               }\r
+               catch (IOException e) {\r
+                       throw new DemoCutterException("Unexpected I/O Exception occurred when reading CD track of demo " + inFile.getPath(), e);\r
+               }\r
+       }\r
+\r
+       private void prepareStreams() {\r
+               try {\r
+                       this.inStream = new DataInputStream(new FileInputStream(this.inFile));\r
+               } catch (FileNotFoundException e) {\r
+                       throw new DemoCutterException("Could not open demo file " + inFile.getPath(), e);\r
+               }\r
+               \r
+               try {\r
+                       this.outStream = new DataOutputStream(new FileOutputStream(this.outFile));\r
+               } catch (FileNotFoundException e) {\r
+                       throw new DemoCutterException("Could not open demo file " + outFile.getPath(), e);\r
+               }\r
+       }\r
+       \r
+       private String checkInjectString(String injectionString) {\r
+               while (injectionString.endsWith(";") || injectionString.endsWith("\n")) {\r
+                       injectionString = injectionString.substring(0, injectionString.length()-1);\r
+               }\r
+               return injectionString;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java
new file mode 100644 (file)
index 0000000..6b6666b
--- /dev/null
@@ -0,0 +1,14 @@
+package com.nexuiz.demorecorder.application.democutter;\r
+\r
+public class DemoCutterException extends RuntimeException {\r
+       \r
+       private static final long serialVersionUID = -1419472153834762285L;\r
+       \r
+       public DemoCutterException(String message) {\r
+               super(message);\r
+       }\r
+       public DemoCutterException(String message, Throwable cause) {\r
+               super(message, cause);\r
+       }\r
+\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java
new file mode 100644 (file)
index 0000000..dd8f9ca
--- /dev/null
@@ -0,0 +1,40 @@
+package com.nexuiz.demorecorder.application.democutter;\r
+import java.nio.ByteBuffer;\r
+import java.nio.ByteOrder;\r
+\r
+\r
+public class DemoCutterUtils {\r
+\r
+       public static float byteArrayToFloat(byte[] array) {\r
+               byte[] tmp = new byte[4];\r
+               System.arraycopy(array, 0, tmp, 0, 4);\r
+               int accum = 0;\r
+               int i = 0;\r
+               for (int shiftBy = 0; shiftBy < 32; shiftBy += 8) {\r
+                       accum |= ((long) (tmp[i++] & 0xff)) << shiftBy;\r
+               }\r
+               return Float.intBitsToFloat(accum);\r
+       }\r
+\r
+       public static byte[] convertLittleEndian(int i) {\r
+               ByteBuffer bb = ByteBuffer.allocate(4);\r
+               bb.order(ByteOrder.LITTLE_ENDIAN);\r
+               bb.putInt(i);\r
+               return bb.array();\r
+       }\r
+\r
+       public static byte[] mergeByteArrays(byte[] array1, byte[] array2) {\r
+               ByteBuffer bb = ByteBuffer.allocate(array1.length + array2.length);\r
+               bb.put(array1);\r
+               bb.put(array2);\r
+               return bb.array();\r
+       }\r
+\r
+       public static int convertLittleEndian(byte[] b) {\r
+               ByteBuffer bb = ByteBuffer.allocate(4);\r
+               bb.order(ByteOrder.LITTLE_ENDIAN);\r
+               bb.put(b);\r
+               bb.position(0);\r
+               return bb.getInt();\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java
new file mode 100644 (file)
index 0000000..dcca821
--- /dev/null
@@ -0,0 +1,102 @@
+package com.nexuiz.demorecorder.application.democutter;\r
+import java.io.DataInputStream;\r
+import java.io.EOFException;\r
+import java.io.IOException;\r
+import java.nio.ByteBuffer;\r
+\r
+\r
+public class DemoPacket {\r
+       \r
+       private static final int DEMOMSG_CLIENT_TO_SERVER = 0x80000000;\r
+       \r
+       private DataInputStream inStream = null;\r
+       private boolean isEndOfFile = false;\r
+       private byte[] buffer = new byte[4]; //contains packet length\r
+       private byte[] angles = new byte[12];\r
+       private byte[] data;\r
+       private int packetLength;\r
+       private boolean isClientToServer = false;\r
+       private float svcTime = -1;\r
+\r
+       public DemoPacket(DataInputStream inStream) {\r
+               this.inStream = inStream;\r
+               \r
+               try {\r
+                       inStream.readFully(buffer);\r
+               } catch (EOFException e) {\r
+                       this.isEndOfFile = true;\r
+                       return;\r
+               } catch (IOException e) {\r
+                       throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");\r
+               }\r
+               \r
+               packetLength = DemoCutterUtils.convertLittleEndian(buffer);\r
+               if ((packetLength & DEMOMSG_CLIENT_TO_SERVER) != 0) {\r
+                       packetLength = packetLength & ~DEMOMSG_CLIENT_TO_SERVER;\r
+\r
+                       this.isClientToServer = true;\r
+                       this.readAnglesAndData();\r
+                       return;\r
+               }\r
+               \r
+               this.readAnglesAndData();\r
+               \r
+               // extract svc_time\r
+               this.readSvcTime();\r
+               \r
+       }\r
+       \r
+       public boolean isEndOfFile() {\r
+               return this.isEndOfFile;\r
+       }\r
+       \r
+       public boolean isClientToServerPacket() {\r
+               return this.isClientToServer;\r
+       }\r
+       \r
+       public byte[] getOriginalLengthAsByte() {\r
+               return this.buffer;\r
+       }\r
+       \r
+       public byte[] getAngles() {\r
+               return this.angles;\r
+       }\r
+       \r
+       public byte[] getOriginalData() {\r
+               return this.data;\r
+       }\r
+       \r
+       public float getSvcTime() {\r
+               return this.svcTime;\r
+       }\r
+       \r
+       private void readAnglesAndData() {\r
+               // read angles\r
+               try {\r
+                       inStream.readFully(angles);\r
+               } catch (EOFException e) {\r
+                       throw new DemoCutterException("Invalid Demo Packet");\r
+               } catch (IOException e) {\r
+                       throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");\r
+               }\r
+\r
+               // read data\r
+               data = new byte[packetLength];\r
+               try {\r
+                       inStream.readFully(data);\r
+               } catch (EOFException e) {\r
+                       throw new DemoCutterException("Invalid Demo Packet");\r
+               } catch (IOException e) {\r
+                       throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");\r
+               }\r
+       }\r
+       \r
+       private void readSvcTime() {\r
+               if (data[0] == 0x07) {\r
+                       ByteBuffer bb = ByteBuffer.allocate(4);\r
+                       bb.put(data, 1, 4);\r
+                       byte[] array = bb.array();\r
+                       this.svcTime = DemoCutterUtils.byteArrayToFloat(array);\r
+               }\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java
new file mode 100644 (file)
index 0000000..430c988
--- /dev/null
@@ -0,0 +1,24 @@
+package com.nexuiz.demorecorder.application.jobs;\r
+\r
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
+\r
+/**\r
+ * Job for the ThreadPoolExecutor that will just call the encoder-plugin's execute\r
+ * method.\r
+ */\r
+public class EncoderJob implements Runnable {\r
+       \r
+       private RecordJob job;\r
+       private EncoderPlugin plugin;\r
+       \r
+       public EncoderJob(RecordJob job, EncoderPlugin plugin) {\r
+               this.job = job;\r
+               this.plugin = plugin;\r
+       }\r
+\r
+       @Override\r
+       public void run() {\r
+               this.job.executePlugin(this.plugin);\r
+       }\r
+\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java
new file mode 100644 (file)
index 0000000..429ff98
--- /dev/null
@@ -0,0 +1,636 @@
+package com.nexuiz.demorecorder.application.jobs;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.InputStreamReader;\r
+import java.io.Serializable;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
+import com.nexuiz.demorecorder.application.DemoRecorderException;\r
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
+import com.nexuiz.demorecorder.application.NDRPreferences;\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;\r
+import com.nexuiz.demorecorder.application.democutter.DemoCutter;\r
+import com.nexuiz.demorecorder.application.democutter.DemoCutterException;\r
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
+import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;\r
+\r
+public class RecordJob implements Runnable, Serializable {\r
+       \r
+       private static final long serialVersionUID = -4585637490345587912L;\r
+\r
+       public enum State {\r
+               WAITING, PROCESSING, ERROR, ERROR_PLUGIN, DONE\r
+       }\r
+       \r
+       public static final String CUT_DEMO_FILE_SUFFIX = "_autocut";\r
+       public static final String CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE = "autocap";\r
+       public static final String CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE = "1234567";\r
+       protected static final String[] VIDEO_FILE_ENDINGS = {"avi", "ogv"};\r
+       \r
+       private DemoRecorderApplication appLayer;\r
+       protected String jobName;\r
+       private int jobIndex;\r
+       protected File enginePath;\r
+       protected String engineParameters;\r
+       protected File demoFile;\r
+       protected String relativeDemoPath;\r
+       protected File dpVideoPath;\r
+       protected File videoDestination;\r
+       protected String executeBeforeCap;\r
+       protected String executeAfterCap;\r
+       protected float startSecond;\r
+       protected float endSecond;\r
+       protected State state = State.WAITING;\r
+       protected DemoRecorderException lastException = null;\r
+       \r
+       /**\r
+        * Points to the actual final file, including possible suffixes, e.g. _copy1, and the actualy ending\r
+        */\r
+       protected File actualVideoDestination = null;\r
+       /**\r
+        * Map that identifies the plug-in by its name (String) and maps to the plug-in's job-specific settings\r
+        */\r
+       protected Map<String, Properties> encoderPluginSettings = new HashMap<String, Properties>();\r
+       \r
+       private List<File> cleanUpFiles = null;\r
+       \r
+       public RecordJob(\r
+               DemoRecorderApplication appLayer,\r
+               String jobName,\r
+               int jobIndex,\r
+               File enginePath,\r
+               String engineParameters,\r
+               File demoFile,\r
+               String relativeDemoPath,\r
+               File dpVideoPath,\r
+               File videoDestination,\r
+               String executeBeforeCap,\r
+               String executeAfterCap,\r
+               float startSecond,\r
+               float endSecond\r
+       ) {\r
+               this.appLayer = appLayer;\r
+               this.jobName = jobName;\r
+               this.jobIndex = jobIndex;\r
+               \r
+               this.setEnginePath(enginePath);\r
+               this.setEngineParameters(engineParameters);\r
+               this.setDemoFile(demoFile);\r
+               this.setRelativeDemoPath(relativeDemoPath);\r
+               this.setDpVideoPath(dpVideoPath);\r
+               this.setVideoDestination(videoDestination);\r
+               this.setExecuteBeforeCap(executeBeforeCap);\r
+               this.setExecuteAfterCap(executeAfterCap);\r
+               this.setStartSecond(startSecond);\r
+               this.setEndSecond(endSecond);\r
+       }\r
+       \r
+       public RecordJob(){}\r
+       \r
+       /**\r
+        * Constructor that can be used by other classes such as job templates. Won't throw exceptions\r
+        * as it won't check the input for validity.\r
+        */\r
+       protected RecordJob(\r
+               File enginePath,\r
+               String engineParameters,\r
+               File demoFile,\r
+               String relativeDemoPath,\r
+               File dpVideoPath,\r
+               File videoDestination,\r
+               String executeBeforeCap,\r
+               String executeAfterCap,\r
+               float startSecond,\r
+               float endSecond\r
+               ) {\r
+               this.jobIndex = -1;\r
+               this.enginePath = enginePath;\r
+               this.engineParameters = engineParameters;\r
+               this.demoFile = demoFile;\r
+               this.relativeDemoPath = relativeDemoPath;\r
+               this.dpVideoPath = dpVideoPath;\r
+               this.videoDestination = videoDestination;\r
+               this.executeBeforeCap = executeBeforeCap;\r
+               this.executeAfterCap = executeAfterCap;\r
+               this.startSecond = startSecond;\r
+               this.endSecond = endSecond;\r
+       }\r
+       \r
+       public void execute() {\r
+               if (this.state == State.PROCESSING) {\r
+                       return;\r
+               }\r
+               boolean errorOccurred = false;\r
+               this.setState(State.PROCESSING);\r
+               this.appLayer.fireUserInterfaceUpdate(this);\r
+               cleanUpFiles = new ArrayList<File>();\r
+               \r
+               File cutDemo = computeCutDemoFile();\r
+               cutDemo.delete(); //delete possibly old cutDemoFile\r
+               \r
+               EncoderPlugin recentEncoder = null;\r
+               \r
+               try {\r
+                       this.cutDemo(cutDemo);\r
+                       this.removeOldAutocaps();\r
+                       this.recordClip(cutDemo);\r
+                       this.moveRecordedClip();\r
+                       for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {\r
+                               recentEncoder = plugin;\r
+                               plugin.executeEncoder(this);\r
+                       }\r
+               } catch (DemoRecorderException e) {\r
+                       errorOccurred = true;\r
+                       this.lastException = e;\r
+                       this.setState(State.ERROR);\r
+               } catch (EncoderPluginException e) {\r
+                       errorOccurred = true;\r
+                       this.lastException = new DemoRecorderException("Encoder plug-in " + recentEncoder.getName() + " failed: "\r
+                                       + e.getMessage(), e);\r
+                       this.setState(State.ERROR_PLUGIN);\r
+               } catch (Exception e) {\r
+                       errorOccurred = true;\r
+                       this.lastException = new DemoRecorderException("Executing job failed, click on details for more info", e);\r
+               } finally {\r
+                       NDRPreferences preferences = this.appLayer.getPreferences();\r
+                       if (!Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS))) {\r
+                               cleanUpFiles.add(cutDemo);\r
+                       }\r
+                       if (!errorOccurred) {\r
+                               this.setState(State.DONE);\r
+                       }\r
+                       this.cleanUpFiles();\r
+                       this.appLayer.fireUserInterfaceUpdate(this);\r
+                       this.appLayer.saveJobQueue();\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Will execute just the specified encoder plug-in on an already "done" job.\r
+        * @param pluginName\r
+        */\r
+       public void executePlugin(EncoderPlugin plugin) {\r
+               if (this.getState() != State.DONE) {\r
+                       return;\r
+               }\r
+               this.setState(State.PROCESSING);\r
+               this.appLayer.fireUserInterfaceUpdate(this);\r
+               \r
+               try {\r
+                       plugin.executeEncoder(this);\r
+                       this.setState(State.DONE);\r
+               } catch (EncoderPluginException e) {\r
+                       this.lastException = new DemoRecorderException("Encoder plug-in " + plugin.getName() + " failed: "\r
+                                       + e.getMessage(), e);\r
+                       this.setState(State.ERROR_PLUGIN);\r
+               }\r
+               \r
+               this.appLayer.fireUserInterfaceUpdate(this);\r
+       }\r
+       \r
+       private void cleanUpFiles() {\r
+               try {\r
+                       for (File f : this.cleanUpFiles) {\r
+                               f.delete();\r
+                       }\r
+               } catch (Exception e) {}\r
+               \r
+       }\r
+       \r
+       private void moveRecordedClip() {\r
+               //1. Figure out whether the file is .avi or .ogv\r
+               File sourceFile = null;\r
+               for (String videoExtension : VIDEO_FILE_ENDINGS) {\r
+                       String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE\r
+                       + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;\r
+                       File videoFile = new File(fileString);\r
+                       if (videoFile.exists()) {\r
+                               sourceFile = videoFile;\r
+                               break;\r
+                       }\r
+               }\r
+               \r
+               if (sourceFile == null) {\r
+                       String p = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE\r
+                       + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE;\r
+                       throw new DemoRecorderException("Could not locate the expected video file being generated by Nexuiz (should have been at "\r
+                                       + p + ".avi/.ogv");\r
+               }\r
+               cleanUpFiles.add(sourceFile);\r
+               \r
+               File destinationFile = null;\r
+               NDRPreferences preferences = this.appLayer.getPreferences();\r
+               String sourceFileExtension = DemoRecorderUtils.getFileExtension(sourceFile);\r
+               String destinationFilePath = this.videoDestination + "." + sourceFileExtension;\r
+               destinationFile = new File(destinationFilePath);\r
+               if (destinationFile.exists()) {\r
+                       if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE))) {\r
+                               if (!destinationFile.delete()) {\r
+                                       throw new DemoRecorderException("Could not delete the existing video destinatin file " + destinationFile.getAbsolutePath()\r
+                                                       + " (application setting to overwrite existing video files is enabled!)");\r
+                               }\r
+                       } else {\r
+                               destinationFilePath = this.videoDestination + "_copy" + this.getVideoDestinationCopyNr(sourceFileExtension) + "." + sourceFileExtension;\r
+                               destinationFile = new File(destinationFilePath);\r
+                       }\r
+               }\r
+               \r
+               //finally move the file\r
+               if (!sourceFile.renameTo(destinationFile)) {\r
+                       cleanUpFiles.add(destinationFile);\r
+                       throw new DemoRecorderException("Could not move the video file from " + sourceFile.getAbsolutePath()\r
+                                       + " to " + destinationFile.getAbsolutePath());\r
+               }\r
+               \r
+               this.actualVideoDestination = destinationFile;\r
+       }\r
+       \r
+       /**\r
+        * As destination video files, e.g. "test"[.avi] can already exist, we have to save the\r
+        * the video file to a file name such as test_copy1 or test_copy2.\r
+        * This function will figure out what the number (1, 2....) is.\r
+        * @return\r
+        */\r
+       private int getVideoDestinationCopyNr(String sourceFileExtension) {\r
+               int i = 1;\r
+               File lastFile;\r
+               while (true) {\r
+                       lastFile = new File(this.videoDestination + "_copy" + i + "." + sourceFileExtension);\r
+                       if (!lastFile.exists()) {\r
+                               break;\r
+                       }\r
+                       \r
+                       i++;\r
+               }\r
+               return i;\r
+       }\r
+\r
+       private File computeCutDemoFile() {\r
+               String origFileString = this.demoFile.getAbsolutePath();\r
+               int lastIndex = origFileString.lastIndexOf(File.separator);\r
+               String autoDemoFileName = origFileString.substring(lastIndex+1, origFileString.length());\r
+               //strip .dem ending\r
+               autoDemoFileName = autoDemoFileName.substring(0, autoDemoFileName.length()-4);\r
+               autoDemoFileName = autoDemoFileName + CUT_DEMO_FILE_SUFFIX + ".dem";\r
+               String finalString = origFileString.substring(0, lastIndex) + File.separator + autoDemoFileName;\r
+               File f = new File(finalString);\r
+               \r
+               return f;\r
+       }\r
+       \r
+       private void cutDemo(File cutDemo) {\r
+               String injectAtStart = "";\r
+               String injectBeforeCap = "";\r
+               String injectAfterCap = "";\r
+               \r
+               NDRPreferences preferences = this.appLayer.getPreferences();\r
+               if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING))) {\r
+                       injectAtStart += "r_render 0;";\r
+                       injectBeforeCap += "r_render 1;";\r
+               }\r
+               if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND))) {\r
+                       injectAtStart += "set _volume $volume;volume 0;";\r
+                       injectBeforeCap += "set volume $_volume;";\r
+               }\r
+               injectBeforeCap += this.executeBeforeCap + "\n";\r
+               injectBeforeCap += "set _cl_capturevideo_nameformat $cl_capturevideo_nameformat;set _cl_capturevideo_number $cl_capturevideo_number;";\r
+               injectBeforeCap += "cl_capturevideo_nameformat " + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE + ";";\r
+               injectBeforeCap += "cl_capturevideo_number " + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + ";";\r
+               \r
+               injectAfterCap += this.executeAfterCap + "\n";\r
+               injectAfterCap += "cl_capturevideo_nameformat $_cl_capturevideo_nameformat;cl_capturevideo_number $_cl_capturevideo_number;";\r
+               \r
+               \r
+               DemoCutter cutter = new DemoCutter();\r
+               int fwdSpeedFirstStage, fwdSpeedSecondStage;\r
+               try {\r
+                       fwdSpeedFirstStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE));\r
+                       fwdSpeedSecondStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE));\r
+               } catch (NumberFormatException e) {\r
+                       throw new DemoRecorderException("Make sure that you specified valid numbers for the settings "\r
+                                       + Preferences.FFW_SPEED_FIRST_STAGE + " and " + Preferences.FFW_SPEED_SECOND_STAGE, e);\r
+               }\r
+               \r
+               try {\r
+                       cutter.cutDemo(\r
+                               this.demoFile,\r
+                               cutDemo,\r
+                               this.startSecond,\r
+                               this.endSecond,\r
+                               injectAtStart,\r
+                               injectBeforeCap,\r
+                               injectAfterCap,\r
+                               fwdSpeedFirstStage,\r
+                               fwdSpeedSecondStage\r
+                       );\r
+               } catch (DemoCutterException e) {\r
+                       throw new DemoRecorderException("Error occurred while trying to cut the demo: " + e.getMessage(), e);\r
+               }\r
+               \r
+       }\r
+       \r
+       private void removeOldAutocaps() {\r
+               for (String videoExtension : VIDEO_FILE_ENDINGS) {\r
+                       String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE\r
+                       + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;\r
+                       File videoFile = new File(fileString);\r
+                       cleanUpFiles.add(videoFile);\r
+                       if (videoFile.exists()) {\r
+                               if (!videoFile.delete()) {\r
+                                       throw new DemoRecorderException("Could not delete old obsolete video file " + fileString);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+       \r
+       private void recordClip(File cutDemo) {\r
+               Process nexProc;\r
+               String demoFileName = DemoRecorderUtils.getJustFileNameOfPath(cutDemo);\r
+               String execPath = this.enginePath.getAbsolutePath() + " " + this.engineParameters + " -demo "\r
+                                               + this.relativeDemoPath + "/" + demoFileName;\r
+               File engineDir = this.enginePath.getParentFile();\r
+               try {\r
+                       nexProc = Runtime.getRuntime().exec(execPath, null, engineDir);\r
+                       nexProc.getErrorStream();\r
+                       nexProc.getOutputStream();\r
+                       InputStream is = nexProc.getInputStream();\r
+                       InputStreamReader isr = new InputStreamReader(is);\r
+                       BufferedReader br = new BufferedReader(isr);\r
+                       while (br.readLine() != null) {\r
+                               //System.out.println(line);\r
+                       }\r
+               } catch (IOException e) {\r
+                       throw new DemoRecorderException("I/O Exception occurred when trying to execute the Nexuiz binary", e);\r
+               }\r
+       }\r
+\r
+       public void run() {\r
+               this.execute();\r
+       }\r
+       \r
+       public void setAppLayer(DemoRecorderApplication appLayer) {\r
+               this.appLayer = appLayer;\r
+       }\r
+\r
+       public int getJobIndex() {\r
+               return jobIndex;\r
+       }\r
+\r
+       public File getEnginePath() {\r
+               return enginePath;\r
+       }\r
+\r
+       public void setEnginePath(File enginePath) {\r
+               this.checkForProcessingState();\r
+               if (enginePath == null || !enginePath.exists()) {\r
+                       throw new DemoRecorderException("Could not locate engine binary!");\r
+               }\r
+               if (!enginePath.canExecute()) {\r
+                       throw new DemoRecorderException("The file you specified is not executable!");\r
+               }\r
+               this.enginePath = enginePath.getAbsoluteFile();\r
+       }\r
+\r
+       public String getEngineParameters() {\r
+               return engineParameters;\r
+       }\r
+\r
+       public void setEngineParameters(String engineParameters) {\r
+               this.checkForProcessingState();\r
+               if (engineParameters == null) {\r
+                       engineParameters = "";\r
+               }\r
+               this.engineParameters = engineParameters.trim();\r
+       }\r
+\r
+       public File getDemoFile() {\r
+               return demoFile;\r
+       }\r
+\r
+       public void setDemoFile(File demoFile) {\r
+               this.checkForProcessingState();\r
+               if (demoFile == null || !demoFile.exists()) {\r
+                       throw new DemoRecorderException("Could not locate demo file!");\r
+               }\r
+               if (!doReadWriteTest(demoFile.getParentFile())) {\r
+                       throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");\r
+               }\r
+               if (!demoFile.getAbsolutePath().endsWith(".dem")) {\r
+                       throw new DemoRecorderException("The demo file you specified must have the ending .dem");\r
+               }\r
+               \r
+               this.demoFile = demoFile.getAbsoluteFile();\r
+       }\r
+\r
+       public String getRelativeDemoPath() {\r
+               return relativeDemoPath;\r
+       }\r
+\r
+       public void setRelativeDemoPath(String relativeDemoPath) {\r
+               this.checkForProcessingState();\r
+               if (relativeDemoPath == null) {\r
+                       relativeDemoPath = "";\r
+               }\r
+               \r
+               //get rid of possible slashes\r
+               while (relativeDemoPath.startsWith("/") || relativeDemoPath.startsWith("\\")) {\r
+                       relativeDemoPath = relativeDemoPath.substring(1, relativeDemoPath.length());\r
+               }\r
+               while (relativeDemoPath.endsWith("/") || relativeDemoPath.endsWith("\\")) {\r
+                       relativeDemoPath = relativeDemoPath.substring(0, relativeDemoPath.length() - 1);\r
+               }\r
+               \r
+               this.relativeDemoPath = relativeDemoPath.trim();\r
+       }\r
+\r
+       public File getDpVideoPath() {\r
+               return dpVideoPath;\r
+       }\r
+\r
+       public void setDpVideoPath(File dpVideoPath) {\r
+               this.checkForProcessingState();\r
+               if (dpVideoPath == null || !dpVideoPath.isDirectory()) {\r
+                       throw new DemoRecorderException("Could not locate the specified DPVideo directory!");\r
+               }\r
+               \r
+               if (!this.doReadWriteTest(dpVideoPath)) {\r
+                       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");\r
+               }\r
+               this.dpVideoPath = dpVideoPath.getAbsoluteFile();\r
+       }\r
+\r
+       public File getVideoDestination() {\r
+               return videoDestination;\r
+       }\r
+\r
+       public void setVideoDestination(File videoDestination) {\r
+               this.checkForProcessingState();\r
+               //keep in mind, the parameter videoDestination points to the final avi/ogg file w/o extension!\r
+               if (videoDestination == null || !videoDestination.getParentFile().isDirectory()) {\r
+                       throw new DemoRecorderException("Could not locate the specified video destination");\r
+               }\r
+               \r
+               if (!this.doReadWriteTest(videoDestination.getParentFile())) {\r
+                       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");\r
+               }\r
+               \r
+               this.videoDestination = videoDestination.getAbsoluteFile();\r
+       }\r
+\r
+       public String getExecuteBeforeCap() {\r
+               return executeBeforeCap;\r
+       }\r
+\r
+       public void setExecuteBeforeCap(String executeBeforeCap) {\r
+               this.checkForProcessingState();\r
+               if (executeBeforeCap == null) {\r
+                       executeBeforeCap = "";\r
+               }\r
+               executeBeforeCap = executeBeforeCap.trim();\r
+               while (executeBeforeCap.endsWith(";")) {\r
+                       executeBeforeCap = executeBeforeCap.substring(0, executeBeforeCap.length()-1);\r
+               }\r
+               this.executeBeforeCap = executeBeforeCap;\r
+       }\r
+\r
+       public String getExecuteAfterCap() {\r
+               return executeAfterCap;\r
+       }\r
+\r
+       public void setExecuteAfterCap(String executeAfterCap) {\r
+               this.checkForProcessingState();\r
+               if (executeAfterCap == null) {\r
+                       executeAfterCap = "";\r
+               }\r
+               executeAfterCap = executeAfterCap.trim();\r
+               while (executeAfterCap.endsWith(";")) {\r
+                       executeAfterCap = executeAfterCap.substring(0, executeAfterCap.length()-1);\r
+               }\r
+               if (executeAfterCap.contains("cl_capturevideo_number") || executeAfterCap.contains("cl_capturevideo_nameformat")) {\r
+                       throw new DemoRecorderException("Execute after String cannot contain cl_capturevideo_number or _nameformat changes!");\r
+               }\r
+               this.executeAfterCap = executeAfterCap;\r
+       }\r
+\r
+       public float getStartSecond() {\r
+               return startSecond;\r
+       }\r
+\r
+       public void setStartSecond(float startSecond) {\r
+               this.checkForProcessingState();\r
+               if (startSecond < 0) {\r
+                       throw new DemoRecorderException("Start second cannot be < 0");\r
+               }\r
+               this.startSecond = startSecond;\r
+       }\r
+\r
+       public float getEndSecond() {\r
+               return endSecond;\r
+       }\r
+\r
+       public void setEndSecond(float endSecond) {\r
+               this.checkForProcessingState();\r
+               if (endSecond < this.startSecond) {\r
+                       throw new DemoRecorderException("End second cannot be < start second");\r
+               }\r
+               this.endSecond = endSecond;\r
+       }\r
+\r
+       public State getState() {\r
+               return state;\r
+       }\r
+\r
+       public void setState(State state) {\r
+               this.state = state;\r
+               this.appLayer.fireUserInterfaceUpdate(this);\r
+       }\r
+\r
+       public String getJobName() {\r
+               if (this.jobName == null || this.jobName.equals("")) {\r
+                       return "Job " + this.jobIndex;\r
+               }\r
+               return this.jobName;\r
+       }\r
+       \r
+       public void setJobName(String jobName) {\r
+               if (jobName == null || jobName.equals("")) {\r
+                       this.jobIndex = appLayer.getNewJobIndex();\r
+                       this.jobName = "Job " + this.jobIndex;\r
+               } else {\r
+                       this.jobName = jobName;\r
+               }\r
+       }\r
+\r
+       public DemoRecorderException getLastException() {\r
+               return lastException;\r
+       }\r
+       \r
+       /**\r
+        * Tests whether the given directory is writable by creating a file in there and deleting\r
+        * it again.\r
+        * @param directory\r
+        * @return true if directory is writable\r
+        */\r
+       protected boolean doReadWriteTest(File directory) {\r
+               boolean writable = false;\r
+               String fileName = "tmp." + Math.random()*10000 + ".dat";\r
+               File tempFile = new File(directory, fileName);\r
+               try {\r
+                       writable = tempFile.createNewFile();\r
+                       if (writable) {\r
+                               tempFile.delete();\r
+                       }\r
+               } catch (IOException e) {\r
+                       writable = false;\r
+               }\r
+               return writable;\r
+       }\r
+       \r
+       private void checkForProcessingState() {\r
+               if (this.state == State.PROCESSING) {\r
+                       throw new DemoRecorderException("Cannot modify this job while it is processing!");\r
+               }\r
+       }\r
+\r
+       public Properties getEncoderPluginSettings(EncoderPlugin plugin) {\r
+               if (this.encoderPluginSettings.containsKey(plugin.getName())) {\r
+                       return this.encoderPluginSettings.get(plugin.getName());\r
+               } else {\r
+                       return new Properties();\r
+               }\r
+       }\r
+\r
+       public void setEncoderPluginSetting(String pluginName, String pluginSettingKey, String value) {\r
+               Properties p = this.encoderPluginSettings.get(pluginName);\r
+               if (p == null) {\r
+                       p = new Properties();\r
+                       this.encoderPluginSettings.put(pluginName, p);\r
+               }\r
+               \r
+               p.put(pluginSettingKey, value);\r
+       }\r
+\r
+       public Map<String, Properties> getEncoderPluginSettings() {\r
+               return encoderPluginSettings;\r
+       }\r
+\r
+       public void setEncoderPluginSettings(Map<String, Properties> encoderPluginSettings) {\r
+               this.encoderPluginSettings = encoderPluginSettings;\r
+       }\r
+\r
+       public File getActualVideoDestination() {\r
+               return actualVideoDestination;\r
+       }\r
+       \r
+       public void setActualVideoDestination(File actualVideoDestination) {\r
+               this.actualVideoDestination = actualVideoDestination;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java
new file mode 100644 (file)
index 0000000..d1bcd67
--- /dev/null
@@ -0,0 +1,17 @@
+package com.nexuiz.demorecorder.application.jobs;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
+\r
+public class RecordsDoneJob implements Runnable {\r
+       \r
+       private DemoRecorderApplication appLayer;\r
+       \r
+       public RecordsDoneJob(DemoRecorderApplication appLayer) {\r
+               this.appLayer = appLayer;\r
+       }\r
+\r
+       public void run() {\r
+               this.appLayer.notifyAllJobsDone();\r
+       }\r
+\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java
new file mode 100644 (file)
index 0000000..b3c7de1
--- /dev/null
@@ -0,0 +1,69 @@
+package com.nexuiz.demorecorder.application.plugins;\r
+\r
+import java.util.Properties;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
+import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
+\r
+public interface EncoderPlugin {\r
+       \r
+       /**\r
+        * Makes the application layer known to the plug-in, which is required so that the plug-in\r
+        * can access the preferences of the application. Call this method first before using any\r
+        * of the others.\r
+        */\r
+       public void setApplicationLayer(DemoRecorderApplication appLayer);\r
+\r
+       /**\r
+        * Returns the name of the plug-in. Must not contain a "."\r
+        */\r
+       public String getName();\r
+       \r
+       /**\r
+        * Returns true if the plug-in is enabled (checked from the preferences of the app layer)\r
+        * @return true if the plug-in is enabled\r
+        */\r
+       public boolean isEnabled();\r
+       \r
+       /**\r
+        * Global preferences are preferences of a plug-in that are application-wide and not job-\r
+        * specific. They should be shown in a global preferences dialog.\r
+        * Use this method in order to tell the application layer and GUI which global settings your\r
+        * encoder plug-in offers, and set a reasonable default. Note that for the default-values being\r
+        * set you can either set to "true" or "false", any String (can be empty), or "filechooser" if\r
+        * you want the user to select a file. \r
+        * @return\r
+        */\r
+       public Properties getGlobalPreferences();\r
+       \r
+       /**\r
+        * In order to influence the order of settings being displayed to the user in a UI, return an array\r
+        * of all keys used in the Properties object returned in getGlobalPreferences(), with your desired\r
+        * order.\r
+        * @return\r
+        */\r
+       public String[] getGlobalPreferencesOrder();\r
+       \r
+       /**\r
+        * Here you can return a Properties object that contains keys for values that can be specific to each\r
+        * individual RecordJob. \r
+        * @return\r
+        */\r
+       public Properties getJobSpecificPreferences();\r
+       \r
+       /**\r
+        * In order to influence the order of job-specific settings being displayed to the user in a UI,\r
+        * return an array of all keys used in the Properties object returned in getJobSpecificPreferences(), with\r
+        * your desired order.\r
+        * @return\r
+        */\r
+       public String[] getJobSpecificPreferencesOrder();\r
+       \r
+       /**\r
+        * Will be called by the application layer when a job has been successfully recorded and moved to its\r
+        * final destination. This method has to perform the specific tasks your plug-in is supposed to do.\r
+        * @param job\r
+        * @throws EncoderPluginException\r
+        */\r
+       public void executeEncoder(RecordJob job) throws EncoderPluginException;\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java
new file mode 100644 (file)
index 0000000..70a98b3
--- /dev/null
@@ -0,0 +1,14 @@
+package com.nexuiz.demorecorder.application.plugins;\r
+\r
+public class EncoderPluginException extends Exception {\r
+\r
+       private static final long serialVersionUID = 2200737027476726978L;\r
+\r
+       public EncoderPluginException(String message) {\r
+               super(message);\r
+       }\r
+       \r
+       public EncoderPluginException(String message, Throwable t) {\r
+               super(message, t);\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java
new file mode 100644 (file)
index 0000000..d7188b6
--- /dev/null
@@ -0,0 +1,18 @@
+package com.nexuiz.demorecorder.main;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
+import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;\r
+import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;\r
+\r
+public class Driver {\r
+       \r
+       public static void main(String[] args) {\r
+               SwingGUI.setSystemLAF();\r
+               Thread.setDefaultUncaughtExceptionHandler(new ShowErrorDialogExceptionHandler());\r
+               DemoRecorderApplication appLayer = new DemoRecorderApplication();\r
+               \r
+               SwingGUI gui = new SwingGUI(appLayer);\r
+               appLayer.addUserInterfaceListener(gui);\r
+               \r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java
new file mode 100644 (file)
index 0000000..c8dc355
--- /dev/null
@@ -0,0 +1,20 @@
+package com.nexuiz.demorecorder.ui;\r
+\r
+import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
+\r
+public interface DemoRecorderUI {\r
+\r
+       /**\r
+        * Called by the application layer to inform the GUI about the fact that\r
+        * one or more properties of the given job changed (most likely the status).\r
+        * The given job might also be new to the GUI.\r
+        * @param job the affected job\r
+        */\r
+       public void RecordJobPropertiesChange(RecordJob job);\r
+       \r
+       /**\r
+        * Called by the application layer to inform the GUI that it finished\r
+        * recording all assigned jobs.\r
+        */\r
+       public void recordingFinished();\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java
new file mode 100644 (file)
index 0000000..adb0f99
--- /dev/null
@@ -0,0 +1,737 @@
+package com.nexuiz.demorecorder.ui.swinggui;\r
+\r
+import java.awt.Dimension;\r
+import java.awt.Frame;\r
+import java.awt.Toolkit;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.io.File;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+import java.util.Set;\r
+\r
+import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JComponent;\r
+import javax.swing.JDialog;\r
+import javax.swing.JFileChooser;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.JTextArea;\r
+import javax.swing.JTextField;\r
+import javax.swing.ScrollPaneConstants;\r
+import javax.swing.border.EmptyBorder;\r
+import javax.swing.filechooser.FileFilter;\r
+\r
+import net.miginfocom.swing.MigLayout;\r
+\r
+import org.jdesktop.swingx.JXTable;\r
+import org.jdesktop.swingx.JXTitledSeparator;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
+import com.nexuiz.demorecorder.application.NDRPreferences;\r
+import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;\r
+import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;\r
+\r
+/**\r
+ * Shows the dialog that allows to create a new job, create one from a template\r
+ * or edit an existing job.\r
+ */\r
+\r
+public class JobDialog extends JDialog implements ActionListener {\r
+       private static final long serialVersionUID = 6926246716804560522L;\r
+       public static final int CREATE_NEW_JOB = 0;\r
+       public static final int EDIT_JOB = 1;\r
+       public static final int CREATE_NEW_TEMPLATE = 2;\r
+       public static final int EDIT_TEMPLATE = 3;\r
+       public static final int CREATE_JOB_FROM_TEMPLATE = 4;\r
+\r
+       private DemoRecorderApplication appLayer;\r
+       private RecordJobTemplatesTableModel tableModel;\r
+//     private JXTable templatesTable;\r
+       private Frame parentFrame;\r
+       private int dialogType;\r
+       private RecordJob job = null;\r
+       private JPanel inputPanel;\r
+       private JPanel buttonPanel;\r
+\r
+       private JTextField templateNameField;\r
+       private JTextField templateSummaryField;\r
+       private JTextField enginePathField;\r
+       private JButton enginePathChooserButton;\r
+       private JTextField engineParameterField;\r
+       private JTextField dpVideoDirField;\r
+       private JButton dpVideoDirChooserButton;\r
+       private JTextField relativeDemoPathField;\r
+       private JTextField jobNameField;\r
+       private JTextField demoFileField;\r
+       private JButton demoFileChooserButton;\r
+       private JTextField startSecondField;\r
+       private JTextField endSecondField;\r
+       private JTextArea execBeforeField;\r
+       private JTextArea execAfterField;\r
+       private JTextField videoDestinationField;\r
+       private JButton videoDestinationChooserButton;\r
+       \r
+       private JButton createButton;\r
+       private JButton cancelButton;\r
+       \r
+       //file choosers\r
+       private JFileChooser enginePathFC;\r
+       private JFileChooser dpVideoDirFC;\r
+       private JFileChooser demoFileFC;\r
+       private JFileChooser videoDestinationFC;\r
+       \r
+       private FileFilter userDirFilter = new NexuizUserDirFilter();\r
+       \r
+       private Map<String, JComponent> pluginDialogSettings = new HashMap<String, JComponent>();\r
+\r
+       /**\r
+        * Constructor to create a dialog when creating a new job.\r
+        * @param owner\r
+        * @param appLayer\r
+        */\r
+       public JobDialog(Frame owner, DemoRecorderApplication appLayer) {\r
+               super(owner, true);\r
+               this.parentFrame = owner;\r
+               this.dialogType = CREATE_NEW_JOB;\r
+               this.appLayer = appLayer;\r
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
+\r
+               setTitle("Create new job");\r
+\r
+               this.setupLayout();\r
+       }\r
+       \r
+       /**\r
+        * Constructor to create a dialog when creating a new template.\r
+        * @param owner\r
+        * @param dialogType\r
+        * @param appLayer\r
+        */\r
+       public JobDialog(Frame owner, RecordJobTemplatesTableModel tableModel, JXTable templatesTable, DemoRecorderApplication appLayer) {\r
+               super(owner, true);\r
+               this.parentFrame = owner;\r
+               this.dialogType = CREATE_NEW_TEMPLATE;\r
+               this.tableModel = tableModel;\r
+               this.appLayer = appLayer;\r
+//             this.templatesTable = templatesTable; seems we don't need it\r
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
+               setTitle("Create new template");\r
+\r
+               this.setupLayout();\r
+       }\r
+       \r
+       /**\r
+        * Constructor to use when creating a new job from a template, or when editing a template.\r
+        * @param owner\r
+        * @param template\r
+        * @param type either CREATE_JOB_FROM_TEMPLATE or EDIT_TEMPLATE\r
+        */\r
+       public JobDialog(Frame owner, RecordJobTemplate template, DemoRecorderApplication appLayer, int type) {\r
+               super(owner, true);\r
+               this.parentFrame = owner;\r
+               \r
+               this.job = template;\r
+               this.appLayer = appLayer;\r
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
+               \r
+               if (type != CREATE_JOB_FROM_TEMPLATE && type != EDIT_TEMPLATE) {\r
+                       throw new RuntimeException("Illegal paraameter \"type\"");\r
+               }\r
+               \r
+               this.dialogType = type;\r
+               if (type == CREATE_JOB_FROM_TEMPLATE) {\r
+                       setTitle("Create job from template");\r
+               } else {\r
+                       setTitle("Edit template");\r
+               }\r
+\r
+               this.setupLayout();\r
+       }\r
+       \r
+       /**\r
+        * Constructor to create a dialog to be used when editing an existing job.\r
+        * @param owner\r
+        * @param job\r
+        */\r
+       public JobDialog(Frame owner, RecordJob job, DemoRecorderApplication appLayer) {\r
+               super(owner, true);\r
+               this.parentFrame = owner;\r
+               this.dialogType = EDIT_JOB;\r
+               this.appLayer = appLayer;\r
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
+\r
+               setTitle("Edit job");\r
+               this.job = job;\r
+\r
+               this.setupLayout();\r
+       }\r
+       \r
+       \r
+       \r
+       public void showDialog() {\r
+               this.pack();\r
+               Toolkit t = Toolkit.getDefaultToolkit();\r
+               Dimension screenSize = t.getScreenSize();\r
+               if (getHeight() > screenSize.height) {\r
+                       Dimension newPreferredSize = getPreferredSize();\r
+                       newPreferredSize.height = screenSize.height - 100;\r
+                       setPreferredSize(newPreferredSize);\r
+                       this.pack();\r
+               }\r
+               this.setLocationRelativeTo(this.parentFrame);\r
+               this.setVisible(true);\r
+       }\r
+\r
+       private void setupLayout() {\r
+//             setLayout(new MigLayout("wrap 1", "[grow,fill]", "[]20[]"));\r
+               setLayout(new MigLayout("wrap 1", "[grow,fill]", "[][]"));\r
+               this.setupInputMask();\r
+               this.setupButtonPart();\r
+\r
+       }\r
+\r
+       private void setupInputMask() {\r
+               inputPanel = new JPanel(new MigLayout("insets 0,wrap 3", "[][250::,grow,fill][30::]"));\r
+               JScrollPane inputScrollPane = new JScrollPane(inputPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);\r
+               inputScrollPane.setBorder(new EmptyBorder(0,0,0,0));\r
+               \r
+               JXTitledSeparator environmentHeading = new JXTitledSeparator("Environment settings");\r
+               inputPanel.add(environmentHeading, "span 3,grow");\r
+\r
+               this.setupTemplateNameAndSummary();\r
+               this.setupEnginePath();\r
+               this.setupEngineParameters();\r
+               this.setupDPVideoDir();\r
+               this.setupRelativeDemoPath();\r
+\r
+               JXTitledSeparator jobSettingsHeading = new JXTitledSeparator("Job settings");\r
+               inputPanel.add(jobSettingsHeading, "span 3,grow");\r
+\r
+               this.setupJobName();\r
+               this.setupDemoFile();\r
+               this.setupStartSecond();\r
+               this.setupEndSecond();\r
+               this.setupExecBefore();\r
+               this.setupExecAfter();\r
+               this.setupVideoDestination();\r
+               \r
+               this.setupPluginPreferences();\r
+\r
+               getContentPane().add(inputScrollPane);\r
+       }\r
+       \r
+       private void setupTemplateNameAndSummary() {\r
+               if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != EDIT_TEMPLATE) {\r
+                       return;\r
+               }\r
+               \r
+               //layout stuff\r
+               inputPanel.add(new JLabel("Template name:"));\r
+               templateNameField = new JTextField();\r
+               inputPanel.add(templateNameField, "wrap");\r
+               \r
+               inputPanel.add(new JLabel("Summary:"));\r
+               templateSummaryField = new JTextField();\r
+               inputPanel.add(templateSummaryField, "wrap");\r
+               \r
+               //UI logic stuff\r
+               if (this.dialogType == EDIT_TEMPLATE) {\r
+                       RecordJobTemplate template = (RecordJobTemplate) this.job;\r
+                       templateNameField.setText(template.getName());\r
+                       templateSummaryField.setText(template.getSummary());\r
+               }\r
+       }\r
+       \r
+       private void setupEnginePath() {\r
+               //layout stuff\r
+               inputPanel.add(new JLabel("Engine:"));\r
+               enginePathField = new JTextField();\r
+               enginePathField.setEditable(false);\r
+               inputPanel.add(enginePathField);\r
+               enginePathChooserButton = new FileChooserButton();\r
+               inputPanel.add(enginePathChooserButton);\r
+               \r
+               //UI logic stuff\r
+               this.enginePathFC = createConfiguredFileChooser();\r
+               enginePathChooserButton.addActionListener(this);\r
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       this.enginePathFC.setSelectedFile(this.job.getEnginePath());\r
+                       this.enginePathField.setText(this.job.getEnginePath().getAbsolutePath());\r
+               }\r
+       }\r
+       \r
+       private void setupEngineParameters() {\r
+               //layout stuff\r
+               inputPanel.add(new JLabel("Engine parameters:"));\r
+               engineParameterField = new JTextField();\r
+               inputPanel.add(engineParameterField, "wrap");\r
+               \r
+               //UI logic stuff\r
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       engineParameterField.setText(this.job.getEngineParameters());\r
+               }\r
+       }\r
+       \r
+       private void setupDPVideoDir() {\r
+               //layout stuff\r
+               inputPanel.add(new JLabel("DPVideo directory:"));\r
+               dpVideoDirField = new JTextField();\r
+               dpVideoDirField.setEditable(false);\r
+               inputPanel.add(dpVideoDirField);\r
+               dpVideoDirChooserButton = new FileChooserButton();\r
+               inputPanel.add(dpVideoDirChooserButton);\r
+               \r
+               //UI logic stuff\r
+               dpVideoDirChooserButton.addActionListener(this);\r
+               this.dpVideoDirFC = createConfiguredFileChooser();\r
+               this.dpVideoDirFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\r
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       this.dpVideoDirFC.setSelectedFile(this.job.getDpVideoPath());\r
+                       this.dpVideoDirField.setText(this.job.getDpVideoPath().getAbsolutePath());\r
+               }\r
+       }\r
+       \r
+       private void setupRelativeDemoPath() {\r
+               //layout stuff\r
+               inputPanel.add(new JLabel("Relative demo path:"));\r
+               relativeDemoPathField = new JTextField();\r
+               inputPanel.add(relativeDemoPathField, "wrap 20");\r
+               \r
+               //UI logic stuff\r
+               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE) {\r
+                       relativeDemoPathField.setText("demos");\r
+               }\r
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       relativeDemoPathField.setText(this.job.getRelativeDemoPath());\r
+               }\r
+       }\r
+       \r
+       private void setupJobName() {\r
+               inputPanel.add(new JLabel("Job name:"));\r
+               \r
+               jobNameField = new JTextField();\r
+               inputPanel.add(jobNameField, "wrap");\r
+               \r
+               //UI logic stuff\r
+               if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != CREATE_NEW_JOB) {\r
+                       jobNameField.setText(this.job.getJobName());\r
+               }\r
+       }\r
+       \r
+       private void setupDemoFile() {\r
+               String label;\r
+               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       label = "Demo file:";\r
+               } else {\r
+                       label = "Demo directory:";\r
+               }\r
+               \r
+               //layout stuff\r
+               inputPanel.add(new JLabel(label));\r
+               demoFileField = new JTextField();\r
+               demoFileField.setEditable(false);\r
+               inputPanel.add(demoFileField);\r
+               demoFileChooserButton = new FileChooserButton();\r
+               inputPanel.add(demoFileChooserButton);\r
+               \r
+               //UI logic stuff\r
+               this.demoFileFC = createConfiguredFileChooser();\r
+               demoFileChooserButton.addActionListener(this);\r
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                               this.demoFileFC.setCurrentDirectory(this.job.getDemoFile());\r
+                       } else {\r
+                               this.demoFileFC.setSelectedFile(this.job.getDemoFile());\r
+                       }\r
+                       \r
+                       this.demoFileField.setText(this.job.getDemoFile().getAbsolutePath());\r
+               }\r
+               \r
+               //only specify directories for templates\r
+               if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {\r
+                       this.demoFileFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\r
+               }\r
+       }\r
+       \r
+       private void setupStartSecond() {\r
+               //only exists for jobs, not for templates\r
+               if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {\r
+                       return;\r
+               }\r
+               \r
+               //layout stuff\r
+               inputPanel.add(new JLabel("Start second:"));\r
+               startSecondField = new JTextField();\r
+               inputPanel.add(startSecondField, "wrap");\r
+               \r
+               //UI logic stuff\r
+               if (this.dialogType == EDIT_JOB) {\r
+                       startSecondField.setText(String.valueOf( this.job.getStartSecond() ));\r
+               }\r
+       }\r
+       \r
+       private void setupEndSecond() {\r
+               //only exists for jobs, not for templates\r
+               if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {\r
+                       return;\r
+               }\r
+               \r
+               //layout stuff\r
+               inputPanel.add(new JLabel("End second:"));\r
+               endSecondField = new JTextField();\r
+               inputPanel.add(endSecondField, "wrap");\r
+               \r
+               //UI logic stuff\r
+               if (this.dialogType == EDIT_JOB) {\r
+                       endSecondField.setText(String.valueOf( this.job.getEndSecond() ));\r
+               }\r
+       }\r
+       \r
+       private void setupExecBefore() {\r
+               //layout stuff\r
+               inputPanel.add(new JLabel("Exec before capture:"));\r
+               execBeforeField = new JTextArea(3, 1);\r
+               inputPanel.add(new JScrollPane(execBeforeField), "wrap");\r
+               \r
+               //UI logic stuff\r
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       execBeforeField.setText(this.job.getExecuteBeforeCap());\r
+               }\r
+       }\r
+       \r
+       private void setupExecAfter() {\r
+               //layout stuff\r
+               inputPanel.add(new JLabel("Exec after capture:"));\r
+               execAfterField = new JTextArea(3, 1);\r
+               inputPanel.add(new JScrollPane(execAfterField), "wrap");\r
+               \r
+               //UI logic stuff\r
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       execAfterField.setText(this.job.getExecuteAfterCap());\r
+               }\r
+       }\r
+       \r
+       private void setupVideoDestination() {\r
+               //layout stuff\r
+               inputPanel.add(new JLabel("Video destination:"));\r
+               videoDestinationField = new JTextField();\r
+               videoDestinationField.setEditable(false);\r
+               inputPanel.add(videoDestinationField);\r
+               videoDestinationChooserButton = new FileChooserButton();\r
+               inputPanel.add(videoDestinationChooserButton, "wrap 20");\r
+               \r
+               //UI logic stuff\r
+               videoDestinationChooserButton.addActionListener(this);\r
+               this.videoDestinationFC = createConfiguredFileChooser();\r
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                               this.videoDestinationFC.setCurrentDirectory(this.job.getVideoDestination());\r
+                       } else {\r
+                               this.videoDestinationFC.setSelectedFile(this.job.getVideoDestination());\r
+                       }\r
+                       \r
+                       this.videoDestinationField.setText(this.job.getVideoDestination().getAbsolutePath());\r
+               }\r
+               if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {\r
+                       this.videoDestinationFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\r
+               }\r
+       }\r
+       \r
+       private void setupPluginPreferences() {\r
+               for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {\r
+                       String pluginName = plugin.getName();\r
+                       //only display settings if the plugin actually has any...\r
+                       Properties jobSpecificDefaultPluginPreferences = plugin.getJobSpecificPreferences();\r
+                       Properties jobPluginPreferences = null;\r
+                       if (this.job != null) {\r
+                               jobPluginPreferences = this.job.getEncoderPluginSettings(plugin);\r
+                       }\r
+                       if (jobSpecificDefaultPluginPreferences.size() > 0 && plugin.isEnabled()) {\r
+                               //add heading\r
+                               JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");\r
+                               inputPanel.add(pluginHeading, "span 3,grow");\r
+                               \r
+                               for (String pluginPreferenceKey : plugin.getJobSpecificPreferencesOrder()) {\r
+                                       String value = jobSpecificDefaultPluginPreferences.getProperty(pluginPreferenceKey);\r
+                                       if (this.job != null) {\r
+                                               if (jobPluginPreferences.containsKey(pluginPreferenceKey)) {\r
+                                                       value = jobPluginPreferences.getProperty(pluginPreferenceKey);\r
+                                               }\r
+                                       }\r
+                                       \r
+                                       this.setupSinglePluginSetting(plugin, pluginPreferenceKey, value);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+       \r
+       private void setupSinglePluginSetting(EncoderPlugin plugin, String key, String value) {\r
+               inputPanel.add(new JLabel(key + ":"));\r
+               \r
+               if (SwingGUIUtils.isBooleanValue(value)) {\r
+                       JCheckBox checkbox = new JCheckBox();\r
+                       checkbox.setSelected(Boolean.valueOf(value));\r
+                       inputPanel.add(checkbox, "wrap");\r
+                       this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), checkbox);\r
+               } else if (SwingGUIUtils.isFileChooser(value)) {\r
+                       final JFileChooser fc = new JFileChooser();\r
+                       fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);\r
+                       JButton fcButton = new JButton("...");\r
+                       final JTextField filePathField = new JTextField();\r
+                       filePathField.setEditable(false);\r
+                       inputPanel.add(filePathField);\r
+                       fcButton.addActionListener(new ActionListener() {\r
+                               @Override\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       int returnValue = fc.showOpenDialog(JobDialog.this);\r
+                                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
+                                               File selectedFile = fc.getSelectedFile();\r
+                                               filePathField.setText(selectedFile.getAbsolutePath());\r
+                                       }\r
+                               }\r
+                       });\r
+                       \r
+                       try {\r
+                               File selectedFile = new File(value);\r
+                               if (selectedFile.exists()) {\r
+                                       fc.setSelectedFile(selectedFile);\r
+                                       filePathField.setText(selectedFile.getAbsolutePath());\r
+                               }\r
+                       } catch (Throwable e) {}\r
+                       this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), fc);\r
+                       inputPanel.add(fcButton);\r
+               } else {\r
+                       //textfield\r
+                       JTextField textField = new JTextField();\r
+                       textField.setText(value);\r
+                       this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), textField);\r
+                       inputPanel.add(textField, "wrap");\r
+               }\r
+       }\r
+\r
+       private void setupButtonPart() {\r
+               String createButtonText;\r
+               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                       createButtonText = "Create";\r
+               } else {\r
+                       createButtonText = "Save";\r
+               }\r
+               buttonPanel = new JPanel(new MigLayout("insets 0"));\r
+               createButton = new JButton(createButtonText);\r
+               createButton.addActionListener(this);\r
+               cancelButton = new JButton("Cancel");\r
+               cancelButton.addActionListener(this);\r
+               \r
+               buttonPanel.add(createButton);\r
+               buttonPanel.add(cancelButton);\r
+\r
+               getContentPane().add(buttonPanel);\r
+       }\r
+       \r
+       \r
+       public void actionPerformed(ActionEvent e) {\r
+               if (e.getSource() == enginePathChooserButton) {\r
+                       int returnValue = this.enginePathFC.showOpenDialog(this);\r
+                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
+                               File selectedFile = this.enginePathFC.getSelectedFile();\r
+                               this.enginePathField.setText(selectedFile.getAbsolutePath());\r
+                       }\r
+               } else if (e.getSource() == dpVideoDirChooserButton) {\r
+                       int returnValue = this.dpVideoDirFC.showOpenDialog(this);\r
+                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
+                               File selectedFile = this.dpVideoDirFC.getSelectedFile();\r
+                               this.dpVideoDirField.setText(selectedFile.getAbsolutePath());\r
+                       }\r
+               } else if (e.getSource() == demoFileChooserButton) {\r
+                       int returnValue = this.demoFileFC.showOpenDialog(this);\r
+                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
+                               File selectedFile = this.demoFileFC.getSelectedFile();\r
+                               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
+                                       this.demoFileField.setText(DemoRecorderUtils.getJustFileNameOfPath(selectedFile));\r
+                               } else {\r
+                                       //template, show full path of directory\r
+                                       this.demoFileField.setText(selectedFile.getAbsolutePath());\r
+                               }\r
+                               \r
+                       }\r
+               } else if (e.getSource() == videoDestinationChooserButton) {\r
+                       int returnValue = this.videoDestinationFC.showSaveDialog(this);\r
+                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
+                               File selectedFile = this.videoDestinationFC.getSelectedFile();\r
+                               this.videoDestinationField.setText(selectedFile.getAbsolutePath());\r
+                       }\r
+               } else if (e.getSource() == createButton) {\r
+                       switch (this.dialogType) {\r
+                       case CREATE_NEW_JOB:\r
+                       case CREATE_JOB_FROM_TEMPLATE:\r
+                               this.requestNewRecordJob(); break;\r
+                       case CREATE_NEW_TEMPLATE:\r
+                               this.createNewTemplate();\r
+                               break;\r
+                       case EDIT_JOB:\r
+                               this.editJob();\r
+                               break;\r
+                       case EDIT_TEMPLATE:\r
+                               this.editTemplate();\r
+                               break;\r
+                       }\r
+               } else if (e.getSource() == cancelButton) {\r
+                       dispose();\r
+               }\r
+       }\r
+       \r
+       private void requestNewRecordJob() {\r
+               float startSecond, endSecond = -1;\r
+               try {\r
+                       startSecond = Float.valueOf(this.startSecondField.getText());\r
+                       endSecond = Float.valueOf(this.endSecondField.getText());\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);\r
+                       return;\r
+               }\r
+               \r
+               try {\r
+                       RecordJob j = this.appLayer.createRecordJob(\r
+                               this.jobNameField.getText(),\r
+                               this.enginePathFC.getSelectedFile(),\r
+                               this.engineParameterField.getText(),\r
+                               this.demoFileFC.getSelectedFile(),\r
+                               this.relativeDemoPathField.getText(),\r
+                               this.dpVideoDirFC.getSelectedFile(),\r
+                               this.videoDestinationFC.getSelectedFile(),\r
+                               this.execBeforeField.getText(),\r
+                               this.execAfterField.getText(),\r
+                               startSecond,\r
+                               endSecond\r
+                       );\r
+                       this.saveEncoderPluginSettings(j);\r
+                       dispose();\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(e);\r
+                       return;\r
+               }\r
+               \r
+       }\r
+       \r
+       private void editJob() {\r
+               float startSecond, endSecond = -1;\r
+               try {\r
+                       startSecond = Float.valueOf(this.startSecondField.getText());\r
+                       endSecond = Float.valueOf(this.endSecondField.getText());\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);\r
+                       return;\r
+               }\r
+               \r
+               try {\r
+                       this.job.setJobName(this.jobNameField.getText());\r
+                       this.job.setEnginePath(this.enginePathFC.getSelectedFile());\r
+                       this.job.setEngineParameters(this.engineParameterField.getText());\r
+                       this.job.setDemoFile(this.demoFileFC.getSelectedFile());\r
+                       this.job.setRelativeDemoPath(this.relativeDemoPathField.getText());\r
+                       this.job.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());\r
+                       this.job.setVideoDestination(this.videoDestinationFC.getSelectedFile());\r
+                       this.job.setExecuteBeforeCap(this.execBeforeField.getText());\r
+                       this.job.setExecuteAfterCap(this.execAfterField.getText());\r
+                       this.job.setStartSecond(startSecond);\r
+                       this.job.setEndSecond(endSecond);\r
+                       this.saveEncoderPluginSettings(this.job);\r
+                       this.appLayer.fireUserInterfaceUpdate(this.job);\r
+                       dispose();\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(e);\r
+                       return;\r
+               }\r
+               \r
+       }\r
+       \r
+       private void createNewTemplate() {\r
+               try {\r
+                       RecordJobTemplate templ = new RecordJobTemplate(\r
+                               this.templateNameField.getText(),\r
+                               this.templateSummaryField.getText(),\r
+                               this.jobNameField.getText(),\r
+                               this.enginePathFC.getSelectedFile(),\r
+                               this.engineParameterField.getText(),\r
+                               this.demoFileFC.getSelectedFile(),\r
+                               this.relativeDemoPathField.getText(),\r
+                               this.dpVideoDirFC.getSelectedFile(),\r
+                               this.videoDestinationFC.getSelectedFile(),\r
+                               this.execBeforeField.getText(),\r
+                               this.execAfterField.getText()\r
+                       );\r
+                       this.saveEncoderPluginSettings(templ);\r
+                       this.tableModel.addRecordJobTemplate(templ);\r
+                       dispose();\r
+               } catch (NullPointerException e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that you chose a file/directory in each case!", e, true);\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(e);\r
+                       return;\r
+               }\r
+       }\r
+       \r
+       private void editTemplate() {\r
+               try {\r
+                       RecordJobTemplate template = (RecordJobTemplate) this.job;\r
+                       template.setName(this.templateNameField.getText());\r
+                       template.setSummary(this.templateSummaryField.getText());\r
+                       template.setJobName(this.jobNameField.getText());\r
+                       template.setEnginePath(this.enginePathFC.getSelectedFile());\r
+                       template.setEngineParameters(this.engineParameterField.getText());\r
+                       template.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());\r
+                       template.setRelativeDemoPath(this.relativeDemoPathField.getText());\r
+                       template.setDemoFile(this.demoFileFC.getSelectedFile());\r
+                       template.setExecuteBeforeCap(this.execBeforeField.getText());\r
+                       template.setExecuteAfterCap(this.execAfterField.getText());\r
+                       template.setVideoDestination(this.videoDestinationFC.getSelectedFile());\r
+                       this.saveEncoderPluginSettings(template);\r
+                       dispose();\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(e);\r
+                       return;\r
+               }\r
+       }\r
+       \r
+       private void saveEncoderPluginSettings(RecordJob job) {\r
+               Set<String> keys = this.pluginDialogSettings.keySet();\r
+               //remember, the keys are concatenated, containing both the category and actual key \r
+               for (String key : keys) {\r
+                       JComponent component = this.pluginDialogSettings.get(key);\r
+                       if (component instanceof JCheckBox) {\r
+                               JCheckBox checkbox = (JCheckBox) component;\r
+                               job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), String.valueOf(checkbox.isSelected()));\r
+                       } else if (component instanceof JFileChooser) {\r
+                               JFileChooser fileChooser = (JFileChooser) component;\r
+                               if (fileChooser.getSelectedFile() != null) {\r
+                                       String path = fileChooser.getSelectedFile().getAbsolutePath();\r
+                                       job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);\r
+                               }\r
+                       } else if (component instanceof JTextField) {\r
+                               JTextField textField = (JTextField) component;\r
+                               job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());\r
+                       }\r
+               }\r
+       }\r
+       \r
+       private static class FileChooserButton extends JButton {\r
+               private static final long serialVersionUID = 1335571540372856959L;\r
+               public FileChooserButton() {\r
+                       super("...");\r
+               }\r
+       }\r
+       \r
+       private JFileChooser createConfiguredFileChooser() {\r
+               JFileChooser fc = new JFileChooser();\r
+               fc.setFileHidingEnabled(false);\r
+               fc.setFileFilter(userDirFilter);\r
+               return fc;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java
new file mode 100644 (file)
index 0000000..e4864b2
--- /dev/null
@@ -0,0 +1,31 @@
+package com.nexuiz.demorecorder.ui.swinggui;\r
+\r
+import java.io.File;\r
+\r
+import javax.swing.filechooser.FileFilter;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
+\r
+/**\r
+ * File filter that makes sure that the hidden .nexuiz directory is being shown in the\r
+ * file dialog, but other hidden directories are not.\r
+ */\r
+public class NexuizUserDirFilter extends FileFilter {\r
+\r
+       @Override\r
+       public boolean accept(File f) {\r
+               if (f.isHidden()) {\r
+                       if (f.isDirectory() && DemoRecorderUtils.getJustFileNameOfPath(f).equals(".nexuiz")) {\r
+                               return true;\r
+                       }\r
+                       return false; //don't show other hidden directories/files\r
+               }\r
+               return true;\r
+       }\r
+\r
+       @Override\r
+       public String getDescription() {\r
+               return null;\r
+       }\r
+\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java
new file mode 100644 (file)
index 0000000..7af4a2b
--- /dev/null
@@ -0,0 +1,195 @@
+package com.nexuiz.demorecorder.ui.swinggui;\r
+\r
+import java.awt.Frame;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.io.File;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+import java.util.Set;\r
+\r
+import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JComponent;\r
+import javax.swing.JDialog;\r
+import javax.swing.JFileChooser;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JTextField;\r
+\r
+import net.miginfocom.swing.MigLayout;\r
+\r
+import org.jdesktop.swingx.JXTitledSeparator;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
+import com.nexuiz.demorecorder.application.NDRPreferences;\r
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
+import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;\r
+\r
+public class PreferencesDialog extends JDialog implements ActionListener {\r
+\r
+       private static final long serialVersionUID = 7328399646538571333L;\r
+       private Frame parentFrame;\r
+       private DemoRecorderApplication appLayer;\r
+       private NDRPreferences preferences;\r
+       private Map<String, JComponent> dialogSettings;\r
+       \r
+       private JButton saveButton = new JButton("Save");\r
+       private JButton cancelButton = new JButton("Cancel");\r
+       \r
+       public PreferencesDialog(Frame owner, DemoRecorderApplication appLayer) {\r
+               super(owner, true);\r
+               this.parentFrame = owner;\r
+               this.appLayer = appLayer;\r
+               this.preferences = appLayer.getPreferences();\r
+               this.dialogSettings = new HashMap<String, JComponent>();\r
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
+\r
+               setTitle("Preferences");\r
+\r
+               this.setupLayout();\r
+       }\r
+\r
+       private void setupLayout() {\r
+               setLayout(new MigLayout("wrap 2", "[][::150,fill]"));\r
+               \r
+               //add heading\r
+               JXTitledSeparator applicationHeading = new JXTitledSeparator("Application settings");\r
+               getContentPane().add(applicationHeading, "span 2,grow");\r
+               \r
+               for (int i = 0; i < DemoRecorderApplication.Preferences.PREFERENCES_ORDER.length; i++) {\r
+                       String currentSetting = DemoRecorderApplication.Preferences.PREFERENCES_ORDER[i];\r
+                       if (this.preferences.getProperty(NDRPreferences.MAIN_APPLICATION, currentSetting) != null) {\r
+                               this.setupSingleSetting(NDRPreferences.MAIN_APPLICATION, currentSetting);\r
+                       }\r
+               }\r
+               \r
+               //add plugin settings\r
+               for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {\r
+                       String pluginName = plugin.getName();\r
+                       //only display settings if the plugin actually has any...\r
+                       Properties pluginPreferences = plugin.getGlobalPreferences();\r
+                       if (pluginPreferences.size() > 0) {\r
+                               //add heading\r
+                               JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");\r
+                               getContentPane().add(pluginHeading, "span 2,grow");\r
+                               \r
+                               for (String pluginKey : plugin.getGlobalPreferencesOrder()) {\r
+                                       if (this.preferences.getProperty(pluginName, pluginKey) != null) {\r
+                                               this.setupSingleSetting(pluginName, pluginKey);\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               JPanel buttonPanel = new JPanel();\r
+               buttonPanel.add(saveButton);\r
+               buttonPanel.add(cancelButton);\r
+               saveButton.addActionListener(this);\r
+               cancelButton.addActionListener(this);\r
+               getContentPane().add(buttonPanel, "span 2");\r
+       }\r
+       \r
+       private void setupSingleSetting(String category, String setting) {\r
+               getContentPane().add(new JLabel(setting + ":"));\r
+               \r
+               String value = this.preferences.getProperty(category, setting);\r
+               if (SwingGUIUtils.isBooleanValue(value)) {\r
+                       JCheckBox checkbox = new JCheckBox();\r
+                       this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), checkbox);\r
+                       getContentPane().add(checkbox);\r
+               } else if (SwingGUIUtils.isFileChooser(value)) {\r
+                       final JFileChooser fc = new JFileChooser();\r
+                       fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);\r
+                       JButton fcButton = new JButton("...");\r
+                       fcButton.addActionListener(new ActionListener() {\r
+                               @Override\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       fc.showOpenDialog(PreferencesDialog.this);\r
+                               }\r
+                       });\r
+                       this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), fc);\r
+                       getContentPane().add(fcButton);\r
+               } else {\r
+                       JTextField textField = new JTextField();\r
+                       this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), textField);\r
+                       getContentPane().add(textField);\r
+               }\r
+       }\r
+       \r
+       \r
+       \r
+       public void showDialog() {\r
+               this.loadSettings();\r
+               this.pack();\r
+               this.setLocationRelativeTo(this.parentFrame);\r
+               setResizable(false);\r
+               this.setVisible(true);\r
+       }\r
+       \r
+       /**\r
+        * Loads the settings from the application layer (and global plug-in settings) to the form.\r
+        */\r
+       private void loadSettings() {\r
+               Set<Object> keys = this.preferences.keySet();\r
+               for (Object keyObj : keys) {\r
+                       String concatenatedKey = (String) keyObj;\r
+                       String value;\r
+                       JComponent component = null;\r
+                       if ((value = this.preferences.getProperty(concatenatedKey)) != null) {\r
+                               if (SwingGUIUtils.isBooleanValue(value)) {\r
+                                       component = this.dialogSettings.get(concatenatedKey);\r
+                                       if (component != null) {\r
+                                               ((JCheckBox) component).setSelected(Boolean.valueOf(value));\r
+                                       }\r
+                               } else if (SwingGUIUtils.isFileChooser(value)) {\r
+                                       component = this.dialogSettings.get(concatenatedKey);\r
+                                       try {\r
+                                               File selectedFile = new File(value);\r
+                                               if (selectedFile.exists() && component != null) {\r
+                                                       ((JFileChooser) component).setSelectedFile(selectedFile);\r
+                                               }\r
+                                       } catch (Throwable e) {}\r
+                                       \r
+                               } else {\r
+                                       component = this.dialogSettings.get(concatenatedKey);\r
+                                       if (component != null) {\r
+                                               ((JTextField) component).setText(value);\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public void actionPerformed(ActionEvent e) {\r
+               if (e.getSource() == cancelButton) {\r
+                       this.setVisible(false);\r
+               } else if (e.getSource() == saveButton) {\r
+                       this.saveSettings();\r
+               }\r
+       }\r
+\r
+       private void saveSettings() {\r
+               Set<String> keys = this.dialogSettings.keySet();\r
+               //remember, the keys are concatenated, containing both the category and actual key \r
+               for (String key : keys) {\r
+                       JComponent component = this.dialogSettings.get(key);\r
+                       if (component instanceof JCheckBox) {\r
+                               JCheckBox checkbox = (JCheckBox) component;\r
+                               this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), checkbox.isSelected());\r
+                       } else if (component instanceof JFileChooser) {\r
+                               JFileChooser fileChooser = (JFileChooser) component;\r
+                               if (fileChooser.getSelectedFile() != null) {\r
+                                       String path = fileChooser.getSelectedFile().getAbsolutePath();\r
+                                       this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);\r
+                               }\r
+                       } else if (component instanceof JTextField) {\r
+                               JTextField textField = (JTextField) component;\r
+                               this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());\r
+                       }\r
+               }\r
+               this.setVisible(false);\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java
new file mode 100644 (file)
index 0000000..89ab61c
--- /dev/null
@@ -0,0 +1,113 @@
+package com.nexuiz.demorecorder.ui.swinggui;\r
+\r
+import java.io.File;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderException;\r
+import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
+\r
+public class RecordJobTemplate extends RecordJob {\r
+\r
+       private static final long serialVersionUID = 8311386509410161395L;\r
+       private String templateName;\r
+       private String summary;\r
+\r
+       public RecordJobTemplate(\r
+               String templateName,\r
+               String summary,\r
+               String jobName,\r
+               File enginePath,\r
+               String engineParameters,\r
+               File demoFile,\r
+               String relativeDemoPath,\r
+               File dpVideoPath,\r
+               File videoDestination,\r
+               String executeBeforeCap,\r
+               String executeAfterCap\r
+               ) {\r
+               super();\r
+               \r
+               /*\r
+                * Differences to jobs:\r
+                * - name and summary exist\r
+                * - "Demo file:" -> "Demo directory:"\r
+                * - no start/end second\r
+                */\r
+               \r
+               if (templateName == null || summary == null || jobName == null || enginePath == null || engineParameters == null || \r
+                               demoFile == null || relativeDemoPath == null || dpVideoPath == null || videoDestination == null \r
+                               || executeBeforeCap == null || executeAfterCap == null) {\r
+                       throw new DemoRecorderException("Error: Make sure that you filled the necessary fields! (file choosers!)");\r
+               }\r
+               \r
+               this.templateName = templateName;\r
+               this.summary = summary;\r
+               this.jobName = jobName;\r
+               this.enginePath = enginePath;\r
+               this.engineParameters = engineParameters;\r
+               this.demoFile = demoFile;\r
+               this.relativeDemoPath = relativeDemoPath;\r
+               this.dpVideoPath = dpVideoPath;\r
+               this.videoDestination = videoDestination;\r
+               this.executeBeforeCap = executeBeforeCap;\r
+               this.executeAfterCap = executeAfterCap;\r
+       }\r
+\r
+       public String getName() {\r
+               return templateName;\r
+       }\r
+\r
+       public String getSummary() {\r
+               return summary;\r
+       }\r
+       \r
+       public void setName(String name) {\r
+               this.templateName = name;\r
+       }\r
+\r
+       public void setSummary(String summary) {\r
+               this.summary = summary;\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        * Overwrite this method because here we want to do the read/write test for the path directly\r
+        * (as this one already is the directory), and not its parent directory.\r
+        * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setDemoFile(java.io.File)\r
+        */\r
+       public void setDemoFile(File demoFile) {\r
+               if (demoFile == null || !demoFile.exists()) {\r
+                       throw new DemoRecorderException("Could not locate demo file!");\r
+               }\r
+               if (!doReadWriteTest(demoFile)) {\r
+                       throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");\r
+               }\r
+               this.demoFile = demoFile.getAbsoluteFile();\r
+       }\r
+       \r
+       /*\r
+        * (non-Javadoc)\r
+        * Overwrite this method because here we want to do the read/write test for the path directly\r
+        * (as this one already is the directory), and not its parent directory.\r
+        * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setVideoDestination(java.io.File)\r
+        */\r
+       public void setVideoDestination(File videoDestination) {\r
+               //keep in mind, here videoDestination points to the destination directory, not the destination file\r
+               if (videoDestination == null || !videoDestination.isDirectory()) {\r
+                       throw new DemoRecorderException("Could not locate the specified video destination directory");\r
+               }\r
+               \r
+               if (!this.doReadWriteTest(videoDestination)) {\r
+                       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");\r
+               }\r
+               \r
+               this.videoDestination = videoDestination.getAbsoluteFile();\r
+       }\r
+       \r
+       public String getJobName() {\r
+               return this.jobName;\r
+       }\r
+       \r
+       public void setJobName(String jobName) {\r
+               this.jobName = jobName;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java
new file mode 100644 (file)
index 0000000..ed33c9c
--- /dev/null
@@ -0,0 +1,86 @@
+package com.nexuiz.demorecorder.ui.swinggui;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Component;\r
+import java.awt.Graphics;\r
+import java.awt.SystemColor;\r
+\r
+import javax.swing.BorderFactory;\r
+import javax.swing.Icon;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+\r
+public class StatusBar extends JPanel {\r
+\r
+       private static final long serialVersionUID = -1471757496863555741L;\r
+       private JLabel currentActivity = null;\r
+       \r
+       private static final String STATE_IDLE = "Idle";\r
+       private static final String STATE_WORKING = "Working";\r
+\r
+       public StatusBar() {\r
+               BorderLayout borderLayout = new BorderLayout(0, 0);\r
+               setLayout(borderLayout);\r
+               JPanel rightPanel = new JPanel(new BorderLayout());\r
+               rightPanel.add(new JLabel(new AngledLinesWindowsCornerIcon()), BorderLayout.SOUTH);\r
+               rightPanel.setOpaque(false);\r
+\r
+               add(rightPanel, BorderLayout.EAST);\r
+\r
+               this.currentActivity = new JLabel("Idle");\r
+               add(this.currentActivity, BorderLayout.WEST);\r
+               setBackground(SystemColor.control);\r
+               setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.black));\r
+       }\r
+       \r
+       /**\r
+        * Sets the state/display of the status bar to "idle" (false) or "working" (true).\r
+        * @param state\r
+        */\r
+       public void showState(boolean state) {\r
+               if (state) {\r
+                       currentActivity.setText(STATE_WORKING);\r
+               } else {\r
+                       currentActivity.setText(STATE_IDLE);\r
+               }\r
+       }\r
+\r
+       private static class AngledLinesWindowsCornerIcon implements Icon {\r
+               private static final Color WHITE_LINE_COLOR = new Color(255, 255, 255);\r
+\r
+               private static final Color GRAY_LINE_COLOR = new Color(172, 168, 153);\r
+               private static final int WIDTH = 13;\r
+\r
+               private static final int HEIGHT = 13;\r
+\r
+               public int getIconHeight() {\r
+                       return HEIGHT;\r
+               }\r
+\r
+               public int getIconWidth() {\r
+                       return WIDTH;\r
+               }\r
+\r
+               public void paintIcon(Component c, Graphics g, int x, int y) {\r
+\r
+                       g.setColor(WHITE_LINE_COLOR);\r
+                       g.drawLine(0, 12, 12, 0);\r
+                       g.drawLine(5, 12, 12, 5);\r
+                       g.drawLine(10, 12, 12, 10);\r
+\r
+                       g.setColor(GRAY_LINE_COLOR);\r
+                       g.drawLine(1, 12, 12, 1);\r
+                       g.drawLine(2, 12, 12, 2);\r
+                       g.drawLine(3, 12, 12, 3);\r
+\r
+                       g.drawLine(6, 12, 12, 6);\r
+                       g.drawLine(7, 12, 12, 7);\r
+                       g.drawLine(8, 12, 12, 8);\r
+\r
+                       g.drawLine(11, 12, 12, 11);\r
+                       g.drawLine(12, 12, 12, 12);\r
+\r
+               }\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java
new file mode 100644 (file)
index 0000000..3b5f5de
--- /dev/null
@@ -0,0 +1,1109 @@
+package com.nexuiz.demorecorder.ui.swinggui;\r
+\r
+import java.awt.Container;\r
+import java.awt.Dimension;\r
+import java.awt.Point;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.event.MouseEvent;\r
+import java.awt.event.MouseListener;\r
+import java.awt.event.WindowEvent;\r
+import java.awt.event.WindowListener;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.ObjectInputStream;\r
+import java.io.ObjectOutputStream;\r
+import java.net.URL;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import javax.help.HelpBroker;\r
+import javax.help.HelpSet;\r
+import javax.swing.BorderFactory;\r
+import javax.swing.Icon;\r
+import javax.swing.ImageIcon;\r
+import javax.swing.JButton;\r
+import javax.swing.JFileChooser;\r
+import javax.swing.JFrame;\r
+import javax.swing.JMenu;\r
+import javax.swing.JMenuBar;\r
+import javax.swing.JMenuItem;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JPopupMenu;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.JTable;\r
+import javax.swing.UIManager;\r
+import javax.swing.border.TitledBorder;\r
+\r
+import net.miginfocom.swing.MigLayout;\r
+\r
+import org.jdesktop.swingx.JXTable;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
+import com.nexuiz.demorecorder.application.NDRPreferences;\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;\r
+import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
+import com.nexuiz.demorecorder.application.jobs.RecordJob.State;\r
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
+import com.nexuiz.demorecorder.ui.DemoRecorderUI;\r
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;\r
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobsTableModel;\r
+import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;\r
+import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties;\r
+import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties.XTableState;\r
+\r
+public class SwingGUI extends JFrame implements WindowListener, DemoRecorderUI {\r
+       \r
+       private static final long serialVersionUID = -7287303462488231068L;\r
+       public static final String JOB_TABLE_PREFERENCES_FILENAME = "jobsTable.pref";\r
+       public static final String TEMPLATE_TABLE_PREFERENCES_FILENAME = "templatesTable.pref";\r
+       public static final String TEMPLATE_TABLE_CONTENT_FILENAME = "templates.dat";\r
+\r
+       private DemoRecorderApplication appLayer;\r
+       private PreferencesDialog preferencesDialog;\r
+       \r
+       private JXTable jobsTable = null;\r
+       private JPopupMenu jobsTablePopupMenu;\r
+       private ActionListener jobButtonActionListener = new JobButtonActionListener();\r
+       private MouseListener jobsTableMouseListener = new JobsTableMouseListener();\r
+       \r
+       private JXTable templatesTable = null;\r
+       private JPopupMenu templatesTablePopupMenu;\r
+       private ActionListener templateButtonActionListener = new TemplateButtonActionListener();\r
+       private MouseListener templatesTableMouseListener = new TemplatesTableMouseListener();\r
+       \r
+       private ActionListener recordButtonActionListener = new RecordButtonActionListener();\r
+       \r
+       private static final String LABEL_JOB_CREATE = "Create";\r
+       private static final String LABEL_JOB_CREATE_FROM_TEMPL = "Create from template";\r
+       private static final String LABEL_JOB_DELETE = "Delete";\r
+       private static final String LABEL_JOB_CLEAR = "Clear";\r
+       private static final String LABEL_JOB_EDIT = "Edit job";\r
+       private static final String LABEL_JOB_DUPLICATE = "Duplicate job";\r
+       private static final String LABEL_JOB_START = "Start job";\r
+       private static final String LABEL_JOB_SHOWERROR = "Show error message";\r
+       private static final String LABEL_JOB_RESET_STATE_WAITING = "Reset job status to 'waiting'";\r
+       private static final String LABEL_JOB_RESET_STATE_DONE = "Reset job status to 'done'";\r
+       \r
+       private static final String LABEL_TEMPL_CREATE = "Create";\r
+       private static final String LABEL_TEMPL_CREATE_FROM_JOB = "Create from job";\r
+       private static final String LABEL_TEMPL_DELETE = "Delete";\r
+       private static final String LABEL_TEMPL_CLEAR = "Clear";\r
+       private static final String LABEL_TEMPL_EDIT = "Edit template";\r
+       private static final String LABEL_TEMPL_DUPLICATE = "Duplicate template";\r
+       \r
+       private ActionListener menuButtonActionListener = new MenuButtonActionListener();\r
+       private JMenuItem fileLoadQueue = new JMenuItem("Load job queue", getIcon("fileopen.png"));\r
+       private JMenuItem fileSaveQueue = new JMenuItem("Save job queue", getIcon("filesave.png"));\r
+       private JMenuItem filePreferences = new JMenuItem("Preferences", getIcon("advanced.png"));\r
+       private JMenuItem fileExit = new JMenuItem("Exit", getIcon("exit.png"));\r
+       private JMenuItem helpHelp = new JMenuItem("Show help", getIcon("help.png"));\r
+       private JMenuItem helpAbout = new JMenuItem("About", getIcon("info.png"));\r
+       private JFileChooser jobQueueSaveAsFC = new JFileChooser();\r
+       \r
+       private JButton jobs_create = new JButton(LABEL_JOB_CREATE, getIcon("edit_add.png"));\r
+       private JButton jobs_createFromTempl = new JButton(LABEL_JOB_CREATE_FROM_TEMPL, getIcon("view_right_p.png"));\r
+       private JButton jobs_delete = new JButton(LABEL_JOB_DELETE, getIcon("editdelete.png"));\r
+       private JButton jobs_clear = new JButton(LABEL_JOB_CLEAR, getIcon("editclear.png"));\r
+       private JMenuItem jobs_contextmenu_edit = new JMenuItem(LABEL_JOB_EDIT, getIcon("edit.png"));\r
+       private JMenuItem jobs_contextmenu_duplicate = new JMenuItem(LABEL_JOB_DUPLICATE, getIcon("editcopy.png"));\r
+       private JMenuItem jobs_contextmenu_delete = new JMenuItem(LABEL_JOB_DELETE, getIcon("editdelete.png"));\r
+       private JMenuItem jobs_contextmenu_start = new JMenuItem(LABEL_JOB_START, getIcon("player_play.png"));\r
+       private JMenuItem jobs_contextmenu_showerror = new JMenuItem(LABEL_JOB_SHOWERROR, getIcon("status_unknown.png"));\r
+       private JMenuItem jobs_contextmenu_resetstate_waiting = new JMenuItem(LABEL_JOB_RESET_STATE_WAITING, getIcon("quick_restart.png"));\r
+       private JMenuItem jobs_contextmenu_resetstate_done = new JMenuItem(LABEL_JOB_RESET_STATE_DONE, getIcon("quick_restart_blue.png"));\r
+       private List<JMenuItem> jobs_contextmenu_runPluginMenuItems = new ArrayList<JMenuItem>();\r
+       \r
+       private JButton templ_create = new JButton(LABEL_TEMPL_CREATE, getIcon("edit_add.png"));\r
+       private JButton templ_createFromJob = new JButton(LABEL_TEMPL_CREATE_FROM_JOB, getIcon("view_right_p.png"));\r
+       private JButton templ_delete = new JButton(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));\r
+       private JButton templ_clear = new JButton(LABEL_TEMPL_CLEAR, getIcon("editclear.png"));\r
+       private JMenuItem templ_contextmenu_edit = new JMenuItem(LABEL_TEMPL_EDIT, getIcon("edit.png"));\r
+       private JMenuItem templ_contextmenu_duplicate = new JMenuItem(LABEL_TEMPL_DUPLICATE, getIcon("editcopy.png"));\r
+       private JMenuItem templ_contextmenu_delete = new JMenuItem(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));\r
+       \r
+       private static final String PROCESSING_START = "Start processing";\r
+       private static final String PROCESSING_STOP_NOW = "Stop processing";\r
+       private static final String PROCESSING_STOP_LATER = "Processing will stop after current job finished";\r
+       private JButton processing_start = new JButton(PROCESSING_START, getIcon("player_play.png"));\r
+       private JButton processing_stop = new JButton(PROCESSING_STOP_NOW, getIcon("player_pause.png"));\r
+       \r
+       private StatusBar statusBar = new StatusBar();\r
+       \r
+       private static HelpBroker mainHelpBroker = null;\r
+       private static final String mainHelpSetName = "help/DemoRecorderHelp.hs";\r
+\r
+       public SwingGUI(DemoRecorderApplication appLayer) {\r
+               super("Nexuiz Demo Recorder v0.2");\r
+               addWindowListener(this);\r
+\r
+               this.appLayer = appLayer;\r
+\r
+               this.setupLayout();\r
+               this.setupHelp();\r
+               this.preferencesDialog = new PreferencesDialog(this, appLayer);\r
+\r
+               setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);\r
+               // Display the window.\r
+               pack();\r
+               setVisible(true);\r
+               //now that we have the GUI we can set the parent window for the error dialog\r
+               ShowErrorDialogExceptionHandler.setParentWindow(this);\r
+       }\r
+\r
+       private void setupHelp() {\r
+               if (mainHelpBroker == null){\r
+                       HelpSet mainHelpSet = null;\r
+\r
+                       try {\r
+                               URL hsURL = HelpSet.findHelpSet(null, mainHelpSetName);\r
+                               mainHelpSet = new HelpSet(null, hsURL);\r
+                       } catch (Exception e) {\r
+                               DemoRecorderUtils.showNonCriticalErrorDialog("Could not properly create the help", e, true);\r
+                       }\r
+\r
+                       if (mainHelpSet != null)\r
+                               mainHelpBroker = mainHelpSet.createHelpBroker();\r
+                       }\r
+       }\r
+\r
+       private void setupLayout() {\r
+               setLayout(new MigLayout("wrap 1,insets 10", "[400:700:,grow,fill]",\r
+                               "[grow,fill][grow,fill][][]"));\r
+               Container contentPane = getContentPane();\r
+               setJMenuBar(this.buildMenu());\r
+\r
+               this.setupTemplatePanel();\r
+               this.setupJobPanel();\r
+               this.setupRecordPanel();\r
+\r
+               contentPane.add(statusBar, "south,height 23::");\r
+       }\r
+\r
+       private void setupTemplatePanel() {\r
+               JPanel templatePanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));\r
+               TitledBorder templatePanelTitle = BorderFactory.createTitledBorder("Templates");\r
+               templatePanel.setBorder(templatePanelTitle);\r
+               getContentPane().add(templatePanel);\r
+               \r
+               this.setupTemplatesTable();\r
+               this.loadTableStates(this.templatesTable);\r
+               JScrollPane templateScrollPane = new JScrollPane(templatesTable);\r
+               templatePanel.add(templateScrollPane);\r
+               \r
+               this.templ_create.addActionListener(this.templateButtonActionListener);\r
+               this.templ_createFromJob.addActionListener(this.templateButtonActionListener);\r
+               this.templ_delete.addActionListener(this.templateButtonActionListener);\r
+               this.templ_clear.addActionListener(this.templateButtonActionListener);\r
+               \r
+               this.templ_contextmenu_edit.addActionListener(this.templateButtonActionListener);\r
+               this.templ_contextmenu_duplicate.addActionListener(this.templateButtonActionListener);\r
+               this.templ_contextmenu_delete.addActionListener(this.templateButtonActionListener);\r
+               \r
+               this.configureTableButtons();\r
+               \r
+               JPanel templateControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));\r
+               templateControlButtonPanel.add(this.templ_create);\r
+               templateControlButtonPanel.add(this.templ_createFromJob);\r
+               templateControlButtonPanel.add(this.templ_delete);\r
+               templateControlButtonPanel.add(this.templ_clear);\r
+               templatePanel.add(templateControlButtonPanel);\r
+       }\r
+\r
+       private void setupJobPanel() {\r
+               JPanel jobPanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));\r
+               TitledBorder jobPanelTitle = BorderFactory.createTitledBorder("Jobs");\r
+               jobPanel.setBorder(jobPanelTitle);\r
+               getContentPane().add(jobPanel);\r
+\r
+               this.setupJobsTable();\r
+               this.loadTableStates(this.jobsTable);\r
+               \r
+               JScrollPane jobScrollPane = new JScrollPane(jobsTable);\r
+               jobPanel.add(jobScrollPane);\r
+               \r
+               this.jobs_create.addActionListener(this.jobButtonActionListener);\r
+               this.jobs_createFromTempl.addActionListener(this.jobButtonActionListener);\r
+               this.jobs_delete.addActionListener(this.jobButtonActionListener);\r
+               this.jobs_clear.addActionListener(this.jobButtonActionListener);\r
+               \r
+               this.jobs_contextmenu_edit.addActionListener(this.jobButtonActionListener);\r
+               this.jobs_contextmenu_duplicate.addActionListener(this.jobButtonActionListener);\r
+               this.jobs_contextmenu_delete.addActionListener(this.jobButtonActionListener);\r
+               this.jobs_contextmenu_start.addActionListener(this.jobButtonActionListener);\r
+               this.jobs_contextmenu_showerror.addActionListener(this.jobButtonActionListener);\r
+               this.jobs_contextmenu_resetstate_waiting.addActionListener(this.jobButtonActionListener);\r
+               this.jobs_contextmenu_resetstate_done.addActionListener(this.jobButtonActionListener);\r
+               \r
+               //initialize button states\r
+               configureTableButtons();\r
+               \r
+               JPanel jobControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));\r
+               jobControlButtonPanel.add(this.jobs_create);\r
+               jobControlButtonPanel.add(this.jobs_createFromTempl);\r
+               jobControlButtonPanel.add(this.jobs_delete);\r
+               jobControlButtonPanel.add(this.jobs_clear);\r
+               jobPanel.add(jobControlButtonPanel);\r
+       }\r
+       \r
+       private void setupJobsTable() {\r
+               RecordJobsTableModel tableModel = new RecordJobsTableModel(this.appLayer);\r
+               jobsTable = new JXTable(tableModel);\r
+               jobsTable.setColumnControlVisible(true);\r
+               jobsTable.setPreferredScrollableViewportSize(new Dimension(400, 100));\r
+               jobsTable.addMouseListener(this.jobsTableMouseListener);\r
+       }\r
+       \r
+       private void setupTemplatesTable() {\r
+               RecordJobTemplatesTableModel tableModel = new RecordJobTemplatesTableModel();\r
+               templatesTable = new JXTable(tableModel);\r
+               templatesTable.setColumnControlVisible(true);\r
+               templatesTable.setPreferredScrollableViewportSize(new Dimension(400, 100));\r
+               templatesTable.addMouseListener(this.templatesTableMouseListener);\r
+       }\r
+\r
+       private void setupRecordPanel() {\r
+               JPanel recButtonPanel = new JPanel(new MigLayout());\r
+               recButtonPanel.add(processing_start);\r
+               recButtonPanel.add(processing_stop);\r
+               processing_stop.setEnabled(false);\r
+               processing_start.addActionListener(recordButtonActionListener);\r
+               processing_stop.addActionListener(recordButtonActionListener);\r
+               getContentPane().add(recButtonPanel);\r
+       }\r
+\r
+       public static void setSystemLAF() {\r
+               try {\r
+                       // Set System L&F\r
+                       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\r
+               } catch (Exception e) {\r
+               }\r
+       }\r
+       \r
+       public void RecordJobPropertiesChange(RecordJob job) {\r
+               RecordJobsTableModel jobsTableModel = (RecordJobsTableModel) this.jobsTable.getModel();\r
+               List<RecordJob> recordJobs = jobsTableModel.getRecordJobs();\r
+               int jobIndex = recordJobs.indexOf(job);\r
+               if (jobIndex == -1) {\r
+                       //new job\r
+                       recordJobs.add(job);\r
+                       //add job at the end of the table:\r
+                       int position = jobsTableModel.getRowCount() - 1;\r
+                       jobsTableModel.fireTableRowsInserted(position, position);\r
+               } else {\r
+                       //job already existed\r
+                       jobIndex = this.jobsTable.convertRowIndexToView(jobIndex); //convert due to possible view sorting\r
+                       jobsTableModel.fireTableRowsUpdated(jobIndex, jobIndex);\r
+               }\r
+       }\r
+\r
+       public void recordingFinished() {\r
+               JOptionPane.showMessageDialog(SwingGUI.this, "Finished recording all jobs", "Recording done", JOptionPane.INFORMATION_MESSAGE);\r
+               statusBar.showState(false);\r
+               processing_start.setEnabled(true);\r
+               processing_stop.setEnabled(false);\r
+               processing_stop.setText(PROCESSING_STOP_NOW);\r
+       }\r
+\r
+       private JMenuBar buildMenu() {\r
+               JMenuBar menuBar = new JMenuBar();\r
+\r
+               JMenu fileMenu = new JMenu("File");\r
+               fileMenu.add(fileLoadQueue);\r
+               fileMenu.add(fileSaveQueue);\r
+               fileMenu.add(filePreferences);\r
+               fileMenu.add(fileExit);\r
+               menuBar.add(fileMenu);\r
+               \r
+               fileLoadQueue.addActionListener(menuButtonActionListener);\r
+               fileSaveQueue.addActionListener(menuButtonActionListener);\r
+               filePreferences.addActionListener(menuButtonActionListener);\r
+               fileExit.addActionListener(menuButtonActionListener);\r
+\r
+               JMenu helpMenu = new JMenu("Help");\r
+               helpMenu.add(helpHelp);\r
+               helpMenu.add(helpAbout);\r
+               menuBar.add(helpMenu);\r
+               \r
+               helpHelp.addActionListener(menuButtonActionListener);\r
+               helpAbout.addActionListener(menuButtonActionListener);\r
+               \r
+               this.setupEncoderPluginButtons();\r
+               \r
+               this.jobsTablePopupMenu = new JPopupMenu();\r
+               this.jobsTablePopupMenu.add(jobs_contextmenu_edit);\r
+               this.jobsTablePopupMenu.add(jobs_contextmenu_duplicate);\r
+               this.jobsTablePopupMenu.add(jobs_contextmenu_delete);\r
+               this.jobsTablePopupMenu.add(jobs_contextmenu_start);\r
+               //add JMenus for plugins\r
+               for (JMenuItem menuItem : jobs_contextmenu_runPluginMenuItems) {\r
+                       this.jobsTablePopupMenu.add(menuItem);\r
+               }\r
+               this.jobsTablePopupMenu.add(jobs_contextmenu_showerror);\r
+               this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_waiting);\r
+               this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_done);\r
+               \r
+               \r
+               \r
+               \r
+               this.templatesTablePopupMenu = new JPopupMenu();\r
+               this.templatesTablePopupMenu.add(templ_contextmenu_edit);\r
+               this.templatesTablePopupMenu.add(templ_contextmenu_duplicate);\r
+               this.templatesTablePopupMenu.add(templ_contextmenu_delete);\r
+\r
+               return menuBar;\r
+       }\r
+       \r
+       private void setupEncoderPluginButtons() {\r
+               for (EncoderPlugin plugin : appLayer.getEncoderPlugins()) {\r
+                       JMenuItem pluginMenuItem = new JMenuItem("Just run " + plugin.getName() + " plugin", getIcon("package.png"));\r
+                       pluginMenuItem.addActionListener(jobButtonActionListener);\r
+                       this.jobs_contextmenu_runPluginMenuItems.add(pluginMenuItem);\r
+               }\r
+       }\r
+\r
+       private void saveTableStates(JXTable table) {\r
+               String fileName;\r
+               if (table == jobsTable) {\r
+                       fileName = JOB_TABLE_PREFERENCES_FILENAME;\r
+               } else {\r
+                       fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;\r
+               }\r
+               String exceptionMessage = "An error occurred while trying to save the table state file " + fileName;\r
+               \r
+               XProperties.XTableProperty t = new XProperties.XTableProperty();\r
+               XTableState tableState;\r
+               try {\r
+                       tableState = (XTableState) t.getSessionState(table);\r
+               } catch (Exception e) { //most likely ClassCastException\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);\r
+                       return;\r
+               }\r
+               \r
+               File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);\r
+               DemoRecorderUtils.attemptFileCreation(tableStateFile);\r
+               \r
+               try {\r
+                       FileOutputStream fout = new FileOutputStream(tableStateFile);\r
+                       ObjectOutputStream oos = new ObjectOutputStream(fout);\r
+                       oos.writeObject(tableState);\r
+                       oos.close();\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);\r
+               }\r
+       }\r
+       \r
+       private void loadTableStates(JXTable table) {\r
+               String fileName;\r
+               if (table == jobsTable) {\r
+                       fileName = JOB_TABLE_PREFERENCES_FILENAME;\r
+               } else {\r
+                       fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;\r
+               }\r
+               \r
+               XProperties.XTableProperty t = new XProperties.XTableProperty();\r
+               \r
+               File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);\r
+               \r
+               XTableState tableState;\r
+               \r
+               try {\r
+                       FileInputStream fin = new FileInputStream(tableStateFile);\r
+                       ObjectInputStream ois = new ObjectInputStream(fin);\r
+                       tableState = (XTableState) ois.readObject();\r
+                       t.setSessionState(table, tableState);\r
+               } catch (Exception e) {\r
+                        //manually hide columns\r
+                       if (table == jobsTable) {\r
+                               //re-create table to be sure\r
+                               this.setupJobsTable();\r
+                               //manually hide some columns\r
+                               jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_AFTER_CAP).setVisible(false);\r
+                               jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_BEFORE_CAP).setVisible(false);\r
+                               jobsTable.getColumnExt(RecordJobsTableModel.VIDEO_DESTINATION_PATH).setVisible(false);\r
+                               jobsTable.getColumnExt(RecordJobsTableModel.DPVIDEO_PATH).setVisible(false);\r
+                               jobsTable.getColumnExt(RecordJobsTableModel.RELATIVE_DEMO_PATH).setVisible(false);\r
+                               jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PARAMETERS).setVisible(false);\r
+                               jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PATH).setVisible(false);\r
+                       } else {\r
+                               //re-create table to be sure\r
+                               this.setupTemplatesTable();\r
+                               //manually hide some columns\r
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_AFTER_CAP).setVisible(false);\r
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_BEFORE_CAP).setVisible(false);\r
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.VIDEO_DESTINATION_PATH).setVisible(false);\r
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.DPVIDEO_PATH).setVisible(false);\r
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.RELATIVE_DEMO_PATH).setVisible(false);\r
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.DEMO_FILE_PATH).setVisible(false);\r
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PARAMETERS).setVisible(false);\r
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PATH).setVisible(false);\r
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.JOB_NAME).setVisible(false);\r
+                       }\r
+               }\r
+       }\r
+       \r
+       private class MenuButtonActionListener implements ActionListener {\r
+\r
+               public void actionPerformed(ActionEvent e) {\r
+                       if (e.getSource() == fileLoadQueue) {\r
+                               int result = jobQueueSaveAsFC.showOpenDialog(SwingGUI.this);\r
+                               if (result == JFileChooser.APPROVE_OPTION) {\r
+                                       File selectedFile = jobQueueSaveAsFC.getSelectedFile();\r
+                                       if (selectedFile.isFile()) {\r
+                                               RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();\r
+                                               tableModel.loadNewJobQueue(selectedFile);\r
+                                               configureTableButtons();\r
+                                       }\r
+                               }\r
+                               \r
+                       } else if (e.getSource() == fileSaveQueue) {\r
+                               int result = jobQueueSaveAsFC.showSaveDialog(SwingGUI.this);\r
+                               if (result == JFileChooser.APPROVE_OPTION) {\r
+                                       File selectedFile = jobQueueSaveAsFC.getSelectedFile();\r
+                                       if (!DemoRecorderUtils.getFileExtension(selectedFile).equals("queue")) {\r
+                                               //if file is not a .queue file, make it one\r
+                                               selectedFile = new File(selectedFile.getAbsoluteFile() + ".queue");\r
+                                       }\r
+                                       if (selectedFile.exists()) {\r
+                                               int confirm = JOptionPane.showConfirmDialog(SwingGUI.this, "File already exists. Are you sure you want to overwrite it?", "Confirm overwrite", JOptionPane.YES_NO_OPTION);\r
+                                               if (confirm == JOptionPane.NO_OPTION) {\r
+                                                       return;\r
+                                               }\r
+                                       }\r
+                                       appLayer.saveJobQueue(selectedFile);\r
+                               }\r
+                       } else if (e.getSource() == filePreferences) {\r
+                               preferencesDialog.showDialog();\r
+                       } else if (e.getSource() == fileExit) {\r
+                               shutDown();\r
+                       } else if (e.getSource() == helpHelp) {\r
+                               if (mainHelpBroker != null) {\r
+                                       mainHelpBroker.setDisplayed(true);\r
+                               }\r
+                       } else if (e.getSource() == helpAbout) {\r
+                               showAboutBox();\r
+                       }\r
+               }\r
+               \r
+       }\r
+\r
+       /**\r
+        * Listens to the clicks on buttons that are in the job panel (next to the jobs table)\r
+        * or its context menu.\r
+        */\r
+       private class JobButtonActionListener implements ActionListener {\r
+\r
+               public void actionPerformed(ActionEvent e) {\r
+                       List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);\r
+                       List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);\r
+                       if (e.getSource() == jobs_create) {\r
+                               JobDialog jobDialog = new JobDialog(SwingGUI.this, appLayer);\r
+                               jobDialog.showDialog();\r
+                               configureTableButtons();\r
+                       }\r
+                       else if (e.getSource() == jobs_createFromTempl) {\r
+                               if (selectedTemplates.size() != 1) {\r
+                                       return;\r
+                               }\r
+                               RecordJobTemplate template = (RecordJobTemplate) selectedTemplates.get(0);\r
+//                             JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer);\r
+                               JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer, JobDialog.CREATE_JOB_FROM_TEMPLATE);\r
+                               jobDialog.showDialog();\r
+                               configureTableButtons();\r
+                       }\r
+                       else if (e.getSource() == jobs_delete || e.getSource() == jobs_contextmenu_delete) {\r
+                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected job(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);\r
+                               if (result == JOptionPane.YES_OPTION) {\r
+                                       deleteSelectedJobs(false);\r
+                                       configureTableButtons();\r
+                               }\r
+                       }\r
+                       else if (e.getSource() == jobs_clear) {\r
+                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the job list?", "Confirm clear", JOptionPane.YES_NO_OPTION);\r
+                               if (result == JOptionPane.YES_OPTION) {\r
+                                       deleteSelectedJobs(true);\r
+                                       configureTableButtons();\r
+                               }\r
+                       } else if (e.getSource() == jobs_contextmenu_edit) {\r
+                               if (selectedJobs.size() == 1) {\r
+                                       RecordJob selectedJob = selectedJobs.get(0);\r
+                                       JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);\r
+                                       jobDialog.showDialog();\r
+                                       configureTableButtons();\r
+                               }\r
+                       } else if (e.getSource() == jobs_contextmenu_showerror) {\r
+                               if (selectedJobs.size() == 1) {\r
+                                       RecordJob selectedJob = selectedJobs.get(0);\r
+                                       DemoRecorderUtils.showNonCriticalErrorDialog(selectedJob.getLastException());\r
+                               }\r
+                       } else if (e.getSource() == jobs_contextmenu_resetstate_waiting) {\r
+                               for (RecordJob job : selectedJobs) {\r
+                                       job.setState(RecordJob.State.WAITING);\r
+                               }\r
+                       } else if (e.getSource() == jobs_contextmenu_resetstate_done) {\r
+                               for (RecordJob job : selectedJobs) {\r
+                                       if (job.getState() == State.ERROR_PLUGIN) {\r
+                                               job.setState(RecordJob.State.DONE);\r
+                                       }\r
+                               }\r
+                       } else if (e.getSource() == jobs_contextmenu_start) {\r
+                               appLayer.recordSelectedJobs(selectedJobs);\r
+                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
+                                       processing_start.setEnabled(false);\r
+                                       processing_stop.setEnabled(true);\r
+                                       statusBar.showState(true);\r
+                               }\r
+                       } else if (e.getSource() == jobs_contextmenu_duplicate) {\r
+                               if (selectedJobs.size() > 0) {\r
+                                       this.duplicateRecordJobs(selectedJobs);\r
+                                       //select all new duplicates in the table automatically\r
+                                       jobsTable.setRowSelectionInterval(jobsTable.getRowCount() - selectedJobs.size(), jobsTable.getRowCount() - 1);\r
+                                       configureTableButtons();\r
+                               }\r
+                       } else if (jobs_contextmenu_runPluginMenuItems.contains(e.getSource())) {\r
+                               int index = jobs_contextmenu_runPluginMenuItems.indexOf(e.getSource());\r
+                               EncoderPlugin selectedPlugin = appLayer.getEncoderPlugins().get(index);\r
+                               \r
+                               appLayer.executePluginForSelectedJobs(selectedPlugin, selectedJobs);\r
+                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
+                                       processing_start.setEnabled(false);\r
+                                       processing_stop.setEnabled(true);\r
+                                       statusBar.showState(true);\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               private void duplicateRecordJobs(List<RecordJob> jobs) {\r
+                       String nameSuffix = appLayer.getPreferences().getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE);\r
+                       for (RecordJob job : jobs) {\r
+                               RecordJob newJob = appLayer.createRecordJob(\r
+                                       job.getJobName() + nameSuffix,\r
+                                       job.getEnginePath(),\r
+                                       job.getEngineParameters(),\r
+                                       job.getDemoFile(),\r
+                                       job.getRelativeDemoPath(),\r
+                                       job.getDpVideoPath(),\r
+                                       job.getVideoDestination(),\r
+                                       job.getExecuteBeforeCap(),\r
+                                       job.getExecuteAfterCap(),\r
+                                       job.getStartSecond(),\r
+                                       job.getEndSecond()\r
+                               );\r
+                               newJob.setEncoderPluginSettings(job.getEncoderPluginSettings());\r
+                       }\r
+               }\r
+               \r
+       }\r
+       \r
+       private class TemplateButtonActionListener implements ActionListener {\r
+               public void actionPerformed(ActionEvent e) {\r
+                       if (e.getSource() == templ_create) {\r
+                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
+                               JobDialog jobDialog = new JobDialog(SwingGUI.this, tableModel, templatesTable, appLayer);\r
+                               jobDialog.showDialog();\r
+                               configureTableButtons();\r
+                       }\r
+                       else if (e.getSource() == templ_createFromJob) {\r
+                               this.createTemplateFromJob();\r
+                               configureTableButtons();\r
+                       }\r
+                       else if (e.getSource() == templ_delete || e.getSource() == templ_contextmenu_delete) {\r
+                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected template(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);\r
+                               if (result == JOptionPane.YES_OPTION) {\r
+                                       deleteSelectedTemplates(false);\r
+                               }\r
+                               configureTableButtons();\r
+                       }\r
+                       else if (e.getSource() == templ_clear) {\r
+                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the template list?", "Confirm clear", JOptionPane.YES_NO_OPTION);\r
+                               if (result == JOptionPane.YES_OPTION) {\r
+                                       deleteSelectedTemplates(true);\r
+                               }\r
+                               configureTableButtons();\r
+                       }\r
+                       else if (e.getSource() == templ_contextmenu_edit) {\r
+                               List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);\r
+                               if (selectedTemplates.size() == 1) {\r
+                                       RecordJobTemplate selectedTemplate = (RecordJobTemplate) selectedTemplates.get(0);\r
+                                       JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedTemplate, appLayer, JobDialog.EDIT_TEMPLATE);\r
+                                       jobDialog.showDialog();\r
+                                       configureTableButtons();\r
+                               }\r
+                       }\r
+                       else if (e.getSource() == templ_contextmenu_duplicate) {\r
+                               List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);\r
+                               if (selectedTemplates.size() > 0) {\r
+                                       this.duplicateTemplates(selectedTemplates);\r
+                                       //select all new duplicates in the table automatically\r
+                                       templatesTable.setRowSelectionInterval(templatesTable.getRowCount() - selectedTemplates.size(), templatesTable.getRowCount() - 1);\r
+                                       configureTableButtons();\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               private void createTemplateFromJob() {\r
+                       List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);\r
+                       if (selectedJobs.size() == 1) {\r
+                               RecordJob job = selectedJobs.get(0);\r
+                               RecordJobTemplate templ = new RecordJobTemplate(\r
+                                       "Generated from job",\r
+                                       "Generated from job",\r
+                                       job.getJobName(),\r
+                                       job.getEnginePath(),\r
+                                       job.getEngineParameters(),\r
+                                       job.getDemoFile().getParentFile(),\r
+                                       job.getRelativeDemoPath(),\r
+                                       job.getDpVideoPath(),\r
+                                       job.getVideoDestination().getParentFile(),\r
+                                       job.getExecuteBeforeCap(),\r
+                                       job.getExecuteAfterCap()\r
+                               );\r
+                               templ.setEncoderPluginSettings(job.getEncoderPluginSettings());\r
+                               \r
+                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
+                               tableModel.addRecordJobTemplate(templ);\r
+                       }\r
+               }\r
+               \r
+               private void duplicateTemplates(List<RecordJob> selectedTemplates) {\r
+                       for (RecordJob job : selectedTemplates) {\r
+                               RecordJobTemplate template = (RecordJobTemplate) job;\r
+                               RecordJobTemplate templ = new RecordJobTemplate(\r
+                                       template.getName(),\r
+                                       template.getSummary(),\r
+                                       template.getJobName(),\r
+                                       template.getEnginePath(),\r
+                                       template.getEngineParameters(),\r
+                                       template.getDemoFile(),\r
+                                       template.getRelativeDemoPath(),\r
+                                       template.getDpVideoPath(),\r
+                                       template.getVideoDestination(),\r
+                                       template.getExecuteBeforeCap(),\r
+                                       template.getExecuteAfterCap()\r
+                               );\r
+                               templ.setEncoderPluginSettings(template.getEncoderPluginSettings());\r
+                               \r
+                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
+                               tableModel.addRecordJobTemplate(templ);\r
+                       }\r
+               }\r
+       }\r
+       \r
+       private class RecordButtonActionListener implements ActionListener {\r
+\r
+               public void actionPerformed(ActionEvent e) {\r
+                       if (e.getSource() == processing_start) {\r
+                               appLayer.startRecording();\r
+                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
+                                       processing_start.setEnabled(false);\r
+                                       processing_stop.setEnabled(true);\r
+                                       statusBar.showState(true);\r
+                               }\r
+                       } else if (e.getSource() == processing_stop) {\r
+                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
+                                       appLayer.stopRecording();\r
+                                       processing_stop.setEnabled(false);\r
+                                       processing_stop.setText(PROCESSING_STOP_LATER);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+       \r
+       private void deleteSelectedJobs(boolean deleteAllJobs) {\r
+               RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();\r
+               if (deleteAllJobs) {\r
+                       int rowCount = jobsTable.getRowCount();\r
+                       for (int i = rowCount - 1; i >= 0; i--) {\r
+                               int modelRowIndex = jobsTable.convertRowIndexToModel(i);\r
+                               tableModel.deleteRecordJob(modelRowIndex, i);\r
+                       }\r
+               } else {\r
+                       int[] selectedRows = jobsTable.getSelectedRows();\r
+                       for (int i = selectedRows.length - 1; i >= 0; i--) {\r
+                               int modelRowIndex = jobsTable.convertRowIndexToModel(selectedRows[i]);\r
+                               tableModel.deleteRecordJob(modelRowIndex, selectedRows[i]);\r
+                       }\r
+               }\r
+       }\r
+       \r
+       private void deleteSelectedTemplates(boolean deleteAllTemplates) {\r
+               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
+               if (deleteAllTemplates) {\r
+                       int rowCount = templatesTable.getRowCount();\r
+                       for (int i = rowCount - 1; i >= 0; i--) {\r
+                               int modelRowIndex = templatesTable.convertRowIndexToModel(i);\r
+                               tableModel.deleteRecordJobTemplate(modelRowIndex, i);\r
+                       }\r
+               } else {\r
+                       int[] selectedRows = templatesTable.getSelectedRows();\r
+                       for (int i = selectedRows.length - 1; i >= 0; i--) {\r
+                               int modelRowIndex = templatesTable.convertRowIndexToModel(selectedRows[i]);\r
+                               tableModel.deleteRecordJobTemplate(modelRowIndex, selectedRows[i]);\r
+                       }\r
+               }\r
+               //update the button state of buttons dealing with jobs\r
+               this.configureTableButtons();\r
+       }\r
+       \r
+       /**\r
+        * Iterates through all RecordJob objects (or just the selected ones) and returns true\r
+        * if at least one of them has one or more has the given state(s).\r
+        * @param state\r
+        * @param justSelectedJobs\r
+        * @return\r
+        */\r
+       private boolean checkJobStates(RecordJob.State[] state, boolean justSelectedJobs) {\r
+               boolean foundState = false;\r
+               List<RecordJob> jobsToLookAt = null;\r
+               if (!justSelectedJobs) {\r
+                       jobsToLookAt = this.appLayer.getRecordJobs();\r
+               } else {\r
+                       jobsToLookAt = getSelectedRecordJobs(jobsTable);\r
+               }\r
+               \r
+               for (RecordJob currentJob : jobsToLookAt) {\r
+                       for (int i = 0; i < state.length; i++) {\r
+                               if (currentJob.getState() == state[i]) {\r
+                                       foundState = true;\r
+                                       break;\r
+                               }\r
+                       }\r
+               }\r
+               return foundState;\r
+       }\r
+       \r
+       /**\r
+        * Returns the list of selected RecordJobs or RecordJobTemplates.\r
+        * @param table jobsTable or templatesTable\r
+        * @return list of selected RecordJobs or RecordJobTemplates\r
+        */\r
+       private List<RecordJob> getSelectedRecordJobs(JXTable table) {\r
+               List<RecordJob> list = new ArrayList<RecordJob>();\r
+               if (table.getSelectedRowCount() > 0) {\r
+                       int[] selectedRows = table.getSelectedRows();\r
+                       for (int i = 0; i < selectedRows.length; i++) {\r
+                               int modelRowIndex = table.convertRowIndexToModel(selectedRows[i]);\r
+                               if (table == jobsTable) {\r
+                                       RecordJobsTableModel tableModel = (RecordJobsTableModel) table.getModel();\r
+                                       RecordJob job = tableModel.getRecordJob(modelRowIndex);\r
+                                       if (job != null) {\r
+                                               list.add(job);\r
+                                       }\r
+                               } else {\r
+                                       RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) table.getModel();\r
+                                       RecordJobTemplate template = tableModel.getRecordJobTemplate(modelRowIndex);\r
+                                       if (template != null) {\r
+                                               list.add(template);\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               return list;\r
+       }\r
+       \r
+       private void configureTableButtons() {\r
+               if (jobsTable != null) {\r
+                       if (jobsTable.getRowCount() == 0) {\r
+                               jobs_clear.setEnabled(false);\r
+                               jobs_delete.setEnabled(false);\r
+                       } else {\r
+                               jobs_clear.setEnabled(true);\r
+                               jobs_delete.setEnabled(true);\r
+                               if (jobsTable.getSelectedRowCount() == 0) {\r
+                                       jobs_delete.setEnabled(false);\r
+                               } else {\r
+                                       //go through all elements and check for attributes PROCESSING\r
+                                       RecordJob.State[] lookForState = {RecordJob.State.PROCESSING};\r
+                                       boolean foundState = checkJobStates(lookForState, false);\r
+                                       if (foundState) {\r
+                                               //we have to disable the clear and delete button\r
+                                               jobs_delete.setEnabled(false);\r
+                                       }\r
+                               }\r
+                       }\r
+                       if (templatesTable.getSelectedRowCount() == 1) {\r
+                               jobs_createFromTempl.setEnabled(true);\r
+                       } else {\r
+                               jobs_createFromTempl.setEnabled(false);\r
+                       }\r
+               }\r
+               \r
+               if (templatesTable != null) {\r
+                       templ_createFromJob.setEnabled(false);\r
+                       templ_delete.setEnabled(false);\r
+                       templ_clear.setEnabled(false);\r
+                       \r
+                       if (jobsTable != null && jobsTable.getSelectedRowCount() == 1) {\r
+                               templ_createFromJob.setEnabled(true);\r
+                       }\r
+                       \r
+                       if (templatesTable.getSelectedRowCount() > 0) {\r
+                               templ_delete.setEnabled(true);\r
+                       }\r
+                       \r
+                       if (templatesTable.getRowCount() > 0) {\r
+                               templ_clear.setEnabled(true);\r
+                       }\r
+               }\r
+       }\r
+       \r
+       private class JobsTableMouseListener implements MouseListener {\r
+\r
+               public void mouseClicked(MouseEvent e) {\r
+                       if (e != null && e.getClickCount() == 2) {\r
+                               List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);\r
+                               if (selectedJobs.size() == 1) {\r
+                                       RecordJob selectedJob = selectedJobs.get(0);\r
+                                       if (selectedJob.getState() != RecordJob.State.PROCESSING) {\r
+                                               JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);\r
+                                               jobDialog.showDialog();\r
+                                       }\r
+                               }\r
+                       } else {\r
+                               configureTableButtons();\r
+                       }\r
+               }\r
+\r
+               public void mouseEntered(MouseEvent e) {}\r
+\r
+               public void mouseExited(MouseEvent e) {}\r
+\r
+               public void mousePressed(MouseEvent e) {\r
+                       this.showPopupMenu(e);\r
+               }\r
+\r
+               public void mouseReleased(MouseEvent e) {\r
+                       this.showPopupMenu(e);\r
+               }\r
+               \r
+               private void showPopupMenu(MouseEvent e) {\r
+                       if (e.isPopupTrigger()) {\r
+                               JTable table = (JTable)(e.getSource());\r
+                               Point p = e.getPoint();\r
+                               int row = table.rowAtPoint(p);\r
+                               int[] selectedRows = table.getSelectedRows();\r
+                               //figure out whether we have to reselect the current row under the pointer,\r
+                               //which is only the case if the already selected rows don't include the one under\r
+                               //the pointer yet\r
+                               boolean reSelect = true;\r
+                               for (int i = 0; i < selectedRows.length; i++) {\r
+                                       if (row == selectedRows[i]) {\r
+                                               reSelect = false;\r
+                                               break;\r
+                                       }\r
+                               }\r
+                               \r
+                               if (row != -1 && reSelect) {\r
+                                       table.setRowSelectionInterval(row, row);\r
+                               }\r
+                               \r
+                               this.configurePopupMenu();\r
+                               configureTableButtons();\r
+                               jobsTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());\r
+                       }\r
+               }\r
+               \r
+               private void configurePopupMenu() {\r
+                       //Disable all buttons first\r
+                       jobs_contextmenu_edit.setEnabled(false);\r
+                       jobs_contextmenu_duplicate.setEnabled(false);\r
+                       jobs_contextmenu_delete.setEnabled(false);\r
+                       jobs_contextmenu_resetstate_waiting.setEnabled(false);\r
+                       jobs_contextmenu_resetstate_done.setEnabled(false);\r
+                       jobs_contextmenu_showerror.setEnabled(false);\r
+                       jobs_contextmenu_start.setEnabled(false);\r
+                       for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {\r
+                               pluginItem.setEnabled(false);\r
+                       }\r
+                       \r
+                       //edit, duplicate, and show error buttons\r
+                       if (jobsTable.getSelectedRowCount() == 1) {\r
+                               jobs_contextmenu_edit.setEnabled(true);\r
+                               \r
+                               //Show error button\r
+                               List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);\r
+                               RecordJob selectedJob = selectedJobs.get(0);\r
+                               if (selectedJob.getState() == RecordJob.State.ERROR || selectedJob.getState() == RecordJob.State.ERROR_PLUGIN) {\r
+                                       jobs_contextmenu_showerror.setEnabled(true);\r
+                               }\r
+                       }\r
+                       \r
+                       if (jobsTable.getSelectedRowCount() > 0) {\r
+                               jobs_contextmenu_duplicate.setEnabled(true);\r
+                               //Delete button\r
+                               RecordJob.State[] states = {RecordJob.State.PROCESSING};\r
+                               if (!checkJobStates(states, false)) {\r
+                                       //none of the jobs is processing\r
+                                       jobs_contextmenu_delete.setEnabled(true);\r
+                                       jobs_contextmenu_resetstate_waiting.setEnabled(true);\r
+                               } else {\r
+                                       jobs_contextmenu_edit.setEnabled(false);\r
+                                       jobs_contextmenu_duplicate.setEnabled(false);\r
+                               }\r
+                               \r
+                               //Start button\r
+                               RecordJob.State[] states2 = {RecordJob.State.ERROR, RecordJob.State.DONE, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};\r
+                               if (!checkJobStates(states2, true)) {\r
+                                       //only enable start if none of the selected jobs as any of the States above\r
+                                       //as the only job State that is not listed is "waiting", we only enable the button if all jobs are waiting\r
+                                       jobs_contextmenu_start.setEnabled(true);\r
+                               }\r
+                               \r
+                               //reset to 'done' button\r
+                               RecordJob.State[] states3 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING};\r
+                               if (!checkJobStates(states3, true)) {\r
+                                       //only enable the "reset to done" button when processes have the state DONE or ERROR_PLUGIN\r
+                                       jobs_contextmenu_resetstate_done.setEnabled(true);\r
+                               }\r
+                               \r
+                               //plugin buttons, enable only when state of the job is DONE\r
+                               RecordJob.State[] states4 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};\r
+                               if (!checkJobStates(states4, true)) {\r
+                                       int counter = 0;\r
+                                       for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {\r
+                                               if (appLayer.getEncoderPlugins().get(counter).isEnabled()) {\r
+                                                       pluginItem.setEnabled(true);\r
+                                               }\r
+                                               counter++;\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+               \r
+       }\r
+       \r
+       private class TemplatesTableMouseListener implements MouseListener {\r
+\r
+               public void mouseClicked(MouseEvent e) {\r
+                       if (e != null && e.getClickCount() == 2) {\r
+                               List<RecordJob> selectedJobs = getSelectedRecordJobs(templatesTable);\r
+                               if (selectedJobs.size() == 1) {\r
+                                       RecordJobTemplate selectedJob = (RecordJobTemplate) selectedJobs.get(0);\r
+                                       JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer, JobDialog.EDIT_TEMPLATE);\r
+                                       jobDialog.showDialog();\r
+                                       configureTableButtons();\r
+                               }\r
+                       } else {\r
+                               configureTableButtons();\r
+                       }\r
+               }\r
+\r
+               public void mouseEntered(MouseEvent e) {}\r
+\r
+               public void mouseExited(MouseEvent e) {}\r
+\r
+               public void mousePressed(MouseEvent e) {\r
+                       this.showPopupMenu(e);\r
+               }\r
+\r
+               public void mouseReleased(MouseEvent e) {\r
+                       this.showPopupMenu(e);\r
+               }\r
+               \r
+               private void showPopupMenu(MouseEvent e) {\r
+                       if (e.isPopupTrigger()) {\r
+                               JTable table = (JTable)(e.getSource());\r
+                               Point p = e.getPoint();\r
+                               int row = table.rowAtPoint(p);\r
+                               int[] selectedRows = table.getSelectedRows();\r
+                               //figure out whether we have to reselect the current row under the pointer,\r
+                               //which is only the case if the already selected rows don't include the one under\r
+                               //the pointer yet\r
+                               boolean reSelect = true;\r
+                               for (int i = 0; i < selectedRows.length; i++) {\r
+                                       if (row == selectedRows[i]) {\r
+                                               reSelect = false;\r
+                                               break;\r
+                                       }\r
+                               }\r
+                               \r
+                               if (row != -1 && reSelect) {\r
+                                       table.setRowSelectionInterval(row, row);\r
+                               }\r
+                               \r
+                               this.configurePopupMenu();\r
+                               configureTableButtons();\r
+                               templatesTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());\r
+                       }\r
+               }\r
+               \r
+               private void configurePopupMenu() {\r
+                       //Various buttons\r
+                       templ_contextmenu_edit.setEnabled(false);\r
+                       templ_contextmenu_duplicate.setEnabled(false);\r
+                       templ_contextmenu_delete.setEnabled(false);\r
+                       \r
+                       //Edit button\r
+                       if (templatesTable.getSelectedRowCount() == 1) {\r
+                               templ_contextmenu_edit.setEnabled(true);\r
+                       }\r
+                       \r
+                       //Delete and duplicate button\r
+                       if (templatesTable.getSelectedRowCount() > 0) {\r
+                               templ_contextmenu_delete.setEnabled(true);\r
+                               templ_contextmenu_duplicate.setEnabled(true);\r
+                       }\r
+               }\r
+       }\r
+               \r
+       private void showAboutBox() {\r
+        try {\r
+            InputStream inStream = ClassLoader.getSystemResourceAsStream("about.html");\r
+            StringBuffer out = new StringBuffer();\r
+            byte[] b = new byte[4096];\r
+            for (int n; (n = inStream.read(b)) != -1;) {\r
+                out.append(new String(b, 0, n));\r
+            }\r
+            String htmlString = out.toString();\r
+            htmlString = htmlString.replaceAll("[\\r\\n]", "");\r
+            JOptionPane.showMessageDialog(this, htmlString, "About", JOptionPane.PLAIN_MESSAGE);\r
+        } catch (IOException ex) {\r
+            ex.printStackTrace();\r
+        }\r
+    }\r
+\r
+       public void windowActivated(WindowEvent e) {}\r
+       public void windowClosed(WindowEvent e) {}\r
+       public void windowDeactivated(WindowEvent e) {}\r
+       public void windowDeiconified(WindowEvent e) {}\r
+       public void windowIconified(WindowEvent e) {}\r
+       public void windowOpened(WindowEvent e) {}\r
+\r
+       public void windowClosing(WindowEvent e) {\r
+               this.shutDown();\r
+       }\r
+\r
+       private void shutDown() {\r
+               if (this.appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
+                       int result = JOptionPane.showConfirmDialog(this, "There are still jobs being recorded. Are you sure you want to exit?", "Confirm close", JOptionPane.YES_NO_OPTION);\r
+                       if (result == JOptionPane.NO_OPTION) {\r
+                               return;\r
+                       }\r
+               }\r
+               saveTableStates(jobsTable);\r
+               saveTableStates(templatesTable);\r
+               saveTemplateTableContent();\r
+               this.appLayer.shutDown();\r
+               this.dispose();\r
+               System.exit(0);\r
+       }\r
+       \r
+       private void saveTemplateTableContent() {\r
+               File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, TEMPLATE_TABLE_CONTENT_FILENAME);\r
+               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
+               tableModel.saveTemplateListToFile(path);\r
+       }\r
+       \r
+       private Icon getIcon(String iconString) {\r
+               URL url = ClassLoader.getSystemResource("icons/" + iconString);\r
+               Icon i = new ImageIcon(url);\r
+               return i;\r
+       }\r
+\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java
new file mode 100644 (file)
index 0000000..ddb52f5
--- /dev/null
@@ -0,0 +1,221 @@
+package com.nexuiz.demorecorder.ui.swinggui.tablemodels;\r
+\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileOutputStream;\r
+import java.io.ObjectInputStream;\r
+import java.io.ObjectOutputStream;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import javax.swing.table.AbstractTableModel;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
+import com.nexuiz.demorecorder.application.DemoRecorderException;\r
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
+import com.nexuiz.demorecorder.ui.swinggui.RecordJobTemplate;\r
+import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;\r
+\r
+/**\r
+ * Columns:\r
+ * - Job Name\r
+ * - Engine path\r
+ * - Engine parameters\r
+ * - Demo file\r
+ * - Relative demo path\r
+ * - dpvideo path\r
+ * - video destination\r
+ * - execute before cap\r
+ * - execute after cap\r
+ * - start second\r
+ * - end second\r
+ * - status\r
+ * @author Marius\r
+ *\r
+ */\r
+public class RecordJobTemplatesTableModel extends AbstractTableModel {\r
+       \r
+       private static final long serialVersionUID = 6541517890817708306L;\r
+       \r
+       public static final int TEMPLATE_NAME = 0;\r
+       public static final int TEMPLATE_SUMMARY = 1;\r
+       public static final int JOB_NAME = 2;\r
+       public static final int ENGINE_PATH = 3;\r
+       public static final int ENGINE_PARAMETERS = 4;\r
+       public static final int DEMO_FILE_PATH = 5;\r
+       public static final int RELATIVE_DEMO_PATH = 6;\r
+       public static final int DPVIDEO_PATH = 7;\r
+       public static final int VIDEO_DESTINATION_PATH = 8;\r
+       public static final int EXECUTE_BEFORE_CAP = 9;\r
+       public static final int EXECUTE_AFTER_CAP = 10;\r
+       \r
+       private static final int columns[] = {\r
+               TEMPLATE_NAME,\r
+               TEMPLATE_SUMMARY,\r
+               JOB_NAME,\r
+               ENGINE_PATH,\r
+               ENGINE_PARAMETERS,\r
+               DEMO_FILE_PATH,\r
+               RELATIVE_DEMO_PATH,\r
+               DPVIDEO_PATH,\r
+               VIDEO_DESTINATION_PATH,\r
+               EXECUTE_BEFORE_CAP,\r
+               EXECUTE_AFTER_CAP\r
+       };\r
+       \r
+       private List<RecordJobTemplate> templates;\r
+       \r
+       public RecordJobTemplatesTableModel() {\r
+               templates = new ArrayList<RecordJobTemplate>();\r
+               \r
+               //load table content\r
+               File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, SwingGUI.TEMPLATE_TABLE_CONTENT_FILENAME);\r
+               this.loadTemplateListFromFile(path);\r
+       }\r
+       \r
+       public void deleteRecordJobTemplate(int modelRowIndex, int viewRowIndex) {\r
+               try {\r
+                       this.templates.remove(modelRowIndex);\r
+                       fireTableRowsDeleted(viewRowIndex, viewRowIndex);\r
+               } catch (IndexOutOfBoundsException e) {\r
+                       throw new DemoRecorderException("Couldn't find correspondig template for modelRowIndex " + modelRowIndex\r
+                                       + " and viewRowIndex " + viewRowIndex, e);\r
+               }\r
+       }\r
+       \r
+       public void addRecordJobTemplate(RecordJobTemplate template) {\r
+               this.templates.add(template);\r
+               int position = this.templates.size() - 1;\r
+               fireTableRowsInserted(position, position);\r
+       }\r
+       \r
+       public RecordJobTemplate getRecordJobTemplate(int modelRowIndex) {\r
+               return this.templates.get(modelRowIndex);\r
+       }\r
+\r
+       public int getColumnCount() {\r
+               return columns.length;\r
+       }\r
+\r
+       public int getRowCount() {\r
+               return this.templates.size();\r
+       }\r
+       \r
+       public void saveTemplateListToFile(File path) {\r
+               DemoRecorderUtils.attemptFileCreation(path);\r
+               \r
+               String exceptionMessage = "Could not save the templates to file " + path.getAbsolutePath();\r
+               \r
+               if (!path.exists()) {\r
+                       DemoRecorderException ex = new DemoRecorderException(exceptionMessage);\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(ex);\r
+                       return;\r
+               }\r
+               \r
+               try {\r
+                       FileOutputStream fout = new FileOutputStream(path);\r
+                       ObjectOutputStream oos = new ObjectOutputStream(fout);\r
+                       oos.writeObject(this.templates);\r
+                       oos.close();\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);\r
+               }\r
+       }\r
+       \r
+       @SuppressWarnings("unchecked")\r
+       public void loadTemplateListFromFile(File path) {\r
+               if (!path.exists()) {\r
+                       return;\r
+               }\r
+               \r
+               List<RecordJobTemplate> newTemplateList;\r
+               try {\r
+                       FileInputStream fin = new FileInputStream(path);\r
+                       ObjectInputStream ois = new ObjectInputStream(fin);\r
+                       newTemplateList = (List<RecordJobTemplate>) ois.readObject();\r
+               } catch (Exception e) {\r
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the templates from file " + path.getAbsolutePath(), e, true);\r
+                       return;\r
+               }\r
+               this.templates = newTemplateList;\r
+//             fireTableRowsInserted(0, this.templates.size());\r
+       }\r
+\r
+       public Object getValueAt(int rowIndex, int columnIndex) {\r
+               RecordJobTemplate template = this.templates.get(rowIndex);\r
+               if (template == null) {\r
+                       return null;\r
+               }\r
+               \r
+               if (columnIndex < 0 || columnIndex >= columns.length) {\r
+                       return null;\r
+               }\r
+               \r
+               String cellData = "UNDEF";\r
+               switch (columnIndex) {\r
+               case TEMPLATE_NAME:\r
+                       cellData = template.getName(); break;\r
+               case TEMPLATE_SUMMARY:\r
+                       cellData = template.getSummary(); break;\r
+               case JOB_NAME:\r
+                       cellData = template.getJobName(); break;\r
+               case ENGINE_PATH:\r
+                       cellData = template.getEnginePath().getAbsolutePath(); break;\r
+               case ENGINE_PARAMETERS:\r
+                       cellData = template.getEngineParameters(); break;\r
+               case DEMO_FILE_PATH:\r
+                       cellData = DemoRecorderUtils.getJustFileNameOfPath(template.getDemoFile()); break;\r
+               case RELATIVE_DEMO_PATH:\r
+                       cellData = template.getRelativeDemoPath(); break;\r
+               case DPVIDEO_PATH:\r
+                       cellData = template.getDpVideoPath().getAbsolutePath(); break;\r
+               case VIDEO_DESTINATION_PATH:\r
+                       cellData = template.getVideoDestination().getAbsolutePath(); break;\r
+               case EXECUTE_BEFORE_CAP:\r
+                       cellData = template.getExecuteBeforeCap(); break;\r
+               case EXECUTE_AFTER_CAP:\r
+                       cellData = template.getExecuteAfterCap(); break;\r
+               }\r
+               \r
+               return cellData;\r
+       }\r
+\r
+       @Override\r
+       public String getColumnName(int column) {\r
+               if (column < 0 || column >= columns.length) {\r
+                       return "";\r
+               }\r
+               \r
+               String columnName = "UNDEFINED";\r
+               switch (column) {\r
+               case TEMPLATE_NAME:\r
+                       columnName = "Name"; break;\r
+               case TEMPLATE_SUMMARY:\r
+                       columnName = "Summary"; break;\r
+               case JOB_NAME:\r
+                       columnName = "Job name"; break;\r
+               case ENGINE_PATH:\r
+                       columnName = "Engine path"; break;\r
+               case ENGINE_PARAMETERS:\r
+                       columnName = "Engine parameters"; break;\r
+               case DEMO_FILE_PATH:\r
+                       columnName = "Demo directory"; break;\r
+               case RELATIVE_DEMO_PATH:\r
+                       columnName = "Relative demo path"; break;\r
+               case DPVIDEO_PATH:\r
+                       columnName = "DPVideo path"; break;\r
+               case VIDEO_DESTINATION_PATH:\r
+                       columnName = "Video destination"; break;\r
+               case EXECUTE_BEFORE_CAP:\r
+                       columnName = "Exec before"; break;\r
+               case EXECUTE_AFTER_CAP:\r
+                       columnName = "Exec after"; break;\r
+               }\r
+               \r
+               return columnName;\r
+       }\r
+       \r
+       public List<RecordJobTemplate> getRecordJobTemplates() {\r
+               return this.templates;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java
new file mode 100644 (file)
index 0000000..2d2f5fc
--- /dev/null
@@ -0,0 +1,192 @@
+package com.nexuiz.demorecorder.ui.swinggui.tablemodels;\r
+\r
+import java.io.File;\r
+import java.util.List;\r
+\r
+import javax.swing.table.AbstractTableModel;\r
+\r
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
+import com.nexuiz.demorecorder.application.DemoRecorderException;\r
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
+import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
+\r
+/**\r
+ * Columns:\r
+ * - Job Name\r
+ * - Engine path\r
+ * - Engine parameters\r
+ * - Demo file\r
+ * - Relative demo path\r
+ * - dpvideo path\r
+ * - video destination\r
+ * - execute before cap\r
+ * - execute after cap\r
+ * - start second\r
+ * - end second\r
+ * - status\r
+ * @author Marius\r
+ *\r
+ */\r
+public class RecordJobsTableModel extends AbstractTableModel {\r
+       \r
+       private static final long serialVersionUID = 5024144640874313910L;\r
+       \r
+       public static final int JOB_NAME = 0;\r
+       public static final int ENGINE_PATH = 1;\r
+       public static final int ENGINE_PARAMETERS = 2;\r
+       public static final int DEMO_FILE_PATH = 3;\r
+       public static final int RELATIVE_DEMO_PATH = 4;\r
+       public static final int DPVIDEO_PATH = 5;\r
+       public static final int VIDEO_DESTINATION_PATH = 6;\r
+       public static final int EXECUTE_BEFORE_CAP = 7;\r
+       public static final int EXECUTE_AFTER_CAP = 8;\r
+       public static final int START_SECOND = 9;\r
+       public static final int END_SECOND = 10;\r
+       public static final int STATUS = 11;\r
+       \r
+       private static final int columns[] = {\r
+               JOB_NAME,\r
+               ENGINE_PATH,\r
+               ENGINE_PARAMETERS,\r
+               DEMO_FILE_PATH,\r
+               RELATIVE_DEMO_PATH,\r
+               DPVIDEO_PATH,\r
+               VIDEO_DESTINATION_PATH,\r
+               EXECUTE_BEFORE_CAP,\r
+               EXECUTE_AFTER_CAP,\r
+               START_SECOND,\r
+               END_SECOND,\r
+               STATUS\r
+       };\r
+       \r
+       private DemoRecorderApplication appLayer;\r
+       private List<RecordJob> jobList = null;\r
+       \r
+       public RecordJobsTableModel(DemoRecorderApplication appLayer) {\r
+               this.appLayer = appLayer;\r
+               this.jobList = this.appLayer.getRecordJobs();\r
+       }\r
+       \r
+       public void deleteRecordJob(int modelRowIndex, int viewRowIndex) {\r
+               try {\r
+                       RecordJob job = this.jobList.get(modelRowIndex);\r
+                       if (this.appLayer.deleteRecordJob(job)) {\r
+                               this.jobList.remove(job);\r
+                               fireTableRowsDeleted(viewRowIndex, viewRowIndex);\r
+                       }\r
+               } catch (IndexOutOfBoundsException e) {\r
+                       throw new DemoRecorderException("Couldn't find correspondig job for modelRowIndex " + modelRowIndex\r
+                                       + " and viewRowIndex " + viewRowIndex, e);\r
+               }\r
+       }\r
+       \r
+       public void loadNewJobQueue(File path) {\r
+               this.appLayer.loadJobQueue(path);\r
+               this.jobList = this.appLayer.getRecordJobs();\r
+               fireTableDataChanged();\r
+       }\r
+       \r
+       public RecordJob getRecordJob(int modelRowIndex) {\r
+               return this.jobList.get(modelRowIndex);\r
+       }\r
+\r
+       public int getColumnCount() {\r
+               return columns.length;\r
+       }\r
+\r
+       public int getRowCount() {\r
+               return this.jobList.size();\r
+       }\r
+\r
+       public Object getValueAt(int rowIndex, int columnIndex) {\r
+               RecordJob job = this.jobList.get(rowIndex);\r
+               if (job == null) {\r
+                       return null;\r
+               }\r
+               \r
+               if (columnIndex < 0 || columnIndex >= columns.length) {\r
+                       return null;\r
+               }\r
+               \r
+               String cellData = "UNDEF";\r
+               switch (columnIndex) {\r
+               case JOB_NAME:\r
+                       cellData = job.getJobName(); break;\r
+               case ENGINE_PATH:\r
+                       cellData = job.getEnginePath().getAbsolutePath(); break;\r
+               case ENGINE_PARAMETERS:\r
+                       cellData = job.getEngineParameters(); break;\r
+               case DEMO_FILE_PATH:\r
+                       cellData = DemoRecorderUtils.getJustFileNameOfPath(job.getDemoFile()); break;\r
+               case RELATIVE_DEMO_PATH:\r
+                       cellData = job.getRelativeDemoPath(); break;\r
+               case DPVIDEO_PATH:\r
+                       cellData = job.getDpVideoPath().getAbsolutePath(); break;\r
+               case VIDEO_DESTINATION_PATH:\r
+                       cellData = job.getVideoDestination().getAbsolutePath(); break;\r
+               case EXECUTE_BEFORE_CAP:\r
+                       cellData = job.getExecuteBeforeCap(); break;\r
+               case EXECUTE_AFTER_CAP:\r
+                       cellData = job.getExecuteAfterCap(); break;\r
+               case START_SECOND:\r
+                       cellData = String.valueOf(job.getStartSecond()); break;\r
+               case END_SECOND:\r
+                       cellData = String.valueOf(job.getEndSecond()); break;\r
+               case STATUS:\r
+                       if (job.getState() == RecordJob.State.DONE) {\r
+                               cellData = "done";\r
+                       } else if (job.getState() == RecordJob.State.ERROR) {\r
+                               cellData = "error";\r
+                       } else if (job.getState() == RecordJob.State.ERROR_PLUGIN) {\r
+                               cellData = "plug-in error";\r
+                       } else if (job.getState() == RecordJob.State.PROCESSING) {\r
+                               cellData = "processing";\r
+                       } else if (job.getState() == RecordJob.State.WAITING) {\r
+                               cellData = "waiting";\r
+                       }\r
+               }\r
+               \r
+               return cellData;\r
+       }\r
+\r
+       @Override\r
+       public String getColumnName(int column) {\r
+               if (column < 0 || column >= columns.length) {\r
+                       return "";\r
+               }\r
+               \r
+               String columnName = "UNDEFINED";\r
+               switch (column) {\r
+               case JOB_NAME:\r
+                       columnName = "Name"; break;\r
+               case ENGINE_PATH:\r
+                       columnName = "Engine path"; break;\r
+               case ENGINE_PARAMETERS:\r
+                       columnName = "Engine parameters"; break;\r
+               case DEMO_FILE_PATH:\r
+                       columnName = "Demo name"; break;\r
+               case RELATIVE_DEMO_PATH:\r
+                       columnName = "Relative demo path"; break;\r
+               case DPVIDEO_PATH:\r
+                       columnName = "DPVideo path"; break;\r
+               case VIDEO_DESTINATION_PATH:\r
+                       columnName = "Video destination"; break;\r
+               case EXECUTE_BEFORE_CAP:\r
+                       columnName = "Exec before"; break;\r
+               case EXECUTE_AFTER_CAP:\r
+                       columnName = "Exec after"; break;\r
+               case START_SECOND:\r
+                       columnName = "Start"; break;\r
+               case END_SECOND:\r
+                       columnName = "End"; break;\r
+               case STATUS:\r
+                       columnName = "Status"; break;\r
+               }\r
+               \r
+               return columnName;\r
+       }\r
+       \r
+       public List<RecordJob> getRecordJobs() {\r
+               return this.jobList;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java
new file mode 100644 (file)
index 0000000..41e9b78
--- /dev/null
@@ -0,0 +1,21 @@
+package com.nexuiz.demorecorder.ui.swinggui.utils;\r
+\r
+import java.awt.Component;\r
+import java.lang.Thread.UncaughtExceptionHandler;\r
+\r
+import org.jdesktop.swingx.JXErrorPane;\r
+import org.jdesktop.swingx.error.ErrorInfo;\r
+\r
+public class ShowErrorDialogExceptionHandler implements UncaughtExceptionHandler {\r
+\r
+       private static Component parentWindow = null;\r
+       \r
+       public void uncaughtException(Thread t, Throwable e) {\r
+               ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);\r
+               JXErrorPane.showDialog(parentWindow, info);\r
+       }\r
+\r
+       public static void setParentWindow(Component c) {\r
+               parentWindow = c;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java
new file mode 100644 (file)
index 0000000..65462e7
--- /dev/null
@@ -0,0 +1,26 @@
+package com.nexuiz.demorecorder.ui.swinggui.utils;\r
+\r
+import java.io.File;\r
+\r
+public class SwingGUIUtils {\r
+       public static boolean isBooleanValue(String value) {\r
+               if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {\r
+                       return true;\r
+               }\r
+               return false;\r
+       }\r
+\r
+       public static boolean isFileChooser(String value) {\r
+               if (value.equalsIgnoreCase("filechooser")) {\r
+                       return true;\r
+               }\r
+               try {\r
+                       File file = new File(value);\r
+                       if (file.exists()) {\r
+                               return true;\r
+                       }\r
+               } catch (Throwable e) {\r
+               }\r
+               return false;\r
+       }\r
+}\r
diff --git a/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java b/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java
new file mode 100644 (file)
index 0000000..004b626
--- /dev/null
@@ -0,0 +1,432 @@
+/*\r
+ * Created on 08.02.2007\r
+ *\r
+ */\r
+package com.nexuiz.demorecorder.ui.swinggui.utils;\r
+\r
+import java.awt.Component;\r
+import java.beans.DefaultPersistenceDelegate;\r
+import java.beans.XMLEncoder;\r
+import java.io.Serializable;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.List;\r
+\r
+import javax.swing.SortOrder;\r
+import javax.swing.RowSorter.SortKey;\r
+import javax.swing.table.TableColumn;\r
+import javax.swing.table.TableColumnModel;\r
+\r
+import org.jdesktop.swingx.JXTable;\r
+import org.jdesktop.swingx.JXTaskPane;\r
+import org.jdesktop.swingx.sort.SortUtils;\r
+import org.jdesktop.swingx.table.TableColumnExt;\r
+\r
+/**\r
+ * Container class for SwingX specific SessionStorage Properties. Is Factory for\r
+ * custom PersistanceDelegates\r
+ */\r
+public class XProperties {\r
+\r
+       /**\r
+        * \r
+        * Registers all custom PersistenceDelegates needed by contained Property\r
+        * classes.\r
+        * <p>\r
+        * \r
+        * PersistenceDelegates are effectively static properties shared by all\r
+        * encoders. In other words: Register once on an arbitrary encoder makes\r
+        * them available for all. Example usage:\r
+        * \r
+        * <pre>\r
+        * <code>\r
+        * new XProperties.registerPersistenceDelegates();\r
+        * </code>\r
+        * </pre>\r
+        * \r
+        * PENDING JW: cleanup for 1.6 sorting/filtering incomplete. Missing storage\r
+        * - multiple sort keys\r
+        * \r
+        * PENDING JW: except for comparators: didn't before and is not state that's\r
+        * configurable by users ... so probably won't, not sure, need to revisit -\r
+        * comparator (?) - filters (?) - renderers/stringvalues (?) - enhanced\r
+        * sort-related table state (?)\r
+        */\r
+       public void registerPersistenceDelegates() {\r
+               XMLEncoder encoder = new XMLEncoder(System.out);\r
+               encoder.setPersistenceDelegate(SortKeyState.class, new DefaultPersistenceDelegate(\r
+                               new String[] { "ascending", "modelIndex" }));\r
+               encoder.setPersistenceDelegate(ColumnState.class, new DefaultPersistenceDelegate(\r
+                               new String[] { "width", "preferredWidth", "modelIndex", "visible", "viewIndex" }));\r
+               encoder.setPersistenceDelegate(XTableState.class, new DefaultPersistenceDelegate(\r
+                               new String[] { "columnStates", "sortKeyState", "horizontalScrollEnabled" }));\r
+       }\r
+\r
+       /**\r
+        * Session storage support for JXTaskPane.\r
+        */\r
+       public static class XTaskPaneProperty implements Serializable {\r
+\r
+               private static final long serialVersionUID = -4069436038178318216L;\r
+\r
+               public Object getSessionState(Component c) {\r
+                       checkComponent(c);\r
+                       return new XTaskPaneState(((JXTaskPane) c).isCollapsed());\r
+               }\r
+\r
+               public void setSessionState(Component c, Object state) {\r
+                       checkComponent(c);\r
+                       if ((state != null) && !(state instanceof XTaskPaneState)) {\r
+                               throw new IllegalArgumentException("invalid state");\r
+                       }\r
+                       ((JXTaskPane) c).setCollapsed(((XTaskPaneState) state).isCollapsed());\r
+               }\r
+\r
+               private void checkComponent(Component component) {\r
+                       if (component == null) {\r
+                               throw new IllegalArgumentException("null component");\r
+                       }\r
+                       if (!(component instanceof JXTaskPane)) {\r
+                               throw new IllegalArgumentException("invalid component");\r
+                       }\r
+               }\r
+\r
+       }\r
+\r
+       public static class XTaskPaneState implements Serializable {\r
+               private static final long serialVersionUID = 3363688961112031969L;\r
+               private boolean collapsed;\r
+\r
+               public XTaskPaneState() {\r
+                       this(false);\r
+               }\r
+\r
+               /**\r
+                * @param b\r
+                */\r
+               public XTaskPaneState(boolean collapsed) {\r
+                       this.setCollapsed(collapsed);\r
+               }\r
+\r
+               /**\r
+                * @param collapsed\r
+                *            the collapsed to set\r
+                */\r
+               public void setCollapsed(boolean collapsed) {\r
+                       this.collapsed = collapsed;\r
+               }\r
+\r
+               /**\r
+                * @return the collapsed\r
+                */\r
+               public boolean isCollapsed() {\r
+                       return collapsed;\r
+               }\r
+\r
+       }\r
+\r
+       /**\r
+        * Session storage support for JXTable.\r
+        */\r
+       public static class XTableProperty implements Serializable {\r
+\r
+               private static final long serialVersionUID = -5064142292091374301L;\r
+\r
+               public Object getSessionState(Component c) {\r
+                       checkComponent(c);\r
+                       JXTable table = (JXTable) c;\r
+                       List<ColumnState> columnStates = new ArrayList<ColumnState>();\r
+                       List<TableColumn> columns = table.getColumns(true);\r
+                       List<TableColumn> visibleColumns = table.getColumns();\r
+                       for (TableColumn column : columns) {\r
+                               columnStates.add(new ColumnState((TableColumnExt) column, visibleColumns\r
+                                               .indexOf(column)));\r
+                       }\r
+                    &nb