Version 1.7 - Added 30-minute auto save

committer: Jonathan Bernard <jdbernard@gmail.com>
This commit is contained in:
Jonathan Bernard 2008-11-11 19:29:58 -06:00
parent efcb781ae4
commit e69faa148f
11 changed files with 560 additions and 233 deletions

View File

@ -1,12 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/1">
<file>file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/AboutDialog.java</file>
<file>file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/PunchcardDisplayDialog.java</file>
<file>file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/TimeStamperApp.java</file>
<file>file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/TimeStamperView.java</file>
<file>file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/Timeline.java</file>
<file>file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/TimelineDayDisplay.java</file>
</open-files>
</project-private>

View File

@ -2,7 +2,7 @@ application.desc=Simple application used to track activities throughout time.
application.homepage=
application.title=TimeStamper
application.vendor=Jonathan Bernard
application.version=1.5
application.version=1.7
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form
# This directory is removed when the project is cleaned:

View File

@ -48,54 +48,7 @@
<AuxValues>
<AuxValue name="JavaCodeGenerator_ListenersCodePost" type="java.lang.String" value="mainPanel.addMouseMotionListener(this);"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Component id="dayDisplay" alignment="0" pref="405" max="32767" attributes="0"/>
<Component id="detailPanel" alignment="0" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="dayDisplay" pref="339" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="detailPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="jdbernard.timestamper.TimelineDayDisplay" name="dayDisplay">
<Properties>
<Property name="name" type="java.lang.String" value="dayDisplay" noResource="true"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 100]"/>
</Property>
</Properties>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="405" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="339" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
</Container>
<Container class="javax.swing.JPanel" name="detailPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">

View File

