/********************************************************************************
* *
* (c) Copyright 2010 Verizon Communications USA and The Open University UK *
* *
* This software is freely distributed in accordance with *
* the GNU Lesser General Public (LGPL) license, version 3 or later *
* as published by the Free Software Foundation. *
* For details see LGPL: http://www.fsf.org/licensing/licenses/lgpl.html *
* and GPL: http://www.fsf.org/licensing/licenses/gpl-3.0.html *
* *
* This software is provided by the copyright holders and contributors "as is" *
* and any express or implied warranties, including, but not limited to, the *
* implied warranties of merchantability and fitness for a particular purpose *
* are disclaimed. In no event shall the copyright owner or contributors be *
* liable for any direct, indirect, incidental, special, exemplary, or *
* consequential damages (including, but not limited to, procurement of *
* substitute goods or services; loss of use, data, or profits; or business *
* interruption) however caused and on any theory of liability, whether in *
* contract, strict liability, or tort (including negligence or otherwise) *
* arising in any way out of the use of this software, even if advised of the *
* possibility of such damage. *
* *
********************************************************************************/
package com.compendium.ui.movie;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import javax.media.Controller;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.MediaTimeSetEvent;
import javax.media.Player;
import javax.media.Time;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.compendium.ProjectCompendium;
import com.compendium.core.ICoreConstants;
import com.compendium.core.datamodel.Model;
import com.compendium.core.datamodel.NodePosition;
import com.compendium.core.datamodel.NodePositionTime;
import com.compendium.core.datamodel.NodeSummary;
import com.compendium.core.datamodel.TimeMapView;
import com.compendium.ui.UIImages;
import com.compendium.ui.UILink;
import com.compendium.ui.UINode;
import com.sun.media.util.MediaThread;
public class UITimeLineForNode extends JComponent
implements MouseListener, MouseMotionListener,
ComponentListener, PropertyChangeListener, Runnable, ControllerListener {
/**
* class's own logger
*/
final Logger log = LoggerFactory.getLogger(getClass());
/** the colour of the grabber bar element */
//private static final Color GRABBER_COLOUR = Color.darkGray;
/** the colour of the time span elements.*/
private static final Color SPAN_COLOUR = Color.darkGray;
/** the colour of the time span elements.*/
private static final Color SELECTED_SPAN_COLOUR = Color.yellow;
/** at what distance inpixels from the grabber to implement the snap.*/
private static final int SNAP_SENSITIVITY = 20;
protected boolean justSeeked = false;
protected boolean stopTimer = false;
private MediaThread timer = null;
private Integer localLock = new Integer(0);
private boolean resetMediaTime = false;
int width;
int height;
/** The icon that is used each end of a timespan fop grabbing and expanding the span.*/
private ImageIcon iGrabberMini = null;
int miniGrabberWidth;
int miniGrabberHeight;
boolean leftGrabber = false;
boolean rightGrabber = false;
boolean entered;
boolean dragging = false;
int leftBorder = 0;
int rightBorder = 0;
int sliderWidth;
long pressedAt = 0;
private TimeSpan currentSpan = null;
private boolean onSpan = false;
private MasterTimer player;
private UITimeLinesController controlPanel;
private UINode oNode = null;
private TimeMapView oTimeMapView = null;
private UIMovieMapViewPane oViewPane = null;
private UINodeTimeLinePopupMenu popup = null;
private Vector<NodePositionTime> vtTimes = null;
private Hashtable htTimeSpans = null;
/** Holds the currently selected items in this timeline */
private Hashtable<String, NodePositionTime> selectedItems = new Hashtable<String, NodePositionTime>();
/** How many milliseconds of a second that a screen pixel represents. */
private int pixel_time_scale = UITimeLinesController.DEFAULT_PIXEL_TIME_SCALE;
public UITimeLineForNode(UINode uinode, TimeMapView view, UITimeLinesController cp, MasterTimer p) {
this.oNode = uinode;
//this.oNode.addPropertyChangeListener(this);
//NodePosition pos = uinode.getNodePosition();
//pos.addPropertyChangeListener(this);
this.oViewPane = (UIMovieMapViewPane)oNode.getViewPane();
this.oTimeMapView = view;
//this.oTimeMapView.addPropertyChangeListener(this);
this.controlPanel = cp;
this.player = p;
//this.player.addControllerListener ( this );
NodeSummary node = uinode.getNode();
Hashtable times = oTimeMapView.getTimesForNode(node.getId());
vtTimes = new Vector<NodePositionTime>(times.size());
for (Enumeration ex = times.elements(); ex.hasMoreElements();) {
NodePositionTime nextTime = (NodePositionTime)ex.nextElement();
vtTimes.add(nextTime);
}
Object[] sa = new Object[vtTimes.size()];
vtTimes.copyInto(sa);
List l = Arrays.asList(sa);
Collections.sort(l, new Comparator() {
public int compare(Object o1, Object o2) {
NodePositionTime data1 = (NodePositionTime)o1;
NodePositionTime data2 = (NodePositionTime)o2;
long s1 = data1.getTimeToShow();
long s2 = data2.getTimeToShow();
return (new Double(s1).compareTo(new Double(s2)));
}
});
vtTimes.removeAllElements();
vtTimes.addAll(l);
int countj = vtTimes.size();
if (vtTimes.size() > 0) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(0);
long starttime = nextTime.getTimeToShow();
if (starttime > 0) {
uinode.setVisible(false);
} else {
uinode.setLocation(nextTime.getXPos(), nextTime.getYPos());
uinode.updateLinks();
}
}
// Load the images
iGrabberMini = new ImageIcon(UIImages.sPATH+"grabbershort.gif"); //$NON-NLS-1$
miniGrabberWidth = iGrabberMini.getIconWidth();
miniGrabberHeight = iGrabberMini.getIconHeight();
leftBorder = UITimeLinesController.TIMELINE_LEFT_OFFSET;
rightBorder = controlPanel.rightBorder;
entered = false;
//this.height = 22;
sliderWidth = this.width - leftBorder - rightBorder;
}
/**
* Set the new scale to use to draw this time line header, then redraw it.
* @param scale the scale to use - in hundreths of a second per pixel.
*/
public void setScale(int scale) {
this.pixel_time_scale = scale;
setSize(controlPanel.timeline_length, getHeight());
setPreferredSize(new Dimension(controlPanel.timeline_length, getHeight()));
repaint();
}
//*********************** RUNNABLE RELATED STUFF ***************************/
public void addNotify() {
super.addNotify();
timer = new MediaThread(this);
timer.setName("UITimeLineForNode thread"); //$NON-NLS-1$
timer.useControlPriority();
this.oNode.addPropertyChangeListener(this);
this.oTimeMapView.addPropertyChangeListener(this);
NodePosition pos = oNode.getNodePosition();
pos.addPropertyChangeListener(this);
this.player.addControllerListener ( this );
addMouseListener( this );
addMouseMotionListener( this );
this.addComponentListener ( this );
stopTimer = false;
timer.start();
}
// Cannot make removeNotify synchronized. It will deadlock
// with other mouse event listeners. So we'll have to create
// another lock to synchronize removeNotify and dispose.
Object disposeLock = new Object();
Object syncStop = new Object();
public void removeNotify() {
if (timer != null) {
synchronized (syncStop) {
stopTimer = true;
timer = null;
}
}
oNode.removePropertyChangeListener(this);
NodePosition pos = oNode.getNodePosition();
pos.removePropertyChangeListener(this);
oTimeMapView.removePropertyChangeListener(this);
this.player.removeControllerListener ( this );
removeComponentListener ( this );
removeMouseListener( this );
removeMouseMotionListener( this );
player.removeControllerListener ( this );
super.removeNotify();
}
public synchronized void dispose() {
synchronized (syncStop) {
if (timer != null) {
stopTimer = true;
}
}
timer = null;
}
public void run() {
int counter = 0;
int pausecnt = -1;
int sleepTime;
boolean doUpdate = true;
while (!stopTimer) {
try {
if ( player != null && player.getState() == Controller.Started) {
doUpdate = true;
pausecnt = -1;
} else if (player != null && pausecnt < 5) {
pausecnt ++;
doUpdate = true;
} else if ( resetMediaTime ) {
doUpdate = true;
resetMediaTime = false;
} else {
doUpdate = false;
}
try {
if (doUpdate) {
long nanoDuration = player.getDuration().getNanoseconds();
if (nanoDuration >= 0) {
long nanoTime = player.getMediaTime().getNanoseconds();
long currentTimeMilliseconds = TimeUnit.NANOSECONDS.toMillis(nanoTime);
setCurrentNodeView(currentTimeMilliseconds);
}
}
} catch (Exception e) { }
sleepTime = (isEnabled() ? 200 : 1000);
try { Thread.sleep(sleepTime); } catch (Exception e) {}
counter++;
if (counter == 1000/sleepTime) {
counter = 0;
}
if (justSeeked) {
justSeeked = false;
try { Thread.sleep(1000); } catch (Exception e) {}
}
} catch (Exception e) {}
}
}
public synchronized void controllerUpdate ( ControllerEvent event ) {
synchronized (localLock) {
if (player == null)
return;
if (event instanceof MediaTimeSetEvent) {
Thread.yield();
resetMediaTime = true;
}
}
}
//*********************** PROPERTY CHANGE LISTENER *************************/
/**
* Handles property change events for the TimeMapView else calls super.
* @param evt, the associated PropertyChangeEvent object.
*/
public void propertyChange(PropertyChangeEvent evt) {
String prop = evt.getPropertyName();
Object source = evt.getSource();
//Object oldvalue = evt.getOldValue();
Object newvalue = evt.getNewValue();
if (source instanceof TimeMapView) {
if (prop.equals(TimeMapView.TIME_ADDED_PROPERTY)) {
NodePositionTime newtime = (NodePositionTime)newvalue;
if (newtime.getNode().getId().equals(oNode.getNode().getId())) {
vtTimes.add(newtime);
}
// redraw main if required.
long newPoint = newtime.getTimeToHide()/pixel_time_scale;
if (newPoint > sliderWidth) {
controlPanel.recalculateRequiredTimeline();
}
} else if (prop.equals(TimeMapView.TIME_CHANGED_PROPERTY)) {
NodePositionTime newtime = (NodePositionTime)newvalue;
int count = vtTimes.size();
for (int i=0; i< count; i++) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
if (nextTime.getId().equals(newtime.getId())) {
vtTimes.remove(nextTime);
vtTimes.add(newtime);
long newPoint = newtime.getTimeToHide()/pixel_time_scale;
if (newPoint > sliderWidth) {
controlPanel.recalculateRequiredTimeline();
}
break;
}
}
//If an update happens while a selection is in place
// update the object in the selectedItems list.
if (selectedItems.containsKey(newtime.getId())) {
selectedItems.put(newtime.getId(), newtime);
}
}
if (prop.equals(TimeMapView.TIME_REMOVED_PROPERTY)) {
String id = (String)newvalue;
int count = vtTimes.size();
for (int i=0; i< count; i++) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
if (nextTime.getId().equals(id)) {
vtTimes.remove(nextTime);
//controlPanel.recalculateRequiredTimeline();
break;
}
}
//If an update happens while a selection is in place
// update the object in the selectedItems list.
if (selectedItems.containsKey(id)) {
selectedItems.remove(id);
}
}
repaint();
} else if (source instanceof NodePosition) {
// if the user moves the node then on mouse release, update the relevant time span
if (prop.equals(NodePosition.POSITION_PROPERTY)) {
refreshNodeTimeDialog();
}
}
}
/**
* Refresh any open node dialog time panels, if required.
*/
public void refreshNodeTimeDialog() {
Long time = new Double(player.getMediaTime().getNanoseconds()).longValue();
long millitime = TimeUnit.NANOSECONDS.toMillis(time.longValue());
int count = vtTimes.size();
if (count > 0) {
for (int i=0; i < count; i++) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
long starttime = nextTime.getTimeToShow();
long stoptime = nextTime.getTimeToHide();
if (millitime >= starttime && millitime < stoptime || (starttime == 0 && stoptime == 0)) {
Point loc = oNode.getLocation();
try {
this.oTimeMapView.updateNodeTime(nextTime.getId(), oNode.getNode().getId(), nextTime.getTimeToShow(), nextTime.getTimeToHide(), loc.x, loc.y);
oNode.refreshTimeDialog();
} catch(Exception ex) {
log.error("Error...", ex);
}
break;
}
}
}
}
/**
* Return the UINode associated with this time line.
* @return
*/
public UINode getNode() {
return this.oNode;
}
/**
* Jump to the first time this node is show.
*/
public void showFirstTime() {
if (vtTimes.size() > 0) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(0);
long starttime = nextTime.getTimeToShow();
long newPosition = starttime/pixel_time_scale;
sliderSeek(newPosition);
}
}
/**
* Show/Hide nodes for the given time (in milliseconds)
* @param time current time line time position in milliseconds
*/
public void setCurrentNodeView(long time) {
int count = vtTimes.size();
boolean inVisiblePeriod = false;
int x=-1;
int y=-1;
if (count == 0) {
inVisiblePeriod = true;
} else {
NodePositionTime currentTime = null;
for (int i=0; i < count; i++) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
long starttime = nextTime.getTimeToShow();
long stoptime = nextTime.getTimeToHide();
int xPos = nextTime.getXPos();
int yPos = nextTime.getYPos();
if (time >= starttime && time < stoptime || (starttime == 0 && stoptime == 0)) {
//log.info("setCurrent for="+nextTime.getNode().getLabel());
//log.info("x="+nextTime.getXPos());
//log.info("y="+nextTime.getYPos());
inVisiblePeriod = true;
currentTime = nextTime;
x = xPos;
y = yPos;
break;
}
}
/*if (inVisiblePeriod) {
oNode.refreshTimeDialog(currentTime);
} else {
oNode.refreshTimeDialog(null);
}*/
}
if (inVisiblePeriod) {
oNode.setVisible(true);
for (Enumeration e = oNode.getLinks(); e.hasMoreElements();) {
UILink link = (UILink)e.nextElement();
UINode from = link.getFromNode();
UINode to = link.getToNode();
if (from.isVisible() && to.isVisible()) {
link.setVisible(true);
}
}
if (x !=-1 && y !=-1) {
oNode.setLocation(x, y);
}
oNode.updateLinks();
} else {
oNode.setVisible(false);
for (Enumeration e = oNode.getLinks(); e.hasMoreElements();) {
UILink link = (UILink)e.nextElement();
link.setVisible(false);
}
}
}
/**
* Paint this time line
* @param g the graphics object to use to paint this component.
*/
public void paintComponent(Graphics g) {
int y = (getHeight() / 2) - 2;
g.setColor( getBackground().darker() );
g.drawRect (leftBorder, y, sliderWidth, 3);
g.setColor(getBackground());
g.draw3DRect(leftBorder, y, sliderWidth, 3, false);
if (isEnabled()) {
int count = vtTimes.size();
if (htTimeSpans != null) {
htTimeSpans.clear();
} else {
this.htTimeSpans = new Hashtable(count);
}
int spanWidth = 0;
for (int i=0; i<count; i++) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
long starttime = nextTime.getTimeToShow();
int start = new Long(starttime/pixel_time_scale).intValue();
// If they have switch to a short timeline for example this could happen.
// So just don't paint spans outside the timeline timespan.
if (start > sliderWidth)
start = sliderWidth;
if (start < 0)
start = 0;
long stoptime = nextTime.getTimeToHide();
int stop = new Long(stoptime/pixel_time_scale).intValue();
if (stop > sliderWidth)
stop = sliderWidth;
if (stop < 0)
stop = sliderWidth;
if (start > stop) {
continue;
}
spanWidth = stop-start;
if (this.selectedItems.containsKey(nextTime.getId())) {
g.setColor( getBackground().darker() );
g.drawRect (start+leftBorder, y-1, spanWidth, 6);
g.setColor( SELECTED_SPAN_COLOUR );
g.fillRect(start+leftBorder+1, y, spanWidth-1, 4);
} else {
g.setColor( SPAN_COLOUR );
g.fillRect(start+leftBorder, y-1, spanWidth, 6);
}
//if (spanWidth >= 16) {
iGrabberMini.paintIcon(this, g, start+leftBorder, y-2);
iGrabberMini.paintIcon(this, g, start+leftBorder+(stop-start)-miniGrabberWidth, y-2);
//g.drawImage(imageMiniGrabber, start+leftBorder, y-2, this);
//g.drawImage(imageMiniGrabber, start+leftBorder+(stop-start)-miniGrabberWidth, y-2, this);
//}
TimeSpan span = new TimeSpan(nextTime);
span.setLeftGrabber(new Rectangle(start+leftBorder, y-2, miniGrabberWidth, miniGrabberHeight));
span.setRightGrabber(new Rectangle(start+leftBorder+(stop-start)-miniGrabberWidth, y-2, miniGrabberWidth, miniGrabberHeight));
htTimeSpans.put(new Rectangle(start+leftBorder, y-1, stop-start, 6), span);
}
}
}
//public Dimension getPreferredSize() {
// return new Dimension(super.getPreferredSize().width, UITimeLinesController.ROW_HEIGHT);
//}
/**
* Set the MasterTimer time to reflect the current location that the bar has been dragged to.
* @param pixels the current location of the bar in pixels.
*/
private void sliderSeek(long pixels) {
if (player == null)
return;
long millisvalue = (pixels*pixel_time_scale);
long locationNano = TimeUnit.MILLISECONDS.toNanos(millisvalue);
justSeeked = true;
if (locationNano >= 0) {
long duration = player.getDuration().getNanoseconds();
if (locationNano > duration) {
locationNano = duration;
}
player.setMediaTime(new Time(locationNano));
controlPanel.setMovieTimes(millisvalue);
}
}
/* public float sliderToSeek(int x) {
float s = (float)(x) / (float)(sliderWidth);
return s;
}*/
public int mouseToSlider(int x) {
if (x < leftBorder)
x = leftBorder;
if (x > this.width - rightBorder)
x = this.width - rightBorder;
x -= leftBorder;
return x;
}
private TimeSpan isInTimeSpan(Point p) {
if (htTimeSpans != null) {
for (Enumeration e = this.htTimeSpans.keys(); e.hasMoreElements();) {
Rectangle nextRec = (Rectangle)e.nextElement();
if (nextRec.contains(p)) {
return (TimeSpan)htTimeSpans.get(nextRec);
}
}
}
return null;
}
public void mousePressed(MouseEvent e) {
if (!isEnabled() || this.player == null || this.player.getState() == Player.Started)
return;
onSpan = false;
leftGrabber = false;
rightGrabber = false;
dragging = false;
// Find out where you have clicked.
// In a time span, on the grabber, on the bar.
TimeSpan span = isInTimeSpan(e.getPoint());
if (span != null) {
//NodePositionTime time = span.getTime();
onSpan = true;
controlPanel.setCursor(new Cursor(java.awt.Cursor.HAND_CURSOR));
pressedAt = e.getX();
if (span.getLeftGrabber().contains(e.getPoint())) {
this.leftGrabber = true;
} else if (span.getRightGrabber().contains(e.getPoint())) {
this.rightGrabber = true;
}
if (leftGrabber || rightGrabber) {
//controlPanel.clearSelection();
controlPanel.setCursor(new Cursor(java.awt.Cursor.E_RESIZE_CURSOR));
}
currentSpan = span;
} /*else {
onBar = true;
grabberPosition = mouseToSlider(e.getX());
controlPanel.setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR));
repaint();
}*/
}
public synchronized void mouseReleased(MouseEvent e) {
if (!isEnabled() || player == null)
return;
//boolean isRightMouse = SwingUtilities.isRightMouseButton(e);
boolean isLeftMouse = SwingUtilities.isLeftMouseButton(e);
if (ProjectCompendium.isMac &&
(e.getButton() == 3 && e.isShiftDown())) {
//isRightMouse = true;
isLeftMouse = false;
}
controlPanel.setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR));
if (dragging) {
if (onSpan && isLeftMouse && dragging){
NodePositionTime time = currentSpan.getTime();
Long currenttime = new Double(player.getMediaTime().getNanoseconds()).longValue();
long millitime = TimeUnit.NANOSECONDS.toMillis(currenttime.longValue());
if (leftGrabber || rightGrabber) {
long show = time.getTimeToShow();
long hide = time.getTimeToHide();
Point loc = oNode.getLocation();
try {
this.oTimeMapView.updateNodeTime(time.getId(), oNode.getNode().getId(), show, hide, loc.x, loc.y);
setCurrentNodeView(millitime);
} catch(Exception ex) {
log.error("Error...", ex);
}
} else if (!leftGrabber && !rightGrabber) {
if (selectedItems.containsKey(time.getId())) {
this.controlPanel.dragComplete(time.getId());
} else {
long show = time.getTimeToShow();
long hide = time.getTimeToHide();
Point loc = oNode.getLocation();
try {
this.oTimeMapView.updateNodeTime(time.getId(), oNode.getNode().getId(), show, hide, loc.x, loc.y);
setCurrentNodeView(millitime);
} catch(Exception ex) {
log.error("Error...", ex);
}
}
}
}
}
dragging = false;
pressedAt = 0;
repaint();
}
public synchronized void mouseDragged(MouseEvent e) {
if (!isEnabled() || player == null)
return;
//boolean isRightMouse = SwingUtilities.isRightMouseButton(e);
boolean isLeftMouse = SwingUtilities.isLeftMouseButton(e);
if (ProjectCompendium.isMac &&
(e.getButton() == 3 && e.isShiftDown())) {
isLeftMouse = false;
}
dragging = true;
if (onSpan && isLeftMouse && (leftGrabber || rightGrabber)) {
int newPosition = mouseToSlider(e.getX());
NodePositionTime time = currentSpan.getTime();
long show = time.getTimeToShow();
long hide = time.getTimeToHide();
int leftGrabberCheck = currentSpan.leftGrabber.x+currentSpan.leftGrabber.width;
int rightGrabberCheck = currentSpan.rightGrabber.x;
if (leftGrabber && newPosition+currentSpan.leftGrabber.width > rightGrabberCheck) {
newPosition = rightGrabberCheck-1-currentSpan.leftGrabber.width;
} else if (rightGrabber && newPosition < leftGrabberCheck) {
newPosition = leftGrabberCheck+3;
}
boolean snapTo = false;
long millisecondsNew = (newPosition*pixel_time_scale);
long milliseconds = snapToGrabber(newPosition);
if (milliseconds != millisecondsNew) {
snapTo = true;
}
if (milliseconds >= 0) {
if (leftGrabber) {
show = milliseconds;
} else if (rightGrabber) {
hide = milliseconds;
}
if (!checkForOverlap(time.getId(), show, hide)) {
if (leftGrabber) {
time.setTimeToShow(show);
} else if (rightGrabber) {
time.setTimeToHide(hide);
//if (hide > sliderWidth) {
this.controlPanel.recalculateRequiredTimeline();
//}
}
}
}
if (snapTo) {
Point loc = oNode.getLocation();
try {
this.oTimeMapView.updateNodeTime(time.getId(), oNode.getNode().getId(), show, hide, loc.x, loc.y);
Long currenttime = new Double(player.getMediaTime().getNanoseconds()).longValue();
long millitime = TimeUnit.NANOSECONDS.toMillis(currenttime.longValue());
setCurrentNodeView(millitime);
} catch(Exception ex) {
log.error("Error...", ex);
}
leftGrabber = false;
rightGrabber = false;
onSpan = false;
pressedAt = 0;
}
} else if (onSpan && isLeftMouse && !leftGrabber && !rightGrabber) {
// drag whole span along bar.
int newPosition = mouseToSlider(e.getX());
NodePositionTime time = currentSpan.getTime();
long show = time.getTimeToShow();
long hide = time.getTimeToHide();
long duration = hide-show;
long offset = (newPosition-pressedAt);
long millioffset = (offset*pixel_time_scale);
pressedAt = newPosition;
if (selectedItems.containsKey(time.getId())) {
this.controlPanel.dragMove(time.getId(), millioffset);
} else {
long newShow = millioffset+show;
if (newShow < 0) {
newShow = 0;
}
long newHide = newShow+duration;
if (!checkForOverlap(time.getId(), newShow, newHide)) {
time.setTimeToShow(newShow);
time.setTimeToHide(newHide);
}
}
}
repaint();
}
/**
* Calculate if current position is near grabber, and if so
* set time to grabber.
* @param newPosition
* @param grabberPosition
* @return the milliseconds to set the time to.
*/
public long snapToGrabber(int newPosition) {
long grabberTime = player.getMediaNanoseconds();
long grabberTimeMillis = TimeUnit.NANOSECONDS.toMillis(grabberTime);
long grabberPosition = grabberTimeMillis/pixel_time_scale;
long milliseconds = (newPosition*pixel_time_scale);
NodePositionTime time = currentSpan.getTime();
long show = time.getTimeToShow();
long hide = time.getTimeToHide();
long showPixels=show/pixel_time_scale;
long hidePixels=hide/pixel_time_scale;
boolean travellingTowards = false;
boolean travellingRight = false;
if (newPosition > pressedAt) {
travellingRight = true;
}
if ((leftGrabber && newPosition > showPixels && grabberPosition > newPosition) ||
(leftGrabber && newPosition < showPixels && grabberPosition < newPosition) ||
(rightGrabber && newPosition > hidePixels && grabberPosition > newPosition) ||
(rightGrabber && newPosition < hidePixels && grabberPosition < newPosition)) {
travellingTowards = true;
}
if (travellingTowards) {
if ( (travellingRight && newPosition+SNAP_SENSITIVITY >= grabberPosition) ||
(!travellingRight && newPosition-SNAP_SENSITIVITY <= grabberPosition)) {
if (leftGrabber) {
if (!checkForOverlap(time.getId(), grabberTimeMillis, hide)) {
milliseconds = grabberTimeMillis;
}
} else if (rightGrabber) {
if (!checkForOverlap(time.getId(), show, grabberTimeMillis)) {
milliseconds = grabberTimeMillis;
}
}
}
}
return milliseconds;
}
/**
* Check to see if the passed time is inside another span or another span is inside it
* In which case there is a overlap.
* @param newTime the time to check
* @return true if there is an overlap with another time span, else false;
*/
private boolean checkForOverlap(String id, long start, long stop) {
int count = vtTimes.size();
boolean overlap = false;
for (int i=0; i<count; i++) {
NodePositionTime time = (NodePositionTime)vtTimes.elementAt(i);
if (!id.equals(time.getId())) {
overlap = time.checkForOverlap(start, stop);
if (overlap) {
break;
}
}
}
return overlap;
}
public void showTimeDialog(NodePositionTime span) {
oNode.showTimeDialog(span);
}
public void delete(String sTime, String sNode) {
try {
oTimeMapView.deleteNodeTime(sTime, sNode);
} catch(Exception e) {
log.error("Error...", e);
}
}
/**
* Create a new short timespan at the given x position on the timeline.
* @param x
*/
public void createNewSpan(int x) {
Date date = new Date();
int position = mouseToSlider(x);
long milliseconds = (position*pixel_time_scale);
String id = Model.getStaticUniqueID();
try {
long milliDefaultSpan = this.controlPanel.getDefaultNodeTimeSpanLength();
if (!checkForOverlap(id, milliseconds, milliseconds+milliDefaultSpan)) {
oTimeMapView.addNodeTime(oNode.getNode().getId(), milliseconds, milliseconds+milliDefaultSpan, oNode.getLocation().x, oNode.getLocation().y);
repaint();
}
} catch(Exception ex) {
log.error("Error...", ex);
ProjectCompendium.APP.displayError(ex.getLocalizedMessage());
}
}
/**
* Set the location of the node for the given span, to be the node's current location.
* @param span the span to update the node location for.
*/
// NOT CURRENTLY USED - LOCATION UPDATED DYNAMICALLY
/*public void setCurrentSpanLocationToNode(NodePositionTime span) {
Point p = oNode.getLocation();
if (span != null) {
try {
oTimeMapView.updateNodeTime(span.getId(), span.getNode().getId(), span.getTimeToShow(), span.getTimeToHide(), p.x, p.y);
} catch(Exception ex) {
log.error("Error...", ex);
ProjectCompendium.APP.displayError(ex.getLocalizedMessage());
}
}
}*/
/**
* Can all the selected items move the required amount?
* @param changeValue the amount to check for the move
* @return true if all the items can move, else false.
*/
public synchronized boolean canAllMove(long changeValue) {
boolean canMove = true;
NodePositionTime time = null;
for (Enumeration<NodePositionTime> bars = selectedItems.elements(); bars.hasMoreElements();) {
time = (NodePositionTime)bars.nextElement();
long show = time.getTimeToShow();
long hide = time.getTimeToHide();
long duration = hide-show;
long newShow = changeValue+show;
if (newShow < 0) {
canMove = false;
break;
}
long newHide = newShow+duration;
if (checkForOverlap(time.getId(), newShow, newHide)) {
canMove = false;
break;
}
}
return canMove;
}
/**
* Update all selected items by the amount given in milliseconds
*/
public synchronized void dragMove(String fromID, long changeValue) {
NodePositionTime time = null;
if (changeValue != 0) {
for (Enumeration<NodePositionTime> bars = selectedItems.elements(); bars.hasMoreElements();) {
time = (NodePositionTime)bars.nextElement();
long show = time.getTimeToShow();
long hide = time.getTimeToHide();
long duration = hide-show;
long newShow = changeValue+show;
if (newShow < 0) {
newShow = 0;
}
long newHide = newShow+duration;
if (!checkForOverlap(time.getId(), newShow, newHide)) {
time.setTimeToShow(newShow);
time.setTimeToHide(newHide);
}
}
}
repaint();
}
/**
* Save the change to all selected items
*/
public synchronized void dragComplete(String fromID) {
NodePositionTime time = null;
for (Enumeration<NodePositionTime> bars = selectedItems.elements(); bars.hasMoreElements();) {
time = (NodePositionTime)bars.nextElement();
long show = time.getTimeToShow();
long hide = time.getTimeToHide();
Point loc = oNode.getLocation();
try {
this.oTimeMapView.updateNodeTime(time.getId(), oNode.getNode().getId(), show, hide, loc.x, loc.y);
Long currenttime = new Double(player.getMediaTime().getNanoseconds()).longValue();
long millitime = TimeUnit.NANOSECONDS.toMillis(currenttime.longValue());
setCurrentNodeView(millitime);
} catch(Exception ex) {
log.error("Error...", ex);
}
}
repaint();
}
/**
* Clear all selected items
*/
public void clearSelection() {
selectedItems.clear();
}
/**
* Handle mouseClicked events.
* @param e the MouseEvent for this mouse click.
*/
public void mouseClicked(MouseEvent e) {
if (!isEnabled() || player == null) {
return;
}
controlPanel.setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR));
boolean isRightMouse = SwingUtilities.isRightMouseButton(e);
boolean isLeftMouse = SwingUtilities.isLeftMouseButton(e);
if (ProjectCompendium.isMac &&
( (e.getButton() == 3 && e.isShiftDown()) ||
(e.getButton() == 1 && e.isControlDown()) )) {
isRightMouse = true;
isLeftMouse = false;
}
int clickCount = e.getClickCount();
if (!e.isShiftDown()) {
this.controlPanel.clearSelection();
}
TimeSpan span = isInTimeSpan(e.getPoint());
if (isLeftMouse) {
if (clickCount >= 2) {
if (span == null) {
createNewSpan(e.getX());
} else {
showTimeDialog(span.getTime());
}
} else if (clickCount == 1) {
if (span != null && e.isShiftDown()) {
if (!selectedItems.containsKey(span.getTime().getId())) {
selectedItems.put(span.getTime().getId(), span.getTime());
} else {
selectedItems.remove(span.getTime().getId());
}
repaint();
} else if (span != null &&
!span.getLeftGrabber().contains(e.getPoint()) &&
!span.getRightGrabber().contains(e.getPoint())) {
controlPanel.setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR));
// if you are on a span, move time position to start of span.
long starttime = span.getTime().getTimeToShow();
long newPosition = starttime/pixel_time_scale;
sliderSeek(newPosition);
oViewPane.setSelectedNode(null,ICoreConstants.DESELECTALL);
oNode.setSelected(true);
oViewPane.setSelectedNode(oNode,ICoreConstants.SINGLESELECT);
}
else {
oViewPane.setSelectedNode(null,ICoreConstants.DESELECTALL);
oNode.setSelected(true);
oViewPane.setSelectedNode(oNode,ICoreConstants.SINGLESELECT);
}
}
} else if (isRightMouse) {
if (span != null) {
if (popup != null) {
popup.setVisible(false);
popup = null;
}
//Point place = e.getLocationOnScreen();
Point place = SwingUtilities.convertPoint((Component)e.getSource(), e.getX(), e.getY(), ProjectCompendium.APP);
popup = new UINodeTimeLinePopupMenu(this, span.getTime(), oNode.getNode().getId(), e.getPoint());
popup.show(ProjectCompendium.APP, place.x, place.y);
} else {
if (popup != null) {
popup.setVisible(false);
popup = null;
}
//Point place = e.getLocationOnScreen();
Point place = SwingUtilities.convertPoint((Component)e.getSource(), e.getX(), e.getY(), ProjectCompendium.APP);
popup = new UINodeTimeLinePopupMenu(this, null, oNode.getNode().getId(), e.getPoint());
popup.show(ProjectCompendium.APP, place.x, place.y);
}
}
currentSpan = null;
onSpan = false;
}
public synchronized void mouseEntered(MouseEvent e) {
//if (!isEnabled() || player == null || this.player.getState() == MasterTimer.Started)
// return;
}
public synchronized void mouseExited(MouseEvent e) {
if (!isEnabled() || player == null || this.player.getState() == MasterTimer.Started)
return;
//entered = false;
//onBar = false;
//onSpan = false;
//currentSpan = null;
//this.setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR));
repaint();
}
public synchronized void mouseMoved(MouseEvent e) {
if (!isEnabled() || player == null) {
return;
}
Dimension dim;
Point pointScreen;
controlPanel.setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR));
this.leftGrabber = false;
this.rightGrabber = false;
TimeSpan span = isInTimeSpan(e.getPoint());
if (span != null) {
//NodePositionTime time = span.getTime();
if (span.getLeftGrabber().contains(e.getPoint())) {
this.leftGrabber = true;
} else if (span.getRightGrabber().contains(e.getPoint())) {
this.rightGrabber = true;
}
if (leftGrabber || rightGrabber) {
controlPanel.setCursor(new Cursor(java.awt.Cursor.E_RESIZE_CURSOR));
} else {
controlPanel.setCursor(new Cursor(java.awt.Cursor.HAND_CURSOR));
}
}
}
public void componentResized ( ComponentEvent event ) {
Dimension dim = this.getSize();
if ( dim.width - leftBorder - rightBorder < 1 )
return;
this.width = dim.width;
sliderWidth = this.width - leftBorder - rightBorder;
repaint();
}
public void componentMoved ( ComponentEvent event ) {}
public void componentShown ( ComponentEvent event ) {}
public void componentHidden ( ComponentEvent event ) {}
/**
* This class represents a time span holding the left and right grabber rectangles
* and the associated NodePositionTime object. Used by mouse events.
* @author Michelle Bachler
*/
private class TimeSpan {
private Rectangle leftGrabber = null;
private Rectangle rightGrabber = null;
private NodePositionTime oTime = null;
TimeSpan(NodePositionTime time) {
oTime = time;
}
public void setTime(NodePositionTime time) {
oTime = time;
}
public NodePositionTime getTime() {
return oTime;
}
public void setLeftGrabber(Rectangle left) {
leftGrabber = left;
}
public Rectangle getLeftGrabber() {
return leftGrabber;
}
public void setRightGrabber(Rectangle right) {
rightGrabber = right;
}
public Rectangle getRightGrabber() {
return rightGrabber;
}
}
}