// ********************************************************************** // // <copyright> // // BBN Technologies, a Verizon Company // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/gui/time/TimePanel.java,v $ // $RCSfile: TimePanel.java,v $ // $Revision: 1.1 $ // $Date: 2007/09/25 17:31:26 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.gui.time; import java.awt.BorderLayout; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.BorderFactory; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JToggleButton; import javax.swing.SwingConstants; import javax.swing.border.AbstractBorder; import com.bbn.openmap.I18n; import com.bbn.openmap.event.OMEventSelectionCoordinator; import com.bbn.openmap.gui.MapPanelChild; import com.bbn.openmap.gui.OMComponentPanel; import com.bbn.openmap.gui.event.EventPresenter; import com.bbn.openmap.time.Clock; import com.bbn.openmap.time.TimeEvent; import com.bbn.openmap.time.TimeEventListener; import com.bbn.openmap.time.TimerRateHolder; import com.bbn.openmap.util.PropUtils; /** * The TimePanel is a GUI widget that provides assortment of Clock controls, * including play, step and reverse buttons, a rate controller, a current time * label and a time slider. * <P> * A Clock is needed to create an interface. If there is no clock, an empty * panel with a title will be displayed. * <P> * If you are using the openmap.properties file to configure your application * and want to control the parent component of the TimePanel, use the "parent" * property to specify the marker name of the parent component. The * BasicMapPanel asks MapPanelChildren for their parent's names when deciding * whether to add them or not, when it finds potential children in the * findAndInit method. */ public class TimePanel extends OMComponentPanel implements MapPanelChild, PropertyChangeListener, TimeEventListener { public static Logger logger = Logger.getLogger("com.bbn.openmap.gui.time.TimePanel"); /** * This property is used to signify whether the play filter should be used. */ public final static String PlayFilterProperty = "playfilter"; public final static String NO_TIME_STRING = "--:--:-- (--:--:--)"; public final static String PanelTitleProperty = "panelTitle"; private String defaultPanelTitle = " Timeline Controls "; public final static String RealTimeModeProperty = "realTimeMode"; private boolean realTimeMode = false; public final static String ShowPlayFilterProperty = "showPlayFilter"; private boolean showPlayFilter = true; /** * The Clock object used by the TimePanel. */ protected Clock clock; // / GUI ToolPanel widgets. Kept here to make their visibility // adjustable. protected JToggleButton timeWrapToggle; // protected JSlider timeSlider; protected JLabel timeLabel; protected JLabel mouseTimeLabel; protected JLabel eventDetailLabel; protected JCheckBox playFilter; protected HotwashTimerControlButtonPanel timerControl; protected TimerRateComboBox timerRateControl; protected String preferredLocation = BorderLayout.SOUTH; protected boolean useTimeWrapToggle = false; public transient DecimalFormat df = new DecimalFormat("00"); // KMMOD TimelinePanel timelinePanel; TimeSliderPanel timeSliderPanel; protected String parentName; // Isolating formats that have anything to do with months+days, for International audiences final public static DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy (HH:mm:ss)"); final public static DateFormat dateFormat_realTime = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); final public static DateFormat dayFormat = new SimpleDateFormat("MM/dd/yyyy"); public TimePanel() { // Needs Clock to create interface. } public class NoBorder extends AbstractBorder { NoBorder() {} } public void setProperties(String prefix, Properties props) { super.setProperties(prefix, props); prefix = PropUtils.getScopedPropertyPrefix(prefix); parentName = props.getProperty(prefix + MapPanelChild.ParentNameProperty); defaultPanelTitle = props.getProperty(prefix + PanelTitleProperty, defaultPanelTitle); realTimeMode = PropUtils.booleanFromProperties(props, prefix + RealTimeModeProperty, realTimeMode); showPlayFilter = PropUtils.booleanFromProperties(props, prefix + ShowPlayFilterProperty, showPlayFilter); if(realTimeMode) { // At this point, we know we're going to need a TimeSliderPanel getTimeSliderPanel(); } } /** * A Clock is needed to create an interface. If there is no clock, an empty * panel with a title will be displayed. */ public void createInterface() { removeAll(); String internString = i18n.get(this.getClass(), "timeline_controls", defaultPanelTitle); setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), internString)); if (clock == null) { logger.info("No clock, not putting anything in interface."); return; } // javax.swing.Border debugBorder = // BorderFactory.createLineBorder(Color.red); JPanel leftPanel = new JPanel(); GridBagLayout lgridbag = new GridBagLayout(); leftPanel.setLayout(lgridbag); GridBagConstraints c = new GridBagConstraints(); Insets insets = new Insets(2, 4, 2, 4); c.weightx = 0f; c.fill = GridBagConstraints.VERTICAL; c.weighty = 1f; c.gridx = GridBagConstraints.REMAINDER; c.insets = insets; internString = i18n.get(this.getClass(), "play_selected", "Play Filter"); if(showPlayFilter) { playFilter = new JCheckBox(internString); internString = i18n.get(this.getClass(), "play_selected", I18n.TOOLTIP, "Jump clock to events with play filter markings."); playFilter.setToolTipText(internString); playFilter.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { JCheckBox jcb = (JCheckBox) ae.getSource(); firePropertyChange(PlayFilterProperty, new Boolean(!jcb.isSelected()), new Boolean(jcb.isSelected())); } }); lgridbag.setConstraints(playFilter, c); playFilter.setVisible(false); leftPanel.add(playFilter); } c.fill = GridBagConstraints.NONE; c.weighty = 0f; JLabel dateTimeLabel = new JLabel("Date (Local Time)", SwingConstants.CENTER); lgridbag.setConstraints(dateTimeLabel, c); leftPanel.add(dateTimeLabel); timeLabel = new JLabel(NO_TIME_STRING, SwingConstants.CENTER); Font defaultFont = timeLabel.getFont(); timeLabel.setFont(new java.awt.Font(defaultFont.getName(), defaultFont.getStyle(), 12)); internString = i18n.get(this.getClass(), "time", I18n.TOOLTIP, "Time"); timeLabel.setToolTipText(internString); lgridbag.setConstraints(timeLabel, c); leftPanel.add(timeLabel); timerControl = new HotwashTimerControlButtonPanel(clock); lgridbag.setConstraints(timerControl, c); leftPanel.add(timerControl); clock.addTimeEventListener(timerControl); /* * Not Used, but strangely enough needs to be created in order to tell * the clock how fast to run, one second per clock tick. */ timerRateControl = new TimerRateComboBox(clock); internString = i18n.get(this.getClass(), "timer_rate_control", I18n.TOOLTIP, "Change Clock Rate For Timeline"); timerRateControl.setToolTipText(internString); List<TimerRateHolder> timerRates = clock.getTimerRates(); Iterator<TimerRateHolder> it = timerRates.iterator(); while (it.hasNext()) { TimerRateHolder trh = it.next(); timerRateControl.add(trh.getLabel(), (int) trh.getClockInterval(), (int) trh.getPace()); } int si = timerRates.size() / 2; if (si > 0) { timerRateControl.setSelectedIndex(si); } // lgridbag.setConstraints(timerRateControl, c); // add(timerRateControl); // Right side insets = new Insets(0, 4, 0, 4); JPanel rightPanel = new JPanel(); GridBagLayout rgridbag = new GridBagLayout(); rightPanel.setLayout(rgridbag); c = new GridBagConstraints(); c.insets = insets; c.fill = GridBagConstraints.NONE; c.weighty = 0.0; c.weightx = 0.0; c.gridy = 0; c.anchor = GridBagConstraints.WEST; internString = i18n.get(this.getClass(), "mouse_time", "Mouse Time:"); JLabel mouseTime = new JLabel(internString); rgridbag.setConstraints(mouseTime, c); rightPanel.add(mouseTime); mouseTimeLabel = new JLabel(""); rgridbag.setConstraints(mouseTimeLabel, c); rightPanel.add(mouseTimeLabel); c.anchor = GridBagConstraints.EAST; c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0; eventDetailLabel = new JLabel("", SwingConstants.RIGHT); rgridbag.setConstraints(eventDetailLabel, c); rightPanel.add(eventDetailLabel); TimelinePanel timelinePanel = getTimelinePanel(); timelinePanel.addMapComponent(new TimePanel.Wrapper(this)); c.fill = GridBagConstraints.BOTH; c.weighty = 1f; c.weightx = 1f; c.gridwidth = GridBagConstraints.REMAINDER; c.gridy = 1; rgridbag.setConstraints(timelinePanel, c); rightPanel.add(timelinePanel); TimeSliderPanel timeSliderPanel = getTimeSliderPanel(); // Slider needs to know about the timeline to set projection timeSliderPanel.addMapComponent(timelinePanel.getWrapper()); c.fill = GridBagConstraints.HORIZONTAL; c.weighty = 0f; c.gridy = 2; rgridbag.setConstraints(timeSliderPanel, c); rightPanel.add(timeSliderPanel); GridBagLayout gridbag = new GridBagLayout(); setLayout(gridbag); c = new GridBagConstraints(); c.fill = GridBagConstraints.VERTICAL; c.weighty = 1f; c.insets = new Insets(0, 0, 0, 0); gridbag.setConstraints(leftPanel, c); add(leftPanel); c.fill = GridBagConstraints.BOTH; c.weightx = 1f; c.insets = new Insets(0, 0, 4, 0); gridbag.setConstraints(rightPanel, c); add(rightPanel); revalidate(); } public void updateEventDetailsDisplay(String details) { if (eventDetailLabel != null) { eventDetailLabel.setText(details); } } /** * Displays the provided offset time in the Mouse Time display label. Time * is expected to be milliseconds offset from the beginning of displayed * time. * * @param mouseOffsetTime */ public void updateMouseTimeDisplay(long mouseOffsetTime) { String mtds = "--"; if (realTimeMode) { if (mouseOffsetTime > timelinePanel.getTimelineLayer().getEndTime()) { mouseOffsetTime = timelinePanel.getTimelineLayer().getEndTime(); } } else { if (mouseOffsetTime < 0) { mouseOffsetTime = 0; } } if (mouseOffsetTime >= 0) { mtds = convertOffsetTimeToText(mouseOffsetTime); } if (mouseTimeLabel != null) { mouseTimeLabel.setText(mtds); } } public String convertOffsetTimeToText(long offsetTimeFromLeft) { long offsetTime = offsetTimeFromLeft; if(realTimeMode) { offsetTime -= timelinePanel.getTimelineLayer().getDuration(); } String sign = ""; if(offsetTime < 0) { sign = "-"; offsetTime = -offsetTime; } int hours = (int) (offsetTime / (60 * 60 * 1000)); int minutes = (int) Math.abs((offsetTime % (60 * 60 * 1000)) / (60 * 1000)); int seconds = (int) Math.abs((offsetTime % (60 * 1000)) / 1000); String relativeTime = sign + df.format(hours) + ":" + df.format(minutes) + ":" + df.format(seconds); if(realTimeMode) { Date date = new Date(offsetTimeFromLeft + timeSliderPanel.getTimeSliderLayer().gameStartTime); return TimePanel.dateFormat_realTime.format(date) + " (" + relativeTime + ")"; } else { return relativeTime; } } public void setPreferredLocation(String loc) { preferredLocation = loc; } public String getPreferredLocation() { return preferredLocation; } public void updateTime(TimeEvent te) { if (checkAndSetForNoTime(te)) { return; } if (logger.isLoggable(Level.FINE)) { logger.fine("TimePanel received TIMER_STATUS property update: " + te); } updateTimeLabel(te.getSystemTime(), te.getOffsetTime()); } /** * PropertyChangeListener method called when a Clock fires, or the Clock * time bounds change. */ public void propertyChange(PropertyChangeEvent pce) { String propertyName = pce.getPropertyName(); Object newVal = pce.getNewValue(); if (propertyName.equals(TimelineLayer.PlayFilterProperty)) { timerControl.enableForwardButton(((Boolean) newVal).booleanValue()); } else if (propertyName.equals(TimelineLayer.MouseTimeProperty)) { updateMouseTimeDisplay(((Long) newVal).longValue()); } else if (propertyName.equals(TimelineLayer.EventDetailsProperty)) { updateEventDetailsDisplay((String) newVal); } revalidate(); } protected boolean checkAndSetForNoTime(TimeEvent te) { boolean isNoTime = te == TimeEvent.NO_TIME; if (isNoTime) { updateEventDetailsDisplay(""); updateMouseTimeDisplay(0l); timeLabel.setText(NO_TIME_STRING); } // timerControl.setEnableState(!isNoTime); return isNoTime; } /** * Updates the timeLabel with the proper formats, dashes if needed. * * @param sysTime * @param offsetTime */ public void updateTimeLabel(long sysTime, long offsetTime) { // Do this so the label is reset if the time changes. if (timeLabel != null) { if (sysTime != Long.MAX_VALUE) { if(realTimeMode) { Date date = new Date(sysTime); String timeText = TimePanel.dateFormat_realTime.format(date); timeLabel.setText(timeText); } else { Date date = new Date(sysTime); DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); String sts = dateFormat.format(date); timeLabel.setText(sts + " (" + convertOffsetTimeToText(offsetTime) + ")"); } } else { timeLabel.setText(NO_TIME_STRING); } } } public void setClock(Clock cl) { logger.fine("found and setting clock: " + cl); if (clock != null) { clock.removeTimeEventListener(this); } clock = cl; createInterface(); // Moved here for distributed // configuration if (clock != null) { clock.addTimeEventListener(this); } } public void setPlayFilterVisible(boolean visible) { if (playFilter != null) { playFilter.setVisible(visible); } } public boolean isPlayFilterVisible() { return playFilter != null && playFilter.isVisible(); } public Clock getClock() { return clock; } /** * OMComponentPanel method, called when new components are added to the * MapHandler. Lets the TimePanel register itself as PropertyChangeListener * to the Clock. */ public void findAndInit(Object someObj) { if (someObj instanceof Clock) { setClock((Clock) someObj); getTimelinePanel().getMapHandler().add(someObj); getTimeSliderPanel().getMapHandler().add(someObj); } if (someObj instanceof OMEventSelectionCoordinator) { getTimelinePanel().getMapHandler().add(someObj); } if (someObj instanceof EventPresenter) { getTimelinePanel().getMapHandler().add(someObj); } } /** * OMComponentPanel method, called when new components are removed from the * MapHandler. Lets the TimePanel unregister itself as * PropertyChangeListener to the Clock. */ public void findAndUndo(Object someObj) { if (someObj instanceof Clock && getClock() == someObj) { setClock(null); } } public static class Wrapper { TimePanel timePanel; public Wrapper(TimePanel panel) { this.timePanel = panel; } public TimePanel getTimePanel() { return timePanel; } } public String getParentName() { return parentName; } public void setParentName(String pName) { parentName = pName; } public TimelinePanel getTimelinePanel() { if (timelinePanel == null) { timelinePanel = new TimelinePanel(); timelinePanel.setRealTimeMode(realTimeMode); } return timelinePanel; } public TimeSliderPanel getTimeSliderPanel() { if (timeSliderPanel == null) { timeSliderPanel = new TimeSliderPanel(realTimeMode); } return timeSliderPanel; } public void setUserHasChangedScale(boolean userHasChangedScale) { timeSliderPanel.setUserHasChangedScale(userHasChangedScale); } public void addTimeBoundsUserActionsListener( ITimeBoundsUserActionsListener timeBoundsUserActionsListener) { timeSliderPanel.addTimeBoundsUserActionsListener(timeBoundsUserActionsListener); } public void removeTimeBoundsUserActionsListener( ITimeBoundsUserActionsListener timeBoundsUserActionsListener) { timeSliderPanel.removeTimeBoundsUserActionsListener(timeBoundsUserActionsListener); } }