Package jsky.timeline

Source Code of jsky.timeline.TimeLine

//=== File Prolog =============================================================
//  This code was developed by NASA, Goddard Space Flight Center, Code 588
//  for the Scientist's Expert Assistant (SEA) project.
//
//--- Contents ----------------------------------------------------------------
//  TimeLine
//
//--- Description -------------------------------------------------------------
//  A time line which can contain nodes that can be adjusted by time and duration
//
//--- Notes -------------------------------------------------------------------
//
//--- Development History -----------------------------------------------------
//
//  05/19/99  M. Fishman
//
//    Original implementation.
//
//--- DISCLAIMER---------------------------------------------------------------
//
//  This software is provided "as is" without any warranty of any kind, either
//  express, implied, or statutory, including, but not limited to, any
//  warranty that the software will conform to specification, any implied
//  warranties of merchantability, fitness for a particular purpose, and
//  freedom from infringement, and any warranty that the documentation will
//  conform to the program, or any warranty that the software will be error
//  free.
//
//  In no event shall NASA be liable for any damages, including, but not
//  limited to direct, indirect, special or consequential damages, arising out
//  of, resulting from, or in any way connected with this software, whether or
//  not based upon warranty, contract, tort or otherwise, whether or not
//  injury was sustained by persons or property or otherwise, and whether or
//  not loss was sustained from or arose out of the results of, or use of,
//  their software or services provided hereunder.
//
//=== End File Prolog =========================================================
// $Id: TimeLine.java,v 1.2 2009/02/21 16:43:10 abrighto Exp $

//package gov.nasa.gsfc.util.gui;

package jsky.timeline;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.util.*;
import java.util.List;

import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;

import jsky.science.Time;
import jsky.util.gui.BasicWindowMonitor;
import jsky.util.gui.DialogUtil;
import jsky.coords.HMS;

/**
* A time line which can contain nodes that can be adjusted by time and duration.
* <p/>
* <P>This code was developed by NASA, Goddard Space Flight Center, Code 588
* for the Scientist's Expert Assistant (SEA) project.
*
* @author M. Fishman
* @author A. Brighton (modified original version)
* @version $Revision: 1.2 $
*/
public class TimeLine extends JPanel {

    // constants for setUnitsType()
    public static final String DATE_VIEW = "Date View";
    public static final String TIME_VIEW = "Time View";

    public static final String DISPLAY_WINDOW_CHANGE = "Display Window Change";
    public static final String NODE_ADDED = "node added";
    public static final String NODE_REMOVED = "node removed";

    public static final String SELECTION_MODE = "Selection";
    public static final String ZOOM_MODE = "Zoom";

