View Javadoc

1   package org.lsst.ccs.plugin.jas3.trending.timeselection;
2   
3   import java.awt.Color;
4   import java.awt.Dimension;
5   import java.awt.Window;
6   import java.awt.event.ActionEvent;
7   import java.awt.event.ActionListener;
8   import java.text.ParseException;
9   import java.util.Calendar;
10  import java.util.Date;
11  import java.util.regex.Matcher;
12  import java.util.regex.Pattern;
13  import javax.swing.BorderFactory;
14  import javax.swing.Box;
15  import javax.swing.BoxLayout;
16  import javax.swing.ButtonGroup;
17  import javax.swing.JButton;
18  import javax.swing.JCheckBox;
19  import javax.swing.JComponent;
20  import javax.swing.JLabel;
21  import javax.swing.JOptionPane;
22  import javax.swing.JPanel;
23  import javax.swing.JRadioButton;
24  import javax.swing.JSpinner;
25  import javax.swing.JTextField;
26  import javax.swing.SpinnerDateModel;
27  import javax.swing.SpinnerNumberModel;
28  import javax.swing.SwingUtilities;
29  import javax.swing.event.CaretEvent;
30  import javax.swing.event.CaretListener;
31  import javax.swing.event.ChangeEvent;
32  import javax.swing.event.ChangeListener;
33  
34  /**
35   * GUI panel for entering a custom time window.
36   *
37   * @author onoprien
38   */
39  public class TimeSelectionPanel extends JPanel {
40  
41  // -- Private parts : ----------------------------------------------------------
42      
43      final Pattern _nowPattern = Pattern.compile("\\s*now\\s*-?\\s*(\\d*)\\s*");
44      final int MAX_YEARS = 50;
45      final int SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
46      final int SECONDS_IN_DAY = 24 * 60 * 60;
47      final int SECONDS_IN_HOUR = 60 * 60;
48      final int SECONDS_IN_MINUTE = 60;
49      
50      private final int VSPACE = 5;
51      private final int HSPACE = 10;
52      
53      private final TimePanel fromPanel;
54      private final TimePanel toPanel;
55      private final JTextField nameField;
56      private final JCheckBox saveCheck;
57      
58      static private TimeSelection range;
59      static private JButton okButton;
60  
61      
62  // -- Construction : -----------------------------------------------------------
63      
64      private TimeSelectionPanel() {
65          
66          setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
67          
68          fromPanel = new TimePanel(" Starting ");
69          add(fromPanel);
70          
71          add(Box.createRigidArea(new Dimension(0, 2*VSPACE)));
72          
73          toPanel = new TimePanel(" Ending ");
74          add(toPanel);
75          
76          add(Box.createRigidArea(new Dimension(0, 2*VSPACE)));
77          
78          Box saveBox = Box.createVerticalBox();
79          saveBox.setBorder(BorderFactory.createCompoundBorder(
80                      BorderFactory.createTitledBorder(" Remember As "), 
81                      BorderFactory.createEmptyBorder(VSPACE, HSPACE, VSPACE, HSPACE)));
82          nameField = new JTextField();
83          nameField.setColumns(30);
84          nameField.addCaretListener(new CaretListener() {
85              public void caretUpdate(CaretEvent e) {
86                  saveCheck.setEnabled(!nameField.getText().trim().isEmpty());
87              }
88          });
89          saveBox.add(nameField);
90          saveBox.add(Box.createRigidArea(new Dimension(0, VSPACE)));
91          saveCheck = new JCheckBox("Remember between sessions");
92          saveBox.add(saveCheck);
93          add(saveBox);
94  
95          if (range == null) {
96              fromPanel.setTime("now - "+ SECONDS_IN_DAY);
97              toPanel.setTime("now");
98              saveCheck.setEnabled(false);
99          } else {
100             fromPanel.setTime(range.lowerEdgeString);
101             toPanel.setTime(range.upperEdgeString);
102             nameField.setText(range.name);
103             saveCheck.setSelected(range.isPersistent());
104             saveCheck.setEnabled(true);
105         }
106     }
107 
108 
109 // -- Displaying the GUI : -----------------------------------------------------
110     
111     /**
112      * Opens trending time window creation/editing dialog.
113      * 
114      * @param parent Parent component.
115      * @param seed Time window to be edited, or <tt>null</tt> if a new time window should be created.
116      * @return Modified or created time window, or <tt>null</tt> if the dialog is canceled.
117      */
118     static public TimeSelection editTimeWindow(JComponent parent, TimeSelection seed) {
119         range = seed;
120         okButton = new JButton("  OK  ");
121         final TimeSelectionPanel tsp = new TimeSelectionPanel();
122         okButton.addActionListener(new ActionListener() {
123             public void actionPerformed(ActionEvent e) {
124                 tsp.getRange();
125                 Window w = SwingUtilities.getWindowAncestor(tsp);
126                 if (w != null) w.setVisible(false);
127             }
128         });
129         JButton cancelButton = new JButton("Cancel");
130         cancelButton.addActionListener(new ActionListener() {
131             public void actionPerformed(ActionEvent e) {
132                 range = null;
133                 Window w = SwingUtilities.getWindowAncestor(tsp);
134                 if (w != null) w.setVisible(false);
135             }
136         });
137         JOptionPane.showOptionDialog(parent, tsp, "Select time range", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, new Object[] {okButton,cancelButton}, null);
138         okButton = null;
139         return range;
140     }
141 
142     
143 // -- Local methods : ----------------------------------------------------------
144     
145     private void checkValidy() {
146         okButton.setEnabled(fromPanel.isDataValid() && toPanel.isDataValid());
147     }
148     
149     private TimeSelection getRange() {
150         String name = nameField.getText().trim();
151         boolean persistent = !name.isEmpty() && saveCheck.isSelected();
152         String start =  fromPanel.getTime();
153         String end = toPanel.getTime();
154         if (range == null) {
155             range = new TimeSelection(name, start, end, persistent);
156         } else {
157             range.name = name;
158             range.lowerEdgeString = start;
159             range.upperEdgeString = end;
160             range.setPersistent(persistent);
161         }
162         return range;
163     }
164     
165     
166 // -- Class for single time point selection : ----------------------------------
167     
168     private class TimePanel extends JPanel {
169         
170         final JTextField _field;
171         final ButtonGroup _group;
172         final JRadioButton _absButton;
173         final JRadioButton _relButton;
174         final JSpinner _timeSpinner;
175         final JSpinner _yearSpinner, _daySpinner, _hourSpinner, _minSpinner, _secSpinner;
176         
177         
178         final SpinnerDateModel _timeSpinnerModel;
179         final SpinnerNumberModel _yearSpinnerModel, _daySpinnerModel, _hourSpinnerModel, _minSpinnerModel, _secSpinnerModel;
180         
181         boolean _listen;
182         
183         ChangeListener _absListener = new ChangeListener() {
184             public void stateChanged(ChangeEvent e) {
185                 if (_listen) {
186                     _listen = false;
187                     updateFromAbsolute();
188                     _listen = true;
189                 }
190             }
191         };
192         ChangeListener _relListener = new ChangeListener() {
193             public void stateChanged(ChangeEvent e) {
194                 if (_listen) {
195                     _listen = false;
196                     updateFromRelative();
197                     _listen = true;
198                 }
199             }
200         };
201         
202         TimePanel(String title) {
203             _listen = false;
204             
205             setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
206             setBorder(BorderFactory.createCompoundBorder(
207                     BorderFactory.createTitledBorder(title), 
208                     BorderFactory.createEmptyBorder(VSPACE, HSPACE, VSPACE, HSPACE)));
209             
210             _field = new JTextField();
211             _field.addCaretListener(new CaretListener() {
212                 public void caretUpdate(CaretEvent e) {
213                     if (_listen) {
214                         _listen = false;
215                         updateFromField();
216                         _listen = true;
217                     }
218                 }
219                 
220             });
221             add(_field);
222             
223             add(Box.createRigidArea(new Dimension(0, VSPACE)));
224             
225             Box box = Box.createHorizontalBox();
226             add(box);
227             _group = new ButtonGroup();
228             _absButton = new JRadioButton("Fixed: ");
229             _absButton.addActionListener(new ActionListener() {
230                 public void actionPerformed(ActionEvent e) {
231                     if (_listen) {
232                         _listen = false;
233                         enableRelative(false);
234                         updateFromAbsolute();
235                         _field.setForeground(Color.BLACK);
236                         _listen = true;
237                     }
238                 }
239             });
240             _group.add(_absButton);
241             box.add(_absButton);
242             box.add(Box.createRigidArea(new Dimension(HSPACE, 0)));
243             _timeSpinnerModel = new SpinnerDateModel(new Date(), null, null, Calendar.HOUR);
244             _timeSpinner = new JSpinner(_timeSpinnerModel);
245             _timeSpinner.setEditor(new JSpinner.DateEditor(_timeSpinner, " "+ TimeSelection.DATE_PATTERN +" "));
246             _timeSpinner.addChangeListener(_absListener);
247             box.add(_timeSpinner);
248             box.add(Box.createRigidArea(new Dimension(HSPACE, 0)));
249             box.add(Box.createHorizontalGlue());
250             
251             add(Box.createRigidArea(new Dimension(0, VSPACE)));
252             
253             box = Box.createHorizontalBox();
254             add(box);
255             _relButton = new JRadioButton();
256             _relButton.addActionListener(new ActionListener() {
257                 public void actionPerformed(ActionEvent e) {
258                     if (_listen) {
259                         _listen = false;
260                         enableRelative(true);
261                         updateFromRelative();
262                         _field.setForeground(Color.BLACK);
263                         _listen = true;
264                     }
265                 }
266             });
267             _group.add(_relButton);
268             box.add(_relButton);
269             _yearSpinnerModel = new SpinnerNumberModel(0, 0, MAX_YEARS, 1);
270             _yearSpinner = new JSpinner(_yearSpinnerModel);
271             _yearSpinner.addChangeListener(_relListener);
272             box.add(_yearSpinner);
273             box.add(Box.createRigidArea(new Dimension(HSPACE/2, 0)));
274             box.add(new JLabel("years"));
275             box.add(Box.createRigidArea(new Dimension(HSPACE/2, 0)));
276             _daySpinnerModel = new SpinnerNumberModel(0, 0, 999, 1);
277             _daySpinner = new JSpinner(_daySpinnerModel);
278             _daySpinner.addChangeListener(_relListener);
279             box.add(_daySpinner);
280             box.add(Box.createRigidArea(new Dimension(HSPACE/2, 0)));
281             box.add(new JLabel("days"));
282             box.add(Box.createRigidArea(new Dimension(HSPACE/2, 0)));
283             _hourSpinnerModel = new SpinnerNumberModel(0, 0, 99, 1);
284             _hourSpinner = new JSpinner(_hourSpinnerModel);
285             _hourSpinner.addChangeListener(_relListener);
286             box.add(_hourSpinner);
287             box.add(Box.createRigidArea(new Dimension(HSPACE/2, 0)));
288             box.add(new JLabel("hours"));
289             box.add(Box.createRigidArea(new Dimension(HSPACE/2, 0)));
290             _minSpinnerModel = new SpinnerNumberModel(0, 0, 99, 1);
291             _minSpinner = new JSpinner(_minSpinnerModel);
292             _minSpinner.addChangeListener(_relListener);
293             box.add(_minSpinner);
294             box.add(Box.createRigidArea(new Dimension(HSPACE/2, 0)));
295             box.add(new JLabel("minutes"));
296             box.add(Box.createRigidArea(new Dimension(HSPACE/2, 0)));
297             _secSpinnerModel = new SpinnerNumberModel(0, 0, 9999, 1);
298             _secSpinner = new JSpinner(_secSpinnerModel);
299             _secSpinner.addChangeListener(_relListener);
300             box.add(_secSpinner);
301             box.add(Box.createRigidArea(new Dimension(HSPACE/2, 0)));
302             box.add(new JLabel("seconds  in the past"));
303             box.add(Box.createRigidArea(new Dimension(HSPACE, 0)));
304             box.add(Box.createHorizontalGlue());
305             
306             add(Box.createRigidArea(new Dimension(0, VSPACE)));
307             
308             _listen = true;
309         }
310         
311         boolean isDataValid() {
312             return _field.getForeground() == Color.BLACK;
313         }
314         
315         String getTime() {
316             if (_absButton.isSelected()) {
317                 updateFromAbsolute();
318             } else {
319                 updateFromRelative();
320             }
321             return _field.getText().trim();
322         }
323         
324         void setTime(String time) {
325             _field.setText(time);
326             updateFromField();
327         }
328         
329         private void setValid(boolean isValid) {
330             if (isValid) {
331                 _field.setForeground(Color.BLACK);
332             } else {
333                 _field.setForeground(Color.RED);
334             }
335             TimeSelectionPanel.this.checkValidy();
336         }
337         
338         private void updateFromAbsolute() {
339             _field.setText(TimeSelection.DATE_FORMAT.format(_timeSpinnerModel.getDate()));
340         }
341         
342         private void updateFromRelative() {
343             int seconds = SECONDS_IN_YEAR * _yearSpinnerModel.getNumber().intValue() +
344                           SECONDS_IN_DAY * _daySpinnerModel.getNumber().intValue() +
345                           SECONDS_IN_HOUR * _hourSpinnerModel.getNumber().intValue() +
346                           SECONDS_IN_MINUTE * _minSpinnerModel.getNumber().intValue() +
347                           _secSpinnerModel.getNumber().intValue();
348             _field.setText("now"+ (seconds > 0 ? (" - "+ seconds) : ""));
349         }
350         
351         private void updateFromField() {
352             String data = _field.getText();
353             Matcher m = _nowPattern.matcher(data);
354             long seconds = -1L;
355             if (m.matches()) {
356                 try {
357                     String ddd = m.group(1);
358                     seconds = ddd.isEmpty() ? 0 : Long.parseLong(ddd);
359                     if (seconds / SECONDS_IN_YEAR > MAX_YEARS) {
360                         seconds = -1;
361                     }
362                 } catch (NumberFormatException x) {
363                 }
364                 if (seconds < 0L) {
365                     setValid(false);
366                 } else {
367                     setValid(true);
368                     _yearSpinnerModel.setValue(seconds / SECONDS_IN_YEAR);
369                     seconds %= SECONDS_IN_YEAR;
370                     _daySpinnerModel.setValue(seconds / SECONDS_IN_DAY);
371                     seconds %= SECONDS_IN_DAY;
372                     _hourSpinnerModel.setValue(seconds / SECONDS_IN_HOUR);
373                     seconds %= SECONDS_IN_HOUR;
374                     _minSpinnerModel.setValue(seconds / SECONDS_IN_MINUTE);
375                     seconds %= SECONDS_IN_MINUTE;
376                     _secSpinnerModel.setValue(seconds);
377                     enableRelative(true);
378                 }
379             } else {
380                 try {
381                     Date date = TimeSelection.DATE_FORMAT.parse(data);
382                     _timeSpinnerModel.setValue(date);
383                     enableRelative(false);
384                     setValid(true);
385                 } catch (ParseException x) {
386                     setValid(false);
387                 }
388             }
389         }
390         
391         private void enableRelative(boolean relative) {
392             _timeSpinner.setEnabled(!relative);
393             _yearSpinner.setEnabled(relative);
394             _daySpinner.setEnabled(relative);
395             _hourSpinner.setEnabled(relative);
396             _minSpinner.setEnabled(relative);
397             _secSpinner.setEnabled(relative);
398             if (relative) {
399                 _relButton.setSelected(true);
400             } else {
401                 _absButton.setSelected(true);
402             }
403         }
404        
405     }
406         
407 }