[nexuiz-commits] r8633 - in trunk/misc/tools/NexuizDemoRecorder: . src src/main src/main/java src/main/java/com src/main/java/com/nexuiz src/main/java/com/nexuiz/demorecorder src/main/java/com/nexuiz/demorecorder/application src/main/java/com/nexuiz/demorecorder/application/democutter src/main/java/com/nexuiz/demorecorder/application/jobs src/main/java/com/nexuiz/demorecorder/application/plugins src/main/java/com/nexuiz/demorecorder/main src/main/java/com/nexuiz/demorecorder/ui src/main/java/com/nexuiz/demorecorder/ui/swinggui src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils src/main/resources src/main/resources/help src/main/resources/help/JavaHelpSearch src/main/resources/help/html src/main/resources/help/html/images src/main/resources/icons src/test

DONOTREPLY at icculus.org DONOTREPLY at icculus.org
Thu Feb 11 10:54:58 EST 2010


Author: greenmarine
Date: 2010-02-11 10:54:58 -0500 (Thu, 11 Feb 2010)
New Revision: 8633

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

Added: trunk/misc/tools/NexuizDemoRecorder/pom.xml
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/pom.xml	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/pom.xml	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,83 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>NexuizDemoRecorder</groupId>
+	<artifactId>NexuizDemoRecorder</artifactId>
+	<packaging>jar</packaging>
+	<version>0.2</version>
+	<name>NexuizDemoRecorder</name>
+	<url>http://maven.apache.org</url>
+	<dependencies>
+		<dependency>
+			<groupId>com.miglayout</groupId>
+			<artifactId>miglayout</artifactId>
+			<version>3.7.2</version>
+		</dependency>
+		<dependency>
+			<groupId>org.swinglabs</groupId>
+			<artifactId>swingx</artifactId>
+			<version>1.6</version>
+			<exclusions>
+			<!-- Exclude unneeded libs that have been transitively resolved for SwingX -->
+				<exclusion>
+					<groupId>org.swinglabs</groupId>
+					<artifactId>swing-worker</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>com.jhlabs</groupId>
+					<artifactId>filters</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>javax.help</groupId>
+			<artifactId>javahelp</artifactId>
+			<version>2.0.02</version>
+		</dependency>
+	</dependencies>
+	<build>
+		<resources>
+			<resource>
+				<directory>src/main/resources</directory>
+			</resource>
+		</resources>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>2.0.2</version>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<configuration>
+					<archive>
+						<manifest>
+							<addClasspath>true</addClasspath>
+							<classpathPrefix>lib/</classpathPrefix>
+							<mainClass>com.nexuiz.demorecorder.main.Driver</mainClass>
+						</manifest>
+					</archive>
+				</configuration>
+			</plugin>
+			<plugin>
+				<artifactId>maven-dependency-plugin</artifactId>
+				<executions>
+					<execution>
+						<phase>package</phase>
+						<goals>
+							<goal>copy-dependencies</goal>
+						</goals>
+						<configuration>
+							<outputDirectory>${project.build.directory}/lib</outputDirectory>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,444 @@
+package com.nexuiz.demorecorder.application;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.ServiceLoader;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.nexuiz.demorecorder.application.jobs.EncoderJob;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.jobs.RecordsDoneJob;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.DemoRecorderUI;
+
+public class DemoRecorderApplication {
+	
+	public static class Preferences {
+		public static final String OVERWRITE_VIDEO_FILE = "Overwrite final video destination file if it exists";
+		public static final String DISABLE_RENDERING = "Disable rendering while fast-forwarding";
+		public static final String DISABLE_SOUND = "Disable sound while fast-forwarding";
+		public static final String FFW_SPEED_FIRST_STAGE = "Fast-forward speed (first stage)";
+		public static final String FFW_SPEED_SECOND_STAGE = "Fast-forward speed (second stage)";
+		public static final String DO_NOT_DELETE_CUT_DEMOS = "Do not delete cut demos";
+		public static final String JOB_NAME_APPEND_DUPLICATE = "Append this suffix to job-name when duplicating jobs";
+		
+		public static final String[] PREFERENCES_ORDER = {
+			OVERWRITE_VIDEO_FILE,
+			DISABLE_RENDERING,
+			DISABLE_SOUND,
+			FFW_SPEED_FIRST_STAGE,
+			FFW_SPEED_SECOND_STAGE,
+			DO_NOT_DELETE_CUT_DEMOS,
+			JOB_NAME_APPEND_DUPLICATE
+		};
+	}
+	
+	public static final String PREFERENCES_DIRNAME = "settings";
+	public static final String LOGS_DIRNAME = "logs";
+	public static final String PLUGINS_DIRNAME = "plugins";
+	public static final String APP_PREFERENCES_FILENAME = "app_preferences.xml";
+	public static final String JOBQUEUE_FILENAME = "jobs.dat";
+	
+	public static final int STATE_WORKING = 0;
+	public static final int STATE_IDLE = 1;
+	
+	private RecorderJobPoolExecutor poolExecutor;
+	private List<RecordJob> jobs;
+	private NDRPreferences preferences = null;
+	private List<DemoRecorderUI> registeredUserInterfaces;
+	private List<EncoderPlugin> encoderPlugins;
+	private int state = STATE_IDLE;
+	
+	public DemoRecorderApplication() {
+		poolExecutor = new RecorderJobPoolExecutor();
+		jobs = new CopyOnWriteArrayList<RecordJob>();
+		this.registeredUserInterfaces = new ArrayList<DemoRecorderUI>();
+		this.encoderPlugins = new ArrayList<EncoderPlugin>();
+		this.getPreferences();
+		this.loadPlugins();
+		this.configurePlugins();
+		this.loadJobQueue();
+	}
+	
+	public void setPreference(String category, String preference, boolean value) {
+		this.preferences.setProperty(category, preference, String.valueOf(value));
+	}
+	
+	public void setPreference(String category, String preference, int value) {
+		this.preferences.setProperty(category, preference, String.valueOf(value));
+	}
+	
+	public void setPreference(String category, String preference, String value) {
+		this.preferences.setProperty(category, preference, value);
+	}
+	
+	public NDRPreferences getPreferences() {
+		if (this.preferences == null) {
+			this.preferences = new NDRPreferences();
+			this.createPreferenceDefaultValues();
+			File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);
+			if (preferencesFile.exists()) {
+				FileInputStream fis = null;
+				try {
+					fis = new FileInputStream(preferencesFile);
+					this.preferences.loadFromXML(fis);
+				} catch (Exception e) {
+					DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the application preferences file!", e, true);
+				}
+			}
+		}
+		
+		return this.preferences;
+	}
+	
+	private void createPreferenceDefaultValues() {
+		this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE, "false");
+		this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING, "true");
+		this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND, "true");
+		this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE, "100");
+		this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE, "10");
+		this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS, "false");
+		this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE, " duplicate");
+	}
+	
+	public void savePreferences() {
+		File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);
+		if (!preferencesFile.exists()) {
+			try {
+				preferencesFile.createNewFile();
+			} catch (IOException e) {
+				File parentDir = preferencesFile.getParentFile();
+				if (!parentDir.exists()) {
+					try {
+						if (parentDir.mkdirs() == true) {
+							try {
+								preferencesFile.createNewFile();
+							} catch (Exception ex) {}
+						}
+					} catch (Exception ex) {}
+				}
+			}
+		}
+		
+		if (!preferencesFile.exists()) {
+			DemoRecorderException ex = new DemoRecorderException("Could not create the preferences file " + preferencesFile.getAbsolutePath());
+			DemoRecorderUtils.showNonCriticalErrorDialog(ex);
+			return;
+		}
+		
+		FileOutputStream fos;
+		try {
+			fos = new FileOutputStream(preferencesFile);
+		} catch (FileNotFoundException e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath() + ". Unsufficient rights?", e, true);
+			return;
+		}
+		try {
+			this.preferences.storeToXML(fos, null);
+		} catch (IOException e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath(), e, true);
+		}
+	}
+	
+	public List<RecordJob> getRecordJobs() {
+		return new ArrayList<RecordJob>(this.jobs);
+	}
+	
+	public void startRecording() {
+		if (this.state != STATE_WORKING) {
+			this.state = STATE_WORKING;
+			
+			for (RecordJob currentJob : this.jobs) {
+				if (currentJob.getState() == RecordJob.State.WAITING) {
+					this.poolExecutor.runJob(currentJob);
+				}
+			}
+			
+			//notify ourself when job is done
+			this.poolExecutor.runJob(new RecordsDoneJob(this));
+		}
+	}
+	
+	public void recordSelectedJobs(List<RecordJob> jobList) {
+		if (this.state == STATE_IDLE) {
+			this.state = STATE_WORKING;
+			for (RecordJob currentJob : jobList) {
+				if (currentJob.getState() == RecordJob.State.WAITING) {
+					this.poolExecutor.runJob(currentJob);
+				}
+			}
+			
+			//notify ourself when job is done
+			this.poolExecutor.runJob(new RecordsDoneJob(this));
+		}
+	}
+	
+	public void executePluginForSelectedJobs(EncoderPlugin plugin, List<RecordJob> jobList) {
+		if (this.state == STATE_IDLE) {
+			this.state = STATE_WORKING;
+			for (RecordJob currentJob : jobList) {
+				if (currentJob.getState() == RecordJob.State.DONE) {
+					this.poolExecutor.runJob(new EncoderJob(currentJob, plugin));
+				}
+			}
+			
+			//notify ourself when job is done
+			this.poolExecutor.runJob(new RecordsDoneJob(this));
+		}
+	}
+	
+	public void notifyAllJobsDone() {
+		this.state = STATE_IDLE;
+		
+		//notify all UIs
+		for (DemoRecorderUI currentUI : this.registeredUserInterfaces) {
+			currentUI.recordingFinished();
+		}
+	}
+	
+	public synchronized void stopRecording() {
+		if (this.state == STATE_WORKING) {
+			//clear the queue of the threadpoolexecutor and add the GUI/applayer notify job again
+			this.poolExecutor.clearUnfinishedJobs();
+			this.poolExecutor.runJob(new RecordsDoneJob(this));
+		}
+	}
+	
+	public RecordJob createRecordJob(
+		String name,
+		File enginePath,
+		String engineParameters,
+		File demoFile,
+		String relativeDemoPath,
+		File dpVideoPath,
+		File videoDestination,
+		String executeBeforeCap,
+		String executeAfterCap,
+		float startSecond,
+		float endSecond
+	) {
+		int jobIndex = -1;
+		if (name == null || name.equals("")) {
+			//we don't have a name, so use a generic one 
+			jobIndex = this.getNewJobIndex();
+			name = "Job " + jobIndex;
+		} else {
+			//just use the name and keep jobIndex at -1. Jobs with real names don't need an index
+		}
+		
+		
+		
+		RecordJob newJob = new RecordJob(
+			this,
+			name,
+			jobIndex,
+			enginePath,
+			engineParameters,
+			demoFile,
+			relativeDemoPath,
+			dpVideoPath,
+			videoDestination,
+			executeBeforeCap,
+			executeAfterCap,
+			startSecond,
+			endSecond
+		);
+		this.jobs.add(newJob);
+		this.fireUserInterfaceUpdate(newJob);
+		
+		return newJob;
+	}
+	
+	public synchronized boolean deleteRecordJob(RecordJob job) {
+		if (!this.jobs.contains(job)) {
+			return false;
+		}
+		
+		//don't delete jobs that are scheduled for execution
+		if (this.poolExecutor.getJobList().contains(job)) {
+			return false;
+		}
+		
+		this.jobs.remove(job);
+		return true;
+	}
+	
+	public void addUserInterfaceListener(DemoRecorderUI ui) {
+		this.registeredUserInterfaces.add(ui);
+	}
+	
+	/**
+	 * Makes sure that all registered user interfaces can update their view/display.
+	 * @param job either a job that's new to the UI, or one the UI already knows but of which details changed
+	 */
+	public void fireUserInterfaceUpdate(RecordJob job) {
+		for (DemoRecorderUI ui : this.registeredUserInterfaces) {
+			ui.RecordJobPropertiesChange(job);
+		}
+	}
+	
+	public int getNewJobIndex() {
+		int jobIndex;
+		if (this.jobs.size() == 0) {
+			jobIndex = 1;
+		} else {
+			int greatestIndex = -1;
+			for (RecordJob j : this.jobs) {
+				if (j.getJobIndex() > greatestIndex) {
+					greatestIndex = j.getJobIndex();
+				}
+			}
+			if (greatestIndex == -1) {
+				jobIndex = 1;
+			} else {
+				jobIndex = greatestIndex + 1;
+			}
+		}
+		
+		return jobIndex;
+	}
+	
+	private void loadJobQueue() {
+		File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);
+		this.loadJobQueue(defaultFile);
+	}
+	
+	@SuppressWarnings("unchecked")
+	public void loadJobQueue(File path) {
+		if (!path.exists()) {
+			return;
+		}
+		
+		try {
+			FileInputStream fin = new FileInputStream(path);
+			ObjectInputStream ois = new ObjectInputStream(fin);
+			this.jobs = (List<RecordJob>) ois.readObject();
+			for (RecordJob currentJob : this.jobs) {
+				currentJob.setAppLayer(this);
+			}
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the job queue file " + path.getAbsolutePath(), e, true);
+		}
+		
+	}
+	
+	public void saveJobQueue() {
+		File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);
+		this.saveJobQueue(defaultFile);
+	}
+	
+	public void saveJobQueue(File path) {
+		if (!path.exists()) {
+			try {
+				path.createNewFile();
+			} catch (IOException e) {
+				File parentDir = path.getParentFile();
+				if (!parentDir.exists()) {
+					try {
+						if (parentDir.mkdirs() == true) {
+							try {
+								path.createNewFile();
+							} catch (Exception ex) {}
+						}
+					} catch (Exception ex) {}
+				}
+			}
+		}
+		
+		String exceptionMessage = "Could not save the job queue file " + path.getAbsolutePath();
+		
+		if (!path.exists()) {
+			DemoRecorderException ex = new DemoRecorderException(exceptionMessage);
+			DemoRecorderUtils.showNonCriticalErrorDialog(ex);
+			return;
+		}
+		
+		//make sure that for the next start of the program the state is set to waiting again
+		for (RecordJob job : this.jobs) {
+			if (job.getState() == RecordJob.State.PROCESSING) {
+				job.setState(RecordJob.State.WAITING);
+			}
+			job.setAppLayer(null); //we don't want to serialize the app layer!
+		}
+		
+		try {
+			FileOutputStream fout = new FileOutputStream(path);
+			ObjectOutputStream oos = new ObjectOutputStream(fout);
+			oos.writeObject(this.jobs);
+			oos.close();
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+		}
+		
+		//we sometimes also save the jobqueue and don't exit the program, so restore the applayer again
+		for (RecordJob job : this.jobs) {
+			job.setAppLayer(this);
+		}
+	}
+	
+	public void shutDown() {
+		this.poolExecutor.shutDown();
+		this.savePreferences();
+		this.saveJobQueue();
+	}
+	
+	public int getState() {
+		return this.state;
+	}
+	
+	private void loadPlugins() {
+		File pluginDir = DemoRecorderUtils.computeLocalFile(PLUGINS_DIRNAME, "");
+
+		if (!pluginDir.exists()) {
+			pluginDir.mkdir();
+		}
+
+		File[] jarFiles = pluginDir.listFiles();
+
+		List<URL> urlList = new ArrayList<URL>();
+		for (File f : jarFiles) {
+			try {
+				urlList.add(f.toURI().toURL());
+			} catch (MalformedURLException ex) {}
+		}
+		ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
+		URL[] urls = new URL[urlList.size()];
+		urls = urlList.toArray(urls);
+		URLClassLoader classLoader = new URLClassLoader(urls, parentLoader);
+		
+		ServiceLoader<EncoderPlugin> loader = ServiceLoader.load(EncoderPlugin.class, classLoader);
+		for (EncoderPlugin implementation : loader) {
+			this.encoderPlugins.add(implementation);
+		}
+	}
+	
+	private void configurePlugins() {
+		for (EncoderPlugin plugin : this.encoderPlugins) {
+			plugin.setApplicationLayer(this);
+			Properties pluginPreferences = plugin.getGlobalPreferences();
+			for (Object preference : pluginPreferences.keySet()) {
+				String preferenceString = (String) preference;
+				
+				if (this.preferences.getProperty(plugin.getName(), preferenceString) == null) {
+					String defaultValue = pluginPreferences.getProperty(preferenceString);
+					this.preferences.setProperty(plugin.getName(), preferenceString, defaultValue);
+				}
+			}
+		}
+	}
+
+	public List<EncoderPlugin> getEncoderPlugins() {
+		return encoderPlugins;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,13 @@
+package com.nexuiz.demorecorder.application;
+
+public class DemoRecorderException extends RuntimeException {
+	
+	private static final long serialVersionUID = 965053013957793155L;
+	public DemoRecorderException(String message) {
+		super(message);
+	}
+	public DemoRecorderException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,95 @@
+package com.nexuiz.demorecorder.application;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jdesktop.swingx.JXErrorPane;
+import org.jdesktop.swingx.error.ErrorInfo;
+
+public class DemoRecorderUtils {
+	
+	public static void showNonCriticalErrorDialog(Throwable e) {
+		if (!(e instanceof DemoRecorderException)) {
+			e = new DemoRecorderException("Internal error", e);
+		}
+		ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);
+		JXErrorPane.showDialog(null, info);
+	}
+	
+	/**
+	 * Shows an error dialog that contains the stack trace, catching the exception so that the program flow
+	 * won't be interrupted.
+	 * This method will maybe wrap e in a DemoRecorderException with the given message.
+	 * @param customMessage
+	 * @param e
+	 * @param wrapException set to true if Exception should be wrapped into a DemoRecorderException
+	 */
+	public static void showNonCriticalErrorDialog(String customMessage, Throwable e, boolean wrapException) {
+		Throwable ex = e;
+		if (wrapException && !(e instanceof DemoRecorderException)) {
+			ex = new DemoRecorderException(customMessage, e);
+		}
+		
+		ErrorInfo info = new ErrorInfo("Error occurred", ex.getMessage(), null, null, ex, null, null);
+		JXErrorPane.showDialog(null, info);
+	}
+	
+	public static File computeLocalFile(String subDir, String fileName) {
+		String path = System.getProperty("user.dir");
+		if (subDir != null && !subDir.equals("")) {
+			path += File.separator + subDir;
+		}
+		path += File.separator + fileName;
+		return new File(path);
+	}
+	
+	/**
+	 * Returns just the name of the file for a given File. E.g. if the File points to
+	 * /home/someuser/somedir/somefile.end the function will return "somefile.end"
+	 * @param file
+	 * @return just the name of the file
+	 */
+	public static String getJustFileNameOfPath(File file) {
+		String fileString = file.getAbsolutePath();
+		int lastIndex = fileString.lastIndexOf(File.separator);
+		String newString = fileString.substring(lastIndex+1, fileString.length());
+		return newString;
+	}
+	
+	/**
+	 * Attempts to create an empty file (unless it already exists), including the creation
+	 * of parent directories. If it succeeds to do so (or if the file already existed), true
+	 * will be returned. Otherwise false will be returned
+	 * @param file the file to be created
+	 * @return true if file already existed or could successfully created, false otherwise
+	 */
+	public static boolean attemptFileCreation(File file) {
+		if (!file.exists()) {
+			try {
+				file.createNewFile();
+				return true;
+			} catch (IOException e) {
+				File parentDir = file.getParentFile();
+				if (!parentDir.exists()) {
+					try {
+						if (parentDir.mkdirs() == true) {
+							try {
+								file.createNewFile();
+								return true;
+							} catch (Exception ex) {}
+						}
+					} catch (Exception ex) {}
+				}
+				return false;
+			}
+		} else {
+			return true;
+		}
+	}
+	
+	public static final String getFileExtension(File file) {
+		String fileName = file.getAbsolutePath();
+		String ext = (fileName.lastIndexOf(".") == -1) ? "" : fileName.substring(fileName.lastIndexOf(".") + 1,fileName.length());
+		return ext;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,66 @@
+package com.nexuiz.demorecorder.application;
+
+import java.util.Properties;
+
+/**
+ * Class that stores the application and global plug-in preferences of the Nexuiz
+ * Demo Recorder application. Set and Get property methods have been modified to 
+ * now supply a category.
+ */
+public class NDRPreferences extends Properties {
+	
+	private static final long serialVersionUID = 4363913054294979418L;
+	private static final String CONCATENATOR = ".";
+	/**
+	 * Category that defines a setting to be a setting of the NDR application itself
+	 * (and not of one of the plugins).
+	 */
+	public static final String MAIN_APPLICATION = "NDR";
+
+	/**
+     * Searches for the property with the specified key in this property list.
+     * If the key is not found in this property list, the default property list,
+     * and its defaults, recursively, are then checked. The method returns
+     * <code>null</code> if the property is not found.
+     *
+     * @param   category the category of the setting
+     * @param   key   the property key.
+     * @return  the value in this property list with the specified category+key value.
+     */
+	public String getProperty(String category, String key) {
+		return getProperty(getConcatenatedKey(category, key));
+	}
+	
+	/**
+     * Calls the <tt>Hashtable</tt> method <code>put</code>. Provided for
+     * parallelism with the <tt>getProperty</tt> method. Enforces use of
+     * strings for property keys and values. The value returned is the
+     * result of the <tt>Hashtable</tt> call to <code>put</code>.
+     *
+     * @param category the category of the setting
+     * @param key the key to be placed into this property list.
+     * @param value the value corresponding to <tt>key</tt>.
+     * @return     the previous value of the specified key in this property
+     *             list, or <code>null</code> if it did not have one.
+     */
+	public void setProperty(String category, String key, String value) {
+		setProperty(getConcatenatedKey(category, key), value);
+	}
+	
+	/**
+	 * Returns only the category of a key that is a concatenated string of category and key.
+	 * @param concatenatedString
+	 * @return
+	 */
+	public static String getCategory(String concatenatedString) {
+		return concatenatedString.substring(0, concatenatedString.indexOf(CONCATENATOR));
+	}
+	
+	public static String getKey(String concatenatedString) {
+		return concatenatedString.substring(concatenatedString.indexOf(CONCATENATOR) + 1, concatenatedString.length());
+	}
+	
+	public static String getConcatenatedKey(String category, String key) {
+		return category + CONCATENATOR + key;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,48 @@
+package com.nexuiz.demorecorder.application;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public class RecorderJobPoolExecutor {
+	
+	private int poolSize = 1;
+	private int maxPoolSize = 1;
+	private long keepAliveTime = 10;
+	private ThreadPoolExecutor threadPool = null;
+	private ArrayBlockingQueue<Runnable> queue = null;
+
+	public RecorderJobPoolExecutor() {
+		queue = new ArrayBlockingQueue<Runnable>(99999);
+		threadPool = new ThreadPoolExecutor(poolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, queue);
+	}
+
+	public void runJob(Runnable task) {
+		threadPool.execute(task);
+	}
+	
+	public void clearUnfinishedJobs() {
+		threadPool.getQueue().clear();
+	}
+
+	public void shutDown() {
+		threadPool.shutdownNow();
+	}
+	
+	public synchronized List<RecordJob> getJobList() {
+		List<RecordJob> list = new ArrayList<RecordJob>();
+		for (Runnable job : this.queue) {
+			try {
+				RecordJob j = (RecordJob)job;
+				list.add(j);
+			} catch (ClassCastException e) {}
+		}
+		
+		return list;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,220 @@
+package com.nexuiz.demorecorder.application.democutter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+public class DemoCutter {
+
+	private static final byte CDTRACK_SEPARATOR = 0x0A;
+
+	private DataInputStream inStream;
+	private DataOutputStream outStream;
+	private File inFile;
+	private File outFile;
+
+	/**
+	 * Calls the cutDemo method with reasonable default values for the second and first fast-forward stage.
+	 * @param inFile @see other cutDemo method
+	 * @param outFile @see other cutDemo method
+	 * @param startTime @see other cutDemo method
+	 * @param endTime @see other cutDemo method
+	 * @param injectAtStart @see other cutDemo method
+	 * @param injectBeforeCap @see other cutDemo method
+	 * @param injectAfterCap @see other cutDemo method
+	 */
+	public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap) {
+		this.cutDemo(inFile, outFile, startTime, endTime, injectAtStart, injectBeforeCap, injectAfterCap, 100, 10);
+	}
+	
+	/**
+	 * Cuts the demo by injecting a 2-phase fast forward command until startTime is reached, then injects the cl_capturevideo 1 command
+	 * and once endTime is reached the cl_capturevideo 0 command is injected.
+	 * @param inFile the original demo file
+	 * @param outFile the new cut demo file
+	 * @param startTime when to start capturing (use the gametime in seconds)
+	 * @param endTime when to stop capturing
+	 * @param injectAtStart a String that will be injected right at the beginning of the demo
+	 * 						can be anything that would make sense and can be parsed by DP's console
+	 * @param injectBeforeCap a String that will be injected 5 seconds before capturing starts
+	 * @param injectAfterCap a String that will be injected shortly after capturing ended
+	 * @param ffwSpeedFirstStage fast-forward speed at first stage, when the startTime is still about a minute away (use high values, e.g. 100)
+	 * @param ffwSpeedSecondStage fast-forward speed when coming a few seconds close to startTime, use lower values e.g. 5 or 10
+	 */
+	public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap, int ffwSpeedFirstStage, int ffwSpeedSecondStage) {
+		this.inFile = inFile;
+		this.outFile = outFile;
+		this.prepareStreams();
+		this.readCDTrack();
+		injectAfterCap = this.checkInjectString(injectAfterCap);
+		injectAtStart = this.checkInjectString(injectAtStart);
+		injectBeforeCap = this.checkInjectString(injectBeforeCap);
+
+		byte[] data;
+		float svctime = -1;
+		boolean firstLoop = true;
+		String injectBuffer = "";
+		int demoStarted = 0;
+		boolean endIsReached = false;
+		boolean finalInjectionDone = false;
+		boolean disconnectIssued = false;
+		int svcLoops = 0;
+		float firstSvcTime = -1;
+		float lastSvcTime = -1;
+		
+		try {
+			while (true) {
+				DemoPacket demoPacket = new DemoPacket(this.inStream);
+				if (demoPacket.isEndOfFile()) {
+					break;
+				}
+				
+				if (demoPacket.isClientToServerPacket()) {
+					try {
+						this.outStream.write(demoPacket.getOriginalLengthAsByte());
+						this.outStream.write(demoPacket.getAngles());
+						this.outStream.write(demoPacket.getOriginalData());
+					} catch (IOException e) {
+						throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);
+					}
+					
+					continue;
+				}
+
+				if (demoPacket.getSvcTime() != -1) {
+					svctime = demoPacket.getSvcTime();
+				}
+
+				if (svctime != -1) {
+					if (firstSvcTime == -1) {
+						firstSvcTime = svctime;
+					}
+					lastSvcTime = svctime;
+					
+					if (firstLoop) {
+						injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedFirstStage + "\n\000";
+						firstLoop = false;
+					}
+					if (demoStarted < 1 && svctime > (startTime - 50)) {
+						if (svcLoops == 0) {
+							//make sure that for short demos (duration less than 50 sec)
+							//the injectAtStart is still honored
+							injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedSecondStage + "\n\000";
+						} else {
+							injectBuffer = "\011\nslowmo " + ffwSpeedSecondStage + "\n\000";
+						}
+						
+						demoStarted = 1;
+					}
+					if (demoStarted < 2 && svctime > (startTime - 5)) {
+						injectBuffer = "\011\nslowmo 1;" + injectBeforeCap +"\n\000";
+						demoStarted = 2;
+					}
+					if (demoStarted < 3 && svctime > startTime) {
+						injectBuffer = "\011\ncl_capturevideo 1\n\000";
+						demoStarted = 3;
+					}
+					if (!endIsReached && svctime > endTime) {
+						injectBuffer = "\011\ncl_capturevideo 0\n\000";
+						endIsReached = true;
+					}
+					if (endIsReached && !finalInjectionDone && svctime > (endTime + 1)) {
+						injectBuffer = "\011\n" + injectAfterCap + "\n\000";
+						finalInjectionDone = true;
+					}
+					if (finalInjectionDone && !disconnectIssued && svctime > (endTime + 2)) {
+						injectBuffer = "\011\ndisconnect\n\000";
+						disconnectIssued = true;
+					}
+					svcLoops++;
+				}
+
+				byte[] injectBufferAsBytes = null;
+				try {
+					injectBufferAsBytes = injectBuffer.getBytes("US-ASCII");
+				} catch (UnsupportedEncodingException e) {
+					throw new DemoCutterException("Could not convert String to bytes using US-ASCII charset!", e);
+				}
+
+				data = demoPacket.getOriginalData();
+				if ((injectBufferAsBytes.length + data.length) < 65536) {
+					data = DemoCutterUtils.mergeByteArrays(injectBufferAsBytes, data);
+					injectBuffer = "";
+				}
+				
+				byte[] newLengthLittleEndian = DemoCutterUtils.convertLittleEndian(data.length);
+				try {
+					this.outStream.write(newLengthLittleEndian);
+					this.outStream.write(demoPacket.getAngles());
+					this.outStream.write(data);
+				} catch (IOException e) {
+					throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);
+				}
+
+			}
+			
+			if (startTime < firstSvcTime) {
+				throw new DemoCutterException("Start time for the demo is " + startTime + ", but demo doesn't start before " + firstSvcTime);
+			}
+			if (endTime > lastSvcTime) {
+				throw new DemoCutterException("End time for the demo is " + endTime + ", but demo already stops at " + lastSvcTime);
+			}
+		} catch (DemoCutterException e) {
+			throw e;
+		} catch (Throwable e) {
+			throw new DemoCutterException("Internal error in demo cutter sub-route (invalid demo file?)", e);
+		} finally {
+			try {
+				this.outStream.close();
+				this.inStream.close();
+			} catch (IOException e) {}
+		}
+	}
+
+	
+
+	/**
+	 * Seeks forward in the inStream until CDTRACK_SEPARATOR byte was reached.
+	 * All the content is copied to the outStream.
+	 */
+	private void readCDTrack() {
+		byte lastByte;
+		try {
+			while ((lastByte = inStream.readByte()) != CDTRACK_SEPARATOR) {
+				this.outStream.write(lastByte);
+			}
+			this.outStream.write(CDTRACK_SEPARATOR);
+		} catch (EOFException e) {
+			throw new DemoCutterException("Unexpected EOF occurred when reading CD track of demo " + inFile.getPath(), e);
+		}
+		catch (IOException e) {
+			throw new DemoCutterException("Unexpected I/O Exception occurred when reading CD track of demo " + inFile.getPath(), e);
+		}
+	}
+
+	private void prepareStreams() {
+		try {
+			this.inStream = new DataInputStream(new FileInputStream(this.inFile));
+		} catch (FileNotFoundException e) {
+			throw new DemoCutterException("Could not open demo file " + inFile.getPath(), e);
+		}
+		
+		try {
+			this.outStream = new DataOutputStream(new FileOutputStream(this.outFile));
+		} catch (FileNotFoundException e) {
+			throw new DemoCutterException("Could not open demo file " + outFile.getPath(), e);
+		}
+	}
+	
+	private String checkInjectString(String injectionString) {
+		while (injectionString.endsWith(";") || injectionString.endsWith("\n")) {
+			injectionString = injectionString.substring(0, injectionString.length()-1);
+		}
+		return injectionString;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,14 @@
+package com.nexuiz.demorecorder.application.democutter;
+
+public class DemoCutterException extends RuntimeException {
+	
+	private static final long serialVersionUID = -1419472153834762285L;
+	
+	public DemoCutterException(String message) {
+		super(message);
+	}
+	public DemoCutterException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,40 @@
+package com.nexuiz.demorecorder.application.democutter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+public class DemoCutterUtils {
+
+	public static float byteArrayToFloat(byte[] array) {
+		byte[] tmp = new byte[4];
+  		System.arraycopy(array, 0, tmp, 0, 4);
+		int accum = 0;
+		int i = 0;
+		for (int shiftBy = 0; shiftBy < 32; shiftBy += 8) {
+			accum |= ((long) (tmp[i++] & 0xff)) << shiftBy;
+		}
+		return Float.intBitsToFloat(accum);
+	}
+
+	public static byte[] convertLittleEndian(int i) {
+		ByteBuffer bb = ByteBuffer.allocate(4);
+		bb.order(ByteOrder.LITTLE_ENDIAN);
+		bb.putInt(i);
+		return bb.array();
+	}
+
+	public static byte[] mergeByteArrays(byte[] array1, byte[] array2) {
+		ByteBuffer bb = ByteBuffer.allocate(array1.length + array2.length);
+		bb.put(array1);
+		bb.put(array2);
+		return bb.array();
+	}
+
+	public static int convertLittleEndian(byte[] b) {
+		ByteBuffer bb = ByteBuffer.allocate(4);
+		bb.order(ByteOrder.LITTLE_ENDIAN);
+		bb.put(b);
+		bb.position(0);
+		return bb.getInt();
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,102 @@
+package com.nexuiz.demorecorder.application.democutter;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+
+public class DemoPacket {
+	
+	private static final int DEMOMSG_CLIENT_TO_SERVER = 0x80000000;
+	
+	private DataInputStream inStream = null;
+	private boolean isEndOfFile = false;
+	private byte[] buffer = new byte[4]; //contains packet length
+	private byte[] angles = new byte[12];
+	private byte[] data;
+	private int packetLength;
+	private boolean isClientToServer = false;
+	private float svcTime = -1;
+
+	public DemoPacket(DataInputStream inStream) {
+		this.inStream = inStream;
+		
+		try {
+			inStream.readFully(buffer);
+		} catch (EOFException e) {
+			this.isEndOfFile = true;
+			return;
+		} catch (IOException e) {
+			throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");
+		}
+		
+		packetLength = DemoCutterUtils.convertLittleEndian(buffer);
+		if ((packetLength & DEMOMSG_CLIENT_TO_SERVER) != 0) {
+			packetLength = packetLength & ~DEMOMSG_CLIENT_TO_SERVER;
+
+			this.isClientToServer = true;
+			this.readAnglesAndData();
+			return;
+		}
+		
+		this.readAnglesAndData();
+		
+		// extract svc_time
+		this.readSvcTime();
+		
+	}
+	
+	public boolean isEndOfFile() {
+		return this.isEndOfFile;
+	}
+	
+	public boolean isClientToServerPacket() {
+		return this.isClientToServer;
+	}
+	
+	public byte[] getOriginalLengthAsByte() {
+		return this.buffer;
+	}
+	
+	public byte[] getAngles() {
+		return this.angles;
+	}
+	
+	public byte[] getOriginalData() {
+		return this.data;
+	}
+	
+	public float getSvcTime() {
+		return this.svcTime;
+	}
+	
+	private void readAnglesAndData() {
+		// read angles
+		try {
+			inStream.readFully(angles);
+		} catch (EOFException e) {
+			throw new DemoCutterException("Invalid Demo Packet");
+		} catch (IOException e) {
+			throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");
+		}
+
+		// read data
+		data = new byte[packetLength];
+		try {
+			inStream.readFully(data);
+		} catch (EOFException e) {
+			throw new DemoCutterException("Invalid Demo Packet");
+		} catch (IOException e) {
+			throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");
+		}
+	}
+	
+	private void readSvcTime() {
+		if (data[0] == 0x07) {
+			ByteBuffer bb = ByteBuffer.allocate(4);
+			bb.put(data, 1, 4);
+			byte[] array = bb.array();
+			this.svcTime = DemoCutterUtils.byteArrayToFloat(array);
+		}
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,24 @@
+package com.nexuiz.demorecorder.application.jobs;
+
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+
+/**
+ * Job for the ThreadPoolExecutor that will just call the encoder-plugin's execute
+ * method.
+ */
+public class EncoderJob implements Runnable {
+	
+	private RecordJob job;
+	private EncoderPlugin plugin;
+	
+	public EncoderJob(RecordJob job, EncoderPlugin plugin) {
+		this.job = job;
+		this.plugin = plugin;
+	}
+
+	@Override
+	public void run() {
+		this.job.executePlugin(this.plugin);
+	}
+
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,636 @@
+package com.nexuiz.demorecorder.application.jobs;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;
+import com.nexuiz.demorecorder.application.democutter.DemoCutter;
+import com.nexuiz.demorecorder.application.democutter.DemoCutterException;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;
+
+public class RecordJob implements Runnable, Serializable {
+	
+	private static final long serialVersionUID = -4585637490345587912L;
+
+	public enum State {
+		WAITING, PROCESSING, ERROR, ERROR_PLUGIN, DONE
+	}
+	
+	public static final String CUT_DEMO_FILE_SUFFIX = "_autocut";
+	public static final String CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE = "autocap";
+	public static final String CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE = "1234567";
+	protected static final String[] VIDEO_FILE_ENDINGS = {"avi", "ogv"};
+	
+	private DemoRecorderApplication appLayer;
+	protected String jobName;
+	private int jobIndex;
+	protected File enginePath;
+	protected String engineParameters;
+	protected File demoFile;
+	protected String relativeDemoPath;
+	protected File dpVideoPath;
+	protected File videoDestination;
+	protected String executeBeforeCap;
+	protected String executeAfterCap;
+	protected float startSecond;
+	protected float endSecond;
+	protected State state = State.WAITING;
+	protected DemoRecorderException lastException = null;
+	
+	/**
+	 * Points to the actual final file, including possible suffixes, e.g. _copy1, and the actualy ending
+	 */
+	protected File actualVideoDestination = null;
+	/**
+	 * Map that identifies the plug-in by its name (String) and maps to the plug-in's job-specific settings
+	 */
+	protected Map<String, Properties> encoderPluginSettings = new HashMap<String, Properties>();
+	
+	private List<File> cleanUpFiles = null;
+	
+	public RecordJob(
+		DemoRecorderApplication appLayer,
+		String jobName,
+		int jobIndex,
+		File enginePath,
+		String engineParameters,
+		File demoFile,
+		String relativeDemoPath,
+		File dpVideoPath,
+		File videoDestination,
+		String executeBeforeCap,
+		String executeAfterCap,
+		float startSecond,
+		float endSecond
+	) {
+		this.appLayer = appLayer;
+		this.jobName = jobName;
+		this.jobIndex = jobIndex;
+		
+		this.setEnginePath(enginePath);
+		this.setEngineParameters(engineParameters);
+		this.setDemoFile(demoFile);
+		this.setRelativeDemoPath(relativeDemoPath);
+		this.setDpVideoPath(dpVideoPath);
+		this.setVideoDestination(videoDestination);
+		this.setExecuteBeforeCap(executeBeforeCap);
+		this.setExecuteAfterCap(executeAfterCap);
+		this.setStartSecond(startSecond);
+		this.setEndSecond(endSecond);
+	}
+	
+	public RecordJob(){}
+	
+	/**
+	 * Constructor that can be used by other classes such as job templates. Won't throw exceptions
+	 * as it won't check the input for validity.
+	 */
+	protected RecordJob(
+		File enginePath,
+		String engineParameters,
+		File demoFile,
+		String relativeDemoPath,
+		File dpVideoPath,
+		File videoDestination,
+		String executeBeforeCap,
+		String executeAfterCap,
+		float startSecond,
+		float endSecond
+		) {
+		this.jobIndex = -1;
+		this.enginePath = enginePath;
+		this.engineParameters = engineParameters;
+		this.demoFile = demoFile;
+		this.relativeDemoPath = relativeDemoPath;
+		this.dpVideoPath = dpVideoPath;
+		this.videoDestination = videoDestination;
+		this.executeBeforeCap = executeBeforeCap;
+		this.executeAfterCap = executeAfterCap;
+		this.startSecond = startSecond;
+		this.endSecond = endSecond;
+	}
+	
+	public void execute() {
+		if (this.state == State.PROCESSING) {
+			return;
+		}
+		boolean errorOccurred = false;
+		this.setState(State.PROCESSING);
+		this.appLayer.fireUserInterfaceUpdate(this);
+		cleanUpFiles = new ArrayList<File>();
+		
+		File cutDemo = computeCutDemoFile();
+		cutDemo.delete(); //delete possibly old cutDemoFile
+		
+		EncoderPlugin recentEncoder = null;
+		
+		try {
+			this.cutDemo(cutDemo);
+			this.removeOldAutocaps();
+			this.recordClip(cutDemo);
+			this.moveRecordedClip();
+			for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
+				recentEncoder = plugin;
+				plugin.executeEncoder(this);
+			}
+		} catch (DemoRecorderException e) {
+			errorOccurred = true;
+			this.lastException = e;
+			this.setState(State.ERROR);
+		} catch (EncoderPluginException e) {
+			errorOccurred = true;
+			this.lastException = new DemoRecorderException("Encoder plug-in " + recentEncoder.getName() + " failed: "
+					+ e.getMessage(), e);
+			this.setState(State.ERROR_PLUGIN);
+		} catch (Exception e) {
+			errorOccurred = true;
+			this.lastException = new DemoRecorderException("Executing job failed, click on details for more info", e);
+		} finally {
+			NDRPreferences preferences = this.appLayer.getPreferences();
+			if (!Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS))) {
+				cleanUpFiles.add(cutDemo);
+			}
+			if (!errorOccurred) {
+				this.setState(State.DONE);
+			}
+			this.cleanUpFiles();
+			this.appLayer.fireUserInterfaceUpdate(this);
+			this.appLayer.saveJobQueue();
+		}
+	}
+	
+	/**
+	 * Will execute just the specified encoder plug-in on an already "done" job.
+	 * @param pluginName
+	 */
+	public void executePlugin(EncoderPlugin plugin) {
+		if (this.getState() != State.DONE) {
+			return;
+		}
+		this.setState(State.PROCESSING);
+		this.appLayer.fireUserInterfaceUpdate(this);
+		
+		try {
+			plugin.executeEncoder(this);
+			this.setState(State.DONE);
+		} catch (EncoderPluginException e) {
+			this.lastException = new DemoRecorderException("Encoder plug-in " + plugin.getName() + " failed: "
+					+ e.getMessage(), e);
+			this.setState(State.ERROR_PLUGIN);
+		}
+		
+		this.appLayer.fireUserInterfaceUpdate(this);
+	}
+	
+	private void cleanUpFiles() {
+		try {
+			for (File f : this.cleanUpFiles) {
+				f.delete();
+			}
+		} catch (Exception e) {}
+		
+	}
+	
+	private void moveRecordedClip() {
+		//1. Figure out whether the file is .avi or .ogv
+		File sourceFile = null;
+		for (String videoExtension : VIDEO_FILE_ENDINGS) {
+			String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
+			+ CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
+			File videoFile = new File(fileString);
+			if (videoFile.exists()) {
+				sourceFile = videoFile;
+				break;
+			}
+		}
+		
+		if (sourceFile == null) {
+			String p = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
+			+ CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE;
+			throw new DemoRecorderException("Could not locate the expected video file being generated by Nexuiz (should have been at "
+					+ p + ".avi/.ogv");
+		}
+		cleanUpFiles.add(sourceFile);
+		
+		File destinationFile = null;
+		NDRPreferences preferences = this.appLayer.getPreferences();
+		String sourceFileExtension = DemoRecorderUtils.getFileExtension(sourceFile);
+		String destinationFilePath = this.videoDestination + "." + sourceFileExtension;
+		destinationFile = new File(destinationFilePath);
+		if (destinationFile.exists()) {
+			if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE))) {
+				if (!destinationFile.delete()) {
+					throw new DemoRecorderException("Could not delete the existing video destinatin file " + destinationFile.getAbsolutePath()
+							+ " (application setting to overwrite existing video files is enabled!)");
+				}
+			} else {
+				destinationFilePath = this.videoDestination + "_copy" + this.getVideoDestinationCopyNr(sourceFileExtension) + "." + sourceFileExtension;
+				destinationFile = new File(destinationFilePath);
+			}
+		}
+		
+		//finally move the file
+		if (!sourceFile.renameTo(destinationFile)) {
+			cleanUpFiles.add(destinationFile);
+			throw new DemoRecorderException("Could not move the video file from " + sourceFile.getAbsolutePath()
+					+ " to " + destinationFile.getAbsolutePath());
+		}
+		
+		this.actualVideoDestination = destinationFile;
+	}
+	
+	/**
+	 * As destination video files, e.g. "test"[.avi] can already exist, we have to save the
+	 * the video file to a file name such as test_copy1 or test_copy2.
+	 * This function will figure out what the number (1, 2....) is.
+	 * @return
+	 */
+	private int getVideoDestinationCopyNr(String sourceFileExtension) {
+		int i = 1;
+		File lastFile;
+		while (true) {
+			lastFile = new File(this.videoDestination + "_copy" + i + "." + sourceFileExtension);
+			if (!lastFile.exists()) {
+				break;
+			}
+			
+			i++;
+		}
+		return i;
+	}
+
+	private File computeCutDemoFile() {
+		String origFileString = this.demoFile.getAbsolutePath();
+		int lastIndex = origFileString.lastIndexOf(File.separator);
+		String autoDemoFileName = origFileString.substring(lastIndex+1, origFileString.length());
+		//strip .dem ending
+		autoDemoFileName = autoDemoFileName.substring(0, autoDemoFileName.length()-4);
+		autoDemoFileName = autoDemoFileName + CUT_DEMO_FILE_SUFFIX + ".dem";
+		String finalString = origFileString.substring(0, lastIndex) + File.separator + autoDemoFileName;
+		File f = new File(finalString);
+		
+		return f;
+	}
+	
+	private void cutDemo(File cutDemo) {
+		String injectAtStart = "";
+		String injectBeforeCap = "";
+		String injectAfterCap = "";
+		
+		NDRPreferences preferences = this.appLayer.getPreferences();
+		if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING))) {
+			injectAtStart += "r_render 0;";
+			injectBeforeCap += "r_render 1;";
+		}
+		if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND))) {
+			injectAtStart += "set _volume $volume;volume 0;";
+			injectBeforeCap += "set volume $_volume;";
+		}
+		injectBeforeCap += this.executeBeforeCap + "\n";
+		injectBeforeCap += "set _cl_capturevideo_nameformat $cl_capturevideo_nameformat;set _cl_capturevideo_number $cl_capturevideo_number;";
+		injectBeforeCap += "cl_capturevideo_nameformat " + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE + ";";
+		injectBeforeCap += "cl_capturevideo_number " + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + ";";
+		
+		injectAfterCap += this.executeAfterCap + "\n";
+		injectAfterCap += "cl_capturevideo_nameformat $_cl_capturevideo_nameformat;cl_capturevideo_number $_cl_capturevideo_number;";
+		
+		
+		DemoCutter cutter = new DemoCutter();
+		int fwdSpeedFirstStage, fwdSpeedSecondStage;
+		try {
+			fwdSpeedFirstStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE));
+			fwdSpeedSecondStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE));
+		} catch (NumberFormatException e) {
+			throw new DemoRecorderException("Make sure that you specified valid numbers for the settings "
+					+ Preferences.FFW_SPEED_FIRST_STAGE + " and " + Preferences.FFW_SPEED_SECOND_STAGE, e);
+		}
+		
+		try {
+			cutter.cutDemo(
+				this.demoFile,
+				cutDemo,
+				this.startSecond,
+				this.endSecond,
+				injectAtStart,
+				injectBeforeCap,
+				injectAfterCap,
+				fwdSpeedFirstStage,
+				fwdSpeedSecondStage
+			);
+		} catch (DemoCutterException e) {
+			throw new DemoRecorderException("Error occurred while trying to cut the demo: " + e.getMessage(), e);
+		}
+		
+	}
+	
+	private void removeOldAutocaps() {
+		for (String videoExtension : VIDEO_FILE_ENDINGS) {
+			String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
+			+ CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
+			File videoFile = new File(fileString);
+			cleanUpFiles.add(videoFile);
+			if (videoFile.exists()) {
+				if (!videoFile.delete()) {
+					throw new DemoRecorderException("Could not delete old obsolete video file " + fileString);
+				}
+			}
+		}
+	}
+	
+	private void recordClip(File cutDemo) {
+		Process nexProc;
+		String demoFileName = DemoRecorderUtils.getJustFileNameOfPath(cutDemo);
+		String execPath = this.enginePath.getAbsolutePath() + " " + this.engineParameters + " -demo "
+						+ this.relativeDemoPath + "/" + demoFileName;
+		File engineDir = this.enginePath.getParentFile();
+		try {
+			nexProc = Runtime.getRuntime().exec(execPath, null, engineDir);
+			nexProc.getErrorStream();
+			nexProc.getOutputStream();
+			InputStream is = nexProc.getInputStream();
+			InputStreamReader isr = new InputStreamReader(is);
+			BufferedReader br = new BufferedReader(isr);
+			while (br.readLine() != null) {
+				//System.out.println(line);
+			}
+		} catch (IOException e) {
+			throw new DemoRecorderException("I/O Exception occurred when trying to execute the Nexuiz binary", e);
+		}
+	}
+
+	public void run() {
+		this.execute();
+	}
+	
+	public void setAppLayer(DemoRecorderApplication appLayer) {
+		this.appLayer = appLayer;
+	}
+
+	public int getJobIndex() {
+		return jobIndex;
+	}
+
+	public File getEnginePath() {
+		return enginePath;
+	}
+
+	public void setEnginePath(File enginePath) {
+		this.checkForProcessingState();
+		if (enginePath == null || !enginePath.exists()) {
+			throw new DemoRecorderException("Could not locate engine binary!");
+		}
+		if (!enginePath.canExecute()) {
+			throw new DemoRecorderException("The file you specified is not executable!");
+		}
+		this.enginePath = enginePath.getAbsoluteFile();
+	}
+
+	public String getEngineParameters() {
+		return engineParameters;
+	}
+
+	public void setEngineParameters(String engineParameters) {
+		this.checkForProcessingState();
+		if (engineParameters == null) {
+			engineParameters = "";
+		}
+		this.engineParameters = engineParameters.trim();
+	}
+
+	public File getDemoFile() {
+		return demoFile;
+	}
+
+	public void setDemoFile(File demoFile) {
+		this.checkForProcessingState();
+		if (demoFile == null || !demoFile.exists()) {
+			throw new DemoRecorderException("Could not locate demo file!");
+		}
+		if (!doReadWriteTest(demoFile.getParentFile())) {
+			throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");
+		}
+		if (!demoFile.getAbsolutePath().endsWith(".dem")) {
+			throw new DemoRecorderException("The demo file you specified must have the ending .dem");
+		}
+		
+		this.demoFile = demoFile.getAbsoluteFile();
+	}
+
+	public String getRelativeDemoPath() {
+		return relativeDemoPath;
+	}
+
+	public void setRelativeDemoPath(String relativeDemoPath) {
+		this.checkForProcessingState();
+		if (relativeDemoPath == null) {
+			relativeDemoPath = "";
+		}
+		
+		//get rid of possible slashes
+		while (relativeDemoPath.startsWith("/") || relativeDemoPath.startsWith("\\")) {
+			relativeDemoPath = relativeDemoPath.substring(1, relativeDemoPath.length());
+		}
+		while (relativeDemoPath.endsWith("/") || relativeDemoPath.endsWith("\\")) {
+			relativeDemoPath = relativeDemoPath.substring(0, relativeDemoPath.length() - 1);
+		}
+		
+		this.relativeDemoPath = relativeDemoPath.trim();
+	}
+
+	public File getDpVideoPath() {
+		return dpVideoPath;
+	}
+
+	public void setDpVideoPath(File dpVideoPath) {
+		this.checkForProcessingState();
+		if (dpVideoPath == null || !dpVideoPath.isDirectory()) {
+			throw new DemoRecorderException("Could not locate the specified DPVideo directory!");
+		}
+		
+		if (!this.doReadWriteTest(dpVideoPath)) {
+			throw new DemoRecorderException("The DPVideo directory is not writable! It needs to be writable so that the file can be moved to its new location");
+		}
+		this.dpVideoPath = dpVideoPath.getAbsoluteFile();
+	}
+
+	public File getVideoDestination() {
+		return videoDestination;
+	}
+
+	public void setVideoDestination(File videoDestination) {
+		this.checkForProcessingState();
+		//keep in mind, the parameter videoDestination points to the final avi/ogg file w/o extension!
+		if (videoDestination == null || !videoDestination.getParentFile().isDirectory()) {
+			throw new DemoRecorderException("Could not locate the specified video destination");
+		}
+		
+		if (!this.doReadWriteTest(videoDestination.getParentFile())) {
+			throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");
+		}
+		
+		this.videoDestination = videoDestination.getAbsoluteFile();
+	}
+
+	public String getExecuteBeforeCap() {
+		return executeBeforeCap;
+	}
+
+	public void setExecuteBeforeCap(String executeBeforeCap) {
+		this.checkForProcessingState();
+		if (executeBeforeCap == null) {
+			executeBeforeCap = "";
+		}
+		executeBeforeCap = executeBeforeCap.trim();
+		while (executeBeforeCap.endsWith(";")) {
+			executeBeforeCap = executeBeforeCap.substring(0, executeBeforeCap.length()-1);
+		}
+		this.executeBeforeCap = executeBeforeCap;
+	}
+
+	public String getExecuteAfterCap() {
+		return executeAfterCap;
+	}
+
+	public void setExecuteAfterCap(String executeAfterCap) {
+		this.checkForProcessingState();
+		if (executeAfterCap == null) {
+			executeAfterCap = "";
+		}
+		executeAfterCap = executeAfterCap.trim();
+		while (executeAfterCap.endsWith(";")) {
+			executeAfterCap = executeAfterCap.substring(0, executeAfterCap.length()-1);
+		}
+		if (executeAfterCap.contains("cl_capturevideo_number") || executeAfterCap.contains("cl_capturevideo_nameformat")) {
+			throw new DemoRecorderException("Execute after String cannot contain cl_capturevideo_number or _nameformat changes!");
+		}
+		this.executeAfterCap = executeAfterCap;
+	}
+
+	public float getStartSecond() {
+		return startSecond;
+	}
+
+	public void setStartSecond(float startSecond) {
+		this.checkForProcessingState();
+		if (startSecond < 0) {
+			throw new DemoRecorderException("Start second cannot be < 0");
+		}
+		this.startSecond = startSecond;
+	}
+
+	public float getEndSecond() {
+		return endSecond;
+	}
+
+	public void setEndSecond(float endSecond) {
+		this.checkForProcessingState();
+		if (endSecond < this.startSecond) {
+			throw new DemoRecorderException("End second cannot be < start second");
+		}
+		this.endSecond = endSecond;
+	}
+
+	public State getState() {
+		return state;
+	}
+
+	public void setState(State state) {
+		this.state = state;
+		this.appLayer.fireUserInterfaceUpdate(this);
+	}
+
+	public String getJobName() {
+		if (this.jobName == null || this.jobName.equals("")) {
+			return "Job " + this.jobIndex;
+		}
+		return this.jobName;
+	}
+	
+	public void setJobName(String jobName) {
+		if (jobName == null || jobName.equals("")) {
+			this.jobIndex = appLayer.getNewJobIndex();
+			this.jobName = "Job " + this.jobIndex;
+		} else {
+			this.jobName = jobName;
+		}
+	}
+
+	public DemoRecorderException getLastException() {
+		return lastException;
+	}
+	
+	/**
+	 * Tests whether the given directory is writable by creating a file in there and deleting
+	 * it again.
+	 * @param directory
+	 * @return true if directory is writable
+	 */
+	protected boolean doReadWriteTest(File directory) {
+		boolean writable = false;
+		String fileName = "tmp." + Math.random()*10000 + ".dat";
+		File tempFile = new File(directory, fileName);
+		try {
+			writable = tempFile.createNewFile();
+			if (writable) {
+				tempFile.delete();
+			}
+		} catch (IOException e) {
+			writable = false;
+		}
+		return writable;
+	}
+	
+	private void checkForProcessingState() {
+		if (this.state == State.PROCESSING) {
+			throw new DemoRecorderException("Cannot modify this job while it is processing!");
+		}
+	}
+
+	public Properties getEncoderPluginSettings(EncoderPlugin plugin) {
+		if (this.encoderPluginSettings.containsKey(plugin.getName())) {
+			return this.encoderPluginSettings.get(plugin.getName());
+		} else {
+			return new Properties();
+		}
+	}
+
+	public void setEncoderPluginSetting(String pluginName, String pluginSettingKey, String value) {
+		Properties p = this.encoderPluginSettings.get(pluginName);
+		if (p == null) {
+			p = new Properties();
+			this.encoderPluginSettings.put(pluginName, p);
+		}
+		
+		p.put(pluginSettingKey, value);
+	}
+
+	public Map<String, Properties> getEncoderPluginSettings() {
+		return encoderPluginSettings;
+	}
+
+	public void setEncoderPluginSettings(Map<String, Properties> encoderPluginSettings) {
+		this.encoderPluginSettings = encoderPluginSettings;
+	}
+
+	public File getActualVideoDestination() {
+		return actualVideoDestination;
+	}
+	
+	public void setActualVideoDestination(File actualVideoDestination) {
+		this.actualVideoDestination = actualVideoDestination;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,17 @@
+package com.nexuiz.demorecorder.application.jobs;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+
+public class RecordsDoneJob implements Runnable {
+	
+	private DemoRecorderApplication appLayer;
+	
+	public RecordsDoneJob(DemoRecorderApplication appLayer) {
+		this.appLayer = appLayer;
+	}
+
+	public void run() {
+		this.appLayer.notifyAllJobsDone();
+	}
+
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,69 @@
+package com.nexuiz.demorecorder.application.plugins;
+
+import java.util.Properties;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public interface EncoderPlugin {
+	
+	/**
+	 * Makes the application layer known to the plug-in, which is required so that the plug-in
+	 * can access the preferences of the application. Call this method first before using any
+	 * of the others.
+	 */
+	public void setApplicationLayer(DemoRecorderApplication appLayer);
+
+	/**
+	 * Returns the name of the plug-in. Must not contain a "."
+	 */
+	public String getName();
+	
+	/**
+	 * Returns true if the plug-in is enabled (checked from the preferences of the app layer)
+	 * @return true if the plug-in is enabled
+	 */
+	public boolean isEnabled();
+	
+	/**
+	 * Global preferences are preferences of a plug-in that are application-wide and not job-
+	 * specific. They should be shown in a global preferences dialog.
+	 * Use this method in order to tell the application layer and GUI which global settings your
+	 * encoder plug-in offers, and set a reasonable default. Note that for the default-values being
+	 * set you can either set to "true" or "false", any String (can be empty), or "filechooser" if
+	 * you want the user to select a file. 
+	 * @return
+	 */
+	public Properties getGlobalPreferences();
+	
+	/**
+	 * In order to influence the order of settings being displayed to the user in a UI, return an array
+	 * of all keys used in the Properties object returned in getGlobalPreferences(), with your desired
+	 * order.
+	 * @return
+	 */
+	public String[] getGlobalPreferencesOrder();
+	
+	/**
+	 * Here you can return a Properties object that contains keys for values that can be specific to each
+	 * individual RecordJob. 
+	 * @return
+	 */
+	public Properties getJobSpecificPreferences();
+	
+	/**
+	 * In order to influence the order of job-specific settings being displayed to the user in a UI,
+	 * return an array of all keys used in the Properties object returned in getJobSpecificPreferences(), with
+	 * your desired order.
+	 * @return
+	 */
+	public String[] getJobSpecificPreferencesOrder();
+	
+	/**
+	 * Will be called by the application layer when a job has been successfully recorded and moved to its
+	 * final destination. This method has to perform the specific tasks your plug-in is supposed to do.
+	 * @param job
+	 * @throws EncoderPluginException
+	 */
+	public void executeEncoder(RecordJob job) throws EncoderPluginException;
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,14 @@
+package com.nexuiz.demorecorder.application.plugins;
+
+public class EncoderPluginException extends Exception {
+
+	private static final long serialVersionUID = 2200737027476726978L;
+
+	public EncoderPluginException(String message) {
+		super(message);
+	}
+	
+	public EncoderPluginException(String message, Throwable t) {
+		super(message, t);
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/main/Driver.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,18 @@
+package com.nexuiz.demorecorder.main;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;
+import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;
+
+public class Driver {
+	
+	public static void main(String[] args) {
+		SwingGUI.setSystemLAF();
+		Thread.setDefaultUncaughtExceptionHandler(new ShowErrorDialogExceptionHandler());
+		DemoRecorderApplication appLayer = new DemoRecorderApplication();
+		
+		SwingGUI gui = new SwingGUI(appLayer);
+		appLayer.addUserInterfaceListener(gui);
+		
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,20 @@
+package com.nexuiz.demorecorder.ui;
+
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public interface DemoRecorderUI {
+
+	/**
+	 * Called by the application layer to inform the GUI about the fact that
+	 * one or more properties of the given job changed (most likely the status).
+	 * The given job might also be new to the GUI.
+	 * @param job the affected job
+	 */
+	public void RecordJobPropertiesChange(RecordJob job);
+	
+	/**
+	 * Called by the application layer to inform the GUI that it finished
+	 * recording all assigned jobs.
+	 */
+	public void recordingFinished();
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,737 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.border.EmptyBorder;
+import javax.swing.filechooser.FileFilter;
+
+import net.miginfocom.swing.MigLayout;
+
+import org.jdesktop.swingx.JXTable;
+import org.jdesktop.swingx.JXTitledSeparator;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;
+import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;
+
+/**
+ * Shows the dialog that allows to create a new job, create one from a template
+ * or edit an existing job.
+ */
+
+public class JobDialog extends JDialog implements ActionListener {
+	private static final long serialVersionUID = 6926246716804560522L;
+	public static final int CREATE_NEW_JOB = 0;
+	public static final int EDIT_JOB = 1;
+	public static final int CREATE_NEW_TEMPLATE = 2;
+	public static final int EDIT_TEMPLATE = 3;
+	public static final int CREATE_JOB_FROM_TEMPLATE = 4;
+
+	private DemoRecorderApplication appLayer;
+	private RecordJobTemplatesTableModel tableModel;
+//	private JXTable templatesTable;
+	private Frame parentFrame;
+	private int dialogType;
+	private RecordJob job = null;
+	private JPanel inputPanel;
+	private JPanel buttonPanel;
+
+	private JTextField templateNameField;
+	private JTextField templateSummaryField;
+	private JTextField enginePathField;
+	private JButton enginePathChooserButton;
+	private JTextField engineParameterField;
+	private JTextField dpVideoDirField;
+	private JButton dpVideoDirChooserButton;
+	private JTextField relativeDemoPathField;
+	private JTextField jobNameField;
+	private JTextField demoFileField;
+	private JButton demoFileChooserButton;
+	private JTextField startSecondField;
+	private JTextField endSecondField;
+	private JTextArea execBeforeField;
+	private JTextArea execAfterField;
+	private JTextField videoDestinationField;
+	private JButton videoDestinationChooserButton;
+	
+	private JButton createButton;
+	private JButton cancelButton;
+	
+	//file choosers
+	private JFileChooser enginePathFC;
+	private JFileChooser dpVideoDirFC;
+	private JFileChooser demoFileFC;
+	private JFileChooser videoDestinationFC;
+	
+	private FileFilter userDirFilter = new NexuizUserDirFilter();
+	
+	private Map<String, JComponent> pluginDialogSettings = new HashMap<String, JComponent>();
+
+	/**
+	 * Constructor to create a dialog when creating a new job.
+	 * @param owner
+	 * @param appLayer
+	 */
+	public JobDialog(Frame owner, DemoRecorderApplication appLayer) {
+		super(owner, true);
+		this.parentFrame = owner;
+		this.dialogType = CREATE_NEW_JOB;
+		this.appLayer = appLayer;
+		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+		setTitle("Create new job");
+
+		this.setupLayout();
+	}
+	
+	/**
+	 * Constructor to create a dialog when creating a new template.
+	 * @param owner
+	 * @param dialogType
+	 * @param appLayer
+	 */
+	public JobDialog(Frame owner, RecordJobTemplatesTableModel tableModel, JXTable templatesTable, DemoRecorderApplication appLayer) {
+		super(owner, true);
+		this.parentFrame = owner;
+		this.dialogType = CREATE_NEW_TEMPLATE;
+		this.tableModel = tableModel;
+		this.appLayer = appLayer;
+//		this.templatesTable = templatesTable; seems we don't need it
+		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+		setTitle("Create new template");
+
+		this.setupLayout();
+	}
+	
+	/**
+	 * Constructor to use when creating a new job from a template, or when editing a template.
+	 * @param owner
+	 * @param template
+	 * @param type either CREATE_JOB_FROM_TEMPLATE or EDIT_TEMPLATE
+	 */
+	public JobDialog(Frame owner, RecordJobTemplate template, DemoRecorderApplication appLayer, int type) {
+		super(owner, true);
+		this.parentFrame = owner;
+		
+		this.job = template;
+		this.appLayer = appLayer;
+		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+		
+		if (type != CREATE_JOB_FROM_TEMPLATE && type != EDIT_TEMPLATE) {
+			throw new RuntimeException("Illegal paraameter \"type\"");
+		}
+		
+		this.dialogType = type;
+		if (type == CREATE_JOB_FROM_TEMPLATE) {
+			setTitle("Create job from template");
+		} else {
+			setTitle("Edit template");
+		}
+
+		this.setupLayout();
+	}
+	
+	/**
+	 * Constructor to create a dialog to be used when editing an existing job.
+	 * @param owner
+	 * @param job
+	 */
+	public JobDialog(Frame owner, RecordJob job, DemoRecorderApplication appLayer) {
+		super(owner, true);
+		this.parentFrame = owner;
+		this.dialogType = EDIT_JOB;
+		this.appLayer = appLayer;
+		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+		setTitle("Edit job");
+		this.job = job;
+
+		this.setupLayout();
+	}
+	
+	
+	
+	public void showDialog() {
+		this.pack();
+		Toolkit t = Toolkit.getDefaultToolkit();
+		Dimension screenSize = t.getScreenSize();
+		if (getHeight() > screenSize.height) {
+			Dimension newPreferredSize = getPreferredSize();
+			newPreferredSize.height = screenSize.height - 100;
+			setPreferredSize(newPreferredSize);
+			this.pack();
+		}
+		this.setLocationRelativeTo(this.parentFrame);
+		this.setVisible(true);
+	}
+
+	private void setupLayout() {
+//		setLayout(new MigLayout("wrap 1", "[grow,fill]", "[]20[]"));
+		setLayout(new MigLayout("wrap 1", "[grow,fill]", "[][]"));
+		this.setupInputMask();
+		this.setupButtonPart();
+
+	}
+
+	private void setupInputMask() {
+		inputPanel = new JPanel(new MigLayout("insets 0,wrap 3", "[][250::,grow,fill][30::]"));
+		JScrollPane inputScrollPane = new JScrollPane(inputPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+		inputScrollPane.setBorder(new EmptyBorder(0,0,0,0));
+		
+		JXTitledSeparator environmentHeading = new JXTitledSeparator("Environment settings");
+		inputPanel.add(environmentHeading, "span 3,grow");
+
+		this.setupTemplateNameAndSummary();
+		this.setupEnginePath();
+		this.setupEngineParameters();
+		this.setupDPVideoDir();
+		this.setupRelativeDemoPath();
+
+		JXTitledSeparator jobSettingsHeading = new JXTitledSeparator("Job settings");
+		inputPanel.add(jobSettingsHeading, "span 3,grow");
+
+		this.setupJobName();
+		this.setupDemoFile();
+		this.setupStartSecond();
+		this.setupEndSecond();
+		this.setupExecBefore();
+		this.setupExecAfter();
+		this.setupVideoDestination();
+		
+		this.setupPluginPreferences();
+
+		getContentPane().add(inputScrollPane);
+	}
+	
+	private void setupTemplateNameAndSummary() {
+		if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != EDIT_TEMPLATE) {
+			return;
+		}
+		
+		//layout stuff
+		inputPanel.add(new JLabel("Template name:"));
+		templateNameField = new JTextField();
+		inputPanel.add(templateNameField, "wrap");
+		
+		inputPanel.add(new JLabel("Summary:"));
+		templateSummaryField = new JTextField();
+		inputPanel.add(templateSummaryField, "wrap");
+		
+		//UI logic stuff
+		if (this.dialogType == EDIT_TEMPLATE) {
+			RecordJobTemplate template = (RecordJobTemplate) this.job;
+			templateNameField.setText(template.getName());
+			templateSummaryField.setText(template.getSummary());
+		}
+	}
+	
+	private void setupEnginePath() {
+		//layout stuff
+		inputPanel.add(new JLabel("Engine:"));
+		enginePathField = new JTextField();
+		enginePathField.setEditable(false);
+		inputPanel.add(enginePathField);
+		enginePathChooserButton = new FileChooserButton();
+		inputPanel.add(enginePathChooserButton);
+		
+		//UI logic stuff
+		this.enginePathFC = createConfiguredFileChooser();
+		enginePathChooserButton.addActionListener(this);
+		if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			this.enginePathFC.setSelectedFile(this.job.getEnginePath());
+			this.enginePathField.setText(this.job.getEnginePath().getAbsolutePath());
+		}
+	}
+	
+	private void setupEngineParameters() {
+		//layout stuff
+		inputPanel.add(new JLabel("Engine parameters:"));
+		engineParameterField = new JTextField();
+		inputPanel.add(engineParameterField, "wrap");
+		
+		//UI logic stuff
+		if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			engineParameterField.setText(this.job.getEngineParameters());
+		}
+	}
+	
+	private void setupDPVideoDir() {
+		//layout stuff
+		inputPanel.add(new JLabel("DPVideo directory:"));
+		dpVideoDirField = new JTextField();
+		dpVideoDirField.setEditable(false);
+		inputPanel.add(dpVideoDirField);
+		dpVideoDirChooserButton = new FileChooserButton();
+		inputPanel.add(dpVideoDirChooserButton);
+		
+		//UI logic stuff
+		dpVideoDirChooserButton.addActionListener(this);
+		this.dpVideoDirFC = createConfiguredFileChooser();
+		this.dpVideoDirFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+		if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			this.dpVideoDirFC.setSelectedFile(this.job.getDpVideoPath());
+			this.dpVideoDirField.setText(this.job.getDpVideoPath().getAbsolutePath());
+		}
+	}
+	
+	private void setupRelativeDemoPath() {
+		//layout stuff
+		inputPanel.add(new JLabel("Relative demo path:"));
+		relativeDemoPathField = new JTextField();
+		inputPanel.add(relativeDemoPathField, "wrap 20");
+		
+		//UI logic stuff
+		if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE) {
+			relativeDemoPathField.setText("demos");
+		}
+		if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			relativeDemoPathField.setText(this.job.getRelativeDemoPath());
+		}
+	}
+	
+	private void setupJobName() {
+		inputPanel.add(new JLabel("Job name:"));
+		
+		jobNameField = new JTextField();
+		inputPanel.add(jobNameField, "wrap");
+		
+		//UI logic stuff
+		if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != CREATE_NEW_JOB) {
+			jobNameField.setText(this.job.getJobName());
+		}
+	}
+	
+	private void setupDemoFile() {
+		String label;
+		if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			label = "Demo file:";
+		} else {
+			label = "Demo directory:";
+		}
+		
+		//layout stuff
+		inputPanel.add(new JLabel(label));
+		demoFileField = new JTextField();
+		demoFileField.setEditable(false);
+		inputPanel.add(demoFileField);
+		demoFileChooserButton = new FileChooserButton();
+		inputPanel.add(demoFileChooserButton);
+		
+		//UI logic stuff
+		this.demoFileFC = createConfiguredFileChooser();
+		demoFileChooserButton.addActionListener(this);
+		if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+				this.demoFileFC.setCurrentDirectory(this.job.getDemoFile());
+			} else {
+				this.demoFileFC.setSelectedFile(this.job.getDemoFile());
+			}
+			
+			this.demoFileField.setText(this.job.getDemoFile().getAbsolutePath());
+		}
+		
+		//only specify directories for templates
+		if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {
+			this.demoFileFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+		}
+	}
+	
+	private void setupStartSecond() {
+		//only exists for jobs, not for templates
+		if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {
+			return;
+		}
+		
+		//layout stuff
+		inputPanel.add(new JLabel("Start second:"));
+		startSecondField = new JTextField();
+		inputPanel.add(startSecondField, "wrap");
+		
+		//UI logic stuff
+		if (this.dialogType == EDIT_JOB) {
+			startSecondField.setText(String.valueOf( this.job.getStartSecond() ));
+		}
+	}
+	
+	private void setupEndSecond() {
+		//only exists for jobs, not for templates
+		if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {
+			return;
+		}
+		
+		//layout stuff
+		inputPanel.add(new JLabel("End second:"));
+		endSecondField = new JTextField();
+		inputPanel.add(endSecondField, "wrap");
+		
+		//UI logic stuff
+		if (this.dialogType == EDIT_JOB) {
+			endSecondField.setText(String.valueOf( this.job.getEndSecond() ));
+		}
+	}
+	
+	private void setupExecBefore() {
+		//layout stuff
+		inputPanel.add(new JLabel("Exec before capture:"));
+		execBeforeField = new JTextArea(3, 1);
+		inputPanel.add(new JScrollPane(execBeforeField), "wrap");
+		
+		//UI logic stuff
+		if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			execBeforeField.setText(this.job.getExecuteBeforeCap());
+		}
+	}
+	
+	private void setupExecAfter() {
+		//layout stuff
+		inputPanel.add(new JLabel("Exec after capture:"));
+		execAfterField = new JTextArea(3, 1);
+		inputPanel.add(new JScrollPane(execAfterField), "wrap");
+		
+		//UI logic stuff
+		if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			execAfterField.setText(this.job.getExecuteAfterCap());
+		}
+	}
+	
+	private void setupVideoDestination() {
+		//layout stuff
+		inputPanel.add(new JLabel("Video destination:"));
+		videoDestinationField = new JTextField();
+		videoDestinationField.setEditable(false);
+		inputPanel.add(videoDestinationField);
+		videoDestinationChooserButton = new FileChooserButton();
+		inputPanel.add(videoDestinationChooserButton, "wrap 20");
+		
+		//UI logic stuff
+		videoDestinationChooserButton.addActionListener(this);
+		this.videoDestinationFC = createConfiguredFileChooser();
+		if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+				this.videoDestinationFC.setCurrentDirectory(this.job.getVideoDestination());
+			} else {
+				this.videoDestinationFC.setSelectedFile(this.job.getVideoDestination());
+			}
+			
+			this.videoDestinationField.setText(this.job.getVideoDestination().getAbsolutePath());
+		}
+		if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {
+			this.videoDestinationFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+		}
+	}
+	
+	private void setupPluginPreferences() {
+		for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
+			String pluginName = plugin.getName();
+			//only display settings if the plugin actually has any...
+			Properties jobSpecificDefaultPluginPreferences = plugin.getJobSpecificPreferences();
+			Properties jobPluginPreferences = null;
+			if (this.job != null) {
+				jobPluginPreferences = this.job.getEncoderPluginSettings(plugin);
+			}
+			if (jobSpecificDefaultPluginPreferences.size() > 0 && plugin.isEnabled()) {
+				//add heading
+				JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");
+				inputPanel.add(pluginHeading, "span 3,grow");
+				
+				for (String pluginPreferenceKey : plugin.getJobSpecificPreferencesOrder()) {
+					String value = jobSpecificDefaultPluginPreferences.getProperty(pluginPreferenceKey);
+					if (this.job != null) {
+						if (jobPluginPreferences.containsKey(pluginPreferenceKey)) {
+							value = jobPluginPreferences.getProperty(pluginPreferenceKey);
+						}
+					}
+					
+					this.setupSinglePluginSetting(plugin, pluginPreferenceKey, value);
+				}
+			}
+		}
+	}
+	
+	private void setupSinglePluginSetting(EncoderPlugin plugin, String key, String value) {
+		inputPanel.add(new JLabel(key + ":"));
+		
+		if (SwingGUIUtils.isBooleanValue(value)) {
+			JCheckBox checkbox = new JCheckBox();
+			checkbox.setSelected(Boolean.valueOf(value));
+			inputPanel.add(checkbox, "wrap");
+			this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), checkbox);
+		} else if (SwingGUIUtils.isFileChooser(value)) {
+			final JFileChooser fc = new JFileChooser();
+			fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+			JButton fcButton = new JButton("...");
+			final JTextField filePathField = new JTextField();
+			filePathField.setEditable(false);
+			inputPanel.add(filePathField);
+			fcButton.addActionListener(new ActionListener() {
+				@Override
+				public void actionPerformed(ActionEvent e) {
+					int returnValue = fc.showOpenDialog(JobDialog.this);
+					if (returnValue == JFileChooser.APPROVE_OPTION) {
+						File selectedFile = fc.getSelectedFile();
+						filePathField.setText(selectedFile.getAbsolutePath());
+					}
+				}
+			});
+			
+			try {
+				File selectedFile = new File(value);
+				if (selectedFile.exists()) {
+					fc.setSelectedFile(selectedFile);
+					filePathField.setText(selectedFile.getAbsolutePath());
+				}
+			} catch (Throwable e) {}
+			this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), fc);
+			inputPanel.add(fcButton);
+		} else {
+			//textfield
+			JTextField textField = new JTextField();
+			textField.setText(value);
+			this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), textField);
+			inputPanel.add(textField, "wrap");
+		}
+	}
+
+	private void setupButtonPart() {
+		String createButtonText;
+		if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+			createButtonText = "Create";
+		} else {
+			createButtonText = "Save";
+		}
+		buttonPanel = new JPanel(new MigLayout("insets 0"));
+		createButton = new JButton(createButtonText);
+		createButton.addActionListener(this);
+		cancelButton = new JButton("Cancel");
+		cancelButton.addActionListener(this);
+		
+		buttonPanel.add(createButton);
+		buttonPanel.add(cancelButton);
+
+		getContentPane().add(buttonPanel);
+	}
+	
+	
+	public void actionPerformed(ActionEvent e) {
+		if (e.getSource() == enginePathChooserButton) {
+			int returnValue = this.enginePathFC.showOpenDialog(this);
+			if (returnValue == JFileChooser.APPROVE_OPTION) {
+				File selectedFile = this.enginePathFC.getSelectedFile();
+				this.enginePathField.setText(selectedFile.getAbsolutePath());
+			}
+		} else if (e.getSource() == dpVideoDirChooserButton) {
+			int returnValue = this.dpVideoDirFC.showOpenDialog(this);
+			if (returnValue == JFileChooser.APPROVE_OPTION) {
+				File selectedFile = this.dpVideoDirFC.getSelectedFile();
+				this.dpVideoDirField.setText(selectedFile.getAbsolutePath());
+			}
+		} else if (e.getSource() == demoFileChooserButton) {
+			int returnValue = this.demoFileFC.showOpenDialog(this);
+			if (returnValue == JFileChooser.APPROVE_OPTION) {
+				File selectedFile = this.demoFileFC.getSelectedFile();
+				if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+					this.demoFileField.setText(DemoRecorderUtils.getJustFileNameOfPath(selectedFile));
+				} else {
+					//template, show full path of directory
+					this.demoFileField.setText(selectedFile.getAbsolutePath());
+				}
+				
+			}
+		} else if (e.getSource() == videoDestinationChooserButton) {
+			int returnValue = this.videoDestinationFC.showSaveDialog(this);
+			if (returnValue == JFileChooser.APPROVE_OPTION) {
+				File selectedFile = this.videoDestinationFC.getSelectedFile();
+				this.videoDestinationField.setText(selectedFile.getAbsolutePath());
+			}
+		} else if (e.getSource() == createButton) {
+			switch (this.dialogType) {
+			case CREATE_NEW_JOB:
+			case CREATE_JOB_FROM_TEMPLATE:
+				this.requestNewRecordJob(); break;
+			case CREATE_NEW_TEMPLATE:
+				this.createNewTemplate();
+				break;
+			case EDIT_JOB:
+				this.editJob();
+				break;
+			case EDIT_TEMPLATE:
+				this.editTemplate();
+				break;
+			}
+		} else if (e.getSource() == cancelButton) {
+			dispose();
+		}
+	}
+	
+	private void requestNewRecordJob() {
+		float startSecond, endSecond = -1;
+		try {
+			startSecond = Float.valueOf(this.startSecondField.getText());
+			endSecond = Float.valueOf(this.endSecondField.getText());
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);
+			return;
+		}
+		
+		try {
+			RecordJob j = this.appLayer.createRecordJob(
+				this.jobNameField.getText(),
+				this.enginePathFC.getSelectedFile(),
+				this.engineParameterField.getText(),
+				this.demoFileFC.getSelectedFile(),
+				this.relativeDemoPathField.getText(),
+				this.dpVideoDirFC.getSelectedFile(),
+				this.videoDestinationFC.getSelectedFile(),
+				this.execBeforeField.getText(),
+				this.execAfterField.getText(),
+				startSecond,
+				endSecond
+			);
+			this.saveEncoderPluginSettings(j);
+			dispose();
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog(e);
+			return;
+		}
+		
+	}
+	
+	private void editJob() {
+		float startSecond, endSecond = -1;
+		try {
+			startSecond = Float.valueOf(this.startSecondField.getText());
+			endSecond = Float.valueOf(this.endSecondField.getText());
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);
+			return;
+		}
+		
+		try {
+			this.job.setJobName(this.jobNameField.getText());
+			this.job.setEnginePath(this.enginePathFC.getSelectedFile());
+			this.job.setEngineParameters(this.engineParameterField.getText());
+			this.job.setDemoFile(this.demoFileFC.getSelectedFile());
+			this.job.setRelativeDemoPath(this.relativeDemoPathField.getText());
+			this.job.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());
+			this.job.setVideoDestination(this.videoDestinationFC.getSelectedFile());
+			this.job.setExecuteBeforeCap(this.execBeforeField.getText());
+			this.job.setExecuteAfterCap(this.execAfterField.getText());
+			this.job.setStartSecond(startSecond);
+			this.job.setEndSecond(endSecond);
+			this.saveEncoderPluginSettings(this.job);
+			this.appLayer.fireUserInterfaceUpdate(this.job);
+			dispose();
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog(e);
+			return;
+		}
+		
+	}
+	
+	private void createNewTemplate() {
+		try {
+			RecordJobTemplate templ = new RecordJobTemplate(
+				this.templateNameField.getText(),
+				this.templateSummaryField.getText(),
+				this.jobNameField.getText(),
+				this.enginePathFC.getSelectedFile(),
+				this.engineParameterField.getText(),
+				this.demoFileFC.getSelectedFile(),
+				this.relativeDemoPathField.getText(),
+				this.dpVideoDirFC.getSelectedFile(),
+				this.videoDestinationFC.getSelectedFile(),
+				this.execBeforeField.getText(),
+				this.execAfterField.getText()
+			);
+			this.saveEncoderPluginSettings(templ);
+			this.tableModel.addRecordJobTemplate(templ);
+			dispose();
+		} catch (NullPointerException e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that you chose a file/directory in each case!", e, true);
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog(e);
+			return;
+		}
+	}
+	
+	private void editTemplate() {
+		try {
+			RecordJobTemplate template = (RecordJobTemplate) this.job;
+			template.setName(this.templateNameField.getText());
+			template.setSummary(this.templateSummaryField.getText());
+			template.setJobName(this.jobNameField.getText());
+			template.setEnginePath(this.enginePathFC.getSelectedFile());
+			template.setEngineParameters(this.engineParameterField.getText());
+			template.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());
+			template.setRelativeDemoPath(this.relativeDemoPathField.getText());
+			template.setDemoFile(this.demoFileFC.getSelectedFile());
+			template.setExecuteBeforeCap(this.execBeforeField.getText());
+			template.setExecuteAfterCap(this.execAfterField.getText());
+			template.setVideoDestination(this.videoDestinationFC.getSelectedFile());
+			this.saveEncoderPluginSettings(template);
+			dispose();
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog(e);
+			return;
+		}
+	}
+	
+	private void saveEncoderPluginSettings(RecordJob job) {
+		Set<String> keys = this.pluginDialogSettings.keySet();
+		//remember, the keys are concatenated, containing both the category and actual key 
+		for (String key : keys) {
+			JComponent component = this.pluginDialogSettings.get(key);
+			if (component instanceof JCheckBox) {
+				JCheckBox checkbox = (JCheckBox) component;
+				job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), String.valueOf(checkbox.isSelected()));
+			} else if (component instanceof JFileChooser) {
+				JFileChooser fileChooser = (JFileChooser) component;
+				if (fileChooser.getSelectedFile() != null) {
+					String path = fileChooser.getSelectedFile().getAbsolutePath();
+					job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);
+				}
+			} else if (component instanceof JTextField) {
+				JTextField textField = (JTextField) component;
+				job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());
+			}
+		}
+	}
+	
+	private static class FileChooserButton extends JButton {
+		private static final long serialVersionUID = 1335571540372856959L;
+		public FileChooserButton() {
+			super("...");
+		}
+	}
+	
+	private JFileChooser createConfiguredFileChooser() {
+		JFileChooser fc = new JFileChooser();
+		fc.setFileHidingEnabled(false);
+		fc.setFileFilter(userDirFilter);
+		return fc;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,31 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+
+/**
+ * File filter that makes sure that the hidden .nexuiz directory is being shown in the
+ * file dialog, but other hidden directories are not.
+ */
+public class NexuizUserDirFilter extends FileFilter {
+
+	@Override
+	public boolean accept(File f) {
+		if (f.isHidden()) {
+			if (f.isDirectory() && DemoRecorderUtils.getJustFileNameOfPath(f).equals(".nexuiz")) {
+				return true;
+			}
+			return false; //don't show other hidden directories/files
+		}
+		return true;
+	}
+
+	@Override
+	public String getDescription() {
+		return null;
+	}
+
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,195 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import net.miginfocom.swing.MigLayout;
+
+import org.jdesktop.swingx.JXTitledSeparator;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;
+
+public class PreferencesDialog extends JDialog implements ActionListener {
+
+	private static final long serialVersionUID = 7328399646538571333L;
+	private Frame parentFrame;
+	private DemoRecorderApplication appLayer;
+	private NDRPreferences preferences;
+	private Map<String, JComponent> dialogSettings;
+	
+	private JButton saveButton = new JButton("Save");
+	private JButton cancelButton = new JButton("Cancel");
+	
+	public PreferencesDialog(Frame owner, DemoRecorderApplication appLayer) {
+		super(owner, true);
+		this.parentFrame = owner;
+		this.appLayer = appLayer;
+		this.preferences = appLayer.getPreferences();
+		this.dialogSettings = new HashMap<String, JComponent>();
+		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+		setTitle("Preferences");
+
+		this.setupLayout();
+	}
+
+	private void setupLayout() {
+		setLayout(new MigLayout("wrap 2", "[][::150,fill]"));
+		
+		//add heading
+		JXTitledSeparator applicationHeading = new JXTitledSeparator("Application settings");
+		getContentPane().add(applicationHeading, "span 2,grow");
+		
+		for (int i = 0; i < DemoRecorderApplication.Preferences.PREFERENCES_ORDER.length; i++) {
+			String currentSetting = DemoRecorderApplication.Preferences.PREFERENCES_ORDER[i];
+			if (this.preferences.getProperty(NDRPreferences.MAIN_APPLICATION, currentSetting) != null) {
+				this.setupSingleSetting(NDRPreferences.MAIN_APPLICATION, currentSetting);
+			}
+		}
+		
+		//add plugin settings
+		for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
+			String pluginName = plugin.getName();
+			//only display settings if the plugin actually has any...
+			Properties pluginPreferences = plugin.getGlobalPreferences();
+			if (pluginPreferences.size() > 0) {
+				//add heading
+				JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");
+				getContentPane().add(pluginHeading, "span 2,grow");
+				
+				for (String pluginKey : plugin.getGlobalPreferencesOrder()) {
+					if (this.preferences.getProperty(pluginName, pluginKey) != null) {
+						this.setupSingleSetting(pluginName, pluginKey);
+					}
+				}
+			}
+		}
+		
+		JPanel buttonPanel = new JPanel();
+		buttonPanel.add(saveButton);
+		buttonPanel.add(cancelButton);
+		saveButton.addActionListener(this);
+		cancelButton.addActionListener(this);
+		getContentPane().add(buttonPanel, "span 2");
+	}
+	
+	private void setupSingleSetting(String category, String setting) {
+		getContentPane().add(new JLabel(setting + ":"));
+		
+		String value = this.preferences.getProperty(category, setting);
+		if (SwingGUIUtils.isBooleanValue(value)) {
+			JCheckBox checkbox = new JCheckBox();
+			this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), checkbox);
+			getContentPane().add(checkbox);
+		} else if (SwingGUIUtils.isFileChooser(value)) {
+			final JFileChooser fc = new JFileChooser();
+			fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+			JButton fcButton = new JButton("...");
+			fcButton.addActionListener(new ActionListener() {
+				@Override
+				public void actionPerformed(ActionEvent e) {
+					fc.showOpenDialog(PreferencesDialog.this);
+				}
+			});
+			this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), fc);
+			getContentPane().add(fcButton);
+		} else {
+			JTextField textField = new JTextField();
+			this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), textField);
+			getContentPane().add(textField);
+		}
+	}
+	
+	
+	
+	public void showDialog() {
+		this.loadSettings();
+		this.pack();
+		this.setLocationRelativeTo(this.parentFrame);
+		setResizable(false);
+		this.setVisible(true);
+	}
+	
+	/**
+	 * Loads the settings from the application layer (and global plug-in settings) to the form.
+	 */
+	private void loadSettings() {
+		Set<Object> keys = this.preferences.keySet();
+		for (Object keyObj : keys) {
+			String concatenatedKey = (String) keyObj;
+			String value;
+			JComponent component = null;
+			if ((value = this.preferences.getProperty(concatenatedKey)) != null) {
+				if (SwingGUIUtils.isBooleanValue(value)) {
+					component = this.dialogSettings.get(concatenatedKey);
+					if (component != null) {
+						((JCheckBox) component).setSelected(Boolean.valueOf(value));
+					}
+				} else if (SwingGUIUtils.isFileChooser(value)) {
+					component = this.dialogSettings.get(concatenatedKey);
+					try {
+						File selectedFile = new File(value);
+						if (selectedFile.exists() && component != null) {
+							((JFileChooser) component).setSelectedFile(selectedFile);
+						}
+					} catch (Throwable e) {}
+					
+				} else {
+					component = this.dialogSettings.get(concatenatedKey);
+					if (component != null) {
+						((JTextField) component).setText(value);
+					}
+				}
+			}
+		}
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		if (e.getSource() == cancelButton) {
+			this.setVisible(false);
+		} else if (e.getSource() == saveButton) {
+			this.saveSettings();
+		}
+	}
+
+	private void saveSettings() {
+		Set<String> keys = this.dialogSettings.keySet();
+		//remember, the keys are concatenated, containing both the category and actual key 
+		for (String key : keys) {
+			JComponent component = this.dialogSettings.get(key);
+			if (component instanceof JCheckBox) {
+				JCheckBox checkbox = (JCheckBox) component;
+				this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), checkbox.isSelected());
+			} else if (component instanceof JFileChooser) {
+				JFileChooser fileChooser = (JFileChooser) component;
+				if (fileChooser.getSelectedFile() != null) {
+					String path = fileChooser.getSelectedFile().getAbsolutePath();
+					this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);
+				}
+			} else if (component instanceof JTextField) {
+				JTextField textField = (JTextField) component;
+				this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());
+			}
+		}
+		this.setVisible(false);
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,113 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.io.File;
+
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public class RecordJobTemplate extends RecordJob {
+
+	private static final long serialVersionUID = 8311386509410161395L;
+	private String templateName;
+	private String summary;
+
+	public RecordJobTemplate(
+		String templateName,
+		String summary,
+		String jobName,
+		File enginePath,
+		String engineParameters,
+		File demoFile,
+		String relativeDemoPath,
+		File dpVideoPath,
+		File videoDestination,
+		String executeBeforeCap,
+		String executeAfterCap
+		) {
+		super();
+		
+		/*
+		 * Differences to jobs:
+		 * - name and summary exist
+		 * - "Demo file:" -> "Demo directory:"
+		 * - no start/end second
+		 */
+		
+		if (templateName == null || summary == null || jobName == null || enginePath == null || engineParameters == null || 
+				demoFile == null || relativeDemoPath == null || dpVideoPath == null || videoDestination == null 
+				|| executeBeforeCap == null || executeAfterCap == null) {
+			throw new DemoRecorderException("Error: Make sure that you filled the necessary fields! (file choosers!)");
+		}
+		
+		this.templateName = templateName;
+		this.summary = summary;
+		this.jobName = jobName;
+		this.enginePath = enginePath;
+		this.engineParameters = engineParameters;
+		this.demoFile = demoFile;
+		this.relativeDemoPath = relativeDemoPath;
+		this.dpVideoPath = dpVideoPath;
+		this.videoDestination = videoDestination;
+		this.executeBeforeCap = executeBeforeCap;
+		this.executeAfterCap = executeAfterCap;
+	}
+
+	public String getName() {
+		return templateName;
+	}
+
+	public String getSummary() {
+		return summary;
+	}
+	
+	public void setName(String name) {
+		this.templateName = name;
+	}
+
+	public void setSummary(String summary) {
+		this.summary = summary;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * Overwrite this method because here we want to do the read/write test for the path directly
+	 * (as this one already is the directory), and not its parent directory.
+	 * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setDemoFile(java.io.File)
+	 */
+	public void setDemoFile(File demoFile) {
+		if (demoFile == null || !demoFile.exists()) {
+			throw new DemoRecorderException("Could not locate demo file!");
+		}
+		if (!doReadWriteTest(demoFile)) {
+			throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");
+		}
+		this.demoFile = demoFile.getAbsoluteFile();
+	}
+	
+	/*
+	 * (non-Javadoc)
+	 * Overwrite this method because here we want to do the read/write test for the path directly
+	 * (as this one already is the directory), and not its parent directory.
+	 * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setVideoDestination(java.io.File)
+	 */
+	public void setVideoDestination(File videoDestination) {
+		//keep in mind, here videoDestination points to the destination directory, not the destination file
+		if (videoDestination == null || !videoDestination.isDirectory()) {
+			throw new DemoRecorderException("Could not locate the specified video destination directory");
+		}
+		
+		if (!this.doReadWriteTest(videoDestination)) {
+			throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");
+		}
+		
+		this.videoDestination = videoDestination.getAbsoluteFile();
+	}
+	
+	public String getJobName() {
+		return this.jobName;
+	}
+	
+	public void setJobName(String jobName) {
+		this.jobName = jobName;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,86 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.SystemColor;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+public class StatusBar extends JPanel {
+
+	private static final long serialVersionUID = -1471757496863555741L;
+	private JLabel currentActivity = null;
+	
+	private static final String STATE_IDLE = "Idle";
+	private static final String STATE_WORKING = "Working";
+
+	public StatusBar() {
+		BorderLayout borderLayout = new BorderLayout(0, 0);
+		setLayout(borderLayout);
+		JPanel rightPanel = new JPanel(new BorderLayout());
+		rightPanel.add(new JLabel(new AngledLinesWindowsCornerIcon()), BorderLayout.SOUTH);
+		rightPanel.setOpaque(false);
+
+		add(rightPanel, BorderLayout.EAST);
+
+		this.currentActivity = new JLabel("Idle");
+		add(this.currentActivity, BorderLayout.WEST);
+		setBackground(SystemColor.control);
+		setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.black));
+	}
+	
+	/**
+	 * Sets the state/display of the status bar to "idle" (false) or "working" (true).
+	 * @param state
+	 */
+	public void showState(boolean state) {
+		if (state) {
+			currentActivity.setText(STATE_WORKING);
+		} else {
+			currentActivity.setText(STATE_IDLE);
+		}
+	}
+
+	private static class AngledLinesWindowsCornerIcon implements Icon {
+		private static final Color WHITE_LINE_COLOR = new Color(255, 255, 255);
+
+		private static final Color GRAY_LINE_COLOR = new Color(172, 168, 153);
+		private static final int WIDTH = 13;
+
+		private static final int HEIGHT = 13;
+
+		public int getIconHeight() {
+			return HEIGHT;
+		}
+
+		public int getIconWidth() {
+			return WIDTH;
+		}
+
+		public void paintIcon(Component c, Graphics g, int x, int y) {
+
+			g.setColor(WHITE_LINE_COLOR);
+			g.drawLine(0, 12, 12, 0);
+			g.drawLine(5, 12, 12, 5);
+			g.drawLine(10, 12, 12, 10);
+
+			g.setColor(GRAY_LINE_COLOR);
+			g.drawLine(1, 12, 12, 1);
+			g.drawLine(2, 12, 12, 2);
+			g.drawLine(3, 12, 12, 3);
+
+			g.drawLine(6, 12, 12, 6);
+			g.drawLine(7, 12, 12, 7);
+			g.drawLine(8, 12, 12, 8);
+
+			g.drawLine(11, 12, 12, 11);
+			g.drawLine(12, 12, 12, 12);
+
+		}
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,1109 @@
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.help.HelpBroker;
+import javax.help.HelpSet;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.UIManager;
+import javax.swing.border.TitledBorder;
+
+import net.miginfocom.swing.MigLayout;
+
+import org.jdesktop.swingx.JXTable;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.jobs.RecordJob.State;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.DemoRecorderUI;
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobsTableModel;
+import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;
+import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties;
+import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties.XTableState;
+
+public class SwingGUI extends JFrame implements WindowListener, DemoRecorderUI {
+	
+	private static final long serialVersionUID = -7287303462488231068L;
+	public static final String JOB_TABLE_PREFERENCES_FILENAME = "jobsTable.pref";
+	public static final String TEMPLATE_TABLE_PREFERENCES_FILENAME = "templatesTable.pref";
+	public static final String TEMPLATE_TABLE_CONTENT_FILENAME = "templates.dat";
+
+	private DemoRecorderApplication appLayer;
+	private PreferencesDialog preferencesDialog;
+	
+	private JXTable jobsTable = null;
+	private JPopupMenu jobsTablePopupMenu;
+	private ActionListener jobButtonActionListener = new JobButtonActionListener();
+	private MouseListener jobsTableMouseListener = new JobsTableMouseListener();
+	
+	private JXTable templatesTable = null;
+	private JPopupMenu templatesTablePopupMenu;
+	private ActionListener templateButtonActionListener = new TemplateButtonActionListener();
+	private MouseListener templatesTableMouseListener = new TemplatesTableMouseListener();
+	
+	private ActionListener recordButtonActionListener = new RecordButtonActionListener();
+	
+	private static final String LABEL_JOB_CREATE = "Create";
+	private static final String LABEL_JOB_CREATE_FROM_TEMPL = "Create from template";
+	private static final String LABEL_JOB_DELETE = "Delete";
+	private static final String LABEL_JOB_CLEAR = "Clear";
+	private static final String LABEL_JOB_EDIT = "Edit job";
+	private static final String LABEL_JOB_DUPLICATE = "Duplicate job";
+	private static final String LABEL_JOB_START = "Start job";
+	private static final String LABEL_JOB_SHOWERROR = "Show error message";
+	private static final String LABEL_JOB_RESET_STATE_WAITING = "Reset job status to 'waiting'";
+	private static final String LABEL_JOB_RESET_STATE_DONE = "Reset job status to 'done'";
+	
+	private static final String LABEL_TEMPL_CREATE = "Create";
+	private static final String LABEL_TEMPL_CREATE_FROM_JOB = "Create from job";
+	private static final String LABEL_TEMPL_DELETE = "Delete";
+	private static final String LABEL_TEMPL_CLEAR = "Clear";
+	private static final String LABEL_TEMPL_EDIT = "Edit template";
+	private static final String LABEL_TEMPL_DUPLICATE = "Duplicate template";
+	
+	private ActionListener menuButtonActionListener = new MenuButtonActionListener();
+	private JMenuItem fileLoadQueue = new JMenuItem("Load job queue", getIcon("fileopen.png"));
+	private JMenuItem fileSaveQueue = new JMenuItem("Save job queue", getIcon("filesave.png"));
+	private JMenuItem filePreferences = new JMenuItem("Preferences", getIcon("advanced.png"));
+	private JMenuItem fileExit = new JMenuItem("Exit", getIcon("exit.png"));
+	private JMenuItem helpHelp = new JMenuItem("Show help", getIcon("help.png"));
+	private JMenuItem helpAbout = new JMenuItem("About", getIcon("info.png"));
+	private JFileChooser jobQueueSaveAsFC = new JFileChooser();
+	
+	private JButton jobs_create = new JButton(LABEL_JOB_CREATE, getIcon("edit_add.png"));
+	private JButton jobs_createFromTempl = new JButton(LABEL_JOB_CREATE_FROM_TEMPL, getIcon("view_right_p.png"));
+	private JButton jobs_delete = new JButton(LABEL_JOB_DELETE, getIcon("editdelete.png"));
+	private JButton jobs_clear = new JButton(LABEL_JOB_CLEAR, getIcon("editclear.png"));
+	private JMenuItem jobs_contextmenu_edit = new JMenuItem(LABEL_JOB_EDIT, getIcon("edit.png"));
+	private JMenuItem jobs_contextmenu_duplicate = new JMenuItem(LABEL_JOB_DUPLICATE, getIcon("editcopy.png"));
+	private JMenuItem jobs_contextmenu_delete = new JMenuItem(LABEL_JOB_DELETE, getIcon("editdelete.png"));
+	private JMenuItem jobs_contextmenu_start = new JMenuItem(LABEL_JOB_START, getIcon("player_play.png"));
+	private JMenuItem jobs_contextmenu_showerror = new JMenuItem(LABEL_JOB_SHOWERROR, getIcon("status_unknown.png"));
+	private JMenuItem jobs_contextmenu_resetstate_waiting = new JMenuItem(LABEL_JOB_RESET_STATE_WAITING, getIcon("quick_restart.png"));
+	private JMenuItem jobs_contextmenu_resetstate_done = new JMenuItem(LABEL_JOB_RESET_STATE_DONE, getIcon("quick_restart_blue.png"));
+	private List<JMenuItem> jobs_contextmenu_runPluginMenuItems = new ArrayList<JMenuItem>();
+	
+	private JButton templ_create = new JButton(LABEL_TEMPL_CREATE, getIcon("edit_add.png"));
+	private JButton templ_createFromJob = new JButton(LABEL_TEMPL_CREATE_FROM_JOB, getIcon("view_right_p.png"));
+	private JButton templ_delete = new JButton(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));
+	private JButton templ_clear = new JButton(LABEL_TEMPL_CLEAR, getIcon("editclear.png"));
+	private JMenuItem templ_contextmenu_edit = new JMenuItem(LABEL_TEMPL_EDIT, getIcon("edit.png"));
+	private JMenuItem templ_contextmenu_duplicate = new JMenuItem(LABEL_TEMPL_DUPLICATE, getIcon("editcopy.png"));
+	private JMenuItem templ_contextmenu_delete = new JMenuItem(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));
+	
+	private static final String PROCESSING_START = "Start processing";
+	private static final String PROCESSING_STOP_NOW = "Stop processing";
+	private static final String PROCESSING_STOP_LATER = "Processing will stop after current job finished";
+	private JButton processing_start = new JButton(PROCESSING_START, getIcon("player_play.png"));
+	private JButton processing_stop = new JButton(PROCESSING_STOP_NOW, getIcon("player_pause.png"));
+	
+	private StatusBar statusBar = new StatusBar();
+	
+	private static HelpBroker mainHelpBroker = null;
+	private static final String mainHelpSetName = "help/DemoRecorderHelp.hs";
+
+	public SwingGUI(DemoRecorderApplication appLayer) {
+		super("Nexuiz Demo Recorder v0.2");
+		addWindowListener(this);
+
+		this.appLayer = appLayer;
+
+		this.setupLayout();
+		this.setupHelp();
+		this.preferencesDialog = new PreferencesDialog(this, appLayer);
+
+		setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+		// Display the window.
+		pack();
+		setVisible(true);
+		//now that we have the GUI we can set the parent window for the error dialog
+		ShowErrorDialogExceptionHandler.setParentWindow(this);
+	}
+
+	private void setupHelp() {
+		if (mainHelpBroker == null){
+			HelpSet mainHelpSet = null;
+
+			try {
+				URL hsURL = HelpSet.findHelpSet(null, mainHelpSetName);
+				mainHelpSet = new HelpSet(null, hsURL);
+			} catch (Exception e) {
+				DemoRecorderUtils.showNonCriticalErrorDialog("Could not properly create the help", e, true);
+			}
+
+			if (mainHelpSet != null)
+				mainHelpBroker = mainHelpSet.createHelpBroker();
+			}
+	}
+
+	private void setupLayout() {
+		setLayout(new MigLayout("wrap 1,insets 10", "[400:700:,grow,fill]",
+				"[grow,fill][grow,fill][][]"));
+		Container contentPane = getContentPane();
+		setJMenuBar(this.buildMenu());
+
+		this.setupTemplatePanel();
+		this.setupJobPanel();
+		this.setupRecordPanel();
+
+		contentPane.add(statusBar, "south,height 23::");
+	}
+
+	private void setupTemplatePanel() {
+		JPanel templatePanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));
+		TitledBorder templatePanelTitle = BorderFactory.createTitledBorder("Templates");
+		templatePanel.setBorder(templatePanelTitle);
+		getContentPane().add(templatePanel);
+		
+		this.setupTemplatesTable();
+		this.loadTableStates(this.templatesTable);
+		JScrollPane templateScrollPane = new JScrollPane(templatesTable);
+		templatePanel.add(templateScrollPane);
+		
+		this.templ_create.addActionListener(this.templateButtonActionListener);
+		this.templ_createFromJob.addActionListener(this.templateButtonActionListener);
+		this.templ_delete.addActionListener(this.templateButtonActionListener);
+		this.templ_clear.addActionListener(this.templateButtonActionListener);
+		
+		this.templ_contextmenu_edit.addActionListener(this.templateButtonActionListener);
+		this.templ_contextmenu_duplicate.addActionListener(this.templateButtonActionListener);
+		this.templ_contextmenu_delete.addActionListener(this.templateButtonActionListener);
+		
+		this.configureTableButtons();
+		
+		JPanel templateControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));
+		templateControlButtonPanel.add(this.templ_create);
+		templateControlButtonPanel.add(this.templ_createFromJob);
+		templateControlButtonPanel.add(this.templ_delete);
+		templateControlButtonPanel.add(this.templ_clear);
+		templatePanel.add(templateControlButtonPanel);
+	}
+
+	private void setupJobPanel() {
+		JPanel jobPanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));
+		TitledBorder jobPanelTitle = BorderFactory.createTitledBorder("Jobs");
+		jobPanel.setBorder(jobPanelTitle);
+		getContentPane().add(jobPanel);
+
+		this.setupJobsTable();
+		this.loadTableStates(this.jobsTable);
+		
+		JScrollPane jobScrollPane = new JScrollPane(jobsTable);
+		jobPanel.add(jobScrollPane);
+		
+		this.jobs_create.addActionListener(this.jobButtonActionListener);
+		this.jobs_createFromTempl.addActionListener(this.jobButtonActionListener);
+		this.jobs_delete.addActionListener(this.jobButtonActionListener);
+		this.jobs_clear.addActionListener(this.jobButtonActionListener);
+		
+		this.jobs_contextmenu_edit.addActionListener(this.jobButtonActionListener);
+		this.jobs_contextmenu_duplicate.addActionListener(this.jobButtonActionListener);
+		this.jobs_contextmenu_delete.addActionListener(this.jobButtonActionListener);
+		this.jobs_contextmenu_start.addActionListener(this.jobButtonActionListener);
+		this.jobs_contextmenu_showerror.addActionListener(this.jobButtonActionListener);
+		this.jobs_contextmenu_resetstate_waiting.addActionListener(this.jobButtonActionListener);
+		this.jobs_contextmenu_resetstate_done.addActionListener(this.jobButtonActionListener);
+		
+		//initialize button states
+		configureTableButtons();
+		
+		JPanel jobControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));
+		jobControlButtonPanel.add(this.jobs_create);
+		jobControlButtonPanel.add(this.jobs_createFromTempl);
+		jobControlButtonPanel.add(this.jobs_delete);
+		jobControlButtonPanel.add(this.jobs_clear);
+		jobPanel.add(jobControlButtonPanel);
+	}
+	
+	private void setupJobsTable() {
+		RecordJobsTableModel tableModel = new RecordJobsTableModel(this.appLayer);
+		jobsTable = new JXTable(tableModel);
+		jobsTable.setColumnControlVisible(true);
+		jobsTable.setPreferredScrollableViewportSize(new Dimension(400, 100));
+		jobsTable.addMouseListener(this.jobsTableMouseListener);
+	}
+	
+	private void setupTemplatesTable() {
+		RecordJobTemplatesTableModel tableModel = new RecordJobTemplatesTableModel();
+		templatesTable = new JXTable(tableModel);
+		templatesTable.setColumnControlVisible(true);
+		templatesTable.setPreferredScrollableViewportSize(new Dimension(400, 100));
+		templatesTable.addMouseListener(this.templatesTableMouseListener);
+	}
+
+	private void setupRecordPanel() {
+		JPanel recButtonPanel = new JPanel(new MigLayout());
+		recButtonPanel.add(processing_start);
+		recButtonPanel.add(processing_stop);
+		processing_stop.setEnabled(false);
+		processing_start.addActionListener(recordButtonActionListener);
+		processing_stop.addActionListener(recordButtonActionListener);
+		getContentPane().add(recButtonPanel);
+	}
+
+	public static void setSystemLAF() {
+		try {
+			// Set System L&F
+			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+		} catch (Exception e) {
+		}
+	}
+	
+	public void RecordJobPropertiesChange(RecordJob job) {
+		RecordJobsTableModel jobsTableModel = (RecordJobsTableModel) this.jobsTable.getModel();
+		List<RecordJob> recordJobs = jobsTableModel.getRecordJobs();
+		int jobIndex = recordJobs.indexOf(job);
+		if (jobIndex == -1) {
+			//new job
+			recordJobs.add(job);
+			//add job at the end of the table:
+			int position = jobsTableModel.getRowCount() - 1;
+			jobsTableModel.fireTableRowsInserted(position, position);
+		} else {
+			//job already existed
+			jobIndex = this.jobsTable.convertRowIndexToView(jobIndex); //convert due to possible view sorting
+			jobsTableModel.fireTableRowsUpdated(jobIndex, jobIndex);
+		}
+	}
+
+	public void recordingFinished() {
+		JOptionPane.showMessageDialog(SwingGUI.this, "Finished recording all jobs", "Recording done", JOptionPane.INFORMATION_MESSAGE);
+		statusBar.showState(false);
+		processing_start.setEnabled(true);
+		processing_stop.setEnabled(false);
+		processing_stop.setText(PROCESSING_STOP_NOW);
+	}
+
+	private JMenuBar buildMenu() {
+		JMenuBar menuBar = new JMenuBar();
+
+		JMenu fileMenu = new JMenu("File");
+		fileMenu.add(fileLoadQueue);
+		fileMenu.add(fileSaveQueue);
+		fileMenu.add(filePreferences);
+		fileMenu.add(fileExit);
+		menuBar.add(fileMenu);
+		
+		fileLoadQueue.addActionListener(menuButtonActionListener);
+		fileSaveQueue.addActionListener(menuButtonActionListener);
+		filePreferences.addActionListener(menuButtonActionListener);
+		fileExit.addActionListener(menuButtonActionListener);
+
+		JMenu helpMenu = new JMenu("Help");
+		helpMenu.add(helpHelp);
+		helpMenu.add(helpAbout);
+		menuBar.add(helpMenu);
+		
+		helpHelp.addActionListener(menuButtonActionListener);
+		helpAbout.addActionListener(menuButtonActionListener);
+		
+		this.setupEncoderPluginButtons();
+		
+		this.jobsTablePopupMenu = new JPopupMenu();
+		this.jobsTablePopupMenu.add(jobs_contextmenu_edit);
+		this.jobsTablePopupMenu.add(jobs_contextmenu_duplicate);
+		this.jobsTablePopupMenu.add(jobs_contextmenu_delete);
+		this.jobsTablePopupMenu.add(jobs_contextmenu_start);
+		//add JMenus for plugins
+		for (JMenuItem menuItem : jobs_contextmenu_runPluginMenuItems) {
+			this.jobsTablePopupMenu.add(menuItem);
+		}
+		this.jobsTablePopupMenu.add(jobs_contextmenu_showerror);
+		this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_waiting);
+		this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_done);
+		
+		
+		
+		
+		this.templatesTablePopupMenu = new JPopupMenu();
+		this.templatesTablePopupMenu.add(templ_contextmenu_edit);
+		this.templatesTablePopupMenu.add(templ_contextmenu_duplicate);
+		this.templatesTablePopupMenu.add(templ_contextmenu_delete);
+
+		return menuBar;
+	}
+	
+	private void setupEncoderPluginButtons() {
+		for (EncoderPlugin plugin : appLayer.getEncoderPlugins()) {
+			JMenuItem pluginMenuItem = new JMenuItem("Just run " + plugin.getName() + " plugin", getIcon("package.png"));
+			pluginMenuItem.addActionListener(jobButtonActionListener);
+			this.jobs_contextmenu_runPluginMenuItems.add(pluginMenuItem);
+		}
+	}
+
+	private void saveTableStates(JXTable table) {
+		String fileName;
+		if (table == jobsTable) {
+			fileName = JOB_TABLE_PREFERENCES_FILENAME;
+		} else {
+			fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;
+		}
+		String exceptionMessage = "An error occurred while trying to save the table state file " + fileName;
+		
+		XProperties.XTableProperty t = new XProperties.XTableProperty();
+		XTableState tableState;
+		try {
+			tableState = (XTableState) t.getSessionState(table);
+		} catch (Exception e) { //most likely ClassCastException
+			DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+			return;
+		}
+		
+		File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);
+		DemoRecorderUtils.attemptFileCreation(tableStateFile);
+		
+		try {
+			FileOutputStream fout = new FileOutputStream(tableStateFile);
+			ObjectOutputStream oos = new ObjectOutputStream(fout);
+			oos.writeObject(tableState);
+			oos.close();
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+		}
+	}
+	
+	private void loadTableStates(JXTable table) {
+		String fileName;
+		if (table == jobsTable) {
+			fileName = JOB_TABLE_PREFERENCES_FILENAME;
+		} else {
+			fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;
+		}
+		
+		XProperties.XTableProperty t = new XProperties.XTableProperty();
+		
+		File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);
+		
+		XTableState tableState;
+		
+		try {
+			FileInputStream fin = new FileInputStream(tableStateFile);
+			ObjectInputStream ois = new ObjectInputStream(fin);
+			tableState = (XTableState) ois.readObject();
+			t.setSessionState(table, tableState);
+		} catch (Exception e) {
+			 //manually hide columns
+			if (table == jobsTable) {
+				//re-create table to be sure
+				this.setupJobsTable();
+				//manually hide some columns
+				jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_AFTER_CAP).setVisible(false);
+				jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_BEFORE_CAP).setVisible(false);
+				jobsTable.getColumnExt(RecordJobsTableModel.VIDEO_DESTINATION_PATH).setVisible(false);
+				jobsTable.getColumnExt(RecordJobsTableModel.DPVIDEO_PATH).setVisible(false);
+				jobsTable.getColumnExt(RecordJobsTableModel.RELATIVE_DEMO_PATH).setVisible(false);
+				jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PARAMETERS).setVisible(false);
+				jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PATH).setVisible(false);
+			} else {
+				//re-create table to be sure
+				this.setupTemplatesTable();
+				//manually hide some columns
+				templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_AFTER_CAP).setVisible(false);
+				templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_BEFORE_CAP).setVisible(false);
+				templatesTable.getColumnExt(RecordJobTemplatesTableModel.VIDEO_DESTINATION_PATH).setVisible(false);
+				templatesTable.getColumnExt(RecordJobTemplatesTableModel.DPVIDEO_PATH).setVisible(false);
+				templatesTable.getColumnExt(RecordJobTemplatesTableModel.RELATIVE_DEMO_PATH).setVisible(false);
+				templatesTable.getColumnExt(RecordJobTemplatesTableModel.DEMO_FILE_PATH).setVisible(false);
+				templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PARAMETERS).setVisible(false);
+				templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PATH).setVisible(false);
+				templatesTable.getColumnExt(RecordJobTemplatesTableModel.JOB_NAME).setVisible(false);
+			}
+		}
+	}
+	
+	private class MenuButtonActionListener implements ActionListener {
+
+		public void actionPerformed(ActionEvent e) {
+			if (e.getSource() == fileLoadQueue) {
+				int result = jobQueueSaveAsFC.showOpenDialog(SwingGUI.this);
+				if (result == JFileChooser.APPROVE_OPTION) {
+					File selectedFile = jobQueueSaveAsFC.getSelectedFile();
+					if (selectedFile.isFile()) {
+						RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();
+						tableModel.loadNewJobQueue(selectedFile);
+						configureTableButtons();
+					}
+				}
+				
+			} else if (e.getSource() == fileSaveQueue) {
+				int result = jobQueueSaveAsFC.showSaveDialog(SwingGUI.this);
+				if (result == JFileChooser.APPROVE_OPTION) {
+					File selectedFile = jobQueueSaveAsFC.getSelectedFile();
+					if (!DemoRecorderUtils.getFileExtension(selectedFile).equals("queue")) {
+						//if file is not a .queue file, make it one
+						selectedFile = new File(selectedFile.getAbsoluteFile() + ".queue");
+					}
+					if (selectedFile.exists()) {
+						int confirm = JOptionPane.showConfirmDialog(SwingGUI.this, "File already exists. Are you sure you want to overwrite it?", "Confirm overwrite", JOptionPane.YES_NO_OPTION);
+						if (confirm == JOptionPane.NO_OPTION) {
+							return;
+						}
+					}
+					appLayer.saveJobQueue(selectedFile);
+				}
+			} else if (e.getSource() == filePreferences) {
+				preferencesDialog.showDialog();
+			} else if (e.getSource() == fileExit) {
+				shutDown();
+			} else if (e.getSource() == helpHelp) {
+				if (mainHelpBroker != null) {
+					mainHelpBroker.setDisplayed(true);
+				}
+			} else if (e.getSource() == helpAbout) {
+				showAboutBox();
+			}
+		}
+		
+	}
+
+	/**
+	 * Listens to the clicks on buttons that are in the job panel (next to the jobs table)
+	 * or its context menu.
+	 */
+	private class JobButtonActionListener implements ActionListener {
+
+		public void actionPerformed(ActionEvent e) {
+			List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+			List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);
+			if (e.getSource() == jobs_create) {
+				JobDialog jobDialog = new JobDialog(SwingGUI.this, appLayer);
+				jobDialog.showDialog();
+				configureTableButtons();
+			}
+			else if (e.getSource() == jobs_createFromTempl) {
+				if (selectedTemplates.size() != 1) {
+					return;
+				}
+				RecordJobTemplate template = (RecordJobTemplate) selectedTemplates.get(0);
+//				JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer);
+				JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer, JobDialog.CREATE_JOB_FROM_TEMPLATE);
+				jobDialog.showDialog();
+				configureTableButtons();
+			}
+			else if (e.getSource() == jobs_delete || e.getSource() == jobs_contextmenu_delete) {
+				int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected job(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);
+				if (result == JOptionPane.YES_OPTION) {
+					deleteSelectedJobs(false);
+					configureTableButtons();
+				}
+			}
+			else if (e.getSource() == jobs_clear) {
+				int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the job list?", "Confirm clear", JOptionPane.YES_NO_OPTION);
+				if (result == JOptionPane.YES_OPTION) {
+					deleteSelectedJobs(true);
+					configureTableButtons();
+				}
+			} else if (e.getSource() == jobs_contextmenu_edit) {
+				if (selectedJobs.size() == 1) {
+					RecordJob selectedJob = selectedJobs.get(0);
+					JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);
+					jobDialog.showDialog();
+					configureTableButtons();
+				}
+			} else if (e.getSource() == jobs_contextmenu_showerror) {
+				if (selectedJobs.size() == 1) {
+					RecordJob selectedJob = selectedJobs.get(0);
+					DemoRecorderUtils.showNonCriticalErrorDialog(selectedJob.getLastException());
+				}
+			} else if (e.getSource() == jobs_contextmenu_resetstate_waiting) {
+				for (RecordJob job : selectedJobs) {
+					job.setState(RecordJob.State.WAITING);
+				}
+			} else if (e.getSource() == jobs_contextmenu_resetstate_done) {
+				for (RecordJob job : selectedJobs) {
+					if (job.getState() == State.ERROR_PLUGIN) {
+						job.setState(RecordJob.State.DONE);
+					}
+				}
+			} else if (e.getSource() == jobs_contextmenu_start) {
+				appLayer.recordSelectedJobs(selectedJobs);
+				if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+					processing_start.setEnabled(false);
+					processing_stop.setEnabled(true);
+					statusBar.showState(true);
+				}
+			} else if (e.getSource() == jobs_contextmenu_duplicate) {
+				if (selectedJobs.size() > 0) {
+					this.duplicateRecordJobs(selectedJobs);
+					//select all new duplicates in the table automatically
+					jobsTable.setRowSelectionInterval(jobsTable.getRowCount() - selectedJobs.size(), jobsTable.getRowCount() - 1);
+					configureTableButtons();
+				}
+			} else if (jobs_contextmenu_runPluginMenuItems.contains(e.getSource())) {
+				int index = jobs_contextmenu_runPluginMenuItems.indexOf(e.getSource());
+				EncoderPlugin selectedPlugin = appLayer.getEncoderPlugins().get(index);
+				
+				appLayer.executePluginForSelectedJobs(selectedPlugin, selectedJobs);
+				if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+					processing_start.setEnabled(false);
+					processing_stop.setEnabled(true);
+					statusBar.showState(true);
+				}
+			}
+		}
+		
+		private void duplicateRecordJobs(List<RecordJob> jobs) {
+			String nameSuffix = appLayer.getPreferences().getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE);
+			for (RecordJob job : jobs) {
+				RecordJob newJob = appLayer.createRecordJob(
+					job.getJobName() + nameSuffix,
+					job.getEnginePath(),
+					job.getEngineParameters(),
+					job.getDemoFile(),
+					job.getRelativeDemoPath(),
+					job.getDpVideoPath(),
+					job.getVideoDestination(),
+					job.getExecuteBeforeCap(),
+					job.getExecuteAfterCap(),
+					job.getStartSecond(),
+					job.getEndSecond()
+				);
+				newJob.setEncoderPluginSettings(job.getEncoderPluginSettings());
+			}
+		}
+		
+	}
+	
+	private class TemplateButtonActionListener implements ActionListener {
+		public void actionPerformed(ActionEvent e) {
+			if (e.getSource() == templ_create) {
+				RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+				JobDialog jobDialog = new JobDialog(SwingGUI.this, tableModel, templatesTable, appLayer);
+				jobDialog.showDialog();
+				configureTableButtons();
+			}
+			else if (e.getSource() == templ_createFromJob) {
+				this.createTemplateFromJob();
+				configureTableButtons();
+			}
+			else if (e.getSource() == templ_delete || e.getSource() == templ_contextmenu_delete) {
+				int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected template(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);
+				if (result == JOptionPane.YES_OPTION) {
+					deleteSelectedTemplates(false);
+				}
+				configureTableButtons();
+			}
+			else if (e.getSource() == templ_clear) {
+				int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the template list?", "Confirm clear", JOptionPane.YES_NO_OPTION);
+				if (result == JOptionPane.YES_OPTION) {
+					deleteSelectedTemplates(true);
+				}
+				configureTableButtons();
+			}
+			else if (e.getSource() == templ_contextmenu_edit) {
+				List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);
+				if (selectedTemplates.size() == 1) {
+					RecordJobTemplate selectedTemplate = (RecordJobTemplate) selectedTemplates.get(0);
+					JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedTemplate, appLayer, JobDialog.EDIT_TEMPLATE);
+					jobDialog.showDialog();
+					configureTableButtons();
+				}
+			}
+			else if (e.getSource() == templ_contextmenu_duplicate) {
+				List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);
+				if (selectedTemplates.size() > 0) {
+					this.duplicateTemplates(selectedTemplates);
+					//select all new duplicates in the table automatically
+					templatesTable.setRowSelectionInterval(templatesTable.getRowCount() - selectedTemplates.size(), templatesTable.getRowCount() - 1);
+					configureTableButtons();
+				}
+			}
+		}
+		
+		private void createTemplateFromJob() {
+			List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+			if (selectedJobs.size() == 1) {
+				RecordJob job = selectedJobs.get(0);
+				RecordJobTemplate templ = new RecordJobTemplate(
+					"Generated from job",
+					"Generated from job",
+					job.getJobName(),
+					job.getEnginePath(),
+					job.getEngineParameters(),
+					job.getDemoFile().getParentFile(),
+					job.getRelativeDemoPath(),
+					job.getDpVideoPath(),
+					job.getVideoDestination().getParentFile(),
+					job.getExecuteBeforeCap(),
+					job.getExecuteAfterCap()
+				);
+				templ.setEncoderPluginSettings(job.getEncoderPluginSettings());
+				
+				RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+				tableModel.addRecordJobTemplate(templ);
+			}
+		}
+		
+		private void duplicateTemplates(List<RecordJob> selectedTemplates) {
+			for (RecordJob job : selectedTemplates) {
+				RecordJobTemplate template = (RecordJobTemplate) job;
+				RecordJobTemplate templ = new RecordJobTemplate(
+					template.getName(),
+					template.getSummary(),
+					template.getJobName(),
+					template.getEnginePath(),
+					template.getEngineParameters(),
+					template.getDemoFile(),
+					template.getRelativeDemoPath(),
+					template.getDpVideoPath(),
+					template.getVideoDestination(),
+					template.getExecuteBeforeCap(),
+					template.getExecuteAfterCap()
+				);
+				templ.setEncoderPluginSettings(template.getEncoderPluginSettings());
+				
+				RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+				tableModel.addRecordJobTemplate(templ);
+			}
+		}
+	}
+	
+	private class RecordButtonActionListener implements ActionListener {
+
+		public void actionPerformed(ActionEvent e) {
+			if (e.getSource() == processing_start) {
+				appLayer.startRecording();
+				if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+					processing_start.setEnabled(false);
+					processing_stop.setEnabled(true);
+					statusBar.showState(true);
+				}
+			} else if (e.getSource() == processing_stop) {
+				if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+					appLayer.stopRecording();
+					processing_stop.setEnabled(false);
+					processing_stop.setText(PROCESSING_STOP_LATER);
+				}
+			}
+		}
+	}
+	
+	private void deleteSelectedJobs(boolean deleteAllJobs) {
+		RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();
+		if (deleteAllJobs) {
+			int rowCount = jobsTable.getRowCount();
+			for (int i = rowCount - 1; i >= 0; i--) {
+				int modelRowIndex = jobsTable.convertRowIndexToModel(i);
+				tableModel.deleteRecordJob(modelRowIndex, i);
+			}
+		} else {
+			int[] selectedRows = jobsTable.getSelectedRows();
+			for (int i = selectedRows.length - 1; i >= 0; i--) {
+				int modelRowIndex = jobsTable.convertRowIndexToModel(selectedRows[i]);
+				tableModel.deleteRecordJob(modelRowIndex, selectedRows[i]);
+			}
+		}
+	}
+	
+	private void deleteSelectedTemplates(boolean deleteAllTemplates) {
+		RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+		if (deleteAllTemplates) {
+			int rowCount = templatesTable.getRowCount();
+			for (int i = rowCount - 1; i >= 0; i--) {
+				int modelRowIndex = templatesTable.convertRowIndexToModel(i);
+				tableModel.deleteRecordJobTemplate(modelRowIndex, i);
+			}
+		} else {
+			int[] selectedRows = templatesTable.getSelectedRows();
+			for (int i = selectedRows.length - 1; i >= 0; i--) {
+				int modelRowIndex = templatesTable.convertRowIndexToModel(selectedRows[i]);
+				tableModel.deleteRecordJobTemplate(modelRowIndex, selectedRows[i]);
+			}
+		}
+		//update the button state of buttons dealing with jobs
+		this.configureTableButtons();
+	}
+	
+	/**
+	 * Iterates through all RecordJob objects (or just the selected ones) and returns true
+	 * if at least one of them has one or more has the given state(s).
+	 * @param state
+	 * @param justSelectedJobs
+	 * @return
+	 */
+	private boolean checkJobStates(RecordJob.State[] state, boolean justSelectedJobs) {
+		boolean foundState = false;
+		List<RecordJob> jobsToLookAt = null;
+		if (!justSelectedJobs) {
+			jobsToLookAt = this.appLayer.getRecordJobs();
+		} else {
+			jobsToLookAt = getSelectedRecordJobs(jobsTable);
+		}
+		
+		for (RecordJob currentJob : jobsToLookAt) {
+			for (int i = 0; i < state.length; i++) {
+				if (currentJob.getState() == state[i]) {
+					foundState = true;
+					break;
+				}
+			}
+		}
+		return foundState;
+	}
+	
+	/**
+	 * Returns the list of selected RecordJobs or RecordJobTemplates.
+	 * @param table jobsTable or templatesTable
+	 * @return list of selected RecordJobs or RecordJobTemplates
+	 */
+	private List<RecordJob> getSelectedRecordJobs(JXTable table) {
+		List<RecordJob> list = new ArrayList<RecordJob>();
+		if (table.getSelectedRowCount() > 0) {
+			int[] selectedRows = table.getSelectedRows();
+			for (int i = 0; i < selectedRows.length; i++) {
+				int modelRowIndex = table.convertRowIndexToModel(selectedRows[i]);
+				if (table == jobsTable) {
+					RecordJobsTableModel tableModel = (RecordJobsTableModel) table.getModel();
+					RecordJob job = tableModel.getRecordJob(modelRowIndex);
+					if (job != null) {
+						list.add(job);
+					}
+				} else {
+					RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) table.getModel();
+					RecordJobTemplate template = tableModel.getRecordJobTemplate(modelRowIndex);
+					if (template != null) {
+						list.add(template);
+					}
+				}
+			}
+		}
+		
+		return list;
+	}
+	
+	private void configureTableButtons() {
+		if (jobsTable != null) {
+			if (jobsTable.getRowCount() == 0) {
+				jobs_clear.setEnabled(false);
+				jobs_delete.setEnabled(false);
+			} else {
+				jobs_clear.setEnabled(true);
+				jobs_delete.setEnabled(true);
+				if (jobsTable.getSelectedRowCount() == 0) {
+					jobs_delete.setEnabled(false);
+				} else {
+					//go through all elements and check for attributes PROCESSING
+					RecordJob.State[] lookForState = {RecordJob.State.PROCESSING};
+					boolean foundState = checkJobStates(lookForState, false);
+					if (foundState) {
+						//we have to disable the clear and delete button
+						jobs_delete.setEnabled(false);
+					}
+				}
+			}
+			if (templatesTable.getSelectedRowCount() == 1) {
+				jobs_createFromTempl.setEnabled(true);
+			} else {
+				jobs_createFromTempl.setEnabled(false);
+			}
+		}
+		
+		if (templatesTable != null) {
+			templ_createFromJob.setEnabled(false);
+			templ_delete.setEnabled(false);
+			templ_clear.setEnabled(false);
+			
+			if (jobsTable != null && jobsTable.getSelectedRowCount() == 1) {
+				templ_createFromJob.setEnabled(true);
+			}
+			
+			if (templatesTable.getSelectedRowCount() > 0) {
+				templ_delete.setEnabled(true);
+			}
+			
+			if (templatesTable.getRowCount() > 0) {
+				templ_clear.setEnabled(true);
+			}
+		}
+	}
+	
+	private class JobsTableMouseListener implements MouseListener {
+
+		public void mouseClicked(MouseEvent e) {
+			if (e != null && e.getClickCount() == 2) {
+				List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+				if (selectedJobs.size() == 1) {
+					RecordJob selectedJob = selectedJobs.get(0);
+					if (selectedJob.getState() != RecordJob.State.PROCESSING) {
+						JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);
+						jobDialog.showDialog();
+					}
+				}
+			} else {
+				configureTableButtons();
+			}
+		}
+
+		public void mouseEntered(MouseEvent e) {}
+
+		public void mouseExited(MouseEvent e) {}
+
+		public void mousePressed(MouseEvent e) {
+			this.showPopupMenu(e);
+		}
+
+		public void mouseReleased(MouseEvent e) {
+			this.showPopupMenu(e);
+		}
+		
+		private void showPopupMenu(MouseEvent e) {
+			if (e.isPopupTrigger()) {
+				JTable table = (JTable)(e.getSource());
+				Point p = e.getPoint();
+				int row = table.rowAtPoint(p);
+				int[] selectedRows = table.getSelectedRows();
+				//figure out whether we have to reselect the current row under the pointer,
+				//which is only the case if the already selected rows don't include the one under
+				//the pointer yet
+				boolean reSelect = true;
+				for (int i = 0; i < selectedRows.length; i++) {
+					if (row == selectedRows[i]) {
+						reSelect = false;
+						break;
+					}
+				}
+				
+				if (row != -1 && reSelect) {
+					table.setRowSelectionInterval(row, row);
+				}
+				
+				this.configurePopupMenu();
+				configureTableButtons();
+				jobsTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());
+			}
+		}
+		
+		private void configurePopupMenu() {
+			//Disable all buttons first
+			jobs_contextmenu_edit.setEnabled(false);
+			jobs_contextmenu_duplicate.setEnabled(false);
+			jobs_contextmenu_delete.setEnabled(false);
+			jobs_contextmenu_resetstate_waiting.setEnabled(false);
+			jobs_contextmenu_resetstate_done.setEnabled(false);
+			jobs_contextmenu_showerror.setEnabled(false);
+			jobs_contextmenu_start.setEnabled(false);
+			for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {
+				pluginItem.setEnabled(false);
+			}
+			
+			//edit, duplicate, and show error buttons
+			if (jobsTable.getSelectedRowCount() == 1) {
+				jobs_contextmenu_edit.setEnabled(true);
+				
+				//Show error button
+				List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+				RecordJob selectedJob = selectedJobs.get(0);
+				if (selectedJob.getState() == RecordJob.State.ERROR || selectedJob.getState() == RecordJob.State.ERROR_PLUGIN) {
+					jobs_contextmenu_showerror.setEnabled(true);
+				}
+			}
+			
+			if (jobsTable.getSelectedRowCount() > 0) {
+				jobs_contextmenu_duplicate.setEnabled(true);
+				//Delete button
+				RecordJob.State[] states = {RecordJob.State.PROCESSING};
+				if (!checkJobStates(states, false)) {
+					//none of the jobs is processing
+					jobs_contextmenu_delete.setEnabled(true);
+					jobs_contextmenu_resetstate_waiting.setEnabled(true);
+				} else {
+					jobs_contextmenu_edit.setEnabled(false);
+					jobs_contextmenu_duplicate.setEnabled(false);
+				}
+				
+				//Start button
+				RecordJob.State[] states2 = {RecordJob.State.ERROR, RecordJob.State.DONE, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};
+				if (!checkJobStates(states2, true)) {
+					//only enable start if none of the selected jobs as any of the States above
+					//as the only job State that is not listed is "waiting", we only enable the button if all jobs are waiting
+					jobs_contextmenu_start.setEnabled(true);
+				}
+				
+				//reset to 'done' button
+				RecordJob.State[] states3 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING};
+				if (!checkJobStates(states3, true)) {
+					//only enable the "reset to done" button when processes have the state DONE or ERROR_PLUGIN
+					jobs_contextmenu_resetstate_done.setEnabled(true);
+				}
+				
+				//plugin buttons, enable only when state of the job is DONE
+				RecordJob.State[] states4 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};
+				if (!checkJobStates(states4, true)) {
+					int counter = 0;
+					for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {
+						if (appLayer.getEncoderPlugins().get(counter).isEnabled()) {
+							pluginItem.setEnabled(true);
+						}
+						counter++;
+					}
+				}
+			}
+		}
+		
+	}
+	
+	private class TemplatesTableMouseListener implements MouseListener {
+
+		public void mouseClicked(MouseEvent e) {
+			if (e != null && e.getClickCount() == 2) {
+				List<RecordJob> selectedJobs = getSelectedRecordJobs(templatesTable);
+				if (selectedJobs.size() == 1) {
+					RecordJobTemplate selectedJob = (RecordJobTemplate) selectedJobs.get(0);
+					JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer, JobDialog.EDIT_TEMPLATE);
+					jobDialog.showDialog();
+					configureTableButtons();
+				}
+			} else {
+				configureTableButtons();
+			}
+		}
+
+		public void mouseEntered(MouseEvent e) {}
+
+		public void mouseExited(MouseEvent e) {}
+
+		public void mousePressed(MouseEvent e) {
+			this.showPopupMenu(e);
+		}
+
+		public void mouseReleased(MouseEvent e) {
+			this.showPopupMenu(e);
+		}
+		
+		private void showPopupMenu(MouseEvent e) {
+			if (e.isPopupTrigger()) {
+				JTable table = (JTable)(e.getSource());
+				Point p = e.getPoint();
+				int row = table.rowAtPoint(p);
+				int[] selectedRows = table.getSelectedRows();
+				//figure out whether we have to reselect the current row under the pointer,
+				//which is only the case if the already selected rows don't include the one under
+				//the pointer yet
+				boolean reSelect = true;
+				for (int i = 0; i < selectedRows.length; i++) {
+					if (row == selectedRows[i]) {
+						reSelect = false;
+						break;
+					}
+				}
+				
+				if (row != -1 && reSelect) {
+					table.setRowSelectionInterval(row, row);
+				}
+				
+				this.configurePopupMenu();
+				configureTableButtons();
+				templatesTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());
+			}
+		}
+		
+		private void configurePopupMenu() {
+			//Various buttons
+			templ_contextmenu_edit.setEnabled(false);
+			templ_contextmenu_duplicate.setEnabled(false);
+			templ_contextmenu_delete.setEnabled(false);
+			
+			//Edit button
+			if (templatesTable.getSelectedRowCount() == 1) {
+				templ_contextmenu_edit.setEnabled(true);
+			}
+			
+			//Delete and duplicate button
+			if (templatesTable.getSelectedRowCount() > 0) {
+				templ_contextmenu_delete.setEnabled(true);
+				templ_contextmenu_duplicate.setEnabled(true);
+			}
+		}
+	}
+		
+	private void showAboutBox() {
+        try {
+            InputStream inStream = ClassLoader.getSystemResourceAsStream("about.html");
+            StringBuffer out = new StringBuffer();
+            byte[] b = new byte[4096];
+            for (int n; (n = inStream.read(b)) != -1;) {
+                out.append(new String(b, 0, n));
+            }
+            String htmlString = out.toString();
+            htmlString = htmlString.replaceAll("[\\r\\n]", "");
+            JOptionPane.showMessageDialog(this, htmlString, "About", JOptionPane.PLAIN_MESSAGE);
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+	public void windowActivated(WindowEvent e) {}
+	public void windowClosed(WindowEvent e) {}
+	public void windowDeactivated(WindowEvent e) {}
+	public void windowDeiconified(WindowEvent e) {}
+	public void windowIconified(WindowEvent e) {}
+	public void windowOpened(WindowEvent e) {}
+
+	public void windowClosing(WindowEvent e) {
+		this.shutDown();
+	}
+
+	private void shutDown() {
+		if (this.appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+			int result = JOptionPane.showConfirmDialog(this, "There are still jobs being recorded. Are you sure you want to exit?", "Confirm close", JOptionPane.YES_NO_OPTION);
+			if (result == JOptionPane.NO_OPTION) {
+				return;
+			}
+		}
+		saveTableStates(jobsTable);
+		saveTableStates(templatesTable);
+		saveTemplateTableContent();
+		this.appLayer.shutDown();
+		this.dispose();
+		System.exit(0);
+	}
+	
+	private void saveTemplateTableContent() {
+		File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, TEMPLATE_TABLE_CONTENT_FILENAME);
+		RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+		tableModel.saveTemplateListToFile(path);
+	}
+	
+	private Icon getIcon(String iconString) {
+		URL url = ClassLoader.getSystemResource("icons/" + iconString);
+		Icon i = new ImageIcon(url);
+		return i;
+	}
+
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,221 @@
+package com.nexuiz.demorecorder.ui.swinggui.tablemodels;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.ui.swinggui.RecordJobTemplate;
+import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;
+
+/**
+ * Columns:
+ * - Job Name
+ * - Engine path
+ * - Engine parameters
+ * - Demo file
+ * - Relative demo path
+ * - dpvideo path
+ * - video destination
+ * - execute before cap
+ * - execute after cap
+ * - start second
+ * - end second
+ * - status
+ * @author Marius
+ *
+ */
+public class RecordJobTemplatesTableModel extends AbstractTableModel {
+	
+	private static final long serialVersionUID = 6541517890817708306L;
+	
+	public static final int TEMPLATE_NAME = 0;
+	public static final int TEMPLATE_SUMMARY = 1;
+	public static final int JOB_NAME = 2;
+	public static final int ENGINE_PATH = 3;
+	public static final int ENGINE_PARAMETERS = 4;
+	public static final int DEMO_FILE_PATH = 5;
+	public static final int RELATIVE_DEMO_PATH = 6;
+	public static final int DPVIDEO_PATH = 7;
+	public static final int VIDEO_DESTINATION_PATH = 8;
+	public static final int EXECUTE_BEFORE_CAP = 9;
+	public static final int EXECUTE_AFTER_CAP = 10;
+	
+	private static final int columns[] = {
+		TEMPLATE_NAME,
+		TEMPLATE_SUMMARY,
+		JOB_NAME,
+		ENGINE_PATH,
+		ENGINE_PARAMETERS,
+		DEMO_FILE_PATH,
+		RELATIVE_DEMO_PATH,
+		DPVIDEO_PATH,
+		VIDEO_DESTINATION_PATH,
+		EXECUTE_BEFORE_CAP,
+		EXECUTE_AFTER_CAP
+	};
+	
+	private List<RecordJobTemplate> templates;
+	
+	public RecordJobTemplatesTableModel() {
+		templates = new ArrayList<RecordJobTemplate>();
+		
+		//load table content
+		File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, SwingGUI.TEMPLATE_TABLE_CONTENT_FILENAME);
+		this.loadTemplateListFromFile(path);
+	}
+	
+	public void deleteRecordJobTemplate(int modelRowIndex, int viewRowIndex) {
+		try {
+			this.templates.remove(modelRowIndex);
+			fireTableRowsDeleted(viewRowIndex, viewRowIndex);
+		} catch (IndexOutOfBoundsException e) {
+			throw new DemoRecorderException("Couldn't find correspondig template for modelRowIndex " + modelRowIndex
+					+ " and viewRowIndex " + viewRowIndex, e);
+		}
+	}
+	
+	public void addRecordJobTemplate(RecordJobTemplate template) {
+		this.templates.add(template);
+		int position = this.templates.size() - 1;
+		fireTableRowsInserted(position, position);
+	}
+	
+	public RecordJobTemplate getRecordJobTemplate(int modelRowIndex) {
+		return this.templates.get(modelRowIndex);
+	}
+
+	public int getColumnCount() {
+		return columns.length;
+	}
+
+	public int getRowCount() {
+		return this.templates.size();
+	}
+	
+	public void saveTemplateListToFile(File path) {
+		DemoRecorderUtils.attemptFileCreation(path);
+		
+		String exceptionMessage = "Could not save the templates to file " + path.getAbsolutePath();
+		
+		if (!path.exists()) {
+			DemoRecorderException ex = new DemoRecorderException(exceptionMessage);
+			DemoRecorderUtils.showNonCriticalErrorDialog(ex);
+			return;
+		}
+		
+		try {
+			FileOutputStream fout = new FileOutputStream(path);
+			ObjectOutputStream oos = new ObjectOutputStream(fout);
+			oos.writeObject(this.templates);
+			oos.close();
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+		}
+	}
+	
+	@SuppressWarnings("unchecked")
+	public void loadTemplateListFromFile(File path) {
+		if (!path.exists()) {
+			return;
+		}
+		
+		List<RecordJobTemplate> newTemplateList;
+		try {
+			FileInputStream fin = new FileInputStream(path);
+			ObjectInputStream ois = new ObjectInputStream(fin);
+			newTemplateList = (List<RecordJobTemplate>) ois.readObject();
+		} catch (Exception e) {
+			DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the templates from file " + path.getAbsolutePath(), e, true);
+			return;
+		}
+		this.templates = newTemplateList;
+//		fireTableRowsInserted(0, this.templates.size());
+	}
+
+	public Object getValueAt(int rowIndex, int columnIndex) {
+		RecordJobTemplate template = this.templates.get(rowIndex);
+		if (template == null) {
+			return null;
+		}
+		
+		if (columnIndex < 0 || columnIndex >= columns.length) {
+			return null;
+		}
+		
+		String cellData = "UNDEF";
+		switch (columnIndex) {
+		case TEMPLATE_NAME:
+			cellData = template.getName(); break;
+		case TEMPLATE_SUMMARY:
+			cellData = template.getSummary(); break;
+		case JOB_NAME:
+			cellData = template.getJobName(); break;
+		case ENGINE_PATH:
+			cellData = template.getEnginePath().getAbsolutePath(); break;
+		case ENGINE_PARAMETERS:
+			cellData = template.getEngineParameters(); break;
+		case DEMO_FILE_PATH:
+			cellData = DemoRecorderUtils.getJustFileNameOfPath(template.getDemoFile()); break;
+		case RELATIVE_DEMO_PATH:
+			cellData = template.getRelativeDemoPath(); break;
+		case DPVIDEO_PATH:
+			cellData = template.getDpVideoPath().getAbsolutePath(); break;
+		case VIDEO_DESTINATION_PATH:
+			cellData = template.getVideoDestination().getAbsolutePath(); break;
+		case EXECUTE_BEFORE_CAP:
+			cellData = template.getExecuteBeforeCap(); break;
+		case EXECUTE_AFTER_CAP:
+			cellData = template.getExecuteAfterCap(); break;
+		}
+		
+		return cellData;
+	}
+
+	@Override
+	public String getColumnName(int column) {
+		if (column < 0 || column >= columns.length) {
+			return "";
+		}
+		
+		String columnName = "UNDEFINED";
+		switch (column) {
+		case TEMPLATE_NAME:
+			columnName = "Name"; break;
+		case TEMPLATE_SUMMARY:
+			columnName = "Summary"; break;
+		case JOB_NAME:
+			columnName = "Job name"; break;
+		case ENGINE_PATH:
+			columnName = "Engine path"; break;
+		case ENGINE_PARAMETERS:
+			columnName = "Engine parameters"; break;
+		case DEMO_FILE_PATH:
+			columnName = "Demo directory"; break;
+		case RELATIVE_DEMO_PATH:
+			columnName = "Relative demo path"; break;
+		case DPVIDEO_PATH:
+			columnName = "DPVideo path"; break;
+		case VIDEO_DESTINATION_PATH:
+			columnName = "Video destination"; break;
+		case EXECUTE_BEFORE_CAP:
+			columnName = "Exec before"; break;
+		case EXECUTE_AFTER_CAP:
+			columnName = "Exec after"; break;
+		}
+		
+		return columnName;
+	}
+	
+	public List<RecordJobTemplate> getRecordJobTemplates() {
+		return this.templates;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,192 @@
+package com.nexuiz.demorecorder.ui.swinggui.tablemodels;
+
+import java.io.File;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+/**
+ * Columns:
+ * - Job Name
+ * - Engine path
+ * - Engine parameters
+ * - Demo file
+ * - Relative demo path
+ * - dpvideo path
+ * - video destination
+ * - execute before cap
+ * - execute after cap
+ * - start second
+ * - end second
+ * - status
+ * @author Marius
+ *
+ */
+public class RecordJobsTableModel extends AbstractTableModel {
+	
+	private static final long serialVersionUID = 5024144640874313910L;
+	
+	public static final int JOB_NAME = 0;
+	public static final int ENGINE_PATH = 1;
+	public static final int ENGINE_PARAMETERS = 2;
+	public static final int DEMO_FILE_PATH = 3;
+	public static final int RELATIVE_DEMO_PATH = 4;
+	public static final int DPVIDEO_PATH = 5;
+	public static final int VIDEO_DESTINATION_PATH = 6;
+	public static final int EXECUTE_BEFORE_CAP = 7;
+	public static final int EXECUTE_AFTER_CAP = 8;
+	public static final int START_SECOND = 9;
+	public static final int END_SECOND = 10;
+	public static final int STATUS = 11;
+	
+	private static final int columns[] = {
+		JOB_NAME,
+		ENGINE_PATH,
+		ENGINE_PARAMETERS,
+		DEMO_FILE_PATH,
+		RELATIVE_DEMO_PATH,
+		DPVIDEO_PATH,
+		VIDEO_DESTINATION_PATH,
+		EXECUTE_BEFORE_CAP,
+		EXECUTE_AFTER_CAP,
+		START_SECOND,
+		END_SECOND,
+		STATUS
+	};
+	
+	private DemoRecorderApplication appLayer;
+	private List<RecordJob> jobList = null;
+	
+	public RecordJobsTableModel(DemoRecorderApplication appLayer) {
+		this.appLayer = appLayer;
+		this.jobList = this.appLayer.getRecordJobs();
+	}
+	
+	public void deleteRecordJob(int modelRowIndex, int viewRowIndex) {
+		try {
+			RecordJob job = this.jobList.get(modelRowIndex);
+			if (this.appLayer.deleteRecordJob(job)) {
+				this.jobList.remove(job);
+				fireTableRowsDeleted(viewRowIndex, viewRowIndex);
+			}
+		} catch (IndexOutOfBoundsException e) {
+			throw new DemoRecorderException("Couldn't find correspondig job for modelRowIndex " + modelRowIndex
+					+ " and viewRowIndex " + viewRowIndex, e);
+		}
+	}
+	
+	public void loadNewJobQueue(File path) {
+		this.appLayer.loadJobQueue(path);
+		this.jobList = this.appLayer.getRecordJobs();
+		fireTableDataChanged();
+	}
+	
+	public RecordJob getRecordJob(int modelRowIndex) {
+		return this.jobList.get(modelRowIndex);
+	}
+
+	public int getColumnCount() {
+		return columns.length;
+	}
+
+	public int getRowCount() {
+		return this.jobList.size();
+	}
+
+	public Object getValueAt(int rowIndex, int columnIndex) {
+		RecordJob job = this.jobList.get(rowIndex);
+		if (job == null) {
+			return null;
+		}
+		
+		if (columnIndex < 0 || columnIndex >= columns.length) {
+			return null;
+		}
+		
+		String cellData = "UNDEF";
+		switch (columnIndex) {
+		case JOB_NAME:
+			cellData = job.getJobName(); break;
+		case ENGINE_PATH:
+			cellData = job.getEnginePath().getAbsolutePath(); break;
+		case ENGINE_PARAMETERS:
+			cellData = job.getEngineParameters(); break;
+		case DEMO_FILE_PATH:
+			cellData = DemoRecorderUtils.getJustFileNameOfPath(job.getDemoFile()); break;
+		case RELATIVE_DEMO_PATH:
+			cellData = job.getRelativeDemoPath(); break;
+		case DPVIDEO_PATH:
+			cellData = job.getDpVideoPath().getAbsolutePath(); break;
+		case VIDEO_DESTINATION_PATH:
+			cellData = job.getVideoDestination().getAbsolutePath(); break;
+		case EXECUTE_BEFORE_CAP:
+			cellData = job.getExecuteBeforeCap(); break;
+		case EXECUTE_AFTER_CAP:
+			cellData = job.getExecuteAfterCap(); break;
+		case START_SECOND:
+			cellData = String.valueOf(job.getStartSecond()); break;
+		case END_SECOND:
+			cellData = String.valueOf(job.getEndSecond()); break;
+		case STATUS:
+			if (job.getState() == RecordJob.State.DONE) {
+				cellData = "done";
+			} else if (job.getState() == RecordJob.State.ERROR) {
+				cellData = "error";
+			} else if (job.getState() == RecordJob.State.ERROR_PLUGIN) {
+				cellData = "plug-in error";
+			} else if (job.getState() == RecordJob.State.PROCESSING) {
+				cellData = "processing";
+			} else if (job.getState() == RecordJob.State.WAITING) {
+				cellData = "waiting";
+			}
+		}
+		
+		return cellData;
+	}
+
+	@Override
+	public String getColumnName(int column) {
+		if (column < 0 || column >= columns.length) {
+			return "";
+		}
+		
+		String columnName = "UNDEFINED";
+		switch (column) {
+		case JOB_NAME:
+			columnName = "Name"; break;
+		case ENGINE_PATH:
+			columnName = "Engine path"; break;
+		case ENGINE_PARAMETERS:
+			columnName = "Engine parameters"; break;
+		case DEMO_FILE_PATH:
+			columnName = "Demo name"; break;
+		case RELATIVE_DEMO_PATH:
+			columnName = "Relative demo path"; break;
+		case DPVIDEO_PATH:
+			columnName = "DPVideo path"; break;
+		case VIDEO_DESTINATION_PATH:
+			columnName = "Video destination"; break;
+		case EXECUTE_BEFORE_CAP:
+			columnName = "Exec before"; break;
+		case EXECUTE_AFTER_CAP:
+			columnName = "Exec after"; break;
+		case START_SECOND:
+			columnName = "Start"; break;
+		case END_SECOND:
+			columnName = "End"; break;
+		case STATUS:
+			columnName = "Status"; break;
+		}
+		
+		return columnName;
+	}
+	
+	public List<RecordJob> getRecordJobs() {
+		return this.jobList;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,21 @@
+package com.nexuiz.demorecorder.ui.swinggui.utils;
+
+import java.awt.Component;
+import java.lang.Thread.UncaughtExceptionHandler;
+
+import org.jdesktop.swingx.JXErrorPane;
+import org.jdesktop.swingx.error.ErrorInfo;
+
+public class ShowErrorDialogExceptionHandler implements UncaughtExceptionHandler {
+
+	private static Component parentWindow = null;
+	
+	public void uncaughtException(Thread t, Throwable e) {
+		ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);
+		JXErrorPane.showDialog(parentWindow, info);
+	}
+
+	public static void setParentWindow(Component c) {
+		parentWindow = c;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,26 @@
+package com.nexuiz.demorecorder.ui.swinggui.utils;
+
+import java.io.File;
+
+public class SwingGUIUtils {
+	public static boolean isBooleanValue(String value) {
+		if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+			return true;
+		}
+		return false;
+	}
+
+	public static boolean isFileChooser(String value) {
+		if (value.equalsIgnoreCase("filechooser")) {
+			return true;
+		}
+		try {
+			File file = new File(value);
+			if (file.exists()) {
+				return true;
+			}
+		} catch (Throwable e) {
+		}
+		return false;
+	}
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,432 @@
+/*
+ * Created on 08.02.2007
+ *
+ */
+package com.nexuiz.demorecorder.ui.swinggui.utils;
+
+import java.awt.Component;
+import java.beans.DefaultPersistenceDelegate;
+import java.beans.XMLEncoder;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.swing.SortOrder;
+import javax.swing.RowSorter.SortKey;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+
+import org.jdesktop.swingx.JXTable;
+import org.jdesktop.swingx.JXTaskPane;
+import org.jdesktop.swingx.sort.SortUtils;
+import org.jdesktop.swingx.table.TableColumnExt;
+
+/**
+ * Container class for SwingX specific SessionStorage Properties. Is Factory for
+ * custom PersistanceDelegates
+ */
+public class XProperties {
+
+	/**
+	 * 
+	 * Registers all custom PersistenceDelegates needed by contained Property
+	 * classes.
+	 * <p>
+	 * 
+	 * PersistenceDelegates are effectively static properties shared by all
+	 * encoders. In other words: Register once on an arbitrary encoder makes
+	 * them available for all. Example usage:
+	 * 
+	 * <pre>
+	 * <code>
+	 * new XProperties.registerPersistenceDelegates();
+	 * </code>
+	 * </pre>
+	 * 
+	 * PENDING JW: cleanup for 1.6 sorting/filtering incomplete. Missing storage
+	 * - multiple sort keys
+	 * 
+	 * PENDING JW: except for comparators: didn't before and is not state that's
+	 * configurable by users ... so probably won't, not sure, need to revisit -
+	 * comparator (?) - filters (?) - renderers/stringvalues (?) - enhanced
+	 * sort-related table state (?)
+	 */
+	public void registerPersistenceDelegates() {
+		XMLEncoder encoder = new XMLEncoder(System.out);
+		encoder.setPersistenceDelegate(SortKeyState.class, new DefaultPersistenceDelegate(
+				new String[] { "ascending", "modelIndex" }));
+		encoder.setPersistenceDelegate(ColumnState.class, new DefaultPersistenceDelegate(
+				new String[] { "width", "preferredWidth", "modelIndex", "visible", "viewIndex" }));
+		encoder.setPersistenceDelegate(XTableState.class, new DefaultPersistenceDelegate(
+				new String[] { "columnStates", "sortKeyState", "horizontalScrollEnabled" }));
+	}
+
+	/**
+	 * Session storage support for JXTaskPane.
+	 */
+	public static class XTaskPaneProperty implements Serializable {
+
+		private static final long serialVersionUID = -4069436038178318216L;
+
+		public Object getSessionState(Component c) {
+			checkComponent(c);
+			return new XTaskPaneState(((JXTaskPane) c).isCollapsed());
+		}
+
+		public void setSessionState(Component c, Object state) {
+			checkComponent(c);
+			if ((state != null) && !(state instanceof XTaskPaneState)) {
+				throw new IllegalArgumentException("invalid state");
+			}
+			((JXTaskPane) c).setCollapsed(((XTaskPaneState) state).isCollapsed());
+		}
+
+		private void checkComponent(Component component) {
+			if (component == null) {
+				throw new IllegalArgumentException("null component");
+			}
+			if (!(component instanceof JXTaskPane)) {
+				throw new IllegalArgumentException("invalid component");
+			}
+		}
+
+	}
+
+	public static class XTaskPaneState implements Serializable {
+		private static final long serialVersionUID = 3363688961112031969L;
+		private boolean collapsed;
+
+		public XTaskPaneState() {
+			this(false);
+		}
+
+		/**
+		 * @param b
+		 */
+		public XTaskPaneState(boolean collapsed) {
+			this.setCollapsed(collapsed);
+		}
+
+		/**
+		 * @param collapsed
+		 *            the collapsed to set
+		 */
+		public void setCollapsed(boolean collapsed) {
+			this.collapsed = collapsed;
+		}
+
+		/**
+		 * @return the collapsed
+		 */
+		public boolean isCollapsed() {
+			return collapsed;
+		}
+
+	}
+
+	/**
+	 * Session storage support for JXTable.
+	 */
+	public static class XTableProperty implements Serializable {
+
+		private static final long serialVersionUID = -5064142292091374301L;
+
+		public Object getSessionState(Component c) {
+			checkComponent(c);
+			JXTable table = (JXTable) c;
+			List<ColumnState> columnStates = new ArrayList<ColumnState>();
+			List<TableColumn> columns = table.getColumns(true);
+			List<TableColumn> visibleColumns = table.getColumns();
+			for (TableColumn column : columns) {
+				columnStates.add(new ColumnState((TableColumnExt) column, visibleColumns
+						.indexOf(column)));
+			}
+			XTableState tableState = new XTableState(columnStates
+					.toArray(new ColumnState[columnStates.size()]));
+			tableState.setHorizontalScrollEnabled(table.isHorizontalScrollEnabled());
+			List<? extends SortKey> sortKeys = null;
+			if (table.getRowSorter() != null) {
+				sortKeys = table.getRowSorter().getSortKeys();
+			}
+			// PENDING: store all!
+			if ((sortKeys != null) && (sortKeys.size() > 0)) {
+				tableState.setSortKey(sortKeys.get(0));
+			}
+			return tableState;
+		}
+
+		public void setSessionState(Component c, Object state) {
+			checkComponent(c);
+			JXTable table = (JXTable) c;
+			XTableState tableState = ((XTableState) state);
+			ColumnState[] columnState = tableState.getColumnStates();
+			List<TableColumn> columns = table.getColumns(true);
+			if (canRestore(columnState, columns)) {
+				for (int i = 0; i < columnState.length; i++) {
+					columnState[i].configureColumn((TableColumnExt) columns.get(i));
+				}
+				restoreVisibleSequence(columnState, table.getColumnModel());
+			}
+			table.setHorizontalScrollEnabled(tableState.getHorizontalScrollEnabled());
+			if (tableState.getSortKey() != null) {
+				table.getRowSorter()
+						.setSortKeys(Collections.singletonList(tableState.getSortKey()));
+			}
+		}
+
+		private void restoreVisibleSequence(ColumnState[] columnStates, TableColumnModel model) {
+			List<ColumnState> visibleStates = getSortedVisibleColumnStates(columnStates);
+			for (int i = 0; i < visibleStates.size(); i++) {
+				TableColumn column = model.getColumn(i);
+				int modelIndex = visibleStates.get(i).getModelIndex();
+				if (modelIndex != column.getModelIndex()) {
+					int currentIndex = -1;
+					for (int j = i + 1; j < model.getColumnCount(); j++) {
+						TableColumn current = model.getColumn(j);
+						if (current.getModelIndex() == modelIndex) {
+							currentIndex = j;
+							break;
+						}
+					}
+					model.moveColumn(currentIndex, i);
+				}
+			}
+
+		}
+
+		private List<ColumnState> getSortedVisibleColumnStates(ColumnState[] columnStates) {
+			List<ColumnState> visibleStates = new ArrayList<ColumnState>();
+			for (ColumnState columnState : columnStates) {
+				if (columnState.getVisible()) {
+					visibleStates.add(columnState);
+				}
+			}
+			Collections.sort(visibleStates, new VisibleColumnIndexComparator());
+			return visibleStates;
+		}
+
+		/**
+		 * Returns a boolean to indicate if it's reasonably safe to restore the
+		 * properties of columns in the list from the columnStates. Here:
+		 * returns true if the length of both are the same and the modelIndex of
+		 * the items at the same position are the same, otherwise returns false.
+		 * 
+		 * @param columnState
+		 * @param columns
+		 * @return
+		 */
+		private boolean canRestore(ColumnState[] columnState, List<TableColumn> columns) {
+			if ((columnState == null) || (columnState.length != columns.size()))
+				return false;
+			for (int i = 0; i < columnState.length; i++) {
+				if (columnState[i].getModelIndex() != columns.get(i).getModelIndex()) {
+					return false;
+				}
+			}
+			return true;
+		}
+
+		private void checkComponent(Component component) {
+			if (component == null) {
+				throw new IllegalArgumentException("null component");
+			}
+			if (!(component instanceof JXTable)) {
+				throw new IllegalArgumentException("invalid component - expected JXTable");
+			}
+		}
+
+	}
+
+	public static class XTableState implements Serializable {
+		private static final long serialVersionUID = -3566913244872587438L;
+		ColumnState[] columnStates = new ColumnState[0];
+		boolean horizontalScrollEnabled;
+		SortKeyState sortKeyState;
+
+		public XTableState(ColumnState[] columnStates, SortKeyState sortKeyState,
+				boolean horizontalScrollEnabled) {
+			this.columnStates = copyColumnStates(columnStates);
+			this.sortKeyState = sortKeyState;
+			setHorizontalScrollEnabled(horizontalScrollEnabled);
+
+		}
+
+		public void setSortKey(SortKey sortKey) {
+			this.sortKeyState = new SortKeyState(sortKey);
+
+		}
+
+		private SortKey getSortKey() {
+			if (sortKeyState != null) {
+				return sortKeyState.getSortKey();
+			}
+			return null;
+		}
+
+		public XTableState(ColumnState[] columnStates) {
+			this.columnStates = copyColumnStates(columnStates);
+		}
+
+		public ColumnState[] getColumnStates() {
+			return copyColumnStates(this.columnStates);
+		}
+
+		public boolean getHorizontalScrollEnabled() {
+			return horizontalScrollEnabled;
+		}
+
+		public void setHorizontalScrollEnabled(boolean horizontalScrollEnabled) {
+			this.horizontalScrollEnabled = horizontalScrollEnabled;
+		}
+
+		private ColumnState[] copyColumnStates(ColumnState[] states) {
+			if (states == null) {
+				throw new IllegalArgumentException("invalid columnWidths");
+			}
+			ColumnState[] copy = new ColumnState[states.length];
+			System.arraycopy(states, 0, copy, 0, states.length);
+			return copy;
+		}
+
+		public SortKeyState getSortKeyState() {
+			return sortKeyState;
+		}
+	}
+
+	/**
+	 * Quick hack to make SortKey encodable. How to write a PersistenceDelegate
+	 * for a SortKey? Boils down to how to write a delegate for the
+	 * uninstantiable class (SwingX) SortOrder which does enum-mimickry (defines
+	 * privately intantiated constants)
+	 * 
+	 */
+	public static class SortKeyState implements Serializable {
+		private static final long serialVersionUID = 5819342622261460894L;
+
+		int modelIndex;
+
+		boolean ascending;
+
+		/**
+		 * Constructor used by the custom PersistenceDelegate.
+		 * 
+		 * @param ascending
+		 * @param modelIndex
+		 * @param comparator
+		 */
+		public SortKeyState(boolean ascending, int modelIndex) {
+			this.ascending = ascending;
+			this.modelIndex = modelIndex;
+		}
+
+		/**
+		 * Constructor used by property.
+		 * 
+		 * @param sortKey
+		 */
+		public SortKeyState(SortKey sortKey) {
+			this(SortUtils.isAscending(sortKey.getSortOrder()), sortKey.getColumn());
+		}
+
+		protected SortKey getSortKey() {
+			SortOrder sortOrder = getAscending() ? SortOrder.ASCENDING : SortOrder.DESCENDING;
+			return new SortKey(getModelIndex(), sortOrder);
+		}
+
+		public boolean getAscending() {
+			return ascending;
+		}
+
+		public int getModelIndex() {
+			return modelIndex;
+		}
+	}
+
+	public static class ColumnState implements Serializable {
+		private static final long serialVersionUID = 6037947151025126049L;
+		private int width;
+		private int preferredWidth;
+		private int modelIndex;
+		private boolean visible;
+		private int viewIndex;
+
+		/**
+		 * Constructor used by the custom PersistenceDelegate.
+		 * 
+		 * @param width
+		 * @param preferredWidth
+		 * @param modelColumn
+		 * @param visible
+		 * @param viewIndex
+		 */
+		public ColumnState(int width, int preferredWidth, int modelColumn, boolean visible,
+				int viewIndex) {
+			this.width = width;
+			this.preferredWidth = preferredWidth;
+			this.modelIndex = modelColumn;
+			this.visible = visible;
+			this.viewIndex = viewIndex;
+		}
+
+		/**
+		 * Constructor used by the Property.
+		 * 
+		 * @param columnExt
+		 * @param viewIndex
+		 */
+		public ColumnState(TableColumnExt columnExt, int viewIndex) {
+			this(columnExt.getWidth(), columnExt.getPreferredWidth(), columnExt.getModelIndex(),
+					columnExt.isVisible(), viewIndex);
+		}
+
+		/**
+		 * Restores column properties if the model index is the same as the
+		 * column's model index. Does nothing otherwise.
+		 * <p>
+		 * 
+		 * Here the properties are: width, preferredWidth, visible.
+		 * 
+		 * @param columnExt
+		 *            the column to configure
+		 */
+		public void configureColumn(TableColumnExt columnExt) {
+			if (modelIndex != columnExt.getModelIndex())
+				return;
+			columnExt.setPreferredWidth(preferredWidth);
+			columnExt.setWidth(width);
+			columnExt.setVisible(visible);
+		}
+
+		public int getModelIndex() {
+			return modelIndex;
+		}
+
+		public int getViewIndex() {
+			return viewIndex;
+		}
+
+		public boolean getVisible() {
+			return visible;
+		}
+
+		public int getWidth() {
+			return width;
+		}
+
+		public int getPreferredWidth() {
+			return preferredWidth;
+		}
+
+	}
+
+	public static class VisibleColumnIndexComparator implements Comparator<Object> {
+
+		public int compare(Object o1, Object o2) {
+			return ((ColumnState) o1).getViewIndex() - ((ColumnState) o2).getViewIndex();
+		}
+
+	}
+
+}

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/about.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/about.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/about.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,21 @@
+<html>
+    <h1>Nexuiz Demo Recorder v0.2</h1>
+    Written by GreEn`mArine<br>
+    <h2>Credits</h2>
+    <ul>
+    	<li>
+    		Thanks for the used icons goes to<br>
+    		<a href="http://www.everaldo.com/crystal/?action=downloads">http://www.everaldo.com/crystal/?action=downloads</a>
+		</li>
+		<li>
+			Thanks to divVerent for the demotc.pl script without which<br>
+			this project would have been impossible.
+		</li>
+    </ul>
+    
+    <h2>Usage</h2>
+    Open the help in order to find out how to use this software.
+    
+    <h2>License</h2>
+    GPL v2
+</html>
\ No newline at end of file

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelp.hs
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelp.hs	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelp.hs	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
+<!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net--><!DOCTYPE helpset PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN" "http://java.sun.com/products/javahelp/helpset_2_0.dtd">
+
+<helpset version="1.0">
+ <title>Demo Recorder Help </title>
+ <maps>
+  <homeID>
+top
+  </homeID>
+  <mapref location="Map.jhm"/>
+
+ </maps>
+ <view>
+  <name>
+TOC
+  </name>
+  <label>
+TOC 
+  </label>
+  <type>
+javax.help.TOCView
+  </type>
+  <data>
+DemoRecorderHelpTOC.xml
+  </data>
+ </view>
+ 
+ <view>
+  <name>
+Search
+  </name>
+  <label>
+Search 
+  </label>
+  <type>
+javax.help.SearchView
+  </type>
+  <data engine="com.sun.java.help.search.DefaultSearchEngine">
+JavaHelpSearch
+  </data>
+ </view>
+</helpset>

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpIndex.xml
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpIndex.xml	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpIndex.xml	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE index PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Index Version 1.0//EN" "http://java.sun.com/products/javahelp/index_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->
+
+<index version="1.0"/>

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpTOC.xml
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpTOC.xml	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/DemoRecorderHelpTOC.xml	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE toc PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp TOC Version 1.0//EN" "http://java.sun.com/products/javahelp/toc_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->
+
+<toc version="1.0">
+ <tocitem text="Introduction " target="html.introduction"/>
+
+ <tocitem text="Basic tutorial " target="html.basic_tutorial"/>
+
+ <tocitem text="Templates " target="html.templates"/>
+
+ <tocitem text="Open/Save Jobs and Templates " target="html.open-save"/>
+
+ <tocitem text="Preferences dialog " target="html.preferences"/>
+
+ <tocitem text="Compatibility and limitations " target="html.compat-limitations"/>
+
+ <tocitem text="FAQ " target="html.faq"/>
+
+ <tocitem text="Advanced user topics " target="html.advanced-topics">
+  <tocitem text="How it works " target="html.advanced-how-it-works"/>
+
+  <tocitem text="Table settings " target="html.advanced-table-settings"/>
+
+  <tocitem text="Preliminary stop " target="html.advanced-prelim-stop"/>
+
+ </tocitem>
+ <tocitem text="Plug-in architecture " target="html.plugin-architecture">
+  <tocitem text="VirtualDub plug-in " target="html.plugin-virtualdub"/>
+
+ </tocitem>
+ <tocitem text="Credits " target="html.credits"/>
+
+ <tocitem text="License " target="html.license"/>
+
+ <tocitem text="Changelog " target="html.changelog"/>
+
+</toc>
\ No newline at end of file

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JHelpDev Project.xml
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JHelpDev Project.xml	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JHelpDev Project.xml	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE jhelpdev PUBLIC "-//jhelpdev.sourcefore.net//JHelpDev Configuration Settings 1.2" "http://jhelpdev.sourceforge.net/dtd/jhelpdev_1_2.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.org-->
+
+<jhelpdev version="1.2">
+ <config>
+  <project>
+DemoRecorderHelp
+  </project>
+  <projectdir>
+D:\Daten\Eclipse Projects\NexuizDemoRecorder\src\main\resources\help
+  </projectdir>
+  <startpage>
+html/introduction.html
+  </startpage>
+  <popupicon>
+
+  </popupicon>
+ </config>
+ <view>
+  <helptitle>
+Demo Recorder Help
+  </helptitle>
+  <toc showing="yes" label="Table of Contents"/>
+
+  <index showing="no" label="Index"/>
+
+  <search showing="yes" label="Search"/>
+
+ </view>
+ <encoding value="ISO-8859-1"/>
+
+</jhelpdev>
\ No newline at end of file

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS.TAB
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS.TAB	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/DOCS.TAB	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,14 @@
+eÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝÿÿÿÿÿ÷_÷_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝÿÿÿÿÿÿÿÝÿÿÿÿÿÿÿuÿÿuÿÿÿÿÿÿÿÿ÷_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝÿÿÿÿý×ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷_ÿÿÿuÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý×ÿÿÿÿÿÿÿÿ÷_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@7û*ÌebøÖBìå(⍦Ob³J6,Ù©
+³vRꯪŒ/ŽX³r(Í‹1‹3,Ï‹ª8³J4³Š6/4³<£J8¨Ò¨â¯3þ*0³K¬É‹£J³Š4º¾Œ+Œÿ‹4³h£"(Ì£J4®º+9ªÌê/Œ.»Ì/Œ‹Œ*7¢Œ.9"*6Ë0¿üÈ£(£Jî£
+Ì*4£;*1¤,⮣K0£
+0³"£ºþ¨ÞʸÒó6ªÊ»³2(ªªªªªºªª®0¸Â£
+£"º®3âŒ*͸ª£
+4®4£Kªº0ª¨Â¨Ê(ȳ
+ºªª®þÌ.¯®0£
+0º2¢ºªìª³
+Ì*0¾0³:.ŒŠ6(Ê(È£"Œ+þ꺌*2.몣
+ªªº0ªªº2.£
+0ªª³
+0ª£
+ªª£
+«Œ¢Ì*ªŒ*0ª«ªê«Œ*0ªÌ,ÂÿÌ,ÂÌ,ÂÿÿÌ/0¿ÿÿÿÿÿÿÌ/ó0³"Ì/ÿüÂÿÿÌ,È¿ÿüÂÿÌ/ÿ0¿Ì/þ«ªªªªªêªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª¦
\ No newline at end of file

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/OFFSETS
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/OFFSETS
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/POSITIONS
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/POSITIONS
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/SCHEMA
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/SCHEMA	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/SCHEMA	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,2 @@
+JavaSearch 1.0
+TMAP bs=2048 rt=1 fl=-1 id1=1038 id2=1

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/TMAP
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/JavaHelpSearch/TMAP
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/Map.jhm
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/Map.jhm	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/Map.jhm	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE map PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 1.0//EN" "http://java.sun.com/products/javahelp/map_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->
+
+<map version="1.0">
+<mapID target="top" url="html/introduction.html "/>
+
+<mapID target="html.preferences" url="html/preferences.html"/>
+
+<mapID target="html.advanced-prelim-stop" url="html/advanced-prelim-stop.html"/>
+
+<mapID target="html.license" url="html/license.html"/>
+
+<mapID target="html.compat-limitations" url="html/compat-limitations.html"/>
+
+<mapID target="html.open-save" url="html/open-save.html"/>
+
+<mapID target="html.introduction" url="html/introduction.html"/>
+
+<mapID target="html.advanced-topics" url="html/advanced-topics.html"/>
+
+<mapID target="html.credits" url="html/credits.html"/>
+
+<mapID target="html.advanced-how-it-works" url="html/advanced-how-it-works.html"/>
+
+<mapID target="html.plugin-architecture" url="html/plugin-architecture.html"/>
+
+<mapID target="html.changelog" url="html/changelog.html"/>
+
+<mapID target="html.faq" url="html/faq.html"/>
+
+<mapID target="html.advanced-table-settings" url="html/advanced-table-settings.html"/>
+
+<mapID target="html.basic_tutorial_jobs_create" url="html/basic_tutorial.html#jobs_create"/>
+
+<mapID target="html.templates" url="html/templates.html"/>
+
+<mapID target="html.plugin-virtualdub" url="html/plugin-virtualdub.html"/>
+
+<mapID target="html.basic_tutorial" url="html/basic_tutorial.html"/>
+
+</map>
\ No newline at end of file

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-how-it-works.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-how-it-works.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-how-it-works.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,77 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>How it works</H1>
+<P>The following is a description of how
+the program processes a single job:</P>
+<UL>
+	<LI><P>The program takes your original
+	demo, e.g. &ldquo;C:\Nexuiz\data\demos\test.dem&rdquo; and creates a
+	copy of it, the <I>cut demo</I>. This file has the name
+	&lt;original_demo_name&gt;<B>_autocut</B>.dem. The cut demo is
+	different to the original demo in a way that console commands have
+	been <I>injected</I>, so basically the engine thinks that you had
+	entered them yourself (e.g. cl_capturevideo 1 to start recording).</P>
+	<UL>
+		<LI><P>Since it is possible to inject
+		any possible console command, the first command that is injected is
+		to disable rendering (r_render 0), save the value of your &ldquo;volume&rdquo;
+		setting and then setting it to 0 (as long as you have sound and
+		rendering disabled while fast-forwarding in the preferences of the
+		Demo Recorder). Then a slowmo 100 command (first stage value from
+		the Demo Recorder preferences) is injected in order to fast-forward
+		the demo.</P>
+		<LI><P>Then, when the game time in the
+		demo is about 1 minute less than your specified start time of the
+		job, slowmo is reduced to 10 (second stage value from the
+		preferences)</P>
+		<LI><P>Then, when the game time in the
+		demo is about 5 seconds less than your specified start time, <B>slowmo</B>
+		is set to <B>1</B>, rendering and sound is enabled again, and
+		whatever your put into the <B>exec before capture</B> field is
+		being injected, too. Then, the values of cl_capturevideo_nameformat
+		and _number are being saved to a temporary variable and are
+		overwritten with defined values (<B>autorec</B> and <B>1234567</B>),
+		so that the Nexuiz Demo Recorder will know the exact name of the
+		output file (which is necessary so that it can move that file to
+		your desired video destination/location)</P>
+		<LI><P>When the the start time is
+		reached, cl_capturevideo 1 is injected, once the end time is
+		reached, cl_capturevideo 0 is injected.</P>
+		<LI><P>Shortly after, whatever you put
+		into the exec after capture field is executed, and then the
+		original values of cl_capturevideo_nameformat and _number are being
+		restored.</P>
+		<LI><P>Then a disconnect command is
+		injected</P>
+	</UL>
+	<LI><P>Next, your specified Nexuiz
+	engine binary is launched. The parameters given to the binary are:</P>
+	<UL>
+		<LI><P>The content of the <B>engine
+		parameters</B> field of the job, and</P>
+		<LI><P><B>-demo
+		&lt;relative-demo-path&gt;/&lt;demo-file-name&gt;</B> (this will
+		start Nexuiz, e.g. <B>-demo demos/test.dem</B>, launch the <B>test.dem</B>
+		demo, and the engine will play the complete demo until a disconnect
+		is issued (which we have injected above), and then Nexuiz will
+		close automatically. This -demo parameter exists since Nexuiz 1.0</P>
+	</UL>
+	<LI><P>Once the Nexuiz Demo Recorder
+	notices that your Nexuiz engine binary closed, it will look for the
+	recorded video file in &lt;DPVideo-directory&gt;/autorec1234567.&lt;avi/ogv&gt;
+	and move it (rename it) to your desired &lt;video-destination&gt;,
+	keeping the original extension of the file. In case the preferences
+	are setup to not overwrite an existing video file, a file with
+	ending _copy1 (2, 3, &hellip;) will be created.</P>
+</UL>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-prelim-stop.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-prelim-stop.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-prelim-stop.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Preliminary stop</H1>
+<P>If you want to preliminarily stop the
+recording process, the only thing you can do is to switch to the
+Nexuiz Demo Recorder window and click on <B>stop processing</B>.
+However, if Nexuiz is still open and processing a demo, you have to
+end it manually of wait for it to end. This means that clicking on
+<B>stop processing</B> button will simply remove all jobs that
+haven't been started yet from the internal queue of jobs that would
+normally be processed (of course they <B>won't</B> be removed from
+your jobs table).</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-table-settings.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-table-settings.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-table-settings.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Table settings</H1>
+<P>Both the jobs and templates table can
+be customized. You can define whether to sort the jobs/templates
+according to a particular column, you can change the order of
+columns, and you can add and remove (hide) additional columns by
+clicking the small icon as shown here on this image:</P>
+<P><img src="images/customize_tables.gif" /></P>
+<P>These settings are being saved and
+automatically restored whenever you close and open the program. In
+case you messed the columns and want them to be reset to default, go
+into the settings sub-folder of the Nexuiz Demo Recorder and delete
+the jobsTable.pref or templatesTable.pref file (.pref for
+&ldquo;preferences&rdquo;)</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-topics.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-topics.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/advanced-topics.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,18 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Advanced user topics</H1>
+<P>Here you find additional information
+that is not mandatory for you to know, but might help you
+understanding what and how the tool does stuff, or what you can do
+with it.</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/basic_tutorial.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/basic_tutorial.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/basic_tutorial.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,169 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+   <H1>Basic tutorial</H1>
+<P>The following images shows the main window:</P>
+<img src="images/main_window.gif" />
+<P>You will notice that the main window is separated into
+4 areas:</P>
+<UL>
+	<LI><P>The menu bar (File, Help)</P>
+	<LI><P>The &quot;Templates&quot; area which has a table
+	and 4 buttons on the right</P>
+	<LI><P>The &quot;Jobs&quot; area which also has a table
+	and 4 buttons on the right</P>
+	<LI><P>The Start/stop processing buttons</P>
+</UL>
+<H2>Step 1</H2>
+<P>Minimize this demo recorder application for a moment
+and figure out which demo(s) you actually want to record. Once you
+know the names of the demo files, you have to figure out the start
+time and end time (when you want the recording process to start and
+end). You have to be aware that the Nexuiz demo recorder needs a
+value in seconds. The problem is that there are 2 different kinds of
+values.
+</P>
+<UL>
+	<LI><P>The first one is the one you see in your HUD in
+	the top right corner. This value is not useful for the Nexuiz demo
+	recorder, because the time is shown in the &lt;minutes:seconds&gt;
+	format. Also this value will be changed/reset to 0 in case the
+	ready-restart feature was using in the game, which resets the time.</P>
+	<LI><P>On the other hand there is the <B>absolute time
+	value</B>. It represents the amount of time that passed on the
+	server since the map was loaded. This value will always increase
+	each second, and won't be reset to 0 after a ready-restart call
+	either. This is the value you need for this tool. You can obtain it
+	the following ways</P>
+	<UL>
+		<LI><P>1: There is a sub-folder &quot;tools&quot; in
+		this package which contains a .patch file that allows you to patch
+		the source-code of the Nexuiz 2.5.2 engine. What this patch does is
+		to add a new variable &quot;showgametime&quot;. Once you patched
+		your source-code and compiled the engine, you can set this variable
+		to 1, and then in the lower right corner you will see the current
+		game time. Please note that I cannot help you with patching the
+		sources and compiling the engine. For Microsoft Windows users I put
+		the Nexuiz binary (darkplaces.exe) into the package as well, so you
+		can use this one (copy it into your Nexuiz directory)</P>
+		<LI><P>2: If the demo was recorded with Nexuiz 2.5 or
+		newer you can obtain the time values by opening the console and
+		entering this: prvm_global client time</P>
+		<LI><P>3: If the demo was recorded with a Nexuiz version
+		older than 2.5, just pause the demo at the desired moment, open the
+		scoreboard (by default: hold tabulator key). The scoreboard will
+		show you the current time in &lt;minutes&gt;:&lt;seconds&gt;.
+		Calculate the time using a calculator (time = minutes * 60 +
+		seconds)</P>
+	</UL>
+</UL>
+<P>Make sure that you know the demo file name, start and
+end time, then open the Nexuiz demo recorder tool again</P>
+<H2>Step 2</H2>
+<P><a name="jobs_create">In the &quot;Jobs&quot; panel</a>, click on the +Create
+button. A new dialog will open which asks you for all different kinds
+of things:</P>
+<img src="images/create_job.gif" />
+<UL>
+	<LI><P><b>Engine:</b> click on the ... button and specify the
+	Nexuiz engine that should be used for recording</P>
+	<LI><P><b>Engine parameters:</b> Only fill this out if you already know what it
+	means. Examples include to execute a particular config (+exec
+	someConfig.cfg), or set a special directory (-userdir option) so
+	that your Engine will use a different set of configs</P>
+	<LI><P><b>DPVideo directory:</b> here you have to specify the
+	path where your Nexuiz usually creates new .avi or .ogv files. This
+	is one of the items where you will see that it is required that you
+	already have recorded demos previously. Usually the path will be
+	Nexuiz/data/video (on Windows) or ~/.nexuiz/data/video</P>
+	<LI><P><b>Relative demo path:</b> This is the path that is being
+	used within the virtual file-system of Nexuiz in order to find your
+	demo. Normally, when starting a demo by hand, you would enter
+	something like &quot;playdemo demos/stormkeep_demo.dem&quot;,
+	because usually demos are stored in the directory &quot;demos&quot;.
+	In case you changed that (e.g. If the demo is in a sub-directory of
+	the demos directory), make sure to put this into the field. But
+	usually you <B>won't</B> have to change the value (demos) to
+	anything else. Just leave it as it is.</P>
+	<LI><P><b>Job name:</b> Here you can specify a name for this job. If
+	you don't specify any name, a name will be automatically chosen, using
+	the format "Job x", where <b>x</b> is a index number.</P>
+	<LI><P><b>Demo file:</b> obviously you have to specify the demo
+	file here</P>
+	<LI><P><b>Start second</b> and <b>end second</b> are self-explanatory</P>
+	<LI><P><b>Exec before capture:</b> Here you have the chance to
+	enter console commands (that is, commands that you could otherwise
+	also enter into the console of the game). One example could be to
+	execute a config here that will make sure that the HUD is hidden. Or
+	you might want to change the FPS for the rendered video by setting
+	cl_capturevideo_fps to another value. If you want to put several
+	commands in here you can either separate them by a new-line, or by ;
+	(semi-colon)</P>
+	<LI><P><b>Exec after capture:</b> works like the &quot;exec
+	before capture&quot; field. Can be used for anything, e.g. Restoring
+	settings that you altered in the exec before field.</P>
+	<LI><P><b>Video destination:</b> Here you have to specify where
+	you want the recorded video file to be moved once the recording
+	process is done. Please note that you have to enter a name for the
+	file, however you should not enter the extension (.avi or .ogv). You
+	don't have to, because the Nexuiz demo recorder will automatically
+	figure out whether the Nexuiz engine generated an avi or ogv file,
+	and will keep its ending when moving the file to its destination.</P>
+</UL>
+<P>Once you filled out all fields, click on create. In
+case you did something wrong with one of the fields, you will
+hopefully get an error message immediately, which allows you to
+correct the mistake. If nothing was filled out incorrectly the dialog
+should close and you should now see a &quot;Job 1&quot; in the job
+queue.</P>
+<P>In order to edit a job, either double-click on it, or
+right-click it and click on Edit</P>
+<H2>Step 3</H2>
+<P>Now that you have setup your first job, click on the
+<B>Start processing</B> button. After a short moment you should see
+Nexuiz opening. Don't be surprised if the &quot;Loading&quot; image
+appears for a long time. What you don't see is that Nexuiz is
+fast-forwarding your demo to the start time you specified, which can
+take some time. After that, once Nexuiz started and finished
+recording the stuff you wanted it to record, it should close
+automatically. The demo recorder should show you a new message dialog
+that it finished recording all jobs. The &quot;Status&quot; column of
+your first job should now be showing &quot;done&quot;.</P>
+<H2>Trouble-shooting:</H2>
+<P>In case something went wrong, the status column of your job will
+show &quot;error&quot; (in the jobs table). In order to find out what the problem was,
+right-click on the job and click on &quot;Show error message&quot;.
+The message dialog that pops up will hopefully help you to figure out
+what went wrong. If it is something that you think you can fix, do so
+(by editing the job settings), then right-click the job again and click on
+&quot;Reset job status to waiting&quot;. This will set the status of the job to
+&quot;waiting&quot; again.</P>
+<H2>Job queue processing</H2>
+<P>You need to be aware that once you click on the <b>Start
+processing</b> button only the jobs with status &quot;waiting&quot; will be
+put into a queue and will be executed one after another. All other jobs
+(with status &quot;error&quot; or &quot;done&quot; will not be started.
+If an error occurs while processing one of the jobs this job will set its state to
+&quot;error&quot; and the next job in queue will be executed. That is, the behavior
+of the Nexuiz demo recorder is to continue working even if one or more individual
+jobs failed.</P>
+<P>In case you already have a job list with several
+jobs but you want to just record one particular job (even though all
+other jobs also have the state &quot;waiting&quot;), just right-click
+on the particular job and click on &quot;Start job&quot;. Note that this only
+works if the Nexuiz demo recorder is currently not working on any other jobs.</P>
+<H2>Further reading</H2>
+<P>Congratulations, you managed your
+first steps using this program. You should read the other help
+chapters as well in order to be able to fully utilize the
+functionality of the program.</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/changelog.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/changelog.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/changelog.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+<H1>Changelog</H1>
+<H2>v0.1 -&gt; v0.2</H2>
+<H3>Compatibility changes</H3>
+<UL>
+	<LI><P>Table preferences, job queues and template lists are not compatible
+	between v0.1 and 0.2! Make sure you install v0.2 in a new direcory!</P>
+	<LI><P>NDR v0.2 requires Java SE 6 now (instead of Java SE 5 which was
+	required for v0.1)</P>
+</UL>
+<H3>Functionality changes and fixes</H3>
+<UL>
+	<LI><P><a href="plugin-architecture.html">Plugin architecture</a> that allows other Java developers to include
+	their own plug-ins that can encode the recorded job to
+	another format (e.g. H.264)</P>
+	<LI><P>Ships with <a href="plugin-virtualdub.html">encoder plug-in for VirtualDub</a> (MS Windows,
+	www.virtualdub.org)</P>
+	<LI><P>Duplicates can now be created with <b>more than one job</b> selected,
+	duplicating <b>all</b> selected ones at once</P>
+	<LI><P><b>Templates</b> can now be duplicated</P>
+	<LI><P>Fixed bug where deleting selected rows in the tables caused
+	a weird exception</P>
+	<LI><P>Jobs can be given individual names (don't have to be "Job 1",
+	"Job 2", ... anymore)</P>
+</UL>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/compat-limitations.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/compat-limitations.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/compat-limitations.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+   <H1>Compatibility and limitations</H1>
+<P><BR><BR>
+</P>
+<H1>Compatibility</H1>
+<H2>Operating system</H2>
+<P>Since this tool is written in Java
+you can use it on Windows, Linux and Mac OS. This means that it can
+be used on all platforms supported by Nexuiz. You do need Java 6.</P>
+<H2>Nexuiz engine version</H2>
+<P>In order to use this tool you will
+need to use a Nexuiz engine of version 2.5 or newer. The reason is
+that the engine needs to have the variables
+cl_capturevideo_nameformat and cl_capturevideo_number. If you use
+older engines, such as the 2.3 engine, recording will work, but the
+tool will be unable to move the avi clip rendered by the engine to
+your desired location. However, you can manually move it and simply
+ignore the fact that the job finished with an &ldquo;error&rdquo;.</P>
+<H2>Demo file version</H2>
+<P>There is not really any limitation
+about the Nexuiz version that was used when recording the demo. That
+means that you should be able to use the Nexuiz demo recorder with
+demos from previous Nexuiz versions (they can be older than 2.5). You
+just have to make sure that the engine binary you use for recording
+them is v2.5 and newer. Keep in mind that using a newer-generation
+engine such as the 2.5 one for older demos should usually work
+without problems. You might have to move the dataXXXXXX.pk3 files
+from the Nexuiz version that corresponds to the demo version into the
+data directory.</P>
+<H2>Nexuiz Demo Recorder job queues</H2>
+<P>Table preferences, job queues and template lists are not compatible
+between v0.1 and newer versions! Make sure you install this in a different
+direcory than v0.1!</P>
+<H1>Limitations</H1>
+<P>This tool only works with 1-map demos
+(such as the one being automatically recorded by Nexuiz when using
+the cl_autodemo feature). If you manually recorded a demo that
+contains several maps, you can only specify a start- and endtime for
+the first map. However, you can try to use the demosplit.pl script
+from svn (trunk/misc/tools/demosplit.pl) to split demos having
+several maps into several 1-map demos. However, note that this tool
+will probably only work properly for older demos (up to Nexuiz v2.4.2
+demos).</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/credits.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/credits.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/credits.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Credits</H1>
+<P>Credits to the <A HREF="http://www.everaldo.com/crystal/">the
+Crystal project</A> (<A HREF="http://www.everaldo.com/crystal/">http://www.everaldo.com/crystal/</A>)
+for the icons. Thanks to the development teams of
+<A HREF="https://javahelp.dev.java.net/" target="_blank">JavaHelp</A>
+(<A HREF="https://javahelp.dev.java.net/" target="_blank">https://javahelp.dev.java.net/</A>,
+<A HREF="https://swingx.dev.java.net/">SwingX</A> (<A HREF="https://swingx.dev.java.net/">https://swingx.dev.java.net/</A>)
+and <A HREF="http://www.miglayout.com/">MigLayout</A> (<A HREF="http://www.miglayout.com/">http://www.miglayout.com/</A>).
+Thanks to the <A HREF="http://jhelpdev.sourceforge.net/">JHelpDev</A> team
+(<A HREF="http://jhelpdev.sourceforge.net/">http://jhelpdev.sourceforge.net/</A>) for developing the
+tool that allowed me to create this documentation without much hassle.
+Thanks to divverent (div0) for the <b>demotc</b> perl script. Thanks to merlijn for
+some suggestions and hints. Thanks to Dave who offered some "useless" patches :P.
+Thank <B>you</B> for using the Nexuiz Demo Recorder.</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/faq.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/faq.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/faq.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,100 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>FAQ</H1>
+<P><I>Question: Is it possible to have a
+job being recorded with one of the Nexuiz presets (Low, medium,
+normal, high, ultra), without affecting my Nexuiz installation in
+general (so that this configuration is just active during capturing
+the current demo?</I></P>
+<P>Yes it is. In Nexuiz there are config
+files for this, which are called:</P>
+<UL>
+	<LI><P>effects-low.cfg</P>
+	<LI><P>effects-med.cfg</P>
+	<LI><P>effects-normal.cfg</P>
+	<LI><P>effects-high.cfg</P>
+	<LI><P>effects-ultra.cfg</P>
+	<LI><P>effects-ultimate.cfg</P>
+	<LI><P>effects-omg.cfg</P>
+</UL>
+<P>In order to use them, there are 2
+approaches</P>
+<P><B>Approach 1:</B> You should
+consider to use a new directory for each new set of effect configs
+you want to try out. This will simply help you to avoid loss of your
+original config, or any unwanted modification to it. For example,
+setup Nexuiz manually by starting it with some userdir, e.g. <B>-userdir
+effectTestUltimate</B>, start Nexuiz, go into the menu and set the
+effects to ultimate (e<I>xec effects-ultimate.cfg</I> in the
+console). Maybe fine-tune settings as you like. Then put the <B>-userdir
+effectTestUltimate</B> directive into the <B>engine parameters</B>
+field of the job.</P>
+<P><b>Approach 2:</b> For this approach it is
+recommended that you do a backup of your config somewhere else, even
+though everything should work fine and your original config should
+not be modified at all after the demo was recorded.</P>
+<P>Add these 4 lines to the <b>exec before
+capture</b> field:</P>
+<P><B>saveconfig backup.cfg</B></P>
+<P><B>cvar_resettodefaults_all</B></P>
+<P><B>exec effects-ultimate.cfg</B></P>
+<P><B>r_restart</B></P>
+<P>The first line will backup your
+current config to &ldquo;backup.cfg&rdquo;. The second line resets
+all variables (you can remove this if you want, but sometimes these
+effect-xyz.cfg config files will not set every single variable to the
+new value, but assume that some values have their default value,
+which might not be the case in your config. So resetting everything
+to defaults is recommended. Of course you can choose to use another
+effects config than <B>effects-ultimate.cfg</B>. After executing that
+effects config a restart of the renderer (<B>r_restart</B>) is
+mandatory, because of some of the changes of the effects config not
+being applied without a renderer-restart. Also, keep in mind that the
+cvar_resettodefaults_all will have changed your Nexuiz screen
+resolution (vid_height, vid_width). The r_restart command however
+won't take these changes into account (it will still use your old resolution),
+only <B>vid_restart</B> would.
+This just means that you do not have to change these 2 variables
+manually to &ldquo;keep&rdquo; your old screen resolution, as long as
+you don't use vid_restart.</P>
+<P>Also add this to your <b>exec after
+capture</b> field:</P>
+<P><B>cvar_resettodefaults_all</B></P>
+<P><B>exec backup.cfg</B></P>
+<P>Please note: in case you want to
+setup other special things, e.g. whether to record in the AVI instead
+of OGV format, the video FPS, etc, you'd have to put these settings
+some place <B>after</B> the <B>cvar_resettodefaults_all</B> command
+in the <B>exec before capture</B> field.</P>
+<P><BR><BR>
+</P>
+<P><I>Question: Is it possible to record
+a job in a particular screen resolution?</I></P>
+<P>Yes it is. The height and width are
+configured by setting the value vid_height and vid_width to your
+desired value. For example to record a demo in the HD resolution,
+which is 1280x720, put this into the <B>exec before capture</B>
+field:</P>
+<P ALIGN=LEFT><B>vid_width 1280</B></P>
+<P ALIGN=LEFT><B>vid_height 720</B></P>
+<P ALIGN=LEFT><B>vid_restart</B></P>
+<P><BR><BR>
+</P>
+<P><I>Question: I have found a bug, or I
+encountered some other problem, what do I do? </I>
+</P>
+<P>Well, truth to be told, I cannot
+guarantee for any support of this tool. You can put questions or
+comments into the forum thread of the AlienTrap forums where you
+downloaded the tool from.</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_job.gif
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_job.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_template.gif
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/create_template.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/customize_tables.gif
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/customize_tables.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/edit_job_vdub.gif
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/edit_job_vdub.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/main_window.gif
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/main_window.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_dialog.gif
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_dialog.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_global_vdub.gif
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/images/preferences_global_vdub.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/introduction.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/introduction.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/introduction.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Introduction</H1>
+<P>Welcome to the Nexuiz demo recorder. This tool is made
+for people <B>who are already familiar</B> with the manual demo
+recording process and are looking for a way to automate this process.
+In simple words, this tool allows you to create jobs that are being
+put into a job queue, so that they can be executed one after another,
+at a point in time when you don't need to access your computer
+anymore. For each job you have to specify the demo you want to
+record, the second when the record is supposed to start and end, as
+well as the location where to save the final video file to. You can
+also use one or more of the available plug-ins to encode the resulting
+video file to another format.</P>
+<P>To get started, take a look at the <a href="basic_tutorial.html">basic tutorial</a></P>
+<P>Click <a href="changelog.html">here</a> to learn about the new features of
+this release.</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/license.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/license.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/license.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,15 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>License</H1>
+<P>This tool is released under the <b>GPL v2</b> (same license as Nexuiz).</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/open-save.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/open-save.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/open-save.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,29 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+   <H1>Open/Save Jobs and Templates</H1>
+<P>First of all, all your templates and
+jobs will automatically be saved when closing the program (and
+automatically loaded when starting it again). You can find these
+files in the sub-directory &ldquo;settings&rdquo;of the program, the
+files are called <B>templates.dat</B> and <B>jobs.dat</B>. Please
+note that the job queue will also be saved each time a job finished
+its execution (so that you don't lose the progress of your jobs in
+case your computer crashes while processing).</P>
+<P>You have the possibility to manually
+save and load other job queues (this might be handy if you are
+working on different projects). For this simply use the <B>File menu</B>
+and click on <B>Load job queue</B> or <B>Save job queue</B>
+respectively. Please be aware that <B>Load job queue</B> will clear
+your current job queue and it cannot be recovered, so you better save
+your original job queue before loading a new one.</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-architecture.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-architecture.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-architecture.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,81 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+<H1>Plug-in architecture</H1>
+<H2>Introduction</H2>
+<P>While the Nexuiz Demo Recorder (NDR)
+saves you a lot of time, what many video editors still need to do is
+to transcode/convert/encode/you-name-it the resulting huge avi clip
+to another (lossless) format. Or, if you are not an editor but just a
+player who wants to distribute the recorded clip, unless you had
+Nexuiz encode the clip in the Ogg Theora codec (cl_capturevideo_ogg
+1), you might want to encode it in XviD, H.264 or some other lossy
+codec. For encoding people use all different kinds of tools. Some
+have a GUI, some don't. Some are available on just one platform, some
+are cross-platform to some extent. However, since this release the
+NDR is now able to also encode clips to whatever format you like by
+the use of <B>encoder plug-ins</B>. For this the NDR has been
+extended with a plug-in architecture that will look for plug-ins in
+the &ldquo;plugins&rdquo; folder. NDR plug-ins are basically just a
+bridge between the NDR application and an actual encoder. Examples
+for actual encoders are <B>VirtualDub</B> for MS Windows, or <B>mencoder</B>
+for Linux. What a plug-in will do is to assemble a command line that
+will execute this encoder and hand it the required parameters.</P>
+<H2>Extension is easy</H2>
+<P>In this release the NDR ships with
+just one plug-in: the VirtualDub plug-in. However, if you are a Java
+developer it is really easy for you to write your own plug-ins that
+will be able to handle whatever encoder you want to use. If you are
+interested, have a look into the <B>src</B> folder at the <B>Sample
+Plugin</B>. All you need to do is to implement an interface, edit
+some meta-information files and build a jar file that is being put
+into the <B>plugins</B> directory of the NDR.</P>
+<H2>Plug-in settings</H2>
+<P>Each plug-in allows the user to
+interact with it, by offering settings (or &ldquo;preferences&rdquo;).
+There are 2 kinds of settings:</P>
+<UL>
+	<LI><P>Global settings, which will be
+	shown in the File &rarr; Preferences dialog</P>
+	<LI><P>Job/template-specific settings,
+	which will be shown in the dialog you use when creating or editing
+	jobs or templates</P>
+</UL>
+<H2>Use plug-ins later on</H2>
+<P>In order to invoke the functionality
+of the plug-in you are not required to set up everything at the very
+beginning (before the clip has actually been recorded). Of course,
+you can do so, and in this case the job would be recorded and, right
+after that, be encoded as well. But you can also choose to have jobs
+recorded first, and then later when you see the need to encode them,
+edit the job settings (add your plug-in settings there), and then
+right-click the job and select &ldquo;just run the xyz plug-in&rdquo;.
+Basically this menu item will work on any job with status = <B>done</B>.</P>
+<P>Of course you may not move the
+recorded clip from the destination that you set up, because otherwise
+the plug-in would not find the file to encode it.</P>
+<H2>Error handling</H2>
+<P>In case something went wrong during
+the execution of the plug-in itself, you will see a &ldquo;plug-in
+error&rdquo; in the <B>status</B> column of the jobs table. Like for
+normal &ldquo;errors&rdquo;, you can right-click the job and click on
+&ldquo;show error message&rdquo; in order to see the error message of
+the plug-in. In case you can repair whatever went wrong, do so, then
+right-click the job and click on &ldquo;reset job status to <B>done</B>&rdquo;.
+Then you can run the plug-in again.</P>
+<P>If a plug-in appeared to do its job
+just fine but you suspect that something went wrong, you might want
+to have a look at the <B>logs</B> directory in the NDR folder.
+Depending on whether the one who created the plug-in implemented it,
+you might see log files which are basically text files containing the
+output of the encoder itself.</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-virtualdub.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-virtualdub.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/plugin-virtualdub.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,163 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+<H1>VirtualDub plug-in</H1>
+<H2>Introduction</H2>
+<P><B><U>Important:</U> Make sure that
+you read this documentation carefully in order to avoid mistakes!</B></P>
+<P>The prerequisite for using this
+plug-in is that you already familiar with the &ldquo;manual&rdquo;
+process of encoding clips with VirtualDub (<A HREF="http://www.virtualdub.org/">www.virtualdub.org</A>).
+The great thing about VirtualDub is that it offers both a graphical
+user interface (GUI), accessible via <B>virtualdub.exe</B>, and a
+command line interface (CLI), accessible via <B>vdub.exe</B>.</P>
+<H2>Step 1: create VDub configuration
+files (VCF)</H2>
+<P>Let's say we had a clip that we
+wanted to encode not only once, but twice &ndash; into different
+formats. Let's say that the first goal is to encode it to a loss-less
+Huffyuv clip, and the second goal is to encode it to a quality-based
+1-pass XviD clip using MP3 sound.</P>
+<P>What you need to do first is to open
+VirtualDub, set up the settings for one of the goals (by going into
+the video compression and audio compression dialogs) and then click
+on File &rarr; Save processing settings (CTRL+S). You will end up
+with saving a .vcf file somewhere on your hard drive &hellip;
+remember where you put it! After that, you'd set up the settings for
+your second goal and save these processing settings as well, as a new
+.vcf file.</P>
+<H2>Step 2: Enable and set up the VDub
+plug-in</H2>
+<P>First we need to set up the global
+settings of the plug-in. Go into the preferences dialog and enable
+the plug-in.</P>
+<P><img src="images/preferences_global_vdub.gif"/></P>
+<P>Use the &ldquo;...&rdquo; button to
+specify the path to the vdub.exe file. The setting <B>max number of
+VCFs per job</B> is important, because the number you put in there
+will influence how many VCF files you will be able to select in the
+<B>job dialog</B> (the dialog where you create or edit a
+job/template). For our example we need to set it to 2, but it doesn't
+matter if you choose a higher value. Since VDub will encode <B>two</B>
+files for us, we will have to tell VDub under which filenames to save
+them. You can select which approach you want to use by configuring
+the setting <B>output as suffix(0) or file(1)</B>. If you <B>don't</B>
+check the checkbox this means that you will enter a small part of the
+file name, the <B>suffix</B>, that will be put at the end of the
+original file name. E.g. if your original filename was
+&ldquo;C:\render\myVideoFile.avi&rdquo;, and the suffix is
+&ldquo;_huffyuv&rdquo;, the resulting file would be
+&ldquo;C:\render\myVideoFile_huffyuv.avi&rdquo;. However, if you
+checked that checkbox, you would instead get a &ldquo;...&rdquo;
+button that allows you to specify the filename for the resulting file
+manually (this is handy if you wanted to save the file to another
+folder for example).</P>
+<P>The <B>show extra options</B> setting
+will be explained at the end of this page.</P>
+<P>Let's now have a look at the dialog
+when editing a job</P>
+<H2>Step 3: Set up the job-specific
+settings</H2>
+<P>Open the job dialog.</P>
+<P><img src="images/edit_job_vdub.gif" />/P>
+<P>Let's ignore the 2 checkboxes at the
+top for a moment. What you will need to do is to specify the path to
+the 2 .vcf files you created previously in VirtualDub in step 1. Then
+you need to specify the suffix (or the file using the file-chooser)
+so that VDub knows where to save the encoded videos.</P>
+<H2>Step 4: Run the plug-in</H2>
+<UL>
+	<LI><P>If you had your job recorded
+	(without using the plug-in) then the status of the job will already
+	be &ldquo;<B>done&rdquo;</B> and you can right-click the job and run
+	the VirtualDub plug-in</P>
+	<LI><P>If the job has <B>not</B> been
+	recorded yet, just click on Start processing and wait for the result</P>
+</UL>
+<H2>Additional information</H2>
+<H3>Empty VCF paths</H3>
+<P>As you know you can specify any
+number in the <B>preferences dialog</B> for the setting <B>max number
+of VCFs per job</B>. In case of our example that consists of 2 jobs,
+if you selected 3 (or more) as max. number, and then opened the <B>job
+dialog</B>, you would see 3 fields each for the <B>path to VCF file</B>
+and <B>output file</B> setting. This, however, is not a problem. The
+plug-in will only execute those VCF jobs for which both the path to
+the VCF and the output file fields are filled with proper
+information.</P>
+<H3>VDub job control</H3>
+<P>You still don't know what the 2
+checkboxes in the <B>job dialog</B> at the top of the VDub settings
+mean. What you need to know is that VirtualDub is having its own &ldquo;job
+queue&rdquo;, which, however, is called <B>&ldquo;job control&rdquo;</B>.</P>
+<P>The following scenarios can make
+sense:</P>
+<UL>
+	<LI><P>Having <B>both</B> checkboxes
+	<B>enabled</B>: Each time a new NDR job is executed which has
+	VirtualDub jobs, the <B>job control</B> of VirtualDub will be
+	cleared. This means that if you had other, old things in there
+	(possibly when working with VirtualDub outside of the NDR) that
+	these will be deleted from VirtualDub's job control, before the new
+	jobs are added there. Checking the second checkbox means that after
+	adding one of the VirtualDub jobs from within the the NDR,
+	VirtualDub will actually encode them right away (which you will
+	want, at least under normal circumstances).</P>
+	<LI><P>Having <B>both</B> checkboxes
+	<B>disabled</B>: In this case the only thing the NDR would do would
+	be to add the two jobs to the job control of VirtualDub, but
+	VirtualDub won't be encoding them. They are just put into the queue.
+	This means that if you were to start the the graphical user
+	interface of VirtualDub after the NDR completed processing, and open
+	the Job Control (F4), you'd see these jobs &ldquo;waiting&rdquo;,
+	and you could render them by pressing the Start button in Virtual
+	Dub.</P>
+</UL>
+<P>The reason why these checkboxes exist
+is flexibility. I often want the NDR to record stuff over night,
+because during the recording process my PC is virtually unusable.
+However, encoding clips in VirtualDub is a task that, depending on
+the codec, can be done in the background and allows me to use the PC
+for other things. In this case I'd disable both checkboxes, have the
+NDR record all clips and add them to VirtualDub's job control, and
+then on the next day, I decide when and which jobs to encode into
+different formats from within the VirtualDub GUI.</P>
+<P>And btw, just to be clear, the <B>second</B>
+checkbox means that, <B>when enabled</B>, VirtualDub would actually
+start encoding <U><B>all</B></U> clips that have already been in
+VirtualDub's job control and that have not been encoded yet.</P>
+<H3>Extra options</H3>
+<P>If you selected the <B>show extra
+options</B> checkbox in the <B>preferences dialog</B>, you will
+notice that, in the <B>job dialog</B>, you got 2 additional
+settings/checkboxes for each VCF file. Their names are pretty much
+self-explanatory. I added these checkboxes because, after a scene was
+rendered, I usually have the clip encoded into a loss-less format
+(e.g. Huffyuv) right away. Since it is no problem to encode further
+things (e.g. compressed XviD movies) based on the Huffyuv clip
+(instead of always using the original avi file), enabling both these
+checkboxes for VCF 1 would mean that the 2<SUP>nd</SUP> (3<SUP>rd</SUP>,
+4<SUP>th</SUP> &hellip;) VCF jobs will be based on the
+huffyuv-encoded file, and I got rid of original, big video as well,
+saving hard disk space.</P>
+<H3>Trouble shooting</H3>
+<P>In case you suspect that the
+expected, encoded file(s) is somehow incorrect (or even missing),
+have a look at the <B>logs</B> directory in the NDR folder. You will
+see log-files with the following format:</P>
+<P>VirtualDub_NameOfTheJob_vcf1.log</P>
+<P>Instead of &ldquo;vcf1&rdquo; you
+might also find &ldquo;vcf2&rdquo;, &ldquo;vcf3&rdquo;... it
+specifies for which VCF file of that job the log file stands for.</P>
+<P><BR><BR>
+</P>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/preferences.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/preferences.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/preferences.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,64 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Preferences dialog</H1>
+<P>The preferences dialog allows you to
+change the behavior of the program in certain situaitons.</P>
+<P><img src="images/preferences_dialog.gif" /></P>
+<UL>
+	<LI><P><b>Overwrite final video
+	destination file if it exists:</b> This checkbox allows you to determine
+	whether the final destination video file (e.g.
+	<B>C:\render\fragOfTheMonth.avi</B>) should be overwritten by a job
+	if that file already existed from your harddrive. By default this
+	option is disabled. The program will, in this case, not overwrite
+	any existing files, but save the video file with a &ldquo;copyX&rdquo;
+	suffix, for example <B>C:\render\fragOfTheMonth_copy1.avi</B></P>
+	<LI><P><b>Disable rendering while
+	fast-forwarding:</b> As the Nexuiz demo recorder creates a cutted demo
+	that has slowmo-commands in it to fast-forward it to the desired
+	start time, the fast-forwarding process is often slowed down due to
+	the action taking place on your screen (and your graphic card
+	choking with the mere speed of the demo, e.g. during a &ldquo;slowmo
+	100&rdquo; period). However, there is a variable called r_render in
+	the engine which can be set to 0, which will disable any render
+	updates. This will speed up the fast-forwarding process massively.
+	Of course, r_render is being set to 1 a few seconds before start
+	time is reached.</P>
+	<LI><P><b>Disable sound while
+	fast-forwarding:</b> The value of the variable volume is saved and the
+	set to 0 right after the demo was loaded. The reason is that it can
+	be annoying to hear the in-game sounds while fast-forwarding.</P>
+	<LI><P><b>Fast-forward speed (first stage
+	or second stage):</b> The demo is fast-forwarded using two different
+	fast-forward speeds. In the first stage, when the start time is
+	still about a minute away, the &ldquo;first stage&rdquo; value is
+	used. Then, until 5 seconds before the start time, the &ldquo;second
+	stage&rdquo; value is used. You will only need to manipulate these
+	values if you discover that the final recorded video is inaccurate
+	when it comes to the time when the recording starts (e.g. when then
+	video recording starts too late, reduce these 2 values for first and
+	second stage a bit &ndash; you will have to experiment)</P>
+	<LI><P><b>Do not delete cut demos:</b> The
+	file name of the automatically created cut demo is like the original
+	name of your demo, but ends with &ldquo;_autocut.dem&rdquo;.
+	Normally this demo is created, being recorded from, and then deleted
+	again. If you enable this option the demo file is not deleted, and
+	you can inspect it with a Hex editor or whatever else you want to do
+	with it (maybe send it to a friend to have him record it?)</P>
+	<LI><P><b>Append this suffix to job-name when duplicating jobs:</b>
+	As you now have customizable job names it would probably be a bad idea to
+	give duplicated jobs the same name as the original jobs. This is why you can
+	set up a suffix that will be appended to the original job name.</P>
+
+</UL>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/templates.html
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/templates.html	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/help/html/templates.html	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,69 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+<H1>Templates</H1>
+<P>The Nexuiz demo recorder offers you
+templates that you can use to create new jobs (which are based on a
+template) more quickly and efficiently. Imagine these &quot;templates&quot;
+didn't exist ... you would always have to specify the complete path
+to the engine, the video file, the demo file, and the final video
+file &ndash; each single time when creating a job, from scratch. This
+would take a long time and would be inefficient.</P>
+<P>Instead you are encouraged to create
+templates. Templates can be created either from scratch (click on the
+<B>+ Create</B> button next to the template table) or from an
+existing job (select the job, then click on the button <B>Create from
+job</B>).</P>
+<P>The latter will just add a new entry
+in the templates table, without prompting you for any further
+information. The template's name and summary will be &quot;Generated
+from job&quot;, and you can change this by double-clicking this
+generated template, renaming it and giving it a meaningful summary,
+then click save. You will notice that all other values have been
+taken from the job and don't need to be filled out by you anymore.</P>
+<P>Here you can see the template dialog
+when creating a new template:</P>
+<P><img src="images/create_template.gif" /></P>
+<P>The dialog looks very much alike the
+&quot;create job&quot; dialog presented to you in the <a href="basic_tutorial.html#jobs_create">Basic
+tutorial</a>. There are a few differences, however:</P>
+<UL>
+	<LI><P>The dialog shows you a <B>template
+	name</B> and <B>description</B></P>
+	<LI><P>Instead of specifying a <B>demo
+	file</B> you are now specifying a <B>demo directory</B></P>
+	<LI><P>Instead of specifying a <B>video
+	destination file</B>, you now just specify the <B>directory</B></P>
+	<LI><P>You
+	don't specify a <B>start time</B> or <B>end time</B> for templates,
+	because these 2 input parameters are specific for each job and don't
+	make sense to be saved for a template</P>
+</UL>
+<P>Once you have a template you can
+create new jobs easily by selecting a template in the templates table
+and then click on the <B>Create from template</B> button in the jobs
+panel.
+</P>
+<H2>What could templates also be good
+for?</H2>
+<UL>
+	<LI><P>Using different configs for
+	recording (you can set a config to be used either in the &ldquo;engine
+	parameters&rdquo; field, e.g. <B>+exec myconfig.cfg</B>, or you
+	could put the <B>exec myconfig.cfg</B> into the &ldquo;exec before&rdquo;
+	field.</P>
+	<LI><P>Recording with different Nexuiz
+	engines to compare the visual quality</P>
+	<LI><P>Inspire me &hellip; what do you
+	use templates for? Post it to the forum!</P>
+</UL>
+  </body>
+</html>
+

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/advanced.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/advanced.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit_add.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/edit_add.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editclear.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editclear.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editcopy.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editcopy.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editdelete.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/editdelete.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/exit.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/exit.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/fileopen.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/fileopen.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/filesave.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/filesave.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/help.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/help.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/info.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/info.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/package.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/package.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_pause.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_pause.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_play.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/player_play.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart_blue.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/quick_restart_blue.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/status_unknown.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/status_unknown.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/view_right_p.png
===================================================================
(Binary files differ)


Property changes on: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/icons/view_right_p.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/misc/tools/NexuizDemoRecorder/src/main/resources/jsmooth exe project.jsmooth
===================================================================
--- trunk/misc/tools/NexuizDemoRecorder/src/main/resources/jsmooth exe project.jsmooth	                        (rev 0)
+++ trunk/misc/tools/NexuizDemoRecorder/src/main/resources/jsmooth exe project.jsmooth	2010-02-11 15:54:58 UTC (rev 8633)
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+<JVMSearchPath>registry</JVMSearchPath>
+<JVMSearchPath>javahome</JVMSearchPath>
+<JVMSearchPath>jrepath</JVMSearchPath>
+<JVMSearchPath>jdkpath</JVMSearchPath>
+<JVMSearchPath>exepath</JVMSearchPath>
+<JVMSearchPath>jview</JVMSearchPath>
+<arguments></arguments>
+<classPath>..\..\..\..\..\Nexuiz Demo Recorder\Build v0.2\NexuizDemoRecorder-0.2.jar</classPath>
+<currentDirectory>${EXECUTABLEPATH}</currentDirectory>
+<embeddedJar>false</embeddedJar>
+<executableName>..\..\..\..\..\Nexuiz Demo Recorder\Build v0.2\NexuizDemoRecorder.exe</executableName>
+<iconLocation>..\..\..\..\..\..\temp\128x128\filesystems\folder_video.png</iconLocation>
+<initialMemoryHeap>-1</initialMemoryHeap>
+<mainClassName>com.nexuiz.demorecorder.main.Driver</mainClassName>
+<maximumMemoryHeap>-1</maximumMemoryHeap>
+<maximumVersion></maximumVersion>
+<minimumVersion>1.6</minimumVersion>
+<skeletonName>Windowed Wrapper</skeletonName>
+<skeletonProperties>
+<key>Message</key>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>URL</key>
+<value>http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleProcess</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>SingleInstance</key>
+<value>1</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>JniSmooth</key>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<key>Debug</key>
+<value>0</value>
+</skeletonProperties>
+</jsmoothproject>



More information about the nexuiz-commits mailing list