--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="BPT Plugin" />
+ <meta name="description" value="Binary Partition Tree Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.BptSegmenter" />
+ <jar file="bptplugin.jar" />
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="IGC Plugin" />
+ <meta name="description" value="Interactive Graph Cuts Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.IgcSegmenter" />
+ <jar file="igcplugin.jar" />
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="SIOX Plugin" />
+ <meta name="description" value="Simple Interactive Object Extraction Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.SioxSegmenter" />
+ <jar file="sioxplugin.jar" />
+ <jar file="sioxapi.jar" />
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="SRG Plugin" />
+ <meta name="description" value="Seeded Region Growing Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.SrgSegmenter" />
+ <jar file="srgplugin.jar" />
+</plugin>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+ <!-- Generated using the CDVP Interactive Segmentation Tool -->
+ <meta http-equiv="Content-type"
+ content="text/html; charset=utf-8"/>
+ <meta name="Generator"
+ content="Interactive Segmentation Tool"/>
+
+ <title>${page-title}</title>
+
+ <script type="text/javascript" language="javascript">
+
+${preloads}
+
+ function rollover(docElem, image) {
+ docElem.src = image.src
+ return true
+ }
+
+ </script>
+
+</head>
+<body>
+ <!-- Image -->
+ <img name="${image-name}"
+ src="${image-href}"
+ usemap="#${map-name}"
+ alt="${image-alt}"
+ title="${image-alt}"/>
+
+ <!-- Image map -->
+ <map name="${map-name}" id="${map-name}">
+${contents}
+ </map>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ======================================================================
+ Aug 20, 2009 2:11:20 PM
+
+ istapp
+ The interactive segmentation tool
+
+ Kevin McGuinness
+ ====================================================================== -->
+<project name="istapp" default="default">
+ <description>
+ The interactive segmentation tool
+ </description>
+
+ <property file="resources/ant/properties" />
+ <property name="src.dir" location="src"/>
+ <property name="build.dir" location="build"/>
+ <property name="lib.dir" location="lib"/>
+ <property name="doc.dir" location="doc"/>
+ <property name="dist.dir" location="dist"/>
+ <property name="bin.jar" value="istapp.jar"/>
+ <property name="src.jar" value="istapp-src.jar"/>
+ <property name="doc.zip" value="istapp-doc.zip"/>
+ <property name="plugins.dir" location="plugins"/>
+ <property name="deb.prefix" value="${build.dir}/packages/debian/usr" />
+
+ <!-- The following four properties are needed by the copy-plugins action -->
+ <property name="bptplugin.dir" location="../BPT Plugin" />
+ <property name="igcplugin.dir" location="../IGC Plugin" />
+ <property name="srgplugin.dir" location="../SRG Plugin" />
+ <property name="sioxplugin.dir" location="../SIOX Plugin" />
+
+
+ <path id="class.path">
+ <pathelement location="${build.dir}"/>
+ <fileset dir="${lib.dir}" includes="*.jar"/>
+ </path>
+
+ <!-- Define tasks -->
+ <taskdef name="deb" classpath="resources/ant/lib/ant-deb.jar"
+ classname="com.googlecode.ant_deb_task.Deb" />
+ <taskdef name="gzip2" classpath="resources/ant/lib/ant-gzip2.jar"
+ classname="gzip2.GZip2" />
+ <taskdef name="swt_identify" classpath="resources/ant/lib/ant-swt-identify.jar"
+ classname="ie.cdvp.utils.ant.SwtIdentify" />
+ <taskdef name="jsmooth" classpath="resources/ant/lib/jsmoothgen-ant.jar"
+ classname="net.charabia.jsmoothgen.ant.JSmoothGen" />
+ <taskdef name="izpack" classpath="resources/ant/lib/standalone-compiler.jar"
+ classname="com.izforge.izpack.ant.IzPackTask" />
+
+ <target name="init">
+ <mkdir dir="${build.dir}"/>
+ <mkdir dir="${build.dir}/classes"/>
+ <mkdir dir="${build.dir}/packages"/>
+ <mkdir dir="${build.dir}/temp"/>
+ <mkdir dir="${doc.dir}"/>
+ <mkdir dir="${dist.dir}"/>
+ </target>
+
+ <target name="clean">
+ <delete dir="${build.dir}/classes" />
+ <delete dir="${build.dir}/packages" />
+ <delete dir="${build.dir}/temp" />
+ <delete dir="${doc.dir}" />
+ <delete dir="${dist.dir}" />
+ </target>
+
+ <target name="compile" depends="init">
+ <javac srcdir="${src.dir}"
+ destdir="${build.dir}/classes"
+ debug="${java.debug}"
+ source="${java.source.version}"
+ target="${java.target.version}">
+ <classpath refid="class.path"/>
+ </javac>
+ <!-- copy resources -->
+ <copy todir="${build.dir}/classes">
+ <fileset dir="${src.dir}">
+ <include name="**/*.html" />
+ </fileset>
+ </copy>
+ </target>
+
+ <target name="doc" depends="init">
+ <javadoc sourcepath="${src.dir}"
+ destdir="${doc.dir}/api"
+ author="true"
+ version="true"
+ use="true"
+ windowtitle="${app.name}"
+ classpathref="class.path">
+ <link href="http://java.sun.com/javase/6/docs/api/"/>
+ </javadoc>
+ </target>
+
+ <target name="dist" depends="compile,doc">
+ <!-- class file jar -->
+ <jar destfile="${dist.dir}/${bin.jar}">
+ <fileset dir="${build.dir}/classes">
+ <include name="**/*"/>
+ </fileset>
+ </jar>
+ <!-- source code jar -->
+ <jar destfile="${dist.dir}/${src.jar}">
+ <fileset dir="${src.dir}">
+ <include name="**/*.java"/>
+ </fileset>
+ </jar>
+ <!-- documentation zip -->
+ <zip destfile="${dist.dir}/${doc.zip}">
+ <fileset dir="${doc.dir}">
+ <include name="**/*"/>
+ </fileset>
+ </zip>
+ </target>
+
+ <target name="copy-plugins">
+ <!-- Copy plugins from the other projects -->
+ <copy todir="${plugins.dir}">
+ <fileset dir="${bptplugin.dir}/dist/" includes="BPTPlugin.plugin/**/*" />
+ <fileset dir="${igcplugin.dir}/dist/" includes="IGCPlugin.plugin/**/*" />
+ <fileset dir="${srgplugin.dir}/dist/" includes="SRGPlugin.plugin/**/*" />
+ <fileset dir="${sioxplugin.dir}/dist/" includes="SIOXPlugin.plugin/**/*" />
+ </copy>
+ </target>
+
+ <target name="package-mac" depends="dist">
+ <!-- Make the folders -->
+ <mkdir dir="${dist.dir}/${app.name}.app"/>
+ <mkdir dir="${dist.dir}/${app.name}.app/Contents"/>
+ <mkdir dir="${dist.dir}/${app.name}.app/Contents/MacOS"/>
+ <mkdir dir="${dist.dir}/${app.name}.app/Contents/Resources"/>
+ <mkdir dir="${dist.dir}/${app.name}.app/Contents/Resources/Java"/>
+ <mkdir dir="${dist.dir}/${app.name}.app/Contents/Resources/Java/plugins"/>
+
+ <!-- Copy and filter plist and PkgInfo -->
+ <copy todir="${dist.dir}/${app.name}.app/Contents/" overwrite="true">
+ <fileset dir="resources/plaf/mac/" includes="Info.plist,PkgInfo"/>
+ <filterset>
+ <filter token="name" value="${app.name}"/>
+ <filter token="version" value="${app.version}"/>
+ <filter token="synopsis" value="${app.synopsis}"/>
+ <filter token="icons" value="icon.icns"/>
+ <filter token="exename" value="ist"/>
+ </filterset>
+ </copy>
+
+ <!-- Copy and filter launch script -->
+ <copy todir="${dist.dir}/${app.name}.app/Contents/MacOS" overwrite="true">
+ <fileset dir="resources/plaf/mac/" includes="ist" />
+ <filterset>
+ <filter token="appmain" value="${app.main}"/>
+ </filterset>
+ </copy>
+
+ <!-- Copy resources -->
+ <copy todir="${dist.dir}/${app.name}.app/Contents/Resources">
+ <fileset dir="resources/plaf/mac" includes="icon.icns" />
+ <fileset dir="." includes="resources/config/*" />
+ <fileset dir="." includes="resources/icons/*" />
+ <fileset dir="." includes="resources/doc/*" />
+ </copy>
+
+ <!-- Copy jars -->
+ <copy todir="${dist.dir}/${app.name}.app/Contents/Resources/Java">
+ <fileset dir="dist" includes="${bin.jar}" />
+ <fileset dir="lib" includes="istapi.jar,swt-cocoa-32.jar,swt-cocoa-64.jar,jface.jar" />
+ </copy>
+
+ <!-- Copy plugins -->
+ <copy todir="${dist.dir}/${app.name}.app/Contents/Resources/Java/plugins">
+ <fileset dir="${plugins.dir}" >
+ <include name="**/*" />
+ <exclude name="**/*.dll" />
+ <exclude name="**/*.so" />
+ </fileset>
+ </copy>
+
+ <!-- Make executable -->
+ <chmod perm="0755" file="${dist.dir}/${app.name}.app/Contents/MacOS/ist" />
+
+ <!-- Bundle into a zip file -->
+ <zip destfile="${dist.dir}/${app.package}_${app.version}_mac.zip">
+ <zipfileset dir="${dist.dir}/${app.name}.app" prefix="${app.name}.app">
+ <include name="**/*" />
+ <exclude name="Contents/MacOS/*"/>
+ </zipfileset>
+
+ <zipfileset dir="${dist.dir}/${app.name}.app" prefix="${app.name}.app" filemode="755">
+ <include name="Contents/MacOS/*" />
+ </zipfileset>
+ </zip>
+ </target>
+
+ <target name="package-debian" depends="dist">
+ <!-- Create folder structure -->
+ <mkdir dir="${build.dir}/packages/debian/usr/bin" />
+ <mkdir dir="${build.dir}/packages/debian/usr/share/${app.package}" />
+ <mkdir dir="${build.dir}/packages/debian/usr/share/doc/${app.package}" />
+ <mkdir dir="${build.dir}/packages/debian/usr/share/applications" />
+ <mkdir dir="${build.dir}/packages/debian/usr/share/man/man1" />
+ <mkdir dir="${build.dir}/packages/debian/usr/share/icons/hicolor" />
+
+ <!-- Man page & copyright-->
+ <gzip2 destfile="${build.dir}/packages/debian/usr/share/man/man1/ist.1.gz"
+ src="resources/plaf/debian/ist.1" level="9" filesystem="unix" />
+ <copy todir="${build.dir}/packages/debian/usr/share/doc/${app.package}"
+ file="resources/plaf/debian/copyright" />
+
+ <!-- Shortcuts & icons -->
+ <copy tofile="${build.dir}/packages/debian/usr/share/applications/${app.package}.desktop"
+ file="resources/plaf/debian/desktop" />
+ <copy todir="${build.dir}/packages/debian/usr/share/icons/hicolor">
+ <fileset dir="resources/icons" includes="icon-*.png" />
+ <regexpmapper from="icon-(.*)\.png" to="\1x\1/apps/${app.package}.png" />
+ </copy>
+
+ <!-- Binary (launcher) -->
+ <copy tofile="${build.dir}/packages/debian/usr/bin/ist"
+ file="resources/plaf/debian/ist" overwrite="true">
+ <filterset>
+ <filter token="jars" value="${bin.jar}:istapi.jar:jface.jar:swt.jar"/>
+ <filter token="data" value="/usr/share/${app.package}" />
+ <filter token="main" value="${app.main}" />
+ </filterset>
+ </copy>
+
+ <!-- Application data -->
+ <copy todir="${build.dir}/packages/debian/usr/share/${app.package}">
+ <fileset dir="dist" includes="${bin.jar}"/>
+ <fileset dir="lib" includes="istapi.jar,jface.jar" />
+ <fileset dir="." includes="resources/config/**/*" />
+ <fileset dir="." includes="resources/icons/**/*" />
+ <fileset dir="." includes="resources/doc/**/*" />
+ <fileset dir="." >
+ <include name="plugins/**/*" />
+ <exclude name="plugins/**/*.jnilib" />
+ <exclude name="plugins/**/*.dll" />
+ </fileset>
+ </copy>
+
+ <!-- Build i386 deb -->
+ <deb todir="dist" package="${app.package}" priority="extra"
+ section="graphics" architecture="i386"
+ depends="sun-java5-jre | sun-java6-jre"
+ postinst="resources/plaf/debian/postinst"
+ postrm="resources/plaf/debian/postrm" >
+
+ <version upstream="${app.version}" debian="1" />
+ <changelog file="resources/plaf/debian/changelog" />
+ <changelog file="resources/plaf/debian/changelog" debian="true" />
+ <maintainer name="${app.author}" email="${app.email}" />
+
+ <description synopsis="${app.synopsis}">
+ A tool that allows you to interactively extract an object
+ from an image using a variety of scribble based algorithms
+ </description>
+
+ <tarfileset dir="${build.dir}/packages/debian/" includes="**/*" excludes="usr/bin/*" />
+ <tarfileset dir="${build.dir}/packages/debian/" includes="usr/bin/*" filemode="0755" />
+ <tarfileset file="${lib.dir}/swt-gtk.jar" fullpath="usr/share/${app.package}/swt.jar"/>
+ </deb>
+
+ <!-- Build amd64 deb -->
+ <deb todir="dist" package="${app.package}" priority="extra"
+ section="graphics" architecture="amd64"
+ depends="sun-java5-jre | sun-java6-jre"
+ postinst="resources/plaf/debian/postinst"
+ postrm="resources/plaf/debian/postrm" >
+
+ <version upstream="${app.version}" debian="1" />
+ <changelog file="resources/plaf/debian/changelog" />
+ <changelog file="resources/plaf/debian/changelog" debian="true" />
+ <maintainer name="${app.author}" email="${app.email}" />
+
+ <description synopsis="${app.synopsis}">
+ A tool that allows you to interactively extract an object
+ from an image using a variety of scribble based algorithms
+ </description>
+
+ <tarfileset dir="${build.dir}/packages/debian/" includes="**/*" excludes="usr/bin/*" />
+ <tarfileset dir="${build.dir}/packages/debian/" includes="usr/bin/*" filemode="0755" />
+ <tarfileset file="${lib.dir}/swt-gtk-64.jar" fullpath="usr/share/${app.package}/swt.jar"/>
+ </deb>
+ </target>
+
+ <target name="package-linux" depends="dist">
+ <mkdir dir="${build.dir}/packages/linux" />
+
+ <!-- common stuff -->
+ <copy todir="${build.dir}/packages/linux" >
+ <fileset dir="dist" includes="istapp.jar"/>
+ <fileset dir="lib" includes="istapi.jar"/>
+ <fileset dir="lib" includes="jface.jar"/>
+ <fileset dir="." includes="resources/config/**/*"/>
+ <fileset dir="." includes="resources/icons/**/*"/>
+ <fileset dir="." includes="resources/doc/**/*"/>
+ </copy>
+
+ <!-- plugins -->
+ <copy todir="${build.dir}/packages/linux" >
+ <fileset dir="." >
+ <include name="plugins/**/*" />
+ <exclude name="plugins/**/*.dll" />
+ <exclude name="plugins/**/*.jnilib" />
+ <exclude name="plugins/**/*.dynlib" />
+ </fileset>
+ </copy>
+
+ <!-- tar i386 -->
+ <tar destfile="${dist.dir}/${app.package}_${app.version}_linux_i386.tar.gz"
+ compression="gzip">
+ <tarfileset dir="${build.dir}/packages/linux"
+ includes="**/*"
+ prefix="${app.package}_${app.version}"/>
+ <tarfileset dir="${lib.dir}"
+ includes="swt-gtk.jar"
+ fullpath="${app.package}_${app.version}/swt.jar" />
+ <tarfileset file="resources/plaf/linux/ist"
+ fullpath="${app.package}_${app.version}/ist"
+ filemode="0755"/>
+ </tar>
+
+ <!-- tar x86_64 -->
+ <tar destfile="${dist.dir}/${app.package}_${app.version}_linux_x86_64.tar.gz"
+ compression="gzip">
+ <tarfileset dir="${build.dir}/packages/linux"
+ includes="**/*"
+ prefix="${app.package}_${app.version}"/>
+ <tarfileset dir="${lib.dir}"
+ includes="swt-gtk-64.jar"
+ fullpath="${app.package}_${app.version}/swt.jar" />
+ <tarfileset file="resources/plaf/linux/ist"
+ fullpath="${app.package}_${app.version}/ist"
+ filemode="0755"/>
+ </tar>
+ </target>
+
+ <target name="package-windows" depends="dist">
+
+ <!-- Make windows package -->
+ <mkdir dir="${build.dir}/packages/windows" />
+ <jsmooth project="resources/plaf/windows/ist.jsmooth"
+ skeletonroot="resources/plaf/windows/skeletons" />
+ <move file="resources/plaf/windows/ist.exe"
+ todir="${build.dir}/packages/windows" />
+ <copy todir="${build.dir}/packages/windows" >
+ <fileset dir="dist" includes="istapp.jar"/>
+ <fileset dir="lib" includes="istapi.jar"/>
+ <fileset dir="lib" includes="jface.jar"/>
+ <fileset dir="lib" includes="swt-win.jar"/>
+ <fileset dir="." includes="resources/config/**/*"/>
+ <fileset dir="." includes="resources/icons/**/*"/>
+ <fileset dir="." includes="resources/doc/**/*"/>
+ </copy>
+ <copy todir="${build.dir}/packages/windows" >
+ <fileset dir="." >
+ <include name="plugins/**/*" />
+ <exclude name="plugins/**/*.so" />
+ <exclude name="plugins/**/*.jnilib" />
+ <exclude name="plugins/**/*.dynlib" />
+ </fileset>
+ </copy>
+
+ <!-- Build an installer -->
+ <copy todir="build/temp"
+ file="resources/plaf/windows/install.xml" overwrite="true">
+ <filterset>
+ <filter token="name" value="${app.name}" />
+ <filter token="version" value="${app.version}" />
+ <filter token="author" value="${app.author}" />
+ <filter token="email" value="${app.email}" />
+ <filter token="website" value="${app.website}" />
+ </filterset>
+ </copy>
+ <izpack input="build/temp/install.xml"
+ output="dist/${app.package}_${app.version}_win32.jar"
+ installerType="standard" basedir="." />
+ <copy todir="build/temp"
+ file="resources/plaf/windows/installer.jsmooth" overwrite="true">
+ <filterset>
+ <filter token="input"
+ value="../../dist/${app.package}_${app.version}_win32.jar" />
+ <filter token="output"
+ value="../../dist/${app.package}_${app.version}_win32.exe" />
+ <filter token="icon"
+ value="../../resources/plaf/windows/installer.ico" />
+ </filterset>
+ </copy>
+ <jsmooth project="build/temp/installer.jsmooth"
+ skeletonroot="resources/plaf/windows/skeletons" />
+ </target>
+
+
+
+ <target name="package" depends="package-mac,package-linux,package-debian,package-windows">
+ </target>
+
+ <target name="default" depends="clean,package">
+ </target>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="lib" path="._swt-cocoa-32.jar"/>
+ <classpathentry kind="lib" path="._swt-cocoa-64.jar"/>
+ <classpathentry kind="lib" path="istapi-src.jar"/>
+ <classpathentry kind="lib" path="istapi.jar"/>
+ <classpathentry kind="lib" path="jface.jar"/>
+ <classpathentry kind="lib" path="swt-cocoa-32.jar"/>
+ <classpathentry kind="lib" path="swt-cocoa-64.jar"/>
+ <classpathentry kind="lib" path="swt-gtk-64.jar"/>
+ <classpathentry kind="lib" path="swt-gtk.jar"/>
+ <classpathentry kind="lib" path="swt-win.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>TestAnn</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="BPT Plugin" />
+ <meta name="description" value="Binary Partition Tree Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.BptSegmenter" />
+ <jar file="bptplugin.jar" />
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="IGC Plugin" />
+ <meta name="description" value="Interactive Graph Cuts Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.IgcSegmenter" />
+ <jar file="igcplugin.jar" />
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="SIOX Plugin" />
+ <meta name="description" value="Simple Interactive Object Extraction Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.SioxSegmenter" />
+ <jar file="sioxplugin.jar" />
+ <jar file="sioxapi.jar" />
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="SRG Plugin" />
+ <meta name="description" value="Seeded Region Growing Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.SrgSegmenter" />
+ <jar file="srgplugin.jar" />
+</plugin>
--- /dev/null
+app.name=Interactive Segmentation Tool
+app.package=ist
+app.version=1.3.4
+app.author=Kevin McGuinness
+app.email=kevin.mcguinness@eeng.dcu.ie
+app.website=http://kspace.cdvp.dcu.ie/public/interactive-segmentation
+app.synopsis=The CDVP Interactive Segmentation Tool
+app.main=ie.dcu.apps.ist.Main
+
+# Compilation properties
+java.debug=off
+java.source.version=1.5
+java.target.version=1.5
+
--- /dev/null
+
+OpenAction.dialog.text=Open an image or saved context
+OpenAction.dialog.filter.exts=*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.ctx
+OpenAction.dialog.filter.text=Image and Context Files
+
+SaveAction.dialog.text=Save Segmentation Context
+SaveAction.dialog.filter.exts=*.ctx
+SaveAction.dialog.filter.text=Segmentation Context Files
+
+ExportViewAction.dialog.text=Export View As Image
+ExportViewAction.dialog.filter.exts=*.jpg;*.jpeg;*.png;*.gif;*.bmp;
+ExportViewAction.dialog.filter.text=Image Files
+
+OpenExperimentAction.dialog.text=Open an experiment file
+OpenExperimentAction.dialog.filter.exts=*.exp
+OpenExperimentAction.dialog.filter.text=Experiment Files
+
+# Actions
+action.OpenAction.text=&Open...@Command+O
+action.OpenAction.image=file:resources/icons/open.png
+action.OpenAction.tooltip=Open image
+
+action.SaveAction.text=&Save@Command+S
+action.SaveAction.image=file:resources/icons/save.png
+action.SaveAction.tooltip=Save segmentation context
+
+action.SaveAsAction.text=S&ave As...@Command+Shift+S
+action.SaveAsAction.image=file:resources/icons/save-as.png
+action.SaveAsAction.tooltip=Save segmentation context as...
+
+action.ExportViewAction.text=&Current View...@Command+Alt+V
+action.ExportViewAction.image=file:resources/icons/image.png
+action.ExportViewAction.tooltip=Export view as image
+
+action.ExportImageMapAction.text=&HTML Image Map...@Command+Alt+H
+action.ExportImageMapAction.image=file:resources/icons/html.png
+action.ExportImageMapAction.tooltip=Export objects as HTML image map
+
+action.ExportTransparentPNGAction.text=Export Transparent &PNG...@Command+Alt+P
+action.ExportTransparentPNGAction.image=file:resources/icons/image.png
+action.ExportTransparentPNGAction.tooltip=Export a PNG of the current segmentation with a transparent background
+
+action.ImportAction.text=&Import...
+action.ImportAction.image=file:resources/icons/import.png
+action.ImportAction.tooltip=Import images
+
+action.PrintAction.text=&Print@Command+P
+action.PrintAction.image=file:resources/icons/print.png
+action.PrintAction.tooltip=Print view
+
+action.ExitAction.text=E&xit@Command+Q
+action.ExitAction.image=file:resources/icons/exit.png
+action.ExitAction.tooltip=Quit the application
+
+action.UndoAction.text=&Undo@Command+Z
+action.UndoAction.image=file:resources/icons/undo.png
+action.UndoAction.tooltip=Undo last markup
+
+action.RedoAction.text=&Redo@Command+Y
+action.RedoAction.image=file:resources/icons/redo.png
+action.RedoAction.tooltip=Redo markup
+
+action.CopyAction.text=&Copy@Command+C
+action.CopyAction.image=file:resources/icons/copy.png
+action.CopyAction.tooltip=Copy view to clipboard as image
+
+action.PreferencesAction.text=&Preferences...@Command+,
+action.PreferencesAction.image=file:resources/icons/preferences.png
+action.PreferencesAction.tooltip=Change application settings
+
+action.AddFilesAction.text=&Add Files...@Command+A
+action.AddFilesAction.image=file:resources/icons/add.png
+action.AddFilesAction.tooltip=Add image files to project
+
+action.RemoveFilesAction.text=&Remove Files@Command+R
+action.RemoveFilesAction.image=file:resources/icons/remove.png
+action.RemoveFilesAction.tooltip=Remove selected image files from project
+
+action.PreviousAction.text=&Previous@Command+[
+action.PreviousAction.image=file:resources/icons/previous.png
+action.PreviousAction.tooltip=Move to previous image in directory
+
+action.NextAction.text=&Next@Command+]
+action.NextAction.image=file:resources/icons/next.png
+action.NextAction.tooltip=Move to next image in directory
+
+action.HelpAction.text=&Help Contents...@Command+F1
+action.HelpAction.image=file:resources/icons/help.png
+action.HelpAction.tooltip=Show application help browser
+action.HelpAction.helpURL=http://kspace.cdvp.dcu.ie/public/interactive-segmentation/doc/help.html
+
+action.AboutAction.text=&About...
+action.AboutAction.image=file:resources/icons/about.png
+action.AboutAction.tooltip=About the application
+action.AboutAction.aboutImage=file:resources/icons/about-box.png
+
+action.OpenExperimentAction.text=Open &Experiment...
+action.OpenExperimentAction.image=file:resources/icons/experiment.png
+action.OpenExperimentAction.tooltip=Open an experiment file and switch to experiment mode
+
+action.ConfigureSegmenterAction.text=&Configure Segmenter...@Command+Shift+C
+action.ConfigureSegmenterAction.image=file:resources/icons/preferences.png
+action.ConfigureSegmenterAction.tooltip=Configure segmenter parameters
+
+action.SelectSegmenterAction.NA=Algorithm not available on this platform
+
+
+
--- /dev/null
+
+OpenAction.dialog.text=Open an image or saved context
+OpenAction.dialog.filter.exts=*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.ctx
+OpenAction.dialog.filter.text=Image and Context Files
+
+SaveAction.dialog.text=Save Segmentation Context
+SaveAction.dialog.filter.exts=*.ctx
+SaveAction.dialog.filter.text=Segmentation Context Files
+
+ExportViewAction.dialog.text=Export View As Image
+ExportViewAction.dialog.filter.exts=*.jpg;*.jpeg;*.png;*.gif;*.bmp;
+ExportViewAction.dialog.filter.text=Image Files
+
+OpenExperimentAction.dialog.text=Open an experiment file
+OpenExperimentAction.dialog.filter.exts=*.exp
+OpenExperimentAction.dialog.filter.text=Experiment Files
+
+# Actions
+action.OpenAction.text=&Open...@Ctrl+O
+action.OpenAction.image=file:resources/icons/open.png
+action.OpenAction.tooltip=Open image
+
+action.SaveAction.text=&Save@Ctrl+S
+action.SaveAction.image=file:resources/icons/save.png
+action.SaveAction.tooltip=Save segmentation context
+
+action.SaveAsAction.text=S&ave As...@Ctrl+Shift+S
+action.SaveAsAction.image=file:resources/icons/save-as.png
+action.SaveAsAction.tooltip=Save segmentation context as...
+
+action.ExportViewAction.text=&Current View...@Ctrl+Shift+V
+action.ExportViewAction.image=file:resources/icons/image.png
+action.ExportViewAction.tooltip=Export view as image
+
+action.ExportImageMapAction.text=&HTML Image Map...@Ctrl+Shift+H
+action.ExportImageMapAction.image=file:resources/icons/html.png
+action.ExportImageMapAction.tooltip=Export objects as HTML image map
+
+action.ExportTransparentPNGAction.text=Export Transparent &PNG...@Ctrl+Shift+P
+action.ExportTransparentPNGAction.image=file:resources/icons/image.png
+action.ExportTransparentPNGAction.tooltip=Export a PNG of the current segmentation with a transparent background
+
+
+action.ImportAction.text=&Import...
+action.ImportAction.image=file:resources/icons/import.png
+action.ImportAction.tooltip=Import images
+
+action.PrintAction.text=&Print@Ctrl+P
+action.PrintAction.image=file:resources/icons/print.png
+action.PrintAction.tooltip=Print view
+
+action.ExitAction.text=E&xit@Ctrl+Q
+action.ExitAction.image=file:resources/icons/exit.png
+action.ExitAction.tooltip=Quit the application
+
+action.UndoAction.text=&Undo@Ctrl+Z
+action.UndoAction.image=file:resources/icons/undo.png
+action.UndoAction.tooltip=Undo last markup
+
+action.RedoAction.text=&Redo@Ctrl+Y
+action.RedoAction.image=file:resources/icons/redo.png
+action.RedoAction.tooltip=Redo markup
+
+action.CopyAction.text=&Copy@Ctrl+C
+action.CopyAction.image=file:resources/icons/copy.png
+action.CopyAction.tooltip=Copy view to clipboard as image
+
+action.PreferencesAction.text=&Preferences...
+action.PreferencesAction.image=file:resources/icons/preferences.png
+action.PreferencesAction.tooltip=Change application settings
+
+action.AddFilesAction.text=&Add Files...@Ctrl+A
+action.AddFilesAction.image=file:resources/icons/add.png
+action.AddFilesAction.tooltip=Add image files to project
+
+action.RemoveFilesAction.text=&Remove Files@Ctrl+R
+action.RemoveFilesAction.image=file:resources/icons/remove.png
+action.RemoveFilesAction.tooltip=Remove selected image files from project
+
+action.PreviousAction.text=&Previous@Ctrl+[
+action.PreviousAction.image=file:resources/icons/previous.png
+action.PreviousAction.tooltip=Move to previous image in directory
+
+action.NextAction.text=&Next@Ctrl+]
+action.NextAction.image=file:resources/icons/next.png
+action.NextAction.tooltip=Move to next image in directory
+
+action.HelpAction.text=&Help Contents...@Ctrl+F1
+action.HelpAction.image=file:resources/icons/help.png
+action.HelpAction.tooltip=Show application help browser
+action.HelpAction.helpURL=http://kspace.cdvp.dcu.ie/public/interactive-segmentation/doc/help.html
+
+action.AboutAction.text=&About...
+action.AboutAction.image=file:resources/icons/about.png
+action.AboutAction.tooltip=About the application
+action.AboutAction.aboutImage=file:resources/icons/about-box.png
+
+action.OpenExperimentAction.text=Open &Experiment...
+action.OpenExperimentAction.image=file:resources/icons/experiment.png
+action.OpenExperimentAction.tooltip=Open an experiment file and switch to experiment mode
+
+action.ConfigureSegmenterAction.text=&Configure Segmenter...@Ctrl+Shift+C
+action.ConfigureSegmenterAction.image=file:resources/icons/preferences.png
+action.ConfigureSegmenterAction.tooltip=Configure segmenter parameters
+
+action.SelectSegmenterAction.NA=Algorithm not available on this platform
--- /dev/null
+
+AppRecentMenu.icon=file:resources/icons/open-recent.png
+ExportMenu.icon=file:resources/icons/export.png
+
+# Preferences Dialog
+PrefsDialog.title=Preferences
+PrefsDialog.application=Application
+PrefsDialog.status=Status Area
+PrefsDialog.view=View Preferences
+PrefsDialog.experiment=Experiment Mode
+PrefsDialog.general.tab=General
+PrefsDialog.close.text=&Close
+PrefsDialog.reset.text=&Reset
+PrefsDialog.enable-feedback.text=Show position and color in the status area
+PrefsDialog.foreground-color.text=Foreground color for markup
+PrefsDialog.background-color.text=Background color for markup
+PrefsDialog.experiment-embedded.text=Show experiment panel embedded in main window
+PrefsDialog.confirm-exit.text=Confirm exit when application is closed
+
+# Experiment Panel
+ExperimentPanel.description.title=Task Description
+ExperimentPanel.timeout-message=The time allocated for this task is up!
+ExperimentPanel.experiment-complete-message=The experiment has been completed
+ExperimentPanel.button.text.start=Start
+ExperimentPanel.button.text.finish=Finish
--- /dev/null
+
+AppRecentMenu.icon=file:resources/icons/open-recent.png
+ExportMenu.icon=file:resources/icons/export.png
+
+# Preferences Dialog
+PrefsDialog.title=Preferences
+PrefsDialog.application=Application
+PrefsDialog.status=Status Area
+PrefsDialog.view=View Preferences
+PrefsDialog.experiment=Experiment Mode
+PrefsDialog.general.tab=General
+PrefsDialog.close.text=&Close
+PrefsDialog.close.icon=file:resources/icons/close.png
+PrefsDialog.reset.text=&Reset
+PrefsDialog.reset.icon=file:resources/icons/reset.png
+PrefsDialog.enable-feedback.text=Show position and color in the status area
+PrefsDialog.foreground-color.text=Foreground color for markup
+PrefsDialog.background-color.text=Background color for markup
+PrefsDialog.experiment-embedded.text=Show experiment panel embedded in main window
+PrefsDialog.confirm-exit.text=Confirm exit when application is closed
+
+# Experiment Panel
+ExperimentPanel.description.title=Task Description
+ExperimentPanel.timeout-message=The time allocated for this task is up!
+ExperimentPanel.experiment-complete-message=The experiment has been completed
+ExperimentPanel.button.text.start=Start
+ExperimentPanel.button.text.finish=Finish
--- /dev/null
+SegmentationView.Action.Foreground.text=Markup Foreground
+SegmentationView.Action.Foreground.tooltip=Markup Foreground Pixels
+SegmentationView.Action.Foreground.image=file:resources/icons/foreground.png
+SegmentationView.Action.Background.text=Markup Background
+SegmentationView.Action.Background.tooltip=Markup Background Pixels
+SegmentationView.Action.Background.image=file:resources/icons/background.png
+SegmentationView.Action.ZoomIn.text=Zoom In
+SegmentationView.Action.ZoomIn.tooltip=Zoom In
+SegmentationView.Action.ZoomIn.image=file:resources/icons/zoom-in.png
+SegmentationView.Action.ZoomOut.text=Zoom Out
+SegmentationView.Action.ZoomOut.tooltip=Zoom Out
+SegmentationView.Action.ZoomOut.image=file:resources/icons/zoom-out.png
+SegmentationView.Action.ZoomOriginal.text=Zoom Original
+SegmentationView.Action.ZoomOriginal.tooltip=Zoom to Original Size
+SegmentationView.Action.ZoomOriginal.image=file:resources/icons/zoom-original.png
+SegmentationView.Action.ZoomBestFit.text=Zoom Best Fit
+SegmentationView.Action.ZoomBestFit.tooltip=Zoom to Best Fit
+SegmentationView.Action.ZoomBestFit.image=file:resources/icons/zoom-best-fit.png
+SegmentationView.Action.Repaint.text=Refresh
+SegmentationView.Action.Repaint.tooltip=Refresh and repaint view
+SegmentationView.Action.Repaint.image=file:resources/icons/refresh.png
+SegmentationView.Action.Undo.text=&Undo@Ctrl+Z
+SegmentationView.Action.Undo.tooltip=Undo last markup
+SegmentationView.Action.Undo.image=file:resources/icons/undo.png
+SegmentationView.Action.Redo.text=&Redo@Ctrl+Y
+SegmentationView.Action.Redo.tooltip=Redo markup
+SegmentationView.Action.Redo.image=file:resources/icons/redo.png
+SegmentationView.Action.Clear.text=&Clear@Ctrl+DELETE
+SegmentationView.Action.Clear.tooltip=Clear all markup
+SegmentationView.Action.Clear.image=file:resources/icons/clear.png
+SegmentationView.Action.SetBrushSize.text=Set &Brush@Ctrl+B
+SegmentationView.Action.SetBrushSize.tooltip=Set the markup brush size
+SegmentationView.Action.SetBrushSize.image=file:resources/icons/brush.png
+SegmentationView.Action.AutoApply.text=Auto Segment@Ctrl+Shift+A
+SegmentationView.Action.AutoApply.tooltip=Toggle auto segment
+SegmentationView.Action.AutoApply.image=file:resources/icons/auto.png
+SegmentationView.Action.Apply.text=Segment@Ctrl+Shift+S
+SegmentationView.Action.Apply.tooltip=Apply new markup now
+SegmentationView.Action.Apply.image=file:resources/icons/apply.png
+SegmentationView.Action.SetPainter.text=View :
+SegmentationView.Action.SetPainter.tooltip=Select how to view the image/segmentation.
+SegmentationView.Action.SetPainter.image=
+SegmentationView.Action.SegmenterOptions.text=Configure Segmenter@Ctrl+Shift+C
+SegmentationView.Action.SegmenterOptions.tooltip=Configure the segmenter
+SegmentationView.Action.SegmenterOptions.image=file:resources/icons/preferences.png
+SegmentationView.Action.SetLabel.text=Annotate :
+SegmentationView.Action.SetLabel.tooltip=Annotate the segmented piece.
+SegmentationView.Action.SetLabel.image=
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head><meta content="text/html; charset=ISO-8859-1" http-equiv="content-type"><title>Interactive Segmentation Tool - User Guide</title><style type="text/css">
+.guicommand {
+ font-family: "Courier New",Courier,monospace;
+ font-weight: normal;
+ color: #990100;
+}
+.guishortcut {
+ font-family: "Courier New",Courier,monospace;
+ color: #666666;
+ font-style: italic;
+}
+.section {
+ font-weight: bold;
+ color: #404040;
+}
+.all {
+ margin: 0px;
+ padding: 10px 15px 50px;
+ background-color: white;
+}
+body {
+ margin: 0px 10px;
+ padding: 0px;
+ border-right-width: 1px;
+ border-left-width: 1px;
+ font-family: Arial,Helvetica,sans-serif;
+ border-right-style: dotted;
+ border-left-style: dotted;
+ background-color: #eff0ea;
+}
+h1 {
+ margin: 0px;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ padding-top: 10px;
+ padding-bottom: 15px;
+ text-align: center;
+ color: #404040;
+}
+h2 {
+ text-align: center;
+ color: #404040;
+}
+h3 {
+ color: #404040;
+}
+li {
+ padding-bottom: 10px;
+}
+.footer {
+ font-size: x-small;
+ text-align: center;
+}
+
+</style></head>
+<body>
+<div class="all">
+<h1>Interactive Segmentation Tool</h1>
+<h2>User Guide</h2>
+<h3>Usage</h3>
+In this section I will outline how to perform typical operations using
+the tool, such as opening images, extracting objects and exporting
+segmentation masks.<br>
+<br>
+<span class="section">Open an Image</span> <br>
+To open an image, select <span class="guicommand">File->Open</span><span class="guishortcut"></span>. Recently open files can
+be accessed using the <span class="guicommand">File->Open
+Recent</span> menu. Alternatively, an image file can be dragged
+from your file system and dropped into the application to open it.
+Currently images of type JPEG, PNG, GIF, and BMP are supported.<br>
+<br>
+<span class="section">Select a Segmenter</span><br>
+There are several different segmenters available in the tool. These can
+be selected from the <span class="guicommand">Tools</span>
+menu<span class="guicommand"></span>. To select one,
+click on it.<br>
+<br>
+<span class="section">Segmenting the Image </span><br class="section">
+Once you have an image open and a segmenter selected, you can segment
+the image by drawing scribbles on the image. First, move the mouse over
+the object you want to extract. Left click and drag inside the object
+to draw some scribbles inside the object. Now, move the mouse outside
+the object to some background pixels. Using the right mouse button,
+scribble outside the object to select some background. If using Mac
+OSX, or if you do not have a right mouse button (Graphics Tablet etc),
+you can use <span style="font-family: monospace;">Ctrl</span>
+and click instead of right click.<br>
+<br>
+The image will be segmented and the foreground will be brightened and
+the background darkened. If you are not happy with the segmentation,
+you can add more foreground and background scribbles.<br>
+<br>
+<span class="section">Undo, Redo and Clear</span><br>
+If you make a mistake, it can be undone by selecting <span class="guicommand">Edit->Undo</span><span style="font-weight: bold;"><span style="font-weight: bold;"></span> </span>or
+clicking the undo <img style="width: 16px; height: 16px;" alt="Undo Button" src="icons/undo.png"> button in the
+toolbar. To redo, select <span class="guicommand">Edit->Redo </span><span style="font-weight: bold;"></span>or click the
+redo <img style="width: 16px; height: 16px;" alt="Redo Button" src="icons/redo.png"> button. To
+remove all forground and background markup, click on the clear <img style="width: 16px; height: 16px;" alt="Clear Button" src="icons/reset.png"> button in the toolbar.<br>
+<br>
+<span class="section">Zooming</span><br>
+To get a closer look at the image, the toolbar can also be
+used to zoom-in on the image. To zoom-in by 10% use the <img style="width: 16px; height: 16px;" alt="Zoom In" src="icons/zoom-in.png"> button. To zoom-out by 10% use
+the <img style="width: 16px; height: 16px;" alt="Zoom Out" src="icons/zoom-out.png"> button. To
+zoom the image such that it fits into the window without needing
+scrollbars, use the <img style="width: 16px; height: 16px;" alt="Zoom Best Fit" src="icons/zoom-best-fit.png">
+button. To restore the image to its original size use the <img style="width: 16px; height: 16px;" alt="Zoom Original" src="icons/zoom-original.png"> button.<br>
+<br>
+<span class="section">Brush Size</span><br>
+To change the brush size, use the <img style="width: 16px; height: 16px;" alt="Paintbrush" src="icons/brush.png"> button to show the brush size
+chooser. It is often useful to use a bigger brush to make markings more
+visible or to quickly mark up more pixels.<br>
+<br>
+<span class="section">Views</span><br>
+It is sometimes useful to view the segmentation results in different
+ways. The default selected view is called "Combined" and shows the
+image, it's segmentation highlighted and the scribbles all overlayed on
+the same image. By using the drop down menu, you can select a different
+view:<br>
+<ul>
+<li><span style="font-style: italic;">Combined</span>:
+Shows the image with foreground areas brightened and background areas
+darkened. The foreground and background scribbles are overlayed.</li>
+<li><span style="font-style: italic;">Original:</span>
+Shows the original image, without any segmentation or scribbles
+overlayed.</li>
+<li><span style="font-style: italic;">Markup:</span>
+Shows only the foreground and background markup (scribbles).</li>
+<li><span style="font-style: italic;">Mask:</span>
+Shows the segmentation mask. That is, an image such that the foreground
+area is completely white and the background area completely black.</li>
+<li><span style="font-style: italic;">Foreground
+Only:</span> Removes all background elements and displays only
+the foreground object.</li>
+<li><span style="font-style: italic;">Outline
+Overlayed: </span>Displays and outline of the object overlayed
+onto the original image.</li>
+</ul>
+<strong>Note:</strong><span style="font-weight: bold;">
+</span>Only the combined view displays the foreground and
+background markup (scribbles). So if you are wondering where the
+scribbles disappeared to, select the combined view to make them visible.<br>
+<br class="section">
+<span class="section">Saving and Exporting</span><br>
+To save an work in progress segmentation, select <span class="guicommand">File->Save</span> or <span class="guicommand">File->Save As</span>. This
+will save the current segmentation and markup as a context file (<span style="font-family: monospace;">.ctx</span>). When
+finished segmenting an image, you may want to export the view as an
+image. To export the current view select <span class="guicommand">File->Export</span>.
+For example, if you wanted to save the segmentation mask, select the
+mask view and then <span class="guicommand">File->Export</span>.
+<br>
+<br class="section">
+<span class="section">Configuring a Segmenter</span><br>
+Some segmenters have parameters and options that can are configurable.
+To configure the selected segmenter, select <span class="guicommand">Tools->Configure
+Segmenter</span>, or click the <img style="width: 16px; height: 16px;" alt="Configure" src="icons/configure.png"> button in the toolbar.<br>
+<br class="section">
+<span class="section">Navigating Directories of Images</span><br>
+To quickly jump to the next or previous image in the current directory
+(the one containing the open image), use the <span class="guicommand">Go->Next</span> and <span class="guicommand">Go->Previous</span> menu
+items.<br>
+<br>
+<span class="section">Experiments</span><br class="section">
+Experiment files can be opened using the <span class="guicommand">Tools->Open
+Experiment</span> menu item.<br><br><br>
+<div class="footer">Kevin McGuinness 2008
+</div>
+</div>
+</body></html>
\ No newline at end of file
--- /dev/null
+ist (1.3.4-1) stable; urgency=low
+
+ * Support for Mac OS X leopard (64 bit JVM)
+
+ * Fixed bug in transparent PNG export on Windows platforms
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Thur, 27 Mar 2010 19:10:00 +0000
+
+ist (1.3.3-1) stable; urgency=low
+
+ * Updated API documentation
+
+ * Larger dialog for segmenter options
+
+ * Linux plugin builds now use GCC-4.1 for better compatibility
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Wed, 7 Oct 2009 19:00:00 +0000
+
+ist (1.3.2-1) stable; urgency=low
+
+ * Moved more components to the API layer. This includes painters and the image
+ control. This allows other apps to share core components.
+
+ * Added export transparent PNG option
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Wed, 7 Oct 2009 18:00:00 +0000
+
+ist (1.3.1-1) stable; urgency=low
+
+ * Updated export HTML feature to export XHTML
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Wed, 2 Sep 2009 18:00:00 +0000
+
+ist (1.3.0-1) stable; urgency=low
+
+ * Major restructuring of application to ease plugin development
+
+ * New version of SWT
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Mon, 24 Aug 2009 18:00:00 +0000
+
+ist (1.2.5-1) stable; urgency=low
+
+ * Added code for automated evaluation
+
+ * Update man page and run templates
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Mon, 9 Mar 2009 16:21:00 +0000
+
+ist (1.2.4-1) stable; urgency=medium
+
+ * Fixed erratic behavior of SIOX segmenter
+
+ * Added a lot of code for automated evaluation
+
+ * Added option to force segmenters to retain all markup pixels in mask.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Fri, 23 Jan 2009 18:00:00 +0000
+
+ist (1.2.3-1) stable; urgency=medium
+
+ * Fixed redo bug (redo was calling undo)
+
+ * Segmentation context does not create an Image unless asked to now. This
+ prevents it initializing the swt Display object unless it needs to, allowing
+ console applications that use the core segmentation stuff to be created
+ without initializing the Display.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Thu, 8 Jan 2009 15:00:00 +0000
+
+ist (1.2.2-1) stable; urgency=low
+
+ * Fixed application name in menu bar on OS X
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 6 Jan 2009 15:00:00 +0000
+
+ist (1.2.1-1) stable; urgency=low
+
+ * Use command key instead of ctrl key for shortcuts on Mac OSX
+
+ * Zip file for app bundle on OS X: it's better supported than tar.gz
+
+ * Modified about box to display SWT version
+
+ * Added standard Command+, binding for preferences on Mac
+
+ * Use small icons in menus on all platforms
+
+ * Removed icons in preference dialog buttons on Mac OS
+
+ * Improved and included several new icons
+
+ * Fixed visual bug on linux: horizontal separators didn't look right in
+ the export dialog.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Wed, 17 Dec 2008 17:00:00 +0000
+
+ist (1.2.0-1) stable; urgency=low
+
+ * New feature: export to HTML image maps integrated. Supports exporting
+ polygons and rectangles for now. Can export several rollover effects using
+ the javascript image swapping technique.
+
+ * Fixed bug on Mac OS X: application did not always exit properly
+
+ * Open browser window to show the HTML page when export completes
+
+ * Option to turn on or off the open browser window behavior
+
+ * Best practices: use var for global image preloads.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 16 Dec 2008 21:37:00 +0000
+
+ist (1.1.0-1) stable; urgency=low
+
+ * Several new features are planned for introduction, so the version number
+ has been incremented to 1.1
+
+ * Changed context file format. This is now stored as a zip archive including
+ the image, mask and annotations, so that it is more "self-contained". The
+ new file format is NOT compatible with the old one.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 29 Feb 2008 12:25:00 +0000
+
+ist (1.0.6-1) stable; urgency=low
+
+ * Added startup check for gdiplus.dll for platforms where it's needed but not
+ installed by default. Program will now show a useful message and exit gracefully
+ instead of spewing stack traces on the console and behaving oddly.
+
+ * Fixed bug that caused the auto-apply toggle button to be always disabled
+ after first image load.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 28 Feb 2008 10:36:00 +0000
+
+ist (1.0.5-1) stable; urgency=medium
+
+ * Fixed bug that caused the brush control (and some other toolbar buttons) to be
+ disabled after an experiment was cancelled.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 12 Feb 2008 13:25:00 +0000
+
+ist (1.0.4-1) stable; urgency=low
+
+ * Updated the integrated help file (help.html)
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 28 Jan 2008 16:40:00 +0000
+
+ist (1.0.3-1) stable; urgency=low
+
+ * MacOSX PowerPC now supported
+
+ * Mostly non-functional changes on Linux, refactoring and the like
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 25 Jan 2008 16:08:00 +0000
+
+ist (1.0.2-1) stable; urgency=low
+
+ * Application icon added
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 16 Jan 2008 19:43:00 +0000
+
+ist (1.0.1-1) stable; urgency=low
+
+ * Created debian package
+
+ * Support added for 64 bit linux
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 15 Jan 2008 19:43:00 +0000
--- /dev/null
+ist
+
+Copyright: Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie>
+ Center for Digital Video Processing
+ Dublin City University
+
+2008-01-14
+
+The home page of ist is at:
+https://www.kspace.cdvp.dcu.ie/public/interactive-segmentation
--- /dev/null
+# freedesktop.org desktop entry
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Interactive Segmentation Tool
+Comment=Interactively extract objects from images using various algorithms
+Icon=ist
+Exec=ist
+Terminal=false
+Categories=GTK;Graphics
--- /dev/null
+#!/bin/sh
+
+# Setup
+data=@data@
+main=@main@
+jars=@jars@
+
+# Change directory
+cd "${data}"
+
+# This is needed for the browser widget on linux
+if [ -z "${MOZILLA_FIVE_HOME}" ]; then
+ # Check if common locations exist
+ if [ -d "/usr/lib/firefox" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/firefox
+ elif [ -d "/usr/lib/mozilla-firefox" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/mozilla-firefox
+ elif [ -d "/usr/lib/mozilla" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/mozilla
+ fi
+fi
+
+# Set library path
+if [ -n "${MOZILLA_FIVE_HOME}" ]; then
+ export LD_LIBRARY_PATH=${MOZILLA_FIVE_HOME}:${LD_LIBRARY_PATH}
+fi
+
+# Launch
+if [ -n "${JAVA_HOME}" ]; then
+ ${JAVA_HOME}/bin/java -classpath "${jars}" ${main} $*
+else
+ java -classpath "${jars}" ${main} $*
+fi
+
+cd "$OLDPWD"
+
--- /dev/null
+.TH "ist" "1" "1.3.3" "Kevin McGuinness" "Graphics"
+.SH "NAME"
+.LP
+ist \- Interactive Segmentation Tool
+.SH "SYNTAX"
+.LP
+ist [options]
+
+.SH "DESCRIPTION"
+.LP
+Launches the interactive segmentation tool graphical user interface
+
+.SH "ENVIRONMENT VARIABLES"
+.LP
+.TP
+\fBMOZILLA_FIVE_HOME\fP
+Specifies the location of the mozilla 5 browser
+(firefox etc.). This is usually something like
+/usr/lib/firefox
+
+.SH "EXAMPLES"
+.LP
+To run this program the standard way type:
+.LP
+$ ist
+
+.SH "AUTHORS"
+.LP
+Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie>
--- /dev/null
+#!/bin/sh
+
+sudo update-desktop-database
+sudo gtk-update-icon-cache /usr/share/icons/hicolor
--- /dev/null
+#!/bin/sh
+
+sudo update-desktop-database
+sudo gtk-update-icon-cache /usr/share/icons/hicolor
--- /dev/null
+#!/bin/sh
+
+jars=istapp.jar:istapi.jar:swt.jar:jface.jar
+main=ie.dcu.apps.ist.Main
+
+# Change directory
+cd "`dirname "$0"`"
+
+# This is needed for the SWT browser widget on linux
+if [ -z "${MOZILLA_FIVE_HOME}" ]; then
+
+ # Check if common locations exist
+ if [ -d "/usr/lib/firefox" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/firefox
+ elif [ -d "/usr/lib/mozilla-firefox" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/mozilla-firefox
+ elif [ -d "/usr/lib/mozilla" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/mozilla
+ fi
+
+fi
+
+# Set library path
+if [ -n "${MOZILLA_FIVE_HOME}" ]; then
+ export LD_LIBRARY_PATH=${MOZILLA_FIVE_HOME}:${LD_LIBRARY_PATH}
+fi
+
+if [ -n "${JAVA_HOME}" ]; then
+ ${JAVA_HOME}/bin/java -classpath "${jars}" ${main} $*
+else
+ java -classpath "${jars}" ${main} $*
+fi
+
+cd "$OLDPWD"
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>CFBundleName</key>
+ <string>@name@</string>
+ <key>CFBundleShortVersionString</key>
+ <string>@version@</string>
+ <key>CFBundleGetInfoString</key>
+ <string>@synopsis@ (v@version@)</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <string>false</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleExecutable</key>
+ <string>@exename@</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleIconFile</key>
+ <string>@icons@</string>
+ </dict>
+</plist>
--- /dev/null
+#!/bin/sh
+
+# What kinda mac are we on
+CPU=`uname -p`
+
+# Absolute path to where the app was launched from
+APP_PACKAGE="`dirname "$0"`/../.."
+APP_PACKAGE="`cd "${APP_PACKAGE}"; pwd`"
+
+# Locations of resources, jars, and libs
+LIB_DIR="Libraries/${CPU}"
+
+# Move to the resource directory
+cd "${APP_PACKAGE}/Contents/Resources"
+
+# Establish whether we should use the 32 bit or 64 bit cocoa library
+SWT_LIBNAME=swt-cocoa-32.jar
+java -version 2>&1 | grep "64-Bit" > /dev/null
+if [ "$?" -eq "0" ]; then
+ SWT_LIBNAME=swt-cocoa-64.jar
+fi
+
+echo "SWT Library: ${SWT_LIBNAME}"
+
+# Launch app
+java \
+ -XstartOnFirstThread \
+ -classpath "Java/istapp.jar:Java/istapi.jar:Java/jface.jar:Java/${SWT_LIBNAME}" \
+ -Dorg.eclipse.swt.internal.carbon.noFocusRing \
+ -Dorg.eclipse.swt.internal.carbon.smallFonts \
+ @appmain@ $*
+
+# Go back to the old working directory
+cd "${OLDPWD}"
--- /dev/null
+<?xml version="1.0" encoding="iso-8859-1" standalone="yes" ?>
+
+<!--
+ IzPack install file for the Interactive Segmentation Tool
+-->
+
+<installation version="1.0">
+
+ <!-- The info section. -->
+ <info>
+ <appname>@name@</appname>
+ <appversion>@version@</appversion>
+ <authors>
+ <author name="@author@" email="@email@"/>
+ </authors>
+ <url>@website@</url>
+ <javaversion>1.5</javaversion>
+ </info>
+
+ <!-- The gui preferences indication. -->
+ <guiprefs width="640" height="480" resizable="no"/>
+
+ <!-- The locale section. -->
+ <locale>
+ <langpack iso3="eng"/>
+ </locale>
+
+ <!-- The resources section. -->
+ <resources>
+ <res src="resources/plaf/windows/shortcuts.xml" id="shortcutSpec.xml"/>
+ </resources>
+
+ <!-- Shell-link for windows. -->
+ <native type="izpack" name="ShellLink.dll" />
+
+ <!-- The panels section. -->
+ <panels>
+ <panel classname="HelloPanel"/>
+ <panel classname="TargetPanel"/>
+ <panel classname="PacksPanel"/>
+ <panel classname="InstallPanel"/>
+ <panel classname="ShortcutPanel"/>
+ <panel classname="FinishPanel"/>
+ </panels>
+
+ <!-- The packs section. -->
+ <packs>
+ <pack name="Base" required="yes">
+ <description> Base installation files </description>
+ <fileset dir="build/packages/windows" targetdir="${INSTALL_PATH}" >
+ <include name="**/*" />
+ </fileset>
+ </pack>
+ </packs>
+
+</installation>
--- /dev/null
+<?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>
+ <jarLocation>@input@</jarLocation>
+ <iconLocation>@icon@</iconLocation>
+ <currentDirectory>${EXECUTABLEPATH}</currentDirectory>
+ <embeddedJar>true</embeddedJar>
+ <executableName>@output@</executableName>
+ <initialMemoryHeap>-1</initialMemoryHeap>
+ <mainClassName>com.izforge.izpack.installer.Installer</mainClassName>
+ <maximumMemoryHeap>-1</maximumMemoryHeap>
+ <maximumVersion></maximumVersion>
+ <minimumVersion>1.5</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>Debug</key>
+ <value>0</value>
+ </skeletonProperties>
+</jsmoothproject>
--- /dev/null
+<?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>
+ <classPath>istapp.jar</classPath>
+ <classPath>istapi.jar</classPath>
+ <classPath>swt-win.jar</classPath>
+ <classPath>jface.jar</classPath>
+ <currentDirectory>${EXECUTABLEPATH}</currentDirectory>
+ <embeddedJar>false</embeddedJar>
+ <executableName>ist.exe</executableName>
+ <iconLocation>application.ico</iconLocation>
+ <initialMemoryHeap>-1</initialMemoryHeap>
+ <mainClassName>ie.dcu.apps.ist.Main</mainClassName>
+ <maximumMemoryHeap>-1</maximumMemoryHeap>
+ <minimumVersion>1.5.0</minimumVersion>
+ <skeletonName>Windowed Wrapper</skeletonName>
+ <skeletonProperties>
+ <key>Message</key>
+ <value>This program needs Java to run.
+ Please download it at http://www.java.com</value>
+ </skeletonProperties>
+ <skeletonProperties>
+ <key>Debug</key>
+ <value>0</value>
+ </skeletonProperties>
+</jsmoothproject>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\r
+<shortcuts>\r
+ <skipIfNotSupported/>\r
+ <programGroup defaultName="IST" location="applications"/>\r
+ \r
+ <shortcut\r
+ name="Interactive Segmentation Tool"\r
+ target="$INSTALL_PATH\ist.exe"\r
+ workingDirectory="$INSTALL_PATH"\r
+ description="Launch the Interactive Segmentation Tool"\r
+ initialState="normal"\r
+ programGroup="yes"\r
+ desktop="yes"\r
+ applications="no"\r
+ startMenu="no"\r
+ startup="no"\r
+ >\r
+ <createForPack name="Base"/>\r
+ </shortcut>\r
+\r
+ <shortcut\r
+ name="Uninstall"\r
+ target="${JAVA_HOME}\bin\java.exe"\r
+ commandLine="-jar "$INSTALL_PATH/Uninstaller/uninstaller.jar""\r
+ description="Uninstall Interactive Segmentation Tool"\r
+ initialState="noShow"\r
+ programGroup="yes"\r
+ desktop="no"\r
+ applications="no"\r
+ startMenu="no"\r
+ startup="no"\r
+ >\r
+ <createForPack name="Base"/>\r
+ </shortcut>\r
+\r
+</shortcuts>\r
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_SIMPLEWRAPPER_DESCRIPTION</description>
+<executableName>jwrap.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Windowed Wrapper</shortName>
+<skeletonProperties>
+<description>SKEL_SIMPLEWRAPPER_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_SIMPLEWRAPPER_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_SIMPLEWRAPPER_PROPERTY_URL_DESCRIPTION</description>
+<idName>URL</idName>
+<label>SKEL_SIMPLEWRAPPER_PROPERTY_URL_LABEL</label>
+<type>string</type>
+<value>http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_DESCRIPTION</description>
+<idName>SingleProcess</idName>
+<label>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_SINGLEINSTANCE_DESCRIPTION</description>
+<idName>SingleInstance</idName>
+<label>SKEL_GENERIC_SINGLEINSTANCE</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.swt.SwtUtils;
+
+import java.util.*;
+import java.util.logging.Logger;
+import java.util.prefs.*;
+
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Manages application preferences
+ *
+ * @author Kevin McGuinness
+ */
+public class AppPrefs {
+
+ public interface Keys {
+ public static final String ENABLE_FEEDBACK = "enable-feedback";
+ public static final String FOREGROUND_COLOR = "foreground-color";
+ public static final String BACKGROUND_COLOR = "background-color";
+ public static final String EXPERIMENT_EMBEDDED = "experiment-embedded";
+ public static final String CONFIRM_EXIT = "confirm-exit";
+ }
+
+ public interface SupportedTypes {
+ public static final Class<Integer> INTEGER = Integer.class;
+ public static final Class<Double> DOUBLE = Double.class;
+ public static final Class<Float> FLOAT = Float.class;
+ public static final Class<Short> SHORT = Short.class;
+ public static final Class<Byte> BYTE = Byte.class;
+ public static final Class<Long> LONG = Long.class;
+ public static final Class<String> STRING = String.class;
+ public static final Class<Boolean> BOOLEAN = Boolean.class;
+ public static final Class<RGB> RGB = RGB.class;
+ }
+
+
+ private final Logger log;
+ private final Preferences prefs;
+ private final List<PreferenceChangeListener> listeners;
+
+
+ public AppPrefs() {
+ this.log = Logger.getLogger(getClass().getName());
+ this.prefs = Preferences.userRoot().node(Application.APP_ID);
+ this.listeners = new LinkedList<PreferenceChangeListener>();
+
+ addSwtThreadAdaptionListener();
+ }
+
+
+ private void addSwtThreadAdaptionListener() {
+ prefs.addPreferenceChangeListener(new PreferenceChangeListener() {
+ public void preferenceChange(PreferenceChangeEvent evt) {
+ firePreferenceChangedSynced(evt);
+ }
+ });
+ }
+
+
+ private void firePreferenceChangedSynced(final PreferenceChangeEvent evt) {
+
+ // Delegate to event SWT event dispatching thread
+ Display.getDefault().syncExec(new Runnable() {
+ public void run() {
+ firePreferenceChanged(evt);
+ }
+ });
+
+ }
+
+
+ private void firePreferenceChanged(PreferenceChangeEvent evt) {
+ for (PreferenceChangeListener listener : listeners) {
+ listener.preferenceChange(evt);
+ }
+ }
+
+
+ public void sync() {
+ try {
+ prefs.sync();
+ } catch (BackingStoreException e) {
+ log.warning("Error syncing preferences: " + e.getMessage());
+ }
+ }
+
+
+ public void flush() {
+ try {
+ prefs.flush();
+ } catch (BackingStoreException e) {
+ log.warning("Error writing preferences: " + e.getMessage());
+ }
+ }
+
+
+ public void clear() {
+ try {
+ prefs.clear();
+ } catch (BackingStoreException e) {
+ log.warning("Error clearing preferences: " + e.getMessage());
+ }
+ }
+
+
+ public String[] keys() {
+ try {
+ return prefs.keys();
+ } catch (BackingStoreException e) {
+ log.warning("Error retrieving preference keys: " + e.getMessage());
+ }
+ return new String[0];
+ }
+
+
+ public void remove(String key) {
+ prefs.remove(key);
+ }
+
+
+ public <T> T get(Class<T> clazz, String key, T def) {
+ String value = (def != null) ?
+ get(key, def.toString()) : get(key, null);
+
+ return decode(clazz, value, def);
+ }
+
+
+ public <T> void put(Class<T> clazz, String key, T value) {
+ prefs.put(key, encode(clazz, value));
+ }
+
+
+ public void put(String key, String value) {
+ prefs.put(key, value);
+ }
+
+
+ public String get(String key, String def) {
+ return prefs.get(key, def);
+ }
+
+
+ public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
+ listeners.add(pcl);
+ }
+
+
+ public void removePreferenceChangeListener(PreferenceChangeListener pcl) {
+ listeners.remove(pcl);
+ }
+
+
+ public <T> String encode(Class<T> clazz, T value) {
+ if (clazz.equals(RGB.class)) {
+ return encodeRGB((RGB) value);
+ } else if (value != null) {
+ return String.valueOf(value);
+ } else {
+ return null;
+ }
+ }
+
+
+ public <T> T decode(Class<T> clazz, String str, T def) {
+ if (str == null) {
+ return def;
+ } else if (isNumeric(clazz)) {
+ return clazz.cast(decodeNumeric(clazz, str, def));
+ } else if (isString(clazz)) {
+ return clazz.cast(str);
+ } else if (isBoolean(clazz)) {
+ return clazz.cast(decodeBoolean(str, (Boolean) def));
+ } else if (clazz.equals(RGB.class)) {
+ return clazz.cast(decodeRGB(str, (RGB) def));
+ } else {
+ log.warning("Unable to decode class: " + clazz.getName());
+ }
+
+ return def;
+ }
+
+
+ private boolean isString(Class<?> clazz) {
+ return clazz.equals(String.class);
+ }
+
+
+ private boolean isBoolean(Class<?> clazz) {
+ return clazz.equals(Boolean.class);
+ }
+
+
+
+ private boolean isNumeric(Class<?> clazz) {
+ return
+ clazz.equals(Integer.class) ||
+ clazz.equals(Double.class) ||
+ clazz.equals(Float.class) ||
+ clazz.equals(Short.class) ||
+ clazz.equals(Byte.class) ||
+ clazz.equals(Long.class);
+ }
+
+
+ private RGB decodeRGB(String str, RGB def) {
+ if (str.startsWith("#")) {
+ try {
+ int hex = Integer.parseInt(str.substring(1), 16);
+ return SwtUtils.int2rgb(hex);
+ } catch (NumberFormatException e) {
+ log.warning("Not a valid color: " + str);
+ }
+ }
+ return def;
+ }
+
+
+ private String encodeRGB(RGB rgb) {
+ return String.format("#%06x", SwtUtils.rgb2int(rgb));
+ }
+
+
+ private boolean decodeBoolean(String str, boolean def) {
+ return str.equalsIgnoreCase("true") ||
+ (str.equalsIgnoreCase("false") ? false : def);
+ }
+
+
+ private <T> T decodeNumeric(Class<T> clazz, String s, T def) {
+ try {
+ if (clazz.equals(Integer.class)) {
+ return clazz.cast(Integer.parseInt(s));
+
+ } else if (clazz.equals(Double.class)) {
+ return clazz.cast(Double.parseDouble(s));
+
+ } else if (clazz.equals(Float.class)) {
+ return clazz.cast(Float.parseFloat(s));
+
+ } else if (clazz.equals(Short.class)) {
+ return clazz.cast(Short.parseShort(s));
+
+ } else if (clazz.equals(Byte.class)) {
+ return clazz.cast(Byte.parseByte(s));
+
+ } else if (clazz.equals(Long.class)) {
+ return clazz.cast(Long.parseLong(s));
+
+ }
+ } catch (NumberFormatException ex) {
+ log.warning("Invalid numeric value in preferences: " + s);
+ }
+ return def;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import static ie.dcu.segment.annotate.AnnotationType.*;
+import static ie.dcu.apps.ist.AppPrefs.Keys.*;
+import static ie.dcu.apps.ist.AppPrefs.SupportedTypes.*;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.apps.ist.AppPrefs.*;
+
+import java.util.prefs.*;
+
+import org.eclipse.jface.resource.*;
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Manages updating various components when preferences change.
+ *
+ * @author Kevin McGuinness
+ */
+class AppPrefsManager implements PreferenceChangeListener {
+
+ private final AppWindow window;
+ private final ColorRegistry colorRegistry;
+ private final AppPrefs prefs;
+
+
+ public AppPrefsManager(AppWindow window) {
+ this.window = window;
+ this.colorRegistry = JFaceResources.getColorRegistry();
+ this.prefs = loadPreferences();
+ }
+
+
+ private AppPrefs loadPreferences() {
+ AppPrefs prefs = new AppPrefs();
+ prefs.addPreferenceChangeListener(this);
+ return prefs;
+ }
+
+
+ public AppPrefs getPrefs() {
+ return prefs;
+ }
+
+
+ public void update() {
+ updateFeedbackEnabled();
+ updateBackgroundColor();
+ updateForegroundColor();
+ updateExperimentEmbedded();
+ }
+
+
+ public void updateExperimentEmbedded() {
+ boolean on = prefs.get(BOOLEAN, Keys.EXPERIMENT_EMBEDDED, false);
+ window.setExperimentModeEmbedded(on);
+ }
+
+
+ public void updateFeedbackEnabled() {
+ boolean on = prefs.get(BOOLEAN, Keys.ENABLE_FEEDBACK, true);
+ window.setEnableFeedback(on);
+ }
+
+
+ public void updateForegroundColor() {
+ RGB color = prefs.get(RGB, Keys.FOREGROUND_COLOR, DEFAULT_FOREGROUND);
+ colorRegistry.put(AnnotationType.Foreground.key, color);
+ window.getView().repaint();
+ }
+
+
+ public void updateBackgroundColor() {
+ RGB color = prefs.get(RGB, Keys.BACKGROUND_COLOR, DEFAULT_BACKGROUND);
+ colorRegistry.put(AnnotationType.Background.key, color);
+ window.getView().repaint();
+ }
+
+
+ public void preferenceChange(PreferenceChangeEvent evt) {
+ String pref = evt.getKey();
+
+ if (pref.equals(ENABLE_FEEDBACK)) {
+ updateFeedbackEnabled();
+
+ } else if (pref.equals(FOREGROUND_COLOR)) {
+ updateForegroundColor();
+
+ } else if (pref.equals(BACKGROUND_COLOR)) {
+ updateBackgroundColor();
+
+ } else if (pref.equals(EXPERIMENT_EMBEDDED)) {
+ updateExperimentEmbedded();
+ }
+
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.apps.ist.recent.*;
+
+import java.io.*;
+
+/**
+ * Manages applications recent files list.
+ *
+ * @author Kevin McGuinness
+ */
+public class AppRecentFiles extends RecentFiles {
+ public static final String APP_SETTINGS_PATH = ".ist";
+ public static final String HISTORY_FILE_NAME = ".history";
+
+ private static AppRecentFiles history;
+
+
+ private AppRecentFiles(File store) {
+ super(store);
+ }
+
+
+ public static AppRecentFiles getInstance() {
+ if (history == null) {
+ history = create();
+ }
+ return history;
+ }
+
+
+ private static AppRecentFiles create() {
+ AppRecentFiles history = null;
+ try {
+ history = new AppRecentFiles(getHistoryFile());
+ history.load();
+ } catch (IOException e) {
+ getLogger().warning("Unable to create history file: " + e);
+ }
+
+ return history;
+ }
+
+
+ @Override
+ public void load() {
+ try {
+ super.load();
+ } catch (IOException e) {
+ getLogger().warning("Error loading recent documents list: " + e);
+ }
+ }
+
+ @Override
+ public void store() {
+ try {
+ super.store();
+ } catch (IOException e) {
+ getLogger().warning("Error storing recent documents list: " + e);
+ }
+ }
+
+
+ private static File getHistoryFile() throws IOException {
+ File file = new File(getSettingsDirectory(), HISTORY_FILE_NAME);
+ if (file.exists()) {
+ // Check we can write
+ if (file.canWrite()) {
+ return file;
+ }
+ } else {
+ return file;
+ }
+
+ throw new IOException("History file is unwritable");
+ }
+
+
+ private static File getSettingsDirectory() throws IOException {
+ String home = System.getProperty("user.home");
+ File settings = new File(home, APP_SETTINGS_PATH);
+
+ if (!settings.exists()) {
+ if (settings.mkdirs()) {
+ return settings;
+ }
+ } else {
+ return settings;
+ }
+
+ throw new IOException("Unable to create application settings directory");
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist;
+
+import ie.dcu.apps.ist.actions.ActionManager;
+import ie.dcu.apps.ist.actions.HoverMenuManager;
+import ie.dcu.apps.ist.actions.OpenAction;
+import ie.dcu.apps.ist.actions.OpenRecentAction;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Properties;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+
+/**
+ * Recent files menu.
+ *
+ * @author Kevin McGuinness
+ */
+class AppRecentMenu extends HoverMenuManager {
+ private final AppRecentFiles history;
+ private final EmptyAction empty;
+ private final AppWindow window;
+ private IAction action;
+
+ public AppRecentMenu(AppWindow window, IMenuManager parent) {
+ super("Open &Recent");
+
+ this.window = window;
+ setParent(parent);
+ history = AppRecentFiles.getInstance();
+ add(empty = new EmptyAction());
+ addMenuListener(menuListener);
+
+ ActionManager actions = window.getActions();
+ if (actions != null) {
+ OpenAction openAction = actions.get(OpenAction.class);
+ openAction.addPropertyChangeListener(actionListener);
+ action = openAction;
+ }
+
+ try {
+ Properties props = window.getProperties();
+ String icon = props.getProperty("AppRecentMenu.icon",
+ "file:icons/open-recent.png");
+ setImageURL(new URL(icon));
+ } catch (MalformedURLException e) {
+ // Ignore
+ }
+ }
+
+ @Override
+ public void fill(Menu parent, int index) {
+ super.fill(parent, index);
+ MenuItem item = getMenuItem();
+ if (item != null) {
+ item.setEnabled(action.isEnabled());
+ }
+ }
+
+
+ private void buildMenu() {
+ removeAll();
+
+ if (history.empty()) {
+ add(empty);
+ } else {
+ for (File file : history) {
+ add(new OpenRecentAction(window.getActions(), file));
+ }
+ }
+
+ update(false);
+ }
+
+
+ private final class EmptyAction extends Action {
+ public EmptyAction() {
+ setText("No Recent Files");
+ setEnabled(false);
+ }
+ };
+
+
+ private final IMenuListener menuListener = new IMenuListener() {
+ public void menuAboutToShow(IMenuManager manager) {
+ buildMenu();
+ }
+ };
+
+
+ private final IPropertyChangeListener actionListener
+ = new IPropertyChangeListener() {
+
+ public void propertyChange(PropertyChangeEvent event) {
+ MenuItem item = getMenuItem();
+ if (item != null) {
+ item.setEnabled(action.isEnabled());
+ }
+ }
+ };
+
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import java.net.*;
+
+import org.eclipse.jface.resource.*;
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Application status bar message type.
+ *
+ * @author Kevin McGuinness
+ */
+public enum AppStatus {
+ Information, Warning, Error;
+
+ public Image getIcon() {
+ String key = key();
+ Image image = JFaceResources.getImage(key);
+ if (image == null) {
+ ImageRegistry registry = JFaceResources.getImageRegistry();
+ ImageDescriptor descriptor = createImageDescriptor();
+ registry.put(key, descriptor);
+ image = registry.get(key);
+ }
+ return image;
+ }
+
+ private ImageDescriptor createImageDescriptor() {
+ try {
+ return ImageDescriptor.createFromURL(new URL(key()));
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String key() {
+ switch (this) {
+ case Information:
+ return "file:resources/icons/dialog-information.png";
+ case Warning:
+ return "file:resources/icons/dialog-warning.png";
+ default:
+ return "file:resources/icons/dialog-error.png";
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.apps.ist.actions.*;
+import ie.dcu.apps.ist.event.*;
+import ie.dcu.apps.ist.exp.Experiment;
+import ie.dcu.apps.ist.views.*;
+import ie.dcu.segment.*;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.swt.*;
+import ie.dcu.swt.event.*;
+import ie.dcu.swt.layout.LayoutFactory;
+import ie.dcu.util.*;
+
+import java.io.*;
+import java.net.*;
+import java.util.Properties;
+import java.util.logging.*;
+
+import org.eclipse.jface.action.*;
+import org.eclipse.jface.resource.*;
+import org.eclipse.jface.window.ApplicationWindow;
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Main interactive segmentation tool application window.
+ *
+ * @author Kevin McGuinness
+ */
+public class AppWindow extends ApplicationWindow implements FileDropListener {
+ private static final Logger log = Logger.getLogger("AppWindow");
+
+ private final Properties props;
+
+ private AppPrefsManager prefsManager;
+ private ActionManager actions;
+ private Composite content;
+ private SegmentationView view;
+ private Shell shell;
+ private ImageObserver observer;
+ private Experiment experiment;
+ private ExperimentPanel experimentPanel;
+ private boolean experimentModeEmbedded;
+
+
+ public AppWindow() {
+ super(null);
+
+ props = loadProperties();
+
+ addMenuBar();
+ addStatusLine();
+ }
+
+
+ @Override
+ protected boolean canHandleShellCloseEvent() {
+ if (!ExitAction.confirmExit(this)) {
+ return false;
+ }
+ return super.canHandleShellCloseEvent();
+ }
+
+
+ private Properties loadProperties() {
+ try {
+ return Application.loadProperties(getPropertiesFile());
+ } catch (IOException e) {
+ log.severe("Unable to load properties file " + e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String getPropertiesFile() {
+ return OsUtils.isMacOS() ? "application.mac" : "application";
+ }
+
+
+ public void updateWindowTitle() {
+ String title;
+ if (hasContext()) {
+ String file = getContext().getFile().getName();
+ title = String.format("%s [%s]", Application.APP_NAME, file);
+
+ } else {
+ title = Application.APP_NAME;
+ }
+
+ if (isExperimentMode()) {
+ title += " - Experiment Mode";
+ }
+
+ getShell().setText(title);
+ }
+
+
+ private SegmentationView createView(Composite parent) {
+
+ // Load props
+ Properties props;
+ try {
+ props = Application.loadProperties("view");
+ } catch (IOException e) {
+ log.severe("Error loading view properties " + e.getLocalizedMessage());
+ throw new RuntimeException(e);
+ }
+
+ // Create view
+ SegmentationView view = new SegmentationView(props, parent, 0);
+
+ boolean blocksOnFork = false;
+
+ // Set runnable context
+ view.setRunnableContext(this, blocksOnFork);
+
+ // Add observer
+ observer = new ImageObserver(view);
+
+ // Return new view
+ return view;
+ }
+
+
+ private static ImageDescriptor createImageDescriptor(String url) {
+ try {
+ return ImageDescriptor.createFromURL(new URL(url));
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private void updateEnabledActions() {
+ ActionManager a = getActions();
+ boolean ctxAvailable = hasContext();
+ boolean experimentMode = isExperimentMode();
+
+ a.setEnabled(OpenAction.class, !experimentMode);
+ a.setEnabled(PreferencesAction.class, !experimentMode);
+ a.setEnabled(SaveAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(SaveAsAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(ExportViewAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(ExportTransparentPNGAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(PrintAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(CopyAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(UndoAction.class, ctxAvailable && view.canUndo());
+ a.setEnabled(RedoAction.class, ctxAvailable && view.canRedo());
+ a.setEnabled(NextAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(PreviousAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(OpenExperimentAction.class, !experimentMode);
+
+ updateExportImageMapAction();
+
+ if (experimentMode) {
+
+ boolean changeSelection = false;
+
+ // Enable experiment segmenters
+ for (SelectSegmenterAction s : a.getSegmentationActions()) {
+ Segmenter segmenter = s.getSegmenter();
+ boolean enabled = experiment.using(segmenter);
+ s.setEnabled(enabled);
+
+ if (!s.isEnabled() && s.isChecked()) {
+ // The selected segmenter has been disabled,
+ // we need to select a different one
+ changeSelection = true;
+ s.setChecked(false);
+ }
+ }
+
+ if (changeSelection) {
+ // Select the first available segmenter
+ for (SelectSegmenterAction s : a.getSegmentationActions()) {
+ if (experiment.using(s.getSegmenter())) {
+ s.setChecked(true);
+ s.run();
+ break;
+ }
+ }
+ }
+
+ } else {
+ // Enable all select segmenter actions
+ for (SelectSegmenterAction s : a.getSegmentationActions()) {
+ s.setEnabled(true);
+ }
+ }
+ }
+
+ private void updateExportImageMapAction() {
+ ActionManager a = getActions();
+ SegmentationContext ctx = getContext();
+
+ if (ctx != null && !isExperimentMode()) {
+ AnnotationManager am = ctx.getAnnotations();
+ boolean hasObject = am.hasForegroundAnnotation()
+ && am.hasBackgroundAnnotation();
+ a.setEnabled(ExportImageMapAction.class, hasObject);
+
+ } else {
+ a.setEnabled(ExportImageMapAction.class, false);
+ }
+ }
+
+
+ private void updateUndoRedoActions() {
+ ActionManager a = getActions();
+ boolean ctxAvailable = hasContext();
+ a.setEnabled(UndoAction.class, ctxAvailable && view.canUndo());
+ a.setEnabled(RedoAction.class, ctxAvailable && view.canRedo());
+ }
+
+
+ @Override
+ protected void configureShell(Shell shell) {
+ this.shell = shell;
+ super.configureShell(shell);
+ updateWindowTitle();
+ loadIcons(shell);
+ shell.setSize(800, 600);
+ SwtUtils.center(shell);
+ }
+
+
+ private void loadIcons(Shell shell) {
+ final String[] icons = {
+ "resources/icons/icon-16.png",
+ "resources/icons/icon-24.png",
+ "resources/icons/icon-32.png",
+ "resources/icons/icon-48.png"
+ };
+
+ Image[] images = new Image[icons.length];
+ Display display = shell.getDisplay();
+
+ try {
+ int i = 0;
+ for (String file : icons) {
+ images[i++] = new Image(display, file);
+ }
+
+ shell.setImages(images);
+
+ } catch (SWTException e) {
+ log.warning("Error loading application window icons: "
+ + e.getLocalizedMessage());
+ }
+ }
+
+
+ @Override
+ protected Control createContents(Composite parent) {
+ content = new Composite(parent, SWT.NONE);
+
+ content.setLayout(LayoutFactory.createGridLayout(0, 0, 2, false));
+
+ view = createView(content);
+ view.setLayoutData(LayoutFactory.createGridData());
+ view.addContextChangeListener(contextListener);
+
+ SwtUtils.createFileDropTarget(view, this);
+
+ // Done creating contents
+ getPrefsManager().update();
+ updateEnabledActions();
+
+ return content;
+ }
+
+
+ @Override
+ protected boolean showTopSeperator() {
+ return false;
+ }
+
+
+ @Override
+ protected MenuManager createMenuManager() {
+ MenuManager bar = new MenuManager();
+
+ ActionManager actions = getActions();
+
+ MenuManager file = addMenu(bar, "&File");
+ file.add(actions.get(OpenAction.class));
+
+ file.add(new AppRecentMenu(this, file));
+
+ file.add(new Separator());
+ file.add(actions.get(SaveAction.class));
+ file.add(actions.get(SaveAsAction.class));
+ file.add(new Separator());
+
+ // Export menu ->
+ MenuManager exportMenu = addMenu(file, "&Export",
+ props.getProperty("ExportMenu.icon"));
+
+ exportMenu.add(actions.get(ExportViewAction.class));
+ exportMenu.add(actions.get(ExportImageMapAction.class));
+ exportMenu.add(actions.get(ExportTransparentPNGAction.class));
+
+ file.add(new Separator());
+ file.add(actions.get(PrintAction.class));
+ file.add(new Separator());
+ file.add(actions.get(ExitAction.class));
+
+ MenuManager edit = addMenu(bar, "&Edit");
+
+ edit.add(actions.get(UndoAction.class));
+ edit.add(actions.get(RedoAction.class));
+ edit.add(new Separator());
+ edit.add(actions.get(CopyAction.class));
+ edit.add(new Separator());
+ edit.add(actions.get(PreferencesAction.class));
+
+ MenuManager tools = addMenu(bar, "&Tools");
+ tools.add(actions.get(OpenExperimentAction.class));
+ tools.add(new Separator());
+
+ for (SelectSegmenterAction a : actions.getSegmentationActions()) {
+ tools.add(a);
+ }
+
+ tools.add(new Separator());
+ tools.add(actions.get(ConfigureSegmenterAction.class));
+
+ MenuManager go = addMenu(bar, "&Go");
+ go.add(actions.get(NextAction.class));
+ go.add(actions.get(PreviousAction.class));
+
+
+ MenuManager help = addMenu(bar, "&Help");
+
+ help.add(actions.get(HelpAction.class));
+ help.add(actions.get(AboutAction.class));
+
+ if (OsUtils.isMacOSX()) {
+ // Enhance the UI on the Mac
+ IAction aboutAction = actions.get(AboutAction.class);
+ IAction prefAction = actions.get(PreferencesAction.class);
+ Listener quitListener = new Listener() {
+ public void handleEvent(Event evt) {
+ getActions().get(ExitAction.class).runWithEvent(evt);
+ }
+ };
+ CocoaUIEnhancer enhancer = new CocoaUIEnhancer(Application.APP_NAME);
+ enhancer.hookApplicationMenu(Display.getDefault(),
+ quitListener, aboutAction, prefAction);
+ }
+
+ return bar;
+ }
+
+
+ protected static MenuManager addMenu(MenuManager parent, String text) {
+ MenuManager menu = new HoverMenuManager(text);
+ parent.add(menu);
+ return menu;
+ }
+
+ protected static MenuManager addMenu(MenuManager parent,
+ String text, String image) {
+
+ MenuManager menu;
+ try {
+ menu = new HoverMenuManager(text, image != null ?
+ new URL(image) : null);
+ } catch (MalformedURLException e) {
+ log.log(Level.WARNING, "Malformed URL", e);
+ menu = new HoverMenuManager(text);
+ }
+
+ parent.add(menu);
+ return menu;
+ }
+
+
+
+ public void setExperiment(Experiment ex) {
+ if (experiment != ex) {
+ experiment = ex;
+ setContext(null);
+
+ if (ex == null) {
+
+ if (experimentPanel != null) {
+ experimentPanel.dispose();
+ experimentPanel = null;
+ content.layout();
+ }
+ } else {
+
+ if (experimentModeEmbedded) {
+ experimentPanel = new ExperimentPanel(this, content, SWT.NONE);
+ GridData data = new GridData(SWT.FILL, SWT.FILL, false, true);
+ data.widthHint = 180;
+ data.verticalIndent = 5;
+ experimentPanel.setLayoutData(data);
+ content.layout();
+
+ } else {
+ ExperimentPanel.open(this);
+ }
+ }
+ updateEnabledActions();
+ updateWindowTitle();
+ }
+ }
+
+
+ public Experiment getExperiment() {
+ return experiment;
+ }
+
+
+ public boolean isExperimentMode() {
+ return experiment != null;
+ }
+
+
+ public void setExperimentModeEmbedded(boolean on) {
+ this.experimentModeEmbedded = on;
+ }
+
+
+ public AppPrefs getPrefs() {
+ return getPrefsManager().getPrefs();
+ }
+
+
+ AppPrefsManager getPrefsManager() {
+ if (prefsManager == null) {
+ prefsManager = new AppPrefsManager(this);
+ }
+ return prefsManager;
+ }
+
+
+ public Properties getProperties() {
+ return props;
+ }
+
+
+ public ActionManager getActions() {
+ if (actions == null) {
+ actions = new ActionManager(this);
+ }
+ return actions;
+ }
+
+
+ public Shell getShell() {
+ Shell shell = super.getShell();
+ if (shell == null) {
+ return this.shell;
+ }
+ return shell;
+ }
+
+
+ public SegmentationView getView() {
+ return view;
+ }
+
+
+ public SegmentationContext getContext() {
+ if (view != null) {
+ return view.getContext();
+ }
+ return null;
+ }
+
+
+ public File getContextFile() {
+ if (hasContextFile()) {
+ return getContext().getFile();
+ }
+ return null;
+ }
+
+
+ public Image getIcon(String url) {
+ Image image = JFaceResources.getImage(url);
+ if (image == null) {
+ ImageRegistry registry = JFaceResources.getImageRegistry();
+ ImageDescriptor descriptor = createImageDescriptor(url);
+ registry.put(url, descriptor);
+ image = registry.get(url);
+ }
+ return image;
+ }
+
+
+ public boolean hasContext() {
+ if (view != null) {
+ return view.getContext() != null;
+ }
+ return false;
+ }
+
+
+ public boolean hasContextFile() {
+ return getContext() != null ? getContext().hasContextFile() : false;
+ }
+
+
+ public void setEnableFeedback(boolean on) {
+ if (observer != null) {
+ observer.setEnabled(on);
+ }
+ }
+
+
+ public void setContext(SegmentationContext ctx) {
+ if (ctx != null) {
+ // Set a segmenter if one has not been set
+ if (!view.hasSegmenter()) {
+ SegmenterRegistry registry = SegmenterRegistry.getInstance();
+ view.setSegmenter(registry.getDefault());
+ }
+ }
+
+ // Set context
+ view.setContext(ctx);
+ updateWindowTitle();
+ }
+
+
+ public void status(String message) {
+ setStatus(message);
+ }
+
+
+ public void status(String format, Object... args) {
+ setStatus(String.format(format, args));
+ }
+
+
+ public void status(Image image, String format, Object... args) {
+ StatusLineManager manager = getStatusLineManager();
+ if (manager != null) {
+ manager.setMessage(image, String.format(format, args));
+ }
+ }
+
+
+ public void status(AppStatus s, String format, Object... args) {
+ if (format == null) {
+ setStatus(null);
+
+ } else {
+ StatusLineManager manager = getStatusLineManager();
+ if (manager != null) {
+ if (s == AppStatus.Error) {
+ manager.setErrorMessage(s.getIcon(), String.format(format, args));
+ } else {
+ manager.setMessage(s.getIcon(), String.format(format, args));
+ }
+ }
+ }
+ }
+
+
+ public void drop(FileDropEvent evt) {
+ System.out.println("drop received");
+ for (File f : evt.files()) {
+ if (isAcceptable(f)) {
+ getActions().get(OpenAction.class).open(f);
+ break;
+ }
+ }
+ }
+
+
+ public boolean isAcceptable(File file) {
+ String ext = FileUtils.getExtension(file);
+ return (ext.equals(".ctx") || SwtUtils.getImageFormat(file) != -1);
+ }
+
+
+ private void handleContextChanged(ContextChangedEvent evt) {
+
+ // Remove listener from old context
+ if (evt.oldContext != null) {
+ evt.oldContext.removeAnnotationListener(annotationListener);
+ }
+
+ // Add listener to new context
+ if (evt.newContext != null) {
+ evt.newContext.addAnnotationListener(annotationListener);
+ }
+
+ // Update application state
+ updateEnabledActions();
+ }
+
+
+ private void handleAnnotationPerformed(AnnotationEvent e) {
+
+ // Update state of undo and redo menu items
+ updateUndoRedoActions();
+ updateExportImageMapAction();
+ }
+
+
+ private final ContextChangeListener contextListener = new ContextChangeListener() {
+ public void contextChanged(ContextChangedEvent evt) {
+ handleContextChanged(evt);
+ }
+ };
+
+
+ private final AnnotationListener annotationListener = new AnnotationAdapter() {
+ public void annotationsChanged(AnnotationEvent e) {
+ handleAnnotationPerformed(e);
+ }
+ };
+
+
+ private class ImageObserver implements MouseMoveListener, ContextChangeListener {
+ private static final String MONOSPACE_FONT = "Monospace";
+
+ private final ImageControl ctrl;
+ private ImageData image;
+ private boolean inside;
+ private boolean enabled;
+
+
+ public ImageObserver(SegmentationView view) {
+ view.addContextChangeListener(this);
+ view.getCanvas().addMouseMoveListener(this);
+ ctrl = view.getImageControl();
+ contextChanged(view.getContext());
+ FontDescriptor fd = FontDescriptor.createFrom(MONOSPACE_FONT, 8, SWT.NORMAL);
+ JFaceResources.getFontRegistry().put(MONOSPACE_FONT, fd.getFontData());
+ }
+
+
+ public void setEnabled(boolean enabled) {
+ if (this.enabled != enabled) {
+ status(null);
+ Font font = JFaceResources.getDefaultFont();
+ getStatusLineManager().getControl().setFont(font);
+ this.inside = false;
+ this.enabled = enabled;
+ }
+ }
+
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+
+ public void mouseMove(MouseEvent e) {
+ if (isEnabled()) {
+ Point pt = getImagePoint(e);
+ if (pt != null) {
+ inside(pt, getColor(pt));
+ } else {
+ outside();
+ }
+ }
+ }
+
+
+ private void outside() {
+ if (inside) {
+ status(null);
+ Font font = JFaceResources.getDefaultFont();
+ getStatusLineManager().getControl().setFont(font);
+ inside = false;
+ }
+ }
+
+
+ private final Point getImagePoint(MouseEvent e) {
+ if (image != null) {
+ Point pt = new Point(e.x, e.y);
+ if (ctrl.imageContains(pt)) {
+ return ctrl.canvasToImage(pt);
+ }
+ }
+ return null;
+ }
+
+
+ private final RGB getColor(Point pt) {
+
+ int pixel = image.getPixel(pt.x, pt.y);
+ return image.palette.getRGB(pixel);
+ }
+
+
+ private void inside(Point pt, RGB c) {
+ if (!inside) {
+ Font font = JFaceResources.getFont(MONOSPACE_FONT);
+ getStatusLineManager().getControl().setFont(font);
+ }
+
+ status(AppStatus.Information,
+ "Location [%4d,%4d] Color [%3d,%3d,%3d]",
+ pt.x, pt.y, c.red, c.green, c.blue);
+
+ inside = true;
+ }
+
+
+ public void contextChanged(ContextChangedEvent evt) {
+ contextChanged(evt.newContext);
+ }
+
+
+ public void contextChanged(SegmentationContext ctx) {
+ if (ctx != null) {
+ image = ctx.getImageData();
+ } else {
+ image = null;
+ }
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.util.*;
+
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.util.*;
+
+import static ie.dcu.util.FileUtils.pathJoin;
+import static ie.dcu.util.OsUtils.*;
+
+/**
+ * Base application. Contains information strings and methods to load
+ * application wide resources.
+ *
+ * @author Kevin McGuinness
+ */
+public class Application {
+ public static final String APP_ID = "ist";
+ public static final String APP_NAME = "Interactive Segmentation and Annotation Tool";
+ public static final String APP_VERSION = "1.3.4";
+ public static final String APP_RESOURCE_DIR = "resources";
+
+ /**
+ * Default locations to look for plugins
+ */
+ public static final String[] DEFAULT_PLUGIN_SEARCH_PATH
+ = defaultPluginSearchPath();
+
+ /**
+ * Loads a properties file from the application resource area
+ * (resources/config)
+ *
+ * @param name
+ * The properties file name. ".properties" will be appended if
+ * necessary.
+ * @return A Properties object
+ * @throws IOException
+ * If there is an error creating the Properties object.
+ */
+ public static Properties loadProperties(String name) throws IOException {
+ if (!name.endsWith(".properties")) {
+ name = name + ".properties";
+ }
+
+ String path = FileUtils.pathJoin(APP_RESOURCE_DIR, "config", name);
+ File file = new File(path);
+ System.out.println("Path:" + file);
+ return PropsUtils.load(file);
+ }
+
+ /**
+ * Returns the users plugins folder
+ */
+ public static String userPluginsFolder() {
+ return pathJoin(userHome(), ".ist", "plugins");
+ }
+
+ /**
+ * Returns the applications base folder
+ */
+ public static String applicationFolder() {
+ String apphome = System.getenv("IST_HOME");
+ if (apphome != null) {
+ File file = new File(apphome);
+ if (file.isDirectory()) {
+ return file.getAbsolutePath();
+ }
+ }
+
+ ProtectionDomain domain = Application.class.getProtectionDomain();
+ CodeSource source = domain.getCodeSource();
+ URL location = source.getLocation();
+ String path = location.getPath();
+ try {
+
+ path = URLDecoder.decode(path, "UTF-8");
+ if (isWindows()) {
+ path = path.substring(1);
+ }
+
+ path = new File(path).getParent();
+ } catch (UnsupportedEncodingException e) {
+ path = System.getProperty("user.dir");
+ }
+
+ return path;
+ }
+
+ public static String applicationPluginsFolder() {
+ return pathJoin(applicationFolder(), "plugins");
+ }
+
+ public static String osxPluginSearchPath() {
+ return pathJoin(userHome(), "Library",
+ "Application Support", APP_NAME, "Plugins");
+ }
+
+ private static String[] defaultPluginSearchPath() {
+ ArrayList<String> paths = new ArrayList<String>();
+ paths.add(userPluginsFolder());
+ if (isMacOSX()) {
+ paths.add(osxPluginSearchPath());
+ }
+ paths.add(applicationPluginsFolder());
+ return paths.toArray(new String[paths.size()]);
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.eval.*;
+
+import java.util.*;
+
+public class EvaluatorRegistry {
+ private static EvaluatorRegistry instance;
+ private final Set<Evaluator> evaluators;
+
+ private EvaluatorRegistry() {
+ evaluators = new LinkedHashSet<Evaluator>();
+ init();
+ }
+
+
+ private void init() {
+ add(new ConfusionMatrixEvaluator());
+ add(new BoundaryAccuracyEvaluator());
+ }
+
+
+ public static EvaluatorRegistry getInstance() {
+ if (instance == null) {
+ instance = new EvaluatorRegistry();
+ }
+ return instance;
+ }
+
+
+ public Evaluator find(String name) {
+ for (Evaluator e : evaluators) {
+
+ // Check name
+ String ename = e.getName();
+ if (ename.equals(name)) {
+ return e;
+ }
+
+ // Check class name
+ String cname = e.getClass().getName();
+ if (cname.equals(name)) {
+ return e;
+ }
+
+ // Check simple name
+ String sname = e.getClass().getSimpleName();
+ if (sname.equals(name)) {
+ return e;
+ }
+ }
+
+ return null;
+ }
+
+
+ public void add(Evaluator evaluator) {
+ evaluators.add(evaluator);
+ }
+
+
+ public Set<Evaluator> evaluators() {
+ return Collections.unmodifiableSet(evaluators);
+ }
+
+
+ public Iterator<Evaluator> iterator() {
+ return evaluators.iterator();
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.plugin.*;
+import ie.dcu.segment.Segmenter;
+
+import java.util.logging.Logger;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Application launcher.
+ *
+ * @author Kevin McGuinness
+ */
+public class Main {
+ public static final Logger log = Logger.getLogger("Main");
+
+ /**
+ * Message to show when GDI+ is not installed.
+ */
+ private static final String NO_GDIPLUS =
+ "Unable to load required library: GDI+ (gdiplus.dll)\n\n" +
+ "The GDI+ library needs to be installed before this program " +
+ "can be used on this platform. See the note at the end of " +
+ "the download page for further details.";
+
+
+ public static void main(String[] args) {
+ System.out.print(Application.DEFAULT_PLUGIN_SEARCH_PATH);
+ for (String s: Application.DEFAULT_PLUGIN_SEARCH_PATH) {
+ System.out.println(s);
+ }
+
+ Display.setAppName(Application.APP_NAME);
+ check();
+ loadPlugins();
+ AppWindow window = new AppWindow();
+ window.setBlockOnOpen(true);
+ window.open();
+ System.exit(0);
+ }
+
+ private static void loadPlugins() {
+ PluginManager manager = new PluginManager();
+ for (String path : Application.DEFAULT_PLUGIN_SEARCH_PATH) {
+ manager.searchPath().add(path);
+ }
+ manager.loadPlugins();
+ for (Plugin plugin : manager.plugins()) {
+ String classname = plugin.getMetadata("segmenter");
+ if (classname != null) {
+ loadSegmenterPlugin(plugin, classname);
+ }
+ }
+ }
+
+ private static void loadSegmenterPlugin(Plugin plugin, String classname) {
+ try {
+ Segmenter segmenter = (Segmenter)
+ plugin.loadObject(classname);
+
+ SegmenterRegistry.getInstance().add(segmenter);
+
+ } catch (PluginException e) {
+ log.severe("Unable to load plugin: " + e);
+ }
+ }
+
+ public static boolean contains(String[] args, String argument) {
+ for (String arg : args) {
+ while (arg.startsWith("-")) {
+ arg = arg.substring(1);
+ }
+
+ if (arg.equals(argument)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void check() {
+ // Check for potential missing GDI+ on Win2K, NT, ME 98
+ String os = System.getProperty("os.name");
+ if (os.equals("Windows 2000") ||
+ os.equals("Windows NT") ||
+ os.equals("Windows ME") ||
+ os.equals("Windows 98"))
+ {
+ try {
+ System.loadLibrary("gdiplus");
+ } catch (RuntimeException e) {
+ exit(NO_GDIPLUS);
+ }
+ }
+ }
+
+ static void exit(String message) {
+ Display display = Display.getDefault();
+ Rectangle rect = display.getBounds();
+
+ Shell shell = display.getActiveShell();
+ if (shell == null) {
+ shell = new Shell(display, SWT.NO_TRIM);
+ shell.setBounds(rect.width/2, rect.height/2, 0, 0);
+ shell.open();
+ shell.setText("Error");
+ }
+
+ MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
+ box.setText("Error");
+ box.setMessage(message);
+ box.open();
+ display.dispose();
+ System.exit(1);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.segment.painters.*;
+
+import java.util.*;
+
+public class PainterRegistry {
+ private final LinkedHashMap<String, SegmentationPainter> painters;
+
+ public PainterRegistry() {
+ painters = new LinkedHashMap<String, SegmentationPainter>();
+ init();
+ }
+
+
+ private void init() {
+ add(new CombinedPainter());
+ add(new OriginalPainter());
+ add(new MarkupPainter());
+ add(new MaskPainter());
+ add(new ForegroundOnlyPainter());
+ add(new OutlineOverlayPainter());
+ }
+
+
+ public void add(SegmentationPainter painter) {
+ painters.put(painter.getName(), painter);
+ }
+
+
+ public SegmentationPainter get(String painter) {
+ return painters.get(painter);
+ }
+
+
+ public Collection<SegmentationPainter> values() {
+ return painters.values();
+ }
+
+
+ public void dispose() {
+ for (SegmentationPainter p : values()) {
+ p.dispose();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.segment.*;
+
+import java.util.*;
+
+/**
+ * Registry for segmenters.
+ *
+ * @author Kevin McGuinness
+ */
+public class SegmenterRegistry implements Iterable<Segmenter> {
+ private static final String DEFAULT_SEGMENTER_NAME = "IgcSegmenter";
+ private static SegmenterRegistry instance;
+ private final Set<Segmenter> segmenters;
+
+ private SegmenterRegistry() {
+ segmenters = new LinkedHashSet<Segmenter>();
+ init();
+ }
+
+
+ private void init() {
+ // OutlineSegmenter is not very useful
+ // add(new OutlineSegmenter());
+
+ // Geodesic Active Contours is too sensitive to parameters
+ // for general use on natural images.
+ // add(new SeededGacSegmenter());
+
+ //add(new SrgSegmenter());
+ //add(new IgcSegmenter());
+ //add(new BptSegmenter());
+ //add(new SioxSegmenter());
+ //add(new SpatiogramSegmenter());
+ }
+
+
+ public static SegmenterRegistry getInstance() {
+ if (instance == null) {
+ instance = new SegmenterRegistry();
+ }
+ return instance;
+ }
+
+
+ public Segmenter find(String name) {
+ for (Segmenter s : segmenters) {
+
+ // Check name
+ String ename = s.getName();
+ if (ename.equals(name)) {
+ return s;
+ }
+
+ // Check class name
+ String cname = s.getClass().getName();
+ if (cname.equals(name)) {
+ return s;
+ }
+
+ // Check simple name
+ String sname = s.getClass().getSimpleName();
+ if (sname.equals(name)) {
+ return s;
+ }
+ }
+
+ return null;
+ }
+
+
+ public boolean isDefault(Segmenter s) {
+ if (s != null) {
+ if (segmenters.size() == 1 && segmenters.contains(s)) {
+ return true;
+ }
+ return s.getClass().getSimpleName().equals(DEFAULT_SEGMENTER_NAME);
+ }
+ return false;
+ }
+
+
+ public Segmenter getDefault() {
+ for (Segmenter s : segmenters) {
+ if (isDefault(s)) {
+ return s;
+ }
+ }
+ return null;
+ }
+
+
+ public void add(Segmenter segmenter) {
+ segmenters.add(segmenter);
+ }
+
+
+ public Set<Segmenter> segmenters() {
+ return Collections.unmodifiableSet(segmenters);
+ }
+
+
+ public Iterator<Segmenter> iterator() {
+ return segmenters.iterator();
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.Application;
+import ie.dcu.swt.layout.LayoutFactory;
+
+import org.eclipse.jface.dialogs.*;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.*;
+
+
+/**
+ * Show an about dialog.
+ *
+ * @author Kevin McGuinness
+ */
+public class AboutAction extends AppAction {
+
+ private AboutBox dialog;
+
+ public AboutAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ if (dialog == null) {
+ dialog = new AboutBox();
+ }
+ dialog.open();
+ }
+
+
+ private class AboutBox extends Dialog {
+
+ private static final String MESSAGE =
+ " Kevin McGuinness <a href=\"mailto:kevin.mcguinness@eeng.dcu.ie\">" +
+ "kevin.mcguinness@eeng.dcu.ie</a>\n" +
+ " Center for Digital Video Processing\n" +
+ " Dublin City University.\n" +
+ " <a href=\"http://www.cdvp.dcu.ie\">http://www.cdvp.dcu.ie</a>";
+
+
+ private Composite composite;
+
+
+ protected AboutBox() {
+ super(window);
+ }
+
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(LayoutFactory.createGridLayout(5, 5));
+
+ String image = string("aboutImage");
+ addImage(image);
+ addLabel(String.format("Version %s", Application.APP_VERSION), SWT.CENTER);
+ addLabel(" SWT Version " + swtVersionString(), SWT.CENTER);
+ addLabel(" Developed By:", SWT.LEFT);
+ addLink(MESSAGE);
+
+ return composite;
+ }
+
+ private String swtVersionString() {
+ return String.format("%d (%s)", SWT.getVersion(), SWT.getPlatform());
+ }
+
+
+ @Override
+ protected void createButtonsForButtonBar(Composite parent) {
+ createButton(parent,
+ IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
+ }
+
+
+ @Override
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ shell.setText("About");
+ }
+
+
+ private void addLink(String string) {
+ Link link = new Link(composite, 0);
+ link.setText(string);
+ link.setLayoutData(LayoutFactory.createGridData());
+ }
+
+
+ private void addLabel(String text, int alignment) {
+ Label label = new Label(composite, 0);
+ label.setText(text);
+ label.setAlignment(alignment);
+ label.setLayoutData(LayoutFactory.createGridData());
+ label.setFont(JFaceResources.getBannerFont());
+ }
+
+ private void addImage(String url) {
+ Label label = new Label(composite, 0);
+ label.setImage(window.getIcon(url));
+ label.setLayoutData(LayoutFactory.createGridData());
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.*;
+import ie.dcu.segment.Segmenter;
+import ie.dcu.util.OsUtils;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * Manages application actions.
+ *
+ * @author Kevin McGuinness
+ */
+public class ActionManager {
+ private static final Logger log = Logger.getLogger("ActionManager");
+
+ private final AppWindow window;
+ private final Map<String, AppAction> actions;
+ private ArrayList<SelectSegmenterAction> segmenterActions;
+ private Properties props;
+
+
+ public ActionManager(AppWindow window) {
+
+ this.window = window;
+ this.actions = new HashMap<String, AppAction>();
+
+ try {
+ this.props = Application.loadProperties(getPropertiesFile());
+ } catch (IOException e) {
+ log.severe("Error loading action properties: " +
+ e.getLocalizedMessage());
+ throw new RuntimeException(e);
+ }
+
+ init();
+ }
+
+
+ private String getPropertiesFile() {
+ return OsUtils.isMacOS() ? "actions.mac" : "actions";
+ }
+
+
+ Properties getProperties() {
+ return props;
+ }
+
+
+ AppWindow getWindow() {
+ return window;
+ }
+
+
+ public void init() {
+ add(new OpenAction(this));
+ add(new SaveAction(this));
+ add(new SaveAsAction(this));
+ add(new AboutAction(this));
+ add(new CopyAction(this));
+ add(new ExitAction(this));
+ add(new ExportViewAction(this));
+ add(new ExportImageMapAction(this));
+ add(new ExportTransparentPNGAction(this));
+ add(new HelpAction(this));
+ add(new PreferencesAction(this));
+ add(new PrintAction(this));
+ add(new RedoAction(this));
+ add(new UndoAction(this));
+ add(new NextAction(this));
+ add(new PreviousAction(this));
+ add(new OpenExperimentAction(this));
+ add(new ConfigureSegmenterAction(this));
+
+ // Add select segmenter actions
+ SegmenterRegistry registry = SegmenterRegistry.getInstance();
+ for (Segmenter s : registry) {
+ add(new SelectSegmenterAction(this, registry, s));
+ }
+ }
+
+
+ public void add(AppAction action) {
+ actions.put(action.id(), action);
+ }
+
+
+ public <T extends AppAction> T get(Class<T> clazz) {
+ return clazz.cast(actions.get(id(clazz)));
+ }
+
+
+ public <T extends AppAction> T get(Class<T> clazz, String id) {
+ return clazz.cast(actions.get(id));
+ }
+
+
+ public void setEnabled(String id, boolean enabled) {
+ AppAction action = actions.get(id);
+ if (action != null) {
+ action.setEnabled(enabled);
+ }
+ }
+
+
+ public <T extends AppAction> void setEnabled(Class<T> clazz, boolean enabled) {
+ AppAction action = get(clazz);
+ if (action != null) {
+ action.setEnabled(enabled);
+ }
+ }
+
+
+ public Collection<SelectSegmenterAction> getSegmentationActions() {
+ if (segmenterActions == null) {
+ segmenterActions = new ArrayList<SelectSegmenterAction>();
+ for (AppAction a : actions.values()) {
+ if (a instanceof SelectSegmenterAction) {
+ segmenterActions.add((SelectSegmenterAction) a);
+ }
+ }
+ }
+
+ return Collections.unmodifiableCollection(segmenterActions);
+ }
+
+
+ private String id(Class<?> clazz) {
+ return clazz.getName();
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.util.logging.*;
+
+import org.eclipse.jface.dialogs.*;
+import org.eclipse.swt.graphics.*;
+
+import ie.dcu.apps.ist.*;
+
+/**
+ * Root class for all application actions.
+ *
+ * @author Kevin McGuinness
+ */
+public class AppAction extends ConfiguredAction {
+
+ // Make easier for subclasses to set status
+ protected static final AppStatus Information = AppStatus.Information;
+ protected static final AppStatus Warning = AppStatus.Warning;
+ protected static final AppStatus Error = AppStatus.Error;
+
+ // Logger
+ protected final Logger log = Logger.getLogger(getClass().getSimpleName());
+
+ // Window
+ protected final AppWindow window;
+
+ // Action manager
+ protected final ActionManager manager;
+
+
+ public AppAction(ActionManager m) {
+ super(m.getProperties());
+ manager = m;
+ window = m.getWindow();
+ }
+
+
+ public AppAction(ActionManager m, int style) {
+ super(m.getProperties(), style);
+ manager = m;
+ window = m.getWindow();
+ }
+
+
+ @Override
+ public void arm() {
+ super.arm();
+ status(AppStatus.Information, getDescription());
+ }
+
+
+ @Override
+ public void disarm() {
+ super.disarm();
+ window.setStatus(null);
+ }
+
+
+ public String id() {
+ return getClass().getName();
+ }
+
+
+ protected Image icon(String key) {
+ String prop = string(key);
+ if (prop != null) {
+ return window.getIcon(prop);
+ }
+ return null;
+ }
+
+
+ protected void error(String message) {
+ MessageDialog.openError(window.getShell(), "Error", message);
+ }
+
+
+ protected void error(String format, Object... args) {
+ error(String.format(format, args));
+ }
+
+
+ protected void warning(String message) {
+ MessageDialog.openWarning(window.getShell(), "Warning", message);
+ }
+
+
+ protected void warning(String format, Object... args) {
+ warning(String.format(format, args));
+ }
+
+
+ protected void info(String message) {
+ MessageDialog.openInformation(window.getShell(), "Information", message);
+ }
+
+
+ protected void info(String format, Object... args) {
+ info(String.format(format, args));
+ }
+
+
+ protected void log(Level level, String message, Throwable th) {
+ log.log(level, message, th);
+ }
+
+
+ protected String property(String key) {
+ return properties.getProperty(key);
+ }
+
+
+ protected void status(AppStatus s, String format, Object... args) {
+ window.status(s, format, args);
+ }
+
+
+ protected void status(String format, Object... args) {
+ status(Information, format, args);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.views.SegmentationView;
+
+/**
+ * Show the configure segmenter dialog.
+ *
+ * @author Kevin McGuinness
+ */
+public class ConfigureSegmenterAction extends AppAction {
+
+ public ConfigureSegmenterAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ SegmentationView view = window.getView();
+ if (view.canShowOptions()) {
+ view.showSegmenterOptions();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.net.*;
+import java.util.*;
+import java.util.logging.*;
+
+import org.eclipse.jface.action.*;
+import org.eclipse.jface.resource.*;
+
+/**
+ * Hover Action configured from a properties file.
+ *
+ * @author Kevin McGuinness
+ */
+public class ConfiguredAction extends HoverAction {
+ private static final Logger log = Logger.getLogger("ConfiguredAction");
+
+ protected final Properties properties;
+ private final String id;
+
+
+ public ConfiguredAction(Properties properties) {
+ this(properties, null, AS_PUSH_BUTTON);
+ }
+
+
+ public ConfiguredAction(Properties properties, int style) {
+ this(properties, null, style);
+ }
+
+
+ public ConfiguredAction(Properties properties, String id, int style) {
+ super(null, style);
+
+ assert (properties != null);
+ this.properties = properties;
+
+ if (id != null) {
+ this.id = id;
+ } else {
+ this.id = getClass().getSimpleName();
+ }
+
+ configure();
+ }
+
+
+ private void configure() {
+
+ // Set id
+ setId(id);
+
+ // Set text
+ setText(string("text"));
+
+ // Set description
+ String description;
+ if ((description = string("description")) != null) {
+ setDescription(description);
+ }
+
+ // Set tooltip
+ String tooltip;
+ if ((tooltip = string("tooltip")) != null) {
+ setToolTipText(tooltip);
+ }
+
+ // Set accelerator
+ Integer keycode;
+ if ((keycode = accelerator("accelerator")) != null) {
+ setAccelerator(keycode);
+ }
+
+ // Set images
+ ImageDescriptor im;
+
+ if ((im = image("image")) != null) {
+ setImageDescriptor(im);
+ }
+
+ if ((im = image("hoverImage")) != null) {
+ setHoverImageDescriptor(im);
+ }
+
+ if ((im = image("disabledImage")) != null) {
+ setDisabledImageDescriptor(im);
+ }
+
+ // Set status
+ Boolean status;
+
+ if ((status = bool("checked") != null)) {
+ setChecked(status);
+ }
+
+ if ((status = bool("enabled") != null)) {
+ setEnabled(status);
+ }
+ }
+
+
+ protected final Integer accelerator(String key) {
+ String value = string(key);
+
+ if (value == null) {
+ return null;
+ }
+
+ int code = Action.convertAccelerator(value);
+
+ if (code == 0) {
+ log.warning("No accelerator: " + value);
+ return null;
+ }
+
+ if (code == -1) {
+ log.warning("Not a valid accelerator keycode: " + value);
+ return null;
+ }
+
+ return code;
+ }
+
+
+ protected final Boolean bool(String key) {
+ String value = string(key);
+
+ if (value == null) {
+ return null;
+ }
+
+ if (value.trim().equals("true")) {
+ return true;
+ } else if (value.trim().equals("false")) {
+ return false;
+ } else {
+ log.warning("Not a boolean value: " + value);
+ return null;
+ }
+ }
+
+
+ protected final ImageDescriptor image(String key) {
+ String url = string(key);
+
+ if (url == null) {
+ return null;
+ }
+
+ try {
+ return ImageDescriptor.createFromURL(new URL(url));
+
+ } catch (MalformedURLException e) {
+ log.warning("Invalid URL for image " + url);
+ return null;
+ }
+ }
+
+
+ protected final String string(String key) {
+ return properties.getProperty(key(key));
+ }
+
+
+ protected final String key(String name) {
+ return String.format("action.%s.%s", id, name);
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.views.SegmentationView;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.segment.painters.SegmentationPainter;
+import ie.dcu.swt.*;
+
+import java.awt.Toolkit;
+import java.awt.datatransfer.*;
+import java.awt.image.BufferedImage;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Copy view to clipboard.
+ *
+ * @author Kevin McGuinness
+ */
+public class CopyAction extends AppAction {
+
+ public CopyAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+
+ if (window.hasContext()) {
+ copy();
+ }
+ }
+
+
+ private void copy() {
+ setClipboard(paintContext());
+ status("View copied to clipboard as image");
+ }
+
+
+ private static BufferedImage convert(Image image) {
+ BufferedImage im = ImageConverter.convert(image.getImageData());
+ image.dispose();
+ return im;
+ }
+
+
+ public void setClipboard(Image image) {
+ ImageSelection selection = new ImageSelection(convert(image));
+ Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
+ clip.setContents(selection, null);
+ }
+
+
+
+ private Image paintContext() {
+ SegmentationView view = window.getView();
+ SegmentationContext ctx = window.getContext();
+ SegmentationPainter painter = view.getPainter();
+ ObservableImage im = createCompatibleImage(ctx);
+ painter.paint(ctx, im);
+ return im.getImage();
+ }
+
+
+ private ObservableImage createCompatibleImage(SegmentationContext ctx) {
+ Image im = new Image(Display.getCurrent(), ctx.getBounds());
+ return new ObservableImage(im);
+ }
+
+
+ private static class ImageSelection implements Transferable {
+ private BufferedImage image;
+
+
+ public ImageSelection(BufferedImage image) {
+ this.image = image;
+ }
+
+
+ public DataFlavor[] getTransferDataFlavors() {
+ return new DataFlavor[] { DataFlavor.imageFlavor };
+ }
+
+
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return DataFlavor.imageFlavor.equals(flavor);
+ }
+
+
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException
+ {
+ if (!DataFlavor.imageFlavor.equals(flavor)) {
+ throw new UnsupportedFlavorException(flavor);
+ }
+ return image;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.*;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+
+/**
+ * Terminate the application.
+ *
+ * @author Kevin McGuinness
+ */
+public class ExitAction extends AppAction {
+ public ExitAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (confirmExit(window)) {
+ window.close();
+ System.exit(0);
+ }
+ }
+
+ public static boolean confirmExit(AppWindow window) {
+ boolean confirm = window.getPrefs().get(
+ Boolean.class, AppPrefs.Keys.CONFIRM_EXIT, true);
+
+ if (confirm) {
+ return MessageDialog.openConfirm(
+ window.getShell(), "Confirm",
+ "Really exit the application?");
+ }
+
+ return true;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.dialogs.ExportDialog;
+import ie.dcu.apps.ist.export.imagemap.*;
+import ie.dcu.segment.*;
+import ie.dcu.swt.ImageConverter;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.logging.Level;
+
+import org.eclipse.swt.program.Program;
+
+/**
+ * Export segmentation results as HTML image maps
+ *
+ * @author Kevin McGuinness
+ */
+public class ExportImageMapAction extends AppAction {
+
+ public ExportImageMapAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+
+ if (window.hasContext()) {
+
+ // Get options from user
+ ExportDialog dialog = new ExportDialog(window.getShell());
+ ExportDialog.Result result = dialog.open();
+
+ if (result != null) {
+
+ // Grab image and mask
+ SegmentationContext ctx = window.getContext();
+ SegmentationMask mask = ctx.getMask();
+ BufferedImage image = ImageConverter.convert(ctx.getImageData());
+
+ // Setup exporter
+ Exporter exporter = new Exporter(image, mask);
+ exporter.setEffect(result.effect);
+ exporter.setHtmlFile(result.html);
+ exporter.setImageFile(result.image);
+ exporter.setObjectLink(result.link);
+ exporter.setExportShape(result.shape);
+ exporter.setObjectDescription(result.description);
+
+ // Export
+ try {
+ exporter.export(result.folder);
+ } catch (IOException e) {
+ handleError(e);
+ return;
+ } catch (ExportException e) {
+ handleError(e);
+ return;
+ }
+
+ if (result.open) {
+ File file = new File(result.folder, result.html);
+ Program program = Program.findProgram(".html");
+ if (program != null) {
+ program.execute(file.getAbsolutePath());
+ }
+ }
+ }
+ }
+ }
+
+ private void handleError(Exception e) {
+ log(Level.WARNING, "Error exporting view as HTML image map", e);
+
+ // Show error dialog
+ error("Error exporting view as HTML image map: %s",
+ e.getLocalizedMessage());
+
+ // Set status message
+ status(Error, "Error exporting view as HTML image map");
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.segment.*;
+import ie.dcu.swt.SwtUtils;
+import ie.dcu.util.FileUtils;
+
+import java.io.*;
+import java.util.logging.Level;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.FileDialog;
+
+public class ExportTransparentPNGAction extends AppAction {
+
+private static final String DEFAULT_SUFFIX = ".png";
+
+ private FileDialog dialog;
+ private String directory;
+
+ public ExportTransparentPNGAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ File file = getFile();
+ if (file != null) {
+ directory = file.getParent();
+ export(file);
+ }
+ }
+ }
+
+
+ private void export(File file) {
+ ImageData im = createSemiTransparentImage();
+ try {
+ SwtUtils.saveImage(im, file);
+ } catch (IOException e) {
+ handleError(file, e);
+ }
+ }
+
+
+ private ImageData createSemiTransparentImage() {
+ SegmentationContext ctx = window.getContext();
+ ImageData image = ctx.getImageData();
+ SegmentationMask mask = ctx.getMask();
+ ImageData result = new ImageData(image.width, image.height, 32, image.palette);
+
+ int[] pixels = new int[image.width];
+ byte[] alphas = new byte[image.width];
+
+ for (int y = 0; y < image.height; y++) {
+ image.getPixels(0, y, image.width, pixels, 0);
+
+ int offset = y * mask.cols;
+ for (int i = 0; i < mask.cols; i++) {
+ if (mask.values[i+offset] == SegmentationMask.BACKGROUND) {
+ alphas[i] = (byte) 0x00;
+ } else {
+ alphas[i] = (byte) 0xff;
+ }
+ }
+
+ result.setPixels(0, y, image.width, pixels, 0);
+ result.setAlphas(0, y, image.width, alphas, 0);
+ }
+
+ return result;
+ }
+
+
+ private void handleError(File file, IOException e) {
+ // Log
+ log(Level.WARNING, "Error exporting view as image", e);
+
+ // Show error dialog
+ error("Error exporting view as image %s: %s",
+ file.getName(), e.getLocalizedMessage()
+ );
+
+ // Set status message
+ status(Error, "Error exporting view as image %s", file.getName());
+ }
+
+
+ private File getFile() {
+ createExportDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return checkFile(path);
+ }
+
+ return null;
+ }
+
+
+ private File checkFile(String path) {
+ File file = new File(path);
+
+ if (SwtUtils.getImageFormat(file) != -1) {
+ return file;
+ }
+
+ // Unknown file extension, so assume jpg
+ return new File(FileUtils.replaceExtension(path, DEFAULT_SUFFIX));
+ }
+
+
+ private void createExportDialog() {
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.SAVE | SWT.SHEET);
+ dialog.setText("Export Transparent PNG");
+ dialog.setFilterExtensions(new String[]{"*.png"});
+ dialog.setFilterNames(new String[]{"PNG Images"});
+ }
+
+ dialog.setFileName(getFileName());
+
+ String dir = getDirectory();
+ if (dir != null) {
+ // Unfortunately this is buggy on SWT/GTK and
+ // the filter path isin't always set :'-(
+ dialog.setFilterPath(dir);
+ }
+ }
+
+ private String getFileName() {
+ File file = window.getContext().getFile();
+ return FileUtils.replaceExtension(file.getName(), DEFAULT_SUFFIX);
+ }
+
+
+ private String getDirectory() {
+ if (directory == null) {
+ File folder = window.getContext().getFolder();
+ directory = (folder != null) ?
+ directory = folder.getAbsolutePath() : null;
+ }
+
+ return directory;
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+
+import ie.dcu.apps.ist.views.SegmentationView;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.segment.painters.SegmentationPainter;
+import ie.dcu.swt.*;
+import ie.dcu.util.FileUtils;
+
+import java.io.*;
+import java.util.logging.Level;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.*;
+
+
+/**
+ * Export current view as image.
+ *
+ * @author Kevin McGuinness
+ */
+public class ExportViewAction extends AppAction {
+
+ private static final String DEFAULT_SUFFIX = ".png";
+
+ private FileDialog dialog;
+ private String directory;
+
+ public ExportViewAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ File file = getFile();
+ if (file != null) {
+ directory = file.getParent();
+ export(file);
+ }
+ }
+ }
+
+
+ private void export(File file) {
+ Image im = paintContext();
+
+ try {
+ SwtUtils.saveImage(im, file);
+ status("View successfully exported as %s", file.getName());
+
+ } catch (IOException e) {
+
+ handleError(file, e);
+
+ } finally {
+
+ im.dispose();
+ }
+ }
+
+
+ private void handleError(File file, IOException e) {
+ // Log
+ log(Level.WARNING, "Error exporting view as image", e);
+
+ // Show error dialog
+ error("Error exporting view as image %s: %s",
+ file.getName(), e.getLocalizedMessage()
+ );
+
+ // Set status message
+ status(Error, "Error exporting view as image %s", file.getName());
+ }
+
+
+ private File getFile() {
+ createExportDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return checkFile(path);
+ }
+
+ return null;
+ }
+
+
+ private File checkFile(String path) {
+ File file = new File(path);
+
+ if (SwtUtils.getImageFormat(file) != -1) {
+ return file;
+ }
+
+ // Unknown file extension, so assume jpg
+ return new File(FileUtils.replaceExtension(path, DEFAULT_SUFFIX));
+ }
+
+
+ private void createExportDialog() {
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.SAVE | SWT.SHEET);
+ dialog.setText(property("ExportViewAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+ }
+
+ dialog.setFileName(getFileName());
+
+ String dir = getDirectory();
+ if (dir != null) {
+ // Unfortunately this is buggy on SWT/GTK and
+ // the filter path isin't always set :'-(
+ dialog.setFilterPath(dir);
+ }
+ }
+
+ private String getFileName() {
+ File file = window.getContext().getFile();
+ return FileUtils.replaceExtension(file.getName(), DEFAULT_SUFFIX);
+ }
+
+
+ private String getDirectory() {
+ if (directory == null) {
+ File folder = window.getContext().getFolder();
+ directory = (folder != null) ?
+ directory = folder.getAbsolutePath() : null;
+ }
+
+ return directory;
+ }
+
+
+
+ private String[] getFilters() {
+ return new String[] { property("ExportViewAction.dialog.filter.exts") };
+ }
+
+
+ private String[] getFilterNames() {
+ return new String[] { property("ExportViewAction.dialog.filter.text") };
+ }
+
+
+ private Image paintContext() {
+ SegmentationView view = window.getView();
+ SegmentationContext ctx = window.getContext();
+ SegmentationPainter painter = view.getPainter();
+ ObservableImage im = createCompatibleImage(ctx);
+ painter.paint(ctx, im);
+ return im.getImage();
+ }
+
+
+ private ObservableImage createCompatibleImage(SegmentationContext ctx) {
+ Image im = new Image(Display.getCurrent(), ctx.getBounds());
+ return new ObservableImage(im);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.swt.layout.LayoutFactory;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Show a help dialog.
+ *
+ * @author Kevin McGuinness
+ */
+public class HelpAction extends AppAction {
+
+ private HelpBox dialog;
+
+
+ public HelpAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ if (dialog == null) {
+ dialog = new HelpBox();
+ }
+
+ if (dialog.available()) {
+ dialog.open();
+ } else {
+ info("Help browser unavailable on this system");
+ }
+ }
+
+
+ /**
+ * Help dialog box, just wraps a browser.
+ */
+ private class HelpBox {
+ private Shell shell;
+ private Composite content;
+ private Browser browser;
+
+
+ public HelpBox() {
+ createShell();
+ createContent();
+ }
+
+
+ private void createShell() {
+ shell = new Shell(window.getShell(), SWT.SHELL_TRIM);
+
+ // Prevent the shell from disposing on close
+ shell.addShellListener(new ShellAdapter() {
+ public void shellClosed(ShellEvent evt) {
+ evt.doit = false;
+ close();
+ }
+ });
+
+ // Use a fill layout
+ shell.setLayout(new GridLayout());
+
+ // Set size and title
+ shell.setSize(640, 480);
+ shell.setText("Help");
+ }
+
+
+ private void createContent() {
+ // Create content pane
+ content = new Composite(shell, SWT.BORDER);
+ content.setLayout(new FillLayout());
+ content.setLayoutData(LayoutFactory.createGridData());
+
+ // Add browser widget
+ try {
+ browser = new Browser(content, SWT.NONE);
+ } catch (SWTError e) {
+
+ // Log warning and return
+ log.warning("Browser unavailable: " + e.getMessage());
+ browser = null;
+ return;
+ }
+
+ // Go to help home page
+ home();
+ }
+
+
+ public void home() {
+ String urlString = string("helpURL");
+ if (urlString != null) {
+ go(urlString);
+ } else {
+ browser.setText("Help file is unavailable");
+ }
+ }
+
+
+ public void go(String url) {
+ browser.setUrl(url);
+ }
+
+
+ public boolean available() {
+ return browser != null;
+ }
+
+
+ public void open() {
+ shell.setVisible(true);
+ }
+
+
+ public void close() {
+ shell.setVisible(false);
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import org.eclipse.jface.action.*;
+import org.eclipse.jface.resource.*;
+
+/**
+ * Default implementation of {@link IHoverAction}.
+ *
+ * @author Kevin McGuinness
+ */
+public class HoverAction extends Action implements IHoverAction {
+ private boolean armed = false;
+
+ /**
+ * Creates a new action with no text and no image. Configure the action later
+ * using the set methods.
+ */
+ public HoverAction() {
+ super();
+ }
+
+
+ /**
+ * Creates a new action with the given text and no image.
+ *
+ * @param text
+ * the string used as the text for the action, or <code>null</code>
+ * if there is no text
+ */
+ public HoverAction(String text) {
+ super(text);
+ }
+
+
+ /**
+ * Creates a new action with the given text and image.
+ *
+ * @param text
+ * the action's text, or <code>null</code> if there is no text
+ * @param image
+ * the action's image, or <code>null</code> if there is no image
+ */
+ public HoverAction(String text, ImageDescriptor image) {
+ super(text, image);
+ }
+
+
+ /**
+ * Creates a new action with the given text and style.
+ *
+ * @param text
+ * the action's text, or <code>null</code> if there is no text.
+ * @param style
+ * one of
+ * <code>AS_PUSH_BUTTON</code>,
+ * <code>AS_CHECK_BOX</code>,
+ * <code>AS_DROP_DOWN_MENU</code>,
+ * <code>AS_RADIO_BUTTON</code>, and
+ * <code>AS_UNSPECIFIED</code>.
+ */
+ public HoverAction(String text, int style) {
+ super(text, style);
+ }
+
+
+ /**
+ * Sets the armed property to <code>true</code>.
+ */
+ public void arm() {
+ armed = true;
+ }
+
+
+ /**
+ * Sets the armed property to <code>false</code>.
+ */
+ public void disarm() {
+ armed = false;
+ }
+
+
+ /**
+ * Returns <code>true</code> if armed.
+ */
+ public boolean isArmed() {
+ return armed;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.widgets.ImageMenuManager;
+
+import java.net.URL;
+import java.util.*;
+
+import org.eclipse.jface.action.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Menu Manager that notifies instances of IHoverAction when they are armed and
+ * disarmed.
+ *
+ * @author Kevin McGuinness
+ */
+public class HoverMenuManager extends ImageMenuManager {
+
+ /**
+ * Stores items that have hover listeners added to them.
+ */
+ private final Set<MenuItem> hoverItems = new HashSet<MenuItem>();
+
+
+ /**
+ * Create hover menu manager.
+ */
+ public HoverMenuManager() {
+ init();
+ }
+
+
+ /**
+ * Create hover menu manager with id and text.
+ *
+ * @param text
+ * the text for the menu, or <code>null</code> if none.
+ * @param imageURL
+ * the menu image url, or <code>null</code>.
+ */
+ public HoverMenuManager(String text, URL imageURL) {
+ super(text, imageURL);
+ init();
+ }
+
+
+ /**
+ * Create hover menu manager text.
+ *
+ * @param text
+ * the text for the menu, or <code>null</code> if none.
+ */
+ public HoverMenuManager(String text) {
+ super(text);
+ init();
+ }
+
+
+ private void init() {
+ addMenuListener(menuListener);
+ }
+
+
+ private void addHoverListeners() {
+ Menu menu = getMenu();
+ for (MenuItem item : menu.getItems()) {
+ addHoverListener(item);
+ }
+ }
+
+
+ private void addHoverListener(MenuItem item) {
+ IHoverAction action = getHoverAction(item);
+ if (action != null) {
+ addHoverListener(item, action);
+ }
+ }
+
+
+ private void addHoverListener(MenuItem item, IHoverAction action) {
+ if (!hoverItems.contains(item)) {
+ item.addArmListener(new HoverListener(action));
+ hoverItems.add(item);
+ }
+ }
+
+
+ private void disarmAll() {
+ Menu menu = getMenu();
+ for (MenuItem item : menu.getItems()) {
+ disarm(item);
+ }
+ }
+
+
+ private void disarm(MenuItem item) {
+ IHoverAction action = getHoverAction(item);
+ if (action != null) {
+ disarm(action);
+ }
+ }
+
+
+ private void disarm(IHoverAction action) {
+ if (action.isArmed()) {
+ action.disarm();
+ }
+ }
+
+
+ private IHoverAction getHoverAction(MenuItem item) {
+ Object data = item.getData();
+ if (data instanceof ActionContributionItem) {
+ IAction action = ((ActionContributionItem) data).getAction();
+
+ if (action instanceof IHoverAction) {
+ return (IHoverAction) action;
+ }
+ }
+ return null;
+ }
+
+
+ private IMenuListener menuListener = new IMenuListener2() {
+ public void menuAboutToShow(IMenuManager manager) {
+ addHoverListeners();
+ }
+
+ public void menuAboutToHide(IMenuManager manager) {
+ disarmAll();
+ }
+ };
+
+
+ private final class HoverListener implements ArmListener {
+ private final IHoverAction action;
+
+ public HoverListener(IHoverAction action) {
+ this.action = action;
+ }
+
+
+ public void widgetArmed(ArmEvent e) {
+ disarmAll();
+ action.arm();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import org.eclipse.jface.action.*;
+
+/**
+ * Extension of IAction that supports arming when the mouse hovers over the
+ * item. This may be useful for displaying descriptions of menu items in the
+ * status bar, for example. To use hover actions the {@link HoverMenuManager}
+ * must be used.
+ *
+ * @author Kevin McGuinness
+ */
+public interface IHoverAction extends IAction {
+
+
+ /**
+ * Executed when the action is hovered/armed.
+ */
+ public void arm();
+
+
+ /**
+ * Executed when the action is de-hovered/disarmed.
+ */
+ public void disarm();
+
+
+ /**
+ * Query if the action is armed.
+ *
+ * @return <code>true</code> if armed.
+ */
+ public boolean isArmed();
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Jump to the next image in the directory
+ *
+ * @author Kevin McGuinness
+ */
+public class NextAction extends AppAction {
+ // Supported file extensions
+ static final String EXTENSIONS = "jpg;jpeg;png;gif;bmp;ctx";
+
+ private final java.io.FileFilter imageFilter;
+
+
+ public NextAction(ActionManager m) {
+ super(m);
+
+ // Create an image file filter
+ imageFilter = new SimpleFileFilter(EXTENSIONS);
+ }
+
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ // Get currently opened file
+ File file = window.getContext().getFile();
+
+ // Find the one after & open it
+ File next = findNext(file);
+ if (next != null) {
+ manager.get(OpenAction.class).open(next);
+ }
+ }
+ }
+
+
+ private File findNext(File file) {
+ File dir = file.getParentFile();
+
+ if (dir != null) {
+
+ // List the images
+ File[] files = dir.listFiles(imageFilter);
+
+ // Need at least one
+ if (files.length > 1) {
+
+ // Search for ourself...
+ for (int i = 0; i < files.length; i++) {
+
+ if (files[i].equals(file)) {
+
+ // Use next file (wrap back to first)
+ return files[(i+1)%files.length];
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
+
+class SimpleFileFilter implements java.io.FileFilter {
+
+ /**
+ * Holds the list of extensions. It is expected it will be quite
+ * short, so we use an ArrayList.
+ */
+ private final ArrayList<String> extensions;
+
+
+ /**
+ * Construct a simple file extension filter based on a semicolon separated
+ * list of file extensions.
+ *
+ * @param extensions
+ * A list of file extensions (ex. jpeg;jpg;png)
+ */
+ public SimpleFileFilter(String extensions) {
+ String[] exts = extensions.split(";");
+ this.extensions = new ArrayList<String>(exts.length);
+
+ for (String s : exts) {
+ if ((s = s.trim()).length() != 0) {
+ if (s.startsWith("*")) {
+ s = s.substring(1);
+ }
+
+ if (!s.startsWith(".")) {
+ s = "." + s;
+ }
+
+ this.extensions.add(s);
+ }
+ }
+ }
+
+
+ public boolean accept(File pathname) {
+ if (pathname.isFile()) {
+ String name = pathname.getName();
+
+ // List of extensions should be fairly short..
+ for (String ext : extensions) {
+ if (name.endsWith(ext)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.*;
+import ie.dcu.segment.SegmentationContext;
+
+import java.io.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Open an image or segmentation context.
+ *
+ * @author Kevin McGuinness
+ */
+public class OpenAction extends AppAction {
+
+ private FileDialog dialog;
+
+ public OpenAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ File file = getFile();
+ if (file != null) {
+ open(file);
+ }
+ }
+
+
+ public boolean open(File file) {
+ String name = file.getName();
+ try {
+ if (SegmentationContext.isContextFile(file)) {
+
+ // Load context
+ window.setContext(SegmentationContext.load(file));
+ status("Opened segmentation context %s successfully", name);
+ } else {
+
+ // Create context
+ window.setContext(SegmentationContext.create(file));
+ status("Opened image file %s successfully", name);
+ }
+
+ // Save history
+ AppRecentFiles.getInstance().add(file);
+
+ // Ok
+ return true;
+
+ } catch (IOException e) {
+
+ handleError(file, e);
+ status(Warning, "Proplem opening file %s", name);
+
+ return false;
+ }
+ }
+
+
+ private void handleError(File file, IOException e) {
+
+ // Get appropriate message
+ String message = e.getCause() == null ? e.getLocalizedMessage() :
+ e.getCause().getLocalizedMessage();
+
+ // Log warning
+ log.warning(String.format(
+ "Unable to open %s\n Problem: %s", file, message
+ ));
+
+ // Show warning dialog
+ warning("Unable to open %s:\n%s", file.getName(), message);
+ }
+
+
+ private void createOpenDialog() {
+ // Create dialog if necessary
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.OPEN | SWT.SHEET);
+ dialog.setText(property("OpenAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+ }
+ }
+
+ private String[] getFilters() {
+ return new String[] { property("OpenAction.dialog.filter.exts") };
+ }
+
+ private String[] getFilterNames() {
+ return new String[] { property("OpenAction.dialog.filter.text") };
+ }
+
+
+ private File getFile() {
+ createOpenDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.exp.*;
+
+import java.io.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Open an experiment file and switch to experiment mode.
+ *
+ * @author Kevin McGuinness
+ */
+public class OpenExperimentAction extends AppAction {
+
+ private FileDialog dialog;
+
+
+ public OpenExperimentAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ File file = getFile();
+ if (file != null) {
+ open(file);
+ }
+ }
+
+
+ private void open(File file) {
+ try {
+ Experiment ex = ExperimentFactory.getInstance().load(file);
+ window.setExperiment(ex);
+
+ } catch (IOException ex) {
+ handleError(file, ex);
+ } catch (FormatException ex) {
+ handleError(file, ex);
+ }
+ }
+
+
+ private void handleError(File file, Exception ex) {
+ // Get appropriate message
+ String mesg = ex.getMessage();
+
+ // Log warning
+ log.warning(String.format(
+ "Unable to open experiment file %s\n Problem: %s", file, mesg
+ ));
+
+ // Show warning dialog
+ warning("Unable to open experiment file %s:\n%s", file.getName(), mesg);
+ }
+
+
+ private File getFile() {
+ createOpenDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+ }
+
+
+ private void createOpenDialog() {
+ // Create dialog if necessary
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.OPEN | SWT.SHEET);
+ dialog.setText(property("OpenExperimentAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+ }
+ }
+
+ private String[] getFilters() {
+ return new String[] {
+ property("OpenExperimentAction.dialog.filter.exts")
+ };
+ }
+
+ private String[] getFilterNames() {
+ return new String[] {
+ property("OpenExperimentAction.dialog.filter.text")
+ };
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+
+/**
+ * Action to open a recent file.
+ *
+ * @author Kevin McGuinness
+ */
+public class OpenRecentAction extends AppAction {
+ private final File file;
+
+ public OpenRecentAction(ActionManager m, File file) {
+ super(m);
+ this.file = file;
+ setText(file.getName());
+ String path = file.getAbsolutePath();
+ setToolTipText(path);
+ setDescription(String.format("Open file %s", path));
+ }
+
+ public void run() {
+ manager.get(OpenAction.class).open(file);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.dialogs.*;
+
+/**
+ * Show preferences dialog box.
+ *
+ * @author Kevin McGuinness
+ */
+public class PreferencesAction extends AppAction {
+ private PrefsDialog dialog;
+
+ public PreferencesAction(ActionManager m) {
+ super(m);
+ }
+
+
+ public void run() {
+ if (dialog == null) {
+ dialog = new PrefsDialog(window);
+ }
+ dialog.open();
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+
+/**
+ * Jump to previous file in current directory.
+ *
+ * @author Kevin McGuinness
+ */
+public class PreviousAction extends AppAction {
+ private final java.io.FileFilter imageFilter;
+
+
+ public PreviousAction(ActionManager m) {
+ super(m);
+
+ // Create an image file filter
+ imageFilter = new SimpleFileFilter(NextAction.EXTENSIONS);
+ }
+
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ // Get currently opened file
+ File file = window.getContext().getFile();
+
+ // Find the one before & open it
+ File next = findPrevious(file);
+ if (next != null) {
+ manager.get(OpenAction.class).open(next);
+ }
+ }
+ }
+
+
+
+ private File findPrevious(File file) {
+ File dir = file.getParentFile();
+
+ if (dir != null) {
+
+ // List the images
+ File[] files = dir.listFiles(imageFilter);
+
+ // Need at least one
+ if (files.length > 1) {
+
+ // Search for ourself...
+ for (int i = 0; i < files.length; i++) {
+
+ if (files[i].equals(file)) {
+
+ // Use previous file (wrap back to last)
+ return files[(i > 0) ? i - 1 : files.length - 1];
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+
+import ie.dcu.apps.ist.views.SegmentationView;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.segment.painters.SegmentationPainter;
+import ie.dcu.swt.ObservableImage;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.printing.*;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Send current view to printer.
+ *
+ * @author Kevin McGuinness
+ */
+public class PrintAction extends AppAction {
+
+ private static final String JOB_NAME = "Interactive Segmentation Tool";
+ private PrintDialog dialog;
+
+ public PrintAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ status("Printing...");
+ PrinterData data = getPrintData();
+ if (data != null) {
+ print(data);
+ status("Printing complete");
+ } else {
+ status(null);
+ }
+ }
+ }
+
+
+ private PrinterData getPrintData() {
+ createPrintDialog();
+ return dialog.open();
+ }
+
+
+ private void print(PrinterData data) {
+ Printer printer = new Printer(data);
+ try {
+ print(printer);
+
+ } finally {
+ printer.dispose();
+ }
+ }
+
+
+ private void print(Printer printer) {
+ if (printer.startJob(JOB_NAME)) {
+ if (printer.startPage()) {
+ GC gc = new GC(printer);
+ try {
+ paint(gc, printer);
+ } finally {
+ gc.dispose();
+ }
+ printer.endPage();
+ }
+ printer.endJob();
+ }
+ }
+
+
+ private void paint(GC gc, Printer printer) {
+ Image im = paintContext();
+ try {
+ gc.drawImage(im, 30, 30);
+ } finally {
+ im.dispose();
+ }
+ }
+
+
+ private void createPrintDialog() {
+ if (dialog == null) {
+ dialog = new PrintDialog(window.getShell(), SWT.SHEET);
+ }
+ }
+
+
+ private Image paintContext() {
+ SegmentationView view = window.getView();
+ SegmentationContext ctx = window.getContext();
+ SegmentationPainter painter = view.getPainter();
+ ObservableImage im = createCompatibleImage(ctx);
+ painter.paint(ctx, im);
+ return im.getImage();
+ }
+
+
+ private ObservableImage createCompatibleImage(SegmentationContext ctx) {
+ Image im = new Image(Display.getCurrent(), ctx.getBounds());
+ return new ObservableImage(im);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.views.SegmentationView;
+
+
+/**
+ * Redo last annotation action.
+ *
+ * @author Kevin McGuinness
+ */
+public class RedoAction extends AppAction {
+ public RedoAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ SegmentationView view = window.getView();
+ if (view.canRedo()) {
+ view.redo();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+import java.util.logging.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Save segmentation context.
+ *
+ * @author Kevin McGuinness
+ */
+public class SaveAction extends AppAction {
+
+ private FileDialog dialog;
+
+ public SaveAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+
+ if (window.hasContext()) {
+
+ File file;
+ if (window.hasContextFile()) {
+ file = window.getContextFile();
+ } else {
+ file = getFile();
+ }
+
+ if (file != null) {
+ save(file);
+ }
+ }
+ }
+
+
+ private void createSaveDialog() {
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.SAVE | SWT.SHEET);
+ dialog.setText(property("SaveAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+
+ String dir = getDirectory();
+ if (dir != null) {
+ dialog.setFilterPath(dir);
+ }
+ }
+
+ dialog.setFileName(getFileName());
+ }
+
+
+ private String getDirectory() {
+ File folder = window.getContext().getFolder();
+ if (folder != null) {
+ return folder.getAbsolutePath();
+ }
+
+ return null;
+ }
+
+
+ private String getFileName() {
+ return window.getContext().getDefaultFilename();
+ }
+
+
+ private String[] getFilters() {
+ return new String[] { property("SaveAction.dialog.filter.exts") };
+ }
+
+
+ private String[] getFilterNames() {
+ return new String[] { property("SaveAction.dialog.filter.text") };
+ }
+
+
+ private File getFile() {
+ createSaveDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+ }
+
+
+ private void save(File file) {
+ try {
+ window.getContext().save(file);
+ window.updateWindowTitle();
+ status("Saved context %s successfully", file.getName());
+ } catch (IOException e) {
+ handleError(file, e);
+ }
+ }
+
+
+ private void handleError(File file, IOException e) {
+
+ // Log
+ log(Level.SEVERE, "Error saving file", e);
+
+ // Show error dialog
+ error("Error saving segmentation context %s: %s",
+ file.getName(), e.getLocalizedMessage()
+ );
+
+ // Set status message
+ status(Error, "Error saving segmentation context %s", file.getName());
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+import java.util.logging.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Save segmentation context as.
+ *
+ * @author Kevin McGuinness
+ */
+public class SaveAsAction extends AppAction {
+
+ private FileDialog dialog;
+
+ public SaveAsAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ File file = getFile();
+ if (file != null) {
+ save(file);
+ }
+ }
+ }
+
+
+ private void createSaveDialog() {
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.SAVE | SWT.SHEET);
+ dialog.setText(property("SaveAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+
+ String dir = getDirectory();
+ if (dir != null) {
+ dialog.setFilterPath(dir);
+ }
+ }
+
+ dialog.setFileName(getFileName());
+ }
+
+
+ private String getDirectory() {
+ File folder = window.getContext().getFolder();
+ if (folder != null) {
+ return folder.getAbsolutePath();
+ }
+
+ return null;
+ }
+
+
+
+ private String getFileName() {
+ return window.getContext().getDefaultFilename();
+ }
+
+
+ private String[] getFilters() {
+ return new String[] { property("SaveAction.dialog.filter.exts") };
+ }
+
+
+ private String[] getFilterNames() {
+ return new String[] { property("SaveAction.dialog.filter.text") };
+ }
+
+
+ private File getFile() {
+ createSaveDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+ }
+
+
+ private void save(File file) {
+ try {
+ window.getContext().save(file);
+ window.updateWindowTitle();
+ status("Saved context %s successfully", file.getName());
+ } catch (IOException e) {
+ handleError(file, e);
+ }
+ }
+
+
+ private void handleError(File file, IOException e) {
+
+ // Log
+ log(Level.SEVERE, "Error saving file", e);
+
+ // Show error dialog
+ error("Error saving segmentation context %s: %s",
+ file.getName(), e.getLocalizedMessage()
+ );
+
+ // Set status message
+ status(Error, "Error saving segmentation context %s", file.getName());
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.SegmenterRegistry;
+import ie.dcu.apps.ist.views.SegmentationView;
+import ie.dcu.segment.Segmenter;
+
+/**
+ * Segmenter radio buttons.
+ *
+ * @author Kevin McGuinness
+ */
+public class SelectSegmenterAction extends AppAction {
+ private final Segmenter segmenter;
+
+ public SelectSegmenterAction(ActionManager m, SegmenterRegistry r, Segmenter s) {
+ super(m, AS_RADIO_BUTTON);
+ this.segmenter = s;
+ setText(s.getName());
+ setToolTipText(s.getDescription());
+
+ if (!s.isAvailable()) {
+ super.setEnabled(false);
+ setChecked(false);
+
+ String naText = string("NA");
+ setToolTipText(naText);
+ setDescription(naText);
+ } else {
+ setChecked(r.isDefault(s));
+ }
+ }
+
+ public void setEnabled(boolean enabled) {
+ // Disallow enabling unavailable segmentation algorithms
+ if (!enabled || (segmenter.isAvailable() && enabled)) {
+ super.setEnabled(enabled);
+ }
+ }
+
+
+ public Segmenter getSegmenter() {
+ return segmenter;
+ }
+
+
+ @Override
+ public String id() {
+ return segmenter.getClass().getName();
+ }
+
+
+ @Override
+ public void run() {
+ if (isChecked()) {
+ SegmentationView view = window.getView();
+ view.setSegmenter(segmenter);
+ }
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.views.SegmentationView;
+
+/**
+ * Undo last annotation.
+ *
+ * @author Kevin McGuinness
+ */
+public class UndoAction extends AppAction {
+ public UndoAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ SegmentationView view = window.getView();
+ if (view.canUndo()) {
+ view.undo();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.controllers;
+
+
+
+import ie.dcu.segment.annotate.*;
+import ie.dcu.swt.*;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.Canvas;
+
+public class AnnotationTool extends MouseMotionAdapter {
+ private final AnnotationManager manager;
+ private ImageControl view;
+ private Canvas canvas;
+
+ private Annotation current;
+ private AnnotationType type;
+ private int lineWidth;
+ private Point last;
+ private boolean invert;
+
+
+ public AnnotationTool(AnnotationManager manager) {
+ this (manager, null);
+ }
+
+ /**
+ *
+ * @param manager
+ * @param view
+ */
+ public AnnotationTool(AnnotationManager manager, ImageControl view) {
+ assert (manager != null);
+
+ this.manager = manager;
+ this.current = null;
+ this.lineWidth = 1;
+ this.type = AnnotationType.Foreground;
+
+ attach(view);
+ }
+
+
+ /**
+ *
+ * @param view
+ */
+ public void attach(ImageControl view) {
+ detach();
+
+ if (view != null) {
+ this.view = view;
+ canvas = view.getCanvas();
+ canvas.addMouseListener(this);
+ canvas.addMouseMoveListener(this);
+ }
+ }
+
+
+
+ public void detach() {
+ if (view != null) {
+ canvas.removeMouseListener(this);
+ canvas.removeMouseMoveListener(this);
+ view = null;
+ canvas = null;
+ current = null;
+ }
+ }
+
+
+ /**
+ * Returns the image control associated with the annotation tool.
+ */
+ public ImageControl getView() {
+ return view;
+ }
+
+
+ /**
+ * @return the line width
+ */
+ public int getLineWidth() {
+ return lineWidth;
+ }
+
+
+ /**
+ * @param width the line width to set
+ */
+ public void setLineWidth(int width) {
+ assert (width > 0);
+ this.lineWidth = width;
+ }
+
+
+ /**
+ * @return the annotation type
+ */
+ public AnnotationType getType() {
+ return type;
+ }
+
+
+ /**
+ * @param type the annotation type to set
+ */
+ public void setType(AnnotationType type) {
+ assert (type != null);
+ this.type = type;
+ }
+
+
+
+ public void begin(Point pt) {
+
+ // Create a new annotation
+ current = new Annotation(getTypeForUse(), lineWidth, view.canvasToImage(pt));
+
+ // Give immediate visual feedback
+ feedback(pt);
+
+ // Set last
+ last = pt;
+ }
+
+
+ public void append(Point pt) {
+
+ // Update the annotation
+ current.add(view.canvasToImage(pt));
+
+ // Give immediate visual feedback
+ feedback(last, pt);
+
+ // Set last
+ last = pt;
+ }
+
+
+ public void commit(Point pt) {
+ // Clip points to image bounds
+ clip(last, pt);
+
+ // Add point to annotation
+ current.add(view.canvasToImage(pt));
+
+ // Add annotation to annotation manager
+ manager.add(current);
+
+ // Give immediate visual feedback
+ feedback(last, pt);
+
+ // Mark current annotation as null
+ current = null;
+ }
+
+
+ public void cancel() {
+
+ // Repaint dirty area
+ view.repaint(current.getBounds());
+
+ // Discard annotation
+ current = null;
+ }
+
+
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ if (view == null || !view.isEnabled()) {
+ current = null;
+ return;
+ }
+
+ ObservableImage image = view.getImage();
+ if (image != null) {
+
+ if (e.button == 1 || e.button == 3) {
+
+ Point pt = new Point(e.x, e.y);
+
+ if (view.imageContains(pt)) {
+
+ // Check for right mouse button or ctrl modifier
+
+ if (e.button == 3 || (e.stateMask & SWT.CTRL) != 0) {
+ // Invert type
+ invert = true;
+ } else {
+ invert = false;
+ }
+
+ if (current != null) {
+ // Cancel last action
+ cancel();
+ }
+
+ begin(pt);
+ }
+ }
+ }
+
+ return;
+ }
+
+
+ @Override
+ public void mouseMove(MouseEvent e) {
+ if (view == null || !view.isEnabled()) {
+ current = null;
+ return;
+ }
+
+ if (current != null) {
+ Point pt = new Point(e.x, e.y);
+
+ if (view.imageContains(pt)) {
+ // Normal case: append
+ append(pt);
+
+ } else {
+ // Line drawn outside image
+
+ if (view.getImage() != null) {
+
+ commit(pt);
+
+ } else {
+ // No image: cancel
+ cancel();
+ }
+ }
+ }
+
+ }
+
+
+ @Override
+ public void mouseUp(MouseEvent e) {
+ if (view == null || !view.isEnabled()) {
+ current = null;
+ return;
+ }
+
+ if (current != null) {
+ Point pt = new Point(e.x, e.y);
+
+ if (view.imageContains(pt)) {
+
+ // Normal case: commit
+ commit(pt);
+ } else {
+
+ // Line drawn outside image
+ if (view.getImage() != null) {
+
+ commit(pt);
+ } else {
+
+ // No image
+ cancel();
+ }
+ }
+ }
+ }
+
+
+ private void clip(Point p, Point q) {
+ SwtUtils.clip(view.getCanvasImageBounds(), p, q);
+ }
+
+
+ private GC createFeedbackGC() {
+ GC gc = new GC(canvas);
+ gc.setAntialias(SWT.ON);
+ gc.setLineCap(SWT.CAP_ROUND);
+ gc.setLineJoin(SWT.JOIN_ROUND);
+ gc.setLineWidth((int) Math.floor(lineWidth * view.getZoom()));
+ gc.setForeground(getTypeForUse().getColor());
+ return gc;
+ }
+
+
+ private void feedback(Point p1) {
+ feedback(p1, new Point(p1.x+1, p1.y));
+ }
+
+
+ private void feedback(Point p1, Point p2) {
+ GC gc = createFeedbackGC();
+ gc.setClipping(getClip(p1, p2));
+ gc.drawLine(p1.x, p1.y, p2.x, p2.y);
+ gc.dispose();
+ }
+
+
+ private Rectangle getClip(Point p1, Point p2) {
+ int x1 = Math.min(p1.x, p2.x) - lineWidth;
+ int x2 = Math.max(p1.x, p2.x) + lineWidth;
+ int y1 = Math.min(p1.y, p2.y) - lineWidth;
+ int y2 = Math.max(p1.y, p2.y) + lineWidth;
+ Rectangle r = new Rectangle(x1, y1, x2 - x1, y2 - y1);
+ return r.intersection(view.getCanvasImageBounds());
+ }
+
+
+ private AnnotationType getTypeForUse() {
+ return (invert) ? type.invert() : type;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.controllers;
+
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.widgets.*;
+
+public class MouseMotionAdapter extends MouseAdapter implements MouseMoveListener {
+
+ public MouseMotionAdapter() {
+ }
+
+
+ public MouseMotionAdapter(Control control) {
+ add(control);
+ }
+
+
+ public void add(Control control) {
+ control.addMouseListener(this);
+ control.addMouseMoveListener(this);
+ }
+
+
+ public void mouseMove(MouseEvent e) {
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.dialogs;
+
+import ie.dcu.apps.ist.export.imagemap.AreaShape;
+import ie.dcu.apps.ist.export.imagemap.RolloverEffect;
+import ie.dcu.swt.SwtUtils;
+import ie.dcu.swt.layout.LayoutFactory;
+
+import java.io.File;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Dialog;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.MessageBox;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class ExportDialog extends Dialog {
+
+ public class Result {
+ public final File folder;
+ public final String html;
+ public final String image;
+ public final String link;
+ public final String description;
+ public final AreaShape shape;
+ public final RolloverEffect effect;
+ public final boolean open;
+
+ Result() {
+ folder = new File(folderText.getText().trim());
+ html = htmlText.getText().trim();
+ image = imageText.getText().trim();
+ link = linkText.getText().trim();
+ description = descriptionText.getText();
+ shape = getShape();
+ effect = getEffect();
+ open = openButton.getSelection();
+ }
+ };
+
+ // Result
+ private Result result;
+
+ // Top level components
+ private Shell shell;
+ private Composite content;
+ private Composite widgets;
+ private Composite buttons;
+
+ // Widgets
+ private Text folderText;
+ private Button browseButton;
+ private Text htmlText;
+ private Text imageText;
+ private Text linkText;
+ private Button effectCheck;
+ private Combo effectCombo;
+ private Button[] shapeRadios;
+ private Text descriptionText;
+ private Button openButton;
+
+ // Dialog buttons
+ private Button cancelButton;
+ private Button exportButton;
+
+ // Effects
+ private final String[] ROLLOVER_EFFECTS = {
+ "Outline Object", "Darken Background", "Highlight Object"
+ };
+
+ // Shapes
+ private final String[] SHAPES = {
+ "Polygon", "Rectangle", "Circle"
+ };
+
+ // Default title
+ private static final String TITLE = "Export HTML Image Map";
+
+ public ExportDialog(Shell shell) {
+ super(shell);
+ setText(TITLE);
+ }
+
+ public ExportDialog(Shell shell, int style) {
+ super(shell, style);
+ setText(TITLE);
+ }
+
+ public Result open() {
+ Shell parent = getParent();
+ shell = new Shell(parent, SWT.DIALOG_TRIM |
+ SWT.APPLICATION_MODAL | SWT.SHEET);
+ shell.setText(getText());
+ shell.setLayout(new FillLayout());
+
+ createUI();
+
+ shell.pack();
+
+ SwtUtils.center(parent, shell);
+
+ shell.open();
+ Display display = parent.getDisplay();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) display.sleep();
+ }
+
+ return result;
+ }
+
+ private void createUI() {
+ // Overall layout
+ content = new Composite(shell, 0);
+ content.setLayout(LayoutFactory.createGridLayout(0, 0, 1, false));
+ widgets = new Composite(content, 0);
+ widgets.setLayout(LayoutFactory.createGridLayout(10, 5, 3, false));
+ widgets.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ hline(content);
+ buttons = new Composite(content, 0);
+ buttons.setLayout(LayoutFactory.createGridLayout(10, 5, 3, false));
+ buttons.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+
+ // Form content
+ label(widgets, "Export folder:");
+ folderText = text(widgets, "");
+ browseButton = button(widgets, "Browse...");
+ label(widgets, "HTML file name:");
+ htmlText = text(widgets, "imagemap.html");
+ spacer(widgets);
+ label(widgets, "Image file name:");
+ imageText = text(widgets, "image.png");
+ spacer(widgets);
+ hline(widgets);
+ label(widgets, "Export shape:");
+ shapeRadios = radios(widgets, SHAPES);
+ // TODO: Enable when available
+ shapeRadios[2].setEnabled(false);
+ spacer(widgets);
+ label(widgets, "Object link:");
+ linkText = text(widgets, "http://");
+ spacer(widgets);
+ label(widgets, "Object description:");
+ descriptionText = text(widgets, "");
+ spacer(widgets);
+ effectCheck = checkbox(widgets, "Rollover effect:", true, 1);
+ effectCombo = combo(widgets, ROLLOVER_EFFECTS);
+ spacer(widgets);
+ hline(widgets);
+ openButton = checkbox(widgets,
+ "Open image map in browser after export completes", true, 3);
+
+ // Button bar
+ expander(buttons);
+ cancelButton = new Button(buttons, SWT.PUSH);
+ cancelButton.setLayoutData(layout4());
+ cancelButton.setText("Cancel");
+ exportButton = new Button(buttons, SWT.PUSH);
+ exportButton.setLayoutData(layout4());
+ exportButton.setText("Export");
+
+ // Set the default button
+ shell.setDefaultButton(exportButton);
+
+ // Add listeners
+ addListeners();
+ }
+
+ private void addListeners() {
+
+ browseButton.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event e) {
+ String dir = getDirectory();
+ if (dir != null) {
+ folderText.setText(dir);
+ }
+ }
+ });
+
+ cancelButton.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event e) {
+ result = null;
+ shell.dispose();
+ }
+ });
+
+ exportButton.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event e) {
+ if (validate()) {
+ result = new Result();
+ shell.dispose();
+ }
+ }
+ });
+
+ effectCheck.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event e) {
+ effectCombo.setEnabled(effectCheck.getSelection());
+ }
+ });
+ }
+
+ private boolean validate() {
+
+ File folder = new File(folderText.getText());
+ if (!folder.isDirectory()) {
+ validationError("The export folder must be specified");
+ folderText.setFocus();
+ return false;
+ }
+
+ String htmlFile = htmlText.getText().trim();
+ if (htmlFile.equals("")) {
+ validationError("The HTML file name cannot be empty");
+ htmlText.setFocus();
+ return false;
+ }
+
+ String imageFile = imageText.getText().trim();
+ if (imageFile.equals("")) {
+ validationError("The image file name cannot be empty");
+ imageText.setFocus();
+ return false;
+ }
+
+ return true;
+ }
+
+ private void validationError(String message) {
+ MessageBox box = new MessageBox(shell, SWT.OK | SWT.ICON_INFORMATION);
+ box.setMessage(message);
+ box.setText("Invalid Input");
+ box.open();
+ }
+
+ private AreaShape getShape() {
+ for (Button bt : shapeRadios) {
+ if (bt.getSelection()) {
+ return AreaShape.valueOf(bt.getText());
+ }
+ }
+ return null;
+ }
+
+ private RolloverEffect getEffect() {
+ RolloverEffect effect = null;
+ if (effectCheck.getSelection()) {
+ String text = effectCombo.getText();
+ if (text.equals("Outline Object")) {
+ effect = RolloverEffect.outlineObject();
+ } else if (text.equals("Darken Background")) {
+ effect = RolloverEffect.darkenBackground();
+ } else if (text.equals("Highlight Object")) {
+ effect = RolloverEffect.brightenForeground();
+ }
+ }
+ return effect;
+ }
+
+ private String getDirectory() {
+ DirectoryDialog dialog = new DirectoryDialog(shell);
+ return dialog.open();
+ }
+
+ private Button[] radios(Composite parent, String ... items) {
+ Composite c = new Composite(parent, SWT.NONE);
+ c.setLayout(LayoutFactory.createGridLayout(5, 10, items.length, false));
+ c.setLayoutData(layout2());
+
+ Button[] bts = new Button[items.length];
+ for (int i = 0; i < bts.length; i++) {
+ bts[i] = new Button(c, SWT.RADIO);
+ bts[i].setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
+ bts[i].setText(items[i]);
+ bts[i].setSelection(i == 0);
+ }
+
+ return bts;
+ }
+
+ private Label hline(Composite parent) {
+ Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
+ GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
+ data.horizontalSpan = 3;
+ data.heightHint = 10;
+ label.setLayoutData(data);
+ return label;
+ }
+
+ private Label spacer(Composite parent) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ return label;
+ }
+
+ private Label expander(Composite parent) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ return label;
+ }
+
+ private Combo combo(Composite parent, String ... items) {
+ Combo combo = new Combo(parent, SWT.READ_ONLY);
+ combo.setItems(items);
+ combo.setLayoutData(layout2());
+ if (items.length > 0) {
+ combo.select(0);
+ }
+ return combo;
+ }
+
+ private Button checkbox(Composite parent, String text,
+ boolean checked, int span) {
+
+ Button bt = new Button(parent, SWT.CHECK);
+ bt.setText(text);
+ bt.setSelection(checked);
+ GridData data = layout1();
+ data.horizontalSpan = span;
+ bt.setLayoutData(data);
+ return bt;
+ }
+
+ private Label label(Composite parent, String text) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setText(text);
+ label.setLayoutData(layout1());
+ return label;
+ }
+
+ private Text text(Composite parent, String value) {
+ Text text = new Text(parent, SWT.BORDER | SWT.SINGLE);
+ text.setText(value);
+ text.setLayoutData(layout2());
+ return text;
+ }
+
+ private Button button(Composite parent, String text) {
+ Button bt = new Button(parent, SWT.PUSH);
+ bt.setText(text);
+ bt.setLayoutData(layout3());
+ return bt;
+ }
+
+ private GridData layout1() {
+ return new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ }
+
+ private GridData layout2() {
+ return new GridData(SWT.FILL, SWT.CENTER, true, false);
+ }
+
+ private GridData layout3() {
+ return new GridData(SWT.RIGHT, SWT.CENTER, false, false);
+ }
+
+ private GridData layout4() {
+ return new GridData(SWT.RIGHT, SWT.BOTTOM, false, false);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.dialogs;
+
+import static ie.dcu.segment.annotate.AnnotationType.*;
+import ie.dcu.apps.ist.*;
+import ie.dcu.apps.ist.AppPrefs.Keys;
+import ie.dcu.apps.ist.widgets.ColorSelector;
+import ie.dcu.swt.SwtUtils;
+import ie.dcu.swt.layout.LayoutFactory;
+
+import java.util.*;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+public class PrefsDialog {
+ private static final String APPLICATION = "application";
+ private static final String STATUS = "status";
+ private static final String VIEW = "view";
+ private static final String TITLE = "title";
+ private static final String GENERAL = "general";
+ private static final String CLOSE = "close";
+ private static final String RESET = "reset";
+ private static final String TEXT = "text";
+ private static final String ICON = "icon";
+ private static final String TTIP = "ttip";
+ private static final String EXPERIMENT = "experiment";
+
+ private final AppWindow window;
+ private final AppPrefs prefs;
+ private final Shell shell;
+ private final Composite content;
+ private final TabFolder folder;
+ private final Composite buttonBar;
+ private final Composite generalTab;
+ private final Button resetButton;
+ private final Button closeButton;
+ private final HashMap<String, Object> controls;
+
+ private transient GridData gd;
+
+
+ public PrefsDialog(AppWindow window) {
+ this.window = window;
+ this.prefs = window.getPrefs();
+ this.shell = createShell();
+ this.content = createContent();
+ this.folder = createTabFolder();
+ this.buttonBar = createButtonBar();
+ this.generalTab = createTab(GENERAL);
+ this.resetButton = createResetButton();
+ this.closeButton = createCloseButton();
+ this.controls = new HashMap<String, Object>();
+
+ createControls();
+ loadPreferences();
+
+ SwtUtils.center(window.getShell(), shell);
+ }
+
+
+ private void createControls() {
+ // Initialize general tab
+ createBanner(generalTab, APPLICATION);
+
+ createLabel(generalTab, Keys.CONFIRM_EXIT);
+ createCheckBox(generalTab, Keys.CONFIRM_EXIT);
+
+ createBanner(generalTab, STATUS);
+
+ createLabel(generalTab, Keys.ENABLE_FEEDBACK);
+ createCheckBox(generalTab, Keys.ENABLE_FEEDBACK);
+
+ createBanner(generalTab, VIEW);
+
+ createLabel(generalTab, Keys.FOREGROUND_COLOR);
+ createColorSelector(generalTab, Keys.FOREGROUND_COLOR);
+
+ createLabel(generalTab, Keys.BACKGROUND_COLOR);
+ createColorSelector(generalTab, Keys.BACKGROUND_COLOR);
+
+ createBanner(generalTab, EXPERIMENT);
+
+ createLabel(generalTab, Keys.EXPERIMENT_EMBEDDED);
+ createCheckBox(generalTab, Keys.EXPERIMENT_EMBEDDED);
+ }
+
+
+ private void loadPreferences() {
+
+ // Load confirm exit
+ loadBool(Keys.CONFIRM_EXIT, true);
+
+ // Load enable feedback
+ loadBool(Keys.ENABLE_FEEDBACK, true);
+
+ // Load foreground color
+ loadColor(Keys.FOREGROUND_COLOR, DEFAULT_FOREGROUND);
+
+ // Load background color
+ loadColor(Keys.BACKGROUND_COLOR, DEFAULT_BACKGROUND);
+
+ // Load experiment embedded
+ loadBool(Keys.EXPERIMENT_EMBEDDED, false);
+ }
+
+
+ private Shell createShell() {
+ Shell s = new Shell(window.getShell(), SWT.SHELL_TRIM | SWT.SHEET);
+
+ // Add listener
+ s.addListener(SWT.Close, adapter);
+
+ // Use a fill layout
+ s.setLayout(new FillLayout());
+
+ // Set size and title
+ s.setSize(420, 450);
+ s.setText(property(TITLE, "Preferences"));
+
+ return s;
+ }
+
+
+ private Composite createContent() {
+
+ // Create content pane with grid layout
+ Composite c = new Composite(shell, 0);
+ c.setLayout(new GridLayout());
+ return c;
+ }
+
+
+ private TabFolder createTabFolder() {
+ // Create folder
+ TabFolder f = new TabFolder(content, SWT.TOP);
+
+ // Layout folder
+ f.setLayoutData(LayoutFactory.createGridData());
+
+ return f;
+ }
+
+
+ private Composite createButtonBar() {
+ // Create bar
+ Composite bar = new Composite(content, SWT.NONE);
+
+ // Layout bar
+ bar.setLayout(LayoutFactory.createGridLayout(0, 0, 2, true));
+ bar.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false));
+
+ return bar;
+ }
+
+
+ private Composite createTab(String name) {
+ // Create item
+ TabItem item = new TabItem(folder, SWT.NONE);
+ item.setText(property(name, "tab", name));
+
+ // Create control
+ Composite control = new Composite(folder, SWT.NONE);
+ control.setLayout(LayoutFactory.createGridLayout(10, 5, 2, false));
+
+ // Set control
+ item.setControl(control);
+
+ // Return control (item can be ignored)
+ return control;
+ }
+
+
+ private Button createResetButton() {
+ // Create button
+ Button bt = createButton(buttonBar, RESET, SWT.PUSH);
+
+ // Layout button
+ gd = new GridData(SWT.LEFT, SWT.BOTTOM, true, false);
+ gd.minimumWidth = 85;
+ bt.setLayoutData(gd);
+
+ // Done
+ return bt;
+ }
+
+
+ private Button createCloseButton() {
+ // Create button
+ Button bt = createButton(buttonBar, CLOSE, SWT.PUSH);
+
+ // Layout button
+ gd = new GridData(SWT.RIGHT, SWT.BOTTOM, true, false);
+ gd.minimumWidth = 85;
+ bt.setLayoutData(gd);
+
+ // Done
+ return bt;
+ }
+
+
+ private Button createCheckBox(Composite tab, String key) {
+ Button bt = new Button(tab, SWT.CHECK);
+
+ // Layout button
+ gd = new GridData(SWT.RIGHT, SWT.CENTER, false, false);
+ gd.horizontalIndent = 5;
+ bt.setLayoutData(gd);
+
+ // Add listener
+ bt.addListener(SWT.Selection, adapter);
+ bt.setData(key);
+
+ // Put into controls
+ controls.put(key, bt);
+
+ // Done
+ return bt;
+ }
+
+
+ private ColorSelector createColorSelector(Composite tab, String key) {
+ ColorSelector cs = new ColorSelector(tab);
+
+ // Layout button
+ gd = new GridData(SWT.RIGHT, SWT.CENTER, false, false);
+ gd.horizontalIndent = 10;
+ gd.minimumWidth = 80;
+ cs.setLayoutData(gd);
+
+ // Add listener
+ cs.addListener(SWT.Selection, adapter);
+ cs.setData(key);
+
+ // Put into controls
+ controls.put(key, cs);
+
+ // Done
+ return cs;
+ }
+
+
+ private Label createBanner(Composite tab, String key) {
+ // Create label
+ Label lb = new Label(tab, SWT.NONE);
+ lb.setText(property(key, key));
+ lb.setFont(JFaceResources.getBannerFont());
+
+ // Layout label
+ gd = new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1);
+ gd.verticalIndent = 5;
+ lb.setLayoutData(gd);
+
+ return lb;
+ }
+
+
+ private Label createLabel(Composite tab, String key) {
+ // Create label
+ Label lb = new Label(tab, SWT.NONE);
+ lb.setText(property(key, TEXT, key));
+
+ // Layout label
+ gd = new GridData(SWT.LEFT, SWT.CENTER, true, false);
+ gd.horizontalIndent = 5;
+ lb.setLayoutData(gd);
+
+ return lb;
+ }
+
+
+ private Button createButton(Composite parent, String key, int style) {
+ Button bt = new Button(parent, style);
+
+ // Configure button
+ configure(bt, key);
+
+ // Add listener
+ bt.addListener(SWT.Selection, adapter);
+
+ // Done
+ return bt;
+ }
+
+
+ protected void handleEvent(Event event) {
+ switch (event.type) {
+ case SWT.Close:
+ handleClose(event);
+ break;
+ case SWT.Selection:
+ handleSelection(event);
+ break;
+ }
+
+ }
+
+
+ private void handleClose(Event event) {
+ // Prevent the shell from disposing on close
+ event.doit = false;
+ close();
+ }
+
+
+ private void handleSelection(Event event) {
+ if (event.widget == closeButton) {
+ close();
+ } else if (event.widget == resetButton) {
+ reset();
+ } else {
+ Object data = event.widget.getData();
+ if (data instanceof String) {
+ handleSelection(event, (String) data);
+ }
+ }
+
+ }
+
+
+ private void handleSelection(Event event, String key) {
+
+ if (key.equals(Keys.ENABLE_FEEDBACK)) {
+ storeBool(key);
+
+ } else if (key.equals(Keys.FOREGROUND_COLOR)) {
+
+ storeColor(key);
+
+ } else if (key.equals(Keys.BACKGROUND_COLOR)) {
+
+ storeColor(key);
+
+ } else if (key.equals(Keys.EXPERIMENT_EMBEDDED)) {
+
+ storeBool(key);
+
+ } else if (key.equals(Keys.CONFIRM_EXIT)) {
+
+ storeBool(key);
+ }
+
+ }
+
+
+ private void storeBool(String key) {
+ Boolean bool = control(Button.class, key).getSelection();
+ prefs.put(Boolean.class, key, bool);
+ }
+
+
+ private void storeColor(String key) {
+ RGB color = control(ColorSelector.class, key).getColor();
+ if (color != null) {
+ prefs.put(RGB.class, key, color);
+ }
+ }
+
+
+ private void loadBool(String key, boolean def) {
+ Boolean bool = prefs.get(Boolean.class, key, def);
+ control(Button.class, key).setSelection(bool);
+ }
+
+
+ private void loadColor(String key, RGB def) {
+ RGB color = prefs.get(RGB.class, key, def);
+ control(ColorSelector.class, key).setColor(color);
+ }
+
+
+ public void reset() {
+ // Set defaults
+ prefs.clear();
+
+ // Reload
+ loadPreferences();
+ }
+
+
+ public void close() {
+ shell.setVisible(false);
+ }
+
+
+ public void open() {
+ shell.setVisible(true);
+ }
+
+
+ private void configure(Button bt, String key) {
+ // Read properties
+ String text = property(key, TEXT, "");
+ String icon = property(key, ICON, null);
+ String ttip = property(key, TTIP, null);
+
+ // Set values
+ bt.setText(text);
+ bt.setImage(image(icon));
+ bt.setToolTipText(ttip);
+ bt.setData(key);
+ }
+
+
+ private <T> T control(Class<T> clazz, String key) {
+ return clazz.cast(controls.get(key));
+ }
+
+
+ private String property(String key, String def) {
+ Properties p = window.getProperties();
+ return p.getProperty(String.format("PrefsDialog.%s", key), def);
+ }
+
+
+ private String property(String key, String type, String def) {
+ Properties p = window.getProperties();
+ String prop = String.format("PrefsDialog.%s.%s", key, type);
+ return p.getProperty(prop, def);
+ }
+
+
+ private Image image(String url) {
+ return (url != null) ? window.getIcon(url) : null;
+ }
+
+
+ private final Listener adapter = new Listener() {
+ public void handleEvent(Event event) {
+ PrefsDialog.this.handleEvent(event);
+ }
+ };
+}
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+public interface ContextChangeListener extends EventListener {
+ public void contextChanged(ContextChangedEvent evt);
+}
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+
+import ie.dcu.segment.SegmentationContext;
+
+import java.util.*;
+
+
+public class ContextChangedEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+ public final SegmentationContext oldContext;
+ public final SegmentationContext newContext;
+
+ public ContextChangedEvent(
+ Object source,
+ SegmentationContext oldContext,
+ SegmentationContext newContext
+ ) {
+ super(source);
+ this.oldContext = oldContext;
+ this.newContext = newContext;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+public class StateEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+ public StateEvent(Object source) {
+ super(source);
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+/**
+ * @author Kevin McGuinness
+ */
+public interface StateListener extends EventListener {
+ public void stateChanged(StateEvent evt);
+}
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+
+import ie.dcu.apps.ist.widgets.Ticker;
+
+import java.util.EventObject;
+
+public class TickerEvent extends EventObject {
+ private static final long serialVersionUID = 755693053854019177L;
+
+ private final long elapsed;
+
+ public TickerEvent(Ticker t, long elapsed) {
+ super(t);
+ this.elapsed = elapsed;
+ }
+
+ public Ticker getTicker() {
+ return (Ticker) getSource();
+ }
+
+ public long getElapsed() {
+ return elapsed;
+ }
+};
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+public interface TickerListener extends EventListener {
+ public void tick(TickerEvent evt);
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.event;
+
+
+import ie.dcu.apps.ist.widgets.SwtTimer;
+
+import java.util.EventObject;
+
+/**
+ * @author Kevin McGuinness
+ */
+public class TimeoutEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+ public TimeoutEvent(SwtTimer source) {
+ super(source);
+ }
+
+ public SwtTimer getTimer() {
+ return (SwtTimer) getSource();
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+/**
+ * @author Kevin McGuinness
+ */
+public interface TimeoutListener extends EventListener {
+ public void timeoutOccured(TimeoutEvent evt);
+}
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+import ie.dcu.eval.Evaluator;
+import ie.dcu.apps.ist.*;
+import ie.dcu.segment.Segmenter;
+
+import java.io.*;
+import java.util.*;
+
+public class Experiment {
+ public static final int MAX_TASKS = 2500;
+ public static final int MAX_SEGMENTERS = 50;
+ public static final int MAX_EVALUATORS = 100;
+
+ private String name;
+ private File dir;
+ private File outputFile;
+ private int time;
+ private List<Segmenter> segmenters;
+ private List<Evaluator> evaluators;
+ private List<Task> tasks;
+ private StorageSelection saveUserMasks;
+ private File saveUserMasksDir;
+
+ Experiment() {
+
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+
+ public File getDirectory() {
+ return dir;
+ }
+
+
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+
+ public int getTime() {
+ return time;
+ }
+
+
+ public StorageSelection getStorageSelection() {
+ return saveUserMasks;
+ }
+
+
+ public File getStorageDirectory() {
+ return saveUserMasksDir;
+ }
+
+
+ public List<Segmenter> getSegmenters() {
+ return Collections.unmodifiableList(segmenters);
+ }
+
+
+ public List<Evaluator> getEvaluators() {
+ return Collections.unmodifiableList(evaluators);
+ }
+
+
+ public List<Task> getTasks() {
+ return Collections.unmodifiableList(tasks);
+ }
+
+
+ public boolean using(Segmenter segmenter) {
+ for (Segmenter s : segmenters) {
+ if (s.equals(segmenter)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public void load(File file) throws IOException, FormatException {
+ InputStream in = null;
+ try {
+ in = new FileInputStream(file);
+ load(in);
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ }
+ }
+
+
+ public void load(InputStream in) throws IOException, FormatException {
+
+ // Create properties object
+ Properties props = new Properties();
+ props.load(in);
+
+ // Load name
+ name = props.getProperty("name", "Experiment");
+
+ // Load directory
+ dir = getDir(props, "dir");
+
+ // Load output file
+ outputFile = getOutputFile(props);
+
+ // Load task time
+ time = getTimeout(props, "time");
+
+ // Get save user masks selection
+ saveUserMasks = getStorageSelection(props, "save-user-masks");
+
+ // .. and directory
+ saveUserMasksDir = getUserMaskDir(props, "save-user-masks.dir");
+
+ // Load segmenters
+ segmenters = new ArrayList<Segmenter>();
+ for (int i = 1; i <= MAX_SEGMENTERS; i++) {
+ String key = "segmenters." + i;
+ String val = props.getProperty(key);
+
+ if (val == null) {
+ break;
+ }
+
+ Segmenter segmenter = findSegmenter(val);
+ if (segmenter != null) {
+ segmenters.add(segmenter);
+ } else {
+ exception("Segmenter not found: <%s>", val);
+ }
+ }
+
+ // Load evaluators
+ evaluators = new ArrayList<Evaluator>();
+ for (int i = 1; i <= MAX_EVALUATORS; i++) {
+ String key = "evaluators." + i;
+ String val = props.getProperty(key);
+
+ if (val == null) {
+ break;
+ }
+
+ Evaluator evaluator = findEvaluator(val);
+ if (evaluator != null) {
+ evaluators.add(evaluator);
+ } else {
+ exception("Evaluator not found: <%s>", val);
+ }
+ }
+
+ // Load tasks
+ tasks = new ArrayList<Task>();
+ for (int i = 1; i <= MAX_TASKS; i++) {
+
+ // Form keys
+ String imkey = String.format("task.%d.im", i);
+ String gtkey = String.format("task.%d.gt", i);
+ String desckey = String.format("task.%d.description", i);
+
+ // Check if we're done
+ if (props.getProperty(imkey) == null) {
+ break;
+ }
+
+ // Get image and ground truth file
+ File im = getFile(props, imkey);
+ File gt = getFile(props, gtkey);
+
+ // Get description
+ String desc = getDescription(props, desckey);
+
+ // Add task
+ tasks.add(new Task(im, gt, desc));
+ }
+ }
+
+
+ private static StorageSelection getStorageSelection(
+ Properties props, String key
+ ) {
+ String prop = props.getProperty(key);
+ if (prop != null) {
+ StorageSelection selection = StorageSelection.parse(prop);
+ if (selection != null) {
+ return selection;
+ }
+ }
+ return StorageSelection.None;
+ }
+
+
+ private File getUserMaskDir(Properties props, String key)
+ throws FormatException {
+
+ String fname = props.getProperty(key);
+ if (fname == null) {
+ fname = "user";
+ }
+
+ File file = new File(fname);
+ if (!file.isAbsolute()) {
+ file = new File(dir, fname);
+ }
+
+
+ if (!file.exists()) {
+ // Try and create it
+ if (!file.mkdirs()) {
+ String path = file.getAbsolutePath();
+ exception("Couldn't create directory %s", path);
+ }
+
+ } else if (!file.isDirectory()) {
+ String path = file.getAbsolutePath();
+ exception("%s is not a directory", path);
+ }
+
+ return file;
+ }
+
+
+ private static Segmenter findSegmenter(String name) {
+ return SegmenterRegistry.getInstance().find(name);
+ }
+
+
+ private static Evaluator findEvaluator(String name) {
+ return EvaluatorRegistry.getInstance().find(name);
+ }
+
+
+ private File getOutputFile(Properties props) {
+ String fname = props.getProperty("output", "output.txt");
+ File file = new File(fname);
+ if (file.isAbsolute()) {
+ return file;
+ }
+ return new File(dir, fname);
+ }
+
+
+
+ private static String getDescription(Properties props, String key)
+ throws FormatException {
+
+ String desc = props.getProperty(key);
+ if (desc == null) {
+ exception("Expected description property for %s", key);
+ }
+ return desc;
+ }
+
+
+ private File getFile(Properties props, String key)
+ throws FormatException {
+
+ String fname = props.getProperty(key);
+
+ if (fname == null) {
+ exception("Expected file property for %s", key);
+ }
+
+ File file = new File(fname);
+ if (file.isAbsolute()) {
+ if (file.isFile()) {
+ return file;
+ }
+ }
+
+ file = new File(dir, fname);
+ if (!file.isFile()) {
+ exception("File not found %s", file.getAbsolutePath());
+ }
+
+ return file;
+ }
+
+
+ private static File getDir(Properties props, String key)
+ throws FormatException {
+
+ String prop = props.getProperty(key);
+ if (prop == null) {
+ exception("Required directory element not found: %a",key);
+ }
+
+ File file = new File(prop);
+ if (!file.isDirectory()) {
+ exception("%s is not a directory", prop);
+ }
+
+ return file;
+ }
+
+
+ private static int getTimeout(Properties props, String key)
+ throws FormatException {
+
+ String prop = props.getProperty(key, "120");
+ try {
+ return Integer.parseInt(prop);
+ } catch (NumberFormatException ex) {
+ exception("%s is not an integer", key);
+ }
+
+ return 0;
+ }
+
+
+ private static void exception(String format, Object ... args)
+ throws FormatException
+ {
+ throw new FormatException(String.format(format, args));
+ }
+
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("name=<").append(name).append('>');
+ sb.append(" dir=<").append(dir.getAbsolutePath()).append('>');
+ sb.append(" output=<").append(outputFile.getAbsolutePath()).append('>');
+ sb.append(" time=<").append(time).append('>');
+
+ // Evaluators
+ sb.append(" evaluators={ ");
+ for (Evaluator e : evaluators) {
+ sb.append(e.getName()).append(' ');
+ }
+ sb.append('}');
+
+ // Tasks
+ sb.append(" tasks={ ");
+ for (Task t : tasks) {
+ sb.append(t).append(' ');
+ }
+ sb.append('}');
+
+ return sb.toString();
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.exp;
+
+import java.io.*;
+
+/**
+ * @author Kevin McGuinness
+ */
+public class ExperimentFactory {
+ private static ExperimentFactory instance;
+
+ protected ExperimentFactory() {
+
+ }
+
+ public static ExperimentFactory getInstance() {
+ if (instance == null) {
+ instance = new ExperimentFactory();
+ }
+ return instance;
+ }
+
+
+ public Experiment load(File file)
+ throws IOException, FormatException {
+
+ Experiment ex = new Experiment();
+ ex.load(file);
+ return ex;
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+import ie.dcu.eval.Evaluator;
+import ie.dcu.segment.SegmentationMask;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.apps.ist.AppWindow;
+import ie.dcu.apps.ist.actions.*;
+
+import java.io.*;
+import java.util.List;
+
+public class ExperimentManager {
+
+ private final AppWindow window;
+
+ private Experiment experiment;
+ private SegmentationContext activeContext;
+ private SegmentationMask referenceMask;
+ private ExperimentResults results;
+ private List<Task> tasks;
+ private Task activeTask;
+ private int progress;
+ private boolean aborted;
+
+
+ public ExperimentManager(AppWindow window) {
+ this.window = window;
+ }
+
+
+ public void beginExperiment(Experiment ex) throws IOException {
+ experiment = ex;
+ tasks = experiment.getTasks();
+ activeContext = null;
+ activeTask = null;
+ progress = 0;
+ results = new ExperimentResults(experiment.getOutputFile());
+ results.beginDocument();
+ results.beginExperiment(experiment);
+ }
+
+
+ public void endExperiment() throws IOException {
+ results.endExperiment();
+ results.flush();
+ results.close();
+ }
+
+
+ public void abortExperiment() {
+ results.endExperiment();
+ results.close();
+ aborted = true;
+ }
+
+
+ public void beginTask() throws IOException {
+ if (isComplete()) {
+ throw new IllegalStateException();
+ }
+
+ // Set active task
+ activeTask = tasks.get(progress);
+
+ // Load context into main window
+ loadContext();
+
+ // Load reference mask
+ loadReferenceMask();
+
+ // Write task header to results
+ results.beginTask(progress, activeTask);
+ }
+
+
+ public void endTask() throws IOException {
+
+ try {
+ // Save final mask
+ StorageSelection ss = experiment.getStorageSelection();
+ if (ss != StorageSelection.None) {
+ store(String.format("task-%d-final.png", progress));
+ }
+
+ } finally {
+
+ activeTask = null;
+ activeContext = null;
+ progress++;
+ results.endTask();
+ results.flush();
+ }
+ }
+
+
+ public boolean isComplete() {
+ return tasks.size() == progress || aborted;
+ }
+
+
+ public Experiment getExperiment() {
+ return experiment;
+ }
+
+
+ public String getTaskDescription() {
+ return activeTask.getDescription();
+ }
+
+
+ public Task getActiveTask() {
+ return activeTask;
+ }
+
+
+ public int getTaskCount() {
+ return tasks.size();
+ }
+
+
+ public int getProgress() {
+ return progress;
+ }
+
+
+ public SegmentationContext getActiveContext() {
+ return activeContext;
+ }
+
+
+ public List<Evaluator> getEvaluators() {
+ return experiment.getEvaluators();
+ }
+
+
+ public File getImageFile() {
+ return activeTask.getImageFile();
+ }
+
+
+ public SegmentationMask getCurrentMask() {
+ return activeContext.getMask();
+ }
+
+
+ public SegmentationMask getReferenceMask() {
+ return referenceMask;
+ }
+
+
+ public void evaluate(int elapsed) {
+
+ if (!getCurrentMask().isUnknown()) {
+ SegmentationMask im = getCurrentMask();
+ SegmentationMask gt = getReferenceMask();
+
+ results.beginEvaluation(elapsed);
+
+ for (Evaluator evaluator : experiment.getEvaluators()) {
+ // Execute evaluator
+ evaluator.run(im, gt);
+
+ // Add results
+ addResults(evaluator);
+ }
+
+ results.endEvaluation();
+ }
+ }
+
+
+ public void store(int elapsed) throws IOException {
+ // Save current mask
+ if (!getCurrentMask().isUnknown()) {
+ // Only save non empty masks
+ StorageSelection ss = experiment.getStorageSelection();
+ if (ss == StorageSelection.All) {
+ store(String.format("task-%d-time-%d.png", progress, elapsed));
+ }
+ }
+ }
+
+
+ private void store(String name) throws IOException {
+ SegmentationMask im = getCurrentMask();
+ File dir = experiment.getStorageDirectory();
+ File file = new File(dir, name);
+ im.save(file);
+ }
+
+
+ private void addResults(Evaluator evaluator) {
+ String[] measures = evaluator.getMeasures();
+ for (String measure : measures) {
+ double value = evaluator.getMeasure(measure);
+ results.addMeasure(measure, value);
+ }
+ }
+
+
+ private void loadReferenceMask() throws IOException {
+ referenceMask = new SegmentationMask(activeContext.getBounds());
+ referenceMask.load(activeTask.getMaskFile());
+ }
+
+
+ private void loadContext() throws IOException {
+ ActionManager actions = window.getActions();
+ OpenAction action = actions.get(OpenAction.class);
+
+ if (!action.open(getImageFile())) {
+ throw new IOException("Unable to load image file");
+ }
+
+ activeContext = window.getContext();
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+import java.io.*;
+
+public class ExperimentResults {
+ private static final String XML_HEADER =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n%n";
+
+ private static final String BEGIN_EXPERIMENT_TAG =
+ "<experiment name=\"%s\" time=\"%d\" dir=\"%s\">%n";
+
+ private static final String BEGIN_TASK_TAG =
+ " <task id=\"%d\" image=\"%s\" gt=\"%s\">%n";
+
+ private static final String BEGIN_EVALUATION_TAG =
+ " <evaluation time=\"%d\">%n";
+
+ private static final String MEASURE_TAG =
+ " <measure name=\"%s\" value=\"%.5f\" />%n";
+
+ private static final String END_EVALUATION_TAG =
+ " </evaluation>%n";
+
+ private static final String END_TASK_TAG =
+ " </task>%n";
+
+ private static final String END_EXPERIMENT_TAG =
+ "</experiment>%n";
+
+
+ private final PrintStream out;
+
+
+
+ ExperimentResults(File file) throws FileNotFoundException {
+ this(new FileOutputStream(file));
+ }
+
+
+ ExperimentResults(OutputStream out) {
+ this.out = new PrintStream(new BufferedOutputStream(out));
+ }
+
+
+ public void beginDocument() {
+ write(XML_HEADER);
+ }
+
+
+ public void beginExperiment(Experiment ex) {
+ String dir = ex.getDirectory().getAbsolutePath();
+ write(BEGIN_EXPERIMENT_TAG, ex.getName(), ex.getTime(), dir);
+ }
+
+
+ public void beginTask(int idx, Task task) {
+ String im = task.getImageFile().getName();
+ String gt = task.getMaskFile().getName();
+ write(BEGIN_TASK_TAG, idx, im, gt);
+ }
+
+
+ public void beginEvaluation(int time) {
+ write(BEGIN_EVALUATION_TAG, time);
+ }
+
+
+ public void endEvaluation() {
+ write(END_EVALUATION_TAG);
+ }
+
+
+ public void addMeasure(String name, double value) {
+ write(MEASURE_TAG, name, value);
+ }
+
+
+ public void endTask() {
+ write(END_TASK_TAG);
+ }
+
+
+ public void endExperiment() {
+ write(END_EXPERIMENT_TAG);
+ }
+
+
+ public void endDocument() throws IOException {
+ flush();
+ }
+
+
+ public void flush() throws IOException {
+ out.flush();
+ if (out.checkError()) {
+ throw new IOException();
+ }
+ }
+
+
+ public void close() {
+ out.close();
+ }
+
+
+ private void write(String format, Object ... args) {
+ out.printf(format, args);
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.exp;
+
+/**
+ * @author Kevin McGuinness
+ */
+public class FormatException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+
+ /**
+ *
+ */
+ public FormatException() {
+ }
+
+
+ /**
+ * @param message
+ */
+ public FormatException(String message) {
+ super(message);
+ }
+
+
+ /**
+ * @param cause
+ */
+ public FormatException(Throwable cause) {
+ super(cause);
+ }
+
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public FormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+/**
+ * Selection of what points in time to output the masks that
+ * the user has obtained so far in their interactions.
+ *
+ * @author Kevin McGuinness
+ */
+public enum StorageSelection {
+ All,
+ None,
+ Final;
+
+ public static StorageSelection parse(String s) {
+ for (StorageSelection ss : values()) {
+ if (ss.toString().equalsIgnoreCase(s)) {
+ return ss;
+ }
+ }
+ return null;
+ }
+};
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+import java.io.*;
+
+public class Task {
+
+ private final File imageFile;
+ private final File maskFile;
+ private final String description;
+
+
+ Task(File imFile, File gtFile, String desc) {
+ imageFile = imFile;
+ maskFile = gtFile;
+ description = desc;
+ }
+
+
+ public File getImageFile() {
+ return imageFile;
+ }
+
+
+ public File getMaskFile() {
+ return maskFile;
+ }
+
+
+ public String getDescription() {
+ return description;
+ }
+
+
+ public String toString() {
+ String im = imageFile.getAbsolutePath();
+ String gt = maskFile.getAbsolutePath();
+ return String.format("[ im=<%s> gt=<%s> desc=<%s> ]", im, gt, description);
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.export.imagemap;
+
+/**
+ * The shape of the area tag for a HTML image map.
+ *
+ * @see <a href="http://www.w3.org/TR/html401/sgml/dtd.html#Shape"/>HTML 4 DTD</a>
+ *
+ * @author Kevin McGuinness
+ */
+public enum AreaShape {
+ Polygon("poly"),
+ Rectangle("rect"),
+ Circle("circle");
+
+ private String text;
+
+ private AreaShape(String text) {
+ this.text = text;
+ }
+
+ /**
+ * Returns the HTML shape attribute value.
+ */
+ public String toString() {
+ return text;
+ }
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.apps.ist.export.imagemap;
+
+/**
+ * Exception thrown when there is a problem producing the export.
+ *
+ * @author Kevin McGuinness
+ */
+public class ExportException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ExportException() {
+ }
+
+ public ExportException(String message) {
+ super(message);
+ }
+
+ public ExportException(Throwable cause) {
+ super(cause);
+ }
+
+ public ExportException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.export.imagemap;
+
+import ie.dcu.image.ContourTracer;
+import ie.dcu.segment.SegmentationMask;
+import ie.dcu.util.FileUtils;
+
+import java.awt.Polygon;
+import java.awt.image.*;
+import java.io.*;
+import java.util.*;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Exports HTML image maps from a segmentation mask and an image.
+ *
+ * @author Kevin McGuinness
+ */
+public class Exporter {
+ private final SegmentationMask mask;
+ private final BufferedImage image;
+
+ private RolloverEffect effect;
+ private String htmlFile = "imagemap.html";
+ private String imageFile = "image.png";
+ private String imageName = "image";
+ private String objectDescription = "";
+ private String objectLink = "";
+ private AreaShape exportShape = AreaShape.Polygon;
+
+ public Exporter(BufferedImage image, SegmentationMask mask) {
+ this.image = image;
+ this.mask = mask;
+ }
+
+ public RolloverEffect getEffect() {
+ return effect;
+ }
+
+ public void setEffect(RolloverEffect effect) {
+ this.effect = effect;
+ }
+
+ public String getHtmlFile() {
+ return htmlFile;
+ }
+
+ public void setHtmlFile(String htmlFile) {
+ this.htmlFile = htmlFile;
+ }
+
+ public String getImageFile() {
+ return imageFile;
+ }
+
+ public void setImageFile(String imageFile) {
+ this.imageFile = imageFile;
+ }
+
+ public String getImageName() {
+ return imageName;
+ }
+
+ public void setImageName(String imageName) {
+ this.imageName = imageName;
+ }
+
+ public String getObjectDescription() {
+ return objectDescription;
+ }
+
+ public void setObjectDescription(String description) {
+ this.objectDescription = description;
+ }
+
+ public String getObjectLink() {
+ return objectLink;
+ }
+
+ public void setObjectLink(String link) {
+ this.objectLink = link;
+ }
+
+ public AreaShape getExportShape() {
+ return exportShape;
+ }
+
+ public void setExportShape(AreaShape shape) {
+ this.exportShape = shape;
+ }
+
+ public void export(File folder) throws IOException, ExportException {
+ ContourTracer tracer = new ContourTracer(mask, SegmentationMask.FOREGROUND);
+ List<Polygon> trace = tracer.trace();
+
+ if (trace.size() == 0) {
+ throw new ExportException("No objects found");
+ }
+
+ List<String> preloads = getPreloads(trace);
+
+ // Write image
+ ImageIO.write(image, "png", new File(folder, imageFile));
+
+ if (effect != null) {
+ int i = 1;
+
+ // Generate effect images
+ for (Polygon object : trace) {
+ RenderedImage im = effect.createEffect(image, object);
+ File output = new File(folder, preloads.get(i++));
+ ImageIO.write(im, "png", output);
+ }
+ }
+
+ // Create image map
+ ImageMap map = new ImageMap();
+ map.setImageHref(imageFile);
+ map.setImageName(imageName);
+
+ // Add javascript preloads
+ for (String str : preloads) {
+ map.addPreload(str);
+ }
+
+ // Add areas
+ int idx = 1;
+ for (Polygon polygon : trace) {
+ MapArea area = new MapArea();
+ switch (exportShape) {
+ case Polygon:
+ area.setPolygon(polygon);
+ break;
+ case Rectangle:
+ area.setRect(polygon.getBounds());
+ break;
+ case Circle:
+ // TODO: Implement circle!
+
+ default:
+ area.setPolygon(polygon);
+ break;
+ }
+
+ area.setAlt(objectDescription);
+ area.setHref(objectLink);
+
+ if (effect != null) {
+
+
+ area.addAttr("onmouseover",
+ String.format("rollover(document.%s, image%s)",
+ imageName, idx++));
+ area.addAttr("onmouseout",
+ String.format("rollover(document.%s, image0)", imageName));
+ }
+
+ map.addArea(area);
+ }
+
+ map.exportToFile(new File(folder, htmlFile));
+ }
+
+ private List<String> getPreloads(List<Polygon> trace) {
+ List<String> preloads = new ArrayList<String>();
+ preloads.add(imageFile);
+
+ if (effect != null) {
+
+ String basename = FileUtils.removeExtension(imageFile);
+ for (int i = 0; i < trace.size(); i++) {
+ String filename = String.format("%s-%d.png", basename, i);
+ preloads.add(filename);
+ }
+ }
+
+ return preloads;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.export.imagemap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class used to generate HTML tags.
+ *
+ * @author Kevin McGuinness
+ */
+class HtmlTag {
+ final String name;
+ final Map<String, String> attrs;
+
+ HtmlTag(String name) {
+ this.name = name;
+ this.attrs = new HashMap<String, String>();
+ }
+
+ HtmlTag attr(String name, String value) {
+ attrs.put(name, escape(value));
+ return this;
+ }
+
+ private static String escape(String value) {
+ return value.replaceAll("\"", "\\\\\"");
+ }
+
+ static void indent(StringBuffer sb, int indent) {
+ for (int i = 0; i < indent; i++) {
+ sb.append(' ');
+ }
+ }
+
+ void open(StringBuffer sb, int indent) {
+ open(sb, indent, false);
+ }
+
+ void open(StringBuffer sb, int indent, boolean empty) {
+
+ // Indent
+ indent(sb, indent);
+
+ // Write start of tag
+ sb.append('<').append(name);
+
+ // Write attributes
+ boolean first = true;
+ for (Map.Entry<String, String> entry : attrs.entrySet()) {
+
+ if (!first) {
+ sb.append('\n');
+ indent(sb, indent + name.length() + 1);
+ }
+
+ sb.append(' ');
+ sb.append(entry.getKey());
+ sb.append('=');
+ sb.append('"');
+ sb.append(entry.getValue());
+ sb.append('"');
+
+ first = false;
+ }
+
+ if (empty) {
+ sb.append('/');
+ }
+ sb.append('>').append('\n');
+ }
+
+ void close(StringBuffer sb, int indent) {
+ indent(sb, indent);
+ sb.append('<');
+ sb.append('/');
+ sb.append(name);
+ sb.append('>');
+ sb.append('\n');
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.export.imagemap;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Exports HTML image maps
+ *
+ * @author Kevin McGuinness
+ */
+public class ImageMap {
+ private static final String TEMPLATE_FILE = "template.html";
+
+ private String pageTitle;
+ private String imageName;
+ private String imageHref;
+ private String imageAlt;
+ private String mapName;
+ private final List<MapArea> areas;
+ private final List<String> preloads;
+
+ public ImageMap() {
+ // set defaults :
+ this.pageTitle = "Image Map";
+ this.imageName = "image";
+ this.imageHref = "";
+ this.imageAlt = "";
+ this.mapName = "imagemap";
+ this.areas = new LinkedList<MapArea>();
+ this.preloads = new LinkedList<String>();
+ }
+
+ public String getPageTitle() {
+ return pageTitle;
+ }
+
+ public void setPageTitle(String pageTitle) {
+ assert (pageTitle != null);
+ this.pageTitle = pageTitle;
+ }
+
+ public String getImageName() {
+ return imageName;
+ }
+
+ public void setImageName(String imageName) {
+ this.imageName = imageName;
+ }
+
+ public String getImageHref() {
+ return imageHref;
+ }
+
+ public void setImageHref(String imageHref) {
+ assert (imageHref != null);
+ this.imageHref = imageHref;
+ }
+
+ public String getImageAlt() {
+ return imageAlt;
+ }
+
+ public void setImageAlt(String imageAlt) {
+ assert (imageAlt != null);
+ this.imageAlt = imageAlt;
+ }
+
+ public String getMapName() {
+ return mapName;
+ }
+
+ public void setMapName(String mapName) {
+ assert (mapName != null);
+ this.mapName = mapName;
+ }
+
+ public void addArea(MapArea area) {
+ areas.add(area);
+ }
+
+ public List<MapArea> areas() {
+ return areas;
+ }
+
+ public void addPreload(String preload) {
+ this.preloads.add(preload);
+ }
+
+ public List<String> preloads() {
+ return preloads;
+ }
+
+ public String export() {
+ String template = loadTemplate();
+
+ // Create preloads buffer
+ StringBuffer preloads = new StringBuffer();
+ int idx = 0;
+ for (String s : preloads()) {
+ HtmlTag.indent(preloads, 8);
+ preloads.append("var image").append(idx);
+ preloads.append(" = new Image()\n");
+ HtmlTag.indent(preloads, 8);
+ preloads.append("image").append(idx);
+ preloads.append(".src = '").append(s).append("'\n");
+ idx++;
+ }
+
+ StringBuffer contents = new StringBuffer();
+ for (MapArea area : areas) {
+ contents.append('\n');
+ area.export(contents, 8);
+ }
+
+ Map<String, String> subs = new HashMap<String, String>();
+ subs.put("image-name", imageName);
+ subs.put("page-title", pageTitle);
+ subs.put("image-href", imageHref);
+ subs.put("image-alt", imageAlt);
+ subs.put("map-name", mapName);
+ subs.put("contents", contents.toString());
+ subs.put("preloads", preloads.toString());
+
+ return substitute(template, subs);
+ }
+
+ public void exportToFile(File file) throws IOException {
+ FileWriter writer = new FileWriter(file);
+ try {
+ writer.append(export());
+ } finally {
+ writer.close();
+ }
+ }
+
+ private String substitute(String template, Map<String, String> subs) {
+ // This could be more efficient..
+ String result = template;
+ for (String key : subs.keySet()) {
+ String regex = String.format("\\$\\{%s\\}", key);
+ result = result.replaceAll(regex, subs.get(key));
+ }
+
+ return result;
+ }
+
+ private String loadTemplate() {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ getClass().getResourceAsStream(TEMPLATE_FILE)));
+
+ StringBuffer buff = new StringBuffer();
+
+ try {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ buff.append(line).append('\n');
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+
+ return buff.toString();
+ }
+
+ public String toString() {
+ return export();
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.export.imagemap;
+
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.net.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+
+public class MapArea {
+
+ private String href;
+ private String alt;
+ private AreaShape shape;
+ private int[] coords;
+ private final Map<String, String> attrs;
+
+ public MapArea() {
+ href = "";
+ alt = "";
+ shape = AreaShape.Polygon;
+ coords = new int[0];
+ attrs = new HashMap<String, String>();
+ }
+
+ public void addAttr(String key, String value) {
+ attrs.put(key, value);
+ }
+
+ public String getHref() {
+ return href;
+ }
+
+ public void setHref(String href) {
+ this.href = href == null ? "" : href;
+ }
+
+ public String getAlt() {
+ return alt;
+ }
+
+ public void setAlt(String alt) {
+ this.alt = alt == null ? "" : alt;
+ }
+
+ public AreaShape getShape() {
+ return shape;
+ }
+
+ public void setShape(AreaShape shape) {
+ this.shape = shape == null ? AreaShape.Polygon : shape;
+ }
+
+ public int[] getCoords() {
+ return coords;
+ }
+
+ public void setCoords(int[] coords) {
+ this.coords = coords == null ? new int[0] : coords;
+ }
+
+ public void setRect(Rectangle rect) {
+ setShape(AreaShape.Rectangle);
+ int[] coords = {
+ rect.x,
+ rect.y,
+ rect.x + rect.width,
+ rect.y + rect.height
+ };
+ setCoords(coords);
+ }
+
+ public void setCircle(int x, int y, int r) {
+ setShape(AreaShape.Circle);
+ int[] coords = { x, y, r };
+ setCoords(coords);
+ }
+
+ public void setPolygon(Polygon poly) {
+ setShape(AreaShape.Polygon);
+ int[] coords = new int[2*poly.npoints];
+ for (int i = 0, j = 0; i < poly.npoints; i++) {
+ coords[j++] = poly.xpoints[i];
+ coords[j++] = poly.ypoints[i];
+ }
+ setCoords(coords);
+ }
+
+ public void export(StringBuffer sb, int indent) {
+ String encodedHREF = href;
+ if (href.length() != 0) {
+ try {
+ URI uri = new URI(href);
+ URL url = uri.toURL();
+ encodedHREF = url.toString();
+ } catch (URISyntaxException e) {
+ // Ignore exceptions, the href string will be used instead
+ } catch (MalformedURLException e) {
+ // Ignore exceptions, the href string will be used instead
+ }
+ }
+
+ HtmlTag tag = new HtmlTag("area");
+ tag
+ .attr("href", encodedHREF)
+ .attr("alt", alt)
+ .attr("title", alt)
+ .attr("shape", shape.toString())
+ .attr("coords", getCoordsString());
+
+ for (Entry<String, String> entry : attrs.entrySet()) {
+ tag.attr(entry.getKey(), entry.getValue());
+ }
+
+ tag.open(sb, indent, true);
+ }
+
+ public String getCoordsString() {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ for (int coord : coords) {
+ if (!first) {
+ sb.append(',');
+ }
+ sb.append(coord);
+ first = false;
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.apps.ist.export.imagemap;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.geom.Area;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+
+/**
+ * The roll-over effect for the image map.
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class RolloverEffect {
+
+ public abstract RenderedImage createEffect(Image image, Polygon object);
+
+ public static RolloverEffect darkenBackground(float alpha) {
+ return new DarkenBackground(alpha);
+ }
+
+ public static RolloverEffect darkenBackground() {
+ return new DarkenBackground();
+ }
+
+ public static RolloverEffect brightenForeground(float alpha) {
+ return new BrightenForeground(alpha);
+ }
+
+ public static RolloverEffect brightenForeground() {
+ return new BrightenForeground();
+ }
+
+ public static RolloverEffect outlineObject() {
+ return new OutlineObject();
+ }
+
+ public static RolloverEffect outlineObject(Color color) {
+ return new OutlineObject(color);
+ }
+
+ public static RolloverEffect outlineObject(Color color, Stroke stroke) {
+ return new OutlineObject(color, stroke);
+ }
+
+ public static class DarkenBackground extends RolloverEffect {
+
+ private float alpha = 0.5f;
+
+ public DarkenBackground() {
+
+ }
+
+ public DarkenBackground(float alpha) {
+ this.alpha = alpha;
+ }
+
+ public float getAlpha() {
+ return alpha;
+ }
+
+ public void setAlpha(float alpha) {
+ this.alpha = alpha;
+ }
+
+ @Override
+ public RenderedImage createEffect(Image image, Polygon object) {
+ int width = image.getWidth(null);
+ int height = image.getHeight(null);
+ BufferedImage im = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ Area area = new Area(new Rectangle(0,0,width,height));
+ area.subtract(new Area(object));
+
+ Graphics g = im.getGraphics();
+ if (g instanceof Graphics2D) {
+ Graphics2D g2 = (Graphics2D) g;
+ g2.drawImage(image, 0, 0, null);
+ g2.setColor(new Color(0,0,0,alpha));
+ g2.fill(area);
+ }
+
+ return im;
+ }
+ }
+
+ public static class BrightenForeground extends RolloverEffect {
+
+ private float alpha = 0.5f;
+
+ public BrightenForeground() {
+
+ }
+
+ public BrightenForeground(float alpha) {
+ this.alpha = alpha;
+ }
+
+ public float getAlpha() {
+ return alpha;
+ }
+
+ public void setAlpha(float alpha) {
+ this.alpha = alpha;
+ }
+
+ @Override
+ public RenderedImage createEffect(Image image, Polygon object) {
+ int width = image.getWidth(null);
+ int height = image.getHeight(null);
+ BufferedImage im = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ Graphics g = im.getGraphics();
+ if (g instanceof Graphics2D) {
+ Graphics2D g2 = (Graphics2D) g;
+ g2.drawImage(image, 0, 0, null);
+ g2.setColor(new Color(1,1,1,alpha));
+ g2.fill(object);
+ }
+
+ return im;
+ }
+ }
+
+ public static class OutlineObject extends RolloverEffect {
+
+ private Color color = Color.white;
+ private Stroke stroke = new BasicStroke(2.0f,
+ BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+
+ public OutlineObject() {
+
+ }
+
+ public OutlineObject(Color color) {
+ this.color = color;
+ }
+
+ public OutlineObject(Color color, Stroke stroke) {
+ this.color = color;
+ this.stroke = stroke;
+ }
+
+ public Color getColor() {
+ return color;
+ }
+
+ public void setColor(Color color) {
+ this.color = color;
+ }
+
+ public Stroke getStroke() {
+ return stroke;
+ }
+
+ public void setStroke(Stroke stroke) {
+ this.stroke = stroke;
+ }
+
+ @Override
+ public RenderedImage createEffect(Image image, Polygon object) {
+ int width = image.getWidth(null);
+ int height = image.getHeight(null);
+ BufferedImage im = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ Graphics g = im.getGraphics();
+ if (g instanceof Graphics2D) {
+ Graphics2D g2 = (Graphics2D) g;
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.drawImage(image, 0, 0, null);
+ g2.setColor(color);
+ g2.setStroke(stroke);
+ g2.draw(object);
+ }
+
+ return im;
+ }
+ }
+}
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+ <!-- Generated using the CDVP Interactive Segmentation Tool -->
+ <meta http-equiv="Content-type"
+ content="text/html; charset=utf-8"/>
+ <meta name="Generator"
+ content="Interactive Segmentation Tool"/>
+
+ <title>${page-title}</title>
+
+ <script type="text/javascript" language="javascript">
+
+${preloads}
+
+ function rollover(docElem, image) {
+ docElem.src = image.src
+ return true
+ }
+
+ </script>
+
+</head>
+<body>
+ <!-- Image -->
+ <img name="${image-name}"
+ src="${image-href}"
+ usemap="#${map-name}"
+ alt="${image-alt}"
+ title="${image-alt}"/>
+
+ <!-- Image map -->
+ <map name="${map-name}" id="${map-name}">
+${contents}
+ </map>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+package ie.dcu.apps.ist.recent;
+
+import java.io.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * Class for managing a recent documents list.
+ *
+ * @author Kevin McGuinness
+ */
+public class RecentFiles implements Iterable<File> {
+ public static final int DEFAULT_CAPACITY = 10;
+
+
+ private final LinkedList<File> history;
+ private final List<RecentFilesListener> listeners;
+ private final File store;
+ private int capacity;
+
+
+ public RecentFiles(File store) {
+
+ // Check the store is ok
+ checkValid(store);
+
+ // Construct
+ this.history = new LinkedList<File>();
+ this.listeners = new LinkedList<RecentFilesListener>();
+ this.store = store;
+ this.capacity = DEFAULT_CAPACITY;
+
+ // Add a vm shutdown hook
+ addShutdownHook();
+ }
+
+
+ public void setCapacity(int capacity) {
+
+ // Check positive
+ if (capacity < 0) {
+ throw new IllegalArgumentException("capacity < 0");
+ }
+
+ if (this.capacity != capacity) {
+ this.capacity = capacity;
+
+ // Capacity may have reduced
+ boolean reduced = history.size() > capacity;
+ while (history.size() > capacity) {
+ history.removeLast();
+ }
+
+ // Fire change
+ if (reduced) {
+ fireHistoryChanged();
+ }
+ }
+ }
+
+
+ public int getCapacity() {
+ return capacity;
+ }
+
+
+ public void add(File file) {
+ if (!history.contains(file)) {
+ history.addFirst(file);
+
+ // Check if we have exceeded capacity
+ if (history.size() > capacity) {
+ history.removeLast();
+ }
+
+ // Fire change
+ fireHistoryChanged();
+ }
+ }
+
+
+ public void clear() {
+ if (!empty()) {
+ history.clear();
+ fireHistoryChanged();
+ }
+ }
+
+
+ public List<File> files() {
+ return Collections.unmodifiableList(history);
+ }
+
+
+ public Iterator<File> iterator() {
+ return files().iterator();
+ }
+
+
+ public boolean empty() {
+ return history.isEmpty();
+ }
+
+
+ public void load() throws IOException {
+ if (!store.exists()) {
+ // Create an empty store
+ store.createNewFile();
+ }
+
+ BufferedReader in = null;
+ try {
+ read(in = new BufferedReader(new FileReader(store)));
+
+ } finally {
+ in.close();
+ }
+ }
+
+
+ public void store() throws IOException {
+ PrintWriter out = null;
+ try {
+ write(out = new PrintWriter(store));
+
+ } finally {
+ out.close();
+ }
+ }
+
+
+ public void addListener(RecentFilesListener rfl) {
+ listeners.add(rfl);
+ }
+
+
+ public void removeListener(RecentFilesListener rfl) {
+ listeners.remove(rfl);
+ }
+
+
+ protected static Logger getLogger() {
+ return Logger.getLogger(RecentFiles.class.getName());
+ }
+
+
+ private void shutdown() {
+ try {
+ store();
+ } catch (IOException e) {
+ getLogger().warning("Error writing recent documents to store: " + e);
+ }
+ }
+
+
+ private void fireHistoryChanged() {
+ RecentFilesEvent evt = null;
+ for (RecentFilesListener listener : listeners) {
+ if (evt == null) {
+ evt = new RecentFilesEvent(this);
+ listener.historyChanged(evt);
+ }
+ }
+ }
+
+
+ private void checkValid(File store) {
+ if (store == null) {
+ throw new IllegalArgumentException("store cannot be null");
+ }
+
+ if (store.isDirectory()) {
+ throw new IllegalArgumentException("store cannot be a directory");
+ }
+
+ if (store.isFile()) {
+ if (!store.canWrite()) {
+ throw new IllegalArgumentException("store not writable");
+ }
+ }
+ }
+
+
+ private void addShutdownHook() {
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() {
+ shutdown();
+ }
+ });
+ }
+
+
+ private void read(BufferedReader in) throws IOException {
+
+ // Remember old history to check for changes
+ LinkedList<File> old = new LinkedList<File>(history);
+
+ // Clear history if not empty
+ history.clear();
+
+ // Read files (discarding no longer existent ones)
+ String line;
+ while ((line = in.readLine()) != null) {
+
+ // Stop when full
+ if (history.size() == capacity) {
+ break;
+ }
+
+ File file = new File(line);
+ if (file.exists()) {
+ history.add(file);
+ }
+ }
+
+ // Fire change if a change occurred
+ if (!history.equals(old)) {
+ fireHistoryChanged();
+ }
+ }
+
+
+ private void write(PrintWriter out) throws IOException {
+ for (File file : history) {
+ String path = file.getAbsolutePath();
+ out.println(path);
+ }
+
+ // Check for errors
+ if (out.checkError()) {
+ throw new IOException("Error writing to file");
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.recent;
+
+import java.util.*;
+
+public class RecentFilesEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+
+ public RecentFilesEvent(RecentFiles source) {
+ super(source);
+ }
+
+
+ public RecentFiles getList() {
+ return (RecentFiles) source;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.recent;
+
+import java.util.*;
+
+public interface RecentFilesListener extends EventListener {
+
+ public void historyChanged(RecentFilesEvent evt);
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.views;
+
+import ie.dcu.apps.ist.AppWindow;
+import ie.dcu.apps.ist.event.*;
+import ie.dcu.apps.ist.exp.*;
+import ie.dcu.apps.ist.widgets.SwtTimer;
+import ie.dcu.segment.annotate.*;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.logging.*;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * @author Kevin McGuinness
+ */
+public class ExperimentPanel extends Composite {
+
+ // Some property keys
+ private static final String DESCRIPTION_TITLE
+ = "description.title";
+
+ private static final String TIMEOUT_MESSAGE
+ = "timeout-message";
+
+ private static final String EXPERIMENT_COMPLETE_MESSAGE
+ = "experiment-complete-message";
+
+ // Type of button in use
+ private static enum ButtonType { Start, Finish };
+
+ // App window and experiment
+ private final AppWindow window;
+ private final Experiment experiment;
+ private final Logger log;
+
+ // Controls
+ private final Group group;
+ private final Button button;
+ private final SwtTimer timer;
+ private final Text text;
+
+ // Shell if the panel is inside a shell
+ private Shell shell;
+
+ // Experiment manager
+ private final ExperimentManager manager;
+ private final EvaluationListener evaluator;
+
+
+ public ExperimentPanel(AppWindow window, Composite parent, int style) {
+ super(parent, style);
+
+ // Set app window
+ this.window = window;
+ this.experiment = window.getExperiment();
+ this.log = Logger.getLogger("Experiment");
+
+
+ // Create controls
+ timer = new SwtTimer(this, SWT.NONE);
+ group = new Group(this, SWT.DEFAULT);
+ button = new Button(this, SWT.PUSH);
+ text = new Text(group, SWT.READ_ONLY | SWT.WRAP);
+
+ // Create manager and evaluator
+ manager = new ExperimentManager(window);
+ evaluator = new EvaluationListener(manager, timer);
+
+ // Setup controls
+ setupControls();
+ layoutControls();
+ addListeners();
+
+ // Begin experiment
+ beginExperiment();
+ }
+
+
+ public static void open(AppWindow win) {
+ Shell app = win.getShell();
+
+ // Style bits
+ int style = SWT.MODELESS | SWT.RESIZE | SWT.BORDER | SWT.TITLE | SWT.CLOSE;
+
+ // Construct shell
+ Shell shell = new Shell(app, style);
+
+ // Determine shell bounds
+ Rectangle bounds = new Rectangle(0,0,180,280);
+
+ // Move shell to an appropriate place (right of app window)
+ Rectangle rect = app.getBounds();
+ bounds.x = rect.x + rect.width;
+ bounds.y = rect.y + rect.height / 2 - bounds.height / 2;
+
+ // Move back if outside the screen
+ Rectangle screen = shell.getDisplay().getBounds();
+ if (bounds.x + bounds.width > screen.width) {
+ bounds.x -= bounds.x + bounds.width - screen.width;
+ }
+
+ // Set bounds
+ shell.setBounds(bounds);
+
+ // Set layout
+ shell.setLayout(new FillLayout());
+
+ // Create experiment panel
+ final ExperimentPanel panel
+ = new ExperimentPanel(win, shell, SWT.NONE);
+
+ panel.shell = shell;
+ panel.updateTitle();
+
+ // Add shell close button listener
+ shell.addShellListener(new ShellAdapter() {
+ public void shellClosed(ShellEvent e) {
+ if (!panel.manager.isComplete()) {
+ e.doit = panel.cancelExperiment();
+ }
+ }
+ });
+
+ // Open experiment panel
+ shell.open();
+ }
+
+
+ private void setupControls() {
+ group.setText(property(DESCRIPTION_TITLE));
+ text.setEnabled(false);
+ enableButton(null);
+ }
+
+
+ private void layoutControls() {
+ GridLayout gl;
+ GridData gd;
+
+ // Setup layout manager for this
+ gl = new GridLayout();
+ gl.marginTop = 5;
+ gl.verticalSpacing = 5;
+ this.setLayout(gl);
+
+ // Setup layout manager for group
+ gl = new GridLayout();
+ group.setLayout(gl);
+
+ // Layout timer
+ gd = new GridData(SWT.FILL, SWT.FILL, true, false);
+ timer.setLayoutData(gd);
+
+ // Layout group
+ gd = new GridData(SWT.FILL, SWT.FILL, true, true);
+ group.setLayoutData(gd);
+
+ // Layout button
+ gd = new GridData(SWT.FILL, SWT.FILL, true, false);
+ button.setLayoutData(gd);
+
+ // Layout text
+ gd = new GridData(SWT.FILL, SWT.FILL, true, true);
+ text.setLayoutData(gd);
+ }
+
+
+ private void addListeners() {
+ button.addListener(SWT.Selection, buttonListener);
+ timer.addTimeoutListener(timeListener);
+ }
+
+
+ /**
+ * Function to call if something goes wrong with the experiment. Shows an
+ * error message and gracefully returns the application to normal mode.
+ *
+ * @param reason
+ * Short description of the problem.
+ */
+ private void abortExperiment(String reason) {
+ timer.clear();
+ evaluator.detach();
+
+ // Show abort message
+ error("Aborting experiment!%n%s", reason);
+
+ // Tell manager of abort
+ manager.abortExperiment();
+
+ // Exit experiment mode
+ exitExperimentMode();
+ }
+
+
+ /**
+ * Does what is necessary to exit experiment mode.
+ */
+ private void exitExperimentMode() {
+ // Remove experiment view
+ window.setExperiment(null);
+
+ // Re-enable interactions
+ window.getView().setEnabled(true);
+
+ // Kill the shell
+ closeShell();
+ }
+
+
+ /**
+ * Closes the encompassing (non-application) shell, if any.
+ */
+ private void closeShell() {
+ if (shell != null) {
+ shell.close();
+ shell.dispose();
+ shell = null;
+ }
+ }
+
+
+ private boolean cancelExperiment() {
+ // Make sure the user knows what they're doing
+ boolean cancel = MessageDialog.openQuestion(window.getShell(), "Confirm",
+ "Are you sure you want to cancel the experiment?");
+
+ if (cancel) {
+ // Guess we better do what the user says, grumble grumble :-/
+ timer.clear();
+ evaluator.detach();
+ manager.abortExperiment();
+ window.setExperiment(null);
+ window.getView().setEnabled(true);
+ }
+
+ return cancel;
+ }
+
+
+ /**
+ * Start the experiment.
+ */
+ private void beginExperiment() {
+ try {
+ manager.beginExperiment(experiment);
+
+ } catch (IOException e) {
+ log.log(Level.SEVERE, "Error starting experiment", e);
+ abortExperiment(e.getMessage());
+ }
+
+ // Begin first task
+ beginTask();
+ }
+
+
+ /**
+ * Finish the experiment.
+ */
+ private void endExperiment() {
+ try {
+ manager.endExperiment();
+
+ } catch (IOException e) {
+ log.log(Level.SEVERE, "Error ending experiment", e);
+ abortExperiment(e.getMessage());
+ }
+
+ // Tell user experiment is complete
+ notifyExperimentComplete();
+
+ // Stop experiment mode
+ exitExperimentMode();
+ }
+
+
+ /**
+ * Load the task.
+ */
+ private void beginTask() {
+
+ // Set timer
+ timer.set(experiment.getTime());
+
+ // Begin task
+ try {
+ manager.beginTask();
+ } catch (IOException e) {
+ log.log(Level.SEVERE, "Error starting task", e);
+ abortExperiment(e.getMessage());
+ }
+
+ // Disable interactions
+ window.getView().setEnabled(false);
+
+ // Update description
+ text.setText(manager.getTaskDescription());
+
+ // Update title text
+ updateTitle();
+
+ // Setup start button
+ enableButton(ButtonType.Start);
+
+ // Attach evaluator
+ evaluator.attach();
+ }
+
+
+ private void updateTitle() {
+ if (shell != null) {
+ int ntasks = manager.getTaskCount();
+ int progress = manager.getProgress();
+ shell.setText(String.format("Task [%d/%d]", progress+1, ntasks));
+ }
+ }
+
+
+ /**
+ * Start the task.
+ */
+ private void startTask() {
+
+ // Enable interactions
+ window.getView().setEnabled(true);
+
+ // Start clock
+ timer.start();
+
+ // Enable finish button
+ enableButton(ButtonType.Finish);
+ }
+
+
+ /**
+ * End the task.
+ */
+ private void endTask() {
+ // Detach evaluator
+ evaluator.detach();
+
+ // Evaluate final result
+ manager.evaluate(timer.getElapsed());
+
+ // End task
+ try {
+ manager.endTask();
+ } catch (IOException e) {
+ log.log(Level.SEVERE, "Error ending task", e);
+ abortExperiment(e.getMessage());
+ }
+
+ // Stop and clear clock
+ timer.clear();
+ }
+
+
+ /**
+ * End the current task and start the next.
+ * End the experiment if finished.
+ */
+ private void nextTask() {
+ endTask();
+ if (manager.isComplete()) {
+ endExperiment();
+ } else {
+ beginTask();
+ }
+ }
+
+
+ private void enableButton(ButtonType type) {
+ if (type != null) {
+ String name = type.toString().toLowerCase();
+ button.setText(property("button.text." + name));
+ button.setData(type);
+ button.setEnabled(true);
+ button.setFocus();
+ } else {
+ button.setText(property("button.text.start"));
+ button.setEnabled(false);
+ button.setData(null);
+ }
+ }
+
+
+ private ButtonType getEnabledButton() {
+ return (ButtonType) button.getData();
+ }
+
+
+ private void handleButtonClicked() {
+ ButtonType btn = getEnabledButton();
+ switch (btn) {
+ case Start:
+ startTask();
+ break;
+ case Finish:
+ // Prevent clicking finish by accident
+ if (manager.getActiveContext().getAnnotations().count() == 0) {
+ getDisplay().beep();
+ } else {
+ nextTask();
+ }
+ break;
+ }
+ }
+
+
+ private void handleTimeout() {
+ // Finish task
+ endTask();
+
+ // Tell user
+ notifyTimeoutOccurred();
+
+ // Next task
+ if (manager.isComplete()) {
+ endExperiment();
+ } else {
+ beginTask();
+ }
+ }
+
+
+ private void notifyExperimentComplete() {
+ message(EXPERIMENT_COMPLETE_MESSAGE);
+ }
+
+
+ private void notifyTimeoutOccurred() {
+ // BEEP BEEP!! time up :-)
+ getDisplay().beep();
+ message(TIMEOUT_MESSAGE);
+ }
+
+
+ private void message(String key) {
+ Shell main = window.getShell();
+ MessageDialog.openInformation(main, "Information", property(key));
+ }
+
+
+ private void error(String message, Object ... args) {
+ Shell main = window.getShell();
+ MessageDialog.openError(main, "Error", String.format(message, args));
+ }
+
+
+ private String property(String key) {
+ return property(key, key);
+ }
+
+
+ private String property(String key, String def) {
+ Properties p = window.getProperties();
+ return p.getProperty(String.format("ExperimentPanel.%s", key), def);
+ }
+
+
+ private final Listener buttonListener = new Listener() {
+ public void handleEvent(Event event) {
+ handleButtonClicked();
+ }
+ };
+
+
+ private final TimeoutListener timeListener = new TimeoutListener() {
+ public void timeoutOccured(TimeoutEvent evt) {
+ handleTimeout();
+ }
+ };
+}
+
+
+class EvaluationListener implements AnnotationListener {
+ private final ExperimentManager manager;
+ private final SwtTimer timer;
+ private final Logger log;
+
+ public EvaluationListener(ExperimentManager manager, SwtTimer timer) {
+ this.manager = manager;
+ this.timer = timer;
+ this.log = Logger.getLogger("Experiment");
+ }
+
+
+ public void attach() {
+ manager.getActiveContext().addAnnotationListener(this);
+ }
+
+
+ public void detach() {
+ manager.getActiveContext().removeAnnotationListener(this);
+ }
+
+
+ private void annotationsChanged(AnnotationEvent e) {
+ // Return if there is nothing to evaluate yet
+ if (e.manager.count() == 0) {
+ return;
+ }
+
+ // Time the change occurred
+ int time = timer.getElapsed();
+ manager.evaluate(time);
+
+ // Store if necessary
+ try {
+ manager.store(time);
+ } catch (IOException ex) {
+ log.log(Level.SEVERE, "Error storing intermediate segmentation mask", e);
+ }
+ }
+
+
+ public void annotationPerformed(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+
+ public void annotationRedone(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+
+ public void annotationUndone(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+
+ public void annotationsCleared(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+}
+
--- /dev/null
+package ie.dcu.apps.ist.views;
+
+
+import ie.dcu.apps.ist.PainterRegistry;
+import ie.dcu.apps.ist.event.*;
+import ie.dcu.apps.ist.widgets.*;
+import ie.dcu.segment.*;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.segment.options.SegmenterOptionDialog;
+import ie.dcu.segment.painters.SegmentationPainter;
+import ie.dcu.swt.*;
+import ie.dcu.swt.event.*;
+
+import java.lang.reflect.InvocationTargetException;
+import java.net.*;
+import java.util.Properties;
+import java.util.logging.*;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.action.*;
+import org.eclipse.jface.operation.*;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+public class SegmentationView extends Composite {
+ // Logger
+ private static final Logger log = Logger.getLogger(SegmentationView.class.getName());
+
+
+ // Segmentation view properties
+ private final Properties props;
+
+
+ // Houses various painters
+ private final PainterRegistry painters;
+
+
+ // View handling annotations
+ private final AnnotatedImageControl view;
+
+
+ // Left, center and right tool bar
+ private final ToolBar bar1, bar2, bar3;
+
+
+ // Control to change brush size
+ private final BrushControl brushControl;
+
+
+ // Handles various events
+ private final EventHandler eventHandler;
+
+
+ // Proxy object to catch and log exceptions thrown by the segmentation
+ // algorithm, without crashing the application.
+ private RobustSegmenterProxy segmenterProxy;
+
+
+ // Auto segment on update flag
+ private boolean auto = true;
+
+
+ // Combo box housing the selectable views
+ private Combo combo;
+
+
+ // Current segmentation tool
+ private Segmenter segmenter;
+
+
+ // Context to run the segmentation in, (may be null)
+ private IRunnableContext runnableContext;
+
+
+ // Flag to indicate that the runnable context blocks when fork is true
+ private boolean blocksOnFork;
+
+
+ public enum Tool {
+ Foreground,
+ Background,
+ ZoomIn,
+ ZoomOut,
+ ZoomOriginal,
+ ZoomBestFit,
+ Repaint,
+ Undo,
+ Redo,
+ Clear,
+ SetBrushSize,
+ AutoApply,
+ Apply,
+ SetPainter,
+ SetLabel,
+ SegmenterOptions;
+ private ToolAction action;
+ };
+
+
+ public SegmentationView(Properties props, Composite parent, int style) {
+ super(parent, style);
+ this.props = props;
+
+ painters = new PainterRegistry();
+ bar1 = new ToolBar(this, SWT.LEFT | SWT.FLAT);
+ bar2 = new ToolBar(this, SWT.RIGHT | SWT.FLAT);
+ bar3 = new ToolBar(this, SWT.CENTER | SWT.FLAT);
+ view = new AnnotatedImageControl(this, SWT.BORDER);
+ brushControl = new BrushControl(getShell(), SWT.BORDER);
+ eventHandler = new EventHandler();
+ segmenterProxy = new RobustSegmenterProxy();
+
+ init();
+ }
+
+
+ /**
+ * Initialize.
+ */
+ private void init() {
+ initTools();
+ createToolbar1();
+ createToolbar2();
+ createToolbar3();
+ layoutControls();
+ updatePainters();
+ addListeners();
+ updateToolStates();
+ }
+
+
+ private void initTools() {
+ for (Tool t : Tool.values()) {
+ if (t.action == null) {
+ new ToolAction(t);
+ }
+ }
+ }
+
+
+ private void addListeners() {
+ brushControl.addSelectionListener(eventHandler);
+ view.addContextChangeListener(eventHandler);
+ view.addZoomListener(eventHandler);
+ addDisposeListener(eventHandler);
+ }
+
+
+ private void createToolbar1() {
+ ToolBarManager m = new ToolBarManager(bar1);
+ m.add(getAction(Tool.Foreground));
+ m.add(getAction(Tool.Background));
+ m.add(new Separator());
+ m.add(getAction(Tool.ZoomIn));
+ m.add(getAction(Tool.ZoomOut));
+ m.add(getAction(Tool.ZoomOriginal));
+ m.add(getAction(Tool.ZoomBestFit));
+ m.add(new Separator());
+ m.add(getAction(Tool.Repaint));
+ m.add(getAction(Tool.Undo));
+ m.add(getAction(Tool.Redo));
+ m.add(getAction(Tool.Clear));
+ m.add(new Separator());
+ m.add(getAction(Tool.SetBrushSize));
+ m.add(new Separator());
+ m.add(getAction(Tool.AutoApply));
+ m.add(getAction(Tool.Apply));
+ m.add(getAction(Tool.SegmenterOptions));
+ m.add(new Separator());
+ m.update(true);
+ }
+
+
+ private void createToolbar2() {
+ SwtUtils.addLabel(bar2, getAction(Tool.SetPainter).getText());
+ combo = SwtUtils.addCombo(bar2, 115, SWT.READ_ONLY);
+ combo.setToolTipText( getAction(Tool.SetPainter).getToolTipText());
+ combo.addSelectionListener(new SelectionAdapter() {
+ public void widgetSelected(SelectionEvent e) {
+ execute(Tool.SetPainter, null);
+ }
+ });
+ bar2.pack();
+ }
+
+ private void createToolbar3() {
+ SwtUtils.addLabel(bar3, getAction(Tool.SetLabel).getText());
+ ToolItem item = new ToolItem(bar3, SWT.SEPARATOR);
+ Text text = new Text(bar3, SWT.SINGLE);
+ text.setToolTipText( getAction(Tool.SetLabel).getToolTipText());
+ text.setText("Enter a Label");
+ item.setWidth(100);
+ item.setControl(text);
+ text.pack();
+ bar3.pack();
+
+ }
+
+
+ private void layoutControls() {
+ GridLayout layout = new GridLayout(3, false);
+ layout.marginHeight = 1;
+ layout.marginWidth = 2;
+ layout.horizontalSpacing = -1; // zero leaves 1 pixel gap
+ layout.verticalSpacing = 1;
+ setLayout(layout);
+
+ Point sz1 = bar1.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ Point sz2 = bar2.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ Point sz3 = bar3.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+
+ int heightHintOfTwo = Math.max(sz1.y, sz2.y);
+ int heightHint = Math.max(heightHintOfTwo, sz3.y);
+
+ // Layout toolbar 1
+ GridData gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = SWT.FILL;
+ gd.heightHint = heightHint;
+ bar1.setLayoutData(gd);
+
+ // Layout toolbar 2
+ gd = new GridData();
+ gd.horizontalAlignment = SWT.FILL;
+ gd.heightHint = heightHint;
+ bar2.setLayoutData(gd);
+
+ // Layout toolbar 3
+ gd = new GridData();
+ gd.horizontalAlignment = SWT.FILL;
+ gd.heightHint = heightHint;
+ bar3.setLayoutData(gd);
+
+ // Layout view
+ gd = new GridData();
+ gd.verticalIndent = 3;
+ gd.horizontalSpan = 3;
+ gd.grabExcessHorizontalSpace = true;
+ gd.grabExcessVerticalSpace = true;
+ gd.horizontalAlignment = SWT.FILL;
+ gd.verticalAlignment = SWT.FILL;
+ view.setLayoutData(gd);
+ }
+
+
+ private void updatePainters() {
+ combo.removeAll();
+ boolean first = true;
+ for (SegmentationPainter p : painters.values()) {
+ combo.add(p.getName());
+ if (first) {
+ combo.setText(p.getName());
+ first = false;
+ }
+ }
+ }
+
+
+ public void setSegmenter(Segmenter segmenter) {
+ if (this.segmenter != segmenter) {
+ SegmentationContext ctx = view.getContext();
+
+ if (this.segmenter != null && ctx != null) {
+ // Finish old segmentation
+ segmenterProxy.finish(ctx);
+
+ // Remove old segmenter
+ this.segmenter = null;
+ }
+
+ // Set new segmenter
+ this.segmenter = segmenter;
+
+ // Initialize a new segmentation
+ start(ctx);
+
+ // Update enabled buttons
+ updateToolStates();
+ }
+ }
+
+
+ public Segmenter getSegmenter() {
+ return segmenter;
+ }
+
+
+ public void setContext(SegmentationContext ctx) {
+
+ // context change handler handles initialization etc.
+ // of segmenter and context
+ view.setContext(ctx);
+ }
+
+
+ public SegmentationContext getContext() {
+ return view.getContext();
+ }
+
+
+ public void setAnnotationType(AnnotationType type) {
+ view.setAnnotationType(type);
+ }
+
+
+ public Canvas getCanvas() {
+ return view.getCanvas();
+ }
+
+
+ public ImageControl getImageControl() {
+ return view.getImageControl();
+ }
+
+
+ public void zoomIn() {
+ view.zoomIn();
+ }
+
+
+ public void zoomOut() {
+ view.zoomOut();
+ }
+
+
+ public void zoomOriginal() {
+ view.zoomOriginal();
+ }
+
+
+ public void zoomBestFit() {
+ view.zoomBestFit();
+ }
+
+
+ public void repaint() {
+ view.repaint();
+ }
+
+
+ public void undo() {
+ view.undo();
+ }
+
+
+ public void redo() {
+ view.redo();
+ }
+
+
+ public void clear() {
+ view.clear();
+ }
+
+
+ public void setBrushSize(int size) {
+ view.setLineWidth(size);
+ }
+
+
+ // For labeling the image
+ public void setLabel(SegmentationPainter painter) {
+ view.setPainter(painter);
+ combo.setText(painter.getName());
+ }
+
+
+ public void setPainter(SegmentationPainter painter) {
+ view.setPainter(painter);
+ combo.setText(painter.getName());
+ }
+
+
+ public SegmentationPainter getPainter() {
+ return view.getPainter();
+ }
+
+
+ public void setAutoApply(boolean checked) {
+ if (!this.auto && checked) {
+ // Going from off to on, resegment now
+ apply();
+ }
+ this.auto = checked;
+ }
+
+
+ public boolean isAutoApply() {
+ return this.auto;
+ }
+
+
+ public boolean canZoomIn() {
+ return view.canZoomIn();
+ }
+
+
+ public boolean canZoomOut() {
+ return view.canZoomOut();
+ }
+
+
+ public boolean canZoomOriginal() {
+ return view.canZoomOriginal();
+ }
+
+
+ public boolean canZoomBestFit() {
+ return view.canZoomBestFit();
+ }
+
+
+ public boolean canUndo() {
+ return view.canUndo();
+ }
+
+
+ public boolean canRedo() {
+ return view.canRedo();
+ }
+
+
+ public boolean canClear() {
+ return view.canClear();
+ }
+
+
+ public boolean canShowOptions() {
+ if (hasSegmenter()) {
+ return segmenter.getOptions().size() > 0;
+ }
+ return false;
+ }
+
+
+ public Action getAction(Tool t) {
+ return t.action;
+ }
+
+
+ public void showSegmenterOptions() {
+ if (canShowOptions()) {
+ SegmenterOptionDialog dialog =
+ new SegmenterOptionDialog(getShell(), segmenter);
+
+ int result = dialog.open();
+ switch (result) {
+ case SegmenterOptionDialog.OK:
+ // Re-segment
+ apply();
+ }
+ }
+ }
+
+
+ public boolean hasSegmenter() {
+ return segmenter != null;
+ }
+
+
+ public void performSegmentation(AnnotationEvent e, SegmentationContext ctx) {
+
+ if (runnableContext == null) {
+ segment(e, ctx);
+ } else {
+ segmentWithRunnableContext(e, ctx);
+ }
+
+ // Repaint everything
+ repaint();
+
+ }
+
+
+ public void setRunnableContext(IRunnableContext context, boolean blocksOnFork) {
+ runnableContext = context;
+ this.blocksOnFork = blocksOnFork;
+ }
+
+
+ public void addContextChangeListener(ContextChangeListener listener) {
+ view.addContextChangeListener(listener);
+ }
+
+
+ public void removeContextChangeListener(ContextChangeListener listener) {
+ view.removeContextChangeListener(listener);
+ }
+
+
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ bar1.setEnabled(enabled);
+ bar2.setEnabled(enabled);
+ view.setEnabled(enabled);
+ updateToolStates();
+ }
+
+
+ private void run(boolean fork, IRunnableWithProgress runnable) {
+ boolean wasEnabled = isEnabled();
+
+ try {
+ // Disable interactions
+ setEnabled(false);
+
+ // Run segmentation
+ runnableContext.run(fork, false, runnable);
+
+ } catch (InvocationTargetException ex) {
+
+ // Can't happen without a runtime exception occurring, so re-wrap
+ throw new RuntimeException(ex);
+
+ } catch (InterruptedException ex) {
+
+ // Can't happen because cancellable is false
+ throw new RuntimeException(ex);
+
+ } finally {
+ setEnabled(wasEnabled);
+ }
+ }
+
+
+ private void segmentWithRunnableContext(
+ final AnnotationEvent e,
+ final SegmentationContext ctx)
+ {
+ // Fork if the segmenter is slow, and only if our runnable context blocks
+ boolean fork = blocksOnFork /*&& !segmenter.isFast()*/;
+
+ run(fork, new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) {
+ monitor.beginTask("Segmenting", IProgressMonitor.UNKNOWN);
+ try {
+ segment(e, ctx);
+ } finally {
+ monitor.done();
+ }
+ }
+ });
+ }
+
+
+ private void start(final SegmentationContext ctx) {
+ if (ctx != null && hasSegmenter()) {
+ if (runnableContext == null) {
+
+ // No runnable context
+ segmenterProxy.init(ctx);
+ segmenterProxy.update(ctx);
+
+ } else {
+
+ // Run with progress monitor
+ run(blocksOnFork, new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) {
+ monitor.beginTask("Initializing", IProgressMonitor.UNKNOWN);
+ try {
+ segmenterProxy.init(ctx);
+ segmenterProxy.update(ctx);
+ } finally {
+ monitor.done();
+ }
+ }
+ });
+
+ }
+
+ // Repaint everything
+ repaint();
+ }
+ }
+
+
+ private void segment(AnnotationEvent e, SegmentationContext ctx) {
+ if (hasSegmenter()) {
+ if (e == null) {
+ // No event triggered this, so do a full update
+ segmenterProxy.update(ctx);
+
+ } else {
+ // Call appropriate segmenter function
+ switch (e.type) {
+ case Cleared:
+ segmenterProxy.update(ctx);
+ break;
+
+ case Undone:
+ segmenterProxy.removed(ctx, e.annotation);
+ break;
+
+ case Redone:
+ case Added:
+ segmenterProxy.added(ctx, e.annotation);
+ break;
+
+ default:
+ throw new RuntimeException();
+ }
+ }
+ }
+ }
+
+
+ private boolean isAnnotatingBackground() {
+ return view.isAnnotatingBackground();
+ }
+
+
+ private boolean isAnnotatingForeground() {
+ return view.isAnnotatingForeground();
+ }
+
+
+ private String getText(Tool a) {
+ return props.getProperty(getKey(a, "text"));
+ }
+
+
+ private String getToolTip(Tool a) {
+ return props.getProperty(getKey(a, "tooltip"));
+ }
+
+
+ private ImageDescriptor getImage(Tool a) {
+ String location = props.getProperty(getKey(a, "image"));
+ if (location != null) {
+ if (location.length() == 0) {
+ return null;
+ }
+
+ try {
+ return ImageDescriptor.createFromURL(new URL(location));
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+
+ private static String getKey(Tool action, String object) {
+ return String.format("SegmentationView.Action.%s.%s", action, object);
+ }
+
+
+ private int getType(Tool a) {
+ switch (a) {
+ case Foreground:
+ case Background:
+ case AutoApply:
+ return Action.AS_CHECK_BOX;
+ case SetBrushSize:
+ return Action.AS_DROP_DOWN_MENU;
+ case SetPainter:
+ return Action.AS_UNSPECIFIED;
+ default:
+ return Action.AS_PUSH_BUTTON;
+ }
+ }
+
+
+ private void execute(Tool a, Event e) {
+ switch (a) {
+ case Foreground:
+ setAnnotationType(AnnotationType.Foreground);
+ break;
+ case Background:
+ setAnnotationType(AnnotationType.Background);
+ break;
+ case ZoomIn:
+ zoomIn();
+ break;
+ case ZoomOut:
+ zoomOut();
+ break;
+ case ZoomOriginal:
+ zoomOriginal();
+ break;
+ case ZoomBestFit:
+ zoomBestFit();
+ break;
+ case Repaint:
+ repaint();
+ break;
+ case Undo:
+ undo();
+ break;
+ case Redo:
+ redo();
+ break;
+ case Clear:
+ clear();
+ break;
+ case SetBrushSize:
+ showBrushControl(e);
+ break;
+ case AutoApply:
+ setAutoApply(a.action.isChecked());
+ break;
+ case Apply:
+ apply();
+ break;
+ case SetPainter:
+ setPainter();
+ break;
+ case SegmenterOptions:
+ showSegmenterOptions();
+ break;
+ }
+
+ updateToolStates();
+ }
+
+
+ private void updateToolStates() {
+ if (isEnabled()) {
+ Tool.Apply.action.setEnabled(!Tool.AutoApply.action.isChecked());
+ Tool.Undo.action.setEnabled(canUndo());
+ Tool.Redo.action.setEnabled(canRedo());
+ Tool.ZoomIn.action.setEnabled(canZoomIn());
+ Tool.ZoomOut.action.setEnabled(canZoomOut());
+ Tool.ZoomOriginal.action.setEnabled(canZoomOriginal());
+ Tool.ZoomBestFit.action.setEnabled(canZoomBestFit());
+ Tool.Foreground.action.setChecked(isAnnotatingForeground());
+ Tool.Background.action.setChecked(isAnnotatingBackground());
+ Tool.Clear.action.setEnabled(canClear());
+ Tool.AutoApply.action.setChecked(auto);
+ Tool.SegmenterOptions.action.setEnabled(canShowOptions());
+
+ // Always enabled if view enabled
+ Tool.SetBrushSize.action.setEnabled(true);
+ Tool.SegmenterOptions.action.setEnabled(true);
+ Tool.SetPainter.action.setEnabled(true);
+ Tool.Repaint.action.setEnabled(true);
+ Tool.Foreground.action.setEnabled(true);
+ Tool.Background.action.setEnabled(true);
+ Tool.AutoApply.action.setEnabled(true);
+
+ } else {
+ // Everything disabled
+ for (Tool t : Tool.values()) {
+ t.action.setEnabled(false);
+ }
+ }
+
+ }
+
+
+ private void setPainter() {
+ SegmentationPainter painter = painters.get(combo.getText());
+ setPainter(painter);
+ }
+
+
+ private void apply() {
+ performSegmentation(null, view.getContext());
+ }
+
+
+ private void showBrushControl(Event e) {
+ brushControl.showBelow(bar1, (ToolItem) e.widget);
+ }
+
+
+ /**
+ * Handles a context change in the view.
+ *
+ * @param e
+ * The event.
+ */
+ private void handleContextChanged(ContextChangedEvent e) {
+ if (e.oldContext != null) {
+ e.oldContext.removeAnnotationListener(eventHandler);
+
+ // Finish old segmentation
+ segmenterProxy.finish(e.oldContext);
+ }
+
+ if (e.newContext != null) {
+ e.newContext.addAnnotationListener(eventHandler);
+
+ // Begin new segmentation (can cause deferred events
+ // to be processed, so we async exec to ensure that
+ // the other context change handlers are called before
+ // deferred events are processed)
+ final SegmentationContext ctx = e.newContext;
+ getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ start(ctx);
+ }
+ });
+
+ }
+
+ updateToolStates();
+ }
+
+
+ /**
+ * Cleans up resources when the view is disposed.
+ */
+ private void handleDisposed() {
+
+ SegmentationContext ctx = view.getContext();
+ if (ctx != null && !ctx.isDisposed()) {
+
+ // Finish any segmentation
+ segmenterProxy.finish(ctx);
+
+ // Dispose context
+ ctx.dispose();
+ }
+
+ // Dispose all painters
+ painters.dispose();
+ }
+
+
+ /**
+ * Tool bar action. Delegates running to the execute method.
+ */
+ private class ToolAction extends Action {
+ private final Tool tool;
+
+ public ToolAction(Tool tool) {
+ super(SegmentationView.this.getText(tool), getType(tool));
+ this.tool = tool;
+ setToolTipText(SegmentationView.this.getToolTip(tool));
+ setImageDescriptor(SegmentationView.this.getImage(tool));
+ tool.action = this;
+ }
+
+ public void runWithEvent(Event e) {
+ execute(tool, e);
+ }
+ }
+
+
+ /**
+ * Handles various events coming from the view and toolbar controls.
+ *
+ */
+ private final class EventHandler extends SelectionAdapter implements
+ ZoomListener,
+ SelectionListener,
+ ContextChangeListener,
+ AnnotationListener,
+ DisposeListener
+ {
+
+ public void widgetDisposed(DisposeEvent e) {
+ handleDisposed();
+ }
+
+
+ public void zoomChanged(ZoomEvent e) {
+ updateToolStates();
+ }
+
+
+ public void widgetSelected(SelectionEvent e) {
+ setBrushSize(brushControl.getBrushSize());
+ }
+
+
+ public void contextChanged(ContextChangedEvent e) {
+ handleContextChanged(e);
+ }
+
+
+ public void annotationPerformed(AnnotationEvent e) {
+ if (!isEnabled()) {
+ log.warning("annotation performed while not enabled");
+ }
+
+ if (auto && isEnabled()) {
+ performSegmentation(e, view.getContext());
+ }
+ updateToolStates();
+ }
+
+
+ public void annotationRedone(AnnotationEvent e) {
+ if (!isEnabled()) {
+ log.warning("annotation redone while not enabled");
+ }
+
+ if (auto && isEnabled()) {
+ performSegmentation(e, view.getContext());
+ }
+ updateToolStates();
+ }
+
+
+ public void annotationUndone(AnnotationEvent e) {
+ if (!isEnabled()) {
+ log.warning("annotation undone while not enabled");
+ }
+
+ if (auto && isEnabled()) {
+ performSegmentation(e, view.getContext());
+ }
+
+ updateToolStates();
+ }
+
+
+ public void annotationsCleared(AnnotationEvent e) {
+ if (!isEnabled()) {
+ log.warning("annotations cleared while not enabled");
+ }
+
+ if (auto && isEnabled()) {
+ performSegmentation(e, view.getContext());
+ }
+ updateToolStates();
+ }
+ };
+
+ /**
+ * Class that prevents segmentation algorithms crashing the application
+ * by catching any thrown exceptions and logging them.
+ *
+ * @author Kevin McGuinness
+ */
+ private class RobustSegmenterProxy {
+
+ public void init(SegmentationContext ctx) {
+ if (segmenter != null) {
+ try {
+ segmenter.init(ctx);
+ } catch (Throwable th) {
+ severe(th, "%s.init()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public void update(SegmentationContext ctx) {
+ if (segmenter != null) {
+ try {
+ segmenter.update(ctx);
+ } catch (Throwable th) {
+ severe(th, "%s.update()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public void added(SegmentationContext ctx, Annotation a) {
+ if (segmenter != null) {
+ try {
+ segmenter.added(ctx, a);
+ } catch (Throwable th) {
+ severe(th, "%s.added()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public void removed(SegmentationContext ctx, Annotation a) {
+ if (segmenter != null) {
+ try {
+ segmenter.removed(ctx, a);
+ } catch (Throwable th) {
+ severe(th, "%s.removed()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public void finish(SegmentationContext ctx) {
+ if (segmenter != null) {
+ try {
+ segmenter.finish(ctx);
+ } catch (Throwable th) {
+ severe(th, "%s.finish()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public String getSegmenterClassName() {
+ return segmenter.getClass().getSimpleName();
+ }
+
+ private void severe(Throwable th, String message, Object ... args) {
+ log.log(Level.SEVERE, String.format(message, args), th);
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+
+import ie.dcu.apps.ist.controllers.AnnotationTool;
+import ie.dcu.apps.ist.event.*;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.segment.painters.*;
+import ie.dcu.swt.*;
+import ie.dcu.swt.event.ZoomListener;
+
+import java.util.*;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.*;
+
+public class AnnotatedImageControl extends Composite {
+ private final List<ContextChangeListener> listeners;
+ private final ImageControl view;
+ private SegmentationContext ctx;
+ private AnnotationTool tool;
+ private SegmentationPainter painter;
+ private Cursor cursor;
+
+ public AnnotatedImageControl(Composite parent, int style) {
+ super(parent, style);
+ setLayout(new FillLayout());
+ view = new ImageControl(this, SWT.NONE);
+ listeners = new ArrayList<ContextChangeListener>(2);
+ painter = new CombinedPainter();
+ view.getCanvas().addMouseMoveListener(cursorChanger);
+ addDisposeListener(disposeListener);
+ }
+
+
+ public ImageControl getImageControl() {
+ return view;
+ }
+
+
+ public Canvas getCanvas() {
+ return view.getCanvas();
+ }
+
+
+ public SegmentationPainter getPainter() {
+ return painter;
+ }
+
+
+ public void setPainter(SegmentationPainter painter) {
+ if (this.painter != painter) {
+ this.painter = painter;
+ recreate();
+ }
+ }
+
+
+ public SegmentationContext getContext() {
+ return ctx;
+ }
+
+
+ public void setContext(SegmentationContext ctx) {
+
+ // Remember old context for event
+ SegmentationContext old = this.ctx;
+
+ // Detach old tool
+ int lineWidth = 1;
+ if (tool != null) {
+ lineWidth = tool.getLineWidth();
+ tool.detach();
+ tool = null;
+ }
+
+ // Detach old annotation listener
+ if (ctx != null) {
+ ctx.getAnnotations().removeAnnotationListener(listener);
+ }
+
+ // Assign context
+ this.ctx = ctx;
+
+ if (ctx != null) {
+ // Attach new annotation listener
+ AnnotationManager annotations = ctx.getAnnotations();
+ annotations.addAnnotationListener(listener);
+
+ // Attach a new annotation tool
+ tool = new AnnotationTool(annotations, view);
+ tool.setLineWidth(lineWidth);
+ }
+
+ // Create the initial image
+ recreate();
+
+ // Fire event
+ fireContextChanged(old);
+ }
+
+
+ public int getLineWidth() {
+ if (tool != null) {
+ return tool.getLineWidth();
+ }
+ return 1;
+ }
+
+
+ public void setLineWidth(int width) {
+ if (tool != null) {
+ tool.setLineWidth(width);
+ }
+ }
+
+
+ public AnnotationType getAnnotationType() {
+ if (tool != null) {
+ return tool.getType();
+ }
+ return AnnotationType.Foreground;
+ }
+
+
+ public void setAnnotationType(AnnotationType type) {
+ if (tool != null) {
+ tool.setType(type);
+ }
+ }
+
+
+ public float getZoom() {
+ return view.getZoom();
+ }
+
+
+ public void setZoom(float zoom) {
+ view.setZoom(zoom);
+ }
+
+
+ public float getZoomStep() {
+ return view.getZoomStep();
+ }
+
+
+ public void setZoomStep(float zoomStep) {
+ view.setZoomStep(zoomStep);
+ }
+
+
+ public void zoomBestFit() {
+ view.zoomBestFit();
+ }
+
+
+ public void zoomIn() {
+ view.zoomIn();
+ }
+
+
+ public void zoomOriginal() {
+ view.zoomOriginal();
+ }
+
+
+ public void zoomOut() {
+ view.zoomOut();
+ }
+
+
+ public void undo() {
+ if (ctx != null) {
+ ctx.getAnnotations().undo();
+ }
+ }
+
+
+ public void redo() {
+ if (ctx != null) {
+ ctx.getAnnotations().redo();
+ }
+ }
+
+
+ public void clear() {
+ if (ctx != null) {
+ ctx.getAnnotations().clear();
+ }
+ }
+
+
+ public boolean canUndo() {
+ if (ctx != null) {
+ return ctx.getAnnotations().canUndo();
+ }
+ return false;
+ }
+
+
+ public boolean canRedo() {
+ if (ctx != null) {
+ return ctx.getAnnotations().canRedo();
+ }
+ return false;
+ }
+
+
+ public boolean canClear() {
+ if (ctx != null) {
+ return ctx.getAnnotations().count() > 0;
+ }
+ return false;
+ }
+
+
+ public boolean canZoomBestFit() {
+ return view.canZoomBestFit();
+ }
+
+
+ public boolean canZoomIn() {
+ return view.canZoomIn();
+ }
+
+
+ public boolean canZoomOriginal() {
+ return view.canZoomOriginal();
+ }
+
+
+ public boolean canZoomOut() {
+ return view.canZoomOut();
+ }
+
+
+ /**
+ * Redraw the entire canvas buffer.
+ */
+ public void repaint() {
+ if (ctx != null) {
+ painter.paint(ctx, view.getImage());
+ }
+ }
+
+
+ public boolean isAnnotatingForeground() {
+ return getAnnotationType() == AnnotationType.Foreground;
+ }
+
+
+ public boolean isAnnotatingBackground() {
+ return getAnnotationType() == AnnotationType.Background;
+ }
+
+
+ public void addAnnotationListener(AnnotationListener listener) {
+ if (ctx == null) {
+ throw new IllegalStateException();
+ }
+ ctx.getAnnotations().addAnnotationListener(listener);
+ }
+
+
+ public void removeAnnotationListener(AnnotationListener listener) {
+ if (ctx != null) {
+ ctx.getAnnotations().removeAnnotationListener(listener);
+ }
+ }
+
+
+ public void addZoomListener(ZoomListener listener) {
+ view.addZoomListener(listener);
+ }
+
+
+ public void removeZoomListener(ZoomListener listener) {
+ view.removeZoomListener(listener);
+ }
+
+
+ public void addContextChangeListener(ContextChangeListener listener) {
+ listeners.add(listener);
+ }
+
+
+ public void removeContextChangeListener(ContextChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+
+ private void fireContextChanged(SegmentationContext old) {
+ ContextChangedEvent e = null;
+ for (ContextChangeListener l : listeners) {
+ if (e == null) {
+ e = new ContextChangedEvent(this, old, ctx);
+ }
+ l.contextChanged(e);
+ }
+ }
+
+
+ /**
+ * Re-construct the display image buffer, and dispose the old one if
+ * necessary. If the context is <code>null</code>, then set the display
+ * buffer to null and dispose of the old one.
+ */
+ private void recreate() {
+ if (ctx == null) {
+
+ // Set null image (disposing the old one)
+ view.setImage(null, true);
+
+ } else {
+
+ // Check if we can reuse what we have
+ boolean sameSize = false;
+ if (view.hasImage()) {
+ if (view.getImageBounds().equals(ctx.getBounds())) {
+
+ // Okay, we can reuse the buffer we have :-)
+ sameSize = true;
+ }
+ }
+
+ if (!sameSize) {
+
+ // Create initial image
+ ObservableImage buffer = new ObservableImage(
+ SwtUtils.createImage(ctx.getBounds())
+ );
+
+ // Set the image (disposing the old one)
+ view.setImage(buffer, true);
+ }
+
+ // Draw the image
+ repaint();
+ }
+ }
+
+
+ private final DisposeListener disposeListener = new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ if (cursor != null) {
+ cursor.dispose();
+ }
+ }
+ };
+
+
+ /**
+ * Changes the cursor to a cross-hair when mouse is over the image.
+ *
+ */
+ private final MouseMoveListener cursorChanger = new MouseMoveListener() {
+
+ public void mouseMove(MouseEvent e) {
+ Canvas canvas = view.getCanvas();
+ if (view.imageContains(new Point(e.x, e.y))) {
+ if (cursor == null) {
+ cursor = CursorFactory.createCrosshairCursor();
+ }
+ canvas.setCursor(cursor);
+ } else {
+ canvas.setCursor(getDisplay().getSystemCursor(SWT.CURSOR_ARROW));
+ }
+ }
+ };
+
+
+ /**
+ * Listens for annotations and updates the buffer and view accordingly.
+ */
+ private final AnnotationListener listener = new AnnotationListener() {
+
+ public void annotationUndone(AnnotationEvent e) {
+ // Suspend notifications to prevent repainting the whole thing
+ view.getImage().setSuspendNotifications(true);
+
+ // Redraw all to buffer
+ repaint();
+
+ // Re-enable notifications
+ view.getImage().setSuspendNotifications(false);
+
+ // Repaint just the changed area
+ view.getImage().fireImageChanged(e.annotation.getBounds());
+ }
+
+
+ public void annotationsCleared(AnnotationEvent e) {
+ // Repaint everything
+ repaint();
+ }
+
+
+ public void annotationRedone(AnnotationEvent e) {
+ // Paint the new annotation
+ e.annotation.paint(view.getImage());
+ }
+
+
+ public void annotationPerformed(AnnotationEvent e) {
+ // Paint the new annotation
+ e.annotation.paint(view.getImage());
+ }
+ };
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+import ie.dcu.swt.PopupComposite;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Pop up control for changing brush sizes.
+ *
+ * @author Kevin McGuinness
+ */
+public class BrushControl extends PopupComposite {
+ private static final int MAX_BRUSH_SIZE = 20;
+
+ private final Label label;
+ private final Scale scale;
+
+
+ public BrushControl(Shell parent, int style) {
+ super(parent, style);
+
+ // Create controls
+ label = new Label(this, SWT.NONE);
+ scale = new Scale(this, SWT.NONE);
+
+ configureControls();
+ configureListeners();
+ layoutControls();
+ }
+
+
+ public void setBrushSize(int size) {
+ scale.setSelection(size);
+ updateLabel();
+ }
+
+
+ public int getBrushSize() {
+ return scale.getSelection();
+ }
+
+
+ public void addSelectionListener(SelectionListener listener) {
+ scale.addSelectionListener(listener);
+ }
+
+
+ public void removeSelectionListener(SelectionListener listener) {
+ scale.removeSelectionListener(listener);
+ }
+
+
+ private void configureControls() {
+ scale.setMinimum(1);
+ scale.setMaximum(MAX_BRUSH_SIZE);
+ scale.setIncrement(1);
+ scale.setPageIncrement(2);
+ setBrushSize(1);
+ }
+
+
+ private void configureListeners() {
+ scale.addSelectionListener(new ScaleChangeListener());
+ }
+
+
+ private void layoutControls() {
+ setLayout(new GridLayout());
+
+ // Layout label
+ GridData gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = SWT.FILL;
+ label.setLayoutData(gd);
+
+ // Layout scale control
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = SWT.FILL;
+ gd.minimumWidth = 150;
+ scale.setLayoutData(gd);
+ }
+
+
+ private void updateLabel() {
+ int size = scale.getSelection();
+ label.setText(String.format("Brush Size: %dpx ", size));
+ }
+
+
+ private final class ScaleChangeListener
+ extends SelectionAdapter {
+
+ public void widgetSelected(SelectionEvent e) {
+ updateLabel();
+ }
+ };
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+import java.beans.*;
+
+import org.eclipse.jface.resource.*;
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Button for selecting colors.
+ *
+ * @author Kevin McGuinness
+ */
+public class ColorSelector {
+ public static final String DATA_KEY = "ColorEditor";
+ public static final String COLOR_PROPERTY = "color";
+
+ private final PropertyChangeSupport pcs;
+ private final Point extent;
+ private final Button button;
+ private final ControlListener listener;
+
+ private Image image;
+ private RGB rgb;
+
+
+ public ColorSelector(Composite parent) {
+
+ pcs = new PropertyChangeSupport(this);
+ button = new Button(parent, SWT.PUSH);
+ extent = computeImageSize(parent);
+ image = new Image(parent.getDisplay(), extent.x, extent.y);
+ listener = new ControlListener();
+
+ GC gc = new GC(image);
+ gc.setBackground(button.getBackground());
+ gc.fillRectangle(0, 0, extent.x, extent.y);
+ gc.dispose();
+
+
+ button.setData(DATA_KEY, this);
+ button.setImage(image);
+ button.addListener(SWT.Selection, listener);
+ button.addListener(SWT.Dispose, listener);
+ }
+
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ pcs.addPropertyChangeListener(listener);
+ }
+
+
+ public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
+ pcs.addPropertyChangeListener(name, listener);
+ }
+
+
+ public PropertyChangeListener[] getPropertyChangeListeners() {
+ return pcs.getPropertyChangeListeners();
+ }
+
+
+ public PropertyChangeListener[] getPropertyChangeListeners(String name) {
+ return pcs.getPropertyChangeListeners(name);
+ }
+
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ pcs.removePropertyChangeListener(listener);
+ }
+
+
+ public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
+ pcs.removePropertyChangeListener(name, listener);
+ }
+
+
+ public RGB getColor() {
+ return rgb;
+ }
+
+
+ public void setColor(RGB rgb) {
+ if (rgb != null && this.rgb != rgb) {
+ RGB old = rgb;
+ this.rgb = rgb;
+ updateImage();
+ pcs.firePropertyChange(COLOR_PROPERTY, old, rgb);
+ }
+ }
+
+
+ public Button getButton() {
+ return button;
+ }
+
+
+ public void addListener(int eventType, Listener listener) {
+ button.addListener(eventType, listener);
+ }
+
+
+ public void setLayoutData(Object layoutData) {
+ button.setLayoutData(layoutData);
+ }
+
+
+ public void setData(Object data) {
+ button.setData(data);
+ }
+
+
+ public Object getData() {
+ return button.getData();
+ }
+
+
+ protected void updateImage() {
+ Display display = button.getDisplay();
+
+ GC gc = new GC(image);
+ gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
+ gc.drawRectangle(0, 2, extent.x - 1, extent.y - 4);
+
+ Color color = new Color(display, rgb);
+ gc.setBackground(color);
+ gc.fillRectangle(1, 3, extent.x - 2, extent.y - 5);
+ gc.dispose();
+
+ button.setImage(image);
+ color.dispose();
+ }
+
+
+ protected Point computeImageSize(Composite parent) {
+ GC gc = new GC(parent);
+ Font f = JFaceResources.getFontRegistry().get(JFaceResources.DEFAULT_FONT);
+ gc.setFont(f);
+ int height = gc.getFontMetrics().getHeight();
+ gc.dispose();
+ Point p = new Point(height * 3 - 6, height);
+ return p;
+ }
+
+
+ private void handleDispose(Event e) {
+ if (image != null) {
+ image.dispose();
+ image = null;
+ }
+ }
+
+
+ private void handleSelection(Event e) {
+ ColorDialog dialog = new ColorDialog(button.getShell());
+ dialog.setRGB(rgb);
+ RGB rgb = dialog.open();
+ setColor(rgb);
+ }
+
+
+ private final class ControlListener implements Listener {
+ public void handleEvent(Event e) {
+ switch (e.type) {
+ case SWT.Selection:
+ handleSelection(e);
+ break;
+ case SWT.Dispose:
+ handleDispose(e);
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+import java.net.URL;
+
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+
+public class ImageMenuManager extends MenuManager {
+ private Image image;
+
+ public ImageMenuManager() {
+ super();
+ }
+
+ public ImageMenuManager(String text) {
+ super(text);
+ }
+
+ public ImageMenuManager(String text, URL imageURL) {
+ super(text);
+ setImageURL(imageURL);
+ }
+
+ public void setImageURL(URL url) {
+ ImageDescriptor descriptor = ImageDescriptor.createFromURL(url);
+ this.image = descriptor.createImage();
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+
+ if (image != null) {
+ image.dispose();
+ }
+ }
+
+ @Override
+ public void fill(Menu parent, int index) {
+ super.fill(parent, index);
+ MenuItem item = getMenuItem();
+ if (item != null) {
+ item.setImage(image);
+ }
+ }
+
+ protected MenuItem getMenuItem() {
+ Menu menu = getMenu();
+ return (menu != null) ? menu.getParentItem() : null;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+
+import ie.dcu.apps.ist.event.*;
+
+import java.util.ArrayList;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * A timer user interface element. Displays a component used
+ * to implement a count-down.
+ *
+ * @author Kevin McGuinness
+ */
+public class SwtTimer implements TickerListener {
+
+ /**
+ * Timer is modeled as a state machine. These are the states.
+ */
+ public static enum State { Initial, Ready, Running, Paused };
+
+
+ /**
+ * List of listeners interested in changes to the object state.
+ */
+ private final ArrayList<StateListener> stateListeners;
+
+
+ /**
+ * List of listeners interested in timeouts.
+ */
+ private final ArrayList<TimeoutListener> timeoutListeners;
+
+
+ /**
+ * The drawing canvas where the numbers are drawn.
+ */
+ private final Canvas canvas;
+
+
+ /**
+ * The ticker posts tick events every second or so.
+ */
+ private Ticker ticker;
+
+
+ /**
+ * The current state of the timer.
+ */
+ private State state;
+
+
+ /**
+ * The font used to render the numbers.
+ */
+ private Font font;
+
+
+ /**
+ * Total time for the timer, in seconds.
+ */
+ private int time;
+
+
+ /**
+ * Remaining (displayed) time, in seconds.
+ */
+ private int remaining;
+
+
+ /**
+ * Create the timer. Style bits are passed to the underlying canvas.
+ *
+ * @param parent
+ * Parent container.
+ * @param style
+ * Style bits.
+ */
+ public SwtTimer(Composite parent, int style) {
+ stateListeners = new ArrayList<StateListener>(1);
+ timeoutListeners = new ArrayList<TimeoutListener>(1);
+ canvas = new Canvas(parent, style);
+ addListeners();
+ enter(State.Initial);
+ }
+
+
+ /**
+ * Add listeners to various components.
+ */
+ private void addListeners() {
+ // Listen for paint events
+ canvas.addPaintListener(new PaintListener() {
+ public void paintControl(PaintEvent e) {
+ paint(e.gc);
+ }
+ });
+
+ // Listen for dispose event
+ canvas.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ dispose();
+ }
+ });
+ }
+
+
+ /**
+ * Set the layout data for the component.
+ *
+ * @param data
+ * The layout data.
+ */
+ public void setLayoutData(Object data) {
+ canvas.setLayoutData(data);
+ }
+
+
+ /**
+ * Returns the layout data for the component.
+ *
+ * @return The layout data.
+ */
+ public Object getLayoutData() {
+ return canvas.getLayoutData();
+ }
+
+
+ /**
+ * Get the contained canvas.
+ *
+ * @return The contained canvas.
+ */
+ public Canvas getCanvas() {
+ return canvas;
+ }
+
+
+ /**
+ * Returns the shell associated with the canvas.
+ *
+ * @return The shell.
+ */
+ public Shell getShell() {
+ return canvas.getShell();
+ }
+
+
+ /**
+ * Get the state of the timer.
+ *
+ * @return The timer state.
+ */
+ public State getState() {
+ return state;
+ }
+
+
+ /**
+ * Add a state change listener.
+ *
+ * @param listener
+ * The listener.
+ */
+ public void addStateListener(StateListener listener) {
+ stateListeners.add(listener);
+ }
+
+
+ /**
+ * Remove a state change listener.
+ *
+ * @param listener
+ * The listener.
+ */
+ public void removeStateListener(StateListener listener) {
+ stateListeners.remove(listener);
+ }
+
+
+ /**
+ * Add a time-out listener.
+ *
+ * @param listener
+ * The listener.
+ */
+ public void addTimeoutListener(TimeoutListener listener) {
+ timeoutListeners.add(listener);
+ }
+
+
+ /**
+ * Remove a time-out listener.
+ *
+ * @param listener
+ * The listener.
+ */
+ public void removeTimeoutListener(TimeoutListener listener) {
+ timeoutListeners.remove(listener);
+ }
+
+
+ /**
+ * Returns an estimate of the elapsed time, in seconds.
+ *
+ * @return The elapsed time.
+ */
+ public int getElapsed() {
+ return time - remaining;
+ }
+
+
+ /**
+ * Set the timeout for the timer, in seconds.
+ *
+ * @param seconds
+ * The number of seconds on the timer.
+ * @throws IllegalStateException
+ * If {@link #canSet()} is <code>false</code>.
+ */
+ public void set(int seconds) throws IllegalStateException {
+ switch (state) {
+ case Initial:
+ case Ready:
+ time = seconds;
+ remaining = time;
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ enter(State.Ready);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if set can be called. Set cannot be called when
+ * the timer is running or paused.
+ *
+ * @return <code>true</code> if set can be called.
+ */
+ public boolean canSet() {
+ switch (state) {
+ case Initial:
+ case Ready:
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Start the timer. A new thread will control the timer count-down, so this
+ * method returns once it has started.
+ *
+ * @throws IllegalStateException
+ * If {@link #canStart()} is <code>false</code>.
+ */
+ public void start() throws IllegalStateException {
+ switch (state) {
+ case Ready:
+ ticker = new Ticker(this, 1000);
+ ticker.start();
+ break;
+ case Paused:
+ ticker.resume();
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ enter(State.Running);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if start can be called. Start cannot be called
+ * when the timer is in it's initial or running state.
+ *
+ * @return <code>true</code> if start can be called.
+ */
+ public boolean canStart() {
+ switch (state) {
+ case Ready:
+ case Paused:
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Pause the count-down. Temporarily suspends the count-down thread.
+ *
+ * @throws IllegalStateException
+ * If {@link #canPause()} is <code>false</code>.
+ */
+ public void pause() throws IllegalStateException {
+ switch (state) {
+ case Running:
+ ticker.pause();
+ break;
+ case Paused:
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ enter(State.Paused);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if pause can be called. Pause cannot be called
+ * when the timer is in it's initial or ready state.
+ *
+ * @return <code>true</code> if pause can be called.
+ */
+ public boolean canPause() {
+ switch (state) {
+ case Running:
+ case Paused:
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Reset the timer. Causes the timer to stop and return to the ready
+ * state, reseting the time-out to the last one set.
+ *
+ * @throws IllegalStateException
+ * If {@link #canReset()} is <code>false</code>.
+ */
+ public void reset() throws IllegalStateException {
+ switch (state) {
+ case Ready:
+ break;
+ case Running:
+ case Paused:
+ ticker.stop();
+ ticker = null;
+ remaining = time;
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ enter(State.Ready);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if reset can be called. Reset cannot be called
+ * when the timer is in it's initial state.
+ *
+ * @return <code>true</code> if reset can be called.
+ */
+ public boolean canReset() {
+ switch (state) {
+ case Ready:
+ case Running:
+ case Paused:
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Clear the timer. Returns the timer to its initial state, stopping
+ * it if necessary.
+ */
+ public void clear() {
+ switch (state) {
+ case Initial:
+ break;
+ case Ready:
+ time = 0;
+ remaining = 0;
+ break;
+ case Running:
+ case Paused:
+ ticker.stop();
+ ticker = null;
+ remaining = 0;
+ time = 0;
+ break;
+ }
+
+ enter(State.Initial);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if clear can be called.
+ *
+ * @return Always returns <code>true</code>.
+ */
+ public boolean canClear() {
+ return true;
+ }
+
+
+ /**
+ * Tell the timer to repaint itself.
+ */
+ public void repaint() {
+ if (!canvas.isDisposed()) {
+ canvas.redraw();
+ }
+ }
+
+
+ /**
+ * Called at each ticker tick interval. Should not be invoked by clients.
+ */
+ public void tick(final TickerEvent evt) {
+ if (canvas.isDisposed()) {
+ // Widget is disposed, ignore timer events
+ return;
+ }
+
+ long elapsed = evt.getElapsed();
+ int remaining = time - (int) (elapsed / 1000);
+
+ if (remaining <= 0) {
+ // We're done
+ this.remaining = 0;
+
+ // Stop and nullify ticker
+ if (ticker != null) {
+ ticker.stopLater();
+ ticker = null;
+ }
+
+ // Enqueue the ui update the event dispatch thread
+ canvas.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ enter(State.Initial);
+
+ // repaint
+ repaint();
+
+ // fire timeout event
+ fireTimeoutEvent();
+ }
+ });
+
+ } else if (this.remaining != remaining) {
+
+ // Update remaining
+ this.remaining = remaining;
+
+ // Enqueue a repaint
+ canvas.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ // repaint
+ repaint();
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Enter the given state.
+ *
+ * @param state The state.
+ */
+ private void enter(State state) {
+ if (this.state != state) {
+ this.state = state;
+ fireStateChanged();
+ }
+ }
+
+
+ /**
+ * Fires a state changed event.
+ */
+ private void fireStateChanged() {
+ if (!stateListeners.isEmpty()) {
+ StateEvent evt = new StateEvent(this);
+ for (StateListener s : stateListeners) {
+ s.stateChanged(evt);
+ }
+ }
+ }
+
+
+ /**
+ * Sends a timeout event to listeners.
+ */
+ private void fireTimeoutEvent() {
+ if (!timeoutListeners.isEmpty()) {
+ TimeoutEvent evt = new TimeoutEvent(this);
+ for (TimeoutListener t : timeoutListeners) {
+ t.timeoutOccured(evt);
+ }
+ }
+ }
+
+
+ /**
+ * Paints the timer.
+ *
+ * @param gc
+ * The graphics context.
+ */
+ private void paint(GC gc) {
+ gc.setFont(getTimerFont());
+
+ int ss = remaining % 60;
+ int mm = (remaining % 3600) / 60;
+ int hh = remaining / 3600;
+
+ String timestr = formatTime(ss, mm, hh);
+
+ Point t = getStringDimensions(gc, timestr);
+ Point c = getCanvasDimension();
+
+ int x = c.x / 2 - t.x / 2;
+ int y = c.y / 2 - t.y / 2;
+
+ if (remaining < 20 && state != State.Initial) {
+ gc.setForeground(getWarningColor());
+ } else {
+ gc.setForeground(getTimerColor());
+ }
+
+ gc.drawString(timestr, x, y);
+ }
+
+
+ /**
+ * Tidies native resources. Called automatically.
+ */
+ private void dispose() {
+ if (font != null) {
+ font.dispose();
+ font = null;
+ }
+ }
+
+
+ /**
+ * Formats a time string.
+ */
+ private String formatTime(int ss, int mm, int hh) {
+ String timestr;
+ if (hh == 0) {
+ timestr = String.format("%02d:%02d", mm, ss);
+ } else {
+ timestr = String.format("%02d:%02d:%02d", hh, mm, ss);
+ }
+ return timestr;
+ }
+
+
+ /**
+ * Returns the dimensions of a given string as drawn with the current font on
+ * the given graphics context.
+ */
+ private static Point getStringDimensions(GC gc, String str) {
+ int x = 0;
+ for (int i = 0; i < str.length(); i++) {
+ x += gc.getAdvanceWidth(str.charAt(i));
+ }
+ int y = gc.getFontMetrics().getHeight();
+ return new Point(x,y);
+ }
+
+
+ /**
+ * Returns the dimensions of the canvas as a point.
+ *
+ * @return A point where x is the width and y is the height.
+ */
+ private Point getCanvasDimension() {
+ Rectangle bounds = canvas.getBounds();
+ return new Point(bounds.width, bounds.height);
+ }
+
+
+ private Color getWarningColor() {
+ return getSystemColor(SWT.COLOR_RED);
+ }
+
+
+ private Color getTimerColor() {
+ return getSystemColor(SWT.COLOR_DARK_BLUE);
+ }
+
+
+ private Font getTimerFont() {
+ if (font == null) {
+ font = new Font(canvas.getDisplay(), "Sans", 30, SWT.NONE);
+ }
+ return font;
+ }
+
+
+ private Color getSystemColor(int id) {
+ return canvas.getDisplay().getSystemColor(id);
+ }
+
+ public static void main(String[] args) {
+ Display display = new Display();
+ Shell shell = new Shell(display, SWT.SHELL_TRIM);
+ shell.setBounds(800, 100, 220, 200);
+ shell.setLayout(new FillLayout());
+
+ SwtTimer timer = new SwtTimer(shell, SWT.NONE);
+ timer.set(120);
+ timer.start();
+
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ display.dispose();
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+import ie.dcu.apps.ist.event.*;
+
+
+public class Ticker implements Runnable {
+ private final TickerListener listener;
+
+ private Thread thread = null;
+ private boolean pause = false;
+ private boolean stop = false;
+ private long napTime = 100;
+ private long interval = 1000;
+ private long elapsed = 0;
+ private long startTime = 0;
+ private long pausedTime = 0;
+
+ // State variables
+ private boolean paused = false;
+ private boolean stopped = true;
+
+ public Ticker(TickerListener listener) {
+
+ // Check listener
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+
+ this.listener = listener;
+ }
+
+
+ public Ticker(TickerListener listener, long interval) {
+
+ // Check listener
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+
+ // Check interval
+ if (interval < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ // Assign
+ this.listener = listener;
+ this.interval = interval;
+ this.napTime = interval / 10;
+ }
+
+
+ public synchronized long elapsed() {
+ return elapsed;
+ }
+
+
+ public void start() {
+ if (thread != null) {
+ throw new IllegalStateException("Cannot restart threads");
+ }
+ thread = new Thread(this, "TimerThread");
+ thread.start();
+ }
+
+
+ public void run() {
+
+ synchronized (this) {
+ stopped = false;
+ }
+
+ // Set start time
+ startTime = System.currentTimeMillis();
+
+ // Loop
+ while (true) {
+
+ // Yawn! Z Z z z z Z Z Z z z z Z Z Z ...
+ sleep(napTime);
+
+ // Wait while thread is paused
+ synchronized (this) {
+ if (pause) {
+
+ // Remember when pausing started
+ long now = System.currentTimeMillis();
+
+ // Set paused flag to true and notify
+ paused = true;
+ notifyAll();
+
+ // Wait until woken up
+ while (paused) doWait();
+
+ // Add duration we've been paused for
+ pausedTime += System.currentTimeMillis() - now;
+
+ // Clear pause flag && notify
+ pause = false;
+ notifyAll();
+ }
+ }
+
+ // Check for stop flag
+ synchronized (this) {
+ if (stop) {
+ stopped = true;
+ notifyAll();
+ break;
+ }
+ }
+
+ // HI-HO-HI-HO ...
+
+ work();
+ }
+ }
+
+
+ private void work() {
+ boolean event = false;
+
+ synchronized (this) {
+ // Calculate total elapsed time in milliseconds
+ long now = System.currentTimeMillis();
+ long elapsed = now - (startTime + pausedTime);
+
+ // Calculate time elapsed since last event
+ long sinceLast = elapsed - this.elapsed;
+ if (interval <= sinceLast) {
+
+ // Compensate for any difference
+ long diff = sinceLast - interval;
+
+ // Its time for another event
+ this.elapsed = elapsed - diff;
+
+ // Fire event flag
+ event = true;
+ }
+ }
+
+ // Do event
+ if (event) {
+ synchronized (listener) {
+ listener.tick(new TickerEvent(this, elapsed));
+ }
+ }
+ }
+
+
+ public void pause() {
+ // Pause running thread
+ synchronized (this) {
+ pause = true;
+
+ // Wait for pause to occur
+ while (!paused) doWait();
+ }
+ }
+
+
+ public void resume() {
+ // Wake up paused threads
+ synchronized (this) {
+ if (paused) {
+ paused = false;
+ notifyAll();
+ }
+
+ while (pause) doWait();
+ }
+ }
+
+
+ public boolean isPaused() {
+ synchronized (this) {
+ return paused;
+ }
+ }
+
+
+ public void stop() {
+
+ synchronized (this) {
+
+ // Resume paused threads
+ if (paused) {
+ paused = false;
+ notifyAll();
+ }
+
+ while (pause) doWait();
+
+ // Stop
+ stop = true;
+
+ // Wait until stop block reached
+ while (!stopped) doWait();
+ }
+ }
+
+
+ public void stopLater() {
+ synchronized (this) {
+
+ // Resume paused threads
+ if (paused) {
+ paused = false;
+ notifyAll();
+ }
+
+ while (pause) doWait();
+
+ // Stop
+ stop = true;
+ }
+ }
+
+
+ public boolean isStopped() {
+ synchronized (this) {
+ return stopped;
+ }
+ }
+
+
+ private void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+
+
+ private void doWait() {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+}
+
+