package com.ibm.nmon.gui.interval;
import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.Date;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JFormattedTextField;
import javax.swing.JOptionPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;
import javax.swing.JSpinner.DateEditor;
import javax.swing.SwingConstants;
import javax.swing.text.DefaultFormatter;
import javax.swing.text.DefaultFormatterFactory;
import com.ibm.nmon.gui.main.NMONVisualizerGui;
import com.ibm.nmon.gui.time.TimeMaskFormatter;
import com.ibm.nmon.interval.IntervalManager;
import com.ibm.nmon.interval.Interval;
import com.ibm.nmon.gui.Styles;
/**
* Panel for entering multiple intervals using an absolute start time and a repeating duration.
* Offsets between each interval are also supported.
*/
public final class BulkIntervalPanel extends BaseIntervalPanel {
private static final long serialVersionUID = 1817418187436308391L;
// base date time
private final JSpinner start;
private final JFormattedTextField duration;
private final JFormattedTextField days;
private final JFormattedTextField repeat;
private final JFormattedTextField offset;
private final JLabel end;
private final JLabel durationLabel;
private final JLabel repeatLabel;
private final JLabel offsetLabel;
private final SimpleDateFormat FORMAT = new SimpleDateFormat(Styles.DATE_FORMAT_STRING);
private final BulkDocumentListener durationListener;
private final BulkDocumentListener daysListener;
private final BulkDocumentListener repeatListener;
private final BulkDocumentListener offsetListener;
public BulkIntervalPanel(NMONVisualizerGui gui) {
super(gui);
setLayout(new BorderLayout());
JLabel startLabel = new JLabel("Start:");
startLabel.setHorizontalAlignment(SwingConstants.TRAILING);
startLabel.setFont(Styles.LABEL);
start = new JSpinner(new SpinnerDateModel(new Date(getDefaultStartTime()), null, null, Calendar.MINUTE));
start.setEditor(new DateEditor(start, Styles.DATE_FORMAT_STRING_WITH_YEAR));
JLabel endLabel = new JLabel("End:");
endLabel.setHorizontalAlignment(SwingConstants.TRAILING);
endLabel.setVerticalAlignment(SwingConstants.BOTTOM);
endLabel.setFont(Styles.LABEL);
end = new JLabel();
end.setFont(Styles.BOLD);
// end.setVerticalAlignment(SwingConstants.TOP);
DefaultFormatter formatter = new DefaultFormatter();
formatter.setValueClass(Integer.class);
durationLabel = new JLabel("Duration:");
durationLabel.setHorizontalAlignment(SwingConstants.TRAILING);
durationLabel.setFont(Styles.LABEL);
duration = new JFormattedTextField();
duration.setFormatterFactory(TimeMaskFormatter.createFormatterFactory(true));
duration.setColumns(5);
duration.setValue(0);
days = new JFormattedTextField();
days.setFormatterFactory(new DefaultFormatterFactory(formatter));
// assume a small number of days will be entered
days.setColumns(3);
days.setValue(1);
days.setHorizontalAlignment(SwingConstants.TRAILING);
repeatLabel = new JLabel("Repeat:");
repeatLabel.setHorizontalAlignment(SwingConstants.TRAILING);
repeatLabel.setFont(Styles.LABEL);
repeat = new JFormattedTextField();
repeat.setFormatterFactory(new DefaultFormatterFactory(formatter));
// assume a small number of repeats will be entered
repeat.setColumns(3);
repeat.setHorizontalAlignment(SwingConstants.TRAILING);
repeat.setValue(2);
offsetLabel = new JLabel("Time Between:");
offsetLabel.setHorizontalAlignment(SwingConstants.TRAILING);
offsetLabel.setFont(Styles.LABEL);
offset = new JFormattedTextField();
offset.setFormatterFactory(TimeMaskFormatter.createFormatterFactory(true));
offset.setColumns(5);
offset.setValue(0);
// set last after other fields that determine the end time are setup
end.setText((FORMAT.format(new Date(getEndTime()))));
JButton hourly = new JButton("1 Hour");
hourly.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Calendar cal = Calendar.getInstance(BulkIntervalPanel.this.gui.getDisplayTimeZone());
cal.setTime((Date) start.getValue());
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
start.setValue(cal.getTime());
duration.setValue(3600);
days.setValue(0);
requestFocus(repeat);
}
});
JButton daily = new JButton("1 Day");
daily.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Calendar cal = Calendar.getInstance(BulkIntervalPanel.this.gui.getDisplayTimeZone());
cal.setTime((Date) start.getValue());
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
start.setValue(cal.getTime());
duration.setValue(0);
days.setValue(1);
requestFocus(repeat);
}
});
// 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;
// stretch out start and end times
fieldConstraints.gridwidth = 2;
centerPanel.add(startLabel, labelConstraints);
centerPanel.add(start, fieldConstraints);
labelConstraints.gridx += 3;
fieldConstraints.gridx += 3;
centerPanel.add(endLabel, labelConstraints);
centerPanel.add(end, fieldConstraints);
labelConstraints.gridx = 0;
fieldConstraints.gridx = 1;
fieldConstraints.gridwidth = 1;
fieldConstraints.gridx = 1;
++labelConstraints.gridy;
++fieldConstraints.gridy;
centerPanel.add(durationLabel, labelConstraints);
centerPanel.add(duration, fieldConstraints);
++fieldConstraints.gridx;
fieldConstraints.gridx = 1;
++labelConstraints.gridy;
++fieldConstraints.gridy;
centerPanel.add(durationLabel, labelConstraints);
centerPanel.add(duration, fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(new JLabel("HH:mm:ss"), fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(days, fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(new JLabel("days"), fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(hourly, fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(daily, fieldConstraints);
fieldConstraints.gridx = 1;
++labelConstraints.gridy;
++fieldConstraints.gridy;
centerPanel.add(repeatLabel, labelConstraints);
centerPanel.add(repeat, fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(new JLabel("times"), fieldConstraints);
fieldConstraints.gridx = 1;
++labelConstraints.gridy;
++fieldConstraints.gridy;
centerPanel.add(offsetLabel, labelConstraints);
centerPanel.add(offset, fieldConstraints);
++fieldConstraints.gridx;
centerPanel.add(new JLabel("HH:mm:ss"), 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);
// override the BaseIntervalPanel action to instead create multiple intervals
ActionListener addIntervals = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int repeatCount = (Integer) repeat.getValue();
if (repeatCount == 0) {
return;
}
// arbitrary limit to keep users from entering huge numbers
if (repeatCount > 99) {
JOptionPane.showMessageDialog(BulkIntervalPanel.this.getParent(),
"Repeat count must be less than 100", "Large Repeat Count", JOptionPane.WARNING_MESSAGE);
return;
}
long durationMillis = ((Integer) duration.getValue() * 1000L) + ((Integer) days.getValue() * 86400000L);
if (durationMillis == 0) {
return;
}
long startTime = getStartTime();
IntervalManager intervalManager = BulkIntervalPanel.this.gui.getIntervalManager();
Interval interval = null;
for (int i = 0; i < repeatCount; i++) {
long endTime = startTime + durationMillis;
interval = new Interval(startTime, endTime);
// append a number to each name
if (!"".equals(name.getText())) {
interval.setName(name.getText() + ' ' + (i + 1));
}
startTime = endTime + ((Integer) offset.getValue() * 1000L);
if (intervalManager.addInterval(interval)) {
firePropertyChange("interval", intervalManager.getCurrentInterval(), interval);
}
}
intervalManager.setCurrentInterval(interval);
}
};
add.addActionListener(addIntervals);
durationListener = new BulkDocumentListener(this, duration, duration, days, repeat, offset);
daysListener = new BulkDocumentListener(this, days, duration, days, repeat, offset);
repeatListener = new BulkDocumentListener(this, repeat, duration, days, repeat, offset);
offsetListener = new BulkDocumentListener(this, repeat, duration, days, repeat, offset);
duration.getDocument().addDocumentListener(durationListener);
days.getDocument().addDocumentListener(daysListener);
repeat.getDocument().addDocumentListener(repeatListener);
offset.getDocument().addDocumentListener(offsetListener);
durationListener.addPropertyChangeListener(this);
daysListener.addPropertyChangeListener(this);
repeatListener.addPropertyChangeListener(this);
offsetListener.addPropertyChangeListener(this);
start.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
long startTime = getStartTime();
long endTime = getEndTime();
if (startTime < endTime) {
Interval i = new Interval(startTime, endTime);
firePropertyChange("interval", null, i);
end.setText(FORMAT.format(new Date(endTime)));
}
}
});
}
@Override
protected long getEndTime() {
return getEndTime(getStartTime(), (Integer) duration.getValue(), (Integer) days.getValue(),
(Integer) repeat.getValue(), (Integer) offset.getValue());
}
long getEndTime(long startTime, int duration, int days, int repeat, int offset) {
// subtract one offset because it is not needed on last interval
return startTime + ((repeat * (duration + days * 86400 + offset)) - offset) * 1000;
}
@Override
long getStartTime() {
return ((Date) start.getValue()).getTime();
}
@Override
TimeZone getTimeZone() {
DateEditor de = (DateEditor) start.getEditor();
return de.getFormat().getTimeZone();
}
@Override
protected void setStartToEnd() {
start.setValue(new Date(getEndTime()));
requestFocus(duration);
}
@Override
protected void setTimes(long start, long end) {
if (end > start) {
int offsetMillis = (Integer) offset.getValue();
int repeatCount = (Integer) repeat.getValue();
long durationMillis = 0;
if (repeatCount == 0) {
durationMillis = end - start - offsetMillis;
}
else {
durationMillis = (end - start - (offsetMillis * (repeatCount - 1))) / repeatCount;
}
int numDays = (int) (durationMillis / 86400000L);
int endTime = (int) (durationMillis / 1000 % 86400);
days.setValue(numDays);
duration.setValue(endTime);
}
else {
duration.setValue(0);
days.setValue(0);
// update end here since it will not update in propertyChange() with an invalid itnerval
this.end.setText((FORMAT.format(new Date(end))));
}
// set start last since it fires a ChangeEvent which needs the new end time
this.start.setValue(new Date(start));
requestFocus(duration);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (enabled) {
requestFocus(duration);
}
}
@Override
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
// document listeners should also propagate property changes to listeners
durationListener.addPropertyChangeListener(listener);
daysListener.addPropertyChangeListener(listener);
repeatListener.addPropertyChangeListener(listener);
offsetListener.addPropertyChangeListener(listener);
super.addPropertyChangeListener(propertyName, listener);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("timeZone".equals(evt.getPropertyName())) {
TimeZone timeZone = (TimeZone) evt.getNewValue();
DateEditor de = (DateEditor) start.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) start.getModel()).setCalendarField(Calendar.MINUTE);
((SpinnerDateModel) start.getModel()).setCalendarField(Calendar.SECOND);
FORMAT.setTimeZone(timeZone);
end.setText(FORMAT.format(getEndTime()));
}
else if ("values".equals(evt.getPropertyName())) {
long[] updatedValues = (long[]) evt.getNewValue();
boolean validDuration = true;
// 0 days, then duration must be valid & non-zero
if (updatedValues[1] == 0) {
// duration
if (updatedValues[0] < 1) {
validDuration = false;
}
}
if (validDuration) {
durationLabel.setFont(Styles.LABEL);
durationLabel.setForeground(Styles.DEFAULT_COLOR);
duration.setForeground(Styles.DEFAULT_COLOR);
days.setForeground(Styles.DEFAULT_COLOR);
}
else {
durationLabel.setFont(Styles.LABEL_ERROR);
durationLabel.setForeground(Styles.ERROR_COLOR);
duration.setForeground(Styles.ERROR_COLOR);
days.setForeground(Styles.ERROR_COLOR);
}
// repeat
if (updatedValues[2] == 0) {
repeatLabel.setFont(Styles.LABEL_ERROR);
repeatLabel.setForeground(Styles.ERROR_COLOR);
repeat.setForeground(Styles.ERROR_COLOR);
offset.setEnabled(false);
}
else {
repeatLabel.setFont(Styles.LABEL);
repeatLabel.setForeground(Styles.DEFAULT_COLOR);
repeat.setForeground(Styles.DEFAULT_COLOR);
if (updatedValues[2] == 1) {
// repeating once => no offset
offset.setEnabled(false);
}
else {
offset.setEnabled(true);
}
}
// offset: -1 => invalid
if (updatedValues[3] < 0) {
offsetLabel.setFont(Styles.LABEL_ERROR);
offsetLabel.setForeground(Styles.ERROR_COLOR);
offset.setForeground(Styles.ERROR_COLOR);
}
else {
offsetLabel.setFont(Styles.LABEL);
offsetLabel.setForeground(Styles.DEFAULT_COLOR);
offset.setForeground(Styles.DEFAULT_COLOR);
}
// end time
if (updatedValues[4] != -1) {
end.setText(FORMAT.format(updatedValues[4]));
}
}
}
}