diff --git a/.gitignore b/.gitignore
index 6aebbbf..a8961fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,9 @@
-.gradle/
-*.sw?
build/
+dist/
+.gradle/
+staging/
+temp/
+release
.sass-cache
*.build.tar.gz
+*.sw?
diff --git a/gui/.classpath b/gui/.classpath
new file mode 100644
index 0000000..d1b51d8
--- /dev/null
+++ b/gui/.classpath
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/.hgignore b/gui/.hgignore
new file mode 100755
index 0000000..390fc33
--- /dev/null
+++ b/gui/.hgignore
@@ -0,0 +1,4 @@
+.*.swp
+.*.swo
+staging
+dist
diff --git a/gui/.project b/gui/.project
new file mode 100644
index 0000000..cdd8d0a
--- /dev/null
+++ b/gui/.project
@@ -0,0 +1,18 @@
+
+
+ TimeStamper
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.groovy.core.groovyNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/gui/CHANGE ME b/gui/CHANGE ME
new file mode 100644
index 0000000..325d8bd
Binary files /dev/null and b/gui/CHANGE ME differ
diff --git a/gui/TimeStamper.iml b/gui/TimeStamper.iml
new file mode 100644
index 0000000..fcef4e2
--- /dev/null
+++ b/gui/TimeStamper.iml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/TimeStamper.ipr b/gui/TimeStamper.ipr
new file mode 100644
index 0000000..a93361e
--- /dev/null
+++ b/gui/TimeStamper.ipr
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/TimeStamper.iws b/gui/TimeStamper.iws
new file mode 100644
index 0000000..06c4249
--- /dev/null
+++ b/gui/TimeStamper.iws
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/TimeStamper.launch b/gui/TimeStamper.launch
new file mode 100644
index 0000000..54fc5b4
--- /dev/null
+++ b/gui/TimeStamper.launch
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/TimeStamper.tmproj b/gui/TimeStamper.tmproj
new file mode 100644
index 0000000..75c2fd9
--- /dev/null
+++ b/gui/TimeStamper.tmproj
@@ -0,0 +1,73 @@
+
+
+
+
+ documents
+
+
+ filename
+ TimeStamper.launch
+
+
+ filename
+ build.xml
+
+
+ name
+ griffon-app
+ regexFolderFilter
+ !.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$
+ sourceDirectory
+ griffon-app
+
+
+ name
+ test
+ regexFolderFilter
+ !.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$
+ sourceDirectory
+ test
+
+
+ name
+ lib
+ regexFolderFilter
+ !.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$
+ sourceDirectory
+ lib
+
+
+ name
+ scripts
+ regexFolderFilter
+ !.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$
+ sourceDirectory
+ scripts
+
+
+ name
+ src
+ regexFolderFilter
+ !.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$
+ sourceDirectory
+ src
+
+
+ name
+ web-app
+ regexFolderFilter
+ !.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$
+ sourceDirectory
+ web-app
+
+
+ fileHierarchyDrawerWidth
+ 200
+ metaData
+
+ showFileHierarchyDrawer
+
+ windowFrame
+ {{237, 127}, {742, 553}}
+
+
diff --git a/gui/application.properties b/gui/application.properties
new file mode 100755
index 0000000..aaefc60
--- /dev/null
+++ b/gui/application.properties
@@ -0,0 +1,10 @@
+#Do not edit app.griffon.* properties, they may change automatically. DO NOT put application configuration in here, it is not the right place!
+#Thu, 25 Apr 2013 06:40:33 -0500
+#Thu Apr 01 22:28:40 CDT 2010
+app.version=2.1
+app.griffon.version=1.2.0
+app.name=TimeStamper
+
+plugins.swing=1.2.0
+archetype.default=1.2.0
+app.toolkit=swing
diff --git a/gui/build.xml b/gui/build.xml
new file mode 100755
index 0000000..73ee5a7
--- /dev/null
+++ b/gui/build.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/doc/feed.pdf b/gui/doc/feed.pdf
new file mode 100755
index 0000000..307ff47
Binary files /dev/null and b/gui/doc/feed.pdf differ
diff --git a/gui/doc/feed.rst b/gui/doc/feed.rst
new file mode 100755
index 0000000..dd55374
--- /dev/null
+++ b/gui/doc/feed.rst
@@ -0,0 +1,77 @@
+Centralized vs. decentralized
+-----------------------------
+
+Centralized
+```````````
+
+- one central list
+- remote apps that sync with central?
+
+Decentralized
+`````````````
+
+- sync to URL(s)?
+
+ - need a network protocol
+ - HTTP?
+ - SSL?
+
+- group-wise sync?
+
+ - establish master/slaves?
+ - easier than coordinated group-update:::
+
+ map each URL to synch -> the last time updated.
+ if (update_period):
+ forall URLs: synch
+ else if (incoming_update):
+ forall (URLs older than incoming update): synch
+
+- synch based on hash of updates?
+
+ - need canonicalizer for text. Use XML?
+ - hash algorithm:::
+
+ SHA-1 of:
+ concatenate:
+ date (YYYYMMDDhhmmssSSS)
+ name
+ notes
+
+External Feeds
+--------------
+
+Item format
+```````````
+
+ - time started
+ - name/description
+ - notes
+ - category?
+
+Pull from
+`````````
+
+ - needs to be optional
+ - standardized input format
+
+ - easy to parse
+ - no errors, false positives
+ - restrictive.
+
+ - flexible input format
+
+ - matches regex's?
+ - map groups to fields
+
+Push to
+```````
+
+ - optional
+ - standardized output
+
+ - cannot be flexible to match output medium
+
+ - flexible input format
+
+ - choose fields and format values
diff --git a/gui/doc/sample-txt-timeline-with-sync-info.txt b/gui/doc/sample-txt-timeline-with-sync-info.txt
new file mode 100755
index 0000000..dd31de1
--- /dev/null
+++ b/gui/doc/sample-txt-timeline-with-sync-info.txt
@@ -0,0 +1,8 @@
+# sync-options:
+# LOCAL TIMELINE: this file
+# SYNC TO: ssh://jdbernard@jdbernard.no-ip.org/timelines/jdbernard.timeline.txt
+# SYNC TO: http://www.twitter.com/jdbernard
+# pull only
+# update every 30 sec
+# SYNC TO: file:///home/jdbernard/timelines/jdbernard.timeline.bak
+# push only
diff --git a/gui/doc/uml.tsm b/gui/doc/uml.tsm
new file mode 100644
index 0000000..a725ebd
Binary files /dev/null and b/gui/doc/uml.tsm differ
diff --git a/gui/griffon-app/conf/Application.groovy b/gui/griffon-app/conf/Application.groovy
new file mode 100755
index 0000000..77f1a7a
--- /dev/null
+++ b/gui/griffon-app/conf/Application.groovy
@@ -0,0 +1,27 @@
+application {
+ title="TimeStamper"
+ startupGroups=["TimeStamperMain"]
+ autoShutdown=true
+}
+mvcGroups {
+ LogDialog {
+ model="com.jdblabs.timestamper.gui.LogDialogModel"
+ controller="com.jdblabs.timestamper.gui.LogDialogController"
+ view="com.jdblabs.timestamper.gui.LogDialogView"
+ }
+ TimeStamperMain {
+ model="com.jdblabs.timestamper.gui.TimeStamperMainModel"
+ view="com.jdblabs.timestamper.gui.TimeStamperMainView"
+ controller="com.jdblabs.timestamper.gui.TimeStamperMainController"
+ }
+ PunchcardDialog {
+ model="com.jdblabs.timestamper.gui.PunchcardDialogModel"
+ view="com.jdblabs.timestamper.gui.PunchcardDialogView"
+ controller="com.jdblabs.timestamper.gui.PunchcardDialogController"
+ }
+ NotesDialog {
+ model="com.jdblabs.timestamper.gui.NotesDialogModel"
+ view="com.jdblabs.timestamper.gui.NotesDialogView"
+ controller="com.jdblabs.timestamper.gui.NotesDialogController"
+ }
+}
diff --git a/gui/griffon-app/conf/BuildConfig.groovy b/gui/griffon-app/conf/BuildConfig.groovy
new file mode 100644
index 0000000..0921a82
--- /dev/null
+++ b/gui/griffon-app/conf/BuildConfig.groovy
@@ -0,0 +1,191 @@
+// key signing information
+environments {
+ development {
+ signingkey {
+ params {
+ // sigfile = 'GRIFFON'
+ // keystore = "${basedir}/griffon-app/conf/keys/devKeystore"
+ // alias = 'development'
+ storepass = 'BadStorePassword'
+ keypass = 'BadKeyPassword'
+ lazy = true // only sign when unsigned
+ }
+ }
+ }
+ test {
+ griffon {
+ jars {
+ sign = false
+ pack = false
+ }
+ }
+ }
+ production {
+ signingkey {
+ params {
+ // NOTE: for production keys it is more secure to rely on key prompting
+ // no value means we will prompt //storepass = 'BadStorePassword'
+ // no value means we will prompt //keypass = 'BadKeyPassword'
+ lazy = false // sign, regardless of existing signatures
+ }
+ }
+
+ griffon {
+ jars {
+ sign = true
+ pack = true
+ destDir = "${basedir}/staging"
+ }
+ webstart {
+ codebase = 'CHANGE ME'
+ }
+ }
+ }
+}
+
+griffon {
+ memory {
+ //max = '64m'
+ //min = '2m'
+ //minPermSize = '2m'
+ //maxPermSize = '64m'
+ }
+ jars {
+ sign = false
+ pack = false
+ destDir = "${basedir}/staging"
+ jarName = "${appName}.jar"
+ }
+ extensions {
+ jarUrls = []
+ jnlpUrls = []
+ /*
+ props {
+ someProperty = 'someValue'
+ }
+ resources {
+ linux { // windows, macosx, solaris
+ jars = []
+ nativelibs = []
+ props {
+ someProperty = 'someValue'
+ }
+ }
+ }
+ */
+ }
+ webstart {
+ codebase = "${new File(griffon.jars.destDir).toURI().toASCIIString()}"
+ jnlp = 'application.jnlp'
+ }
+ applet {
+ jnlp = 'applet.jnlp'
+ html = 'applet.html'
+ }
+}
+
+// required for custom environments
+signingkey {
+ params {
+ def env = griffon.util.Environment.current.name
+ sigfile = 'GRIFFON-' + env
+ keystore = "${basedir}/griffon-app/conf/keys/${env}Keystore"
+ alias = env
+ // storepass = 'BadStorePassword'
+ // keypass = 'BadKeyPassword'
+ lazy = true // only sign when unsigned
+ }
+}
+
+griffon {
+ doc {
+ logo = '
'
+ sponsorLogo = "
"
+ footer = "
Made with Griffon (@griffon.version@)"
+ }
+}
+
+deploy {
+ application {
+ title = "${appName} ${appVersion}"
+ vendor = System.properties['user.name']
+ homepage = "http://localhost/${appName}"
+ description {
+ complete = "${appName} ${appVersion}"
+ oneline = "${appName} ${appVersion}"
+ minimal = "${appName} ${appVersion}"
+ tooltip = "${appName} ${appVersion}"
+ }
+ icon {
+ 'default' {
+ name = 'griffon-icon-64x64.png'
+ width = '64'
+ height = '64'
+ }
+ splash {
+ name = 'griffon.png'
+ width = '391'
+ height = '123'
+ }
+ selected {
+ name = 'griffon-icon-64x64.png'
+ width = '64'
+ height = '64'
+ }
+ disabled {
+ name = 'griffon-icon-64x64.png'
+ width = '64'
+ height = '64'
+ }
+ rollover {
+ name = 'griffon-icon-64x64.png'
+ width = '64'
+ height = '64'
+ }
+ shortcut {
+ name = 'griffon-icon-64x64.png'
+ width = '64'
+ height = '64'
+ }
+ }
+ }
+}
+
+griffon.project.dependency.resolution = {
+ // inherit Griffon' default dependencies
+ inherits("global") {
+ }
+ log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
+ repositories {
+ griffonHome()
+
+ // uncomment the below to enable remote dependency resolution
+ // from public Maven repositories
+ //mavenLocal()
+ //mavenCentral()
+ //mavenRepo "http://snapshots.repository.codehaus.org"
+ //mavenRepo "http://repository.codehaus.org"
+ //mavenRepo "http://download.java.net/maven/2/"
+ //mavenRepo "http://repository.jboss.com/maven2/"
+ }
+ dependencies {
+ // specify dependencies here under either 'build', 'compile', 'runtime' or 'test' scopes eg.
+
+ // runtime 'mysql:mysql-connector-java:5.1.5'
+ }
+}
+
+log4j = {
+ // Example of changing the log pattern for the default console
+ // appender:
+ appenders {
+ console name: 'stdout', layout: pattern(conversionPattern: '%d [%t] %-5p %c - %m%n')
+ }
+
+ error 'org.codehaus.griffon',
+ 'org.springframework',
+ 'org.apache.karaf',
+ 'groovyx.net'
+ warn 'griffon'
+}
+
diff --git a/gui/griffon-app/conf/Builder.groovy b/gui/griffon-app/conf/Builder.groovy
new file mode 100644
index 0000000..90271bb
--- /dev/null
+++ b/gui/griffon-app/conf/Builder.groovy
@@ -0,0 +1,7 @@
+
+root {
+ 'groovy.swing.SwingBuilder' {
+ controller = ['Threading']
+ view = '*'
+ }
+}
diff --git a/gui/griffon-app/conf/Config.groovy b/gui/griffon-app/conf/Config.groovy
new file mode 100644
index 0000000..a186a6d
--- /dev/null
+++ b/gui/griffon-app/conf/Config.groovy
@@ -0,0 +1,15 @@
+log4j = {
+ // Example of changing the log pattern for the default console
+ // appender:
+ appenders {
+ console name: 'stdout', layout: pattern(conversionPattern: '%d [%t] %-5p %c - %m%n')
+ }
+
+ error 'org.codehaus.griffon'
+
+ info 'griffon.util',
+ 'griffon.core',
+ 'griffon.@application.toolkit@',
+ 'griffon.app'
+}
+
diff --git a/gui/griffon-app/conf/Events.groovy b/gui/griffon-app/conf/Events.groovy
new file mode 100644
index 0000000..de9a54e
--- /dev/null
+++ b/gui/griffon-app/conf/Events.groovy
@@ -0,0 +1,5 @@
+import org.slf4j.LoggerFactory
+
+onNewInstance = { klass, type, instance ->
+ instance.metaClass.logger = LoggerFactory.getLogger(klass.name)
+}
diff --git a/gui/griffon-app/conf/keys/productionKeystore b/gui/griffon-app/conf/keys/productionKeystore
new file mode 100644
index 0000000..d195af0
Binary files /dev/null and b/gui/griffon-app/conf/keys/productionKeystore differ
diff --git a/gui/griffon-app/conf/webstart/applet.html b/gui/griffon-app/conf/webstart/applet.html
new file mode 100644
index 0000000..741952f
--- /dev/null
+++ b/gui/griffon-app/conf/webstart/applet.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/griffon-app/conf/webstart/applet.jnlp b/gui/griffon-app/conf/webstart/applet.jnlp
new file mode 100644
index 0000000..93967a0
--- /dev/null
+++ b/gui/griffon-app/conf/webstart/applet.jnlp
@@ -0,0 +1,55 @@
+
+
+
+
+ TimeStamper
+ TimeStamper
+
+
+ TimeStamper
+ TimeStamper
+ TimeStamper
+ TimeStamper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@jnlpJars@
+
+@jnlpExtensions@
+
+
+
+
+
+
+
diff --git a/gui/griffon-app/conf/webstart/application.jnlp b/gui/griffon-app/conf/webstart/application.jnlp
new file mode 100644
index 0000000..1bea5fe
--- /dev/null
+++ b/gui/griffon-app/conf/webstart/application.jnlp
@@ -0,0 +1,50 @@
+
+
+
+
+ TimeStamper
+ TimeStamper
+
+
+ TimeStamper
+ TimeStamper
+ TimeStamper
+ TimeStamper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@jnlpJars@
+
+@jnlpExtensions@
+
+
+
+
+
+
+
diff --git a/gui/griffon-app/conf/webstart/griffon-icon-128x128.png b/gui/griffon-app/conf/webstart/griffon-icon-128x128.png
new file mode 100755
index 0000000..b0b4327
Binary files /dev/null and b/gui/griffon-app/conf/webstart/griffon-icon-128x128.png differ
diff --git a/gui/griffon-app/conf/webstart/griffon-icon-16x16.png b/gui/griffon-app/conf/webstart/griffon-icon-16x16.png
new file mode 100755
index 0000000..90e269e
Binary files /dev/null and b/gui/griffon-app/conf/webstart/griffon-icon-16x16.png differ
diff --git a/gui/griffon-app/conf/webstart/griffon-icon-24x24.png b/gui/griffon-app/conf/webstart/griffon-icon-24x24.png
new file mode 100755
index 0000000..0d1bb73
Binary files /dev/null and b/gui/griffon-app/conf/webstart/griffon-icon-24x24.png differ
diff --git a/gui/griffon-app/conf/webstart/griffon-icon-256x256.png b/gui/griffon-app/conf/webstart/griffon-icon-256x256.png
new file mode 100755
index 0000000..3cdabd2
Binary files /dev/null and b/gui/griffon-app/conf/webstart/griffon-icon-256x256.png differ
diff --git a/gui/griffon-app/conf/webstart/griffon-icon-32x32.png b/gui/griffon-app/conf/webstart/griffon-icon-32x32.png
new file mode 100755
index 0000000..2c9c1e1
Binary files /dev/null and b/gui/griffon-app/conf/webstart/griffon-icon-32x32.png differ
diff --git a/gui/griffon-app/conf/webstart/griffon-icon-48x48.png b/gui/griffon-app/conf/webstart/griffon-icon-48x48.png
new file mode 100755
index 0000000..3577b8e
Binary files /dev/null and b/gui/griffon-app/conf/webstart/griffon-icon-48x48.png differ
diff --git a/gui/griffon-app/conf/webstart/griffon-icon-64x64.png b/gui/griffon-app/conf/webstart/griffon-icon-64x64.png
new file mode 100755
index 0000000..fe403f8
Binary files /dev/null and b/gui/griffon-app/conf/webstart/griffon-icon-64x64.png differ
diff --git a/gui/griffon-app/conf/webstart/griffon.png b/gui/griffon-app/conf/webstart/griffon.png
new file mode 100755
index 0000000..4a38291
Binary files /dev/null and b/gui/griffon-app/conf/webstart/griffon.png differ
diff --git a/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/LogDialogController.groovy b/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/LogDialogController.groovy
new file mode 100644
index 0000000..9363d8f
--- /dev/null
+++ b/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/LogDialogController.groovy
@@ -0,0 +1,16 @@
+package com.jdblabs.timestamper.gui
+
+class LogDialogController {
+ // these will be injected by Griffon
+ def model
+ def view
+
+ void mvcGroupInit(Map args) {
+ // this method is called after model and view are injected
+ }
+
+ /*
+ def action = { evt = null ->
+ }
+ */
+}
diff --git a/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/NotesDialogController.groovy b/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/NotesDialogController.groovy
new file mode 100644
index 0000000..1a9a544
--- /dev/null
+++ b/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/NotesDialogController.groovy
@@ -0,0 +1,18 @@
+package com.jdblabs.timestamper.gui
+
+import java.awt.Point
+
+class NotesDialogController {
+ // these will be injected by Griffon
+ def model
+ def view
+
+ void mvcGroupInit(Map args) {
+
+ def loc = model.mainMVC.view.frame.location
+ Point p = new Point(0, (int) model.mainMVC.view.frame.bounds.height + 50)
+ p.translate((int) loc.x, (int) loc.y)
+ view.dialog.location = p
+ }
+
+}
diff --git a/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/PunchcardDialogController.groovy b/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/PunchcardDialogController.groovy
new file mode 100644
index 0000000..426d2d2
--- /dev/null
+++ b/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/PunchcardDialogController.groovy
@@ -0,0 +1,22 @@
+package com.jdblabs.timestamper.gui
+
+import java.awt.Point
+
+class PunchcardDialogController {
+ // these will be injected by Griffon
+ def model
+ def view
+
+ void mvcGroupInit(Map args) {
+
+ def loc = model.mainMVC.view.frame.location
+ Point p = new Point(0, (int) model.mainMVC.view.frame.bounds.height + 50)
+ p.translate((int) loc.x, (int) loc.y)
+ view.dialog.location = p
+ }
+
+ /*
+ def action = { evt = null ->
+ }
+ */
+}
diff --git a/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/TimeStamperMainController.groovy b/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/TimeStamperMainController.groovy
new file mode 100644
index 0000000..2faba01
--- /dev/null
+++ b/gui/griffon-app/controllers/com/jdblabs/timestamper/gui/TimeStamperMainController.groovy
@@ -0,0 +1,168 @@
+package com.jdblabs.timestamper.gui
+
+import com.jdbernard.util.SmartConfig
+import com.jdblabs.timestamper.core.TimelineMarker
+import com.jdblabs.timestamper.core.TimelineProperties
+
+class TimeStamperMainController {
+ // these will be injected by Griffon
+ def model
+ def view
+
+ def thisMVC
+ def syncTimers = [:]
+
+ void mvcGroupInit(Map args) {
+
+ def configFile
+
+ logger.trace("Initializing TimeStamperMain MVC...")
+
+ // init mvc map
+ thisMVC = ['model': model, 'view': view, 'controller': this]
+
+ model.notesDialogMVC = buildMVCGroup('NotesDialog', 'notesDialog',
+ 'mainMVC': thisMVC)
+
+ model.punchcardDialogMVC = buildMVCGroup('PunchcardDialog',
+ 'punchcardDialog', 'mainMVC': thisMVC)
+
+ // load application properties
+ String userHomeDir = System.getProperty('user.home')
+ configFile = new File(userHomeDir, ".timestamperrc")
+
+ logger.trace("Reading configuration from {}", configFile.canonicalPath)
+
+ model.config = new SmartConfig(configFile)
+
+ // load the last used timeline file
+ String lastUsed = model.config.lastUsed
+ if (lastUsed == "") {
+ lastUsed = 'timeline.default.properties'
+ model.config.setProperty('lastUsed', lastUsed)
+ }
+
+ // load the plugin directory
+ File pluginDir = model.config.getProperty("pluginDir", new File("plugins"))
+ if (!pluginDir.exists()) pluginDir.mkdirs()
+
+ logger.trace("Adding plugin classpath: '{}'", pluginDir.canonicalPath)
+
+ def pluginLoader = new GroovyClassLoader(this.class.classLoader)
+ pluginLoader.addURL(pluginDir.toURI().toURL())
+ pluginDir.eachFileMatch(/.*\.jar/) { jarfile ->
+ pluginLoader.addURL(jarfile.toURI().toURL()) }
+
+ // instantiate plugins
+ model.config."plugin.classes".split(',').each { className -> try {
+ if (className.trim() == "") return
+ def pluginClass = pluginLoader.loadClass(className)
+ model.plugins <
+ wrapPluginCall { plugin.onStartup(thisMVC) } }
+
+ logger.trace("Reading Timeline properties from '{}'", lastUsed)
+
+ load(new File(lastUsed))
+ }
+
+ def wrapPluginCall(Closure c) {
+ try { c() } catch (Throwable t) {
+ logger.warn("Plugin threw an exception in ${functionName} " +
+ "when passed ${param}:", t)
+ return false
+ }
+ }
+
+ def load = { File propertiesFile ->
+ // pass through plugins
+ propertiesFile = model.plugins.inject(propertiesFile) { file, plugin ->
+ // call plugin
+ def ret = wrapPluginCall { plugin.onTimelineLoad(thisMVC, file) }
+ // if the plugin call succeeded, pass the result, else pass
+ // the last one
+ ret ? ret : file
+ }
+
+ try {
+ model.config.lastUsed = propertiesFile.canonicalPath
+ } catch (IOException ioe) { logger.error(ioe) }
+
+ // load the properties file
+ model.timelineProperties = new TimelineProperties(propertiesFile)
+
+ // load the main timeline
+ model.timeline = model.timelineProperties.timeline
+
+ // load the last marker
+ model.currentMarker = model.timeline.getLastMarker(new Date())
+ }
+
+ def saveas = {
+
+ }
+
+ def exitGracefully = { evt = null ->
+
+ // hide the frame immediately
+ view.frame.visible = false
+
+ logger.trace("Exiting gracefully.")
+
+ // save config
+ logger.debug("Config: {}", model.config)
+ model.config.save()
+
+ // save timeline and properties
+ model.timelineProperties.save()
+
+ // call plugin exit hooks
+ model.plugins.each { plugin ->
+ wrapPluginCall { plugin.onExit(thisMVC) } }
+
+ logger.trace("Completed graceful shutdown.")
+
+ app.shutdown()
+ }
+
+ def newTask = { mark, notes = "No comments.", timestamp = new Date() ->
+ TimelineMarker tm = new TimelineMarker(timestamp, mark, notes)
+
+ // pass through the plugins
+ tm = model.plugins.inject(tm) { marker, plugin ->
+ def ret = wrapPluginCall { plugin.onNewTask(thisMVC, marker) }
+ ret ? ret : marker
+ }
+
+ model.timeline.addMarker(tm)
+ model.currentMarker = model.timeline.getLastMarker(new Date())
+
+ // auto-persist if enabled
+ if (model.timelineProperties.persistOnUpdate)
+ model.timelineProperties.save()
+ }
+
+ def deleteTask = { marker ->
+ // pass through the plugins
+ model.plugins.each { plugin ->
+ wrapPluginCall { plugin.onDeleteTask(thisMVC, marker) } }
+
+ model.timeline.removeMarker(marker)
+ model.currentMarker = model.timeline.getLastMarker(new Date())
+
+ // auto-persist if enabled
+ if (model.timelineProperties.persistOnUpdate)
+ model.timelineProperties.save()
+ }
+
+}
diff --git a/gui/griffon-app/i18n/messages.properties b/gui/griffon-app/i18n/messages.properties
new file mode 100755
index 0000000..e69de29
diff --git a/gui/griffon-app/i18n/resources.properties b/gui/griffon-app/i18n/resources.properties
new file mode 100644
index 0000000..e69de29
diff --git a/gui/griffon-app/lifecycle/Initialize.groovy b/gui/griffon-app/lifecycle/Initialize.groovy
new file mode 100755
index 0000000..0d1f7b8
--- /dev/null
+++ b/gui/griffon-app/lifecycle/Initialize.groovy
@@ -0,0 +1,22 @@
+/*
+ * This script is executed inside the EDT, so be sure to
+ * call long running code in another thread.
+ *
+ * You have the following options
+ * - SwingBuilder.doOutside { // your code }
+ * - Thread.start { // your code }
+ * - SwingXBuilder.withWorker( start: true ) {
+ * onInit { // initialization (optional, runs in current thread) }
+ * work { // your code }
+ * onDone { // finish (runs inside EDT) }
+ * }
+ *
+ * You have the following options to run code again inside EDT
+ * - SwingBuilder.doLater { // your code }
+ * - SwingBuilder.edt { // your code }
+ * - SwingUtilities.invokeLater { // your code }
+ */
+
+import groovy.swing.SwingBuilder
+
+SwingBuilder.lookAndFeel('system', 'nimbus', ['metal', [boldFonts: false]])
diff --git a/gui/griffon-app/lifecycle/Ready.groovy b/gui/griffon-app/lifecycle/Ready.groovy
new file mode 100755
index 0000000..dd993de
--- /dev/null
+++ b/gui/griffon-app/lifecycle/Ready.groovy
@@ -0,0 +1,18 @@
+/*
+ * This script is executed inside the EDT, so be sure to
+ * call long running code in another thread.
+ *
+ * You have the following options
+ * - SwingBuilder.doOutside { // your code }
+ * - Thread.start { // your code }
+ * - SwingXBuilder.withWorker( start: true ) {
+ * onInit { // initialization (optional, runs in current thread) }
+ * work { // your code }
+ * onDone { // finish (runs inside EDT) }
+ * }
+ *
+ * You have the following options to run code again inside EDT
+ * - SwingBuilder.doLater { // your code }
+ * - SwingBuilder.edt { // your code }
+ * - SwingUtilities.invokeLater { // your code }
+ */
\ No newline at end of file
diff --git a/gui/griffon-app/lifecycle/Shutdown.groovy b/gui/griffon-app/lifecycle/Shutdown.groovy
new file mode 100755
index 0000000..dd993de
--- /dev/null
+++ b/gui/griffon-app/lifecycle/Shutdown.groovy
@@ -0,0 +1,18 @@
+/*
+ * This script is executed inside the EDT, so be sure to
+ * call long running code in another thread.
+ *
+ * You have the following options
+ * - SwingBuilder.doOutside { // your code }
+ * - Thread.start { // your code }
+ * - SwingXBuilder.withWorker( start: true ) {
+ * onInit { // initialization (optional, runs in current thread) }
+ * work { // your code }
+ * onDone { // finish (runs inside EDT) }
+ * }
+ *
+ * You have the following options to run code again inside EDT
+ * - SwingBuilder.doLater { // your code }
+ * - SwingBuilder.edt { // your code }
+ * - SwingUtilities.invokeLater { // your code }
+ */
\ No newline at end of file
diff --git a/gui/griffon-app/lifecycle/Startup.groovy b/gui/griffon-app/lifecycle/Startup.groovy
new file mode 100755
index 0000000..3b0e025
--- /dev/null
+++ b/gui/griffon-app/lifecycle/Startup.groovy
@@ -0,0 +1,19 @@
+/*
+ * This script is executed inside the EDT, so be sure to
+ * call long running code in another thread.
+ *
+ * You have the following options
+ * - SwingBuilder.doOutside { // your code }
+ * - Thread.start { // your code }
+ * - SwingXBuilder.withWorker( start: true ) {
+ * onInit { // initialization (optional, runs in current thread) }
+ * work { // your code }
+ * onDone { // finish (runs inside EDT) }
+ * }
+ *
+ * You have the following options to run code again inside EDT
+ * - SwingBuilder.doLater { // your code }
+ * - SwingBuilder.edt { // your code }
+ * - SwingUtilities.invokeLater { // your code }
+ */
+
diff --git a/gui/griffon-app/lifecycle/Stop.groovy b/gui/griffon-app/lifecycle/Stop.groovy
new file mode 100755
index 0000000..e69de29
diff --git a/gui/griffon-app/models/com/jdblabs/timestamper/gui/LogDialogModel.groovy b/gui/griffon-app/models/com/jdblabs/timestamper/gui/LogDialogModel.groovy
new file mode 100644
index 0000000..161a3a1
--- /dev/null
+++ b/gui/griffon-app/models/com/jdblabs/timestamper/gui/LogDialogModel.groovy
@@ -0,0 +1,7 @@
+package com.jdblabs.timestamper.gui
+
+import groovy.beans.Bindable
+
+class LogDialogModel {
+ def mainMVC
+}
diff --git a/gui/griffon-app/models/com/jdblabs/timestamper/gui/NotesDialogModel.groovy b/gui/griffon-app/models/com/jdblabs/timestamper/gui/NotesDialogModel.groovy
new file mode 100644
index 0000000..0d6ee3f
--- /dev/null
+++ b/gui/griffon-app/models/com/jdblabs/timestamper/gui/NotesDialogModel.groovy
@@ -0,0 +1,9 @@
+package com.jdblabs.timestamper.gui
+
+import groovy.beans.Bindable
+
+class NotesDialogModel {
+
+ // needs to be injected by buildMVCGroup call
+ def mainMVC
+}
diff --git a/gui/griffon-app/models/com/jdblabs/timestamper/gui/PunchcardDialogModel.groovy b/gui/griffon-app/models/com/jdblabs/timestamper/gui/PunchcardDialogModel.groovy
new file mode 100644
index 0000000..3c10ea9
--- /dev/null
+++ b/gui/griffon-app/models/com/jdblabs/timestamper/gui/PunchcardDialogModel.groovy
@@ -0,0 +1,9 @@
+package com.jdblabs.timestamper.gui
+
+import groovy.beans.Bindable
+
+class PunchcardDialogModel {
+
+ // needs to be injected by buildMVCGroup() call
+ def mainMVC
+}
diff --git a/gui/griffon-app/models/com/jdblabs/timestamper/gui/TimeStamperMainModel.groovy b/gui/griffon-app/models/com/jdblabs/timestamper/gui/TimeStamperMainModel.groovy
new file mode 100644
index 0000000..79e0039
--- /dev/null
+++ b/gui/griffon-app/models/com/jdblabs/timestamper/gui/TimeStamperMainModel.groovy
@@ -0,0 +1,25 @@
+package com.jdblabs.timestamper.gui
+
+import groovy.beans.Bindable
+import java.awt.Point
+import java.awt.Rectangle
+import java.util.Properties
+import com.jdbernard.util.SmartConfig
+import com.jdblabs.timestamper.core.Timeline
+import com.jdblabs.timestamper.core.TimelineMarker
+import com.jdblabs.timestamper.core.TimelineProperties
+
+class TimeStamperMainModel {
+ @Bindable TimelineMarker currentMarker
+ @Bindable Timeline timeline
+ @Bindable TimelineProperties timelineProperties
+ SmartConfig config
+
+ List plugins = []
+
+ def notesDialogMVC
+ def punchcardDialogMVC
+
+ @Bindable Point absoluteLocation
+ @Bindable Rectangle frameSize
+}
diff --git a/gui/griffon-app/resources/12-em-cross.png b/gui/griffon-app/resources/12-em-cross.png
new file mode 100644
index 0000000..65dfa8d
Binary files /dev/null and b/gui/griffon-app/resources/12-em-cross.png differ
diff --git a/gui/griffon-app/resources/16-em-cross-hover.png b/gui/griffon-app/resources/16-em-cross-hover.png
new file mode 100644
index 0000000..a7740b8
Binary files /dev/null and b/gui/griffon-app/resources/16-em-cross-hover.png differ
diff --git a/gui/griffon-app/resources/16-em-cross.png b/gui/griffon-app/resources/16-em-cross.png
new file mode 100644
index 0000000..466e3bb
Binary files /dev/null and b/gui/griffon-app/resources/16-em-cross.png differ
diff --git a/gui/griffon-app/resources/16-em-pencil.png b/gui/griffon-app/resources/16-em-pencil.png
new file mode 100644
index 0000000..1b4e958
Binary files /dev/null and b/gui/griffon-app/resources/16-em-pencil.png differ
diff --git a/gui/griffon-app/resources/16-file-archive.png b/gui/griffon-app/resources/16-file-archive.png
new file mode 100644
index 0000000..e8d07f5
Binary files /dev/null and b/gui/griffon-app/resources/16-file-archive.png differ
diff --git a/gui/griffon-app/resources/16-tool-a-hover.png b/gui/griffon-app/resources/16-tool-a-hover.png
new file mode 100644
index 0000000..f602f94
Binary files /dev/null and b/gui/griffon-app/resources/16-tool-a-hover.png differ
diff --git a/gui/griffon-app/resources/16-tool-a.png b/gui/griffon-app/resources/16-tool-a.png
new file mode 100644
index 0000000..dcf0cdf
Binary files /dev/null and b/gui/griffon-app/resources/16-tool-a.png differ
diff --git a/gui/griffon-app/resources/24-em-cross.png b/gui/griffon-app/resources/24-em-cross.png
new file mode 100644
index 0000000..a9e18d2
Binary files /dev/null and b/gui/griffon-app/resources/24-em-cross.png differ
diff --git a/gui/griffon-app/resources/24-message-info.png b/gui/griffon-app/resources/24-message-info.png
new file mode 100644
index 0000000..78cee30
Binary files /dev/null and b/gui/griffon-app/resources/24-message-info.png differ
diff --git a/gui/griffon-app/resources/appointment-new-16x16.png b/gui/griffon-app/resources/appointment-new-16x16.png
new file mode 100755
index 0000000..18b7c67
Binary files /dev/null and b/gui/griffon-app/resources/appointment-new-16x16.png differ
diff --git a/gui/griffon-app/resources/appointment-new-32x32.png b/gui/griffon-app/resources/appointment-new-32x32.png
new file mode 100644
index 0000000..85daef3
Binary files /dev/null and b/gui/griffon-app/resources/appointment-new-32x32.png differ
diff --git a/gui/griffon-app/resources/delete-marker.png b/gui/griffon-app/resources/delete-marker.png
new file mode 100644
index 0000000..237ae2a
Binary files /dev/null and b/gui/griffon-app/resources/delete-marker.png differ
diff --git a/gui/griffon-app/resources/document-open-16x16.png b/gui/griffon-app/resources/document-open-16x16.png
new file mode 100644
index 0000000..69dd8d4
Binary files /dev/null and b/gui/griffon-app/resources/document-open-16x16.png differ
diff --git a/gui/griffon-app/resources/document-save-16x16.png b/gui/griffon-app/resources/document-save-16x16.png
new file mode 100644
index 0000000..22ff495
Binary files /dev/null and b/gui/griffon-app/resources/document-save-16x16.png differ
diff --git a/gui/griffon-app/resources/document-save-as-16x16.png b/gui/griffon-app/resources/document-save-as-16x16.png
new file mode 100644
index 0000000..9bed143
Binary files /dev/null and b/gui/griffon-app/resources/document-save-as-16x16.png differ
diff --git a/gui/griffon-app/resources/griffon-icon-128x128.png b/gui/griffon-app/resources/griffon-icon-128x128.png
new file mode 100644
index 0000000..b0b4327
Binary files /dev/null and b/gui/griffon-app/resources/griffon-icon-128x128.png differ
diff --git a/gui/griffon-app/resources/griffon-icon-16x16.png b/gui/griffon-app/resources/griffon-icon-16x16.png
new file mode 100644
index 0000000..90e269e
Binary files /dev/null and b/gui/griffon-app/resources/griffon-icon-16x16.png differ
diff --git a/gui/griffon-app/resources/griffon-icon-24x24.png b/gui/griffon-app/resources/griffon-icon-24x24.png
new file mode 100644
index 0000000..0d1bb73
Binary files /dev/null and b/gui/griffon-app/resources/griffon-icon-24x24.png differ
diff --git a/gui/griffon-app/resources/griffon-icon-256x256.png b/gui/griffon-app/resources/griffon-icon-256x256.png
new file mode 100644
index 0000000..3cdabd2
Binary files /dev/null and b/gui/griffon-app/resources/griffon-icon-256x256.png differ
diff --git a/gui/griffon-app/resources/griffon-icon-32x32.png b/gui/griffon-app/resources/griffon-icon-32x32.png
new file mode 100644
index 0000000..2c9c1e1
Binary files /dev/null and b/gui/griffon-app/resources/griffon-icon-32x32.png differ
diff --git a/gui/griffon-app/resources/griffon-icon-48x48.png b/gui/griffon-app/resources/griffon-icon-48x48.png
new file mode 100644
index 0000000..3577b8e
Binary files /dev/null and b/gui/griffon-app/resources/griffon-icon-48x48.png differ
diff --git a/gui/griffon-app/resources/griffon-icon-64x64.png b/gui/griffon-app/resources/griffon-icon-64x64.png
new file mode 100644
index 0000000..fe403f8
Binary files /dev/null and b/gui/griffon-app/resources/griffon-icon-64x64.png differ
diff --git a/gui/griffon-app/resources/griffon.png b/gui/griffon-app/resources/griffon.png
new file mode 100644
index 0000000..4a38291
Binary files /dev/null and b/gui/griffon-app/resources/griffon.png differ
diff --git a/gui/griffon-app/resources/logback.groovy b/gui/griffon-app/resources/logback.groovy
new file mode 100644
index 0000000..6c91dc7
--- /dev/null
+++ b/gui/griffon-app/resources/logback.groovy
@@ -0,0 +1,20 @@
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder
+import ch.qos.logback.core.ConsoleAppender
+import ch.qos.logback.core.FileAppender
+import static ch.qos.logback.classic.Level.*
+
+appender("CONSOLE", ConsoleAppender) {
+ encoder(PatternLayoutEncoder) {
+ pattern = "%level - %msg%n"
+ }
+}
+
+appender("FILE", FileAppender) {
+ file="timestamper.log"
+ encoder(PatternLayoutEncoder) {
+ pattern = "%date %level %logger - %msg%n"
+ }
+}
+
+root(WARN, ["CONSOLE"])
+logger("com.jdblabs.*", TRACE, ["FILE"], false)
diff --git a/gui/griffon-app/resources/media-skip-backward.png b/gui/griffon-app/resources/media-skip-backward.png
new file mode 100644
index 0000000..94381f5
Binary files /dev/null and b/gui/griffon-app/resources/media-skip-backward.png differ
diff --git a/gui/griffon-app/resources/media-skip-forward.png b/gui/griffon-app/resources/media-skip-forward.png
new file mode 100644
index 0000000..758ec6f
Binary files /dev/null and b/gui/griffon-app/resources/media-skip-forward.png differ
diff --git a/gui/griffon-app/resources/new-marker.png b/gui/griffon-app/resources/new-marker.png
new file mode 100644
index 0000000..f5de8a5
Binary files /dev/null and b/gui/griffon-app/resources/new-marker.png differ
diff --git a/gui/griffon-app/resources/next-day.png b/gui/griffon-app/resources/next-day.png
new file mode 100644
index 0000000..a7de0fe
Binary files /dev/null and b/gui/griffon-app/resources/next-day.png differ
diff --git a/gui/griffon-app/resources/next-week.png b/gui/griffon-app/resources/next-week.png
new file mode 100644
index 0000000..4d7e2cd
Binary files /dev/null and b/gui/griffon-app/resources/next-week.png differ
diff --git a/gui/griffon-app/resources/previous-day.png b/gui/griffon-app/resources/previous-day.png
new file mode 100644
index 0000000..07dcbf1
Binary files /dev/null and b/gui/griffon-app/resources/previous-day.png differ
diff --git a/gui/griffon-app/resources/previous-week.png b/gui/griffon-app/resources/previous-week.png
new file mode 100644
index 0000000..ffcac31
Binary files /dev/null and b/gui/griffon-app/resources/previous-week.png differ
diff --git a/gui/griffon-app/session.vim b/gui/griffon-app/session.vim
new file mode 100644
index 0000000..62acf74
--- /dev/null
+++ b/gui/griffon-app/session.vim
@@ -0,0 +1,1880 @@
+let SessionLoad = 1
+if &cp | set nocp | endif
+let s:cpo_save=&cpo
+set cpo&vim
+inoremap
+nmap v :call Screen_Vars()
+nmap vip
+vmap {j"ry} :call Send_to_Screen(@r)
+map \dg DirDiffGet
+map \dp DirDiffPut
+map \dj DirDiffNext
+map \dk DirDiffPrev
+nmap gx NetrwBrowseX
+nnoremap NetrwBrowseX :call netrw#NetrwBrowseX(expand(""),0)
+let &cpo=s:cpo_save
+unlet s:cpo_save
+set autoindent
+set backspace=indent,eol,start
+set errorformat=%A\ %#[javac]\ %f:%l:\ %m,%-Z\ %#[javac]\ %p^,%-C%.%#
+set expandtab
+set fileencodings=ucs-bom,utf-8,default,latin1
+set helplang=en
+set history=50
+set hlsearch
+set iminsert=0
+set imsearch=0
+set incsearch
+set makeprg=ant
+set nomodeline
+set printoptions=paper:letter
+set ruler
+set runtimepath=~/.vim,/var/lib/vim/addons,/usr/share/vim/vimfiles,/usr/share/vim/vim72,/usr/share/vim/vimfiles/after,/var/lib/vim/addons/after,~/.vim/after
+set shiftwidth=4
+set showcmd
+set suffixes=.bak,~,.swp,.o,.info,.aux,.log,.dvi,.bbl,.blg,.brf,.cb,.ind,.idx,.ilg,.inx,.out,.toc
+set tabstop=4
+let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0
+let v:this_session=expand(":p")
+silent only
+cd ~/projects/TimeStamper/griffon-app
+if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''
+ let s:wipebuf = bufnr('%')
+endif
+set shortmess=aoO
+badd +88 views/com/jdblabs/timestamper/TimeStamperMainView.groovy
+badd +24 models/com/jdblabs/timestamper/TimeStamperMainModel.groovy
+badd +64 controllers/com/jdblabs/timestamper/TimeStamperMainController.groovy
+badd +1 views/com/jdblabs/timestamper/NotesDialogView.groovy
+badd +8 models/com/jdblabs/timestamper/NotesDialogModel.groovy
+badd +1 controllers/com/jdblabs/timestamper/NotesDialogController.groovy
+badd +1 views/com/jdblabs/timestamper/PunchcardDialogView.groovy
+badd +7 models/com/jdblabs/timestamper/PunchcardDialogModel.groovy
+badd +1 controllers/com/jdblabs/timestamper/PunchcardDialogController.groovy
+badd +215 ../src/main/com/jdblabs/timestamper/gui/TimelineDayDisplay.java
+badd +8 ../src/main/com/jdblabs/timestamper/gui/WindowInformation.groovy
+badd +28 conf/Application.groovy
+badd +1 conf/Builder.groovy
+badd +1 conf/Config.groovy
+badd +1 actions/com/jdblabs/timestamper/NotesDialogActions.groovy
+badd +1 actions/com/jdblabs/timestamper/TimeStamperMainActions.groovy
+badd +24 lifecycle/Initialize.groovy
+badd +1 conf/Events.groovy
+badd +13 resources/log4j.properties
+badd +1 ../src/main/com/jdblabs/timestamper/GUIUtil.groovy
+badd +1 controllers/com/jdblabs/timestamper/LogDialogController.groovy
+badd +1 models/com/jdblabs/timestamper/LogDialogModel.groovy
+badd +0 views/com/jdblabs/timestamper/LogDialogView.groovy
+badd +0 ../src/main/com/jdblabs/GUIUtil.groovy
+args views/com/jdblabs/timestamper/TimeStamperMainView.groovy
+edit views/com/jdblabs/timestamper/TimeStamperMainView.groovy
+set splitbelow splitright
+wincmd _ | wincmd |
+vsplit
+1wincmd h
+wincmd w
+wincmd _ | wincmd |
+split
+1wincmd k
+wincmd w
+set nosplitbelow
+set nosplitright
+wincmd t
+set winheight=1 winwidth=1
+exe 'vert 1resize ' . ((&columns * 90 + 91) / 182)
+exe '2resize ' . ((&lines * 23 + 40) / 81)
+exe 'vert 2resize ' . ((&columns * 91 + 91) / 182)
+exe '3resize ' . ((&lines * 54 + 40) / 81)
+exe 'vert 3resize ' . ((&columns * 91 + 91) / 182)
+argglobal
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 110 - ((46 * winheight(0) + 39) / 78)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+110
+normal! 011l
+wincmd w
+argglobal
+edit models/com/jdblabs/timestamper/TimeStamperMainModel.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 23 - ((19 * winheight(0) + 11) / 23)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+23
+normal! 0
+wincmd w
+argglobal
+edit controllers/com/jdblabs/timestamper/TimeStamperMainController.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 34 - ((17 * winheight(0) + 27) / 54)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+34
+normal! 036l
+wincmd w
+3wincmd w
+exe 'vert 1resize ' . ((&columns * 90 + 91) / 182)
+exe '2resize ' . ((&lines * 23 + 40) / 81)
+exe 'vert 2resize ' . ((&columns * 91 + 91) / 182)
+exe '3resize ' . ((&lines * 54 + 40) / 81)
+exe 'vert 3resize ' . ((&columns * 91 + 91) / 182)
+tabedit views/com/jdblabs/timestamper/NotesDialogView.groovy
+set splitbelow splitright
+wincmd _ | wincmd |
+vsplit
+1wincmd h
+wincmd w
+wincmd _ | wincmd |
+split
+1wincmd k
+wincmd w
+set nosplitbelow
+set nosplitright
+wincmd t
+set winheight=1 winwidth=1
+exe 'vert 1resize ' . ((&columns * 90 + 91) / 182)
+exe '2resize ' . ((&lines * 12 + 40) / 81)
+exe 'vert 2resize ' . ((&columns * 91 + 91) / 182)
+exe '3resize ' . ((&lines * 65 + 40) / 81)
+exe 'vert 3resize ' . ((&columns * 91 + 91) / 182)
+argglobal
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 49 - ((48 * winheight(0) + 39) / 78)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+49
+normal! 025l
+wincmd w
+argglobal
+edit models/com/jdblabs/timestamper/NotesDialogModel.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 1 - ((0 * winheight(0) + 6) / 12)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+1
+normal! 0
+wincmd w
+argglobal
+edit controllers/com/jdblabs/timestamper/NotesDialogController.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 1 - ((0 * winheight(0) + 32) / 65)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+1
+normal! 0
+wincmd w
+3wincmd w
+exe 'vert 1resize ' . ((&columns * 90 + 91) / 182)
+exe '2resize ' . ((&lines * 12 + 40) / 81)
+exe 'vert 2resize ' . ((&columns * 91 + 91) / 182)
+exe '3resize ' . ((&lines * 65 + 40) / 81)
+exe 'vert 3resize ' . ((&columns * 91 + 91) / 182)
+tabedit views/com/jdblabs/timestamper/PunchcardDialogView.groovy
+set splitbelow splitright
+wincmd _ | wincmd |
+vsplit
+1wincmd h
+wincmd w
+wincmd _ | wincmd |
+split
+1wincmd k
+wincmd w
+set nosplitbelow
+set nosplitright
+wincmd t
+set winheight=1 winwidth=1
+exe 'vert 1resize ' . ((&columns * 90 + 91) / 182)
+exe '2resize ' . ((&lines * 12 + 40) / 81)
+exe 'vert 2resize ' . ((&columns * 91 + 91) / 182)
+exe '3resize ' . ((&lines * 65 + 40) / 81)
+exe 'vert 3resize ' . ((&columns * 91 + 91) / 182)
+argglobal
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 54 - ((51 * winheight(0) + 39) / 78)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+54
+normal! 011l
+wincmd w
+argglobal
+edit models/com/jdblabs/timestamper/PunchcardDialogModel.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 8 - ((6 * winheight(0) + 6) / 12)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+8
+normal! 07l
+wincmd w
+argglobal
+edit controllers/com/jdblabs/timestamper/PunchcardDialogController.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 1 - ((0 * winheight(0) + 32) / 65)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+1
+normal! 0
+wincmd w
+3wincmd w
+exe 'vert 1resize ' . ((&columns * 90 + 91) / 182)
+exe '2resize ' . ((&lines * 12 + 40) / 81)
+exe 'vert 2resize ' . ((&columns * 91 + 91) / 182)
+exe '3resize ' . ((&lines * 65 + 40) / 81)
+exe 'vert 3resize ' . ((&columns * 91 + 91) / 182)
+tabedit views/com/jdblabs/timestamper/LogDialogView.groovy
+set splitbelow splitright
+wincmd _ | wincmd |
+vsplit
+1wincmd h
+wincmd w
+wincmd _ | wincmd |
+split
+1wincmd k
+wincmd w
+set nosplitbelow
+set nosplitright
+wincmd t
+set winheight=1 winwidth=1
+exe 'vert 1resize ' . ((&columns * 91 + 91) / 182)
+exe '2resize ' . ((&lines * 14 + 40) / 81)
+exe 'vert 2resize ' . ((&columns * 90 + 91) / 182)
+exe '3resize ' . ((&lines * 63 + 40) / 81)
+exe 'vert 3resize ' . ((&columns * 90 + 91) / 182)
+argglobal
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal nomodeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 6 - ((5 * winheight(0) + 39) / 78)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+6
+normal! 04l
+wincmd w
+argglobal
+edit models/com/jdblabs/timestamper/LogDialogModel.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal nomodeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 6 - ((5 * winheight(0) + 7) / 14)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+6
+normal! 014l
+wincmd w
+argglobal
+edit controllers/com/jdblabs/timestamper/LogDialogController.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal nomodeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 3 - ((2 * winheight(0) + 31) / 63)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+3
+normal! 0
+wincmd w
+3wincmd w
+exe 'vert 1resize ' . ((&columns * 91 + 91) / 182)
+exe '2resize ' . ((&lines * 14 + 40) / 81)
+exe 'vert 2resize ' . ((&columns * 90 + 91) / 182)
+exe '3resize ' . ((&lines * 63 + 40) / 81)
+exe 'vert 3resize ' . ((&columns * 90 + 91) / 182)
+tabedit conf/Application.groovy
+set splitbelow splitright
+wincmd _ | wincmd |
+vsplit
+1wincmd h
+wincmd w
+set nosplitbelow
+set nosplitright
+wincmd t
+set winheight=1 winwidth=1
+exe 'vert 1resize ' . ((&columns * 91 + 91) / 182)
+exe 'vert 2resize ' . ((&columns * 90 + 91) / 182)
+argglobal
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 28 - ((24 * winheight(0) + 39) / 78)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+28
+normal! 08l
+wincmd w
+argglobal
+edit lifecycle/Initialize.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal nomodeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 37 - ((36 * winheight(0) + 39) / 78)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+37
+normal! 035l
+wincmd w
+3wincmd w
+exe 'vert 1resize ' . ((&columns * 91 + 91) / 182)
+exe 'vert 2resize ' . ((&columns * 90 + 91) / 182)
+tabedit ../src/main/com/jdblabs/timestamper/gui/TimelineDayDisplay.java
+set splitbelow splitright
+wincmd _ | wincmd |
+vsplit
+1wincmd h
+wincmd w
+set nosplitbelow
+set nosplitright
+wincmd t
+set winheight=1 winwidth=1
+exe 'vert 1resize ' . ((&columns * 91 + 91) / 182)
+exe 'vert 2resize ' . ((&columns * 90 + 91) / 182)
+argglobal
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'java'
+setlocal filetype=java
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal modeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'java'
+setlocal syntax=java
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+62,69fold
+71,74fold
+76,214fold
+62
+normal zc
+71
+normal zc
+76
+normal zc
+let s:l = 215 - ((190 * winheight(0) + 39) / 78)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+215
+normal! 0
+wincmd w
+argglobal
+edit ../src/main/com/jdblabs/GUIUtil.groovy
+setlocal keymap=
+setlocal noarabic
+setlocal autoindent
+setlocal balloonexpr=
+setlocal nobinary
+setlocal bufhidden=
+setlocal buflisted
+setlocal buftype=
+setlocal nocindent
+setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
+setlocal cinoptions=
+setlocal cinwords=if,else,while,do,for,switch
+setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
+setlocal commentstring=/*%s*/
+setlocal complete=.,w,b,u,t,i
+setlocal completefunc=
+setlocal nocopyindent
+setlocal nocursorcolumn
+setlocal nocursorline
+setlocal define=
+setlocal dictionary=
+setlocal nodiff
+setlocal equalprg=
+setlocal errorformat=
+setlocal expandtab
+if &filetype != 'groovy'
+setlocal filetype=groovy
+endif
+setlocal foldcolumn=0
+setlocal foldenable
+setlocal foldexpr=0
+setlocal foldignore=#
+setlocal foldlevel=0
+setlocal foldmarker={{{,}}}
+setlocal foldmethod=manual
+setlocal foldminlines=1
+setlocal foldnestmax=20
+setlocal foldtext=foldtext()
+setlocal formatexpr=
+setlocal formatoptions=tcq
+setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
+setlocal grepprg=
+setlocal iminsert=0
+setlocal imsearch=0
+setlocal include=
+setlocal includeexpr=
+setlocal indentexpr=
+setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
+setlocal noinfercase
+setlocal iskeyword=@,48-57,_,192-255
+setlocal keywordprg=
+setlocal nolinebreak
+setlocal nolisp
+setlocal nolist
+setlocal makeprg=
+setlocal matchpairs=(:),{:},[:]
+setlocal nomodeline
+setlocal modifiable
+setlocal nrformats=octal,hex
+set number
+setlocal number
+setlocal numberwidth=4
+setlocal omnifunc=
+setlocal path=
+setlocal nopreserveindent
+setlocal nopreviewwindow
+setlocal quoteescape=\\
+setlocal noreadonly
+setlocal norightleft
+setlocal rightleftcmd=search
+setlocal noscrollbind
+setlocal shiftwidth=4
+setlocal noshortname
+setlocal nosmartindent
+setlocal softtabstop=0
+setlocal nospell
+setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
+setlocal spellfile=
+setlocal spelllang=en
+setlocal statusline=
+setlocal suffixesadd=
+setlocal swapfile
+setlocal synmaxcol=3000
+if &syntax != 'groovy'
+setlocal syntax=groovy
+endif
+setlocal tabstop=4
+setlocal tags=
+setlocal textwidth=0
+setlocal thesaurus=
+setlocal nowinfixheight
+setlocal nowinfixwidth
+setlocal wrap
+setlocal wrapmargin=0
+silent! normal! zE
+let s:l = 1 - ((0 * winheight(0) + 39) / 78)
+if s:l < 1 | let s:l = 1 | endif
+exe s:l
+normal! zt
+1
+normal! 0
+wincmd w
+3wincmd w
+exe 'vert 1resize ' . ((&columns * 91 + 91) / 182)
+exe 'vert 2resize ' . ((&columns * 90 + 91) / 182)
+tabnext 1
+if exists('s:wipebuf')
+ silent exe 'bwipe ' . s:wipebuf
+endif
+unlet! s:wipebuf
+set winheight=1 winwidth=20 shortmess=filnxtToO
+let s:sx = expand(":p:r")."x.vim"
+if file_readable(s:sx)
+ exe "source " . fnameescape(s:sx)
+endif
+let &so = s:so_save | let &siso = s:siso_save
+doautoall SessionLoadPost
+unlet SessionLoad
+" vim: set ft=vim :
diff --git a/gui/griffon-app/views/com/jdblabs/timestamper/gui/LogDialogView.groovy b/gui/griffon-app/views/com/jdblabs/timestamper/gui/LogDialogView.groovy
new file mode 100644
index 0000000..6004dcb
--- /dev/null
+++ b/gui/griffon-app/views/com/jdblabs/timestamper/gui/LogDialogView.groovy
@@ -0,0 +1,10 @@
+package com.jdblabs.timestamper.gui
+
+import javax.swing.JDialog
+
+dialog = dialog(new JDialog(model.mainMVC.view.frame),
+ title: 'Error Messages...',
+ modal: false) {
+
+ logger.trace( "Building LogDialog view." )
+}
diff --git a/gui/griffon-app/views/com/jdblabs/timestamper/gui/NotesDialogView.groovy b/gui/griffon-app/views/com/jdblabs/timestamper/gui/NotesDialogView.groovy
new file mode 100644
index 0000000..0176083
--- /dev/null
+++ b/gui/griffon-app/views/com/jdblabs/timestamper/gui/NotesDialogView.groovy
@@ -0,0 +1,65 @@
+package com.jdblabs.timestamper.gui
+
+import java.awt.Color
+import java.awt.Point
+import java.awt.Rectangle
+import java.awt.Toolkit
+import javax.swing.BoxLayout
+import javax.swing.JDialog
+import net.miginfocom.swing.MigLayout
+
+Point mousePressRelativeToDialog
+Point offsetFromMainFrame = new Point(0,0)
+boolean coupledToMainFrame = false
+
+mousePressed = { evt -> mousePressRelativeToDialog = evt?.point }
+
+mouseDragged = { evt ->
+ GUIUtil.componentDragged(dialog, evt, mousePressRelativeToDialog,
+ new Rectangle(Toolkit.defaultToolkit.screenSize),
+ model.mainMVC.view.frame.bounds)
+
+ offsetFromMainFrame = GUIUtil.calculateOffset(
+ model.mainMVC.view.frame, dialog)
+
+ coupledToMainFrame = GUIUtil.componentsCoupled(dialog,
+ model.mainMVC.view.frame);
+
+}
+
+dialog = dialog(new JDialog(model.mainMVC.view.frame),
+ title: 'Notes',
+ modal: false,
+ undecorated: true,
+ minimumSize: [325, 200],
+ mousePressed: mousePressed,
+ mouseDragged: mouseDragged,
+ iconImage: imageIcon('/16-em-pencil.png').image,
+ iconImages: [imageIcon('/16-em-pencil.png').image],
+ location: bind(source: model.mainMVC.model,
+ sourceProperty: 'absoluteLocation',
+ converter: { loc ->
+ if (coupledToMainFrame) {
+ Point p = new Point(offsetFromMainFrame)
+ p.translate((int) loc.x, (int) loc.y)
+ return p
+ } else return dialog.location })
+) {
+ logger.trace('Building NotesDialog GUI')
+ panel(
+ border:lineBorder(color: Color.BLACK, thickness:1, parent:true),
+ layout: new MigLayout('insets 10 10 10 10, fill')
+ ) {
+ scrollPane(constraints: 'growx, growy') {
+ notesTextArea = textArea(lineWrap: true, columns: 20, rows: 5,
+ wrapStyleWord: true,
+ text: bind(source: model.mainMVC.model,
+ sourceProperty: 'currentMarker',
+ sourceValue: { model.mainMVC.model.currentMarker?.notes}),
+ keyReleased: {
+ if (model.mainMVC.model.currentMarker != null)
+ model.mainMVC.model.currentMarker.notes =
+ notesTextArea.text})
+ }
+ }
+}
diff --git a/gui/griffon-app/views/com/jdblabs/timestamper/gui/PunchcardDialogView.groovy b/gui/griffon-app/views/com/jdblabs/timestamper/gui/PunchcardDialogView.groovy
new file mode 100644
index 0000000..d321ad1
--- /dev/null
+++ b/gui/griffon-app/views/com/jdblabs/timestamper/gui/PunchcardDialogView.groovy
@@ -0,0 +1,164 @@
+package com.jdblabs.timestamper.gui
+
+import java.awt.Color
+import java.awt.Point
+import java.awt.Toolkit
+import java.awt.Rectangle
+import java.beans.PropertyChangeListener
+import java.text.SimpleDateFormat
+import javax.swing.JDialog
+import java.util.Calendar
+import javax.swing.SwingConstants
+import com.jdblabs.timestamper.gui.TimelineDayDisplay
+import com.toedter.calendar.JDateChooser
+import net.miginfocom.swing.MigLayout
+
+Point mousePressRelativeToDialog
+Point offsetFromMainFrame = new Point(0,0)
+boolean coupledToMainFrame = false
+
+dateFormatter = new SimpleDateFormat("EEE MMM dd")
+
+mousePressed = { evt -> mousePressRelativeToDialog = evt?.point }
+
+mouseDragged = { evt ->
+ GUIUtil.componentDragged(dialog, evt, mousePressRelativeToDialog,
+ new Rectangle(Toolkit.defaultToolkit.screenSize),
+ model.mainMVC.view.frame.bounds)
+
+ offsetFromMainFrame = GUIUtil.calculateOffset(
+ model.mainMVC.view.frame, dialog)
+
+ coupledToMainFrame = GUIUtil.componentsCoupled(dialog,
+ model.mainMVC.view.frame);
+}
+
+dialog = dialog(new JDialog(model.mainMVC.view.frame),
+ title: 'Punchcard',
+ modal: false,
+ undecorated: true,
+ mousePressed: mousePressed,
+ mouseDragged: mouseDragged,
+ iconImage: imageIcon('/16-file-archive.png').image,
+ iconImages: [imageIcon('/16-file-archive.png').image],
+ minimumSize: [450, 500],
+ location: bind(source: model.mainMVC.model,
+ sourceProperty: 'absoluteLocation',
+ converter: { loc ->
+ if (coupledToMainFrame) {
+ Point p = new Point(offsetFromMainFrame)
+ p.translate((int) loc.x, (int) loc.y)
+ return p
+ } else return dialog.location })
+) {
+ logger.trace('Building PunchcardDialog GUI')
+ panel(
+ border:lineBorder(color: Color.BLACK, thickness:1, parent:true),
+ layout: new MigLayout('fill, insets 10 10 10 10',
+ '[grow]', '[grow]10[grow 0]')
+ ) {
+ dayDisplay = widget(constraints: 'grow, gp 200',
+ new TimelineDayDisplay(model.mainMVC.model.timeline),
+ timeline: bind(source: model.mainMVC.model,
+ sourceProperty: 'timeline'))
+
+ model.mainMVC.model.addPropertyChangeListener('currentMarker', {
+ dayDisplay.stateChanged(null) } as PropertyChangeListener)
+
+ panel(
+ border: lineBorder(color: Color.BLACK, thickness: 1),
+ constraints: 'growx, growy 0, newline, bottom',
+ layout: new MigLayout('fill, insets 0 10 0 10',
+ '[grow]10[grow 0]', '[][][grow 0]')
+ ) {
+ dateChooser = widget(new JDateChooser(),
+ constraints: 'growx, gaptop 10',
+ dateFormatString: 'MMM d, yyyy HH:mm',
+ date: bind(source: dayDisplay,
+ sourceProperty: 'selectedTimelineMarker',
+ converter: { it?.timestamp }))
+
+ panel(constraints: 'spany 3, wrap, al trailing',
+ layout: new MigLayout('insets 0', '[]0[]0[]0[]0[]',
+ '[]0[]0[]0[]0[]')
+ ) {
+ label(background: [255, 255, 153],
+ constraints: 'growx, spanx 5, wrap',
+ horizontalAlignment: SwingConstants.CENTER,
+ opaque: true,
+ text: bind(source: dayDisplay,
+ sourceProperty: 'rangeStart',
+ converter: { dateFormatter.format(it) }))
+
+ Calendar calendar = Calendar.getInstance()
+ button(icon: imageIcon('/previous-week.png'),
+ border: emptyBorder(4),
+ actionPerformed: {
+ calendar.time = dayDisplay.rangeStart
+ calendar.add(Calendar.WEEK_OF_YEAR, -1)
+ dayDisplay.day = calendar.time })
+ button(icon: imageIcon('/previous-day.png'),
+ border: emptyBorder(4),
+ actionPerformed: {
+ calendar.time = dayDisplay.rangeStart
+ calendar.add(Calendar.DAY_OF_YEAR, -1)
+ dayDisplay.day = calendar.time })
+ button(text: 'Today',
+ border: emptyBorder(4),
+ actionPerformed: {
+ calendar = Calendar.getInstance()
+ dayDisplay.day = calendar.time })
+ button(icon: imageIcon('/next-day.png'),
+ border: emptyBorder(4),
+ actionPerformed: {
+ calendar.time = dayDisplay.rangeStart
+ calendar.add(Calendar.DAY_OF_YEAR, 1)
+ dayDisplay.day = calendar.time })
+ button(icon: imageIcon('/next-week.png'),
+ constraints: 'wrap',
+ border: emptyBorder(4),
+ actionPerformed: {
+ calendar.time = dayDisplay.rangeStart
+ calendar.add(Calendar.WEEK_OF_YEAR, 1)
+ dayDisplay.day = calendar.time })
+
+ button(text: 'New Marker', icon: imageIcon('/new-marker.png'),
+ constraints: 'growx, spanx 5, wrap',
+ horizontalAlignment: SwingConstants.CENTER,
+ actionPerformed: {
+ model.mainMVC.controller.newTask(markTextField.text,
+ notesTextField.text, dateChooser.date) })
+ button(text: 'Delete Marker',
+ icon: imageIcon('/delete-marker.png'),
+ constraints: 'growx, spanx 5, wrap',
+ horizontalAlignment: SwingConstants.CENTER,
+ actionPerformed: {
+ model.mainMVC.controller.deleteTask(
+ dayDisplay.selectedTimelineMarker) })
+ button(text: 'Apply Changes',
+ icon: imageIcon('/document-save-16x16.png'),
+ constraints: 'growx, spanx 5',
+ horizontalAlignment: SwingConstants.CENTER,
+ actionPerformed: {
+ Date d = dateChooser.date
+ String m = markTextField.text
+ String n = notesTextField.text
+ model.mainMVC.controller.deleteTask(
+ dayDisplay.selectedTimelineMarker)
+ model.mainMVC.controller.newTask(m, n, d) })
+ }
+
+ markTextField = textField(constraints: 'growx, wrap',
+ text: bind(source: dayDisplay,
+ sourceProperty: 'selectedTimelineMarker',
+ converter: { it?.mark }))
+ scrollPane(constraints: 'growx, gapbottom 10',
+ minimumSize: [0, 50]) {
+ notesTextField = textArea( wrapStyleWord: true, lineWrap: true,
+ text: bind(source: dayDisplay,
+ sourceProperty: 'selectedTimelineMarker',
+ converter: { it?.notes }))
+ }
+ }
+ }
+}
diff --git a/gui/griffon-app/views/com/jdblabs/timestamper/gui/TimeStamperMainView.groovy b/gui/griffon-app/views/com/jdblabs/timestamper/gui/TimeStamperMainView.groovy
new file mode 100644
index 0000000..df9f30c
--- /dev/null
+++ b/gui/griffon-app/views/com/jdblabs/timestamper/gui/TimeStamperMainView.groovy
@@ -0,0 +1,205 @@
+package com.jdblabs.timestamper.gui
+
+import java.awt.Color
+import java.awt.Font
+import java.awt.Point
+import java.awt.Rectangle
+import java.awt.Toolkit
+import java.awt.event.KeyEvent
+import javax.swing.BoxLayout
+import javax.swing.JDialog
+import javax.swing.JFileChooser
+import javax.swing.SwingConstants
+import javax.swing.Timer
+import com.jdblabs.timestamper.core.Timeline
+import net.miginfocom.swing.MigLayout
+
+/* ========== *
+ * GUI Events *
+ * ========== */
+
+taskTextFieldChanged = { evt = null ->
+ if (evt.keyCode == KeyEvent.VK_ENTER) {
+ taskTextField.font = taskBoldFont
+ controller.newTask(taskTextField.text)
+ }
+
+ else if (evt.keyCode == KeyEvent.VK_ESCAPE) {
+ taskTextField.font = taskBoldFont
+ taskTextField.text = model.currentMarker.mark
+ }
+
+ else if (!evt.isActionKey())
+ taskTextField.font = taskThinFont
+}
+
+showNotes = { evt = null ->
+ model.notesDialogMVC.view.dialog.visible = notesVisibleButton.selected
+}
+
+showPunchcard = { evt = null ->
+ model.punchcardDialogMVC.view.dialog.visible = punchcardVisibleButton.selected
+}
+
+mousePressed = { evt = null ->
+ mousePressRelativeToFrame = evt?.point
+}
+
+mouseDragged = { evt = null ->
+ GUIUtil.componentDragged(frame, evt, mousePressRelativeToFrame,
+ new Rectangle(Toolkit.defaultToolkit.screenSize))
+}
+
+/* ============== *
+ * GUI Definition *
+ * ============== */
+
+updateTimer = new Timer(1000, action(name: 'GUI Refresh', closure: {
+ Date currentTime = new Date()
+ currentTimeLabel.text = Timeline.shortFormat.format(currentTime)
+ if (model.currentMarker != null) {
+ long seconds = currentTime.time - model.currentMarker.timestamp.time
+ seconds /= 1000
+ long minutes = seconds / 60
+ seconds = seconds % 60
+ long hours = minutes / 60
+ minutes %= 60
+ long days = hours / 24
+ hours %= 24
+
+ StringBuilder sb = new StringBuilder()
+ if (days > 0) sb.append(days + "day ")
+ if (hours > 0) sb.append(hours + "hr ")
+ if (minutes > 0) sb.append(minutes + "min ")
+ sb.append(seconds + "sec")
+
+ totalTimeLabel.text = sb.toString()
+ } else totalTimeLabel.text = ""
+}))
+
+updateTimer.start()
+
+optionsMenu = popupMenu() {
+ menuItem(icon: imageIcon('/document-save-16x16.png'), text: 'Save Timeline',
+ actionPerformed: { model.timelineProperties.save() })
+ menuItem(icon: imageIcon('/document-save-as-16x16.png'),
+ text: 'Save a new copy...', actionPerformed: controller.&saveas)
+ menuItem(icon: imageIcon('/document-open-16x16.png'),
+ text: 'Load Timeline...', actionPerformed: {
+ if (fileDialog.showOpenDialog(frame) ==
+ JFileChooser.APPROVE_OPTION)
+ controller.load(fileDialog.selectedFile) })
+ checkBoxMenuItem(text: 'Save on update?',
+ selected: bind(source: model, sourceProperty: 'timelineProperties',
+ sourceValue: { model.timelineProperties?.persistOnUpdate }),
+ actionPerformed: {
+ model.timelineProperties.persistOnUpdate = it.source.selected })
+ aboutMenuItem = checkBoxMenuItem(text: 'About...',
+ actionPerformed: { aboutDialog.visible = aboutMenuItem.selected })
+}
+
+fileDialog = fileChooser();
+
+frame = application(title:'TimeStamper',
+ location:[50,50],
+ locationByPlatform:true,
+ minimumSize: [325, 0],
+ pack:true,
+ undecorated:true,
+ iconImage: imageIcon('/appointment-new-32x32.png').image,
+ iconImages: [imageIcon('/appointment-new-32x32.png').image,
+ imageIcon('/appointment-new-16x16.png').image],
+ componentMoved: { evt -> model.absoluteLocation = frame.location }
+) {
+ logger.trace('Building TimeStamperMain GUI')
+ panel(
+ border:lineBorder(color:Color.BLACK, thickness:1, parent:true),
+ layout: new MigLayout('insets 0 5 0 0, fill','', '[]0[]0[]'),
+ mousePressed: mousePressed,
+ mouseDragged: mouseDragged
+ ) {
+ def mainFont = new Font(Font.SANS_SERIF, Font.BOLD, 12)
+ def timeFont = new Font(Font.SANS_SERIF, Font.BOLD, 14)
+ label("Current task started at ", font: mainFont)
+ label(constraints: 'align leading', font: timeFont,
+ foreground: [0, 102, 102],
+ text: bind(source: model, sourceProperty: 'currentMarker',
+ sourceValue: {
+ model.currentMarker == null ? "00:00:00" :
+ Timeline.shortFormat.format(model.currentMarker.timestamp)
+ }))
+
+ panel(constraints: 'alignx trailing, aligny top, wrap') {
+ boxLayout(axis: BoxLayout.X_AXIS)
+ button(mouseClicked: { evt ->
+ optionsMenu.show(evt.component, evt.x, evt.y) },
+ icon: imageIcon('/16-tool-a.png'),
+ rolloverIcon: imageIcon('/16-tool-a-hover.png'),
+ border: emptyBorder(0),
+ contentAreaFilled: false,
+ hideActionText: true,
+ toolTipText: 'Options Menu')
+ button(actionPerformed: controller.&exitGracefully,
+ icon: imageIcon('/16-em-cross.png'),
+ rolloverIcon: imageIcon('/16-em-cross-hover.png'),
+ border: emptyBorder(0),
+ contentAreaFilled: false,
+ hideActionText: true,
+ toolTipText: 'Close Application')
+ }
+
+ taskTextField = textField("Task name",
+ constraints: "growx, span 2, w 250::",
+ keyReleased: taskTextFieldChanged,
+ toolTipText: 'The current task',
+ text: bind(source: model, sourceProperty: 'currentMarker',
+ sourceValue: { model.currentMarker?.mark }))
+
+ taskThinFont = taskTextField.font
+ taskBoldFont = taskTextField.font.deriveFont(Font.BOLD)
+
+ panel(constraints: 'alignx leading, aligny top, gapright 5px, wrap') {
+ boxLayout(axis: BoxLayout.X_AXIS)
+ notesVisibleButton = toggleButton(
+ actionPerformed: showNotes,
+ icon: imageIcon('/16-em-pencil.png'),
+ hideActionText: true,
+ border: emptyBorder(4),
+ toolTipText: 'Show/hide task notes.')
+ punchcardVisibleButton = toggleButton(
+ actionPerformed: showPunchcard,
+ icon: imageIcon('/16-file-archive.png'),
+ hideActionText: true,
+ border: emptyBorder(4),
+ toolTipText: 'Show/hide the timeline display.')
+ }
+
+ totalTimeLabel = label("", constraints: 'alignx leading',
+ font: timeFont, foreground: [0, 153, 0])
+
+ currentTimeLabel = label("00:00:00", constraints: 'align trailing',
+ font: timeFont, foreground: [204, 0, 0])
+ }
+}
+
+aboutDialog = dialog(new JDialog(frame),
+ visible: false,
+ locationRelativeTo: null,
+ pack: true,
+ undecorated: true,
+ title: "About TimeStamper v" + app.metadata.'app.version'
+) {
+ panel(layout: new MigLayout('fill'),
+ border: lineBorder(color: Color.BLACK, thickness: 1)) {
+
+ label(font: new Font(Font.SANS_SERIF, Font.PLAIN, 18),
+ text: "TimeStamper", constraints: 'growx, wrap',
+ horizontalAlignment: SwingConstants.CENTER)
+ label(text: "version " + app.metadata.'app.version'
+ + " by Jonathan Bernard", constraints: 'growx, wrap',
+ horizontalAlignment: SwingConstants.CENTER)
+ textField(text: 'http://www.jdb-labs.com/timestamper',
+ constraints: 'growx', foreground: [0,0,200], editable: false,
+ horizontalAlignment: SwingConstants.CENTER)
+ }
+}
diff --git a/gui/griffonw b/gui/griffonw
new file mode 100644
index 0000000..703dc30
--- /dev/null
+++ b/gui/griffonw
@@ -0,0 +1,165 @@
+#!/bin/bash
+
+##############################################################################
+##
+## Griffon start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRIFFON_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Griffon"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/"
+APP_HOME="`pwd -P`"
+cd "$SAVED"
+
+CLASSPATH=$APP_HOME/wrapper/griffon-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add APP_NAME to the JAVA_OPTS as -Xdock:name
+if $darwin; then
+ JAVA_OPTS="$JAVA_OPTS -Xdock:name=$APP_NAME"
+# we may also want to set -Xdock:image
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRIFFON_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRIFFON_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRIFFON_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRIFFON_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GriffonWrapperMain "$@"
diff --git a/gui/griffonw.bat b/gui/griffonw.bat
new file mode 100644
index 0000000..16100b0
--- /dev/null
+++ b/gui/griffonw.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Griffon startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRIFFON_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\wrapper\griffon-wrapper.jar
+
+@rem Execute Griffon
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRIFFON_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GriffonWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRIFFON_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRIFFON_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/gui/keystore.jks b/gui/keystore.jks
new file mode 100644
index 0000000..642d21b
Binary files /dev/null and b/gui/keystore.jks differ
diff --git a/gui/lib/commons-beanutils-1.8.0.jar b/gui/lib/commons-beanutils-1.8.0.jar
new file mode 100644
index 0000000..caf7ae3
Binary files /dev/null and b/gui/lib/commons-beanutils-1.8.0.jar differ
diff --git a/gui/lib/commons-codec-1.4.jar b/gui/lib/commons-codec-1.4.jar
new file mode 100644
index 0000000..458d432
Binary files /dev/null and b/gui/lib/commons-codec-1.4.jar differ
diff --git a/gui/lib/commons-collections-3.2.1.jar b/gui/lib/commons-collections-3.2.1.jar
new file mode 100644
index 0000000..c35fa1f
Binary files /dev/null and b/gui/lib/commons-collections-3.2.1.jar differ
diff --git a/gui/lib/commons-lang-2.4.jar b/gui/lib/commons-lang-2.4.jar
new file mode 100644
index 0000000..532939e
Binary files /dev/null and b/gui/lib/commons-lang-2.4.jar differ
diff --git a/gui/lib/ezmorph-1.0.6.jar b/gui/lib/ezmorph-1.0.6.jar
new file mode 100644
index 0000000..30fad12
Binary files /dev/null and b/gui/lib/ezmorph-1.0.6.jar differ
diff --git a/gui/lib/http-builder-0.5.1.jar b/gui/lib/http-builder-0.5.1.jar
new file mode 100644
index 0000000..47aa035
Binary files /dev/null and b/gui/lib/http-builder-0.5.1.jar differ
diff --git a/gui/lib/httpclient-4.1.1.jar b/gui/lib/httpclient-4.1.1.jar
new file mode 100644
index 0000000..c845ef9
Binary files /dev/null and b/gui/lib/httpclient-4.1.1.jar differ
diff --git a/gui/lib/httpclient-cache-4.1.1.jar b/gui/lib/httpclient-cache-4.1.1.jar
new file mode 100644
index 0000000..3e1da08
Binary files /dev/null and b/gui/lib/httpclient-cache-4.1.1.jar differ
diff --git a/gui/lib/httpcore-4.1.jar b/gui/lib/httpcore-4.1.jar
new file mode 100644
index 0000000..a357c07
Binary files /dev/null and b/gui/lib/httpcore-4.1.jar differ
diff --git a/gui/lib/httpmime-4.1.1.jar b/gui/lib/httpmime-4.1.1.jar
new file mode 100644
index 0000000..01af40b
Binary files /dev/null and b/gui/lib/httpmime-4.1.1.jar differ
diff --git a/gui/lib/jcalendar-1.3.2.jar b/gui/lib/jcalendar-1.3.2.jar
new file mode 100755
index 0000000..b5e5a2d
Binary files /dev/null and b/gui/lib/jcalendar-1.3.2.jar differ
diff --git a/gui/lib/jcl-over-slf4j-1.6.1.jar b/gui/lib/jcl-over-slf4j-1.6.1.jar
new file mode 100644
index 0000000..79e1ec2
Binary files /dev/null and b/gui/lib/jcl-over-slf4j-1.6.1.jar differ
diff --git a/gui/lib/jdb-util-1.1.jar b/gui/lib/jdb-util-1.1.jar
new file mode 100644
index 0000000..94962af
Binary files /dev/null and b/gui/lib/jdb-util-1.1.jar differ
diff --git a/gui/lib/json-lib-2.3-jdk15.jar b/gui/lib/json-lib-2.3-jdk15.jar
new file mode 100644
index 0000000..29d59b9
Binary files /dev/null and b/gui/lib/json-lib-2.3-jdk15.jar differ
diff --git a/gui/lib/logback-classic-0.9.26.jar b/gui/lib/logback-classic-0.9.26.jar
new file mode 100644
index 0000000..b900b64
Binary files /dev/null and b/gui/lib/logback-classic-0.9.26.jar differ
diff --git a/gui/lib/logback-core-0.9.26.jar b/gui/lib/logback-core-0.9.26.jar
new file mode 100644
index 0000000..d50f3cd
Binary files /dev/null and b/gui/lib/logback-core-0.9.26.jar differ
diff --git a/gui/lib/miglayout-3.7.1-swing.jar b/gui/lib/miglayout-3.7.1-swing.jar
new file mode 100644
index 0000000..5a762c8
Binary files /dev/null and b/gui/lib/miglayout-3.7.1-swing.jar differ
diff --git a/gui/lib/nekohtml-1.9.9.jar b/gui/lib/nekohtml-1.9.9.jar
new file mode 100644
index 0000000..2e06271
Binary files /dev/null and b/gui/lib/nekohtml-1.9.9.jar differ
diff --git a/gui/lib/signpost-commonshttp4-1.2.1.1.jar b/gui/lib/signpost-commonshttp4-1.2.1.1.jar
new file mode 100644
index 0000000..e8dae4d
Binary files /dev/null and b/gui/lib/signpost-commonshttp4-1.2.1.1.jar differ
diff --git a/gui/lib/signpost-core-1.2.1.1.jar b/gui/lib/signpost-core-1.2.1.1.jar
new file mode 100644
index 0000000..86e9dac
Binary files /dev/null and b/gui/lib/signpost-core-1.2.1.1.jar differ
diff --git a/gui/lib/slf4j-api-1.6.1.jar b/gui/lib/slf4j-api-1.6.1.jar
new file mode 100644
index 0000000..42e0ad0
Binary files /dev/null and b/gui/lib/slf4j-api-1.6.1.jar differ
diff --git a/gui/lib/smack.jar b/gui/lib/smack.jar
new file mode 100644
index 0000000..73d676a
Binary files /dev/null and b/gui/lib/smack.jar differ
diff --git a/gui/lib/smackx-jingle.jar b/gui/lib/smackx-jingle.jar
new file mode 100644
index 0000000..ae044e4
Binary files /dev/null and b/gui/lib/smackx-jingle.jar differ
diff --git a/gui/lib/smackx.jar b/gui/lib/smackx.jar
new file mode 100644
index 0000000..ad73bed
Binary files /dev/null and b/gui/lib/smackx.jar differ
diff --git a/gui/lib/timestamper-lib-1.5.jar b/gui/lib/timestamper-lib-1.5.jar
new file mode 100644
index 0000000..ae4810d
Binary files /dev/null and b/gui/lib/timestamper-lib-1.5.jar differ
diff --git a/gui/lib/xercesImpl-2.8.1.jar b/gui/lib/xercesImpl-2.8.1.jar
new file mode 100644
index 0000000..3b351f6
Binary files /dev/null and b/gui/lib/xercesImpl-2.8.1.jar differ
diff --git a/gui/lib/xml-apis-1.3.04.jar b/gui/lib/xml-apis-1.3.04.jar
new file mode 100644
index 0000000..d42c0ea
Binary files /dev/null and b/gui/lib/xml-apis-1.3.04.jar differ
diff --git a/gui/lib/xml-resolver-1.2.jar b/gui/lib/xml-resolver-1.2.jar
new file mode 100644
index 0000000..e535bdc
Binary files /dev/null and b/gui/lib/xml-resolver-1.2.jar differ
diff --git a/gui/src/main/com/jdblabs/timestamper/gui/GUIUtil.groovy b/gui/src/main/com/jdblabs/timestamper/gui/GUIUtil.groovy
new file mode 100644
index 0000000..7d9710c
--- /dev/null
+++ b/gui/src/main/com/jdblabs/timestamper/gui/GUIUtil.groovy
@@ -0,0 +1,132 @@
+package com.jdblabs.timestamper.gui
+
+import java.awt.Point
+import java.awt.Rectangle
+import java.awt.Toolkit
+import java.awt.event.MouseEvent
+
+public class GUIUtil {
+
+ static Point calculateWindowMovement(Point currentMousePoint,
+ Point mousePressRelativeToWindow, Rectangle windowBounds,
+ Rectangle... snapBoxes) {
+
+ // this is the point we will compute as the new upper left
+ // coordinate of the frame. It needs to be translated some to account
+ // for the fact that the user can press the mouse anywhere on the
+ // main panel.
+ Point currentWindowPoint = (Point) currentMousePoint.clone();
+ // find out where the new point should be, ignoreing screen bounds
+ currentWindowPoint.translate((int) -mousePressRelativeToWindow.x,
+ (int) -mousePressRelativeToWindow.y);
+
+ Point finalWindowPoint = (Point) currentWindowPoint.clone();
+
+ // snap tests. In the event of a conflict in snaps positions, the
+ // last snap wins
+ int wUp = windowBounds.y;
+ int wDown = windowBounds.y + windowBounds.height;
+ int wLeft = windowBounds.x;
+ int wRight = windowBounds.x + windowBounds.width;
+
+// currentTaskLabel.setText("U:" + wUp + " D:" + wDown + " L:" + wLeft
+// + " R:" + wRight);
+
+ for (Rectangle r : snapBoxes) {
+ int rUp = r.y;
+ int rDown = r.y + r.height;
+ int rLeft = r.x;
+ int rRight = r.x + r.width;
+
+ // check to see if the window is near any of the snap boundaries
+ // if it is, 'snap' the final point to that boundary.
+ if (Math.abs(rUp - wUp) < 20) // top of window to top of rect
+ finalWindowPoint.y = rUp;
+ else if (Math.abs(rUp - wDown) < 20) // bot of w to top of r
+ finalWindowPoint.y = rUp - windowBounds.height;
+ else if (Math.abs(rDown - wUp) < 20) // top of w to bot of r
+ finalWindowPoint.y = rDown;
+ else if (Math.abs(rDown - wDown) < 20) // bot of w to bot of r
+ finalWindowPoint.y = rDown - windowBounds.height;
+
+ if (Math.abs(rLeft - wLeft) < 20) // left of w to left of r
+ finalWindowPoint.x = rLeft;
+ else if (Math.abs(rLeft - wRight) < 20) // right of w to left of r
+ finalWindowPoint.x = rLeft - windowBounds.width;
+ else if (Math.abs(rRight - wLeft) < 20) // left of w to right of r
+ finalWindowPoint.x = rRight;
+ else if (Math.abs(rRight - wRight) < 20) // right of w to right of r
+ finalWindowPoint.x = rRight - windowBounds.width;
+ }
+
+ // this point represents a backwards version of the initial calculation
+ // to find the finalPoint. It says, based on the current final point,
+ // assume I moved the frame to that point. Where, relative to that point
+ // should the mouse be, based on how far it was relative to the point
+ // when the user pressed it.
+ Point whereMouseShouldBe = (Point) finalWindowPoint.clone();
+ whereMouseShouldBe.translate((int) mousePressRelativeToWindow.x,
+ (int) mousePressRelativeToWindow.y);
+
+ // if the actual mouse location is different from the expected location,
+ // then we know that the snapping behaviour has altered the frames new
+ // placement. If this alteration is too large (30 px apart in this case)
+ // then we know the user is trying to pull the frame out of its snapped
+ // position. In this case, we want to ignore the snap calculations and
+ // base the new frame location entirely on the current mouse location
+ if (whereMouseShouldBe.distance(currentMousePoint) > 30) {
+ finalWindowPoint = (Point) currentMousePoint.clone();
+ finalWindowPoint.translate((int) -mousePressRelativeToWindow.x,
+ (int) -mousePressRelativeToWindow.y);
+ }
+
+ return finalWindowPoint;
+ }
+
+ static void componentDragged(frame, MouseEvent evt,
+ Point mousePressRelativeToFrame, Rectangle... snapBoxes) {
+ frame.location = calculateWindowMovement(evt.getLocationOnScreen(),
+ mousePressRelativeToFrame, frame.bounds, snapBoxes)
+ }
+
+ static boolean componentsCoupled(c1, c2) {
+ def h1 = c1.bounds.height
+ def w1 = c1.bounds.width
+ def h2 = c2.bounds.height
+ def w2 = c2.bounds.width
+
+ return (
+ ( // horizontal
+ ( // snapped horizontally
+ ((c1.x - c2.x).abs() < 20) || // c1 left edge to c2 left edge
+ ((c1.x + w1 - c2.x - w2).abs() < 20) || // c1 right edge to c2 right edge
+ ((c1.x + w1 - c2.x).abs() < 20) || // c1 right edge to c2 left edge
+ ((c2.x + w2 - c1.x).abs() < 20) // c1 left edge to c2 right edge
+ ) && (// touching vertically
+ (c1.y <= c2.y && c1.y + h1 >= c2.y) ||
+ (c1.y <= c2.y + h2 && c1.y + h1 >= c2.y + h2) ||
+ (c1.y > c2.y && c1.y + h1 < c2.y + h2)
+ )
+ ) || ( // vertical
+ ( // snapped vertically
+ ((c1.y - c2.y).abs() < 20) || // c1 top to c2 top
+ ((c1.y + h1 - c2.y - h2).abs() < 20) || // c1 bot to c2 bot
+ ((c1.y + h1 - c2.y).abs() < 20) || // c1 bot to c2 top
+ ((c2.y + h2 - c1.y).abs() < 20) // c1 top to c2 bot
+ ) && (// touching horizontally
+ (c1.x <= c2.x && c1.x + w1 >= c2.x) ||
+ (c1.x <= c2.x + w2 && c1.x + w1 >= c2.x + w2) ||
+ (c1.x > c2.x && c1.x + w1 < c2.x + w2)
+ )
+ )
+ )
+ }
+
+ static Point calculateOffset(c1, c2) {
+ Point p = c1.location
+ Rectangle r = c1.bounds
+ Point offset = new Point(c2.location)
+ offset.translate((int) -p.x, (int) -p.y)
+ return offset
+ }
+}
diff --git a/gui/src/main/com/jdblabs/timestamper/gui/TimelineDayDisplay.java b/gui/src/main/com/jdblabs/timestamper/gui/TimelineDayDisplay.java
new file mode 100644
index 0000000..031d2e4
--- /dev/null
+++ b/gui/src/main/com/jdblabs/timestamper/gui/TimelineDayDisplay.java
@@ -0,0 +1,714 @@
+/* TimelineDayDisplay.java */
+
+package com.jdblabs.timestamper.gui;
+
+import com.jdblabs.timestamper.core.TimelineMarker;
+import com.jdblabs.timestamper.core.Timeline;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.geom.Rectangle2D;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Iterator;
+import javax.swing.JComponent;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * @author Jonathan Bernard (jdbernard@gmail.com)
+ */
+public class TimelineDayDisplay extends JComponent implements MouseListener,
+ ChangeListener {
+
+ private ArrayList markerEntries;
+ private ArrayList timeLegendLocations;
+ private ArrayList changeListeners = new ArrayList();
+
+ private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+
+ private Timeline timeline;
+ private TimelineMarker currentMarker;
+
+ private Point lastMousePress;
+
+ private Font markFont;// = getFont().deriveFont(Font.BOLD);
+ private Font notesFont;// = getFont();
+
+ private Color evenTrans = new Color(0.75f, 0.75f, 0.75f, 0.4f);
+ private Color evenOpaque = new Color(0.75f, 0.75f, 0.75f, 1f);
+ private Color oddTrans = new Color(0.5f, 0.5f, 0.5f, 0.4f);
+ private Color oddOpaque = new Color(0.5f, 0.5f, 0.5f, 1f);
+ private Color selectedTrans = new Color(0.5f, 0.75f, 0.5f, 0.4f);
+ private Color selectedOpaque = new Color(0.5f, 0.75f, 0.5f, 1f);
+ private Color fontColor = new Color(0.1f, 0.1f, 0.1f, 1f);
+
+ private Date rangeStartDate = new Date();
+ private Date rangeEndDate = new Date();
+
+ private class MarkerDisplayEntry {
+ public TimelineMarker marker;
+ public float relY;
+ public float relHeight;
+ public Rectangle2D markBounds;
+ public Rectangle2D notesBounds;
+ public Rectangle bounds;
+ }
+
+ private class TimeLegendEntry {
+ public double relY;
+ public String label;
+ }
+
+ private enum TimeDelta {
+ Hourly (Calendar.HOUR_OF_DAY, 1) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:00",
+ c.get(Calendar.HOUR_OF_DAY));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l * 60l * 30l) / millisec < 25);
+ }
+ },
+ ThirtyMin (Calendar.MINUTE, 30) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l * 60l * 15l) / millisec < 25);
+ }
+ },
+ FifteenMin (Calendar.MINUTE, 15) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l * 60l * 10l) / millisec < 25);
+ }
+ },
+ TenMin (Calendar.MINUTE, 10) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l * 60l * 5l) / millisec < 25);
+ }
+ },
+ FiveMin (Calendar.MINUTE, 5) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l * 60l) / millisec < 25);
+ }
+ },
+ Minute (Calendar.MINUTE, 1) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l * 30l) / millisec < 25);
+ }
+ },
+ ThirtySec (Calendar.SECOND, 30) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.MINUTE), c.get(Calendar.SECOND));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l * 15l) / millisec < 25);
+ }
+ },
+ FifteenSec (Calendar.SECOND, 15) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.MINUTE), c.get(Calendar.SECOND));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l * 10l) / millisec < 25);
+ }
+ },
+ TenSec (Calendar.SECOND, 10) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.MINUTE), c.get(Calendar.SECOND));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l * 5l) / millisec < 25);
+ }
+ },
+ FiveSec (Calendar.SECOND, 5) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.MINUTE), c.get(Calendar.SECOND));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1000l) / millisec < 25);
+ }
+ },
+ Second (Calendar.SECOND, 1) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$02d",
+ c.get(Calendar.MINUTE), c.get(Calendar.SECOND));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return ((height * 1500l) / millisec < 25);
+ }
+ },
+ SubSecond (Calendar.MILLISECOND, 100) {
+ public String formatCalendar(Calendar c) {
+ return String.format("%1$02d:%2$03d",
+ c.get(Calendar.SECOND), c.get(Calendar.MILLISECOND));
+ }
+
+ public boolean fitsInHeight(double height, double millisec) {
+ return true;
+ }
+ };
+
+ private int INTERVAL;
+ private int AMOUNT;
+
+ private TimeDelta(int interval, int amount) {
+ INTERVAL = interval;
+ AMOUNT = amount;
+ }
+
+ public Calendar addToCalendar(Calendar c) {
+ c.add(INTERVAL, AMOUNT);
+ return c;
+ }
+
+ public abstract boolean fitsInHeight(double height, double millisec);
+
+ public abstract String formatCalendar(Calendar c); { }
+ }
+
+
+ public TimelineDayDisplay() {
+ this(null, Calendar.getInstance());
+ }
+
+ public TimelineDayDisplay(Timeline t) {
+ this(t, Calendar.getInstance());
+ }
+
+ public TimelineDayDisplay(Timeline t, Calendar day) {
+ super();
+ setDay(day.getTime(), false);
+ addMouseListener(this);
+ this.timeline = t;
+ updateMarkers(getGraphics());
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener l) {
+ pcs.addPropertyChangeListener(l);
+ }
+
+ public void addPropertyChangeListener(String propertyName,
+ PropertyChangeListener l) {
+ pcs.addPropertyChangeListener(propertyName, l);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener l) {
+ pcs.removePropertyChangeListener(l);
+ }
+
+ public Timeline getTimeline() { return timeline; }
+
+ public void setTimeline(Timeline t) {
+ if (timeline == t) return;
+ pcs.firePropertyChange("timeline", timeline, timeline = t);
+ updateMarkers(getGraphics());
+ repaint();
+ }
+
+ /**
+ * Set the range for the visible timeline segment.
+ * @param start The beginning of the desired timeline segment.
+ * @param end The end of the desired timeline segment.
+ */
+ public void setDisplayInterval(Date start, Date end) {
+ if (start == rangeStartDate && end == rangeEndDate) return;
+ pcs.firePropertyChange("rangeStart", rangeStartDate, rangeStartDate = start);
+ pcs.firePropertyChange("rangeEnd", rangeEndDate, rangeEndDate = end);
+ }
+
+ /**
+ * Set the component to show the timeline segment for a specific day. The
+ * visible area will show the full 24-hour day.
+ * @param d The date of the day to display. The exact time of the variable
+ * can be any time in the desired day.
+ */
+ public void setDay(Date d) {
+ setDay(d, true);
+ }
+
+ /**
+ * There is the special case of instance initialization, where it is
+ * desirable to call setDay to handle the range start and end calculations
+ * but where we do not want to immediately update the gui, because it may
+ * not be fully initialized yet.
+ * @param d Day to set as the current day (component will show the range
+ * representing the day from start to finish.
+ * @param update If true
,
+ * updateMarkers(getGraphics)
is called after the range
+ * calculations are made.
+ */
+ private void setDay(Date d, boolean update) {
+ Calendar day = Calendar.getInstance();
+ day.setTime(d);
+ day.set(Calendar.HOUR_OF_DAY, 0);
+ day.set(Calendar.MINUTE, 0);
+ day.set(Calendar.SECOND, 0);
+ pcs.firePropertyChange("rangeStart", rangeStartDate, rangeStartDate = day.getTime());
+
+ day.add(Calendar.DAY_OF_YEAR, 1);
+ pcs.firePropertyChange("rangeEnd", rangeEndDate, rangeEndDate = day.getTime());
+
+ if (update) updateMarkers(getGraphics());
+ }
+
+ /**
+ * Get the starting point for the display range.
+ * @return The starting {@link java.util.Date} for the display range.
+ */
+ public Date getRangeStart() { return rangeStartDate; }
+
+ /**
+ * Get the ending point for the display range.
+ * @return The ending {@link java.util.Date} for the display range.
+ */
+ public Date getRangeEnd() { return rangeEndDate; }
+
+ /**
+ * Set the font used for displaying the name of the entry (more precisely:
+ * {@link com.jdblabs.timestamper.core.TimelineMarker#mark}).
+ * @param f The font to use.
+ */
+ public void setMarkFont(Font f) {
+ if (markFont == f) return;
+ pcs.firePropertyChange("markFont", markFont, markFont = f);
+ repaint();
+ }
+
+ public Font getMarkFont() { return markFont; }
+
+ public void setNotesFont(Font f) {
+ if (notesFont == f) return;
+ pcs.firePropertyChange("notesFont", notesFont, notesFont = f);
+ repaint();
+ }
+
+ public Font getNotesFont() { return notesFont; }
+
+ public void setFontColor(Color f) {
+ if (fontColor == f) return;
+ pcs.firePropertyChange("fontColor", fontColor,
+ fontColor = new Color(f.getRGB()));
+ repaint();
+ }
+
+ public Color getFontColor() { return fontColor; }
+
+ public void setEvenColor(Color c) {
+ if (evenOpaque == c) return;
+ evenTrans = new Color((float) c.getRed() / 255f,
+ (float) c.getGreen() / 255f,
+ (float) c.getBlue() / 255f, 0.4f);
+
+ pcs.firePropertyChange("evenColor", evenOpaque,
+ evenOpaque = new Color(c.getRGB()));
+ repaint();
+ }
+
+ public Color getEvenColor() {
+ return evenOpaque;
+ }
+
+ public void setOddColor(Color c) {
+ if (oddOpaque == c) return;
+ oddTrans = new Color((float) c.getRed() / 255f,
+ (float) c.getGreen() / 255f,
+ (float) c.getBlue() / 255f, 0.4f);
+
+ pcs.firePropertyChange("oddColor", oddOpaque,
+ oddOpaque = new Color(c.getRGB()));
+ repaint();
+ }
+
+ public Color getOddColor() {
+ return oddOpaque;
+ }
+
+ public void setSelectedColor(Color c) {
+ if (selectedOpaque == c) return;
+ selectedTrans = new Color((float) c.getRed() / 255f,
+ (float) c.getGreen() / 255f,
+ (float) c.getBlue() / 255f, 0.4f);
+ pcs.firePropertyChange("selectedColor", selectedOpaque,
+ selectedOpaque = new Color(c.getRGB()));
+ repaint();
+ }
+
+ public Color getSelectedColor() {
+ return selectedOpaque;
+ }
+
+ public TimelineMarker getSelectedTimelineMarker() {
+ return currentMarker;
+ }
+
+ public void updateSelectedMarker(String notes) {
+ currentMarker.setNotes(notes);
+ updateMarkers(getGraphics());
+ }
+
+ /**
+ * updateMarkers sets the internal list of TimelineMarkers, based on the
+ * currently visible timeline. The drawing of the display is split between
+ * this method, which constructs the data representation of what needs to
+ * be drawn, and the paintComponents method, which does the drawing. This is
+ * done to save computation, only recalculating markers when needed.
+ */
+ private void updateMarkers(Graphics g) {
+
+ if (timeline == null) return;
+
+ Insets insets = this.getInsets();
+ Rectangle bounds = this.getBounds();
+ Rectangle canvasBounds = new Rectangle(insets.left, insets.top,
+ bounds.width - insets.left - insets.right - 1,
+ bounds.height - insets.top - insets.bottom - 1);
+
+ Rectangle2D stringBounds = getFontMetrics(getFont()).getStringBounds("00:00 ", g);
+
+ long rangeDiff = rangeEndDate.getTime() - rangeStartDate.getTime();
+
+ markerEntries = new ArrayList();
+ timeLegendLocations = new ArrayList();
+
+ if (markFont == null) markFont = getFont().deriveFont(Font.BOLD);
+ if (notesFont == null) notesFont = getFont();
+
+ // calculate positions of all visible hour lines
+ // choose the increment of time to view
+ TimeDelta timeDelta = TimeDelta.Hourly;
+ if (rangeDiff == 0) rangeDiff = 1;
+
+ for (TimeDelta d : TimeDelta.values()) {
+ if (d.fitsInHeight(canvasBounds.getHeight(), rangeDiff)) {
+ timeDelta = d;
+ break;
+ }
+ }
+
+ Calendar timeCounter = Calendar.getInstance();
+ timeCounter.setTime(rangeStartDate);
+ timeCounter.set(Calendar.MINUTE, 0);
+ timeCounter.set(Calendar.SECOND, 0);
+
+ while (rangeStartDate.after(timeCounter.getTime()))
+ timeDelta.addToCalendar(timeCounter);
+
+ while (rangeEndDate.after(timeCounter.getTime())) {
+ TimeLegendEntry entry = new TimeLegendEntry();
+ entry.relY = ((double) (timeCounter.getTimeInMillis()
+ - rangeStartDate.getTime()) / (double) rangeDiff);
+ entry.label = timeDelta.formatCalendar(timeCounter);
+ timeLegendLocations.add(entry);
+ timeDelta.addToCalendar(timeCounter);
+ }
+
+ // get all relevant markers starting from the marker just before the
+ // visible start of the display
+ TimelineMarker tm = timeline.getLastMarker(rangeStartDate);
+
+ // If there is no previous marker
+ if (tm == null)
+ // try to get the first marker
+ try { tm = timeline.iterator().next(); }
+ // and if there aren't any markers at all, just return, the array is
+ // empty so the display will be empty
+ catch (Exception e) { return; }
+
+ // Now we want to step through the timeline, capturing all markers
+ // between the visible ranges.
+ Iterator itr = timeline.iterator();
+
+ while (!itr.next().equals(tm));
+
+ ArrayList markers = new ArrayList();
+ while (rangeEndDate.after(tm.getTimestamp())) {
+ markers.add(tm);
+ if (itr.hasNext()) tm = itr.next();
+ else break;
+ }
+
+ markers.add(tm);
+
+ for (int i = 0; i < markers.size() - 1; i++) {
+ MarkerDisplayEntry markerEntry = new MarkerDisplayEntry();
+
+ markerEntry.marker = markers.get(i);
+
+ // set string bounds
+ markerEntry.markBounds = getFontMetrics(markFont)
+ .getStringBounds(markers.get(i).getMark(), g);
+ markerEntry.notesBounds = getFontMetrics(notesFont)
+ .getStringBounds(markers.get(i).getNotes(), g);
+
+ // calculate upper bound
+ if ((i == 0) && rangeStartDate.after(markerEntry.marker.getTimestamp())) {
+ //if this is the first marker (before the start time) set the
+ // Y coor to 0, top of display
+ markerEntry.relY = 0;
+ } else {
+ // otherwise, calculate how far down (%-wise) the mark is
+ markerEntry.relY = (float) (((double) (markerEntry.marker.getTimestamp().getTime()
+ - rangeStartDate.getTime())) / (double) rangeDiff);
+ }
+
+ // calculate lower bound
+ if ((i == 0) && rangeStartDate.after(markerEntry.marker.getTimestamp()))
+ // if this is the first marker (before the start time), set the
+ // height to equal the top of the next marker
+ markerEntry.relHeight =
+ markers.get(i + 1).getTimestamp().getTime()
+ - rangeStartDate.getTime();
+ else if (i == markers.size() - 2)
+ // if this is the last visible marker, set the height to extend
+ // to the bottom of the display
+ markerEntry.relHeight = rangeEndDate.getTime()
+ - markerEntry.marker.getTimestamp().getTime();
+ else
+ // set the height to the difference between this marker and the
+ // next.
+ markerEntry.relHeight =
+ markers.get(i + 1).getTimestamp().getTime()
+ - markerEntry.marker.getTimestamp().getTime();
+ markerEntry.relHeight /= rangeDiff;
+
+ markerEntries.add(markerEntry);
+ }
+ repaint();
+ }
+
+ @Override
+ public void paintComponent(Graphics g) {
+ removeAll();
+
+ if (timeline == null) return;
+
+ if (markerEntries == null) updateMarkers(g);
+
+ Insets insets = this.getInsets();
+ Rectangle bounds = this.getBounds();
+ Rectangle canvasBounds = new Rectangle(insets.left, insets.top,
+ bounds.width - insets.left - insets.right - 1,
+ bounds.height - insets.top - insets.bottom - 1);
+ double hourHeight = canvasBounds.getHeight() / 24.0;
+
+ Graphics2D g2d = (Graphics2D) g;
+ Rectangle2D stringBounds = getFontMetrics(getFont()).getStringBounds("00:00 ", g);
+
+ // draw hour lines
+ for (TimeLegendEntry legendEntry : timeLegendLocations) {
+ g.drawLine(canvasBounds.x + (int) stringBounds.getWidth(),
+ (int) (canvasBounds.y + (canvasBounds.height * legendEntry.relY)),
+ canvasBounds.x + canvasBounds.width,
+ (int) (canvasBounds.y + (canvasBounds.height * legendEntry.relY)));
+
+ g.drawString(legendEntry.label, canvasBounds.x + 2,
+ (int) (canvasBounds.y + (canvasBounds.height * legendEntry.relY)
+ + (stringBounds.getHeight() / 2)));
+ }
+
+ for (int i = 0; i < markerEntries.size(); i++) {
+
+ MarkerDisplayEntry curEntry = markerEntries.get(i);
+
+ Rectangle2D markBounds;
+ Rectangle2D notesBounds;
+
+ boolean selected = curEntry.marker.equals(currentMarker);
+
+ // if i == 0, this is the default
+ curEntry.bounds = new Rectangle();
+ curEntry.bounds.y = 3;
+ curEntry.bounds.x = canvasBounds.x + (int) stringBounds.getWidth() + 5;
+ curEntry.bounds.height = 1;
+ curEntry.bounds.width = canvasBounds.width - (int) stringBounds.getWidth() - 8;
+
+ double relTime;
+
+ // calculate upper bound
+ curEntry.bounds.y = (int) Math.round(curEntry.relY
+ * canvasBounds.getHeight());
+
+ if (i == 0) curEntry.bounds.y += 3;
+
+ // calculate lower bound
+ curEntry.bounds.height = (int) Math.round(curEntry.relHeight
+ * canvasBounds.getHeight());
+
+ if (i ==0) curEntry.bounds.height -= 6;
+ else curEntry.bounds.height -= 3;
+
+ // draw box
+ if (selected) g.setColor(selectedTrans);
+ else g.setColor((i % 2 == 0 ? evenTrans : oddTrans));
+ g.fillRect(curEntry.bounds.x, curEntry.bounds.y, curEntry.bounds.width, curEntry.bounds.height);
+
+ if (selected) g.setColor(selectedOpaque);
+ else g2d.setColor((i % 2 == 0 ? evenOpaque : oddOpaque));
+ g2d.setStroke(new BasicStroke(3f));
+ g2d.drawRect(curEntry.bounds.x, curEntry.bounds.y, curEntry.bounds.width, curEntry.bounds.height);
+
+ // draw timestamp name
+ markBounds = (Rectangle2D) curEntry.markBounds.clone();
+ markBounds.setRect(curEntry.bounds.x + 3,
+ curEntry.bounds.y + stringBounds.getHeight(),
+ markBounds.getWidth(), markBounds.getHeight());
+
+ g.setColor(fontColor);
+ g.setFont(markFont);
+ g.drawString(curEntry.marker.getMark(),
+ (int) markBounds.getX(), (int) markBounds.getY());
+
+ // draw notes
+ notesBounds = (Rectangle2D) curEntry.notesBounds.clone();
+ notesBounds.setRect(curEntry.bounds.x + 6,
+ curEntry.bounds.y + stringBounds.getHeight() + markBounds.getHeight(),
+ notesBounds.getWidth(), notesBounds.getHeight());
+
+ if (curEntry.bounds.contains(notesBounds)) {
+ g.setFont(notesFont);
+ g.drawString(curEntry.marker.getNotes(),
+ (int) notesBounds.getX(), (int) notesBounds.getY());
+ }
+ }
+
+ g.setColor(Color.BLACK);
+ g.drawRect(canvasBounds.x, canvasBounds.y, canvasBounds.width, canvasBounds.height);
+ }
+
+ public void addChangeListener(ChangeListener cl) {
+ changeListeners.add(cl);
+ }
+
+ public boolean removeChangeListener(ChangeListener cl) {
+ return changeListeners.remove(cl);
+ }
+
+ private void fireChangeEvent() {
+ for (ChangeListener cl : changeListeners)
+ cl.stateChanged(new ChangeEvent(this));
+ }
+
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ Point topLeft = getLocationOnScreen();
+ currentMarker = null;
+ for (MarkerDisplayEntry markerEntry : markerEntries) {
+ Rectangle absBounds = new Rectangle(markerEntry.bounds);
+ absBounds.translate(topLeft.x, topLeft.y);
+
+ // should only match one entry
+ if (absBounds.contains(e.getLocationOnScreen())) {
+ pcs.firePropertyChange("selectedTimelineMarker",
+ currentMarker, currentMarker = markerEntry.marker);
+ break;
+ }
+ }
+ repaint();
+ fireChangeEvent();
+ } else if (e.getButton() == MouseEvent.BUTTON3) {
+ setDay(rangeStartDate);
+ }
+ }
+
+ public void mousePressed(MouseEvent e) {
+ lastMousePress = e.getPoint();
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ Insets insets = this.getInsets();
+ Rectangle bounds = this.getBounds();
+ Rectangle canvasBounds = new Rectangle(insets.left, insets.top,
+ bounds.width - insets.left - insets.right - 1,
+ bounds.height - insets.top - insets.bottom - 1);
+
+ double rangeDiff = rangeEndDate.getTime() - rangeStartDate.getTime();
+ double y1 = lastMousePress.getY();
+ double y2 = e.getY();
+
+ if (Math.abs(y2 - y1) < 5) return;
+
+ // get time for y1
+ long time1 = (long) Math.round((((y1 - canvasBounds.y)
+ / canvasBounds.height) * rangeDiff) + rangeStartDate.getTime());
+ long time2 = (long) Math.round((((y2 - canvasBounds.y)
+ / canvasBounds.height) * rangeDiff) + rangeStartDate.getTime());
+
+ // left click, scroll
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ long difference = time1 - time2;
+ rangeStartDate.setTime(rangeStartDate.getTime() + difference);
+ rangeEndDate.setTime(rangeEndDate.getTime() + difference);
+ }
+ // right click, zoom
+ else if (e.getButton() == MouseEvent.BUTTON3) {
+ if (time1 < time2) {
+ rangeStartDate.setTime(time1);
+ rangeEndDate.setTime(time2);
+ } else {
+ rangeStartDate.setTime(time2);
+ rangeEndDate.setTime(time1);
+ }
+ }
+ updateMarkers(getGraphics());
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ public void mouseExited(MouseEvent e) {
+ }
+
+ public void stateChanged(ChangeEvent ce) {
+ updateMarkers(getGraphics());
+ repaint();
+ }
+
+ public void setStateChanged(Object o) {
+ stateChanged(null);
+ }
+
+}
diff --git a/gui/src/main/com/jdblabs/timestamper/gui/plugin/HookLogger.groovy b/gui/src/main/com/jdblabs/timestamper/gui/plugin/HookLogger.groovy
new file mode 100644
index 0000000..1a02509
--- /dev/null
+++ b/gui/src/main/com/jdblabs/timestamper/gui/plugin/HookLogger.groovy
@@ -0,0 +1,30 @@
+package com.jdblabs.timestamper.gui.plugin
+
+import com.jdblabs.timestamper.core.TimelineMarker
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+public class HookLogger implements TimestamperPlugin {
+
+ private Logger logger = LoggerFactory.getLogger(getClass())
+
+ public void onStartup(Map mvc) {
+ logger.info("HookLogger: onStartup() called.")
+ }
+
+ public void onExit(Map mvc) {
+ logger.info("HookLogger: onExit() called.")
+ }
+
+ public File onTimelineLoad(Map mvc, File file) {
+ logger.info("HookLogger: onTimelineLoad() called with ${file.canonicalPath}")
+ }
+
+ public TimelineMarker onNewTask(Map mvc, TimelineMarker tm) {
+ logger.info("HookLogger: onNewTask() called with ${tm}")
+ }
+
+ public void onDeleteTask(Map mvc, TimelineMarker tm) {
+ logger.info("HookLogger: onDeleteTask() called with ${tm}")
+ }
+}
diff --git a/gui/src/main/com/jdblabs/timestamper/gui/plugin/TimestamperPlugin.java b/gui/src/main/com/jdblabs/timestamper/gui/plugin/TimestamperPlugin.java
new file mode 100644
index 0000000..41f6a14
--- /dev/null
+++ b/gui/src/main/com/jdblabs/timestamper/gui/plugin/TimestamperPlugin.java
@@ -0,0 +1,57 @@
+package com.jdblabs.timestamper.gui.plugin;
+
+import java.io.File;
+import java.util.Map;
+import com.jdblabs.timestamper.core.TimelineMarker;
+
+public interface TimestamperPlugin {
+
+ /**
+ * Called before the GUI loads a new timeline properties file. This method
+ * allows you to perform an action before a timeline properties file is
+ * loaded, or to change the file that will be loaded by returning a new
+ * File object.
+ * @param mainMVC Map of the model, view, and controller objects for the
+ * timestamper main GUI.
+ * @param propertyFile The file that will be loaded.
+ * @return The property file to load (often the same as the one passed in)
+ */
+ File onTimelineLoad(Map mainMVC, File propertyFile);
+
+ /**
+ * Called after the GUI is initialized but before it is displayed.
+ * @param mainMVC map of the model, view, and controller objects for the
+ * timestamper main GUI.
+ */
+ void onStartup(Map mainMVC);
+
+ /**
+ * Called before the GUI exits. This is a best-attempt call, since the OS
+ * may force-terminate the application before it has a chance to be called.
+ * Long-running operations should not be performed in this call, as some
+ * OSes may impatiently terminate the application if it thinks it has hung.
+ * @param mainMVC map of the model, view, and controller objects for the
+ * timestamper main GUI.
+ */
+ void onExit(Map mainMVC);
+
+ /**
+ * Called before the GUI adds a new marker to the timeline.
+ * This allows you to perform some action when a new marker is created,
+ * modify the marker, or return an entirely new marker.
+ * @param mainMVC map of the model, view, and controller objects for the
+ * timestamper main GUI.
+ * @param newMarker The new TimelineMarker to be added to the timeline.
+ * @return The timeline marker to add to the timeline (often the same
+ * marker passed in).
+ */
+ TimelineMarker onNewTask(Map mainMVC, TimelineMarker newMarker);
+
+ /**
+ * Called before the GUI deletes a marker from the timeline.
+ * @param mainMVC map of the model, view, and controller objects for the
+ * timestamper main GUI.
+ * @param marker The TimelineMarker to be deleted.
+ */
+ void onDeleteTask(Map mainMVC, TimelineMarker marker);
+}
diff --git a/gui/src/main/com/jdblabs/timestamper/gui/plugin/XMPPStatusUpdater.groovy b/gui/src/main/com/jdblabs/timestamper/gui/plugin/XMPPStatusUpdater.groovy
new file mode 100644
index 0000000..8b5bbf9
--- /dev/null
+++ b/gui/src/main/com/jdblabs/timestamper/gui/plugin/XMPPStatusUpdater.groovy
@@ -0,0 +1,56 @@
+package com.jdblabs.timestamper.gui.plugin
+
+import com.jdblabs.timestamper.core.TimelineMarker
+import org.jivesoftware.smack.ConnectionConfiguration
+import org.jivesoftware.smack.XMPPConnection
+import org.jivesoftware.smack.packet.Presence
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+public class XMPPStatusUpdater implements TimestamperPlugin {
+
+ XMPPConnection conn
+ private Logger logger = LoggerFactory.getLogger(getClass())
+
+ public void onStartup(Map mainMVC) {
+
+ logger.trace("Starting XMPPStatusUpdater plugin")
+
+ String server = mainMVC.model.config."xmpp.server"
+ int port = mainMVC.model.config.getProperty("xmpp.port", 5222)
+ String username = mainMVC.model.config."xmpp.username"
+ String password = mainMVC.model.config."xmpp.password"
+
+ try {
+ conn = new XMPPConnection(new ConnectionConfiguration(server, port))
+ conn.connect()
+ conn.login(username, password, "timestamper-xmpp-plugin")
+ } catch (Exception e) {
+ logger.error("Unable to initialize XMPPUpdater.", e)
+ conn = null
+ }
+ }
+
+ public void onExit(Map mainMVC) {
+ logger.trace("Stopping XMPPStatusUpdater plugin")
+
+ try { conn.disconnect() } catch (Exception e) {}
+ }
+
+ public File onTimelineLoad(Map mainMVC, File propFile) {}
+
+ public TimelineMarker onNewTask(Map mainMVC, TimelineMarker newTask) {
+ if (!conn) {
+ logger.info("XMPPStatusUpdater is not initialized, " +
+ "skipping onNewTask()")
+ return newTask
+ }
+
+ logger.trace("Setting XMPP presence based on new task: {}", newTask)
+
+ conn.sendPacket(new Presence(Presence.Type.available, newTask.mark,
+ 0, Presence.Mode.available))
+ }
+
+ public void onDeleteTask(Map mainMVC, TimelineMarker task) {}
+}
diff --git a/gui/test/integration/LogDialogTests.groovy b/gui/test/integration/LogDialogTests.groovy
new file mode 100644
index 0000000..b71965a
--- /dev/null
+++ b/gui/test/integration/LogDialogTests.groovy
@@ -0,0 +1,10 @@
+import griffon.util.IGriffonApplication
+
+class LogDialogTests extends GroovyTestCase {
+
+ IGriffonApplication app
+
+ void testSomething() {
+
+ }
+}
diff --git a/gui/test/integration/NotesDialogTests.groovy b/gui/test/integration/NotesDialogTests.groovy
new file mode 100644
index 0000000..8eb01cc
--- /dev/null
+++ b/gui/test/integration/NotesDialogTests.groovy
@@ -0,0 +1,10 @@
+import griffon.util.IGriffonApplication
+
+class NotesDialogTests extends GroovyTestCase {
+
+ IGriffonApplication app
+
+ void testSomething() {
+
+ }
+}
diff --git a/gui/test/integration/PunchcardDialogTests.groovy b/gui/test/integration/PunchcardDialogTests.groovy
new file mode 100644
index 0000000..c24f8df
--- /dev/null
+++ b/gui/test/integration/PunchcardDialogTests.groovy
@@ -0,0 +1,10 @@
+import griffon.util.IGriffonApplication
+
+class PunchcardDialogTests extends GroovyTestCase {
+
+ IGriffonApplication app
+
+ void testSomething() {
+
+ }
+}
diff --git a/gui/test/integration/TimeStamperMainTests.groovy b/gui/test/integration/TimeStamperMainTests.groovy
new file mode 100644
index 0000000..22b85a2
--- /dev/null
+++ b/gui/test/integration/TimeStamperMainTests.groovy
@@ -0,0 +1,10 @@
+import griffon.util.IGriffonApplication
+
+class TimeStamperMainTests extends GroovyTestCase {
+
+ IGriffonApplication app
+
+ void testSomething() {
+
+ }
+}
diff --git a/gui/test/integration/TimeStamperTests.groovy b/gui/test/integration/TimeStamperTests.groovy
new file mode 100755
index 0000000..d5f9a32
--- /dev/null
+++ b/gui/test/integration/TimeStamperTests.groovy
@@ -0,0 +1,10 @@
+import griffon.util.IGriffonApplication
+
+class TimeStamperTests extends GroovyTestCase {
+
+ IGriffonApplication app
+
+ void testSomething() {
+
+ }
+}
diff --git a/gui/wrapper/griffon-wrapper.jar b/gui/wrapper/griffon-wrapper.jar
new file mode 100644
index 0000000..31f5031
Binary files /dev/null and b/gui/wrapper/griffon-wrapper.jar differ
diff --git a/gui/wrapper/griffon-wrapper.properties b/gui/wrapper/griffon-wrapper.properties
new file mode 100644
index 0000000..ad87f5e
--- /dev/null
+++ b/gui/wrapper/griffon-wrapper.properties
@@ -0,0 +1,7 @@
+#Griffon 1.2.0 upgrade
+#Thu Apr 25 06:40:33 CDT 2013
+distributionBase=GRIFFON_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRIFFON_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://dist.codehaus.org/griffon/griffon-1.2.0-bin.zip