    protected static final double MIN_DISPLAY_WINDOW = 10.0;
    public static final Cursor DEFAULT_CURSOR = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);

    protected Comparator<TimeLineNode> _comparator = new TimeLineNodeComparator();
    protected float _handleHeight = 6;
    protected float _verticalSpacer = 18;
    protected Line2D.Float _centerLine = new Line2D.Float();

    protected Set<TimeLineNode> _nodes;
    protected List<VetoableChangeListener> _vetoableListeners;
    protected int _intervalCount;
    protected String _mode = SELECTION_MODE;
    protected String _unitType = Time.MINUTE;

    protected Time _displayStart;
    protected Time _displayEnd;
    protected Time _intervalInTime;
    protected TimeLineModel _model;

    // If true, draw the timeline start and end labels at the top, otherwise at the bottom
    protected boolean _labelsAtTop = false;


    /**
     * a listener for mouse events.
     */
    protected MouseAdapter _mouseListener = new MouseAdapter() {

        public void mouseClicked(MouseEvent evt) {
            handleMouseClicked(evt);
        }

        public void mousePressed(MouseEvent evt) {
            handleMousePressed(evt);
        }

        public void mouseReleased(MouseEvent evt) {
            handleMousePressed(evt);
        }

    };


    /**
     * an adapter used to handle mouse drag events
     */
    protected MouseMotionAdapter _mouseDragListener = new MouseMotionAdapter() {

        public void mouseDragged(MouseEvent evt) {
            // propogate event to nodes
            for (TimeLineNode node : _nodes) {
                node.handleMouseDragEvent(evt);
            }
            repaint();
        }

        public void mouseMoved(MouseEvent evt) {
            // propogate event to nodes
            for (TimeLineNode node : _nodes) {
                node.handleMouseMoveEvent(evt);
            }
            repaint();
        }
    };

    protected KeyAdapter _keyListener = new KeyAdapter() {

        public void keyPressed(KeyEvent evt) {
            handleKeyEvent(evt);
        }

    };

    protected VetoableChangeListener _myChildListener = new VetoableChangeListener() {
        public void vetoableChange(PropertyChangeEvent evt) throws DetailedPropertyVetoException {
            validatePropertyChange(evt);
            repaint();
        }
    };

    protected PropertyChangeListener _myModelListener = new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
            if (TimeLineModel.NODE_ADDED.equals(evt.getPropertyName())) {
                TimeLineNodeModel model = (TimeLineNodeModel) evt.getNewValue();
                boolean found = false;
                for (Iterator iter = getTimeLineNodesIterator(); iter.hasNext();) {
                    TimeLineNode node = (TimeLineNode) iter.next();
                    if (node.getModel() == model) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    Class nodeClass = model.getGUIClass();
                    try {
                        TimeLineNode node = (TimeLineNode) nodeClass.newInstance();
                        node.setModel(model);
                        addSilentTimeLineNode(node);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            } else if (TimeLineModel.NODE_REMOVED.equals(evt.getPropertyName())) {
                TimeLineNodeModel model = (TimeLineNodeModel) evt.getOldValue();
                for (Iterator iter = getTimeLineNodesIterator(); iter.hasNext();) {
                    TimeLineNode node = (TimeLineNode) iter.next();
                    if (node.getModel() == model) {
                        removeTimeLineNode(node);
                        break;
                    }
                }
            } else if (TimeLineModel.ALL_NODES_REMOVED.equals(evt.getPropertyName())) {
                removeAllTimeLineNodes();
            }
        }
    };

    /**
     * constructor
     */
    public TimeLine() {
        this(0, 50, 50);
    }


    /**
     * constructor
     *
     * @param interval the number of intervals in a 50 minute time line
     */
    public TimeLine(int interval) {
        this(0, 50, interval);
    }

    /**
     * constructor
     *
     * @param start the starting minute of the timeline
     * @param end the ending minute of the timeline
     * @param intervals the number of intervals on the timeline
     */
    public TimeLine(int start, int end, int intervals) {
        this(new Time((double) start, Time.MINUTE), new Time((double) end, Time.MINUTE), intervals);
    }

    /**
     * constructor
     *
     * @param start the starting time of the timeline
     * @param end the ending time of the timeline
     * @param intervals the number of intervals on the timeline
     */
    public TimeLine(Time start, Time end, int intervals) {
        this(new DefaultTimeLineModel(start, end, intervals));
    }


    /**
     * constructor
     *
     * @param model the time line model
     */
    public TimeLine(TimeLineModel model) {
        super();
        Time start = model.getStartTime();
        Time end = model.getEndTime();
        int intervals = model.getIntervalCount();
        MouseInputListener msListener = new MouseInputAdapter() {

            public void mousePressed(MouseEvent e) {
                setCursor(e);
            }

            public void mouseReleased(MouseEvent e) {
                setCursor(e);
            }

            public void mouseMoved(MouseEvent e) {
                setCursor(e);
            }

            private void setCursor(MouseEvent e) {
                Cursor cursor = DEFAULT_CURSOR;
                for (TimeLineNode node : _nodes) {
                    if (node.containsPoint(e.getPoint())) {
                        cursor = node.getCursor(e);
                        break;
                    }
                }
                TimeLine.this.setCursor(cursor);
            }
        };
        this.addMouseListener(msListener);
        this.addMouseMotionListener(msListener);
        setToolTipText("TimeLine");
        ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE);

        _displayStart = start;
        _displayEnd = end;
        _model = model;
        //_model.addPropertyChangeListener(_myModelListener);
        _intervalCount = intervals;
        double intervalsInTime = (end.getValue(Time.SECOND) - start.getValue(Time.SECOND)) /
                (double) _intervalCount;
        _intervalInTime = new Time(intervalsInTime, Time.SECOND);


        addMouseListener(_mouseListener);
        addMouseMotionListener(_mouseDragListener);
        addKeyListener(_keyListener);
        _nodes = new TreeSet<TimeLineNode>(_comparator);

        _vetoableListeners = Collections.synchronizedList(new ArrayList<VetoableChangeListener>(5));
        setBorder(BorderFactory.createEmptyBorder(20, 0, 70, 0));
    }

    /**
     * add a time line node to the time line
     */
    public void addTimeLineNode(TimeLineNode node) throws IllegalNodePositionException {
        if (!_nodes.contains(node)) {
            TimeLine oldParent = node.getParent();
            try {
                node.setParent(this);
                _nodes.add(node);
                validatePropertyChange(new PropertyChangeEvent(node,
                        TimeLine.NODE_ADDED,
                        null,
                        node));

                node.addVetoableChangeListener(_myChildListener);
                addVetoableChangeListener(node);
                _model.addTimeLineNode(node.getModel());
            } catch (DetailedPropertyVetoException ex) {
                if (oldParent != this) {
                    node.setParent(oldParent);
                    _nodes.remove(node);
                    _model.removeTimeLineNode(node.getModel());
                } else {
                    node.setParent(this);
                    node.addVetoableChangeListener(_myChildListener);
                    addVetoableChangeListener(node);
                }
                throw new IllegalNodePositionException("could not add node: " + node.getTimeLineNodeName());
            }
        }
        repaint();
    }


    /**
     * Add a time line node to the time line and save time by not checking or firing events.
     */
    public void addTimeLineNodeNoEvents(TimeLineNode node) throws IllegalNodePositionException {
        if (!_nodes.contains(node)) {
            node.setParent(this);
            _nodes.add(node);
            _model.addTimeLineNode(node.getModel());
            repaint();
        }
    }

    public TimeLineModel getModel() {
        return _model;
    }

    /**
     * Set the model (allan: added this method)
     */
    public void setModel(TimeLineModel model) {
        Time start = model.getStartTime();
        Time end = model.getEndTime();
        int intervals = model.getIntervalCount();

        _displayStart = start;
        _displayEnd = end;
        _model = model;
        //_model.addPropertyChangeListener(_myModelListener);
        _intervalCount = intervals;
        double intervalsInTime = (end.getValue(Time.SECOND) - start.getValue(Time.SECOND)) /
                (double) _intervalCount;
        _intervalInTime = new Time(intervalsInTime, Time.SECOND);
        _nodes = new TreeSet<TimeLineNode>(_comparator);

        _vetoableListeners = Collections.synchronizedList(new ArrayList<VetoableChangeListener>(5));
        repaint();
    }

    /**
     * add a time line node to the time line without checking its legality
     */
    protected void addSilentTimeLineNode(TimeLineNode node) {
        if (!_nodes.contains(node)) {
            node.setParent(this);
            _nodes.add(node);
            node.addVetoableChangeListener(_myChildListener);
            addVetoableChangeListener(node);
            _model.addTimeLineNode(node.getModel());
        }
    }

    /**
     * Set the unit types to display in the timeline.
     */
    public void setUnitsType(String unitType) {
        _unitType = unitType;
    }

    public boolean isLabelsAtTop() {
        return _labelsAtTop;
    }

    public void setLabelsAtTop(boolean b) {
        _labelsAtTop = b;
    }

    public float getHandleHeight() {
        return _handleHeight;
    }

    public void setHandleHeight(float f) {
        _handleHeight = f;
    }

    public float getVerticalSpacer() {
        return _verticalSpacer;
    }

    public void setVerticalSpacer(float f) {
        _verticalSpacer = f;
    }


    /**
     * get the unit types to display in the timeline
     */
    public String getUnitsType() {
        return _unitType;
    }

    /**
     * sets the diplay window of the timeline
     */
    public synchronized void setDisplayArea(Time start, Time end) {
        if ((_displayStart.getValue() != start.getValue()) || (_displayEnd.getValue() != end.getValue())) {
            Time oldStart = _displayStart;
            Time oldEnd = _displayEnd;
            _displayStart = start;
            _displayEnd = end;
            double intervalsInTime = (_displayEnd.getValue(Time.SECOND) - _displayStart.getValue(Time.SECOND)) /
                    (double) _intervalCount;
            _intervalInTime = new Time(intervalsInTime, Time.SECOND);
            try {
                fireVetoableChange(new PropertyChangeEvent(this, DISPLAY_WINDOW_CHANGE, null, null));
            } catch (DetailedPropertyVetoException ex) {
                setDisplayArea(oldStart, oldEnd);
            }


            repaint();
        }
    }

    /**
     * reset the display window to show all data
     */
    public void resetDisplayArea() {
        setDisplayArea(new Time(getStartTime().getValue()), new Time(getEndTime().getValue()));
    }

    /**
     * move the display window of the timeline by the specified amount
     */
    public synchronized void moveDisplayAreaBy(Time time) {
        if (time.getValue() != 0.0) {
            Time startTime = new Time(_displayStart.getValue(Time.SECOND) + time.getValue(Time.SECOND),
                    Time.SECOND);
            Time endTime = new Time(_displayEnd.getValue(Time.SECOND) + time.getValue(Time.SECOND),
                    Time.SECOND);
            setDisplayArea(startTime, endTime);

        }
        repaint();
    }

    /**
     * remove a time line node from the time line
     */
    public synchronized void removeTimeLineNode(TimeLineNode node) {
        if (_nodes.contains(node)) {
            try {
                node.removeVetoableChangeListener(_myChildListener);
                node.setParent(null);
                removeVetoableChangeListener(node);
                _nodes.remove(node);
                fireVetoableChange(new PropertyChangeEvent(node, NODE_REMOVED, node, null));
                _model.removeTimeLineNode(node.getModel());
            } catch (DetailedPropertyVetoException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * remove all time line nodes from time line
     */
    public void removeAllTimeLineNodes() {
        if (_nodes.size() > 0) {
            for (Iterator iter = _nodes.iterator(); iter.hasNext();) {
                TimeLineNode node = (TimeLineNode) iter.next();
                node.setParent(null);
                node.removeVetoableChangeListener(_myChildListener);
                removeVetoableChangeListener(node);
                iter.remove();
            }
            _model.removeAllTimeLineNodes();
        }
    }

    /**
     * get the time value of a single interval in the timeline
     */
    public Time getIntervalTime() {
        return _intervalInTime;
    }


    /**
     * get the number of intervals in the time line
     */
    public int getIntervalCount() {
        return _model.getIntervalCount();
    }

    /**
     * paint the component
     */
    protected void paintComponent(Graphics grph) {
        super.paintComponent(grph);

        Graphics2D g2 = (Graphics2D) grph;
        paintCenterLine(g2);
        paintStartLabel(g2);
        paintEndLabel(g2);
        paintNodes(g2);
    }

    /**
     * paint the component
     */
    protected void paintCenterLine(Graphics2D g2) {
        Dimension dim = getSize();

        // draw the center line
        _centerLine.x1 = _verticalSpacer;
        _centerLine.y1 = dim.height / 2f;
        _centerLine.x2 = dim.width - _verticalSpacer;
        _centerLine.y2 = dim.height / 2f;

        g2.setColor(getBackground());
        g2.fill3DRect((int) _centerLine.x1, (int) _centerLine.y1 - 1, (int) (_centerLine.x2 - _centerLine.x1), 3, false);

        Line2D.Float stopLine = new Line2D.Float();

        // draw left hand stop
        if (_displayStart.getValue(Time.SECOND) <= getStartTime().getValue(Time.SECOND)) {
            stopLine.x1 = _centerLine.x1 - 1;
            stopLine.x2 = _centerLine.x1 - 1;
            stopLine.y1 = (dim.height - _handleHeight) / 2f;
            stopLine.y2 = (dim.height + _handleHeight) / 2f;
            g2.fill3DRect((int) stopLine.x1 - 1, (int) stopLine.y1, 3, (int) (stopLine.y2 - stopLine.y1), false);
        }

        // draw right hand stop
        if (_displayEnd.getValue(Time.SECOND) >= getEndTime().getValue(Time.SECOND)) {
            stopLine.x1 = _centerLine.x2 + 2;
            stopLine.x2 = _centerLine.x2 + 2;
            stopLine.y1 = (dim.height - _handleHeight) / 2f;
            stopLine.y2 = (dim.height + _handleHeight) / 2f;
            g2.fill3DRect((int) stopLine.x1 - 1, (int) stopLine.y1, 3, (int) (stopLine.y2 - stopLine.y1), false);
        }
    }

    /**
     * paint the timeline start label in the current units.
     */
    protected void paintStartLabel(Graphics2D g2) {
        Dimension dim = getSize();
        String startStr = "";

        if (DATE_VIEW.equals(_unitType)) {
            DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT);
            startStr = format.format(getDateForTime(_displayStart));
        } else if (TIME_VIEW.equals(_unitType)) {
            HMS hms = new HMS(_displayStart.getValue(Time.HOUR));
            startStr = hms.toString();
        } else {
            DecimalFormat form = new DecimalFormat();
            form.setMaximumFractionDigits(2);
            startStr = form.format(_displayStart.getValue(_unitType));
        }

        Rectangle2D nameBounds = g2.getFontMetrics().getStringBounds(startStr, g2);
        g2.setColor(Color.black);

        float textX = _centerLine.x1 - (float) (1.0 + nameBounds.getWidth() / 2.0);
        //if (textX < clip.getX()) {
        //   textX = (float) clip.getX();
        //}

        float textY;
        if (_labelsAtTop) {
            textY = (float) nameBounds.getHeight();
        } else {
            int yOff = 32; // additional Y offset for start and end labels (allan)
            textY = (dim.height + _handleHeight) / 2f + (float) (nameBounds.getHeight() + yOff);
        }

        g2.drawString(startStr, textX, textY);
    }

    /**
     * paint the timeline end label in the current units.
     */
    protected void paintEndLabel(Graphics2D g2) {
        Dimension dim = getSize();
        String endStr = "";

        if (DATE_VIEW.equals(_unitType)) {
            DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT);
            endStr = format.format(getDateForTime(_displayEnd));
        } else if (TIME_VIEW.equals(_unitType)) {
            HMS hms = new HMS(_displayEnd.getValue(Time.HOUR));
            endStr = hms.toString();
        } else {
            DecimalFormat form = new DecimalFormat();
            form.setMaximumFractionDigits(2);
            endStr = form.format(_displayEnd.getValue(_unitType));
        }

        Rectangle2D nameBounds = g2.getFontMetrics().getStringBounds(endStr, g2);
        g2.setColor(Color.black);

        float textX = _centerLine.x2 + (float) (1.0 - nameBounds.getWidth() / 2.0);
        //if (((double) textX + nameBounds.getWidth()) > (clip.getX() + clip.getWidth())) {
        //   textX = (float) (clip.getX() + clip.getWidth() - nameBounds.getWidth());
        //}

        float textY;
        if (_labelsAtTop) {
            textY = (float) nameBounds.getHeight();
        } else {
            int yOff = 32; // additional Y offset for start and end labels (allan)
            textY = (dim.height + _handleHeight) / 2f + (float) (nameBounds.getHeight() + yOff);
        }

        g2.drawString(endStr, textX, textY);

        if (!DATE_VIEW.equals(_unitType) && !TIME_VIEW.equals(_unitType)) {
            String unitAbbrev = Time.getUnitsAbbrev(_unitType);
            nameBounds = g2.getFontMetrics().getStringBounds(unitAbbrev, g2);
            textX = _centerLine.x2 + (float) (1.0 - nameBounds.getWidth() / 2.0);
            textY = textY + (float) (nameBounds.getHeight()) - 2f;
            g2.drawString(unitAbbrev, textX, textY);
        }
    }

    /**
     * paint the timeline nodes.
     */
    protected void paintNodes(Graphics2D g2) {
        for (TimeLineNode node : _nodes) {
            node.paintTimeLineNode(g2);
        }
    }


    /**
     * get the displayed start of the timeline
     */
    public Time getDisplayStart() {
        return _displayStart;
    }

    /**
     * get the displayed end of the timeline
     */
    public Time getDisplayEnd() {
        return _displayEnd;
    }


    /**
     * get an iterator for the time line nodes
     */
    public Iterator getTimeLineNodesIterator() {
        return _nodes.iterator();
    }

    /**
     * this method handles any key events received by the panel.
     */
    public void handleKeyEvent(KeyEvent evt) {
        List<TimeLineNode> procList = Collections.synchronizedList(new ArrayList<TimeLineNode>(_nodes.size()));
        try {
            for (TimeLineNode node : _nodes) {
                node.handleKeyEvent(evt);
                procList.add(node);
            }
        } catch (DetailedPropertyVetoException ex) {
            for (TimeLineNode node : procList) {
                node.revertToPrevious();
            }
        }
    }


    /**
     * add a vetoable property change listener to the time line.
     */
    public void addVetoableChangeListener(VetoableChangeListener listener) {
        if (!_vetoableListeners.contains(listener)) {
            _vetoableListeners.add(listener);
        }

    }

    /**
     * remove a vetoable property change listener from the time line
     */
    public void removeVetoableChangeListener(VetoableChangeListener listener) {
        _vetoableListeners.remove(listener);
    }

    /**
     * set the mode of the time line
     */
    public void setMode(String mode) {
        _mode = mode;
    }


    /**
     * get the time value of an X coordinate in the TimeLine
     */
    public Time getTimeForPoint(float xValue) {
        double currentWindowWidth = _displayEnd.getValue(Time.SECOND) - _displayStart.getValue(Time.SECOND);

        xValue = xValue - _verticalSpacer;
        double time = (double) xValue * currentWindowWidth /
                ((double) getSize().width - (2.0 * _verticalSpacer));
        time = time + _displayStart.getValue(Time.SECOND);
        return new Time(time, Time.SECOND);

    }


    /**
     * get the x coordinate for specified time
     */
    public float getPointForTime(Time time) {
        double timeValue = time.getValue(Time.SECOND) - _displayStart.getValue(Time.SECOND);
        double currentWindowWidth = _displayEnd.getValue(Time.SECOND) - _displayStart.getValue(Time.SECOND);
        double xValue = timeValue * ((double) getSize().width - (2.0 * _verticalSpacer)) / currentWindowWidth;
        xValue = xValue + _verticalSpacer;
        return (float) Math.round(xValue);
    }

    /**
     * takes a time and converts it into a date where the new date is
     * equal to the start date + the time
     */
    public Date getDateForTime(Time time) {
        return _model.getDateForTime(time);
    }

    /**
     * takes a date and convert it to a time where the new time is
     * equal to the date - start date
     */
    public Time getTimeForDate(Date date) {
        return _model.getTimeForDate(date);
    }

    /**
     * set the date from which the timeline should start
     * <p/>
     * Note: if the date is not null then all time values are considered offsets from it
     */
    public void setStartDate(Date date) {
        _model.setStartDate(date);
    }

    /**
     * get the start date
     */
    public Date getStartDate() {
        return _model.getStartDate();
    }


    /**
     * get the starting value in the timeline
     */
    public Time getStartTime() {
        return _model.getStartTime();
    }

    /**
     * get the ending value of the timeline
     */
    public Time getEndTime() {
        return _model.getEndTime();
    }


    /**
     * get the list of nodes in the timeline which are currently selected
     */
    public List getSelectedNodes() {
        List<TimeLineNode> list = new ArrayList<TimeLineNode>();
        for (TimeLineNode node : _nodes) {
            if (node.isSelected()) {
                list.add(node);
            }
        }
        return list;
    }

    /**
     * get the time line's mode
     */
    public String getMode() {
        return _mode;
    }

    /**
     * takes a PropertyChangeEvent and throws a PropertyVetoException if anything in the event
     * would cause it to be rejected
     */
    protected void validatePropertyChange(PropertyChangeEvent evt) throws DetailedPropertyVetoException {
        TimeLineNode node = (TimeLineNode) evt.getSource();
        if ((node.getStartTime().getValue(Time.SECOND) < getStartTime().getValue(Time.SECOND))) {
            throw new DetailedPropertyVetoException(this, TimeLineNode.HIT_LEFT_EDGE,
                    "out of bounds", evt);
        } else if (node.getEndTime().getValue(Time.SECOND) > getEndTime().getValue(Time.SECOND)) {
            throw new DetailedPropertyVetoException(this, TimeLineNode.HIT_RIGHT_EDGE,
                    "out of bounds", evt);

        } else {
            if (evt.getPropertyName().equals(TimeLineNode.HIT_LEFT_EDGE)) {
                Time startTime = node.getStartTime();
                Time moveBy = new Time(startTime.getValue(Time.SECOND) - _displayStart.getValue(Time.SECOND),
                        Time.SECOND);
                moveDisplayAreaBy(moveBy);
            } else if (evt.getPropertyName().equals(TimeLineNode.HIT_RIGHT_EDGE)) {
                Time endTime = node.getEndTime();
                Time moveBy = new Time(endTime.getValue(Time.SECOND) - _displayEnd.getValue(Time.SECOND),
                        Time.SECOND);
                moveDisplayAreaBy(moveBy);
            } else {

                fireVetoableChange(evt);
            }
        }

    }

    /**
     * fires a vetoable change event to all listeners of the timeline
     */
    protected void fireVetoableChange(PropertyChangeEvent evt) throws DetailedPropertyVetoException {
        for (VetoableChangeListener listener : _vetoableListeners) {
            if (listener != evt.getSource()) {
                try {
                    listener.vetoableChange(evt);
                } catch (DetailedPropertyVetoException ex) {
                    throw ex;
                } catch (PropertyVetoException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    /**
     * this method handles any mouse clicked events
     */
    protected void handleMouseClicked(MouseEvent evt) {
        if (_mode.equals(SELECTION_MODE)) {
            handleSelectionEvent(evt);
        } else if (_mode.equals(ZOOM_MODE)) {
            handleZoomEvent(evt);

        }
    }

    /**
     * this method handles any mouse pressed or mouse released events
     */
    protected void handleMousePressed(MouseEvent evt) {
        if (_mode.equals(SELECTION_MODE)) {
            handleSelectionEvent(evt);
        } else if (_mode.equals(ZOOM_MODE)) {
            //do nothing
        }
    }

    /**
     * zooms the timeline in or out
     */
    private void handleZoomEvent(MouseEvent evt) {

        if (SwingUtilities.isRightMouseButton(evt)) {
            zoomOut(evt.getPoint());
        } else {

            zoomIn(evt.getPoint());
        }


    }

    /**
     * zoom the time line in
     */
    public void zoomIn() {
        int x = (int) Math.round((_centerLine.x1 + _centerLine.x2) / 2.0);
        int y = Math.round(_centerLine.y1);
        zoomIn(new Point(x, y));
    }

    /**
     * zoom the time line out
     */
    public void zoomOut() {
        int x = (int) Math.round((_centerLine.x1 + _centerLine.x2) / 2.0);
        int y = Math.round(_centerLine.y1);
        zoomOut(new Point(x, y));
    }

    /**
     * zoom the time line in with center point centerPt being at the center
     *
     * @param centerPt the point to center the zoom around
     */
    public void zoomIn(Point centerPt) {
        Time center = getTimeForPoint(centerPt.x);
        double currentWindowWidth = _displayEnd.getValue(Time.SECOND)
                - _displayStart.getValue(Time.SECOND);

        currentWindowWidth = currentWindowWidth / 2.0;

        if (currentWindowWidth < MIN_DISPLAY_WINDOW) {
            currentWindowWidth = MIN_DISPLAY_WINDOW;
        }


        double startX = Math.floor((center.getValue(Time.SECOND) - currentWindowWidth / 2.0));
        double endX = Math.floor((center.getValue(Time.SECOND) + currentWindowWidth / 2.0));
        if (startX < getStartTime().getValue(Time.SECOND)) {
            double adjust = getStartTime().getValue(Time.SECOND) - startX;
            startX += adjust;
            endX += adjust;
        }
        if (endX > getEndTime().getValue(Time.SECOND)) {
            double adjust = endX - getEndTime().getValue(Time.SECOND);
            startX -= adjust;
            endX -= adjust;
        }
        Time startTime = new Time(startX, Time.SECOND);
        Time endTime = new Time(endX, Time.SECOND);
        setDisplayArea(startTime, endTime);
    }


    /**
     * zoom the time line out with center point centerPt being at the center
     *
     * @param centerPt the point to center the zoom around
     */
    public void zoomOut(Point centerPt) {
        Time center = getTimeForPoint(centerPt.x);
        double currentWindowWidth = _displayEnd.getValue(Time.SECOND)
                - _displayStart.getValue(Time.SECOND);
        currentWindowWidth = currentWindowWidth * 2;
        double maxWidth = getEndTime().getValue(Time.SECOND)
                - getStartTime().getValue(Time.SECOND);
        if (currentWindowWidth > maxWidth) {
            currentWindowWidth = maxWidth;
        }


        double startX = Math.floor((center.getValue(Time.SECOND) - currentWindowWidth / 2.0));
        double endX = Math.floor((center.getValue(Time.SECOND) + currentWindowWidth / 2.0));
        if (startX < getStartTime().getValue(Time.SECOND)) {
            double adjust = getStartTime().getValue(Time.SECOND) - startX;
            startX += adjust;
            endX += adjust;
        }
        if (endX > getEndTime().getValue(Time.SECOND)) {
            double adjust = endX - getEndTime().getValue(Time.SECOND);
            startX -= adjust;
            endX -= adjust;
        }
        Time startTime = new Time(startX, Time.SECOND);
        Time endTime = new Time(endX, Time.SECOND);
        setDisplayArea(startTime, endTime);

    }


    /**
     * propogates a selection event down to all timeline nodes
     */
    private void handleSelectionEvent(MouseEvent evt) {
        for (TimeLineNode node : _nodes) {
            node.handleMouseEvent(evt);
        }
    }

    /**
     * inner class used for sorting time line nodes
     */
    private static class TimeLineNodeComparator implements Comparator<TimeLineNode> {
        public int compare(TimeLineNode o1,
                           TimeLineNode o2) {
            double start1 = o1.getStartTime().getValue(Time.SECOND);
            double start2 = o2.getStartTime().getValue(Time.SECOND);
            return (int) Math.round(start1 - start2);

        }
    }

    public String getToolTipText(MouseEvent event) {
        String result = null;
        Point pt = event.getPoint();
        for (TimeLineNode node : _nodes) {
            if (node.containsPoint(pt)) {
                result = node.getDescription(pt);
                break;
            }

        }
        return result;
    }

    /**
     * test main (allan)
     */
    public static void main(String[] args) {
        JFrame frame = new JFrame("TimeLine");
        TimeLine timeLine = new TimeLine();

        TimeLineModel model = new DefaultTimeLineModel(0, 40, 10);
        timeLine.setModel(model);

        try {
            timeLine.addTimeLineNode(new BlockTimeLineNode(new Time(0), new Time(5.0 * 60.0), "Block 1"));

            String label1 = "Label 1";
            DefaultTimeLineNode node1 = new DefaultTimeLineNode(new Time(8.0 * 60.0), new Time(12.0 * 60.0), label1) {

                public String getDescription(Point pt) {
                    // Note: like tooltip for node, can return plain or HTML text here,
                    // see gov.nasa.gsfc.sea.exposureplanner.gui.ExposureTimeLineNodeModel for example
                    String details = "<html><font size='-2'>";
                    details += "<table width=" + '"' + "100%" + '"' + " border=0 cellpadding=0 cellspacing=0 >";
                    details += "<caption align='TOP'><font siz='-1'><div align=center><b>Node Info</b></div></font></caption>";
                    details += "Test info";
                    details += "</table>";
                    details += "</font></html>";
                    return details;
                }
            };
            timeLine.addTimeLineNode(node1);
            node1.addVetoableChangeListener(new VetoableChangeListener() {

                public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
                    System.out.println("XXX node 1 changed: " + evt);
                }
            });

            String label2 = "Label 2";
            DefaultTimeLineNode node2 = new DefaultTimeLineNode(new Time(24.0 * 60.0), new Time(30.0 * 60.0), label2) {

                public String getDescription(Point pt) {
                    return "Node Description";
                }
            };
            timeLine.addTimeLineNode(node2);
            node2.addVetoableChangeListener(new VetoableChangeListener() {

                public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
                    System.out.println("XXX node 2 changed: " + evt);
                }
            });

            // Note: to reset the timeline to empty, you could do this:
            //TimeLineModel model = new DefaultTimeLineModel(10);
            //timeLine.setModel(model);

        } catch (Exception e) {
            DialogUtil.error(e);
        }

        timeLine.setPreferredSize(new Dimension(400, 100));
        frame.getContentPane().add(new JScrollPane(timeLine), BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
        frame.addWindowListener(new BasicWindowMonitor());
    }
}
TOP

Related Classes of jsky.timeline.TimeLine

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.