package timeflow.views; import timeflow.app.ui.ComponentCluster; import timeflow.data.db.*; import timeflow.data.time.*; import timeflow.model.*; import timeflow.views.CalendarView.CalendarPanel; import timeflow.views.CalendarView.ScrollingCalendar; import timeflow.vis.*; import timeflow.vis.timeline.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import javax.swing.*; import java.util.*; public class TimelineView extends AbstractView { AxisRenderer grid; TimelineRenderer timeline; TimelineVisuals visuals; TimelinePanel timelinePanel; JButton fit; ScrollingTimeline scroller; JPanel controls; JScrollBar bar; //JScrollPane scrollpane; public JComponent _getControls() { return controls; } public TimelineView(TFModel model) { super(model); visuals = new TimelineVisuals(model); grid = new AxisRenderer(visuals); timeline = new TimelineRenderer(visuals); timelinePanel = new TimelinePanel(model); scroller = new ScrollingTimeline(); setLayout(new BorderLayout()); add(scroller, BorderLayout.CENTER); JPanel bottom = new JPanel(); bottom.setLayout(new BorderLayout()); add(bottom, BorderLayout.SOUTH); TimelineSlider slider = new TimelineSlider(visuals, 24 * 60 * 60 * 1000L, new Runnable() { @Override public void run() { redraw(); } }); bottom.add(slider, BorderLayout.CENTER); controls = new JPanel(); controls.setBackground(Color.white); controls.setLayout(new BorderLayout());//new GridLayout(2,1)); // top part of grid: zoom buttons. ComponentCluster buttons = new ComponentCluster("Zoom"); ImageIcon zoomOutIcon = new ImageIcon("images/zoom_out.gif"); JButton zoomOut = new JButton(zoomOutIcon); buttons.addContent(zoomOut); zoomOut.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Interval zoom = visuals.getViewInterval().subinterval(-1, 2).intersection(visuals.getGlobalInterval()); moveTime(zoom); } }); ImageIcon zoomOut100Icon = new ImageIcon("images/zoom_out_100.gif"); JButton zoomOutAll = new JButton(zoomOut100Icon); buttons.addContent(zoomOutAll); zoomOutAll.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { moveTime(visuals.getGlobalInterval()); } }); /* // UI for zooming to precisely fit the visible selection. // No one seemed to think this was so important, but we may want it again some day. // if you uncomment this, then also uncomment the line in reset(). ImageIcon zoomSelection=new ImageIcon("images/zoom_selection.gif"); fit=new JButton(zoomSelection); fit.setBackground(Color.white); buttons.addContent(fit); fit.setEnabled(false); fit.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { moveTime(visuals.getFitToVisibleRange()); }}); */ controls.add(buttons, BorderLayout.NORTH); // ok, now do second part of grid: layout style buttons. ComponentCluster layoutPanel = new ComponentCluster("Layout"); ButtonGroup layoutGroup = new ButtonGroup(); ImageIcon diagonalIcon = new ImageIcon("images/layout_diagonal.gif"); JRadioButton diagonal = new JRadioButton(diagonalIcon, true); diagonal.setSelectedIcon(new ImageIcon("images/layout_diagonal_selected.gif")); layoutPanel.addContent(diagonal); diagonal.addActionListener(new LayoutSetter(TimelineVisuals.Layout.TIGHT)); layoutGroup.add(diagonal); ImageIcon looseIcon = new ImageIcon("images/layout_loose.gif"); JRadioButton loose = new JRadioButton(looseIcon, false); loose.setSelectedIcon(new ImageIcon("images/layout_loose_selected.gif")); layoutPanel.addContent(loose); loose.addActionListener(new LayoutSetter(TimelineVisuals.Layout.LOOSE)); layoutGroup.add(loose); ImageIcon graphIcon = new ImageIcon("images/layout_graph.gif"); JRadioButton graph = new JRadioButton(graphIcon, false); graph.setSelectedIcon(new ImageIcon("images/layout_graph_selected.gif")); layoutPanel.addContent(graph); graph.addActionListener(new LayoutSetter(TimelineVisuals.Layout.GRAPH)); layoutGroup.add(graph); controls.add(layoutPanel, BorderLayout.CENTER); } class LayoutSetter implements ActionListener { TimelineVisuals.Layout layout; LayoutSetter(TimelineVisuals.Layout layout) { this.layout = layout; } @Override public void actionPerformed(ActionEvent e) { visuals.setLayoutStyle(layout); redraw(); } } void moveTime(Interval interval) { new TimeAnimator(interval).start(); } void redraw() { visuals.layout(); timelinePanel.drawVisualization(); repaint(); } @Override protected void onscreen(boolean majorChange) { visuals.init(majorChange); reset(majorChange); redraw(); scroller.calibrate(); } @Override protected void _note(TFEvent e) { visuals.note(e); reset(e.affectsRowSet()); } void reset(boolean forceViewChange) { if (forceViewChange || getModel().getViewInterval() == null) { int numSelected = getModel().getActs().size(); int numVisible = DBUtils.count(getModel().getActs(), visuals.getViewInterval(), getModel().getDB().getField(VirtualField.START)); if (numVisible < 10 && numSelected > numVisible) { moveTime(visuals.getFitToVisibleRange()); } } // uncomment this if we are using the fit button again. //fit.setEnabled(getModel().getActs().size() height); // bar.setMinimum(0); // is this double setting necessary? // bar.setMaximum(desired); // more testing is needed, it solved problems // bar.setVisibleAmount(height); // on certain Macs are one point. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { bar.setMinimum(0); bar.setMaximum(desired); bar.setVisibleAmount(height); } }); } } class TimelinePanel extends AbstractVisualizationView { public TimelinePanel(TFModel model) { super(model); addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { moveTime(visuals.getViewInterval().subinterval(.333, .667)); } } @Override public void mouseExited(MouseEvent e) { mouse.setLocation(new Point(-1, -1)); repaint(); } @Override public void mousePressed(MouseEvent e) { // was this a right-click or ctrl-click? ignore. if (e.isPopupTrigger()) { return; } // did we click on a date label? for (Mouseover o : objectLocations) { if (o.contains(e.getX(), e.getY()) && o.thing instanceof Interval) { moveTime((Interval) o.thing); return; } } // if not, prepare firstMouse.setLocation(e.getX(), e.getY()); mouseIsDown = true; repaint(); } @Override public void mouseReleased(MouseEvent event) { if (!mouseIsDown) // this means we had clicked on a date label. { return; } mouseIsDown = false; int a = firstMouse.x; int b = mouse.x; long start = visuals.getTimeScale().toTime(a); long end = visuals.getTimeScale().toTime(b); if (b - a < 0) { Interval total = visuals.getGlobalInterval(); long t = start; start = end; end = t; // reversed long S = visuals.getTimeScale().getInterval().start; long E = visuals.getTimeScale().getInterval().end; // start*M + B = S // end*M + B = E double M = (double)(S - E) / (start - end); long B = E - (long)(end * M); start = (long)(S*M) + B; end = (long)(E*M) + B; if (start < total.start) { start = total.start; } if (end > total.end) { end = total.end; } } moveTime(new Interval(start, end)); } }); addMouseWheelListener(new MouseAdapter() { @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(); repaint(); } }); } public RoughTime getTime(Point p) { TimeScale scale = visuals.getTimeScale(); long timestamp = scale.toTime(p.x); return new RoughTime(timestamp, TimeUnit.DAY); } protected void drawVisualization(Graphics2D g) { if (g == null) { return; } g.setColor(Color.white); g.fillRect(0, 0, 2 * getSize().width, getSize().height); visuals.setBounds(0, 0, getSize().width, getSize().height); objectLocations = new ArrayList(); timeline.render(g, objectLocations); grid.render(g, objectLocations); } protected boolean paintOnTop(Graphics2D g, int w, int h) { if (!mouseIsDown) { return false; } int a = mouse.x; int b = firstMouse.x; if (a > b) { g.setColor(new Color(255, 255, 120, 100)); g.fillRect(b, 0, a - b, h); } else { g.setColor(new Color(255, 120, 255, 100)); g.fillRect(a, 0, b - a, h); } g.setColor(Color.blue); g.drawLine(a, 0, a, h); g.drawLine(b, 0, b, h); return true; } } @Override public String getName() { return "Timeline"; } }