package com.ibm.nmon.gui.interval;
import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;
import javax.swing.SwingConstants;
import javax.swing.JSpinner.DateEditor;
import javax.swing.text.DefaultFormatter;
import javax.swing.text.DefaultFormatterFactory;
import com.ibm.nmon.gui.Styles;
import com.ibm.nmon.gui.main.NMONVisualizerGui;
import com.ibm.nmon.gui.time.TimeMaskFormatter;
import com.ibm.nmon.interval.Interval;
/**
* Panel for entering intervals using relative times. Start and end times are specified in 24 hour
* format. The start time is relative to a base datetime. The end time also supports specifying a
* number of days. Also displays absolute time labels for the start and the end that update as the
* user changes inputs.
*/
final class RelativeTimeIntervalPanel extends BaseIntervalPanel {
private static final long serialVersionUID = -1611515609994676644L;
// base date time
private final JSpinner base;
// start and end times in 24H format
private final JFormattedTextField start;
private final JFormattedTextField end;
// days the interval covers; used with the end time
private final JFormattedTextField days;
private final JLabel startLabel;
private final JLabel endLabel;
private final JLabel startAbsolute;
private final JLabel endAbsolute;
private final SimpleDateFormat FORMAT = new SimpleDateFormat(Styles.DATE_FORMAT_STRING);
private final RelativeTimeDocumentListener startListener;
private final RelativeTimeDocumentListener endListener;
private final RelativeTimeDocumentListener daysListener;
RelativeTimeIntervalPanel(NMONVisualizerGui gui) {
super(gui);
setLayout(new BorderLayout());
add.addActionListener(addInterval);
JLabel baseLabel = new JLabel("Base Time:");
baseLabel.setHorizontalAlignment(SwingConstants.TRAILING);
baseLabel.setFont(Styles.LABEL);
base = new JSpinner(new SpinnerDateModel(new Date(getDefaultStartTime()), null, null, Calendar.MINUTE));
base.setEditor(new DateEditor(base, Styles.DATE_FORMAT_STRING_WITH_YEAR));
base.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
long startTime = getStartTime();
long endTime = getEndTime();
startAbsolute.setText(FORMAT.format(new Date(getStartTime())));
endAbsolute.setText((FORMAT.format(new Date(getEndTime()))));
if (startTime < endTime) {
Interval i = new Interval(startTime, endTime);
firePropertyChange("interval", null, i);
}
}
});
startLabel = new JLabel("Start:");
startLabel.setFont(Styles.LABEL);
startLabel.setHorizontalAlignment(SwingConstants.TRAILING);
start = new JFormattedTextField();
start.setName("start");
start.setFormatterFactory(TimeMaskFormatter.createFormatterFactory(true));
// may not be enough columns with larger fonts
start.setColumns(5);
start.setValue(0);
endLabel = new JLabel("End:");
endLabel.setFont(Styles.LABEL);
endLabel.setHorizontalAlignment(SwingConstants.TRAILING);
end = new JFormattedTextField();
end.setName("end");
end.setFormatterFactory(TimeMaskFormatter.createFormatterFactory(false));
// may not be enough columns with larger fonts
end.setColumns(5);
end.setValue(0);
DefaultFormatter formatter = new DefaultFormatter();
formatter.setValueClass(Integer.class);
days = new JFormattedTextField();
days.setFormatterFactory(new DefaultFormatterFactory(formatter));
// assume a small number of days will be entered
days.setColumns(3);
days.setValue(0);
days.setHorizontalAlignment(SwingConstants.TRAILING);
startAbsolute = new JLabel();
startAbsolute.setFont(Styles.BOLD);
endAbsolute = new JLabel();
endAbsolute.setFont(Styles.BOLD);
// main panel contains name at top, buttons at bottom, all others in the CENTER
JPanel namePanel = new JPanel();
namePanel.add(nameLabel);
namePanel.add(name);
JPanel centerPanel = new JPanel(new GridBagLayout());
GridBagConstraints labelConstraints = new GridBagConstraints();
GridBagConstraints fieldConstraints = new GridBagConstraints();
labelConstraints.gridx = 0;
fieldConstraints.gridx = 1;
labelConstraints.gridy = 0;
fieldConstraints.gridy = 0;
labelConstraints.insets = new Insets(0, 0, 0, 5);
fieldConstraints.insets = new Insets(5, 0, 0, 5);
labelConstraints.fill = GridBagConstraints.HORIZONTAL;
fieldConstraints.fill = GridBagConstraints.HORIZONTAL;
JLabel hhmmss = new JLabel("HH:mm:ss");
// stretch out base time
fieldConstraints.gridwidth = 2;
centerPanel.add(baseLabel, labelConstraints);
centerPanel.add(base, fieldConstraints);
fieldConstraints.gridwidth = 1;
fieldConstraints.gridx = 1;
++labelConstraints.gridy;
++fieldConstraints.gridy;
// end also has days text box and label, so space out the absolute start label
centerPanel.add(startLabel, labelConstraints);
centerPanel.add(start, fieldConstraints);
++fieldConstraints.gridx;
fieldConstraints.gridwidth = 3;
centerPanel.add(hhmmss, fieldConstraints);
// put absolute time in last column
fieldConstraints.gridx += 3;
fieldConstraints.gridwidth = 1;
centerPanel.add(startAbsolute, fieldConstraints);
fieldConstraints.gridx = 1;
++labelConstraints.gridy;
++fieldConstraints.gridy;
hhmmss = new JLabel("HH:mm:ss");
JLabel daysLabel = new JLabel("days");
centerPanel.add(endLabel, labelConstraints);
centerPanel.add(end, fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(hhmmss, fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(days, fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(daysLabel, fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(endAbsolute, fieldConstraints);
JPanel buttonsPanel = new JPanel();
buttonsPanel.add(add);
buttonsPanel.add(endToStart);
buttonsPanel.add(reset);
add(namePanel, BorderLayout.PAGE_START);
add(centerPanel, BorderLayout.CENTER);
add(buttonsPanel, BorderLayout.PAGE_END);
// note the parent field in the listener is paired to the correct text box
startListener = new RelativeTimeDocumentListener(this, start, start, end, days);
endListener = new RelativeTimeDocumentListener(this, end, start, end, days);
daysListener = new RelativeTimeDocumentListener(this, days, start, end, days);
start.getDocument().addDocumentListener(startListener);
end.getDocument().addDocumentListener(endListener);
days.getDocument().addDocumentListener(daysListener);
startListener.addPropertyChangeListener(this);
endListener.addPropertyChangeListener(this);
daysListener.addPropertyChangeListener(this);
}
private long getBaseTime() {
return ((Date) base.getValue()).getTime();
}
@Override
protected long getStartTime() {
return getStartTime((Integer) start.getValue());
}
long getStartTime(int start) {
return getBaseTime() + start * 1000L;
}
@Override
long getEndTime() {
return getEndTime((Integer) end.getValue(), (Integer) days.getValue());
}
long getEndTime(int end, int days) {
return getBaseTime() + end * 1000L + days * 86400000L;
}
@Override
void setTimes(long start, long end) {
// set the base time so that the current start offset gives the correct time
long base = start - (Integer) this.start.getValue() * 1000;
if (end > start) {
long diff = end - base;
int numDays = (int) (diff / 86400000L);
int endTime = (int) (diff / 1000 % 86400);
days.setValue(numDays);
this.end.setValue(endTime);
}
// set base last since it fires a ChangeEvent which needs the new end time
this.base.setValue(new Date(base));
}
@Override
TimeZone getTimeZone() {
DateEditor de = (DateEditor) base.getEditor();
return de.getFormat().getTimeZone();
}
/**
* @param base
* epoch start time
* @param start
* milliseconds < 1 day
* @param end
* epoch end time
*/
@Override
protected void setStartToEnd() {
start.setValue(end.getValue());
requestFocus(start);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (enabled) {
requestFocus(start);
}
}
@Override
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
// document listeners should also propagate property changes to listeners
startListener.addPropertyChangeListener(listener);
endListener.addPropertyChangeListener(listener);
daysListener.addPropertyChangeListener(listener);
super.addPropertyChangeListener(propertyName, listener);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
// all but timeZone fired by IntervalDocumentListeners
if ("interval".equals(evt.getPropertyName())) {
if (evt.getNewValue() == null) {
startLabel.setFont(Styles.LABEL_ERROR);
endLabel.setFont(Styles.LABEL_ERROR);
startLabel.setForeground(Styles.ERROR_COLOR);
endLabel.setForeground(Styles.ERROR_COLOR);
start.setForeground(Styles.ERROR_COLOR);
end.setForeground(Styles.ERROR_COLOR);
days.setForeground(Styles.ERROR_COLOR);
}
else {
startLabel.setFont(Styles.LABEL);
endLabel.setFont(Styles.LABEL);
startLabel.setForeground(Styles.DEFAULT_COLOR);
endLabel.setForeground(Styles.DEFAULT_COLOR);
start.setForeground(Styles.DEFAULT_COLOR);
end.setForeground(Styles.DEFAULT_COLOR);
days.setForeground(Styles.DEFAULT_COLOR);
}
}
else if ("start".equals(evt.getPropertyName())) {
startAbsolute.setText(FORMAT.format(new Date((Long) evt.getNewValue())));
}
else if ("end".equals(evt.getPropertyName())) {
endAbsolute.setText(FORMAT.format(new Date((Long) evt.getNewValue())));
}
else if ("timeZone".equals(evt.getPropertyName())) {
TimeZone timeZone = (TimeZone) evt.getNewValue();
DateEditor de = (DateEditor) base.getEditor();
de.getFormat().setTimeZone(timeZone);
// hack to get the spinner to fire a state change and update the displayed value
// toggle the calendar field back to its original value
((SpinnerDateModel) base.getModel()).setCalendarField(Calendar.MINUTE);
((SpinnerDateModel) base.getModel()).setCalendarField(Calendar.SECOND);
FORMAT.setTimeZone(timeZone);
startAbsolute.setText(FORMAT.format(new Date(getStartTime())));
endAbsolute.setText((FORMAT.format(new Date(getEndTime()))));
}
}
}