package timeflow.vis.timeline;
import timeflow.data.db.*;
import timeflow.data.time.*;
import timeflow.model.*;
import timeflow.vis.TimeScale;
import timeflow.vis.VisualAct;
import timeflow.vis.timeline.*;
import timeflow.util.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class TimelineSlider extends ModelPanel {
TimelineVisuals visuals;
Interval original;
long minRange;
int ew=10;
int eventRadius=2;
TimeScale scale;
Point mouseHit=new Point();
Point mouse=new Point(-1,0);
enum Modify {START, END, POSITION, NONE};
Modify change=Modify.NONE;
Rectangle startRect=new Rectangle(-1,-1,0,0);
Rectangle endRect=new Rectangle(-1,-1,0,0);
Rectangle positionRect=new Rectangle(-1,-1,0,0);
Color sidePlain=Color.orange;
Color sideMouse=new Color(230,100,0);
public TimelineSlider(final TimelineVisuals visuals, final long minRange, final Runnable action)
{
super(visuals.getModel());
this.minRange=minRange;
this.visuals=visuals;
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
int mx=e.getX();
int my=e.getY();
if (positionRect.contains(mx,my))
change=Modify.POSITION;
else if (startRect.contains(mx, my))
change=Modify.START;
else if (endRect.contains(mx,my))
change=Modify.END;
else
change=Modify.NONE;
mouseHit.setLocation(mx,my);
original=window().copy();
mouse.setLocation(mx,my);
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
change=Modify.NONE;
repaint();
}});
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (change==Modify.NONE)
return;
mouse.setLocation(e.getX(), e.getY());
int mouseDiff=mouse.x-mouseHit.x;
Interval limits=visuals.getGlobalInterval();
long timeDiff=scale.spaceToTime(mouseDiff);
switch (change)
{
case POSITION:
window().translateTo(original.start+timeDiff);
window().clampInside(limits);
break;
case START: window().start=Math.min(original.start+timeDiff, original.end-minRange);
window().start=Math.max(window().start, limits.start);
break;
case END: window().end=Math.max(original.end+timeDiff, original.start+minRange);
window().end=Math.min(window().end, limits.end);
}
getModel().setViewInterval(window());
action.run();
repaint();
}
});
}
private Interval window()
{
return visuals.getViewInterval();
}
@Override
public Dimension getPreferredSize()
{
return new Dimension(600,30);
}
public void setMinRange(long minRange)
{
this.minRange=minRange;
}
@Override
public void note(TFEvent e) {
repaint();
}
void setTimeInterval(Interval interval)
{
window().setTo(interval);
repaint();
}
public void paintComponent(Graphics g1)
{
int w=getSize().width, h=getSize().height;
Graphics2D g=(Graphics2D)g1;
long start=System.currentTimeMillis();
// draw main backdrop.
g.setColor(Color.white);
g.fillRect(0,0,w,h);
if (visuals.getModel()==null || visuals.getModel().getActs()==null)
{
g.setColor(Color.darkGray);
g.drawString("No data for timeline.", 5, 20);
return;
}
scale=new TimeScale();
scale.setDateRange(visuals.getGlobalInterval());
scale.setNumberRange(ew, w-ew);
// draw the area for the central "thumb".
int lx=scale.toInt(window().start);
int rx=scale.toInt(window().end);
g.setColor(change==Modify.POSITION ? new Color(255,255,120) : new Color(255,245,200));
positionRect.setBounds(lx,0,rx-lx,h);
g.fill(positionRect);
// Figure out how best to draw events.
// If there are too many, we just draw a kind of histogram of frequency,
// rather than using the timeline layout.
int slotW=2*eventRadius;
int slotNum=w/slotW+1;
int[] slots=new int[slotNum];
int mostInSlot=0;
for (VisualAct v: visuals.getVisualActs())
{
if (!v.isVisible())
continue;
int x=scale.toInt(v.getStart().getTime());
int s=x/slotW;
if (s>=0 && s<slotNum)
{
slots[s]++;
mostInSlot=Math.max(mostInSlot, slots[s]);
}
}
if (mostInSlot>30)
{
g.setColor(Color.gray);
for (int i=0; i<slots.length; i++)
{
int sh=(h*slots[i])/mostInSlot;
g.fillRect(slotW*i, h-sh, slotW, sh);
}
}
else
{
// draw individual events.
for (VisualAct v: visuals.getVisualActs())
{
if (!v.isVisible())
continue;
g.setColor(v.getColor());
int x=scale.toInt(v.getStart().getTime());
int y=eventRadius+(int)(v.getY()*h)/(visuals.getBounds().height-2*eventRadius);
g.fillRect(x-1,y-eventRadius,2*eventRadius,3);
if (v.getEnd()!=null)
{
int endX=scale.toInt(v.getEnd().getTime());
g.drawLine(x,y,endX,y);
}
}
}
g.setColor(Color.gray);
g.drawLine(0,0,w,0);
g.drawLine(0,h-1,w,h-1);
// draw "expansion" areas on sides of thumb.
startRect.setBounds(positionRect.x-ew,1,ew,h-2);
g.setColor(change==Modify.START ? sideMouse : sidePlain);
g.fill(startRect);
endRect.setBounds(positionRect.x+positionRect.width,1,ew,h-2);
g.setColor(change==Modify.END ? sideMouse : sidePlain);
g.fill(endRect);
}
}