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