From 1e74123cfed4374b403574b6cd16bd8d4e73bf45 Mon Sep 17 00:00:00 2001
From: Normand Briere <nbriere@noware.ca>
Date: Tue, 10 Jul 2018 22:17:11 -0400
Subject: [PATCH] Refactoring.

---
 timeflow/data/time/TimeUnit.java              |  472 ++++++-----
 timeflow/app/TimeflowApp.java                 |   12 
 timeflow/views/ListView.java                  |  499 ++++++------
 timeflow/data/time/RoughTime.java             |    3 
 timeflow/vis/Mouseover.java                   |  176 ++--
 timeflow/format/file/HtmlFormat.java          |  264 +++---
 timeflow/vis/timeline/AxisTicMarks.java       |  220 ++--
 timeflow/views/TimelineView.java              |   94 +
 timeflow/views/AbstractVisualizationView.java |    8 
 timeflow/vis/VisualAct.java                   |    1 
 timeflow/app/ui/LinkTabPane.java              |  414 +++++-----
 timeflow/vis/timeline/AxisRenderer.java       |  151 +-
 12 files changed, 1,220 insertions(+), 1,094 deletions(-)

diff --git a/timeflow/app/TimeflowApp.java b/timeflow/app/TimeflowApp.java
index 4826184..1bdc790 100755
--- a/timeflow/app/TimeflowApp.java
+++ b/timeflow/app/TimeflowApp.java
@@ -35,7 +35,8 @@
         public JMenu filterMenu;
         JMenuItem save = new JMenuItem("Save");
         FilterControlPanel filterControlPanel;
