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
app.version=0.1
app.version=2.0
app.griffon.version=0.2
app.name=TimeStamper

View File

@ -1,21 +1,8 @@
package com.jdbernard.timestamper
import java.awt.event.KeyEvent
gracefulExitAction = action (
name: 'Graceful Exit',
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'
}
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"
'NotesDialog' {
actions = 'com.jdbernard.timestamper.NotesDialogActions'
model = 'com.jdbernard.timestamper.NotesDialogModel'
controller = 'com.jdbernard.timestamper.NotesDialogController'
view = 'com.jdbernard.timestamper.NotesDialogView'
controller = 'com.jdbernard.timestamper.NotesDialogController'
}
// MVC Group for "com.jdbernard.timestamper.TimeStamperMain"
'TimeStamperMain' {
actions = 'com.jdbernard.timestamper.TimeStamperMainActions'
model = 'com.jdbernard.timestamper.TimeStamperMainModel'
controller = 'com.jdbernard.timestamper.TimeStamperMainController'
view = 'com.jdbernard.timestamper.TimeStamperMainView'
controller = 'com.jdbernard.timestamper.TimeStamperMainController'
}
}

View File

@ -1,26 +1,11 @@
package com.jdbernard.timestamper
import java.awt.Point
import java.awt.Rectangle
import java.awt.Toolkit
class NotesDialogController {
// these will be injected by Griffon
def model
def view
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
import java.awt.Point
import java.awt.Rectangle
import java.awt.Toolkit
import java.util.Timer
import com.jdbernard.timestamper.core.TimelineMarker
import com.jdbernard.timestamper.core.TimelineProperties
class TimeStamperMainController {
// these will be injected by Griffon
def model
def view
Timer updateTimer
Point mousePressRelativeToFrame
void mvcGroupInit(Map args) {
def notes = buildMVCGroup('NotesDialog')
def punchcard = buildMVCGroup('PunchcardDialog')
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 ->
// save config
try { model.configFile.withOutputStream { out ->
model.config.store(out, null) } }
catch (IOException ioe) {}
// save timeline and properties
model.timelineProperties.save()
app.shutdown()
}
def showToolsMenu = { evt = null ->
}
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))
def newTask = { mark ->
model.currentMarker = new TimelineMarker(new Date(), mark,
"No comments.")
model.timeline.addMarker(model.currentMarker)
}
}

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
import groovy.beans.Bindable
import java.awt.Point
import java.util.Properties
import com.jdbernard.timestamper.core.Timeline
import com.jdbernard.timestamper.core.TimelineMarker
import com.jdbernard.timestamper.core.TimelineProperties
@ -8,5 +10,9 @@ import com.jdbernard.timestamper.core.TimelineProperties
class TimeStamperMainModel {
@Bindable TimelineMarker currentMarker
@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
import java.awt.Color
import java.awt.Point
import java.awt.Rectangle
import java.awt.Toolkit
import javax.swing.BoxLayout
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(
title: 'Notes',
modal: false,
undecorated: true,
minimumSize: [325, 200],
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(
border:lineBorder(color: Color.BLACK, thickness:1, parent:true),
mousePressed: controller.&mousePressed,
mouseDragged: controller.&mouseDragged,
mousePressed: mousePressed,
mouseDragged: mouseDragged,
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,
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
import groovy.beans.Bindable
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.Timer
import com.jdbernard.timestamper.core.Timeline
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',
//size:[320,480],
pack:true,
@ -13,23 +91,29 @@ frame = application(title:'TimeStamper',
locationByPlatform:true,
iconImage: 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(
border:lineBorder(color:Color.BLACK, thickness:1, parent:true),
layout: new MigLayout('insets 0 5 0 0, fill','', '[]0[]0[]'),
mousePressed: controller.&mousePressed,
mouseDragged: controller.&mouseDragged
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("00:00:00", constraints: 'align leading',
font: timeFont, foreground: [0, 102, 102])
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(toolsMenuAction,
button(actionPerformed: showToolsMenu,
icon: imageIcon('/16-tool-a.png'),
rolloverIcon: imageIcon('/16-tool-a-hover.png'),
border: emptyBorder(0),
@ -43,23 +127,35 @@ frame = application(title:'TimeStamper',
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') {
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,
border: emptyBorder(4))
punchcardVisibleButton = toggleButton(showPunchcardAction,
punchcardVisibleButton = toggleButton(
actionPerformed: showPunchcard,
icon: imageIcon('/16-file-archive.png'),
hideActionText: true,
border: emptyBorder(4))
}
label("2hr 18min 56sec", constraints: 'alignx leading', font: timeFont,
foreground: [0, 153, 0])
totalTimeLabel = label("", constraints: 'alignx leading',
font: timeFont, foreground: [0, 153, 0])
label("00:00:00", constraints: 'align trailing', font: timeFont,
foreground: [204, 0, 0])
currentTimeLabel = label("00:00:00", constraints: 'align trailing',
font: timeFont, foreground: [204, 0, 0])
}
}

View File

@ -23,6 +23,8 @@ public class Timeline implements Iterable<TimelineMarker> {
timelineList = new TreeSet<TimelineMarker>();
}
public void addMarker(TimelineMarker tm) { timelineList.add(tm); }
public void addMarker(Date timestamp, String name, String notes) {
timelineList.add(new TimelineMarker(timestamp, name, notes));
}

View File

@ -68,7 +68,14 @@ public class TimelineProperties {
// load local timeline
strURI = config.getProperty(LOCAL_TIMELINE_URI, "");
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 {
try { timelineURI = new URI(strURI); }
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() {
}
}