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])); } } } }