@ -6,16 +6,16 @@
package jdbernard.timestamper;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jdesktop.application.Action;
@ -27,18 +27,31 @@ import org.jdesktop.application.Action;
public class PunchcardDisplayDialog extends JDialog
implements MouseMotionListener, ChangeListener {
/* Layout of class code:
* Constructors
* Generated GUI init code
* Generated class variables
* Additional class variables
* Action methods
* Listener methods
* Additional methods
*/
/** Creates new form PunchcardDisplayDialog */
public PunchcardDisplayDialog(Frame parent, boolean modal,
Timeline timeline, Calendar day) {
public PunchcardDisplayDialog(JFrame parent, boolean modal,
Calendar day) {
super(parent, modal);
this.timeline = timeline;
this.day = day;
initComponents();
dayDisplay.setDay(day.getTime());
dateLabel.setText(dateFormatter.format(day.getTime()));
dayDisplay.addChangeListener(this);
addChangeListener(dayDisplay);
markTextField.setFont(dayDisplay.getMarkFont());
notesTextArea.setFont(dayDisplay.getNotesFont());
dayDisplay.setTimeline(timeline);
dayDisplay.setDay(day);
dayDisplay.addChangeListener(this);
}
/** This method is called from within the constructor to
@ -296,100 +309,6 @@ private void mainPanelMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:e
mousePressRelativeToWindow = evt.getPoint();
}//GEN-LAST:event_mainPanelMousePressed
public void setTimeline(Timeline t) {
timeline = t;
dayDisplay.setTimeline(t);
}
public Timeline getTimeline() {
return timeline;
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) throws Exception {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
Timeline timeline = null;
Calendar day = Calendar.getInstance();
try { timeline = Timeline.readFromFile("test-timeline.txt"); }
catch (Exception e) {
System.err.println("Could not open timeline text:");
e.printStackTrace(System.err);
timeline = new Timeline();
try { new File("PLACE_HERE").createNewFile(); }
catch (Exception e1) {}
}
PunchcardDisplayDialog dialog = new PunchcardDisplayDialog(new javax.swing.JFrame(), true, timeline, day);
dialog.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent e) {
System.exit(0);
}
});
dialog.setVisible(true);
}
});
}
@Action
public void previousWeek() {
day.add(Calendar.WEEK_OF_YEAR, -1);
dayDisplay.setDay(day);
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void previousDay() {
day.add(Calendar.DAY_OF_YEAR, -1);
dayDisplay.setDay(day);
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void currentDay() {
day = Calendar.getInstance();
dayDisplay.setDay(day);
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void nextDay() {
day.add(Calendar.DAY_OF_YEAR, 1);
dayDisplay.setDay(day);
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void nextWeek() {
day.add(Calendar.WEEK_OF_YEAR, 1);
dayDisplay.setDay(day);
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void newMarker() {
dayDisplay.addMarker(timestampDateChooser.getDate(),
markTextField.getText(), notesTextArea.getText());
repaint();
}
@Action
public void deleteMarker() {
dayDisplay.deleteSelectedMarker();
repaint();
}
@Action
public void saveMarkerChanges() {
deleteMarker();
newMarker();
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel buttonPanel;
@ -414,10 +333,71 @@ private void mainPanelMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:e
// End of variables declaration//GEN-END:variables
private Calendar day;
private Timeline timeline;
private SimpleDateFormat dateFormatter =
new SimpleDateFormat("EEE MMM dd");
private SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE MMM dd");
private Point mousePressRelativeToWindow;
private ArrayList<ChangeListener> changeListeners =
new ArrayList<ChangeListener>();
@Action
public void previousWeek() {
day.add(Calendar.WEEK_OF_YEAR, -1);
dayDisplay.setDay(day.getTime());
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void previousDay() {
day.add(Calendar.DAY_OF_YEAR, -1);
dayDisplay.setDay(day.getTime());
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void currentDay() {
day = Calendar.getInstance();
dayDisplay.setDay(day.getTime());
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void nextDay() {
day.add(Calendar.DAY_OF_YEAR, 1);
dayDisplay.setDay(day.getTime());
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void nextWeek() {
day.add(Calendar.WEEK_OF_YEAR, 1);
dayDisplay.setDay(day.getTime());
dateLabel.setText(dateFormatter.format(day.getTime()));
repaint();
}
@Action
public void newMarker() {
dayDisplay.addMarker(timestampDateChooser.getDate(),
markTextField.getText(), notesTextArea.getText());
fireChangeEvent();
repaint();
}
@Action
public void deleteMarker() {
dayDisplay.deleteSelectedMarker();
fireChangeEvent();
repaint();
}
@Action
public void saveMarkerChanges() {
deleteMarker();
newMarker();
}
public void mouseDragged(MouseEvent e) {
setLocation(TimeStamperView.calculateWindowMovement(
@ -429,11 +409,66 @@ private void mainPanelMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:e
public void mouseMoved(MouseEvent e) {
}
public void stateChanged(ChangeEvent e) {
Timeline.TimelineMarker marker = dayDisplay.getSelectedTimelineMarker();
timestampDateChooser.setDate(marker.getTimestamp());
private void fireChangeEvent() {
ChangeEvent ce = new ChangeEvent(this);
for (ChangeListener cl : changeListeners)
cl.stateChanged(ce);
}
public void stateChanged(ChangeEvent e) {
if (e.getSource() == dayDisplay) {
Timeline.TimelineMarker marker = dayDisplay.getSelectedTimelineMarker();
if (marker == null) {
timestampDateChooser.setDate(null);
markTextField.setText("");
notesTextArea.setText("");
} else {
timestampDateChooser.setDate(marker.getTimestamp());
markTextField.setText(marker.getMark());
notesTextArea.setText(marker.getNotes());
}
} else {
for (ChangeListener cl : changeListeners)
if (cl != e.getSource()) cl.stateChanged(e);
}
}
public void addChangeListener(ChangeListener cl) {
changeListeners.add(cl);
}
public boolean removeChangeListener(ChangeListener cl) {
return changeListeners.remove(cl);
}
/**
* @param args the command line arguments
*
public static void main(String args[]) throws Exception {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
Timeline timeline = null;
Calendar day = Calendar.getInstance();
try { timeline = Timeline.readFromFile("test-timeline.txt"); }
catch (Exception e) {
System.err.println("Could not open timeline text:");
e.printStackTrace(System.err);
timeline = new Timeline();
try { new File("PLACE_HERE").createNewFile(); }
catch (Exception e1) {}
}
PunchcardDisplayDialog dialog = new PunchcardDisplayDialog(new javax.swing.JFrame(), true, timeline, day);
dialog.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent e) {
System.exit(0);
}
});
dialog.setVisible(true);
}
});
}*/
}

View File

@ -33,26 +33,32 @@ implements Application.ExitListener {
public TimeStamperApp() {
super();
// set defaults for fields
activeTimeline = new Timeline();
// set up logger
log = Logger.getLogger("jdbernard.timestamper");
log.setLevel(Level.CONFIG);
log.setLevel(Level.ALL);
// add console handler
ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(Level.INFO);
log.addHandler(ch);
// try to add file handler
try {
FileHandler fh = new FileHandler("TimeStamper.log", true);
fh.setFormatter(new SimpleFormatter());
fh.setLevel(Level.ALL);
log.addHandler(fh);
} catch (IOException ioe) {
log.warning("Could not open log file for writing. Switching console"
+ " logging to verbose.");
ch.setLevel(Level.ALL);
}
// load configuration
try {
config = new Properties();
File cfgFile = new File("timestamper.config");
@ -64,6 +70,7 @@ implements Application.ExitListener {
log.warning("Could not load configuration options.");
}
// load the last used timeline
loadTimeline(config.getProperty("lastUsedTimelineFilename"));
}

View File

@ -11,12 +11,15 @@ import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.ButtonGroup;
import javax.swing.JFileChooser;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jdesktop.application.Action;
import org.jdesktop.application.ResourceMap;
import org.jdesktop.application.SingleFrameApplication;
@ -26,7 +29,8 @@ import sun.security.jca.GetInstance;
/**
* The application's main frame.
*/
public class TimeStamperView extends FrameView implements MouseMotionListener {
public class TimeStamperView extends FrameView implements MouseMotionListener,
ChangeListener {
public TimeStamperView(SingleFrameApplication app) {
super(app);
@ -72,6 +76,14 @@ public class TimeStamperView extends FrameView implements MouseMotionListener {
}
}, 0, 1000);
saveTimelineTimer = new Timer();
saveTimelineTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
saveTimeline();
}
}, 0, 1000*60*30);
thinTaskFont = taskTextField.getFont().deriveFont(Font.PLAIN);
boldTaskFont = thinTaskFont.deriveFont(Font.BOLD);
@ -263,6 +275,7 @@ public class TimeStamperView extends FrameView implements MouseMotionListener {
startTimeLabel.setText(Timeline.shortFormat.format(d));
taskTextField.setFont(boldTaskFont);
mostRecentTask = d;
fireChangeEvent();
} else {
taskTextField.setFont(thinTaskFont);
}
@ -329,6 +342,7 @@ public class TimeStamperView extends FrameView implements MouseMotionListener {
private Point mousePressRelativeToFrame;
private JFileChooser fileChooser;
private Timer updateTimer;
private Timer saveTimelineTimer;
private Font boldTaskFont;
private Font thinTaskFont;
private Date mostRecentTask;
@ -338,13 +352,15 @@ public class TimeStamperView extends FrameView implements MouseMotionListener {
private boolean notesVisible = false;
private boolean punchcardVisible = false;
private boolean aboutDialogVisible = false;
private ArrayList<ChangeListener> changeListeners =
new ArrayList<ChangeListener>();
private PunchcardDisplayDialog getPunchcardDisplayDialog() {
if (punchcardDisplayDialog == null) {
punchcardDisplayDialog = new PunchcardDisplayDialog(this.getFrame(),
false,
((TimeStamperApp) getApplication()).getActiveTimeline(),
Calendar.getInstance());
false, Calendar.getInstance());
punchcardDisplayDialog.addChangeListener(this);
addChangeListener(punchcardDisplayDialog);
}
return punchcardDisplayDialog;
}
@ -434,6 +450,26 @@ public class TimeStamperView extends FrameView implements MouseMotionListener {
public void mouseMoved(MouseEvent e) {
}
public void stateChanged(ChangeEvent ce) {
refreshDialog();
}
public void addChangeListener(ChangeListener cl) {
changeListeners.add(cl);
}
public boolean removeChangeListener(ChangeListener cl) {
return changeListeners.remove(cl);
}
private void fireChangeEvent() {
ChangeEvent ce = new ChangeEvent(this);
for (ChangeListener cl : changeListeners)
cl.stateChanged(ce);
refreshDialog();
}
@Action
public void saveTimeline() {
((TimeStamperApp) getApplication()).saveTimeline();
@ -456,16 +492,13 @@ public class TimeStamperView extends FrameView implements MouseMotionListener {
((TimeStamperApp) getApplication()).loadTimeline(
fileChooser.getSelectedFile().getAbsolutePath());
refreshDialog();
fireChangeEvent();
}
public void refreshDialog() {
Timeline t = ((TimeStamperApp) getApplication()).getActiveTimeline();
Timeline.TimelineMarker lastMarker = t.getLastMarker(new Date());
if (punchcardDisplayDialog != null)
punchcardDisplayDialog.setTimeline(t);
if (lastMarker != null) {
mostRecentTask = lastMarker.getTimestamp();
startTimeLabel.setText(Timeline.shortFormat.format(lastMarker.getTimestamp()));

View File

@ -126,6 +126,14 @@ public class Timeline implements Iterable<Timeline.TimelineMarker> {
return timelineList.iterator();
}
/**
* Write the a representation of the timeline to a stream. This method
* flushes the stream after it finishes writing but does not close the
* stream.
* @param stream An open stream to write the timeline representation to.
* @param timeline The timeline to write.
* @throws java.io.IOException
*/
public static void writeToStream(OutputStream stream, Timeline timeline)
throws IOException {
Writer out = new OutputStreamWriter(stream);
@ -162,6 +170,13 @@ public class Timeline implements Iterable<Timeline.TimelineMarker> {
out.flush();
}
/**
* Write a representation of a timeline to a file.
* @param filename The path to the destination file.
* @param timeline The timeline to write.
* @throws java.io.IOException
* @throws java.io.FileNotFoundException
*/
public static void writeToFile(String filename, Timeline timeline)
throws IOException, FileNotFoundException {
OutputStream out = new FileOutputStream(filename);
@ -169,6 +184,13 @@ public class Timeline implements Iterable<Timeline.TimelineMarker> {
out.close();
}
/**
* Create a <b>Timeline</b> instance from a given stream.
* @param stream The stream to read from.
* @return A new <b>Timeline</b> instance specified by the input stream.
* @throws java.io.IOException
* @throws java.io.FileNotFoundException
*/
public static Timeline readFromStream(InputStream stream)
throws IOException, FileNotFoundException {
Scanner in = new Scanner(stream);
@ -235,6 +257,13 @@ public class Timeline implements Iterable<Timeline.TimelineMarker> {
return timeline;
}
/**
* Create a Timline instance from a file representation.
* @param filename The path of the source file.
* @return The new Timeline instance.
* @throws java.io.IOException
* @throws java.io.FileNotFoundException
*/
public static Timeline readFromFile(String filename)
throws IOException, FileNotFoundException {
InputStream in = new FileInputStream(filename);

View File

@ -27,7 +27,8 @@ import javax.swing.event.ChangeListener;
*
* @author jbernard
*/
public class TimelineDayDisplay extends JComponent implements MouseListener {
public class TimelineDayDisplay extends JComponent implements MouseListener,
ChangeListener {
private class MarkerDisplayEntry {
public Timeline.TimelineMarker marker;
@ -38,13 +39,158 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
public Rectangle bounds;
}
private Timeline timeline;
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 Calendar day = Calendar.getInstance();
private Calendar nextDay = Calendar.getInstance();
private ArrayList<TimeLegendEntry> timeLegendLocations;
private Timeline.TimelineMarker currentMarker;
private ArrayList<ChangeListener> changeListeners = new ArrayList<ChangeListener>();
private Point lastMousePress;
private Font markFont;// = getFont().deriveFont(Font.BOLD);
private Font notesFont;// = getFont();
@ -56,42 +202,64 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
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 long oneDayInMilli = (24 * 60 * 60 * 1000);
private Date rangeStartDate = new Date();
private Date rangeEndDate = new Date();
public TimelineDayDisplay() {
super();
setDay(new Date(), false);
addMouseListener(this);
}
public TimelineDayDisplay(Timeline timeline, Calendar day) {
this();
this.timeline = timeline;
this.day = day;
public TimelineDayDisplay(Calendar day) {
setDay(day.getTime(), false);
addMouseListener(this);
updateMarkers(getGraphics());
}
public void setTimeline(Timeline t) {
timeline = t;
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;
}
public Timeline getTimeline() {
return timeline;
/**
* 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);
}
public void setDay(Calendar d) {
day.setTime(d.getTime());
/**
* 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();
nextDay.setTime(day.getTime());
nextDay.add(Calendar.DAY_OF_YEAR, 1);
updateMarkers(getGraphics());
}
day.add(Calendar.DAY_OF_YEAR, 1);
rangeEndDate = day.getTime();
public Calendar getDay() {
return day;
if (update) updateMarkers(getGraphics());
}
public void setMarkFont(Font f) {
@ -155,11 +323,13 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
}
public void addMarker(Date timestamp, String mark, String notes) {
Timeline timeline = TimeStamperApp.getApplication().getActiveTimeline();
timeline.addMarker(timestamp, mark, notes);
updateMarkers(getGraphics());
}
public void deleteSelectedMarker() {
Timeline timeline = TimeStamperApp.getApplication().getActiveTimeline();
timeline.removeMarker(currentMarker);
updateMarkers(getGraphics());
}
@ -169,25 +339,81 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
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().getActiveTimeline();
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();
// get all relevant markers
Timeline.TimelineMarker tm = timeline.getLastMarker(day.getTime());
// 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
Timeline.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<Timeline.TimelineMarker> itr = timeline.iterator();
while (!itr.next().equals(tm));
ArrayList<Timeline.TimelineMarker> markers = new ArrayList<Timeline.TimelineMarker>();
while (nextDay.getTime().after(tm.getTimestamp())) {
while (rangeEndDate.after(tm.getTimestamp())) {
markers.add(tm);
if (itr.hasNext()) tm = itr.next();
else break;
@ -207,29 +433,35 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
.getStringBounds(markers.get(i).getNotes(), g);
// calculate upper bound
if ((i == 0) && day.getTime().after(markerEntry.marker.getTimestamp())) {
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 {
// calculate time in ms since the beginning of the day
markerEntry.relY = markers.get(i).getTimestamp().getTime()
- day.getTimeInMillis();
// calculate percentage of total time in day
markerEntry.relY /= oneDayInMilli;
// 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) && day.getTime().after(markerEntry.marker.getTimestamp()))
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()
- day.getTimeInMillis();
- rangeStartDate.getTime();
else if (i == markers.size() - 2)
markerEntry.relHeight = nextDay.getTimeInMillis()
- markers.get(i).getTimestamp().getTime();
// 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()
- markers.get(i).getTimestamp().getTime();
markerEntry.relHeight /= oneDayInMilli;
- markerEntry.marker.getTimestamp().getTime();
markerEntry.relHeight /= rangeDiff;
markerEntries.add(markerEntry);
}
@ -253,17 +485,15 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
Rectangle2D stringBounds = getFontMetrics(getFont()).getStringBounds("00:00 ", g);
// draw hour lines
for (int i = 1; i < 24; i++) {
int lineY = (int) Math.round(hourHeight * (double) i);
g.drawLine(canvasBounds.x + (int) stringBounds.getWidth(), canvasBounds.y + lineY,
for (TimeLegendEntry legendEntry : timeLegendLocations) {
g.drawLine(canvasBounds.x + (int) stringBounds.getWidth(),
(int) (canvasBounds.y + (canvasBounds.height * legendEntry.relY)),
canvasBounds.x + canvasBounds.width,
canvasBounds.y + lineY);
(int) (canvasBounds.y + (canvasBounds.height * legendEntry.relY)));
//draw hours labels
if ((hourHeight > (stringBounds.getHeight()) || ((i % 2) == 0))) {
g.drawString(String.format("%1$02d:00", i),
canvasBounds.x + 2, lineY + (int) (stringBounds.getHeight() / 2));
}
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++) {
@ -349,7 +579,9 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
}
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);
@ -357,17 +589,56 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
// should only match one entry
if (absBounds.contains(e.getLocationOnScreen())) {
currentMarker = markerEntry.marker;
repaint();
fireChangeEvent();
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) {
@ -375,4 +646,10 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
public void mouseExited(MouseEvent e) {
}
public void stateChanged(ChangeEvent ce) {
updateMarkers(getGraphics());
repaint();
}
}

View File

@ -2,7 +2,7 @@
Application.name = TimeStamper
Application.title = TimeStamper
Application.version = 1.5
Application.version = 1.7
Application.vendor = Jonathan Bernard
Application.homepage =
Application.description = Simple application used to track activities throughout time.

Binary file not shown.

1
todo.xml Executable file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><TodoList name="TimeStamper Todo List"><Description>Features, bugfixes, things that need to be done.</Description><Owner>TimeStamper Development</Owner><TaskList name="unallocated"><Task created="20081023170946" name="WeekTimelineDisplay" priority="0"><Description>Create a display that shows a full week.</Description></Task><Task created="20081023171011" name="MonthTimelineDisplay" priority="0"><Description>Show a full month.</Description></Task><Task allocated="20081029175454" created="20081029180059" name="Add GUI Action Controlsto DayTimelineDisplay" priority="1"><Description>Allow the GUI actions of the component to be enabled/disabled programatically. For example, add setZoomAllowed(boolean allowed) function to enable/disable the ability to zoom with the mouse.</Description></Task></TaskList><TaskList name="old"><Task allocated="20081023170513" created="20081023221630" finished="20081023235630" name="Auto-scale DatyTimelineDisplay Time Scale" priority="1"><Description/></Task><Task allocated="20081023170513" created="20081023235727" finished="20081023235730" name="Click and Drag Scroll On the DayTimelineDisplay" priority="1"><Description>The user is able to click and drag on the timeline displayed and the timeline follows the mouse.</Description></Task><Task allocated="20081023170513" created="20081023193554" finished="20081023193602" name="Deselect markers in DayTimelineDisplay" priority="1"><Description>Allow the user to select no marker in the DayTimelineDisplay by clicking in a region of the display that is not covered by an activity.</Description></Task><Task allocated="20081023170513" created="20081023170924" finished="20081023221612" name="Zoom for DayTimelineDisplay" priority="1"><Description>When the user clicks and drags, the ui should zoom to the region the user covered in the vertical access. For example, if the display shows a full day and the user clicks in 9:00 and drags to 13:00, the ui should refresh to put 9:00 at the top, 13:00 at the bottom, and re-scale the rest in between.</Description></Task></TaskList><TaskList name="current"><Task allocated="20081029175454" created="20081024010642" finished="20081029175523" name="Update All Displays When One Changes" priority="1"><Description>When you make changes to the DayTimelineDisplay, for example, those changes need to be propogated through the tool back to the main display.</Description></Task></TaskList></TodoList>