// ********************************************************************** // // <copyright> // // BBN Technologies, a Verizon Company // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> package com.bbn.openmap.gui.time; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Polygon; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import com.bbn.openmap.MapBean; import com.bbn.openmap.MapHandler; import com.bbn.openmap.MoreMath; import com.bbn.openmap.event.CenterListener; import com.bbn.openmap.event.CenterSupport; import com.bbn.openmap.event.MapMouseListener; import com.bbn.openmap.event.ZoomEvent; import com.bbn.openmap.event.ZoomListener; import com.bbn.openmap.event.ZoomSupport; import com.bbn.openmap.layer.OMGraphicHandlerLayer; import com.bbn.openmap.omGraphics.DrawingAttributes; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMLine; import com.bbn.openmap.omGraphics.OMPoly; import com.bbn.openmap.omGraphics.OMRect; import com.bbn.openmap.omGraphics.OMScalingIcon; import com.bbn.openmap.proj.Cartesian; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.time.Clock; import com.bbn.openmap.time.TimeBounds; import com.bbn.openmap.time.TimeBoundsEvent; import com.bbn.openmap.time.TimeBoundsListener; import com.bbn.openmap.time.TimeEvent; import com.bbn.openmap.time.TimeEventListener; import com.bbn.openmap.time.TimerStatus; import com.bbn.openmap.tools.icon.BasicIconPart; import com.bbn.openmap.tools.icon.IconPart; import com.bbn.openmap.tools.icon.OMIconFactory; /** * Timeline layer * * Render events and allow for their selection on a variable-scale timeline */ public class TimeSliderLayer extends OMGraphicHandlerLayer implements PropertyChangeListener, MapMouseListener, ComponentListener, TimeBoundsListener, TimeEventListener { protected static Logger logger = Logger.getLogger("com.bbn.openmap.gui.time.TimeSliderLayer"); protected CenterSupport centerDelegate; protected ZoomSupport zoomDelegate; long currentTime = 0; long gameStartTime = 0; long gameEndTime = 0; // KMTODO package this up into a standalone widget? // Times are generally in minutes double selectionWidthMinutes = 1.0; double maxSelectionWidthMinutes = 1.0; double selectionCenter = 0; OMRect boundsRectLeftHandle; // handles for scaling selection OMRect boundsRectRightHandle; Color selectionPointLineColor = TimelineLayer.tint; Color selectionPointFillColor = TimelineLayer.tint; Color leftHandleFillColor = Color.black; Color rightHandleFillColor = Color.black; OMLine baseLine; // thick line along middle OMPoly contextPoly; // lines that relate time slider to timeline above. int sliderPointHalfWidth = 5; TimelinePanel timelinePanel; TimelineLayer timelineLayer; public static double magicScaleFactor = 100000000; // KMTODO (Don? I guess // this is to // address a precision issue?) Clock clock; LabelPanel labelPanel; private boolean isNoTime = true; // In realTimeMode, gameEndTime is the origin, rather than gameStartTime private final boolean realTimeMode; // Used to refrain from re-scaling every TimeBoundsUpdate (in realTimeMode // only) private boolean userHasChangedScale = false; private final List<ITimeBoundsUserActionsListener> timeBoundsUserActionsListeners = new ArrayList<ITimeBoundsUserActionsListener>(); private final JButton zoomToSelection = new JButton("Zoom to Selection"); private final JButton renderFixedSelection = new JButton("Show Entire Selection"); /** * Construct the TimelineLayer. * * @param realTimeMode TODO */ public TimeSliderLayer(boolean realTimeMode) { this.realTimeMode = realTimeMode; setName("TimeSlider"); // This is how to set the ProjectionChangePolicy, which // dictates how the layer behaves when a new projection is // received. setProjectionChangePolicy(new com.bbn.openmap.layer.policy.StandardPCPolicy(this, false)); // Making the setting so this layer receives events from the // SelectMouseMode, which has a modeID of "Gestures". Other // IDs can be added as needed. setMouseModeIDsForEvents(new String[] { "Gestures" }); centerDelegate = new CenterSupport(this); zoomDelegate = new ZoomSupport(this); addComponentListener(this); } public void findAndInit(Object someObj) { if (someObj instanceof Clock) { clock = ((Clock) someObj); clock.addTimeEventListener(this); clock.addTimeBoundsListener(this); gameStartTime = ((Clock) someObj).getStartTime(); gameEndTime = ((Clock) someObj).getEndTime(); // Just in case we missed an early TimeBoundEvent updateTimeBounds(gameStartTime, gameEndTime); } if (someObj instanceof CenterListener) { centerDelegate.add((CenterListener) someObj); } if (someObj instanceof ZoomListener) { zoomDelegate.add((ZoomListener) someObj); } if (someObj instanceof TimelinePanel.Wrapper) { timelinePanel = ((TimelinePanel.Wrapper) someObj).getTimelinePanel(); timelinePanel.getMapBean().addPropertyChangeListener(this); timelineLayer = timelinePanel.getTimelineLayer(); } } /** * Called with the projection changes, should just generate the current * markings for the new projection. */ public synchronized OMGraphicList prepare() { OMGraphicList list = getList(); if (list == null) { list = new OMGraphicList(); } else { list.clear(); } TimeDrape drape = new TimeDrape(0, 0, -1, -1); drape.setVisible(isNoTime); drape.setFillPaint(Color.gray); drape.generate(getProjection()); list.add(drape); list.add(getControlWidgetList(getProjection())); return list; } /** * All we want to do here is reset the current position of all of the * widgets, and generate them with the projection for the new position. After * this call, the widgets are ready to paint. */ public synchronized OMGraphicList getControlWidgetList(Projection proj) { Projection projection = getProjection(); OMGraphicList controlWidgetList = new OMGraphicList(); // triangle indicating center of selection ImageIcon selectionPointImage; DrawingAttributes da = new DrawingAttributes(); da.setFillPaint(selectionPointFillColor); da.setLinePaint(selectionPointLineColor); IconPart ip = new BasicIconPart(new Polygon(new int[] { 50, 90, 10, 50 }, new int[] { 10, 90, 90, 10 }, 4), da); selectionPointImage = OMIconFactory.getIcon(32, 32, ip); OMScalingIcon selectionPoint = new OMScalingIcon(0f, 0f, 0, 6, selectionPointImage, 1.0f); final float selectionPointScale = 1.8f; selectionPoint.setMaxScale(selectionPointScale); selectionPoint.setMinScale(selectionPointScale); controlWidgetList.add(selectionPoint); boundsRectLeftHandle = new OMRect(0, 0, 0, 0); boundsRectLeftHandle.setFillPaint(leftHandleFillColor); controlWidgetList.add(boundsRectLeftHandle); boundsRectRightHandle = new OMRect(0, 0, 0, 0); boundsRectRightHandle.setFillPaint(rightHandleFillColor); controlWidgetList.add(boundsRectRightHandle); int[] xs = new int[8]; int[] ys = new int[8]; contextPoly = new OMPoly(xs, ys); contextPoly.setFillPaint(Color.white); controlWidgetList.add(contextPoly); baseLine = new OMLine(0, 0, 0, 0); baseLine.setLinePaint(Color.black); baseLine.setStroke(new BasicStroke(2)); controlWidgetList.add(baseLine); if (projection == null) { return controlWidgetList; // Huhn? } double screenWidth = projection.getWidth(); float scale = (float) (magicScaleFactor * (double) TimelineLayer.forwardProjectMillis(gameEndTime - gameStartTime) / screenWidth); Point2D projCenter = projection.getCenter(); // TODO reconsider proper test here - for the moment, just brute-force // reproject always if (projCenter.getX() > selectionWidthMinutes || scale != projection.getScale()) { double nCenterLon = TimelineLayer.forwardProjectMillis(gameEndTime - gameStartTime) / 2f; projCenter.setLocation(nCenterLon, 0); projection = new Cartesian(projCenter, scale, projection.getWidth(), projection.getHeight()); setProjection(projection); } // Reset primary handle int contextBuffer = (int) (projection.getHeight() * .4); // If 'selectionCenter' is outside current time bounds, first attempt to // recompute from currentTime if (selectionCenter * 60.0 * TimeUnit.SECONDS.toMillis(1) > (gameEndTime - gameStartTime) || selectionCenter < 0) { selectionCenter = TimelineLayer.forwardProjectMillis(currentTime); } // And if _that_ fails, just clamp // DFD changed from TimeUnit.MINUTE.toMillis(1) to MILLISECONDS because // Java 5 doesn't have MINUTE if (selectionCenter * TimeUnit.MILLISECONDS.toMillis(60000) > (gameEndTime - gameStartTime)) { selectionCenter = (gameEndTime - gameStartTime) / TimeUnit.MILLISECONDS.toMillis(60000); } if (selectionCenter < 0) { selectionCenter = 0; } int x = (int) projection.forward(0, selectionCenter).getX(); // Reset bounds and handles Point2D sliderEndPoint = projection.forward(0, selectionWidthMinutes); Point2D origin = projection.forward(0, 0); int selectionHalfWidth = (int) ((sliderEndPoint.getX() - origin.getX()) / 2); int north = contextBuffer; int west = x - selectionHalfWidth; int south = projection.getHeight() - 1; int east = x + selectionHalfWidth; int mid = contextBuffer + 1 + (south - contextBuffer) / 2; if (logger.isLoggable(Level.FINE)) { logger.fine("selectionCenter:" + selectionCenter + ", selectionWidthMinutes:" + selectionWidthMinutes + ", x:" + x + ", origin:" + origin); logger.fine(" projection:" + projection); } selectionPoint.setLon((float) selectionCenter); selectionPoint.generate(projection); // and the two handles for the bounds int handleWest = west - sliderPointHalfWidth; int handleEast = west + sliderPointHalfWidth; final int sliderPointHalfHeight = 2; boundsRectLeftHandle.setLocation(handleWest, north + sliderPointHalfHeight, handleEast, south - sliderPointHalfHeight); boundsRectLeftHandle.generate(projection); handleWest = east - sliderPointHalfWidth; handleEast = east + sliderPointHalfWidth; boundsRectRightHandle.setLocation(handleWest, north + sliderPointHalfHeight, handleEast, south - sliderPointHalfHeight); boundsRectRightHandle.generate(projection); // and the context lines, that show how the current selection maps to // the timeline above xs = contextPoly.getXs(); ys = contextPoly.getYs(); xs[0] = 0; ys[0] = -1; xs[1] = 0; ys[1] = north; xs[2] = west; ys[2] = north; xs[3] = west; ys[3] = south; xs[4] = east; ys[4] = south; xs[5] = east; ys[5] = north; xs[6] = projection.getWidth() - 1; ys[6] = north; xs[7] = projection.getWidth() - 1; ys[7] = -1; contextPoly.generate(projection); baseLine.setPts(new int[] { 0, mid, projection.getWidth(), mid }); baseLine.generate(projection); return controlWidgetList; } protected void updateTimeline() { if (timelinePanel != null) { float scale = (float) (magicScaleFactor * selectionWidthMinutes / getProjection().getWidth()); if (logger.isLoggable(Level.FINE)) { logger.fine("Updating timeline with scale: " + scale); } timelinePanel.getMapBean().setScale(scale); } } public String getName() { return "TimelineLayer"; } public void setSelectionPointFillColor(Color color) { selectionPointFillColor = color; } public void setSelectionPointLineColor(Color color) { selectionPointLineColor = color; } public void setLeftHandleFillColor(Color color) { leftHandleFillColor = color; } public void setRightHandleFillColor(Color color) { rightHandleFillColor = color; } /** * Updates zoom and center listeners with new projection information. * */ protected void finalizeProjection() { Projection projection = getProjection(); Cartesian cartesian = (projection instanceof Cartesian) ? (Cartesian) projection : null; if (cartesian != null) { double screenWidth = cartesian.getWidth(); cartesian.setLeftLimit(TimelineLayer.forwardProjectMillis(gameStartTime)); cartesian.setRightLimit(TimelineLayer.forwardProjectMillis(gameEndTime)); cartesian.setLimitAnchorPoint(new Point2D.Double(TimelineLayer.forwardProjectMillis(-gameStartTime), 0)); float scale = (float) (magicScaleFactor * (double) TimelineLayer.forwardProjectMillis(gameEndTime - gameStartTime) / screenWidth); zoomDelegate.fireZoom(ZoomEvent.ABSOLUTE, scale); double nCenterLon = TimelineLayer.forwardProjectMillis(gameEndTime - gameStartTime) / 2f; logger.fine("Telling the center delegate that the new center is 0, " + nCenterLon); centerDelegate.fireCenter(0, nCenterLon); // We are getting really large values for the center point of the // projection that the layer knows about. The MapBean projection // gets set OK, but then the projection held by the layer wrong, and // it throws the whole widget out of wack. If we set // the center of the projection to what it should be (the center // point between the start and end times), then everything settles // out. double x = cartesian.getCenter().getX(); if (!MoreMath.approximately_equal(x, nCenterLon)) { ((MapBean) ((MapHandler) getBeanContext()).get(MapBean.class)).setCenter(0, nCenterLon); } repaint(); } } public void updateTime(TimeEvent te) { if (checkAndSetForNoTime(te)) { return; } TimerStatus timerStatus = te.getTimerStatus(); if (timerStatus.equals(TimerStatus.STEP_FORWARD) || timerStatus.equals(TimerStatus.STEP_BACKWARD) || timerStatus.equals(TimerStatus.UPDATE)) { currentTime = te.getSystemTime(); currentTime -= gameStartTime; selectionCenter = TimelineLayer.forwardProjectMillis(currentTime); doPrepare(); } if (timerStatus.equals(TimerStatus.FORWARD) || timerStatus.equals(TimerStatus.BACKWARD) || timerStatus.equals(TimerStatus.STOPPED)) { if (logger.isLoggable(Level.FINE)) { logger.fine("updated time: " + te); } // Checking for a running clock prevents a time status // update after the clock is stopped. The // AudioFileHandlers don't care about the current time // if it isn't running. if (realTimeMode || ((Clock) te.getSource()).isRunning()) { // update(te.getSystemTime()); currentTime = te.getSystemTime(); currentTime -= gameStartTime; selectionCenter = TimelineLayer.forwardProjectMillis(currentTime); doPrepare(); } } } /* * @seejava.beans.PropertyChangeListener#propertyChange(java.beans. * PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); if (propertyName == MapBean.ProjectionProperty) { // This property should be from the TimelineLayer's MapBean, solely // for the scale measurement. logger.fine(propertyName + " from " + evt.getSource()); Projection timeLineProj = (Projection) evt.getNewValue(); // Need to solve for selectionWidthMinutes if (!realTimeMode || !userHasChangedScale) { selectionWidthMinutes = timeLineProj.getScale() * getProjection().getWidth() / magicScaleFactor; } if (selectionWidthMinutes > maxSelectionWidthMinutes + .0001 /* || selectionWidthMinutes < .0001 */) { if (logger.isLoggable(Level.FINE)) { logger.fine("resetting selectionWidthMinutes to max (projection change property change), was " + selectionWidthMinutes + ", now " + maxSelectionWidthMinutes); } selectionWidthMinutes = maxSelectionWidthMinutes; } doPrepare(); } } public boolean getUserHasChangedScale() { return userHasChangedScale; } public void setUserHasChangedScale(boolean userHasChangedScale) { this.userHasChangedScale = userHasChangedScale; } protected boolean checkAndSetForNoTime(TimeEvent te) { isNoTime = te == TimeEvent.NO_TIME; return isNoTime; } public MapMouseListener getMapMouseListener() { return this; } public String[] getMouseModeServiceList() { return getMouseModeIDsForEvents(); } enum DragState { NONE, PRIMARY_HANDLE, LEFT_HANDLE, RIGHT_HANDLE } DragState dragState = DragState.NONE; public boolean mousePressed(MouseEvent e) { updateMouseTimeDisplay(e); clearFixedRenderRange(); int x = e.getPoint().x; int y = e.getPoint().y; if (boundsRectLeftHandle.contains(x, y)) { dragState = DragState.LEFT_HANDLE; userHasChangedScale = true; } else if (boundsRectRightHandle.contains(x, y)) { dragState = DragState.RIGHT_HANDLE; userHasChangedScale = true; } else { dragState = DragState.PRIMARY_HANDLE; Projection projection = getProjection(); if (projection != null) { Point2D invPnt = projection.inverse(x, y); setSelectionCenter(invPnt.getX()); updateTimeline(); } } return true; } public boolean mouseReleased(MouseEvent e) { updateMouseTimeDisplay(e); dragState = DragState.NONE; return false; } public boolean mouseClicked(MouseEvent e) { updateMouseTimeDisplay(e); return false; } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { timelineLayer.updateMouseTimeDisplay(new Long(-1)); } void setSelectionCenter(double newCenter) { selectionCenter = newCenter; if (selectionCenter < 0) { selectionCenter = 0; } double offsetEnd = (double) (gameEndTime - gameStartTime) / 1000.0 / 60.0; if (selectionCenter > offsetEnd) { selectionCenter = offsetEnd; } clock.setTime(gameStartTime + (long) (selectionCenter * 60 * 1000)); } public boolean mouseDragged(MouseEvent e) { updateMouseTimeDisplay(e); Projection projection = getProjection(); if (projection == null) { return false; // Huhn? } double worldMouse; Point2D invPnt; int x = e.getPoint().x; int y = e.getPoint().y; int selectionCenterX = (int) projection.forward(0, selectionCenter).getX(); boolean scaleChange = false; switch (dragState) { case PRIMARY_HANDLE: invPnt = projection.inverse(x, y); setSelectionCenter(invPnt.getX()); break; case LEFT_HANDLE: if (x >= selectionCenterX - sliderPointHalfWidth) { x = selectionCenterX - sliderPointHalfWidth; } invPnt = projection.inverse(x, y); worldMouse = invPnt.getX(); selectionWidthMinutes = 2 * (selectionCenter - worldMouse); scaleChange = true; break; case RIGHT_HANDLE: if (x <= selectionCenterX + sliderPointHalfWidth) { x = selectionCenterX + sliderPointHalfWidth; } invPnt = projection.inverse(x, y); worldMouse = invPnt.getX(); selectionWidthMinutes = 2 * (worldMouse - selectionCenter); scaleChange = true; break; } if(scaleChange) { if (selectionWidthMinutes > maxSelectionWidthMinutes) { if (logger.isLoggable(Level.FINE)) { logger.fine("resetting selectionWidthMinutes to max, was " + selectionWidthMinutes + ", now " + maxSelectionWidthMinutes); } selectionWidthMinutes = maxSelectionWidthMinutes; } updateTimeline(); timelineLayer.doPrepare(); } doPrepare(); return true; } public boolean mouseMoved(MouseEvent e) { updateMouseTimeDisplay(e); return true; } public void mouseMoved() { } protected double updateMouseTimeDisplay(MouseEvent e) { Projection proj = getProjection(); Point2D latLong = proj.inverse(e.getPoint()); double lon = latLong.getX(); double endTime = TimelineLayer.forwardProjectMillis(gameEndTime - gameStartTime); if (lon < 0) { lon = 0; } else if (lon > endTime) { lon = endTime; } long offsetMillis = TimelineLayer.inverseProjectMillis(lon); timelineLayer.updateMouseTimeDisplay(new Long(offsetMillis)); return lon; } public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void componentResized(ComponentEvent e) { finalizeProjection(); } public void componentShown(ComponentEvent e) { } public LabelPanel getTimeLabels() { if (labelPanel == null) { labelPanel = new LabelPanel(); } return labelPanel; } public void updateTimeLabels(long startTime, long endTime) { LabelPanel lp = getTimeLabels(); lp.updateTimeLabels(startTime, endTime); } class LabelPanel extends JPanel implements com.bbn.openmap.gui.MapPanelChild { protected JComponent timeStartLabel; protected JComponent timeEndLabel; public final static String NO_TIME_STRING = "--/--/-- (--:--:--)"; public LabelPanel() { GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); setLayout(gridbag); if (realTimeMode) { JButton timeStartLabelButton = new JButton(NO_TIME_STRING); timeStartLabelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for (ITimeBoundsUserActionsListener listener : timeBoundsUserActionsListeners) { listener.invokeDateSelectionGUI(false); } } }); timeStartLabel = timeStartLabelButton; Font f = timeStartLabel.getFont(); f = new Font(f.getFamily(), f.getStyle(), f.getSize() - 1); timeStartLabel.setFont(f); gridbag.setConstraints(timeStartLabel, c); add(timeStartLabel); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0f; JLabel buffer = new JLabel(); gridbag.setConstraints(buffer, c); add(buffer); renderFixedSelection.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { long selectionStart = timelineLayer.getSelectionStart(); long selectionEnd = timelineLayer.getSelectionEnd(); if (selectionStart > 0 && selectionEnd > 0) { for (ITimeBoundsUserActionsListener listener : timeBoundsUserActionsListeners) { listener.setFixedRenderRange(selectionStart, selectionEnd); } } } }); renderFixedSelection.setEnabled(false); renderFixedSelection.setFont(f); c.weightx = 0f; gridbag.setConstraints(renderFixedSelection, c); add(renderFixedSelection); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0f; buffer = new JLabel(); gridbag.setConstraints(buffer, c); add(buffer); zoomToSelection.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { long selectionStart = timelineLayer.getSelectionStart(); long selectionEnd = timelineLayer.getSelectionEnd(); if (selectionStart > 0 && selectionEnd > 0) { timelineLayer.clearSelection(); userHasChangedScale = false; for (ITimeBoundsUserActionsListener listener : timeBoundsUserActionsListeners) { listener.setTimeBounds(selectionStart, selectionEnd); } updateTimeBounds(selectionStart, selectionEnd); updateTimeline(); } } }); zoomToSelection.setEnabled(false); zoomToSelection.setFont(f); c.weightx = 0f; gridbag.setConstraints(zoomToSelection, c); add(zoomToSelection); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0f; buffer = new JLabel(); gridbag.setConstraints(buffer, c); add(buffer); JButton jumpToRealTime = new JButton("Jump to Real Time"); jumpToRealTime.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { userHasChangedScale = false; for (ITimeBoundsUserActionsListener listener : timeBoundsUserActionsListeners) { listener.jumpToRealTime(); } updateTimeline(); } }); jumpToRealTime.setFont(f); c.weightx = 0f; gridbag.setConstraints(jumpToRealTime, c); add(jumpToRealTime); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0f; buffer = new JLabel(); gridbag.setConstraints(buffer, c); add(buffer); c.fill = GridBagConstraints.NONE; c.weightx = 0f; JButton timeEndLabelButton = new JButton(NO_TIME_STRING); timeEndLabelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for (ITimeBoundsUserActionsListener listener : timeBoundsUserActionsListeners) { listener.invokeDateSelectionGUI(true); } } }); timeEndLabel = timeEndLabelButton; timeEndLabel.setFont(f); gridbag.setConstraints(timeEndLabel, c); add(timeEndLabel); } else { timeStartLabel = new JLabel(NO_TIME_STRING); Font f = timeStartLabel.getFont(); f = new Font(f.getFamily(), f.getStyle(), f.getSize() - 1); timeStartLabel.setFont(f); gridbag.setConstraints(timeStartLabel, c); add(timeStartLabel); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0f; JLabel buffer = new JLabel(); gridbag.setConstraints(buffer, c); add(buffer); c.fill = GridBagConstraints.NONE; c.weightx = 0f; timeEndLabel = new JLabel(NO_TIME_STRING, JLabel.RIGHT); timeEndLabel.setFont(f); gridbag.setConstraints(timeEndLabel, c); add(timeEndLabel); } } public String getPreferredLocation() { return BorderLayout.SOUTH; } public void setPreferredLocation(String string) { } public void updateTimeLabels(long startTime, long endTime) { if (realTimeMode) { ((JButton) timeStartLabel).setText(getLabelStringForTime(startTime)); ((JButton) timeEndLabel).setText(getLabelStringForTime(endTime)); } else { ((JLabel) timeStartLabel).setText(getLabelStringForTime(startTime)); ((JLabel) timeEndLabel).setText(getLabelStringForTime(endTime)); } } public String getLabelStringForTime(long time) { String ret = NO_TIME_STRING; if (time != Long.MAX_VALUE && time != Long.MIN_VALUE) { Date date = new Date(time); ret = TimePanel.dateFormat.format(date); } return ret; } public String getParentName() { // TODO Auto-generated method stub return null; } } public void paint(Graphics g) { try { super.paint(g); } catch (Exception e) { if (logger.isLoggable(Level.FINE)) { logger.warning(e.getMessage()); e.printStackTrace(); } } } public static class TimeDrape extends OMRect { int lo; int to; int ro; int bo; public TimeDrape(int leftOffset, int topOffset, int rightOffset, int bottomOffset) { super(0, 0, 0, 0); lo = leftOffset; to = topOffset; ro = rightOffset; bo = bottomOffset; } public boolean generate(Projection proj) { setLocation(0 + lo, 0 + to, proj.getWidth() + ro, proj.getHeight() + bo); return super.generate(proj); } } public void updateTimeBounds(TimeBoundsEvent tbe) { if (logger.isLoggable(Level.FINE)) { logger.fine("updating time bounds: " + tbe); } TimeBounds timeBounds = (TimeBounds) tbe.getNewTimeBounds(); if (timeBounds != null) { updateTimeBounds(timeBounds.getStartTime(), timeBounds.getEndTime()); } else { // TODO handle when time bounds are null, meaning when no time // bounds providers are active. } } public void updateTimeBounds(long start, long end) { // Recall that currentTime is in relative space (assumes start == 0) long boundsStartOffset = start - gameStartTime; currentTime -= boundsStartOffset; selectionCenter = TimelineLayer.forwardProjectMillis(currentTime); gameStartTime = start; gameEndTime = end; updateTimeLabels(gameStartTime, gameEndTime); // DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy // HH:mm:ss"); // Date date = new Date(gameStartTime); // String sts = dateFormat.format(date); // date.setTime(gameEndTime); // String ets = dateFormat.format(date); maxSelectionWidthMinutes = TimelineLayer.forwardProjectMillis(gameEndTime - gameStartTime); if (realTimeMode && !userHasChangedScale) { selectionWidthMinutes = maxSelectionWidthMinutes; } else { if (selectionWidthMinutes > maxSelectionWidthMinutes || selectionWidthMinutes < .0001) { if (logger.isLoggable(Level.FINE)) { logger.fine("resetting selectionWidthMinutes to max (time bounds property change), was " + selectionWidthMinutes + ", now " + maxSelectionWidthMinutes); } selectionWidthMinutes = maxSelectionWidthMinutes; } } finalizeProjection(); updateTimeline(); doPrepare(); repaint(); } /** * Treat mouse wheel rotations like slider-handle drags. * * @param rot The number of rotation clicks (positive for zoom in, negative * for zoom out). */ public void adjustZoomFromMouseWheel(int rot) { Projection projection = getProjection(); if (projection == null) { return; // Huhn? } setUserHasChangedScale(true); // Determine the effect of growing / shrinking the slider scale by 'rot' // pixels // So given current selection width (in minutes) and minutes-per-pixel, // just // apply rot as a delta Point2D minutesPnt0 = projection.inverse(0, 0); Point2D minutesPnt1 = projection.inverse(1, 0); double minutesPerPixel = minutesPnt1.getX() - minutesPnt0.getX(); double minSelectionWidthMinutes = minutesPerPixel * sliderPointHalfWidth * 2; double selectionWidthPixels = selectionWidthMinutes / minutesPerPixel; double multiplier = selectionWidthPixels / 40; // Use a multiplier selectionWidthMinutes += rot * minutesPerPixel * multiplier; if (selectionWidthMinutes < minSelectionWidthMinutes) { selectionWidthMinutes = minSelectionWidthMinutes; } if (selectionWidthMinutes > maxSelectionWidthMinutes) { selectionWidthMinutes = maxSelectionWidthMinutes; } updateTimeline(); doPrepare(); } public void addTimeBoundsUserActionsListener(ITimeBoundsUserActionsListener timeBoundsUserActionsListener) { timeBoundsUserActionsListeners.add(timeBoundsUserActionsListener); } public void removeTimeBoundsUserActionsListener(ITimeBoundsUserActionsListener timeBoundsUserActionsListener) { timeBoundsUserActionsListeners.remove(timeBoundsUserActionsListener); } public void clearFixedRenderRange() { for (ITimeBoundsUserActionsListener listener : timeBoundsUserActionsListeners) { listener.clearFixedRenderRange(); } } void setSelectionValid(boolean valid) { zoomToSelection.setEnabled(valid); renderFixedSelection.setEnabled(valid); } }