Move timestamper-gui into a subfolder.
27
gui/griffon-app/conf/Application.groovy
Executable file
@ -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"
|
||||
}
|
||||
}
|
191
gui/griffon-app/conf/BuildConfig.groovy
Normal file
@ -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 = '<a href="http://griffon-framework.org" target="_blank"><img alt="The Griffon Framework" src="../img/griffon.png" border="0"/></a>'
|
||||
sponsorLogo = "<br/>"
|
||||
footer = "<br/><br/>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'
|
||||
}
|
||||
|
7
gui/griffon-app/conf/Builder.groovy
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
root {
|
||||
'groovy.swing.SwingBuilder' {
|
||||
controller = ['Threading']
|
||||
view = '*'
|
||||
}
|
||||
}
|
15
gui/griffon-app/conf/Config.groovy
Normal file
@ -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'
|
||||
}
|
||||
|
5
gui/griffon-app/conf/Events.groovy
Normal file
@ -0,0 +1,5 @@
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
onNewInstance = { klass, type, instance ->
|
||||
instance.metaClass.logger = LoggerFactory.getLogger(klass.name)
|
||||
}
|
BIN
gui/griffon-app/conf/keys/productionKeystore
Normal file
43
gui/griffon-app/conf/webstart/applet.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script src="http://java.com/js/deployJava.js"></script>
|
||||
<script>
|
||||
var attributes = {id: 'TimeStamper',
|
||||
codebase:'@griffonAppCodebase@',
|
||||
code:'@griffonAppletClass@',
|
||||
archive:'@appletJars@',
|
||||
width:'240', height:'320'} ;
|
||||
var parameters = {fontSize:16,
|
||||
java_arguments: "-Djnlp.packEnabled=true",
|
||||
jnlp_href:'@griffonAppCodebase@/applet.jnlp',
|
||||
draggable:'true',
|
||||
image:'griffon.png',
|
||||
boxmessage:'Loading TimeStamper',
|
||||
boxbgcolor:'#FFFFFF', boxfgcolor:'#000000',
|
||||
codebase_lookup: 'false'} ;
|
||||
var version = '1.5.0' ;
|
||||
deployJava.runApplet(attributes, parameters, version);
|
||||
</script>
|
||||
<!--
|
||||
<APPLET CODEBASE='@griffonAppCodebase@'
|
||||
CODE='@griffonAppletClass@'
|
||||
ARCHIVE='@appletJars@'
|
||||
WIDTH='240' HEIGHT='320'>
|
||||
<PARAM NAME="java_arguments" VALUE="-Djnlp.packEnabled=true">
|
||||
<PARAM NAME='jnlp_href' VALUE='@griffonAppCodebase@/applet.jnlp'>
|
||||
<PARAM NAME='dragggable' VALUE='true'>
|
||||
<PARAM NAME='image' VALUE='griffon.png'>
|
||||
<PARAM NAME='boxmessage' VALUE='Loading TimeStamper'>
|
||||
<PARAM NAME='boxbgcolor' VALUE='#FFFFFF'>
|
||||
<PARAM NAME='boxfgcolor' VALUE='#000000'>
|
||||
<PARAM NAME='codebase_lookup' VALUE='false'>
|
||||
</APPLET>
|
||||
-->
|
||||
</body>
|
||||
</html>
|
55
gui/griffon-app/conf/webstart/applet.jnlp
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE jnlp SYSTEM "http://java.sun.com/dtd/JNLP-1.5.dtd">
|
||||
<jnlp
|
||||
version="2.1"
|
||||
codebase="@griffonAppCodebase@"
|
||||
href="@jnlpFileName@"
|
||||
>
|
||||
<information>
|
||||
<title>TimeStamper</title>
|
||||
<vendor>TimeStamper</vendor>
|
||||
<!--<homepage href="http://app.example.com/"/>-->
|
||||
<!--fallback description-->
|
||||
<description>TimeStamper</description>
|
||||
<description kind="one-line">TimeStamper</description>
|
||||
<description kind="short">TimeStamper</description>
|
||||
<description kind="tooltip">TimeStamper</description>
|
||||
<!-- fallback icon -->
|
||||
<icon href="griffon-icon-48x48.png" kind="default" width="48" height="48"/>
|
||||
<!-- icon used for splash screen -->
|
||||
<icon href="griffon.png" kind="splash" width="381" height="123"/>
|
||||
<!-- icon used in menu -->
|
||||
<icon href="griffon-icon-16x16.png" kind="shortcut" width="16" height="16"/>
|
||||
<!-- icon used on desktop -->
|
||||
<icon href="griffon-icon-32x32.png" kind="shortcut" width="32" height="32"/>
|
||||
<!-- to create shortcuts, uncomment this
|
||||
<shortcut online="true">
|
||||
<desktop/>
|
||||
<menu submenu="TimeStamper"/>
|
||||
</shortcut>
|
||||
-->
|
||||
<offline-allowed/>
|
||||
</information>
|
||||
<security>
|
||||
<all-permissions/>
|
||||
<!--<j2ee-application-client-permissions/>-->
|
||||
</security>
|
||||
<resources>
|
||||
<property name="jnlp.packEnabled" value="true"/>
|
||||
<j2se version="1.5+" @memoryOptions@/>
|
||||
<!-- auto-added jars follow, griffon-rt, app, and groovy -->
|
||||
@jnlpJars@
|
||||
<!-- Add all extra jars below here, or the app may break -->
|
||||
@jnlpExtensions@
|
||||
</resources>
|
||||
<applet-desc
|
||||
documentbase="@griffonAppCodebase@"
|
||||
name="TimeStamperApplet"
|
||||
main-class="@griffonAppletClass@"
|
||||
width="320"
|
||||
height="640">
|
||||
<!-- params are ignored when referenced from web page for 6u10 -->
|
||||
<!--<param name="key1" value="value1"/>-->
|
||||
<!--<param name="key2" value="value2"/>-->
|
||||
</applet-desc>
|
||||
</jnlp>
|
50
gui/griffon-app/conf/webstart/application.jnlp
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE jnlp SYSTEM "http://java.sun.com/dtd/JNLP-1.5.dtd">
|
||||
<jnlp
|
||||
version="2.1"
|
||||
codebase="@griffonAppCodebase@"
|
||||
href="@jnlpFileName@"
|
||||
>
|
||||
<information>
|
||||
<title>TimeStamper</title>
|
||||
<vendor>TimeStamper</vendor>
|
||||
<!--<homepage href="http://app.example.com/"/>-->
|
||||
<!--fallback description-->
|
||||
<description>TimeStamper</description>
|
||||
<description kind="one-line">TimeStamper</description>
|
||||
<description kind="short">TimeStamper</description>
|
||||
<description kind="tooltip">TimeStamper</description>
|
||||
<!-- fallback icon -->
|
||||
<icon href="griffon-icon-48x48.png" kind="default" width="48" height="48"/>
|
||||
<!-- icon used for splash screen -->
|
||||
<icon href="griffon.png" kind="splash" width="381" height="123"/>
|
||||
<!-- icon used in menu -->
|
||||
<icon href="griffon-icon-16x16.png" kind="shortcut" width="16" height="16"/>
|
||||
<!-- icon used on desktop -->
|
||||
<icon href="griffon-icon-32x32.png" kind="shortcut" width="32" height="32"/>
|
||||
<!-- to create shortcuts, uncomment this
|
||||
<shortcut online="true">
|
||||
<desktop/>
|
||||
<menu submenu="TimeStamper"/>
|
||||
</shortcut>
|
||||
<offline-allowed/>
|
||||
-->
|
||||
</information>
|
||||
<security>
|
||||
<all-permissions/>
|
||||
<!--<j2ee-application-client-permissions/>-->
|
||||
</security>
|
||||
<resources>
|
||||
<property name="jnlp.packEnabled" value="true"/>
|
||||
<j2se version="1.5+" @memoryOptions@/>
|
||||
<!-- auto-added jars follow, griffon-rt, app, and groovy -->
|
||||
@jnlpJars@
|
||||
<!-- Add all extra jars below here, or the app may break -->
|
||||
@jnlpExtensions@
|
||||
</resources>
|
||||
<application-desc main-class="@griffonApplicationClass@">
|
||||
<!-- params are ignored when referenced from web page for 6u10 -->
|
||||
<!--<param name="key1" value="value1"/>-->
|
||||
<!--<param name="key2" value="value2"/>-->
|
||||
</application-desc>
|
||||
</jnlp>
|
BIN
gui/griffon-app/conf/webstart/griffon-icon-128x128.png
Executable file
After Width: | Height: | Size: 14 KiB |
BIN
gui/griffon-app/conf/webstart/griffon-icon-16x16.png
Executable file
After Width: | Height: | Size: 1.0 KiB |
BIN
gui/griffon-app/conf/webstart/griffon-icon-24x24.png
Executable file
After Width: | Height: | Size: 1.9 KiB |
BIN
gui/griffon-app/conf/webstart/griffon-icon-256x256.png
Executable file
After Width: | Height: | Size: 31 KiB |
BIN
gui/griffon-app/conf/webstart/griffon-icon-32x32.png
Executable file
After Width: | Height: | Size: 2.5 KiB |
BIN
gui/griffon-app/conf/webstart/griffon-icon-48x48.png
Executable file
After Width: | Height: | Size: 5.1 KiB |
BIN
gui/griffon-app/conf/webstart/griffon-icon-64x64.png
Executable file
After Width: | Height: | Size: 5.8 KiB |
BIN
gui/griffon-app/conf/webstart/griffon.png
Executable file
After Width: | Height: | Size: 15 KiB |
@ -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 ->
|
||||
}
|
||||
*/
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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 ->
|
||||
}
|
||||
*/
|
||||
}
|
@ -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 <<pluginClass.newInstance()
|
||||
|
||||
logger.trace("Loaded plugin: {}", className)
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
logger.warn("Unable to load plugin ${className}: " +
|
||||
"class not found.", cnfe)
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Unable to load plugin ${className}.", t)
|
||||
} }
|
||||
|
||||
// run plugin startup hook
|
||||
model.plugins.each { plugin ->
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
0
gui/griffon-app/i18n/messages.properties
Executable file
0
gui/griffon-app/i18n/resources.properties
Normal file
22
gui/griffon-app/lifecycle/Initialize.groovy
Executable file
@ -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]])
|
18
gui/griffon-app/lifecycle/Ready.groovy
Executable file
@ -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 }
|
||||
*/
|
18
gui/griffon-app/lifecycle/Shutdown.groovy
Executable file
@ -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 }
|
||||
*/
|
19
gui/griffon-app/lifecycle/Startup.groovy
Executable file
@ -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 }
|
||||
*/
|
||||
|
0
gui/griffon-app/lifecycle/Stop.groovy
Executable file
@ -0,0 +1,7 @@
|
||||
package com.jdblabs.timestamper.gui
|
||||
|
||||
import groovy.beans.Bindable
|
||||
|
||||
class LogDialogModel {
|
||||
def mainMVC
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.jdblabs.timestamper.gui
|
||||
|
||||
import groovy.beans.Bindable
|
||||
|
||||
class NotesDialogModel {
|
||||
|
||||
// needs to be injected by buildMVCGroup call
|
||||
def mainMVC
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.jdblabs.timestamper.gui
|
||||
|
||||
import groovy.beans.Bindable
|
||||
|
||||
class PunchcardDialogModel {
|
||||
|
||||
// needs to be injected by buildMVCGroup() call
|
||||
def mainMVC
|
||||
}
|
@ -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
|
||||
}
|
BIN
gui/griffon-app/resources/12-em-cross.png
Normal file
After Width: | Height: | Size: 357 B |
BIN
gui/griffon-app/resources/16-em-cross-hover.png
Normal file
After Width: | Height: | Size: 506 B |
BIN
gui/griffon-app/resources/16-em-cross.png
Normal file
After Width: | Height: | Size: 469 B |
BIN
gui/griffon-app/resources/16-em-pencil.png
Normal file
After Width: | Height: | Size: 257 B |
BIN
gui/griffon-app/resources/16-file-archive.png
Normal file
After Width: | Height: | Size: 293 B |
BIN
gui/griffon-app/resources/16-tool-a-hover.png
Normal file
After Width: | Height: | Size: 393 B |
BIN
gui/griffon-app/resources/16-tool-a.png
Normal file
After Width: | Height: | Size: 275 B |
BIN
gui/griffon-app/resources/24-em-cross.png
Normal file
After Width: | Height: | Size: 626 B |
BIN
gui/griffon-app/resources/24-message-info.png
Normal file
After Width: | Height: | Size: 746 B |
BIN
gui/griffon-app/resources/appointment-new-16x16.png
Executable file
After Width: | Height: | Size: 897 B |
BIN
gui/griffon-app/resources/appointment-new-32x32.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
gui/griffon-app/resources/delete-marker.png
Normal file
After Width: | Height: | Size: 321 B |
BIN
gui/griffon-app/resources/document-open-16x16.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
gui/griffon-app/resources/document-save-16x16.png
Normal file
After Width: | Height: | Size: 911 B |
BIN
gui/griffon-app/resources/document-save-as-16x16.png
Normal file
After Width: | Height: | Size: 866 B |
BIN
gui/griffon-app/resources/griffon-icon-128x128.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
gui/griffon-app/resources/griffon-icon-16x16.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
gui/griffon-app/resources/griffon-icon-24x24.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
gui/griffon-app/resources/griffon-icon-256x256.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
gui/griffon-app/resources/griffon-icon-32x32.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
gui/griffon-app/resources/griffon-icon-48x48.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
gui/griffon-app/resources/griffon-icon-64x64.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
gui/griffon-app/resources/griffon.png
Normal file
After Width: | Height: | Size: 15 KiB |
20
gui/griffon-app/resources/logback.groovy
Normal file
@ -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)
|
BIN
gui/griffon-app/resources/media-skip-backward.png
Normal file
After Width: | Height: | Size: 770 B |
BIN
gui/griffon-app/resources/media-skip-forward.png
Normal file
After Width: | Height: | Size: 771 B |
BIN
gui/griffon-app/resources/new-marker.png
Normal file
After Width: | Height: | Size: 345 B |
BIN
gui/griffon-app/resources/next-day.png
Normal file
After Width: | Height: | Size: 660 B |
BIN
gui/griffon-app/resources/next-week.png
Normal file
After Width: | Height: | Size: 782 B |
BIN
gui/griffon-app/resources/previous-day.png
Normal file
After Width: | Height: | Size: 636 B |
BIN
gui/griffon-app/resources/previous-week.png
Normal file
After Width: | Height: | Size: 764 B |
1880
gui/griffon-app/session.vim
Normal file
@ -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." )
|
||||
}
|
@ -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})
|
||||
}
|
||||
}
|
||||
}
|
@ -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 }))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|