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.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.concurrent.TimeUnit; import javax.media.Controller; import javax.media.ControllerEvent; import javax.media.ControllerListener; import javax.media.MediaTimeSetEvent; import javax.media.Time; import javax.swing.JComponent; import javax.swing.JToolTip; import javax.swing.Popup; import javax.swing.PopupFactory; import javax.swing.SwingUtilities; import com.compendium.ProjectCompendium; import com.sun.media.util.MediaThread; public class UITimeLineBar extends JComponent implements Runnable, ControllerListener, MouseListener, MouseMotionListener { /** * */ private static final long serialVersionUID = 1L; /** the colour of the grabber bar element */ private Color GRABBER_COLOUR = Color.black; /** The width of the grabber bar if drawing as a line.*/ private int grabberWidth=3; /** The current position to draw the grabber bar at.*/ private int grabberPosition = 0; /** The rectangle that represent the grabber bar - for mouse events to use.*/ private Rectangle grabberRectangle = new Rectangle(0, 0); /** Whether the mouse in on the grabber bar or not - used by mouse events.*/ private boolean bBarGrabbed = false; /** The width of the actual timeline area that represents the master time line.*/ private int timelineWidth = 0; /** The tooltip that shows the current position of the mouse in the time space of the master time line.*/ private JToolTip toolTip = null; /** The actual window displaying the popup.*/ transient Popup tipWindow; /** The image for the grabber bar.*/ //Image imageMiniGrabber; /** The width of the grabber bar.*/ //int miniGrabberWidth; /** The parent panel to this bar.*/ private UITimeLinesController controlPanel; /** The MasterTimer that this bar takes it's time from.*/ private MasterTimer player; protected boolean justSeeked = false; protected boolean stopTimer = false; private MediaThread timer = null; private Integer localLock = new Integer(0); private boolean resetMediaTime = false; /** * The constructor * @param cp The parent class, the controller panel. * @param p the MasterTimer class. */ public UITimeLineBar(UITimeLinesController cp, MasterTimer p) { this.controlPanel = cp; this.player = p; this.player.addControllerListener ( this ); this.setOpaque(false); addMouseListener( this ); addMouseMotionListener( this ); //ImageIcon iGrabberMini = new ImageIcon(UIImages.sPATH + "red-line.png"); //imageMiniGrabber = iGrabberMini.getImage(); //miniGrabberWidth = imageMiniGrabber.getWidth(this); addComponentListener(new ComponentAdapter() { public void componentResized ( ComponentEvent event ) { Dimension dim = getSize(); if (timelineWidth != dim.width) { timelineWidth=dim.width; repaint(); } } }); } /** * Paints the bar. */ public void paintComponent(Graphics g) { g.setColor( new Color(133,12,12) ); g.drawLine(grabberPosition, 0, grabberPosition, this.getHeight()); g.setColor( new Color(232,20,20) ); g.drawLine(grabberPosition+1, 0, grabberPosition+1, this.getHeight()); g.setColor( new Color(147,13,13) ); g.drawLine(grabberPosition+2, 0, grabberPosition+2, this.getHeight()); grabberRectangle = new Rectangle(grabberPosition, 0, grabberWidth, this.getHeight()); /*g.drawImage(imageMiniGrabber, grabberPosition, 0, miniGrabberWidth, this.getHeight(), this); g.setColor( GRABBER_COLOUR ); g.drawLine(grabberPosition, 0, grabberPosition+miniGrabberWidth, 0); g.drawLine(grabberPosition, this.getHeight()-1, grabberPosition+miniGrabberWidth-1, this.getHeight()-1); grabberRectangle = new Rectangle(grabberPosition, 0, miniGrabberWidth, this.getHeight());*/ //g.fillRect(grabberPosition, 0, grabberWidth, this.getHeight()); //grabberRectangle = new Rectangle(grabberPosition, 0, grabberWidth, this.getHeight()); } /** * 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*controlPanel.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); } } /** * Relocated the bar to the given time. * @param time the time that the bar needs to relocate to in Nanoseconds. */ public void seek(long time) { if (justSeeked) return; long timeMillis = TimeUnit.NANOSECONDS.toMillis(time); long newPosition = timeMillis/controlPanel.pixel_time_scale; newPosition = checkBounds(newPosition); if (grabberPosition != newPosition || !isEnabled()) { grabberPosition = new Long(newPosition).intValue(); scrollRectToVisible(new Rectangle(grabberPosition-controlPanel.leftBorder, grabberRectangle.y, grabberRectangle.width+(controlPanel.leftBorder*2), grabberRectangle.height)); repaint(); } } /** * Check that the given location is inside the time line. * @param x the location to check. * @return the located, adjusted if required. */ public long checkBounds(long x) { if (x < 0) x = 0; if (x > timelineWidth) x = timelineWidth-1; return x; } /** * Mouse pressed event * @param e the MouseEvent for this mouse press */ public void mousePressed(MouseEvent e) { if (!isEnabled() || player == null) { return; } if (player.getState() == MasterTimer.Started) { controlPanel.stop(); } bBarGrabbed = false; Point newPoint = new Point(e.getX(), e.getY()); if (grabberRectangle.contains(newPoint)) { bBarGrabbed = true; this.setCursor(new Cursor(java.awt.Cursor.HAND_CURSOR)); redispatchMouseExitedEvent(e); } else { redispatchMouseEvent(e); } } /** * Mouse released event * @param e the MouseEvent for this mouse release */ public synchronized void mouseReleased(MouseEvent e) { if (!isEnabled() || player == null) { return; } if (player.getState() == MasterTimer.Started) { controlPanel.stop(); } moveToolTipToCurrent(e.getPoint()); this.setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR)); if (bBarGrabbed) { grabberPosition = new Long(checkBounds(e.getX())).intValue(); sliderSeek(grabberPosition); redispatchMouseExitedEvent(e); } else { redispatchMouseEvent(e); } bBarGrabbed = false; repaint(); } /** * Mouse dragged event * @param e the MouseEvent for this mouse drag */ public synchronized void mouseDragged(MouseEvent e) { if (!isEnabled() || player == null) { return; } if (player.getState() == MasterTimer.Started) { controlPanel.stop(); } if (bBarGrabbed) { grabberPosition = new Long(checkBounds(e.getX())).intValue(); sliderSeek(grabberPosition); redispatchMouseExitedEvent(e); } else { redispatchMouseEvent(e); } repaint(); //having this below the repaint fixes a pain issue on the Mac. moveToolTipToCurrent(e.getPoint()); } /** * Mouse clicked event * @param e the MouseEvent for this mouse click */ public void mouseClicked(MouseEvent e) { if (!isEnabled() || player == null) { return; } if (player.getState() == MasterTimer.Started) { controlPanel.stop(); } UITimeLineHeader header = controlPanel.getHeader(); if (header != null) { Point componentPoint = SwingUtilities.convertPoint(this, e.getPoint(), header); if (header.contains(componentPoint)) { boolean isLeftMouse = SwingUtilities.isLeftMouseButton(e); if (ProjectCompendium.isMac && ( (e.getButton() == 3 && e.isShiftDown()) || (e.getButton() == 1 && e.isControlDown()) )) { isLeftMouse = false; } if (isLeftMouse && e.getClickCount() == 1) { grabberPosition = new Long(checkBounds(e.getX())).intValue(); sliderSeek(grabberPosition); } } } redispatchMouseEvent(e); } /** * Mouse entered event * @param e the MouseEvent for this mouse enter */ public synchronized void mouseEntered(MouseEvent e) { if (!isEnabled() || player == null) return; if ( toolTip == null && isEnabled() && player != null) { moveToolTipToCurrent(e.getPoint()); } redispatchMouseEvent(e); } /** * Mouse exited event * @param e the MouseEvent for this mouse exit */ public synchronized void mouseExited(MouseEvent e) { if (!isEnabled() || player == null) return; if ( toolTip != null) { toolTip.setVisible(false); toolTip = null; tipWindow.hide(); tipWindow = null; } redispatchMouseEvent(e); } /** * Mouse moved event * @param e the MouseEvent for this mouse move */ public synchronized void mouseMoved(MouseEvent e) { if (!isEnabled() || player == null) return; moveToolTipToCurrent(e.getPoint()); Point newPoint = new Point(e.getX(), e.getY()); if (grabberRectangle.contains(newPoint)) { this.setCursor(new Cursor(java.awt.Cursor.HAND_CURSOR)); redispatchMouseExitedEvent(e); } else { this.setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR)); redispatchMouseEvent(e); } } /** * Re-dispatch the mouse Exited event to lower objects. * @param e the event to re-dispatch as a MOUSE_EXITED event. */ private void redispatchMouseExitedEvent(MouseEvent e) { Point point = e.getPoint(); Component[] comps = controlPanel.oNodeTimeLinesPanel.getComponents(); for (int i=0; i< comps.length; i++) { Component comp = comps[i]; if (comp != null) { Point componentPoint = SwingUtilities.convertPoint( this, point, comp); MouseEvent newExitedEvent = new MouseEvent(comp, MouseEvent.MOUSE_EXITED, e.getWhen(), e.getModifiers(), componentPoint.x, componentPoint.y, e.getClickCount(), e.isPopupTrigger()); if (comp instanceof UITimeLineForNode) { UITimeLineForNode line = (UITimeLineForNode)comp; line.mouseExited(newExitedEvent); } else if (comp instanceof UITimeLineForMovie) { UITimeLineForMovie line = (UITimeLineForMovie)comp; line.mouseExited(newExitedEvent); } else { comp.dispatchEvent(newExitedEvent); } } } } /** * Re-dispatch a mouse event to lower objects. * @param e the mouse event to re-dispatch. */ private void redispatchMouseEvent(MouseEvent e) { //redispatchMouseExitedEvent(e); Point point = e.getPoint(); Point containerPoint = SwingUtilities.convertPoint( this, point, controlPanel.oNodeTimeLinesPanel); JComponent comp = (JComponent)controlPanel.oNodeTimeLinesPanel.findComponentAt(containerPoint); if (comp != null) { Point componentPoint = SwingUtilities.convertPoint( this, point, comp); MouseEvent newEvent = new MouseEvent(comp, e.getID(), e.getWhen(), e.getModifiers(), componentPoint.x, componentPoint.y, e.getClickCount(), e.isPopupTrigger()); // dispatchEvent stopped working after refreshing lines from node label change. // no idea why, but this is a work around. if (comp instanceof UITimeLineForNode) { UITimeLineForNode line = (UITimeLineForNode)comp; switch(e.getID()) { case MouseEvent.MOUSE_ENTERED: { line.mouseEntered(newEvent); break; } case MouseEvent.MOUSE_EXITED: { line.mouseExited(newEvent); break; } case MouseEvent.MOUSE_MOVED: { line.mouseMoved(newEvent); break; } case MouseEvent.MOUSE_PRESSED: { line.mousePressed(newEvent); break; } case MouseEvent.MOUSE_DRAGGED: { line.mouseDragged(newEvent); break; } case MouseEvent.MOUSE_RELEASED: { line.mouseReleased(newEvent); break; } case MouseEvent.MOUSE_CLICKED: { line.mouseClicked(newEvent); break; } default: comp.dispatchEvent(newEvent); } } else if (comp instanceof UITimeLineForMovie) { UITimeLineForMovie line = (UITimeLineForMovie)comp; switch(e.getID()) { case MouseEvent.MOUSE_ENTERED: { line.mouseEntered(newEvent); break; } case MouseEvent.MOUSE_EXITED: { line.mouseExited(newEvent); break; } case MouseEvent.MOUSE_MOVED: { line.mouseMoved(newEvent); break; } case MouseEvent.MOUSE_PRESSED: { line.mousePressed(newEvent); break; } case MouseEvent.MOUSE_DRAGGED: { line.mouseDragged(newEvent); break; } case MouseEvent.MOUSE_RELEASED: { line.mouseReleased(newEvent); break; } case MouseEvent.MOUSE_CLICKED: { line.mouseClicked(newEvent); break; } default: comp.dispatchEvent(newEvent); } } else { comp.dispatchEvent(newEvent); } } } /** * Format a time string. * @param time the milliseconds of seconds to format; * @return */ private String formatTime ( long time ) { String strTime = ""; //$NON-NLS-1$ long hours=0; long minutes=0; long seconds=0; long hours10=0; long minutes10=0; long seconds10=0; long milliseconds=0; seconds = time/1000; milliseconds = time%1000; if (seconds > 59) { minutes = seconds/60; seconds = seconds%60; if (minutes > 59) { hours = minutes/60; minutes = minutes%60; } } hours10 = hours / 10; hours = hours % 10; minutes10 = minutes / 10; minutes = minutes % 10; seconds10 = seconds / 10; seconds = seconds % 10; String milli = String.valueOf(milliseconds); if (milli.length() == 3) { milli = "0"+milli; //$NON-NLS-1$ } else if (milli.length() == 2) { milli = "00"+milli; //$NON-NLS-1$ } else if (milli.length() == 1) { milli = "000"+milli; //$NON-NLS-1$ } strTime = new String ( "" + hours10 + hours + ":" + minutes10 + minutes + ":" + seconds10 + seconds + "." + milli ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ return ( strTime ); } /** * Relocate the tooltip text to the given location. * Adjust location to screen. * @param currentLocation the location to relocate the tooltip to. */ public void moveToolTipToCurrent(Point currentLocation) { if (tipWindow != null) { tipWindow.hide(); tipWindow = null; } if (this.isShowing() && player.getState() != MasterTimer.Started) { if (toolTip == null) { toolTip = this.createToolTip(); } Dimension dim = toolTip.getSize (); Point pointScreen = new Point(currentLocation); SwingUtilities.convertPointToScreen(pointScreen, this); pointScreen.x = pointScreen.x - dim.width - 5; pointScreen.y = pointScreen.y+5; toolTip.setLocation ( pointScreen ); long value = (currentLocation.x*controlPanel.pixel_time_scale); String strTool = formatTime(value); toolTip.setTipText(strTool); PopupFactory popupFactory = PopupFactory.getSharedInstance(); tipWindow = popupFactory.getPopup(this, toolTip, pointScreen.x, pointScreen.y); tipWindow.show(); } } /********** RUNNABLE STUFF ************/ /** * Creates the MediaThread timer that the runnable uses, and starts it. */ public void addNotify() { super.addNotify(); timer = new MediaThread(this); timer.setName("UINodeTimeLine thread"); //$NON-NLS-1$ timer.useControlPriority(); 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(); /** * Stops the MediaTimer and hides the tooltip. */ public void removeNotify() { if (timer != null) { synchronized (syncStop) { stopTimer = true; timer = null; } } synchronized (disposeLock) { if ( toolTip != null ) { toolTip.setVisible( false ); tipWindow.hide(); tipWindow = null; } } super.removeNotify(); } /** * Disposes of the timer for this runnable and removes listeners and the tooltip. */ public synchronized void dispose() { synchronized (syncStop) { if (timer != null) { stopTimer = true; } } removeMouseListener( this ); removeMouseMotionListener( this ); player.removeControllerListener ( this ); synchronized (disposeLock) { if (toolTip != null) { toolTip.setVisible(false); toolTip = null; tipWindow.hide(); tipWindow = null; } } timer = null; } /** * The run method for this runnable. * Checks the master timer and moves the bar along as required. */ 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(); seek(nanoTime); } } } 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) {} } } /** * Receives updates from the Master Timer. * Listens for MediaTimeSetEvents only. */ public synchronized void controllerUpdate ( ControllerEvent event ) { synchronized (localLock) { if (player == null) return; if (event instanceof MediaTimeSetEvent) { Thread.yield (); resetMediaTime = true; } } } }