-        LinkTabPane leftPanel;
+        //LinkTabPane
+                JTabbedPane leftPanel;
         TFListener filterMenuMaker = new TFListener()
         {
                 @Override
@@ -90,14 +91,15 @@
 
                 // left tab area, with vertical gray divider.
                 JPanel leftHolder = new JPanel();
-                container.add(leftHolder, BorderLayout.WEST);
+                container.add(leftHolder, BorderLayout.EAST); // WEST);
 
                 leftHolder.setLayout(new BorderLayout());
                 JPanel pad = new Pad(3, 3);
                 pad.setBackground(Color.gray);
                 leftHolder.add(pad, BorderLayout.EAST);
 
-                leftPanel = new LinkTabPane();//JTabbedPane();
+                leftPanel = new //LinkTabPane();
+                                JTabbedPane();
                 leftHolder.add(leftPanel, BorderLayout.CENTER);
 
                 JPanel configPanel = new JPanel();
@@ -112,9 +114,9 @@
                 configPanel.add(legend, BorderLayout.CENTER);
                 legend.add(new SizeLegendPanel(model), BorderLayout.NORTH);
                 legend.add(new ColorLegendPanel(model), BorderLayout.CENTER);
-                leftPanel.addTab(configPanel, "Display", true);
+                leftPanel.add(configPanel, "Display"); //, true);
 
-                leftPanel.addTab(filterControlPanel, "Filter", true);
+                leftPanel.add(filterControlPanel, "Filter"); //, true);
 
                 // center tab area
 
diff --git a/timeflow/app/ui/LinkTabPane.java b/timeflow/app/ui/LinkTabPane.java
index 1f4ee01..911461c 100755
--- a/timeflow/app/ui/LinkTabPane.java
+++ b/timeflow/app/ui/LinkTabPane.java
@@ -8,201 +8,225 @@
 import java.util.*;
 
 // custom JTabbedPane-like thing.
-public class LinkTabPane extends JPanel {
-	
-	ArrayList<String> tabNames=new ArrayList<String>();
-	HashMap<String, JComponent> tabMap=new HashMap<String, JComponent>();
-	String currentName;
-	CardLayout cards=new CardLayout();
-	JPanel center=new JPanel();
-	LinkTop top=new LinkTop();
-	
-	public LinkTabPane()
-	{
-		setBackground(Color.white);
-		setLayout(new BorderLayout());
-		add(top, BorderLayout.NORTH);
-		add(center, BorderLayout.CENTER);
-		center.setLayout(cards);
-		top.addMouseListener(new MouseAdapter() {
-			@Override
-			public void mousePressed(MouseEvent e) {
-				String s=top.getName(e.getX());
-				if (s!=null)
-				{
-					String old=currentName;
-					setCurrentName(s);
-					firePropertyChange("tab", old, s);
-				}
-			}});
-	}
-	
-	public String getTitleAt(int i)
-	{
-		return tabNames.get(i);
-	}
-	
-	public void setSelectedIndex(int i)
-	{
-		setCurrentName(getTitleAt(i));
-	}
-	
-	public void addTab(JComponent component, String name, boolean left)
-	{
-		tabNames.add(name);
-		tabMap.put(name, component);
-		center.add(component, name);
-		top.addName(name, left);
-		repaint();
-		if (currentName==null)
-			currentName=name;
-	}
-	
-	public String getCurrentName()
-	{
-		return currentName;
-	}
-	
-	public void setCurrentName(final String currentName)
-	{
-		this.currentName=currentName;
-		top.repaint();
-		SwingUtilities.invokeLater(new Runnable() {public void run() {cards.show(center, currentName);}});
+public class LinkTabPane extends JPanel
+{
 
-	}
-	
-	class LinkTop extends JPanel
-	{
-		int left, right;
-		ArrayList<HotLink> leftHots=new ArrayList<HotLink>();
-		ArrayList<HotLink> rightHots=new ArrayList<HotLink>();
-		Font font=new Font("Verdana", Font.PLAIN, 14);
-		FontMetrics fm=getFontMetrics(font);
-		
-		LinkTop()
-		{
-			setLayout(new FlowLayout(FlowLayout.LEFT));
-			setBackground(new Color(220,220,220));
-		}
-		
-		public void paintComponent(Graphics g1)
-		{
-			int w=getSize().width, h=getSize().height;
-			Graphics2D g=(Graphics2D)g1;
-			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-			g.setColor(getBackground());
-			g.fillRect(0,0,w,h);
-			g.setColor(Color.gray);
-			for (int i=0; i<2; i++)
-			{
-				g.drawLine(0,i,w,i);
-				g.drawLine(0,h-1-i,w,h-1-i);
-			}
-			
-			for (HotLink hot: leftHots)
-			{
-				draw(g, hot, h, 0);
-			}
-			
-			for (HotLink hot: rightHots)
-			{
-				draw(g, hot, h, w);
-			}
-			
-			for (int i=0; i<leftHots.size(); i++)
-			{
-				
-				if (i<leftHots.size()-1)
-				{
-					HotLink hot=leftHots.get(i);
-					for (int j=0; j<1; j++)
-						g.drawLine(hot.x+hot.width-1-j, 7, hot.x+hot.width-1-j, h-7);
-				}
-			}
-			
-			for (int i=0; i<rightHots.size(); i++)
-			{
-				
-				if (i<rightHots.size()-1)
-				{
-					HotLink hot=rightHots.get(i);
-					for (int j=0; j<1; j++)
-						g.drawLine(hot.x+w-1-j, 7, hot.x+w-1-j, h-7);
-				}
-			}
-		}
-		
-		void draw(Graphics g, HotLink hot, int h, int dx)
-		{
-			int x=hot.x+dx;
-			if (hot.s.equals(currentName))
-			{
-				g.setColor(Color.lightGray);
-				g.fillRect(x,2,hot.width,h-4);
-				g.setColor(Color.gray);
-				g.drawLine(x-1, 0, x-1, h);
-				g.drawLine(x+hot.width-1, 0, x+hot.width-1, h);
-			}
-			g.setColor(Color.darkGray);
-			g.setFont(font);
-			int sw=fm.stringWidth(hot.s);
-			g.drawString(hot.s, x+(hot.width-sw)/2, h-10);
-			
-		}
-		
-		String getName(int x)
-		{
-			for (HotLink h: leftHots)
-			{
-				if (h.x<=x && h.x+h.width>x)
-					return h.s;
-			}
-			for (HotLink h: rightHots)
-			{
-				int w=getSize().width;
-				if (h.x+w<=x && h.x+h.width+w>x)
-					return h.s;
-			}
+        ArrayList<String> tabNames = new ArrayList<String>();
+        HashMap<String, JComponent> tabMap = new HashMap<String, JComponent>();
+        String currentName;
+        CardLayout cards = new CardLayout();
+        JPanel center = new JPanel();
+        LinkTop top = new LinkTop();
 
-			if (leftHots.size()>0)
-				return leftHots.get(leftHots.size()-1).s;
-			if (rightHots.size()>0)
-				return rightHots.get(0).s;
-			return null;
-		}
-		
-	    void addName(String name, boolean leftward)
-		{
-	    	if (leftward)
-	    	{
-	    		int x=right;
-		    	int w=fm.stringWidth(name)+24;
-		    	leftHots.add(new HotLink(name, x, 0, w, 30));
-		    	right+=w;
-	    	}
-	    	else
-	    	{
-	    		int x=left;
-		    	int w=fm.stringWidth(name)+24;
-		    	rightHots.add(new HotLink(name, x-w, 0, w, 30));
-		    	left-=w;		
-	    	}
-		}
-	    
-	    class HotLink extends Rectangle
-	    {
-	    	String s;
-	    	HotLink(String s, int x, int y, int w, int h)
-	    	{
-	    		super(x,y,w,h);
-	    		this.s=s;
-	    	}
-	    }
-		
-		public Dimension getPreferredSize()
-		{
-			return new Dimension(30,30);
-		}
-	}
-	
+        public LinkTabPane()
+        {
+                setBackground(Color.white);
+                setLayout(new BorderLayout());
+                add(top, BorderLayout.NORTH);
+                add(center, BorderLayout.CENTER);
+                center.setLayout(cards);
+                top.addMouseListener(new MouseAdapter()
+                {
+
+                        @Override
+                        public void mousePressed(MouseEvent e)
+                        {
+                                String s = top.getName(e.getX());
+                                if (s != null)
+                                {
+                                        String old = currentName;
+                                        setCurrentName(s);
+                                        firePropertyChange("tab", old, s);
+                                }
+                        }
+                });
+        }
+
+        public String getTitleAt(int i)
+        {
+                return tabNames.get(i);
+        }
+
+        public void setSelectedIndex(int i)
+        {
+                setCurrentName(getTitleAt(i));
+        }
+
+        public void addTab(JComponent component, String name, boolean left)
+        {
+                tabNames.add(name);
+                tabMap.put(name, component);
+                center.add(component, name);
+                top.addName(name, left);
+                repaint();
+                if (currentName == null)
+                {
+                        currentName = name;
+                }
+        }
+
+        public String getCurrentName()
+        {
+                return currentName;
+        }
+
+        public void setCurrentName(final String currentName)
+        {
+                this.currentName = currentName;
+                top.repaint();
+                SwingUtilities.invokeLater(new Runnable()
+                {
+                        public void run()
+                        {
+                                cards.show(center, currentName);
+                        }
+                });
+
+        }
+
+        class LinkTop extends JPanel
+        {
+                int left, right;
+                ArrayList<HotLink> leftHots = new ArrayList<HotLink>();
+                ArrayList<HotLink> rightHots = new ArrayList<HotLink>();
+                Font font = new Font("Verdana", Font.PLAIN, 14);
+                FontMetrics fm = getFontMetrics(font);
+
+                LinkTop()
+                {
+                        setLayout(new FlowLayout(FlowLayout.LEFT));
+                        setBackground(new Color(220, 220, 220));
+                }
+
+                public void paintComponent(Graphics g1)
+                {
+                        int w = getSize().width, h = getSize().height;
+                        Graphics2D g = (Graphics2D) g1;
+                        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+                        g.setColor(getBackground());
+                        g.fillRect(0, 0, w, h);
+                        g.setColor(Color.gray);
+                        for (int i = 0; i < 2; i++)
+                        {
+                                g.drawLine(0, i, w, i);
+                                g.drawLine(0, h - 1 - i, w, h - 1 - i);
+                        }
+
+                        for (HotLink hot : leftHots)
+                        {
+                                draw(g, hot, h, 0);
+                        }
+
+                        for (HotLink hot : rightHots)
+                        {
+                                draw(g, hot, h, w);
+                        }
+
+                        for (int i = 0; i < leftHots.size(); i++)
+                        {
+
+                                if (i < leftHots.size() - 1)
+                                {
+                                        HotLink hot = leftHots.get(i);
+                                        for (int j = 0; j < 1; j++)
+                                        {
+                                                g.drawLine(hot.x + hot.width - 1 - j, 7, hot.x + hot.width - 1 - j, h - 7);
+                                        }
+                                }
+                        }
+
+                        for (int i = 0; i < rightHots.size(); i++)
+                        {
+
+                                if (i < rightHots.size() - 1)
+                                {
+                                        HotLink hot = rightHots.get(i);
+                                        for (int j = 0; j < 1; j++)
+                                        {
+                                                g.drawLine(hot.x + w - 1 - j, 7, hot.x + w - 1 - j, h - 7);
+                                        }
+                                }
+                        }
+                }
+
+                void draw(Graphics g, HotLink hot, int h, int dx)
+                {
+                        int x = hot.x + dx;
+                        if (hot.s.equals(currentName))
+                        {
+                                g.setColor(Color.lightGray);
+                                g.fillRect(x, 2, hot.width, h - 4);
+                                g.setColor(Color.gray);
+                                g.drawLine(x - 1, 0, x - 1, h);
+                                g.drawLine(x + hot.width - 1, 0, x + hot.width - 1, h);
+                        }
+                        g.setColor(Color.darkGray);
+                        g.setFont(font);
+                        int sw = fm.stringWidth(hot.s);
+                        g.drawString(hot.s, x + (hot.width - sw) / 2, h - 10);
+
+                }
+
+                String getName(int x)
+                {
+                        for (HotLink h : leftHots)
+                        {
+                                if (h.x <= x && h.x + h.width > x)
+                                {
+                                        return h.s;
+                                }
+                        }
+                        for (HotLink h : rightHots)
+                        {
+                                int w = getSize().width;
+                                if (h.x + w <= x && h.x + h.width + w > x)
+                                {
+                                        return h.s;
+                                }
+                        }
+
+                        if (leftHots.size() > 0)
+                        {
+                                return leftHots.get(leftHots.size() - 1).s;
+                        }
+                        if (rightHots.size() > 0)
+                        {
+                                return rightHots.get(0).s;
+                        }
+                        return null;
+                }
+
+                void addName(String name, boolean leftward)
+                {
+                        if (leftward)
+                        {
+                                int x = right;
+                                int w = fm.stringWidth(name) + 24;
+                                leftHots.add(new HotLink(name, x, 0, w, 30));
+                                right += w;
+                        } else
+                        {
+                                int x = left;
+                                int w = fm.stringWidth(name) + 24;
+                                rightHots.add(new HotLink(name, x - w, 0, w, 30));
+                                left -= w;
+                        }
+                }
+
+                class HotLink extends Rectangle
+                {
+                        String s;
+
+                        HotLink(String s, int x, int y, int w, int h)
+                        {
+                                super(x, y, w, h);
+                                this.s = s;
+                        }
+                }
+
+                public Dimension getPreferredSize()
+                {
+                        return new Dimension(30, 30);
+                }
+        }
 }
diff --git a/timeflow/data/time/RoughTime.java b/timeflow/data/time/RoughTime.java
index 9cadc5a..55b4bbb 100755
--- a/timeflow/data/time/RoughTime.java
+++ b/timeflow/data/time/RoughTime.java
@@ -100,7 +100,8 @@
 	
 	public String format()
 	{
-		return units.formatFull(time);
+		//return units.formatFull(time);
+		return TimeUnit.SECOND.formatFull(time);
 	}
 	
 	public static int compare(RoughTime t1, RoughTime t2)
diff --git a/timeflow/data/time/TimeUnit.java b/timeflow/data/time/TimeUnit.java
index 760b3be..4032258 100755
--- a/timeflow/data/time/TimeUnit.java
+++ b/timeflow/data/time/TimeUnit.java
@@ -3,241 +3,255 @@
 import java.util.*;
 import java.text.*;
 
-public class TimeUnit {
+public class TimeUnit
+{
 
-	public static final TimeUnit YEAR=new TimeUnit("Years", Calendar.YEAR, 365*24*60*60*1000L, "yyyy", "yyyy");
-	public static final TimeUnit MONTH=new TimeUnit("Months", Calendar.MONTH, 30*24*60*60*1000L, "MMM", "MMM yyyy");
-	public static final TimeUnit WEEK=new TimeUnit("Weeks", Calendar.WEEK_OF_YEAR, 7*24*60*60*1000L, "d", "MMM d yyyy");
-	public static final TimeUnit DAY=new TimeUnit("Days", Calendar.DAY_OF_MONTH, 24*60*60*1000L, "d", "MMM d yyyy");
-	public static final TimeUnit DAY_OF_WEEK=new TimeUnit("Days", Calendar.DAY_OF_WEEK, 24*60*60*1000L, "d", "MMM d yyyy");
-	public static final TimeUnit HOUR=new TimeUnit("Hours", Calendar.HOUR_OF_DAY, 60*60*1000L, "kk:mm", "MMM d yyyy kk:mm");
-	public static final TimeUnit MINUTE=new TimeUnit("Minutes", Calendar.MINUTE, 60*1000L, ":mm", "MMM d yyyy kk:mm");
-	public static final TimeUnit SECOND=new TimeUnit("Seconds", Calendar.SECOND, 1000L, ":ss", "MMM d yyyy kk:mm:ss");
-	public static final TimeUnit DECADE=multipleYears(10);
-	public static final TimeUnit CENTURY=multipleYears(100);
-	
-	private static final double DAY_SIZE=24*60*60*1000L;
-	
-	private int quantity;	
-	private long roughSize;
-	private SimpleDateFormat format, fullFormat;
-	private String name;
-	private int calendarCode;
-	
-	private TimeUnit()
-	{		
-	}
-	
-	private TimeUnit(String name, int calendarCode, long roughSize, String formatPattern, String fullFormatPattern)
-	{
-		this.name=name;
-		this.calendarCode=calendarCode;
-		this.roughSize=roughSize;
-		format=new SimpleDateFormat(formatPattern);
-		fullFormat=new SimpleDateFormat(fullFormatPattern);
-		quantity=1;
-	}
-	
-	public String toString()
-	{
-		return "[TimeUnit: "+name+"]";
-	}
+        public static final TimeUnit YEAR = new TimeUnit("Years", Calendar.YEAR, 365 * 24 * 60 * 60 * 1000L, "yyyy", "yyyy");
+        public static final TimeUnit MONTH = new TimeUnit("Months", Calendar.MONTH, 30 * 24 * 60 * 60 * 1000L, "MMM", "MMM yyyy");
+        public static final TimeUnit WEEK = new TimeUnit("Weeks", Calendar.WEEK_OF_YEAR, 7 * 24 * 60 * 60 * 1000L, "d", "MMM d yyyy");
+        public static final TimeUnit DAY = new TimeUnit("Days", Calendar.DAY_OF_MONTH, 24 * 60 * 60 * 1000L, "d", "MMM d yyyy");
+        public static final TimeUnit DAY_OF_WEEK = new TimeUnit("Days", Calendar.DAY_OF_WEEK, 24 * 60 * 60 * 1000L, "d", "MMM d yyyy");
+        public static final TimeUnit HOUR = new TimeUnit("Hours", Calendar.HOUR_OF_DAY, 60 * 60 * 1000L, "kk:mm", "MMM d yyyy kk:mm");
+        public static final TimeUnit MINUTE = new TimeUnit("Minutes", Calendar.MINUTE, 60 * 1000L, ":mm", "MMM d yyyy kk:mm");
+        public static final TimeUnit SECOND = new TimeUnit("Seconds", Calendar.SECOND, 1000L, ":ss", "MMM d yyyy kk:mm:ss");
+        public static final TimeUnit DECADE = multipleYears(10);
+        public static final TimeUnit CENTURY = multipleYears(100);
+        private static final double DAY_SIZE = 24 * 60 * 60 * 1000L;
+        private int quantity;
+        private long roughSize;
+        private SimpleDateFormat format, fullFormat;
+        private String name;
+        private int calendarCode;
 
-	public static TimeUnit multipleYears(int numYears)
-	{
-		TimeUnit t=new TimeUnit();
-		t.name=numYears+" Years";
-		t.calendarCode=Calendar.YEAR;
-		t.roughSize=YEAR.roughSize*numYears;
-		t.format=YEAR.format;
-		t.fullFormat=YEAR.fullFormat;
-		t.quantity=numYears;
-		return t;
-	}
-	
-	public static TimeUnit multipleWeeks(int num)
-	{
-		TimeUnit t=new TimeUnit();
-		t.name=num+" Weeks";
-		t.calendarCode=Calendar.WEEK_OF_YEAR;
-		t.roughSize=WEEK.roughSize*num;
-		t.format=WEEK.format;
-		t.fullFormat=WEEK.fullFormat;
-		t.quantity=num;
-		return t;
-	}
-	
-	public TimeUnit times(int quantity)
-	{
-		TimeUnit t=new TimeUnit();
-		t.name=quantity+" "+this.name;
-		t.calendarCode=this.calendarCode;
-		t.roughSize=this.roughSize*quantity;
-		t.format=this.format;
-		t.fullFormat=this.fullFormat;
-		t.quantity=quantity;
-		return t;
-		
-	}
+        private TimeUnit()
+        {
+        }
 
-	
-	public int numUnitsIn(TimeUnit u)
-	{
-		return (int)Math.round(u.getRoughSize()/(double)getRoughSize());
-	}
-	
-	public boolean isDayOrLess()
-	{
-		return roughSize <= 24*60*60*1000L;
-	}
-	
-	public RoughTime roundDown(long timestamp)
-	{
-		return round(timestamp, false);
-	}
-	
-	public RoughTime roundUp(long timestamp)
-	{
-		return round(timestamp, true);
-	}
-	
-	private static final int[] calendarUnits={Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH, Calendar.MONTH, Calendar.YEAR};
-	public RoughTime round(long timestamp, boolean up)
-	{
-		Calendar c=TimeUtils.cal(timestamp);
-		
-		if (calendarCode==Calendar.WEEK_OF_YEAR )
-		{
-			c.set(Calendar.DAY_OF_WEEK, c.getMinimum(Calendar.DAY_OF_WEEK));
-		}
-		else
-		{
-		
-			// set to minimum all fields of finer granularity.
-			int roundingCode=calendarCode;
-			if (calendarCode==Calendar.WEEK_OF_YEAR || calendarCode==Calendar.DAY_OF_WEEK)
-				roundingCode=Calendar.DAY_OF_MONTH;
-			for (int i=0; i<calendarUnits.length; i++)
-			{
-				if (calendarUnits[i]==roundingCode)
-					break;
-				if (i==calendarUnits.length-1)
-					throw new IllegalArgumentException("Unsupported Calendar Unit: "+calendarCode);
-				c.set(calendarUnits[i], c.getMinimum(calendarUnits[i]));
-			}
-			if (quantity>1)
-			{
-				c.set(calendarCode, quantity*(c.get(calendarCode)/quantity));
-			}
-		}
-		
-		// if rounding up, then add a unit at current granularity.
-		if (up)
-			c.add(calendarCode, quantity);
-		
-		return new RoughTime(c.getTimeInMillis(), this);
-	}
-	
-	public int get(long timestamp)
-	{
-		Calendar c= TimeUtils.cal(timestamp);
-		int n=c.get(calendarCode);
-		return quantity==1 ? n : n%quantity;
-	}
-	
-	public void addTo(RoughTime r)
-	{
-		addTo(r,1);
-	}
-	
-	public void addTo(RoughTime r, int times)
-	{
-		Calendar c=TimeUtils.cal(r.getTime());
-		c.add(calendarCode, quantity*times);
-		r.setTime(c.getTimeInMillis());
-	}
-	
-	// Finding the difference between two dates, in a given unit of time,
-	// is much subtler than you'd think! And annoyingly, the Calendar class does not do
-	// this for you, even though it actually "knows" how to do so since it
-	// can add fields.
-	//
-	// The most vexing problem is dealing with daylight savings time,
-	// which means that one day a year has 23 hours and one day has 25 hours.
-	// We also have to handle the fact that months and years aren't constant lengths.
-	//
-	// Rather than write all this ourselves, in this code we
-	// use the Calendar class to do the heavy lifting.
-	public long difference(long x, long y)
-	{
-		// If this is not one of the hard cases,
-		// just divide the timespan by the length of time unit.
-		// Note that we're not worrying about hours and daylight savings time.
-		if (calendarCode!=Calendar.YEAR && calendarCode!=Calendar.MONTH && 
-		   calendarCode!=Calendar.DAY_OF_MONTH && calendarCode!=Calendar.DAY_OF_WEEK &&
-		   calendarCode!=Calendar.WEEK_OF_YEAR)
-		{
-			return (x-y)/roughSize;
-		}
-			
-		Calendar c1=TimeUtils.cal(x), c2=TimeUtils.cal(y); 
-		int diff=0;
-		switch (calendarCode)
-		{
-			case Calendar.YEAR:
-				return (c1.get(Calendar.YEAR)-c2.get(Calendar.YEAR))/quantity;
-				
-			case Calendar.MONTH:
-				diff= 12*(c1.get(Calendar.YEAR)-c2.get(Calendar.YEAR))+
-				              c1.get(Calendar.MONTH)-c2.get(Calendar.MONTH);
-				return diff/quantity;
-				
-			case Calendar.DAY_OF_MONTH:
-			case Calendar.DAY_OF_WEEK:
-			case Calendar.DAY_OF_YEAR:
-			case Calendar.WEEK_OF_MONTH:
-			case Calendar.WEEK_OF_YEAR:
-				// This is ugly, but believe me, it beats the alternative methods :-)
-				// We use the Calendar class's knowledge of daylight savings time.
-				// and also the fact that if we calculate this naively, then we aren't going
-				// to be off by more than one in either direction.
-				int naive=(int)Math.round((x-y)/(double)roughSize);
-				c2.add(calendarCode, naive*quantity);
-				if (c1.get(calendarCode)==c2.get(calendarCode))
-					return naive/quantity;
-				c2.add(calendarCode, quantity);
-				if (c1.get(calendarCode)==c2.get(calendarCode))
-					return naive/quantity+1;
-				return naive/quantity-1;
-		}
-		throw new IllegalArgumentException("Unexpected calendar code: "+calendarCode);
-	}
+        private TimeUnit(String name, int calendarCode, long roughSize, String formatPattern, String fullFormatPattern)
+        {
+                this.name = name;
+                this.calendarCode = calendarCode;
+                this.roughSize = roughSize;
+                format = new SimpleDateFormat(formatPattern);
+                fullFormat = new SimpleDateFormat(fullFormatPattern);
+                quantity = 1;
+        }
 
-	public long approxNumInRange(long start, long end)
-	{
-		return 1+(end-start)/roughSize;
-	}
-	
-	public long getRoughSize() {
-		return roughSize;
-	}
+        public String toString()
+        {
+                return "[TimeUnit: " + name + "]";
+        }
 
-	public String format(Date date)
-	{
-		return format.format(date);
-	}
+        public static TimeUnit multipleYears(int numYears)
+        {
+                TimeUnit t = new TimeUnit();
+                t.name = numYears + " Years";
+                t.calendarCode = Calendar.YEAR;
+                t.roughSize = YEAR.roughSize * numYears;
+                t.format = YEAR.format;
+                t.fullFormat = YEAR.fullFormat;
+                t.quantity = numYears;
+                return t;
+        }
 
-	public String formatFull(Date date)
-	{
-		return fullFormat.format(date);
-	}
+        public static TimeUnit multipleWeeks(int num)
+        {
+                TimeUnit t = new TimeUnit();
+                t.name = num + " Weeks";
+                t.calendarCode = Calendar.WEEK_OF_YEAR;
+                t.roughSize = WEEK.roughSize * num;
+                t.format = WEEK.format;
+                t.fullFormat = WEEK.fullFormat;
+                t.quantity = num;
+                return t;
+        }
 
-	public String formatFull(long timestamp)
-	{
-		return fullFormat.format(new Date(timestamp));
-	}
+        public TimeUnit times(int quantity)
+        {
+                TimeUnit t = new TimeUnit();
+                t.name = quantity + " " + this.name;
+                t.calendarCode = this.calendarCode;
+                t.roughSize = this.roughSize * quantity;
+                t.format = this.format;
+                t.fullFormat = this.fullFormat;
+                t.quantity = quantity;
+                return t;
 
-	public String getName() {
-		return name;
-	}
+        }
+
+        public int numUnitsIn(TimeUnit u)
+        {
+                return (int) Math.round(u.getRoughSize() / (double) getRoughSize());
+        }
+
+        public boolean isDayOrLess()
+        {
+                return roughSize <= 24 * 60 * 60 * 1000L;
+        }
+
+        public RoughTime roundDown(long timestamp)
+        {
+                return round(timestamp, false);
+        }
+
+        public RoughTime roundUp(long timestamp)
+        {
+                return round(timestamp, true);
+        }
+        private static final int[] calendarUnits =
+        {
+                Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH, Calendar.MONTH, Calendar.YEAR
+        };
+
+        public RoughTime round(long timestamp, boolean up)
+        {
+                Calendar c = TimeUtils.cal(timestamp);
+
+                if (calendarCode == Calendar.WEEK_OF_YEAR)
+                {
+                        c.set(Calendar.DAY_OF_WEEK, c.getMinimum(Calendar.DAY_OF_WEEK));
+                } else
+                {
+
+                        // set to minimum all fields of finer granularity.
+                        int roundingCode = calendarCode;
+                        if (calendarCode == Calendar.WEEK_OF_YEAR || calendarCode == Calendar.DAY_OF_WEEK)
+                        {
+                                roundingCode = Calendar.DAY_OF_MONTH;
+                        }
+                        for (int i = 0; i < calendarUnits.length; i++)
+                        {
+                                if (calendarUnits[i] == roundingCode)
+                                {
+                                        break;
+                                }
+                                if (i == calendarUnits.length - 1)
+                                {
+                                        throw new IllegalArgumentException("Unsupported Calendar Unit: " + calendarCode);
+                                }
+                                c.set(calendarUnits[i], c.getMinimum(calendarUnits[i]));
+                        }
+                        if (quantity > 1)
+                        {
+                                c.set(calendarCode, quantity * (c.get(calendarCode) / quantity));
+                        }
+                }
+
+                // if rounding up, then add a unit at current granularity.
+                if (up)
+                {
+                        c.add(calendarCode, quantity);
+                }
+
+                return new RoughTime(c.getTimeInMillis(), this);
+        }
+
+        public int get(long timestamp)
+        {
+                Calendar c = TimeUtils.cal(timestamp);
+                int n = c.get(calendarCode);
+                return quantity == 1 ? n : n % quantity;
+        }
+
+        public void addTo(RoughTime r)
+        {
+                addTo(r, 1);
+        }
+
+        public void addTo(RoughTime r, int times)
+        {
+                Calendar c = TimeUtils.cal(r.getTime());
+                c.add(calendarCode, quantity * times);
+                r.setTime(c.getTimeInMillis());
+        }
+
+        // Finding the difference between two dates, in a given unit of time,
+        // is much subtler than you'd think! And annoyingly, the Calendar class does not do
+        // this for you, even though it actually "knows" how to do so since it
+        // can add fields.
+        //
+        // The most vexing problem is dealing with daylight savings time,
+        // which means that one day a year has 23 hours and one day has 25 hours.
+        // We also have to handle the fact that months and years aren't constant lengths.
+        //
+        // Rather than write all this ourselves, in this code we
+        // use the Calendar class to do the heavy lifting.
+        public long difference(long x, long y)
+        {
+                // If this is not one of the hard cases,
+                // just divide the timespan by the length of time unit.
+                // Note that we're not worrying about hours and daylight savings time.
+                if (calendarCode != Calendar.YEAR && calendarCode != Calendar.MONTH
+                        && calendarCode != Calendar.DAY_OF_MONTH && calendarCode != Calendar.DAY_OF_WEEK
+                        && calendarCode != Calendar.WEEK_OF_YEAR)
+                {
+                        return (x - y) / roughSize;
+                }
+
+                Calendar c1 = TimeUtils.cal(x), c2 = TimeUtils.cal(y);
+                int diff = 0;
+                switch (calendarCode)
+                {
+                        case Calendar.YEAR:
+                                return (c1.get(Calendar.YEAR) - c2.get(Calendar.YEAR)) / quantity;
+
+                        case Calendar.MONTH:
+                                diff = 12 * (c1.get(Calendar.YEAR) - c2.get(Calendar.YEAR))
+                                        + c1.get(Calendar.MONTH) - c2.get(Calendar.MONTH);
+                                return diff / quantity;
+
+                        case Calendar.DAY_OF_MONTH:
+                        case Calendar.DAY_OF_WEEK:
+                        case Calendar.DAY_OF_YEAR:
+                        case Calendar.WEEK_OF_MONTH:
+                        case Calendar.WEEK_OF_YEAR:
+                                // This is ugly, but believe me, it beats the alternative methods :-)
+                                // We use the Calendar class's knowledge of daylight savings time.
+                                // and also the fact that if we calculate this naively, then we aren't going
+                                // to be off by more than one in either direction.
+                                int naive = (int) Math.round((x - y) / (double) roughSize);
+                                c2.add(calendarCode, naive * quantity);
+                                if (c1.get(calendarCode) == c2.get(calendarCode))
+                                {
+                                        return naive / quantity;
+                                }
+                                c2.add(calendarCode, quantity);
+                                if (c1.get(calendarCode) == c2.get(calendarCode))
+                                {
+                                        return naive / quantity + 1;
+                                }
+                                return naive / quantity - 1;
+                }
+                throw new IllegalArgumentException("Unexpected calendar code: " + calendarCode);
+        }
+
+        public long approxNumInRange(long start, long end)
+        {
+                return 1 + (end - start) / roughSize;
+        }
+
+        public long getRoughSize()
+        {
+                return roughSize;
+        }
+
+        public String format(Date date)
+        {
+                return format.format(date);
+        }
+
+        public String formatFull(Date date)
+        {
+                return fullFormat.format(date);
+        }
+
+        public String formatFull(long timestamp)
+        {
+                return fullFormat.format(new Date(timestamp));
+        }
+
+        public String getName()
+        {
+                return name;
+        }
 }
diff --git a/timeflow/format/file/HtmlFormat.java b/timeflow/format/file/HtmlFormat.java
index cf50499..3792f28 100755
--- a/timeflow/format/file/HtmlFormat.java
+++ b/timeflow/format/file/HtmlFormat.java
@@ -9,135 +9,145 @@
 
 public class HtmlFormat implements Export
 {
-	TFModel model;
-	java.util.List<Field> fields;
-	Field title;
-	
-	public HtmlFormat() {}
-	
-	public HtmlFormat(TFModel model)
-	{
-		setModel(model);
-	}
-	
-	public void setModel(TFModel model)
-	{
-		this.model=model;
-		fields=model.getDB().getFields();
-		title=model.getDB().getField(VirtualField.LABEL);
-	}
-	
-	@Override
-	public void export(TFModel model, BufferedWriter out) throws Exception {
-		setModel(model);
-		out.write(makeHeader());
-		for (Act a: model.getDB())
-			out.write(makeItem(a));
-		out.write(makeFooter());
-		out.flush();
-	}
-	
-	public void append(ActList acts, int start, int end, StringBuffer b)
-	{
-		for (int i=start; i<end; i++)
-		{
-			Act a=acts.get(i);
-			b.append(makeItem(a,i));
-		}
-	}
-	
-	private String makeItem(Act act)
-	{
-		return makeItem(act, -1);
-	}
-	
-	public String makeItem(Act act, int id)
-	{
-		StringBuffer page=new StringBuffer();
-		
-		
-		page.append("<tr><td valign=top align=left width=200><b>");
-		if (title!=null)
-		{
-			Field f=model.getColorField();
-			Color c=Color.black;
-			if (f!=null)
-			{
-				if (f.getType()==String.class)
-					c=model.getDisplay().makeColor(act.getString(f));
-				else
-				{
-					String[] tags=act.getTextList(f);
-					if (tags.length==0)
-						c=Color.gray;
-					else
-						c=model.getDisplay().makeColor(tags[0]);
-				}
-			}
-			
-			page.append("<font size=+1 color="+htmlColor(c)+">"+act.getString(title)+"</font><br>");
-		}
-		
-		Field startField=model.getDB().getField(VirtualField.START);
-		
-		if (startField!=null)
-		{
-			page.append("<font color=#999999>"+model.getDisplay().format(
-				act.getTime(startField))+"</font>");
-		}
-		page.append("</b><br>");
-		if (id>=0)
-			page.append("<a href=\"e"+id+"\">EDIT</a>");
-		page.append("<br></td><td valign=top>");
-		for (Field f: fields)
-		{
-			page.append("<b><font color=#003399>"+f.getName()+"</font></b>&nbsp;&nbsp;");
-			Object val=act.get(f);
-			if (val instanceof URL)
-			{
-				page.append("<a href=\""+val+"\">"+val+"</a>");
-			}
-			else
-				page.append(model.getDisplay().toString(val));
-			page.append("<br>");
+        TFModel model;
+        java.util.List<Field> fields;
+        Field title;
 
-		}
-		page.append("<br></td></tr>");
+        public HtmlFormat()
+        {
+        }
 
-		return page.toString();
-	}
-	
-	public String makeHeader()
-	{
-		StringBuffer page=new StringBuffer();
-		page.append("<html><body><blockquote>");
-		page.append("<br>File: "+model.getDbFile()+"<br>");
-		page.append("Source: "+model.getDB().getSource()+"<br><br>");
-		page.append("<br><br>");
-		page.append("<table border=0>");
+        public HtmlFormat(TFModel model)
+        {
+                setModel(model);
+        }
 
-		return page.toString();
-	}
-	
-	public String makeFooter()
-	{
-		return "</table></blockquote></body></html>";
-	}
-	
-	
-	static String htmlColor(Color c)
-	{
-		return '#'+hex2(c.getRed())+hex2(c.getGreen())+hex2(c.getBlue());
-	}
-	
-	private static final String hexDigits="0123456789ABCDEF";
-	private static String hex2(int n)
-	{
-		return hexDigits.charAt((n/16)%16)+""+hexDigits.charAt(n%16);
-	}
-	@Override
-	public String getName() {
-		return "HTML List";
-	}
+        public void setModel(TFModel model)
+        {
+                this.model = model;
+                fields = model.getDB().getFields();
+                title = model.getDB().getField(VirtualField.LABEL);
+        }
 
+        @Override
+        public void export(TFModel model, BufferedWriter out) throws Exception
+        {
+                setModel(model);
+                out.write(makeHeader());
+                for (Act a : model.getDB())
+                {
+                        out.write(makeItem(a));
+                }
+                out.write(makeFooter());
+                out.flush();
+        }
+
+        public void append(ActList acts, int start, int end, StringBuffer b)
+        {
+                for (int i = start; i < end; i++)
+                {
+                        Act a = acts.get(i);
+                        b.append(makeItem(a, i));
+                }
+        }
+
+        private String makeItem(Act act)
+        {
+                return makeItem(act, -1);
+        }
+
+        public String makeItem(Act act, int id)
+        {
+                StringBuffer page = new StringBuffer();
+
+                page.append("<tr><td valign=top align=left width=200><b>");
+                if (title != null)
+                {
+                        Field f = model.getColorField();
+                        Color c = Color.black;
+                        if (f != null)
+                        {
+                                if (f.getType() == String.class)
+                                {
+                                        c = model.getDisplay().makeColor(act.getString(f));
+                                } else
+                                {
+                                        String[] tags = act.getTextList(f);
+                                        if (tags.length == 0)
+                                        {
+                                                c = Color.gray;
+                                        } else
+                                        {
+                                                c = model.getDisplay().makeColor(tags[0]);
+                                        }
+                                }
+                        }
+
+                        page.append("<font size=+1 color=" + htmlColor(c) + ">" + act.getString(title) + "</font><br>");
+                }
+
+                Field startField = model.getDB().getField(VirtualField.START);
+
+                if (startField != null)
+                {
+                        page.append("<font color=#999999>" + model.getDisplay().format(act.getTime(startField)) + "</font>");
+                }
+                page.append("</b><br>");
+                if (id >= 0)
+                {
+                        page.append("<a href=\"e" + id + "\">EDIT</a>");
+                }
+                page.append("<br></td><td valign=top>");
+                for (Field f : fields)
+                {
+                        page.append("<b><font color=#003399>" + f.getName() + "</font></b>&nbsp;&nbsp;");
+                        Object val = act.get(f);
+                        if (val instanceof URL)
+                        {
+                                page.append("<a href=\"" + val + "\">" + val + "</a>");
+                        } else
+                        {
+                                page.append(model.getDisplay().toString(val));
+                        }
+                        page.append("<br>");
+
+                }
+                page.append("<br></td></tr>");
+
+                return page.toString();
+        }
+
+        public String makeHeader()
+        {
+                StringBuffer page = new StringBuffer();
+                page.append("<html><body><blockquote>");
+                page.append("<br>File: " + model.getDbFile() + "<br>");
+                page.append("Source: " + model.getDB().getSource() + "<br><br>");
+                page.append("<br><br>");
+                page.append("<table border=0>");
+
+                return page.toString();
+        }
+
+        public String makeFooter()
+        {
+                return "</table></blockquote></body></html>";
+        }
+
+        static String htmlColor(Color c)
+        {
+                return '#' + hex2(c.getRed()) + hex2(c.getGreen()) + hex2(c.getBlue());
+        }
+        private static final String hexDigits = "0123456789ABCDEF";
+
+        private static String hex2(int n)
+        {
+                return hexDigits.charAt((n / 16) % 16) + "" + hexDigits.charAt(n % 16);
+        }
+
+        @Override
+        public String getName()
+        {
+                return "HTML List";
+        }
 }
diff --git a/timeflow/views/AbstractVisualizationView.java b/timeflow/views/AbstractVisualizationView.java
index 51026d6..349ee29 100755
--- a/timeflow/views/AbstractVisualizationView.java
+++ b/timeflow/views/AbstractVisualizationView.java
@@ -31,6 +31,10 @@
         {
                 this.model = model;
 
+                final JPopupMenu popup = new JPopupMenu();
+                final JMenuItem edit = new JMenuItem("Edit");
+                final JMenuItem delete = new JMenuItem("Delete");
+
                 // deal with mouseovers.
                 addMouseMotionListener(new MouseMotionListener()
                 {
@@ -50,9 +54,6 @@
                         }
                 });
 
-
-                final JPopupMenu popup = new JPopupMenu();
-                final JMenuItem edit = new JMenuItem("Edit");
                 edit.addActionListener(new ActionListener()
                 {
 
@@ -64,7 +65,6 @@
                 });
                 popup.add(edit);
 
-                final JMenuItem delete = new JMenuItem("Delete");
                 popup.add(delete);
                 delete.addActionListener(new ActionListener()
                 {
diff --git a/timeflow/views/ListView.java b/timeflow/views/ListView.java
index ec87f1c..1302bfa 100755
--- a/timeflow/views/ListView.java
+++ b/timeflow/views/ListView.java
@@ -20,247 +20,266 @@
 import java.net.URL;
 import java.util.*;
 
-public class ListView extends AbstractView {
+public class ListView extends AbstractView
+{
+        private JEditorPane listDisplay;
+        private JComboBox sortMenu = new JComboBox();
+        private ActComparator sort;//=ActComparator.byTime();
+        private int maxPerPage = 50;
+        private int pageStart = 0;
+        private int lastSize = 0;
+        private ActList acts;
+        private Field sortField;
+        private JLabel pageLabel = new JLabel("Page", JLabel.LEFT);
+        private JComboBox pageMenu = new JComboBox();
+        private boolean changing = false;
+        private JPanel controls;
 
-	private JEditorPane listDisplay;
-	private JComboBox sortMenu=new JComboBox();
-	private ActComparator sort;//=ActComparator.byTime();
-	private int maxPerPage=50;
-	private int pageStart=0;
-	private int lastSize=0;
-	private ActList acts;
-	private Field sortField;
-	
-	private JLabel pageLabel=new JLabel("Page", JLabel.LEFT);
-	private JComboBox pageMenu=new JComboBox();
-	private boolean changing=false;
-	
-	private JPanel controls;
-	
-	public ListView(TFModel model)
-	{
-		super(model);
-		
-		listDisplay=HtmlDisplay.create();
-		listDisplay.addHyperlinkListener(new LinkIt());		
-		JScrollPane scrollPane = new JScrollPane(listDisplay);
-		setLayout(new BorderLayout());
-		add(scrollPane, BorderLayout.CENTER);
-		
-		
-		controls=new JPanel();
-		controls.setLayout(null);
-		controls.setBackground(Color.white);
-		
-		int x=10, y=10;
-		int ch=25, pad=5, cw=160;
-		JLabel sortLabel=new JLabel("Sort Order", JLabel.LEFT);
-		controls.add(sortLabel);
-		sortLabel.setBounds(x,y,cw,ch);
-		y+=ch+pad;
-		
-		controls.add(sortMenu);	
-		sortMenu.setBounds(x,y,cw,ch);
-		y+=ch+3*pad;
-		
-		controls.add(pageLabel);
-		pageLabel.setBounds(x,y,cw,ch);
-		y+=ch+pad;
-		controls.add(pageMenu);
-		pageMenu.setBounds(x,y,cw,ch);
-		
-		showPageMenu(false);
-		pageMenu.addActionListener(pageListener);				
-		sortMenu.addActionListener(sortListener);
-	}
-	
-	protected JComponent _getControls()
-	{
-		return controls;
-	}
-	
-	ActionListener sortListener=new ActionListener() {
-		@Override
-		public void actionPerformed(ActionEvent e) {
-			if (changing || sortMenu.getItemCount()<=0) // this means the action was fired after all items removed.
-				return;
-			sortField=getModel().getDB().getField((String)sortMenu.getSelectedItem());
-			sort=sortField==null ? null : ActComparator.by(sortField);
-			setToFirstPage();
-			makeList();
-		}};
-	
-	ActionListener pageListener=new ActionListener() {
-		@Override
-		public void actionPerformed(ActionEvent e) {
-			if (changing)
-				return;
-			pageStart=maxPerPage*pageMenu.getSelectedIndex();
-			System.out.println(e.getActionCommand());
-			makeList();
-		}};
-	
-	@Override
-	protected void onscreen(boolean majorChange)
-	{
-		_note(null);
-	}
-	
-	public void _note(TFEvent e) {
-		changing=true;
-		if (e==null || e.affectsSchema() || e.affectsRowSet())
-		{
-			sortMenu.removeActionListener(sortListener);
-			sortMenu.removeAllItems();
-			pageStart=0;
-			java.util.List<Field> fields=getModel().getDB().getFields();
-			Field firstField=null;
-			if (fields.size()>0)
-				firstField=fields.get(0);
-			for (Field f: fields)
-			{
-				sortMenu.addItem(f.getName());
-			}
-			sortField=getModel().getDB().getField(VirtualField.START);
-			if (sortField!=null)
-				sortMenu.setSelectedItem(sortField.getName());
-			else
-				sortField=firstField;
-			sortMenu.addActionListener(sortListener);
-			sort=null;
-		}
-		if (e!=null && e.affectsData())
-		{
-			setToFirstPage();
-		}
-		changing=false;
-		makeList();
-	}
-	
-	private void setToFirstPage()
-	{
-		pageStart=0;
-		if (pageMenu.isVisible())
-		{
-			pageMenu.removeActionListener(pageListener);
-			pageMenu.setSelectedIndex(0);
-			pageMenu.addActionListener(pageListener);
-		}
-	}
-	
-	void showPageMenu(boolean visible)
-	{
-		pageLabel.setVisible(visible);
-		pageMenu.setVisible(visible);
-		if (visible)
-		{
-			pageMenu.removeActionListener(pageListener);
-			pageMenu.setSelectedIndex(pageStart/maxPerPage);
-			pageMenu.addActionListener(pageListener);
-		}
-	}
-	
-	
-	void makeList()
-	{
-		HtmlFormat html=new HtmlFormat();
-		html.setModel(getModel());
-		StringBuffer page=new StringBuffer();
-		
-		page.append(html.makeHeader());
-		
-		
-		ActList as=getModel().getActs();
-		if (as==null || as.size()==0 && getModel().getDB().size()==0)
-		{
-			page.append("<tr><td><h1><font color=#003399>Empty Database</font></h1></td></tr>");
-			showPageMenu(false);
-		}
-		else
-		{
-			
-			if (sort==null)
-			{
-				Field timeField=getModel().getDB().getField(VirtualField.START);
-				if (timeField!=null)			
-					sort=ActComparator.by(timeField);
-			}
+        public ListView(TFModel model)
+        {
+                super(model);
 
-			acts=as.copy();
-			if (sort!=null)
-				Collections.sort(acts, sort);
-			
-			boolean pages=acts.size()>maxPerPage;
-			int last=Math.min(acts.size(), pageStart+maxPerPage);
-			if (pages)
-			{
-				int n=acts.size();
-				if (lastSize!=n)
-				{
-					pageMenu.removeActionListener(pageListener);
-					pageMenu.removeAllItems();
-					for (int i=0; i*maxPerPage<n;i++)
-					{
-						pageMenu.addItem("Items "+((i*maxPerPage)+1)+" to "+
-								Math.min(n, (i+1)*maxPerPage));
-					}
-					pageMenu.addActionListener(pageListener);
-					lastSize=n;
-				}
-			}
-			showPageMenu(pages);
-			
-			page.append("<tr><td><h1><font color=#003399>"+(pages? (pageStart+1)+"-"+(last) +" of ": "")+acts.size()+" Events</font></h1>");
-			page.append("<br><br></td></tr>");
+                listDisplay = HtmlDisplay.create();
+                listDisplay.addHyperlinkListener(new LinkIt());
+                JScrollPane scrollPane = new JScrollPane(listDisplay);
+                setLayout(new BorderLayout());
+                add(scrollPane, BorderLayout.CENTER);
 
-			for (int i=pageStart; i<last; i++)
-			{
-				Act a=acts.get(i);
-				page.append(html.makeItem(a,i));
-			}
-		}
-		page.append(html.makeFooter());
-		listDisplay.setText(page.toString());
-		listDisplay.setCaretPosition(0);
-		repaint();
-	}
-	
-	
-	
-	
-	@Override
-	public String getName() {
-		return "List";
-	}
-	
-	static class ArrayRenderer extends DefaultTableCellRenderer {
-	    public void setValue(Object value) {
-	    	setText(Display.arrayToString((Object[])value));
-	    }
-	}
-	
-	public class LinkIt implements HyperlinkListener 
-	{
-		public void hyperlinkUpdate(HyperlinkEvent e) 
-		{
-			if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) 
-				return;
-			
-			String s=e.getDescription();
-			System.out.println(s);
-			if (s.length()>0)
-			{
-				char c=s.charAt(0);
-				if (c=='e') // code for "edit"
-				{
-					int i=Integer.parseInt(s.substring(1));
-					EditRecordPanel.edit(getModel(), acts.get(i));
-					return;
-				}
-				
-			}
-			Display.launchBrowser(e.getURL().toString());
-			
-		} 
-	} 
+
+                controls = new JPanel();
+                controls.setLayout(null);
+                controls.setBackground(Color.white);
+
+                int x = 10, y = 10;
+                int ch = 25, pad = 5, cw = 160;
+                JLabel sortLabel = new JLabel("Sort Order", JLabel.LEFT);
+                controls.add(sortLabel);
+                sortLabel.setBounds(x, y, cw, ch);
+                y += ch + pad;
+
+                controls.add(sortMenu);
+                sortMenu.setBounds(x, y, cw, ch);
+                y += ch + 3 * pad;
+
+                controls.add(pageLabel);
+                pageLabel.setBounds(x, y, cw, ch);
+                y += ch + pad;
+                controls.add(pageMenu);
+                pageMenu.setBounds(x, y, cw, ch);
+
+                showPageMenu(false);
+                pageMenu.addActionListener(pageListener);
+                sortMenu.addActionListener(sortListener);
+        }
+
+        protected JComponent _getControls()
+        {
+                return controls;
+        }
+        ActionListener sortListener = new ActionListener()
+        {
+
+                @Override
+                public void actionPerformed(ActionEvent e)
+                {
+                        if (changing || sortMenu.getItemCount() <= 0) // this means the action was fired after all items removed.
+                        {
+                                return;
+                        }
+                        sortField = getModel().getDB().getField((String) sortMenu.getSelectedItem());
+                        sort = sortField == null ? null : ActComparator.by(sortField);
+                        setToFirstPage();
+                        makeList();
+                }
+        };
+        ActionListener pageListener = new ActionListener()
+        {
+
+                @Override
+                public void actionPerformed(ActionEvent e)
+                {
+                        if (changing)
+                        {
+                                return;
+                        }
+                        pageStart = maxPerPage * pageMenu.getSelectedIndex();
+                        System.out.println(e.getActionCommand());
+                        makeList();
+                }
+        };
+
+        @Override
+        protected void onscreen(boolean majorChange)
+        {
+                _note(null);
+        }
+
+        public void _note(TFEvent e)
+        {
+                changing = true;
+                if (e == null || e.affectsSchema() || e.affectsRowSet())
+                {
+                        sortMenu.removeActionListener(sortListener);
+                        sortMenu.removeAllItems();
+                        pageStart = 0;
+                        java.util.List<Field> fields = getModel().getDB().getFields();
+                        Field firstField = null;
+                        if (fields.size() > 0)
+                        {
+                                firstField = fields.get(0);
+                        }
+                        for (Field f : fields)
+                        {
+                                sortMenu.addItem(f.getName());
+                        }
+                        sortField = getModel().getDB().getField(VirtualField.START);
+                        if (sortField != null)
+                        {
+                                sortMenu.setSelectedItem(sortField.getName());
+                        } else
+                        {
+                                sortField = firstField;
+                        }
+                        sortMenu.addActionListener(sortListener);
+                        sort = null;
+                }
+                if (e != null && e.affectsData())
+                {
+                        setToFirstPage();
+                }
+                changing = false;
+                makeList();
+        }
+
+        private void setToFirstPage()
+        {
+                pageStart = 0;
+                if (pageMenu.isVisible())
+                {
+                        pageMenu.removeActionListener(pageListener);
+                        pageMenu.setSelectedIndex(0);
+                        pageMenu.addActionListener(pageListener);
+                }
+        }
+
+        void showPageMenu(boolean visible)
+        {
+                pageLabel.setVisible(visible);
+                pageMenu.setVisible(visible);
+                if (visible)
+                {
+                        pageMenu.removeActionListener(pageListener);
+                        pageMenu.setSelectedIndex(pageStart / maxPerPage);
+                        pageMenu.addActionListener(pageListener);
+                }
+        }
+
+        void makeList()
+        {
+                HtmlFormat html = new HtmlFormat();
+                html.setModel(getModel());
+                StringBuffer page = new StringBuffer();
+
+                page.append(html.makeHeader());
+
+                ActList as = getModel().getActs();
+                if (as == null || as.size() == 0 && getModel().getDB().size() == 0)
+                {
+                        page.append("<tr><td><h1><font color=#003399>Empty Database</font></h1></td></tr>");
+                        showPageMenu(false);
+                } else
+                {
+
+                        if (sort == null)
+                        {
+                                Field timeField = getModel().getDB().getField(VirtualField.START);
+                                if (timeField != null)
+                                {
+                                        sort = ActComparator.by(timeField);
+                                }
+                        }
+
+                        acts = as.copy();
+                        if (sort != null)
+                        {
+                                Collections.sort(acts, sort);
+                        }
+
+                        boolean pages = acts.size() > maxPerPage;
+                        int last = Math.min(acts.size(), pageStart + maxPerPage);
+                        if (pages)
+                        {
+                                int n = acts.size();
+                                if (lastSize != n)
+                                {
+                                        pageMenu.removeActionListener(pageListener);
+                                        pageMenu.removeAllItems();
+                                        for (int i = 0; i * maxPerPage < n; i++)
+                                        {
+                                                pageMenu.addItem("Items " + ((i * maxPerPage) + 1) + " to "
+                                                        + Math.min(n, (i + 1) * maxPerPage));
+                                        }
+                                        pageMenu.addActionListener(pageListener);
+                                        lastSize = n;
+                                }
+                        }
+                        showPageMenu(pages);
+
+                        page.append("<tr><td><h1><font color=#003399>" + (pages ? (pageStart + 1) + "-" + (last) + " of " : "") + acts.size() + " Events</font></h1>");
+                        page.append("<br><br></td></tr>");
+
+                        for (int i = pageStart; i < last; i++)
+                        {
+                                Act a = acts.get(i);
+                                page.append(html.makeItem(a, i));
+                        }
+                }
+                page.append(html.makeFooter());
+                listDisplay.setText(page.toString());
+                listDisplay.setCaretPosition(0);
+                repaint();
+        }
+
+        @Override
+        public String getName()
+        {
+                return "List";
+        }
+
+        static class ArrayRenderer extends DefaultTableCellRenderer
+        {
+
+                public void setValue(Object value)
+                {
+                        setText(Display.arrayToString((Object[]) value));
+                }
+        }
+
+        public class LinkIt implements HyperlinkListener
+        {
+
+                public void hyperlinkUpdate(HyperlinkEvent e)
+                {
+                        if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED)
+                        {
+                                return;
+                        }
+
+                        String s = e.getDescription();
+                        System.out.println(s);
+                        if (s.length() > 0)
+                        {
+                                char c = s.charAt(0);
+                                if (c == 'e') // code for "edit"
+                                {
+                                        int i = Integer.parseInt(s.substring(1));
+                                        EditRecordPanel.edit(getModel(), acts.get(i));
+                                        return;
+                                }
+
+                        }
+                        Display.launchBrowser(e.getURL().toString());
+
+                }
+        }
 }
diff --git a/timeflow/views/TimelineView.java b/timeflow/views/TimelineView.java
index 778ece2..06190c6 100755
--- a/timeflow/views/TimelineView.java
+++ b/timeflow/views/TimelineView.java
@@ -1,21 +1,24 @@
 package timeflow.views;
 
 import timeflow.app.ui.ComponentCluster;
-import timeflow.data.db.*;
+import timeflow.app.ui.EditRecordPanel;
+import timeflow.data.db.DBUtils;
 import timeflow.data.time.*;
 import timeflow.model.*;
-import timeflow.views.CalendarView.CalendarPanel;
-import timeflow.views.CalendarView.ScrollingCalendar;
-import timeflow.vis.*;
+//import timeflow.views.CalendarView.CalendarPanel;
+//import timeflow.views.CalendarView.ScrollingCalendar;
+import timeflow.vis.Mouseover;
+import timeflow.vis.TimeScale;
+import timeflow.vis.VisualAct;
 import timeflow.vis.timeline.*;
 
 import java.awt.*;
 import java.awt.event.*;
-import java.awt.image.*;
+//import java.awt.image.*;
 
 import javax.swing.*;
 
-import java.util.*;
+import java.util.ArrayList;
 
 public class TimelineView extends AbstractView
 {
@@ -67,12 +70,23 @@
 
                 // top part of grid: zoom buttons.
                 ComponentCluster buttons = new ComponentCluster("Zoom");
-                ImageIcon zoomOutIcon = new ImageIcon("images/zoom_out.gif");
-                JButton zoomOut = new JButton(zoomOutIcon);
+                //ImageIcon zoomOutIcon = new ImageIcon("images/zoom_out.gif");
+                JButton zoomIn = new JButton("In 1/2X");
+                buttons.addContent(zoomIn);
+                zoomIn.addActionListener(new ActionListener()
+                {
+                        @Override
+                        public void actionPerformed(ActionEvent e)
+                        {
+                                Interval zoom = visuals.getViewInterval().subinterval(0.333, 0.667).intersection(visuals.getGlobalInterval());
+                                moveTime(zoom);
+                        }
+                });
+
+                JButton zoomOut = new JButton("Out 2X");
                 buttons.addContent(zoomOut);
                 zoomOut.addActionListener(new ActionListener()
                 {
-
                         @Override
                         public void actionPerformed(ActionEvent e)
                         {
@@ -82,7 +96,7 @@
                 });
 
                 ImageIcon zoomOut100Icon = new ImageIcon("images/zoom_out_100.gif");
-                JButton zoomOutAll = new JButton(zoomOut100Icon);
+                JButton zoomOutAll = new JButton("Fit 100%");
                 buttons.addContent(zoomOutAll);
                 zoomOutAll.addActionListener(new ActionListener()
                 {
@@ -221,17 +235,27 @@
 
                 public void run()
                 {
-                        int n = 15;
+                        int n = 8;
                         for (int i = 0; i < n; i++)
                         {
-                                long start = ((n - i) * i1.start + i * i2.start) / n;
-                                long end = ((n - i) * i1.end + i * i2.end) / n;
+                                final long start = ((n - i) * i1.start + i * i2.start) / n;
+                                final long end = ((n - i) * i1.end + i * i2.end) / n;
                                 try
                                 {
-                                        visuals.setTimeBounds(start, end);
-                                        redraw();
+//                                        visuals.setTimeBounds(start, end);
+//                                        redraw();
+                                        SwingUtilities.invokeAndWait(new Runnable()
+                                        {
+
+                                                @Override
+                                                public void run()
+                                                {
+                                                        visuals.setTimeBounds(start, end);
+                                                        redraw();
+                                                }
+                                        });
                                         
-                                        sleep(20);
+                                        //sleep(20);
                                 } catch (Exception e)
                                 {
                                 }
@@ -312,7 +336,20 @@
                                 {
                                         if (e.getClickCount() == 2)
                                         {
-                                                moveTime(visuals.getViewInterval().subinterval(.333, .667));
+                                                Point p = new Point(e.getX(), e.getY());
+                                                Mouseover o = find(p);
+                                                //moveTime(visuals.getViewInterval().subinterval(.333, .667));
+                                                boolean onAct = o != null && o.thing instanceof VisualAct;
+                                                if (onAct)
+                                                {
+                                                        VisualAct v = (VisualAct) o.thing;
+                                                        selectedAct = v.getAct();
+                                                        EditRecordPanel.edit(getModel(), selectedAct);
+                                                } else
+                                                {
+                                                        selectedTime = getTime(p);
+                                                        EditRecordPanel.add(getModel(), selectedTime);
+                                                }
                                         }
                                 }
 
@@ -358,6 +395,11 @@
                                         int a = firstMouse.x;
                                         int b = mouse.x;
                                         
+                                        if (a == b)
+                                        {
+                                                return;
+                                        }
+                                        
                                         long start = visuals.getTimeScale().toTime(a);
                                         long end = visuals.getTimeScale().toTime(b);
                                         if (b - a < 0)
@@ -397,12 +439,18 @@
                                 @Override
                                 public void mouseWheelMoved(MouseWheelEvent e)
                                 {
-                                        //bar.setValue(bar.getValue() + e.getWheelRotation() * 10);
-                                        visuals.scale *= Math.exp(e.getWheelRotation()/10.0);
-                                        visuals.scale = visuals.layout();
-                                        //System.out.println(visuals.scale);
-                                        timelinePanel.drawVisualization();
-                                        scroller.calibrate();
+                                        if (e.isMetaDown())
+                                        {
+                                                visuals.scale *= Math.exp(e.getWheelRotation()/10.0);
+                                                visuals.scale = visuals.layout();
+                                                //System.out.println(visuals.scale);
+                                                timelinePanel.drawVisualization();
+                                                scroller.calibrate();
+                                        }
+                                        else
+                                        {
+                                                bar.setValue(bar.getValue() + e.getWheelRotation() * 40);
+                                        }
                                         repaint();
                                 }
                         });
diff --git a/timeflow/vis/Mouseover.java b/timeflow/vis/Mouseover.java
index adf3009..eb2b74b 100755
--- a/timeflow/vis/Mouseover.java
+++ b/timeflow/vis/Mouseover.java
@@ -5,90 +5,94 @@
 import java.awt.*;
 import java.util.ArrayList;
 
-public class Mouseover extends Rectangle {
-	public Object thing;
-	public Mouseover(Object thing, int x, int y, int w, int h)
-	{
-		super(x,y,w,h);
-		this.thing=thing;
-	}
-	
-	public void draw(Graphics2D g, int maxW, int maxH, Display display)
-	{
-		g.setColor(new Color(0,53,153));
-		g.setColor(new Color(255,255,0,100));
-		g.fill(this);
-	}
-	
-	protected void draw(Graphics2D g, int maxW, int maxH, Display display, ArrayList labels, int numLines)
-	{
-		if (labels==null || labels.size()==0)
-			return;
-		
-		// draw a background box.
-		
-		// find max number of chars, very very roughly!
-		int boxW=50;
-		for (int i=0; i<labels.size(); i+=2)
-		{
-			if (labels.get(i) instanceof String[])
-				boxW=300;
-			else if (labels.get(i) instanceof String)
-			{
-				boxW=Math.max(boxW, 50+50*((String)labels.get(i)).length());
-			}
-		}	
-		
-		
-		boxW=Math.min(350, boxW);
-		int boxH=18*numLines+10;
-		int mx=this.x+this.width+5;
-		int my=this.y+this.height+35;		
-		
-		// put box in a place where it does not obscure the data
-		// or go off screen.
-		if (my+boxH>maxH-10)
-		{
-			my=Math.max(10,this.y-boxH-5);
-		}
-		if (mx+boxW>maxW-10)
-		{
-			mx=Math.max(10,this.x-boxW-10);
-		}
-		int ty=my;
-		g.setColor(new Color(0,0,0,70));
-		g.fillRoundRect(mx-11, my-16, boxW, boxH,12,12);
-		g.setColor(Color.white);
-		g.fillRoundRect(mx-15, my-20, boxW, boxH,12,12);
-		g.setColor(Color.darkGray);
-		g.drawRoundRect(mx-15, my-20, boxW, boxH,12,12);
-		
-		// finally, draw the darn labels.
-		for (int i=0; i<labels.size(); i+=2)
-		{
-			g.setFont(display.bold());
-			String field=(String)labels.get(i);
-			g.drawString(field,mx,ty);
-			int sw=display.boldFontMetrics().stringWidth(field);
-			g.setFont(display.plain());
-			Object o=labels.get(i+1);
-			if (o instanceof String)
-			{
-				g.drawString((String)o,mx+sw+9,ty);
-				ty+=18;
-			}
-			else
-			{
-				ArrayList<String> lines=(ArrayList<String>)o;
-				int dx=sw+9;
-				for (String line: lines)
-				{
-					g.drawString((String)line,mx+dx,ty);
-					ty+=18;
-					dx=0;
-				}
-				ty+=5;
-			}
-		}		
-	}
+public class Mouseover extends Rectangle
+{
+        public Object thing;
+
+        public Mouseover(Object thing, int x, int y, int w, int h)
+        {
+                super(x, y, w, h);
+                this.thing = thing;
+        }
+
+        public void draw(Graphics2D g, int maxW, int maxH, Display display)
+        {
+                g.setColor(new Color(0, 53, 153));
+                g.setColor(new Color(255, 255, 0, 100));
+                g.fill(this);
+        }
+
+        protected void draw(Graphics2D g, int maxW, int maxH, Display display, ArrayList labels, int numLines)
+        {
+                if (labels == null || labels.size() == 0)
+                {
+                        return;
+                }
+
+                // draw a background box.
+
+                // find max number of chars, very very roughly!
+                int boxW = 50;
+                for (int i = 0; i < labels.size(); i += 2)
+                {
+                        if (labels.get(i) instanceof String[])
+                        {
+                                boxW = 300;
+                        } else if (labels.get(i) instanceof String)
+                        {
+                                boxW = Math.max(boxW, 50 + 50 * ((String) labels.get(i)).length());
+                        }
+                }
+
+
+                boxW = Math.min(350, boxW);
+                int boxH = 18 * numLines + 10;
+                int mx = this.x + this.width + 5;
+                int my = this.y + this.height + 35;
+
+                // put box in a place where it does not obscure the data
+                // or go off screen.
+                if (my + boxH > maxH - 10)
+                {
+                        my = Math.max(10, this.y - boxH - 5);
+                }
+                if (mx + boxW > maxW - 10)
+                {
+                        mx = Math.max(10, this.x - boxW - 10);
+                }
+                int ty = my;
+                g.setColor(new Color(0, 0, 0, 70));
+                g.fillRoundRect(mx - 11, my - 16, boxW, boxH, 12, 12);
+                g.setColor(Color.white);
+                g.fillRoundRect(mx - 15, my - 20, boxW, boxH, 12, 12);
+                g.setColor(Color.darkGray);
+                g.drawRoundRect(mx - 15, my - 20, boxW, boxH, 12, 12);
+
+                // finally, draw the darn labels.
+                for (int i = 0; i < labels.size(); i += 2)
+                {
+                        g.setFont(display.bold());
+                        String field = (String) labels.get(i);
+                        g.drawString(field, mx, ty);
+                        int sw = display.boldFontMetrics().stringWidth(field);
+                        g.setFont(display.plain());
+                        Object o = labels.get(i + 1);
+                        if (o instanceof String)
+                        {
+                                g.drawString((String) o, mx + sw + 9, ty);
+                                ty += 18;
+                        } else
+                        {
+                                ArrayList<String> lines = (ArrayList<String>) o;
+                                int dx = sw + 9;
+                                for (String line : lines)
+                                {
+                                        g.drawString((String) line, mx + dx, ty);
+                                        ty += 18;
+                                        dx = 0;
+                                }
+                                ty += 5;
+                        }
+                }
+        }
 }
diff --git a/timeflow/vis/VisualAct.java b/timeflow/vis/VisualAct.java
index 4c3569b..1351c06 100755
--- a/timeflow/vis/VisualAct.java
+++ b/timeflow/vis/VisualAct.java
@@ -12,7 +12,6 @@
 
 public class VisualAct implements Comparable
 {
-
         Color color;
         String label;
         String mouseOver;
diff --git a/timeflow/vis/timeline/AxisRenderer.java b/timeflow/vis/timeline/AxisRenderer.java
index 7d020cb..0372be6 100755
--- a/timeflow/vis/timeline/AxisRenderer.java
+++ b/timeflow/vis/timeline/AxisRenderer.java
@@ -9,80 +9,83 @@
 import timeflow.vis.Mouseover;
 import timeflow.vis.TimeScale;
 
-public class AxisRenderer {
-	
-	TimelineVisuals visuals;
-	
-	public AxisRenderer(TimelineVisuals visuals)
-	{
-		this.visuals=visuals;
-	}
-	
-	public void render(Graphics2D g, Collection<Mouseover> objectLocations)
-	{		
-		TFModel model=visuals.getModel();
-		g.setColor(model.getDisplay().getColor("chart.background"));
-		Rectangle bounds=visuals.getBounds();
-		
-		TimeScale scale=visuals.getTimeScale();
-		java.util.List<AxisTicMarks> t=AxisTicMarks.allRelevant(scale.getInterval());
-		
-		int dateLabelH=model.getDisplay().getInt("timeline.datelabel.height");
-		int y=bounds.y+bounds.height-dateLabelH;	
-		
-		// draw in reverse order so bigger granularity at top.
-		int n=t.size();
-		for (int i=0; i<n; i++)
-		{
-			render(t.get(i), g, bounds.x, y, dateLabelH-1, bounds.y, i==0, objectLocations);
-			y-=dateLabelH;
-		}
-	}
-	
-	void render(AxisTicMarks t, Graphics2D g, int x, int y, int h, int top, boolean full, Collection<Mouseover> objectLocations)
-	{
-		TFModel model=visuals.getModel();
+public class AxisRenderer
+{
+        TimelineVisuals visuals;
 
-		int n=t.tics.size();
-		for (int i=0; i<n-1; i++)
-		{
-			
-			long start=t.tics.get(i);
-			long end=t.tics.get(i+1);
-			
-			int x0=Math.max(x,visuals.getTimeScale().toInt(start));			
-			int x1=visuals.getTimeScale().toInt(end);
-			
-			int dayOfWeek=TimeUtils.cal(start).get(Calendar.DAY_OF_WEEK);
-			
-			g.setColor(t.unit.isDayOrLess() && (dayOfWeek==1 || dayOfWeek==7) ? 
-					new Color(245,245,245) : new Color(240,240,240));
+        public AxisRenderer(TimelineVisuals visuals)
+        {
+                this.visuals = visuals;
+        }
 
-			g.fillRect(x0, y, x1-x0-1, h);
-			g.setColor(Color.white);
-			g.drawLine(x1-1, y, x1-1, y+h);
-			g.drawLine(x0,y+h,x1,y+h);
-			objectLocations.add(new Mouseover(new Interval(start,end), x0, y, x1-x0-1, h));
-			
-			g.setFont(model.getDisplay().timeLabel());
-			String label=full? t.unit.formatFull(start) : t.unit.format(new Date(start));
-			int tx=x0+3;
-			int ty=y+h-5;
-			g.setColor(full ? Color.darkGray : Color.gray);
-			int sw=model.getDisplay().timeLabelFontMetrics().stringWidth(label);
-			if (sw<x1-tx-3)
-				g.drawString(label, tx,ty);
-			else
-			{
-				int c=label.indexOf(':');
-				if (c>0)
-				{
-					label=label.substring(0,c);
-					sw=model.getDisplay().timeLabelFontMetrics().stringWidth(label);
-					if (sw<x1-tx-3)
-						g.drawString(label, tx,ty);
-				}
-			}
-		}
-	}
+        public void render(Graphics2D g, Collection<Mouseover> objectLocations)
+        {
+                TFModel model = visuals.getModel();
+                g.setColor(model.getDisplay().getColor("chart.background"));
+                Rectangle bounds = visuals.getBounds();
+
+                TimeScale scale = visuals.getTimeScale();
+                java.util.List<AxisTicMarks> t = AxisTicMarks.allRelevant(scale.getInterval());
+
+                int dateLabelH = model.getDisplay().getInt("timeline.datelabel.height");
+                int y = bounds.y + bounds.height - dateLabelH;
+
+                // draw in reverse order so bigger granularity at top.
+                int n = t.size();
+                for (int i = 0; i < n; i++)
+                {
+                        render(t.get(i), g, bounds.x, y, dateLabelH - 1, bounds.y, i == 0, objectLocations);
+                        y -= dateLabelH;
+                }
+        }
+
+        void render(AxisTicMarks t, Graphics2D g, int x, int y, int h, int top, boolean full, Collection<Mouseover> objectLocations)
+        {
+                TFModel model = visuals.getModel();
+
+                int n = t.tics.size();
+                for (int i = 0; i < n - 1; i++)
+                {
+
+                        long start = t.tics.get(i);
+                        long end = t.tics.get(i + 1);
+
+                        int x0 = Math.max(x, visuals.getTimeScale().toInt(start));
+                        int x1 = visuals.getTimeScale().toInt(end);
+
+                        int dayOfWeek = TimeUtils.cal(start).get(Calendar.DAY_OF_WEEK);
+
+                        g.setColor(t.unit.isDayOrLess() && (dayOfWeek == 1 || dayOfWeek == 7)
+                                ? new Color(245, 245, 245) : new Color(240, 240, 240));
+
+                        g.fillRect(x0, y, x1 - x0 - 1, h);
+                        g.setColor(Color.white);
+                        g.drawLine(x1 - 1, y, x1 - 1, y + h);
+                        g.drawLine(x0, y + h, x1, y + h);
+                        objectLocations.add(new Mouseover(new Interval(start, end), x0, y, x1 - x0 - 1, h));
+
+                        g.setFont(model.getDisplay().timeLabel());
+                        String label = full ? t.unit.formatFull(start) : t.unit.format(new Date(start));
+                        int tx = x0 + 3;
+                        int ty = y + h - 5;
+                        g.setColor(full ? Color.darkGray : Color.gray);
+                        int sw = model.getDisplay().timeLabelFontMetrics().stringWidth(label);
+                        if (sw < x1 - tx - 3)
+                        {
+                                g.drawString(label, tx, ty);
+                        } else
+                        {
+                                int c = label.indexOf(':');
+                                if (c > 0)
+                                {
+                                        label = label.substring(0, c);
+                                        sw = model.getDisplay().timeLabelFontMetrics().stringWidth(label);
+                                        if (sw < x1 - tx - 3)
+                                        {
+                                                g.drawString(label, tx, ty);
+                                        }
+                                }
+                        }
+                }
+        }
 }
diff --git a/timeflow/vis/timeline/AxisTicMarks.java b/timeflow/vis/timeline/AxisTicMarks.java
index 85a16dd..edbd96b 100755
--- a/timeflow/vis/timeline/AxisTicMarks.java
+++ b/timeflow/vis/timeline/AxisTicMarks.java
@@ -4,114 +4,116 @@
 
 import timeflow.data.time.*;
 
-public class AxisTicMarks {
-	public TimeUnit unit;
-	public List<Long> tics;
-	
-	private static final TimeUnit[] units={
-		TimeUnit.YEAR, TimeUnit.MONTH, TimeUnit.DAY, TimeUnit.HOUR, TimeUnit.MINUTE, TimeUnit.SECOND
-	};
-	
-	private static final TimeUnit[] histUnits={
-		TimeUnit.YEAR.times(100), TimeUnit.YEAR.times(50), TimeUnit.YEAR.times(25), 
-			TimeUnit.YEAR.times(10), TimeUnit.YEAR.times(5), TimeUnit.YEAR.times(2), TimeUnit.YEAR,
-		TimeUnit.MONTH.times(6), TimeUnit.MONTH.times(3), TimeUnit.MONTH.times(2), TimeUnit.MONTH, 
-		TimeUnit.WEEK, TimeUnit.DAY.times(2), TimeUnit.DAY,
+public class AxisTicMarks
+{
+        public TimeUnit unit;
+        public List<Long> tics;
+        private static final TimeUnit[] units =
+        {
+                TimeUnit.YEAR, TimeUnit.MONTH, TimeUnit.DAY, TimeUnit.HOUR, TimeUnit.MINUTE, TimeUnit.SECOND
+        };
+        private static final TimeUnit[] histUnits =
+        {
+                TimeUnit.YEAR.times(100), TimeUnit.YEAR.times(50), TimeUnit.YEAR.times(25),
+                TimeUnit.YEAR.times(10), TimeUnit.YEAR.times(5), TimeUnit.YEAR.times(2), TimeUnit.YEAR,
+                TimeUnit.MONTH.times(6), TimeUnit.MONTH.times(3), TimeUnit.MONTH.times(2), TimeUnit.MONTH,
+                TimeUnit.WEEK, TimeUnit.DAY.times(2), TimeUnit.DAY,
+                TimeUnit.HOUR,
+                TimeUnit.MINUTE,
+                TimeUnit.SECOND
+        };
 
-		TimeUnit.HOUR, 
-		TimeUnit.MINUTE, 
-		TimeUnit.SECOND
-	};
-	
-	public AxisTicMarks(TimeUnit unit, long start, long end)
-	{
-		this.unit=unit;
-		tics=new ArrayList<Long>();
-		RoughTime r=unit.roundDown(start);
-		tics.add(r.getTime());
-		do
-		{
-			unit.addTo(r);
-			tics.add(r.getTime());
-		} while (r.getTime()<end);	
-	}
-	
-	
-	
-	public static List<AxisTicMarks> allRelevant(Interval interval)
-	{
-		return allRelevant(interval.start, interval.end);
-	}
-	
-	public static List<AxisTicMarks> allRelevant(long start, long end)
-	{
-		return allRelevant(start, end, 40);
-	}
-	
-	public static AxisTicMarks histoTics(long start, long end)
-	{
-		for (int i=histUnits.length-1; i>=0; i--)
-		{
-			TimeUnit u=histUnits[i];
-			long estimate=u.approxNumInRange(start, end);		
-			if (estimate<200 || i==0)
-			{
-				AxisTicMarks t=new AxisTicMarks(u, start, end);
-				return t;
-			}
-		}
-		return null;
-	}
-	
-	public static List<AxisTicMarks> allRelevant(long start, long end, long maxTics)
-	{
-		List<AxisTicMarks> list=new ArrayList<AxisTicMarks>();
-		
-		
-		for (int i=0; i<units.length; i++)
-		{
-			TimeUnit u=units[i];
-			long estimate=u.approxNumInRange(start, end);
-			
-			if (estimate<maxTics)
-			{
-				AxisTicMarks t=new AxisTicMarks(u, start, end);
-				if (list.size()>0)
-				{
-					AxisTicMarks last=list.get(0);
-					if (last.tics.size()==t.tics.size())
-						list.remove(0);
-				}
-				list.add(t);
-				
-			}
-		}
-		while (list.size()>2)
-			list.remove(0);
-		
-		if (list.size()==0) // uh oh! must be many years. we will add in bigger increments.
-		{
-			long length=end-start;
-			long size=365*24*60*60*1000L;
-			int m=1;
-			maxTics=15;
-			while (m<2000000000 && length/(m*size)>maxTics)
-			{
-				if (length/(2*m*size)<=maxTics)
-				{
-					m*=2;
-					break;
-				}
-				if (length/(5*m*size)<=maxTics)
-				{
-					m*=5;
-					break;
-				}
-				m*=10;
-			}	
-			AxisTicMarks t=new AxisTicMarks(TimeUnit.multipleYears(m), start, end);
-			list.add(t);
-		}	
-		return list;
-	}
+        public AxisTicMarks(TimeUnit unit, long start, long end)
+        {
+                this.unit = unit;
+                tics = new ArrayList<Long>();
+                RoughTime r = unit.roundDown(start);
+                tics.add(r.getTime());
+                do
+                {
+                        unit.addTo(r);
+                        tics.add(r.getTime());
+                } while (r.getTime() < end);
+        }
+
+        public static List<AxisTicMarks> allRelevant(Interval interval)
+        {
+                return allRelevant(interval.start, interval.end);
+        }
+
+        public static List<AxisTicMarks> allRelevant(long start, long end)
+        {
+                return allRelevant(start, end, 40);
+        }
+
+        public static AxisTicMarks histoTics(long start, long end)
+        {
+                for (int i = histUnits.length - 1; i >= 0; i--)
+                {
+                        TimeUnit u = histUnits[i];
+                        long estimate = u.approxNumInRange(start, end);
+                        if (estimate < 200 || i == 0)
+                        {
+                                AxisTicMarks t = new AxisTicMarks(u, start, end);
+                                return t;
+                        }
+                }
+                return null;
+        }
+
+        public static List<AxisTicMarks> allRelevant(long start, long end, long maxTics)
+        {
+                List<AxisTicMarks> list = new ArrayList<AxisTicMarks>();
+
+
+                for (int i = 0; i < units.length; i++)
+                {
+                        TimeUnit u = units[i];
+                        long estimate = u.approxNumInRange(start, end);
+
+                        if (estimate < maxTics)
+                        {
+                                AxisTicMarks t = new AxisTicMarks(u, start, end);
+                                if (list.size() > 0)
+                                {
+                                        AxisTicMarks last = list.get(0);
+                                        if (last.tics.size() == t.tics.size())
+                                        {
+                                                list.remove(0);
+                                        }
+                                }
+                                list.add(t);
+
+                        }
+                }
+                while (list.size() > 2)
+                {
+                        list.remove(0);
+                }
+
+                if (list.size() == 0) // uh oh! must be many years. we will add in bigger increments.
+                {
+                        long length = end - start;
+                        long size = 365 * 24 * 60 * 60 * 1000L;
+                        int m = 1;
+                        maxTics = 15;
+                        while (m < 2000000000 && length / (m * size) > maxTics)
+                        {
+                                if (length / (2 * m * size) <= maxTics)
+                                {
+                                        m *= 2;
+                                        break;
+                                }
+                                if (length / (5 * m * size) <= maxTics)
+                                {
+                                        m *= 5;
+                                        break;
+                                }
+                                m *= 10;
+                        }
+                        AxisTicMarks t = new AxisTicMarks(TimeUnit.multipleYears(m), start, end);
+                        list.add(t);
+                }
+                return list;
+        }
 }

--
Gitblit v1.6.2