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;
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 looseIcon=new ImageIcon("images/layout_loose.gif");
JRadioButton loose=new JRadioButton(looseIcon, true);
loose.setSelectedIcon(new ImageIcon("images/layout_loose_selected.gif"));
layoutPanel.addContent(loose);
loose.addActionListener(new LayoutSetter(TimelineVisuals.Layout.LOOSE));
layoutGroup.add(loose);
ImageIcon diagonalIcon=new ImageIcon("images/layout_diagonal.gif");
JRadioButton diagonal=new JRadioButton(diagonalIcon, false);
diagonal.setSelectedIcon(new ImageIcon("images/layout_diagonal_selected.gif"));
layoutPanel.addContent(diagonal);
diagonal.addActionListener(new LayoutSetter(TimelineVisuals.Layout.TIGHT));
layoutGroup.add(diagonal);
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()<getModel().getDB().size());
redraw();
scroller.calibrate();
}
class TimeAnimator extends Thread
{
Interval i1, i2;
TimeAnimator(Interval i2)
{
this.i1=visuals.getViewInterval();
this.i2=i2;
}
TimeAnimator(Interval i1, Interval i2)
{
this.i1=i1;
this.i2=i2;
}
public void run()
{
int n=15;
for (int i=0; i<n; i++)
{
final long start=((n-i)*i1.start+i*i2.start)/n;
final long end=((n-i)*i1.end+i*i2.end)/n;
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
visuals.setTimeBounds(start, end);
redraw();
}});
sleep(20);
} catch (Exception e) {}
}
}
}
class ScrollingTimeline extends JPanel
{
JScrollBar bar;
public ScrollingTimeline()
{
setLayout(new BorderLayout());
add(timelinePanel, BorderLayout.CENTER);
bar=new JScrollBar(JScrollBar.VERTICAL);
add(bar, BorderLayout.EAST);
bar.addAdjustmentListener(new AdjustmentListener() {
@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
timeline.setDY(bar.getValue());
timelinePanel.drawVisualization();
timelinePanel.repaint();
}
});
}
public void setBounds(int x, int y, int w, int h)
{
super.setBounds(x,y,w,h);
calibrate();
}
void calibrate()
{
if (visuals==null)
return;
final int height=getSize().height;
final int desired=Math.max(height,visuals.getFullHeight());
bar.setVisible(desired>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 e) {
if (!mouseIsDown) // this means we had clicked on a date label.
return;
mouseIsDown=false;
int a=Math.min(mouse.x, firstMouse.x);
int b=Math.max(mouse.x, firstMouse.x);
long start, end;
if (b-a<16) // a click rather than a drag; just zoom in a bit
{
repaint();
return;
}
else
{
start=visuals.getTimeScale().toTime(a);
end=visuals.getTimeScale().toTime(b);
}
moveTime(new Interval(start,end));
}});
}
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<Mouseover>();
timeline.render(g, objectLocations);
grid.render(g, objectLocations);
}
protected boolean paintOnTop(Graphics2D g, int w, int h)
{
if (!mouseIsDown)
return false;
int a=Math.min(mouse.x, firstMouse.x);
int b=Math.max(mouse.x, firstMouse.x);
g.setColor(new Color(255,255,120,100));
g.fillRect(a,0,b-a,h);
g.setColor(Color.orange);
g.drawLine(a,0,a,h);
g.drawLine(b,0,b,h);
return true;
}
}
@Override
public String getName() {
return "Timeline";
}
}