diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml
index 0940f1c..c1f155a 100755
--- a/nbproject/private/private.xml
+++ b/nbproject/private/private.xml
@@ -1,12 +1,4 @@
-
- file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/AboutDialog.java
- file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/PunchcardDisplayDialog.java
- file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/TimeStamperApp.java
- file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/TimeStamperView.java
- file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/Timeline.java
- file:/C:/Documents%20and%20Settings/jbernard/My%20Documents/Development/TimeStamper/src/jdbernard/timestamper/TimelineDayDisplay.java
-
diff --git a/nbproject/project.properties b/nbproject/project.properties
index d9d93a2..55844cb 100755
--- a/nbproject/project.properties
+++ b/nbproject/project.properties
@@ -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:
diff --git a/src/jdbernard/timestamper/PunchcardDisplayDialog.form b/src/jdbernard/timestamper/PunchcardDisplayDialog.form
index c36814b..0c94385 100755
--- a/src/jdbernard/timestamper/PunchcardDisplayDialog.form
+++ b/src/jdbernard/timestamper/PunchcardDisplayDialog.form
@@ -48,54 +48,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/jdbernard/timestamper/PunchcardDisplayDialog.java b/src/jdbernard/timestamper/PunchcardDisplayDialog.java
index a5a966a..c0f0644 100755
--- a/src/jdbernard/timestamper/PunchcardDisplayDialog.java
+++ b/src/jdbernard/timestamper/PunchcardDisplayDialog.java
@@ -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 changeListeners =
+ new ArrayList();
+
+ @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);
- markTextField.setText(marker.getMark());
- notesTextArea.setText(marker.getNotes());
+ 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);
+ }
+ });
+ }*/
+
}
diff --git a/src/jdbernard/timestamper/TimeStamperApp.java b/src/jdbernard/timestamper/TimeStamperApp.java
index f007ac8..9e4517c 100755
--- a/src/jdbernard/timestamper/TimeStamperApp.java
+++ b/src/jdbernard/timestamper/TimeStamperApp.java
@@ -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"));
}
diff --git a/src/jdbernard/timestamper/TimeStamperView.java b/src/jdbernard/timestamper/TimeStamperView.java
index d5f1c81..be4ead7 100755
--- a/src/jdbernard/timestamper/TimeStamperView.java
+++ b/src/jdbernard/timestamper/TimeStamperView.java
@@ -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 changeListeners =
+ new ArrayList();
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()));
diff --git a/src/jdbernard/timestamper/Timeline.java b/src/jdbernard/timestamper/Timeline.java
index 992a925..da09ebb 100644
--- a/src/jdbernard/timestamper/Timeline.java
+++ b/src/jdbernard/timestamper/Timeline.java
@@ -126,6 +126,14 @@ public class Timeline implements Iterable {
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 {
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 {
out.close();
}
+ /**
+ * Create a Timeline instance from a given stream.
+ * @param stream The stream to read from.
+ * @return A new Timeline 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 {
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);
diff --git a/src/jdbernard/timestamper/TimelineDayDisplay.java b/src/jdbernard/timestamper/TimelineDayDisplay.java
index 6d34631..3ec239f 100755
--- a/src/jdbernard/timestamper/TimelineDayDisplay.java
+++ b/src/jdbernard/timestamper/TimelineDayDisplay.java
@@ -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 markerEntries;
- private Calendar day = Calendar.getInstance();
- private Calendar nextDay = Calendar.getInstance();
+ private ArrayList timeLegendLocations;
private Timeline.TimelineMarker currentMarker;
private ArrayList changeListeners = new ArrayList();
+ 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 true
,
+ * updateMarkers(getGraphics)
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();
+ timeLegendLocations = new ArrayList();
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 itr = timeline.iterator();
while (!itr.next().equals(tm));
ArrayList markers = new ArrayList();
- 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,
- canvasBounds.x + canvasBounds.width,
- 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,
+ (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,25 +579,66 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
}
public void mouseClicked(MouseEvent e) {
- Point topLeft = getLocationOnScreen();
- for (MarkerDisplayEntry markerEntry : markerEntries) {
- Rectangle absBounds = new Rectangle(markerEntry.bounds);
- absBounds.translate(topLeft.x, topLeft.y);
+ 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;
- repaint();
- fireChangeEvent();
- break;
+ // 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) {
@@ -375,4 +646,10 @@ public class TimelineDayDisplay extends JComponent implements MouseListener {
public void mouseExited(MouseEvent e) {
}
+
+ public void stateChanged(ChangeEvent ce) {
+ updateMarkers(getGraphics());
+ repaint();
+ }
+
}
diff --git a/src/jdbernard/timestamper/resources/TimeStamperApp.properties b/src/jdbernard/timestamper/resources/TimeStamperApp.properties
index 32b4d51..63f8bd3 100755
--- a/src/jdbernard/timestamper/resources/TimeStamperApp.properties
+++ b/src/jdbernard/timestamper/resources/TimeStamperApp.properties
@@ -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.
diff --git a/test-timeline.txt b/test-timeline.txt
deleted file mode 100755
index 1cb120c..0000000
Binary files a/test-timeline.txt and /dev/null differ
diff --git a/todo.xml b/todo.xml
new file mode 100755
index 0000000..70f51ae
--- /dev/null
+++ b/todo.xml
@@ -0,0 +1 @@
+Features, bugfixes, things that need to be done.TimeStamper DevelopmentCreate a display that shows a full week.Show a full month.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.The user is able to click and drag on the timeline displayed and the timeline follows the mouse.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.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.When you make changes to the DayTimelineDisplay, for example, those changes need to be propogated through the tool back to the main display.
\ No newline at end of file