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); } }