Incremental GUI updates. Copied over code for TimelineDayDisplay.
This commit is contained in:
parent
02fc6b5bb7
commit
bff340e910
@ -1,4 +1,4 @@
|
|||||||
#Thu Dec 17 08:19:45 CST 2009
|
#Thu Dec 17 08:19:45 CST 2009
|
||||||
app.version=0.1
|
app.version=2.0
|
||||||
app.griffon.version=0.2
|
app.griffon.version=0.2
|
||||||
app.name=TimeStamper
|
app.name=TimeStamper
|
||||||
|
@ -1,21 +1,8 @@
|
|||||||
package com.jdbernard.timestamper
|
package com.jdbernard.timestamper
|
||||||
|
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
|
||||||
gracefulExitAction = action (
|
gracefulExitAction = action (
|
||||||
name: 'Graceful Exit',
|
name: 'Graceful Exit',
|
||||||
closure: controller.&exitGracefully
|
closure: controller.&exitGracefully
|
||||||
)
|
)
|
||||||
|
|
||||||
toolsMenuAction = action (
|
|
||||||
name: 'Show Tools Menu',
|
|
||||||
closure: controller.&showToolsMenu
|
|
||||||
)
|
|
||||||
|
|
||||||
showNotesAction = action (
|
|
||||||
name: 'Show Notes',
|
|
||||||
closure: controller.&showNotes
|
|
||||||
)
|
|
||||||
|
|
||||||
showPunchcardAction = action (
|
|
||||||
name: 'Show Punchcard',
|
|
||||||
closure: controller.&showPunchcard
|
|
||||||
)
|
|
||||||
|
@ -9,20 +9,27 @@ application {
|
|||||||
//frameClass = 'javax.swing.JFrame'
|
//frameClass = 'javax.swing.JFrame'
|
||||||
}
|
}
|
||||||
mvcGroups {
|
mvcGroups {
|
||||||
|
// MVC Group for "com.jdbernard.timestamper.PunchcardDialog"
|
||||||
|
'PunchcardDialog' {
|
||||||
|
model = 'com.jdbernard.timestamper.PunchcardDialogModel'
|
||||||
|
view = 'com.jdbernard.timestamper.PunchcardDialogView'
|
||||||
|
controller = 'com.jdbernard.timestamper.PunchcardDialogController'
|
||||||
|
}
|
||||||
|
|
||||||
// MVC Group for "com.jdbernard.timestamper.NotesDialog"
|
// MVC Group for "com.jdbernard.timestamper.NotesDialog"
|
||||||
'NotesDialog' {
|
'NotesDialog' {
|
||||||
actions = 'com.jdbernard.timestamper.NotesDialogActions'
|
actions = 'com.jdbernard.timestamper.NotesDialogActions'
|
||||||
model = 'com.jdbernard.timestamper.NotesDialogModel'
|
model = 'com.jdbernard.timestamper.NotesDialogModel'
|
||||||
controller = 'com.jdbernard.timestamper.NotesDialogController'
|
|
||||||
view = 'com.jdbernard.timestamper.NotesDialogView'
|
view = 'com.jdbernard.timestamper.NotesDialogView'
|
||||||
|
controller = 'com.jdbernard.timestamper.NotesDialogController'
|
||||||
}
|
}
|
||||||
|
|
||||||
// MVC Group for "com.jdbernard.timestamper.TimeStamperMain"
|
// MVC Group for "com.jdbernard.timestamper.TimeStamperMain"
|
||||||
'TimeStamperMain' {
|
'TimeStamperMain' {
|
||||||
actions = 'com.jdbernard.timestamper.TimeStamperMainActions'
|
actions = 'com.jdbernard.timestamper.TimeStamperMainActions'
|
||||||
model = 'com.jdbernard.timestamper.TimeStamperMainModel'
|
model = 'com.jdbernard.timestamper.TimeStamperMainModel'
|
||||||
controller = 'com.jdbernard.timestamper.TimeStamperMainController'
|
|
||||||
view = 'com.jdbernard.timestamper.TimeStamperMainView'
|
view = 'com.jdbernard.timestamper.TimeStamperMainView'
|
||||||
|
controller = 'com.jdbernard.timestamper.TimeStamperMainController'
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,11 @@
|
|||||||
package com.jdbernard.timestamper
|
package com.jdbernard.timestamper
|
||||||
|
|
||||||
import java.awt.Point
|
|
||||||
import java.awt.Rectangle
|
|
||||||
import java.awt.Toolkit
|
|
||||||
|
|
||||||
class NotesDialogController {
|
class NotesDialogController {
|
||||||
// these will be injected by Griffon
|
// these will be injected by Griffon
|
||||||
def model
|
def model
|
||||||
def view
|
def view
|
||||||
|
|
||||||
|
|
||||||
void mvcGroupInit(Map args) {
|
void mvcGroupInit(Map args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Point mousePressRelativeToDialog
|
|
||||||
|
|
||||||
def mousePressed = { evt -> mousePressRelativeToDialog = evt?.point }
|
|
||||||
|
|
||||||
def mouseDragged = { evt ->
|
|
||||||
GUIUtil.componentDragged(view.notesDialog, evt,
|
|
||||||
mousePressRelativeToDialog,
|
|
||||||
new Rectangle(Toolkit.defaultToolkit.screenSize),
|
|
||||||
app.views.TimeStamperMain.frame.bounds)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.jdbernard.timestamper
|
||||||
|
|
||||||
|
class PunchcardDialogController {
|
||||||
|
// 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 ->
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
@ -1,49 +1,64 @@
|
|||||||
package com.jdbernard.timestamper
|
package com.jdbernard.timestamper
|
||||||
|
|
||||||
import java.awt.Point
|
import com.jdbernard.timestamper.core.TimelineMarker
|
||||||
import java.awt.Rectangle
|
import com.jdbernard.timestamper.core.TimelineProperties
|
||||||
import java.awt.Toolkit
|
|
||||||
import java.util.Timer
|
|
||||||
|
|
||||||
class TimeStamperMainController {
|
class TimeStamperMainController {
|
||||||
// these will be injected by Griffon
|
// these will be injected by Griffon
|
||||||
def model
|
def model
|
||||||
def view
|
def view
|
||||||
|
|
||||||
Timer updateTimer
|
|
||||||
|
|
||||||
Point mousePressRelativeToFrame
|
|
||||||
|
|
||||||
void mvcGroupInit(Map args) {
|
void mvcGroupInit(Map args) {
|
||||||
def notes = buildMVCGroup('NotesDialog')
|
def notes = buildMVCGroup('NotesDialog')
|
||||||
|
def punchcard = buildMVCGroup('PunchcardDialog')
|
||||||
view.notesDialog = notes.view.notesDialog
|
view.notesDialog = notes.view.notesDialog
|
||||||
|
view.punchcardDialog = punchcard.view.punchcardDialog
|
||||||
|
|
||||||
updateTimer
|
// load application properties
|
||||||
|
Properties prop = new Properties()
|
||||||
|
String userHomeDir = System.getProperty('user.home')
|
||||||
|
model.configFile = new File(userHomeDir, ".timestamperrc")
|
||||||
|
if (!model.configFile.exists()) model.configFile.createNewFile()
|
||||||
|
|
||||||
|
try { model.configFile.withInputStream { prop.load(it) } }
|
||||||
|
catch (IOException ioe) { /* TODO */ }
|
||||||
|
|
||||||
|
model.config = prop
|
||||||
|
|
||||||
|
// load the last used timeline file
|
||||||
|
String lastUsed = model.config.getProperty('lastUsed', null)
|
||||||
|
if (lastUsed == null) {
|
||||||
|
lastUsed = 'timeline.default.properties'
|
||||||
|
model.config.setProperty('lastUsed', lastUsed)
|
||||||
|
}
|
||||||
|
File propertyFile = new File(lastUsed)
|
||||||
|
if (!propertyFile.exists()) propertyFile.createNewFile()
|
||||||
|
|
||||||
|
model.timelineProperties = new TimelineProperties(propertyFile)
|
||||||
|
|
||||||
|
// load the main timeline
|
||||||
|
model.timeline = model.timelineProperties.timeline
|
||||||
|
|
||||||
|
// load the last marker
|
||||||
|
model.currentMarker = model.timeline.getLastMarker(new Date())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def exitGracefully = { evt = null ->
|
def exitGracefully = { evt = null ->
|
||||||
|
// save config
|
||||||
|
try { model.configFile.withOutputStream { out ->
|
||||||
|
model.config.store(out, null) } }
|
||||||
|
catch (IOException ioe) {}
|
||||||
|
|
||||||
|
// save timeline and properties
|
||||||
|
model.timelineProperties.save()
|
||||||
app.shutdown()
|
app.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showToolsMenu = { evt = null ->
|
def newTask = { mark ->
|
||||||
|
model.currentMarker = new TimelineMarker(new Date(), mark,
|
||||||
}
|
"No comments.")
|
||||||
|
model.timeline.addMarker(model.currentMarker)
|
||||||
def showNotes = { evt = null ->
|
|
||||||
view.notesDialog.visible = view.notesVisibleButton.selected
|
|
||||||
}
|
|
||||||
|
|
||||||
def showPunchcard = { evt = null ->
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def mousePressed = { evt = null ->
|
|
||||||
mousePressRelativeToFrame = evt?.point
|
|
||||||
}
|
|
||||||
|
|
||||||
def mouseDragged = { evt = null ->
|
|
||||||
GUIUtil.componentDragged(view.frame, evt, mousePressRelativeToFrame,
|
|
||||||
new Rectangle(Toolkit.defaultToolkit.screenSize))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jdbernard.timestamper
|
||||||
|
|
||||||
|
import groovy.beans.Bindable
|
||||||
|
|
||||||
|
class PunchcardDialogModel {
|
||||||
|
// @Bindable String propName
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package com.jdbernard.timestamper
|
package com.jdbernard.timestamper
|
||||||
|
|
||||||
import groovy.beans.Bindable
|
import groovy.beans.Bindable
|
||||||
|
import java.awt.Point
|
||||||
|
import java.util.Properties
|
||||||
import com.jdbernard.timestamper.core.Timeline
|
import com.jdbernard.timestamper.core.Timeline
|
||||||
import com.jdbernard.timestamper.core.TimelineMarker
|
import com.jdbernard.timestamper.core.TimelineMarker
|
||||||
import com.jdbernard.timestamper.core.TimelineProperties
|
import com.jdbernard.timestamper.core.TimelineProperties
|
||||||
@ -8,5 +10,9 @@ import com.jdbernard.timestamper.core.TimelineProperties
|
|||||||
class TimeStamperMainModel {
|
class TimeStamperMainModel {
|
||||||
@Bindable TimelineMarker currentMarker
|
@Bindable TimelineMarker currentMarker
|
||||||
@Bindable Timeline timeline
|
@Bindable Timeline timeline
|
||||||
@Bindable TimelineProperties properties
|
@Bindable TimelineProperties timelineProperties
|
||||||
|
@Bindable Properties config
|
||||||
|
File configFile
|
||||||
|
|
||||||
|
@Bindable Point absoluteLocation
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,55 @@
|
|||||||
package com.jdbernard.timestamper
|
package com.jdbernard.timestamper
|
||||||
|
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
|
import java.awt.Point
|
||||||
|
import java.awt.Rectangle
|
||||||
|
import java.awt.Toolkit
|
||||||
import javax.swing.BoxLayout
|
import javax.swing.BoxLayout
|
||||||
import net.miginfocom.swing.MigLayout
|
import net.miginfocom.swing.MigLayout
|
||||||
|
|
||||||
|
Point mousePressRelativeToDialog
|
||||||
|
Point offsetFromMainFrame
|
||||||
|
|
||||||
|
mousePressed = { evt -> mousePressRelativeToDialog = evt?.point }
|
||||||
|
|
||||||
|
mouseDragged = { evt ->
|
||||||
|
GUIUtil.componentDragged(view.notesDialog, evt,
|
||||||
|
mousePressRelativeToDialog,
|
||||||
|
new Rectangle(Toolkit.defaultToolkit.screenSize),
|
||||||
|
app.views.TimeStamperMain.frame.bounds)
|
||||||
|
|
||||||
|
Point p = app.views.TimeStamperMain.frame.location
|
||||||
|
offsetFromMainFrame = new Point(notesDialog.location)
|
||||||
|
offsetFromMainFrame.translate((int) -p.x, (int) -p.y)
|
||||||
|
}
|
||||||
|
|
||||||
notesDialog = dialog(
|
notesDialog = dialog(
|
||||||
title: 'Notes',
|
title: 'Notes',
|
||||||
modal: false,
|
modal: false,
|
||||||
undecorated: true,
|
undecorated: true,
|
||||||
minimumSize: [325, 200],
|
minimumSize: [325, 200],
|
||||||
iconImage: imageIcon('/16-em-pencil.png').image,
|
iconImage: imageIcon('/16-em-pencil.png').image,
|
||||||
iconImages: [imageIcon('/16-em-pencil.png').image]
|
iconImages: [imageIcon('/16-em-pencil.png').image],
|
||||||
|
location: bind(source: app.models.TimeStamperMain,
|
||||||
|
sourceProperty: 'absoluteLocation',
|
||||||
|
converter: { loc ->
|
||||||
|
Point p = new Point(offsetFromMainFrame)
|
||||||
|
p.translate((int) loc.x, (int) loc.y)
|
||||||
|
return p})
|
||||||
) {
|
) {
|
||||||
|
|
||||||
panel(
|
panel(
|
||||||
border:lineBorder(color: Color.BLACK, thickness:1, parent:true),
|
border:lineBorder(color: Color.BLACK, thickness:1, parent:true),
|
||||||
mousePressed: controller.&mousePressed,
|
mousePressed: mousePressed,
|
||||||
mouseDragged: controller.&mouseDragged,
|
mouseDragged: mouseDragged,
|
||||||
layout: new MigLayout('insets 5 5 5 5, fill')
|
layout: new MigLayout('insets 5 5 5 5, fill')
|
||||||
) {
|
) {
|
||||||
scrollPane(constraints: 'growx, growy') {
|
scrollPane(constraints: 'growx, growy, spany 2') {
|
||||||
notesTextArea = textArea(lineWrap: true, columns: 20, rows: 5,
|
notesTextArea = textArea(lineWrap: true, columns: 20, rows: 5,
|
||||||
wrapStyleWord: true)
|
wrapStyleWord: true,
|
||||||
|
text: bind(source: app.models.TimeStamperMain,
|
||||||
|
sourceProperty: 'currentMarker',
|
||||||
|
sourceValue: { app.models.TimeStamperMain.currentMarker?.notes}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.jdbernard.timestamper
|
||||||
|
|
||||||
|
punchcardDialog = dialog(
|
||||||
|
/* title: 'Punchcard',
|
||||||
|
modal: false,
|
||||||
|
undecorated: true,
|
||||||
|
iconImage: iconImage('/16-file-archive.png').image,
|
||||||
|
iconImages: [iconImage('/16-file-archive.png').image],
|
||||||
|
minimumSize: [325, 600]*/
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
@ -1,10 +1,88 @@
|
|||||||
package com.jdbernard.timestamper
|
package com.jdbernard.timestamper
|
||||||
|
|
||||||
|
import groovy.beans.Bindable
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
import java.awt.Font
|
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.BoxLayout
|
||||||
|
import javax.swing.Timer
|
||||||
|
import com.jdbernard.timestamper.core.Timeline
|
||||||
import net.miginfocom.swing.MigLayout
|
import net.miginfocom.swing.MigLayout
|
||||||
|
|
||||||
|
Point mousePressRelativeToFrame
|
||||||
|
|
||||||
|
/* ========== *
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
showToolsMenu = { evt = null ->
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
showNotes = { evt = null ->
|
||||||
|
notesDialog.visible = notesVisibleButton.selected
|
||||||
|
}
|
||||||
|
|
||||||
|
showPunchcard = { evt = null ->
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
frame = application(title:'TimeStamper',
|
frame = application(title:'TimeStamper',
|
||||||
//size:[320,480],
|
//size:[320,480],
|
||||||
pack:true,
|
pack:true,
|
||||||
@ -13,23 +91,29 @@ frame = application(title:'TimeStamper',
|
|||||||
locationByPlatform:true,
|
locationByPlatform:true,
|
||||||
iconImage: imageIcon('/appointment-new-32x32.png').image,
|
iconImage: imageIcon('/appointment-new-32x32.png').image,
|
||||||
iconImages: [imageIcon('/appointment-new-32x32.png').image,
|
iconImages: [imageIcon('/appointment-new-32x32.png').image,
|
||||||
imageIcon('/appointment-new-16x16.png').image]
|
imageIcon('/appointment-new-16x16.png').image],
|
||||||
|
componentMoved: { evt -> model.absoluteLocation = frame.location }
|
||||||
) {
|
) {
|
||||||
panel(
|
panel(
|
||||||
border:lineBorder(color:Color.BLACK, thickness:1, parent:true),
|
border:lineBorder(color:Color.BLACK, thickness:1, parent:true),
|
||||||
layout: new MigLayout('insets 0 5 0 0, fill','', '[]0[]0[]'),
|
layout: new MigLayout('insets 0 5 0 0, fill','', '[]0[]0[]'),
|
||||||
mousePressed: controller.&mousePressed,
|
mousePressed: mousePressed,
|
||||||
mouseDragged: controller.&mouseDragged
|
mouseDragged: mouseDragged
|
||||||
) {
|
) {
|
||||||
def mainFont = new Font(Font.SANS_SERIF, Font.BOLD, 12)
|
def mainFont = new Font(Font.SANS_SERIF, Font.BOLD, 12)
|
||||||
def timeFont = new Font(Font.SANS_SERIF, Font.BOLD, 14)
|
def timeFont = new Font(Font.SANS_SERIF, Font.BOLD, 14)
|
||||||
label("Current task started at ", font: mainFont)
|
label("Current task started at ", font: mainFont)
|
||||||
label("00:00:00", constraints: 'align leading',
|
label(constraints: 'align leading', font: timeFont,
|
||||||
font: timeFont, foreground: [0, 102, 102])
|
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') {
|
panel(constraints: 'alignx trailing, aligny top, wrap') {
|
||||||
boxLayout(axis: BoxLayout.X_AXIS)
|
boxLayout(axis: BoxLayout.X_AXIS)
|
||||||
button(toolsMenuAction,
|
button(actionPerformed: showToolsMenu,
|
||||||
icon: imageIcon('/16-tool-a.png'),
|
icon: imageIcon('/16-tool-a.png'),
|
||||||
rolloverIcon: imageIcon('/16-tool-a-hover.png'),
|
rolloverIcon: imageIcon('/16-tool-a-hover.png'),
|
||||||
border: emptyBorder(0),
|
border: emptyBorder(0),
|
||||||
@ -43,23 +127,35 @@ frame = application(title:'TimeStamper',
|
|||||||
hideActionText: true)
|
hideActionText: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
textField("Task name", constraints: "growx, span 2, w 250::")
|
taskTextField = textField("Task name",
|
||||||
|
constraints: "growx, span 2, w 250::",
|
||||||
|
keyReleased: taskTextFieldChanged,
|
||||||
|
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') {
|
panel(constraints: 'alignx leading, aligny top, gapright 5px, wrap') {
|
||||||
boxLayout(axis: BoxLayout.X_AXIS)
|
boxLayout(axis: BoxLayout.X_AXIS)
|
||||||
notesVisibleButton = toggleButton(showNotesAction, icon: imageIcon('/16-em-pencil.png'),
|
notesVisibleButton = toggleButton(
|
||||||
|
actionPerformed: showNotes,
|
||||||
|
icon: imageIcon('/16-em-pencil.png'),
|
||||||
hideActionText: true,
|
hideActionText: true,
|
||||||
border: emptyBorder(4))
|
border: emptyBorder(4))
|
||||||
punchcardVisibleButton = toggleButton(showPunchcardAction,
|
punchcardVisibleButton = toggleButton(
|
||||||
|
actionPerformed: showPunchcard,
|
||||||
icon: imageIcon('/16-file-archive.png'),
|
icon: imageIcon('/16-file-archive.png'),
|
||||||
hideActionText: true,
|
hideActionText: true,
|
||||||
border: emptyBorder(4))
|
border: emptyBorder(4))
|
||||||
}
|
}
|
||||||
|
|
||||||
label("2hr 18min 56sec", constraints: 'alignx leading', font: timeFont,
|
totalTimeLabel = label("", constraints: 'alignx leading',
|
||||||
foreground: [0, 153, 0])
|
font: timeFont, foreground: [0, 153, 0])
|
||||||
|
|
||||||
label("00:00:00", constraints: 'align trailing', font: timeFont,
|
currentTimeLabel = label("00:00:00", constraints: 'align trailing',
|
||||||
foreground: [204, 0, 0])
|
font: timeFont, foreground: [204, 0, 0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ public class Timeline implements Iterable<TimelineMarker> {
|
|||||||
timelineList = new TreeSet<TimelineMarker>();
|
timelineList = new TreeSet<TimelineMarker>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addMarker(TimelineMarker tm) { timelineList.add(tm); }
|
||||||
|
|
||||||
public void addMarker(Date timestamp, String name, String notes) {
|
public void addMarker(Date timestamp, String name, String notes) {
|
||||||
timelineList.add(new TimelineMarker(timestamp, name, notes));
|
timelineList.add(new TimelineMarker(timestamp, name, notes));
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,14 @@ public class TimelineProperties {
|
|||||||
// load local timeline
|
// load local timeline
|
||||||
strURI = config.getProperty(LOCAL_TIMELINE_URI, "");
|
strURI = config.getProperty(LOCAL_TIMELINE_URI, "");
|
||||||
if ("".equals(strURI)) {
|
if ("".equals(strURI)) {
|
||||||
timelineURI = new File("timeline.default.txt").toURI();
|
File defaultTimelineFile = new File("timeline.default.txt");
|
||||||
|
try {
|
||||||
|
if (!defaultTimelineFile.exists())
|
||||||
|
defaultTimelineFile.createNewFile();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
timelineURI = defaultTimelineFile.toURI();
|
||||||
} else {
|
} else {
|
||||||
try { timelineURI = new URI(strURI); }
|
try { timelineURI = new URI(strURI); }
|
||||||
catch (URISyntaxException urise) {
|
catch (URISyntaxException urise) {
|
||||||
|
660
src/main/com/jdbernard/timestamper/gui/TimelineDayDisplay.java
Normal file
660
src/main/com/jdbernard/timestamper/gui/TimelineDayDisplay.java
Normal file
@ -0,0 +1,660 @@
|
|||||||
|
/* TimelineDayDisplay.java
|
||||||
|
* Author: Jonathan Bernard - jonathan.bernard@gemalto.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdbernard.timestamper.gui;
|
||||||
|
|
||||||
|
import com.jdbernard.timestamper.core.TimelineMarker;
|
||||||
|
import com.jdbernard.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.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 jbernard
|
||||||
|
*/
|
||||||
|
public class TimelineDayDisplay extends JComponent implements MouseListener,
|
||||||
|
ChangeListener {
|
||||||
|
|
||||||
|
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); { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<MarkerDisplayEntry> markerEntries;
|
||||||
|
private ArrayList<TimeLegendEntry> timeLegendLocations;
|
||||||
|
private TimelineMarker currentMarker;
|
||||||
|
private ArrayList<ChangeListener> changeListeners = new ArrayList<ChangeListener>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
public TimelineDayDisplay() {
|
||||||
|
super();
|
||||||
|
setDay(new Date(), false);
|
||||||
|
addMouseListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimelineDayDisplay(Calendar day) {
|
||||||
|
setDay(day.getTime(), false);
|
||||||
|
addMouseListener(this);
|
||||||
|
updateMarkers(getGraphics());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
rangeStartDate = start;
|
||||||
|
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 <b><pre>true</pre></b>,
|
||||||
|
* <code>updateMarkers(getGraphics)</code> 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);
|
||||||
|
rangeStartDate = day.getTime();
|
||||||
|
|
||||||
|
day.add(Calendar.DAY_OF_YEAR, 1);
|
||||||
|
rangeEndDate = day.getTime();
|
||||||
|
|
||||||
|
if (update) updateMarkers(getGraphics());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkFont(Font f) {
|
||||||
|
markFont = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Font getMarkFont() {
|
||||||
|
return markFont;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotesFont(Font f) {
|
||||||
|
notesFont = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Font getNotesFont() {
|
||||||
|
return notesFont;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFontColor(Color f) {
|
||||||
|
fontColor = new Color(f.getRGB());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getFontColor() {
|
||||||
|
return fontColor;
|
||||||
|
}
|
||||||
|
public void setEvenColor(Color c) {
|
||||||
|
evenOpaque = new Color(c.getRGB());
|
||||||
|
evenTrans = new Color((float) c.getRed() / 255f,
|
||||||
|
(float) c.getGreen() / 255f,
|
||||||
|
(float) c.getBlue() / 255f, 0.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getEvenColor() {
|
||||||
|
return evenOpaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOddColor(Color c) {
|
||||||
|
oddOpaque = new Color(c.getRGB());
|
||||||
|
oddTrans = new Color((float) c.getRed() / 255f,
|
||||||
|
(float) c.getGreen() / 255f,
|
||||||
|
(float) c.getBlue() / 255f, 0.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getOddColor() {
|
||||||
|
return oddOpaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedColor(Color c) {
|
||||||
|
selectedOpaque = new Color(c.getRGB());
|
||||||
|
selectedTrans = new Color((float) c.getRed() / 255f,
|
||||||
|
(float) c.getGreen() / 255f,
|
||||||
|
(float) c.getBlue() / 255f, 0.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getSelectedColor() {
|
||||||
|
return selectedOpaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimelineMarker getSelectedTimelineMarker() {
|
||||||
|
return currentMarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMarker(Date timestamp, String mark, String notes) {
|
||||||
|
/*Timeline timeline = TimeStamperApp.getApplication()
|
||||||
|
.getTimelineProperties().getTimeline();
|
||||||
|
timeline.addMarker(timestamp, mark, notes);
|
||||||
|
updateMarkers(getGraphics());*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteSelectedMarker() {
|
||||||
|
/*Timeline timeline = TimeStamperApp.getApplication()
|
||||||
|
.getTimelineProperties().getTimeline();
|
||||||
|
timeline.removeMarker(currentMarker);
|
||||||
|
updateMarkers(getGraphics());*/
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
/*Timeline timeline = TimeStamperApp.getApplication()
|
||||||
|
.getTimelineProperties().getTimeline();
|
||||||
|
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<MarkerDisplayEntry>();
|
||||||
|
timeLegendLocations = new ArrayList<TimeLegendEntry>();
|
||||||
|
|
||||||
|
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<TimelineMarker> itr = timeline.iterator();
|
||||||
|
|
||||||
|
while (!itr.next().equals(tm));
|
||||||
|
|
||||||
|
ArrayList<TimelineMarker> markers = new ArrayList<TimelineMarker>();
|
||||||
|
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 (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())) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
test/integration/PunchcardDialogTests.groovy
Normal file
10
test/integration/PunchcardDialogTests.groovy
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import griffon.util.IGriffonApplication
|
||||||
|
|
||||||
|
class PunchcardDialogTests extends GroovyTestCase {
|
||||||
|
|
||||||
|
IGriffonApplication app
|
||||||
|
|
||||||
|
void testSomething() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user