Incremental GUI updates. Copied over code for TimelineDayDisplay.

This commit is contained in:
Jonathan Bernard 2009-12-22 19:26:47 -06:00
parent 02fc6b5bb7
commit bff340e910
15 changed files with 919 additions and 81 deletions

View File

@ -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

View File

@ -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
)

View File

@ -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'
} }
} }

View File

@ -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)
}
} }

View File

@ -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 ->
}
*/
}

View File

@ -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))
} }
} }

View File

@ -0,0 +1,7 @@
package com.jdbernard.timestamper
import groovy.beans.Bindable
class PunchcardDialogModel {
// @Bindable String propName
}

View File

@ -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
} }

View File

@ -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}))
} }
} }

View File

@ -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]*/
) {
}

View File

@ -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])
} }
} }

View File

@ -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));
} }

View File

@ -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) {

View 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();
}
}

View File

@ -0,0 +1,10 @@
import griffon.util.IGriffonApplication
class PunchcardDialogTests extends GroovyTestCase {
IGriffonApplication app
void testSomething() {
}
}