/* * Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata * * Portions of this software were developed by the Unidata Program at the * University Corporation for Atmospheric Research. * * Access and use of this software shall impose the following obligations * and understandings on the user. The user is granted the right, without * any fee or cost, to use, copy, modify, alter, enhance and distribute * this software, and any derivative works thereof, and its supporting * documentation for any purpose whatsoever, provided that this entire * notice appears in all copies of the software, derivative works and * supporting documentation. Further, UCAR requests that the user credit * UCAR/Unidata in any publications that result from the use of this * software or in any product that includes this software. The names UCAR * and/or Unidata, however, may not be used in any advertising or publicity * to endorse or promote any products or commercial entity unless specific * written permission is obtained from UCAR/Unidata. The user also * understands that UCAR/Unidata is not obligated to provide the user with * any support, consulting, training or assistance of any kind with regard * to the use, operation and performance of this software nor to provide * the user with any updates, revisions, new versions or "bug fixes." * * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. */ package ucar.nc2.ui.widget; import thredds.ui.datatype.prefs.DateField; import thredds.ui.datatype.prefs.DurationField; import ucar.nc2.ui.event.ActionSourceListener; import ucar.nc2.ui.event.ActionValueEvent; import ucar.nc2.ui.event.ActionValueListener; import ucar.nc2.units.*; import ucar.util.prefs.ui.Field; import ucar.util.prefs.ui.FieldValidator; import ucar.util.prefs.ui.PrefPanel; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.LineBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Date; /** * Widget to select a point or a range from a date range. */ public class RangeDateSelector extends JPanel implements FieldValidator { public static final String TIME_START = "start"; public static final String TIME_END = "end"; public static final String TIME_DURATION = "duration"; public static final String TIME_RESOLUTION = "resolution"; static private final int SLIDER_RESOLUTION = 1000; private String title, helpMessage; private boolean acceptButton, enableButton, isPointOnly, useLimits; private DateType minLimit, maxLimit; // min and max allowed values private DateRange dateRange; // data model private Scale scale; // various widgets to manipulate the model private JSlider minSlider, maxSlider; private DateField minField, maxField; private DurationField durationField, resolutionField; // ui helpers private PrefPanel pp; private JLabel minLabel, maxLabel; private JButton helpButton; private JToggleButton disableButton; private HelpWindow helpWindow; // event management private ActionSourceListener actionSource; private String actionName = "rangeDateSelection"; private boolean eventOK = true; private static boolean debugEvent = false, debugEvent2 = false; /** * Constructor using info from thredds DQC. * * @param title widget title displayed to user, may be null * @param start starting date as a string * @param end ending date as a string * @param durationS duration as a String * @param minInterval minimum useful interval as a String, may be null. * @param acceptButton add an acceptButton * @param help optional help text * @param pointOnly if user can only select one point, otherwise can select a range of dates. * @throws java.text.ParseException */ public RangeDateSelector(String title, String start, String end, String durationS, String minInterval, boolean enableButton, boolean acceptButton, String help, boolean pointOnly) throws Exception { this( title, new DateRange( (start == null) ? null : new DateType(start, null, null), (end == null) ? null : new DateType(end, null, null), (durationS == null) ? null : new TimeDuration(durationS), (minInterval == null) ? null : new TimeDuration(minInterval)), enableButton, acceptButton, help, pointOnly, true); } /** * Constructor. * @param title widget title displayed to user, may be null * @param range range that the user can select from * @param acceptButton add an accept Button * @param enableButton add an enable Button * @param help optional help text * @param pointOnly if user can only select one point, otherwise can select a range of dates. */ public RangeDateSelector(String title, DateRange range, boolean enableButton, boolean acceptButton, String help, boolean pointOnly, boolean useLimits) { this.title = title; this.dateRange = range; this.acceptButton = acceptButton; this.enableButton = enableButton; this.helpMessage = help; this.isPointOnly = pointOnly; this.useLimits = useLimits; init(); } private void init() { // UI // optional top panel JPanel topPanel; topPanel = new JPanel(new BorderLayout()); JPanel butts = new JPanel(); if (title != null) topPanel.add(BorderLayout.WEST, new JLabel(" " + title + ":")); if (helpMessage != null) { helpButton = new JButton("help"); helpButton.addActionListener(new AbstractAction() { public void actionPerformed(ActionEvent e) { if (helpWindow == null) helpWindow = new HelpWindow(null, "Help on " + title, helpMessage); helpWindow.show(helpButton); } }); butts.add(helpButton); } if (acceptButton) { JButton okButton = new JButton("accept"); okButton.addActionListener(new AbstractAction() { public void actionPerformed(ActionEvent e) { pp.accept(); sendEvent(); } }); butts.add(okButton); acceptButton = false; // dont need it in prefpanel } if (enableButton) { disableButton = new JToggleButton("disable", false); disableButton.addActionListener(new AbstractAction() { public void actionPerformed(ActionEvent e) { boolean b = !disableButton.getModel().isSelected(); minField.setEnabled(b); maxField.setEnabled(b); durationField.setEnabled(b); minSlider.setEnabled(b); maxSlider.setEnabled(b); } }); butts.add(disableButton); } topPanel.add(BorderLayout.EAST, butts); // the sliders JPanel sliderPanel = new JPanel(); sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.Y_AXIS)); sliderPanel.setBorder(new LineBorder(Color.black, 1, true)); minSlider = new JSlider(JSlider.HORIZONTAL, 0, SLIDER_RESOLUTION, 0); maxSlider = new JSlider(JSlider.HORIZONTAL, 0, SLIDER_RESOLUTION, SLIDER_RESOLUTION); Border b = BorderFactory.createEmptyBorder(0, 15, 0, 15); minSlider.setBorder(b); maxSlider.setBorder(b); // set this so we can call setDateRange(); minLabel = new JLabel(); maxLabel = new JLabel(); minField = new DateField(TIME_START, isPointOnly ? "value" : "start", dateRange.getStart(), null); maxField = new DateField(TIME_END, "end", dateRange.getEnd(), null); durationField = new DurationField(TIME_DURATION, "duration", dateRange.getDuration(), null); resolutionField = new DurationField(TIME_RESOLUTION, "resolution", dateRange.getResolution(), null); minField.addValidator(this); maxField.addValidator(this); durationField.addValidator(this); setDateRange(dateRange); JPanel labelPanel = new JPanel(new BorderLayout()); labelPanel.add(minLabel, BorderLayout.WEST); labelPanel.add(maxLabel, BorderLayout.EAST); // the fields use a PrefPanel pp = new PrefPanel(null, null); int row = 0; //if (tit != null) { // pp.addComponent(new JLabel(tit), col, 0, null); // col+=2; //} if (isPointOnly) { pp.addField(minField, 0, row, null); } else { pp.addField(minField, 0, row++, null); pp.addField(maxField, 0, row++, null); pp.addField(durationField, 0, row++, null); pp.addField(resolutionField, 0, row, null); } pp.finish(acceptButton, BorderLayout.EAST); setLayout(new BorderLayout()); // allow width expansion // overall layout sliderPanel.add(topPanel); sliderPanel.add(pp); if (useLimits) { if (!isPointOnly) sliderPanel.add(maxSlider); sliderPanel.add(minSlider); sliderPanel.add(labelPanel); } add(sliderPanel, BorderLayout.NORTH); /// event management // listen for changes from user manupulation maxSlider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (debugEvent2) System.out.println("maxSlider event= " + maxSlider.getValue()); if (!eventOK) return; int pos = maxSlider.getValue(); dateRange.setEnd(scale.slider2world(pos)); synchUI(false); if (dateRange.isPoint()) minSlider.setValue(pos); // drag min along */ } }); minSlider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (debugEvent2) System.out.println("minSlider event= " + minSlider.getValue()); if (!eventOK) return; int pos = minSlider.getValue(); dateRange.setStart(scale.slider2world(pos)); synchUI(false); if (dateRange.isPoint() && !isPointOnly) maxSlider.setValue(pos); // drag max along } }); minField.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { if (debugEvent) System.out.println("minField event= " + e.getNewValue() + " " + e.getNewValue().getClass().getName()); if (!eventOK) return; DateType val = (DateType) minField.getValue(); dateRange.setStart(val); synchUI(true); } }); if (maxField != null) { maxField.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { if (debugEvent) System.out.println("maxField event= " + e.getNewValue()); if (!eventOK) return; DateType val = (DateType) maxField.getValue(); dateRange.setEnd(val); synchUI(true); } }); } if (durationField != null) { durationField.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { if (debugEvent) System.out.println("durationField event= " + e.getNewValue()); if (!eventOK) return; TimeDuration val = durationField.getTimeDuration(); dateRange.setDuration( val); synchUI(true); } }); } if (resolutionField != null) { resolutionField.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { if (debugEvent) System.out.println("resolutionField event= " + e.getNewValue()); if (!eventOK) return; TimeDuration val = resolutionField.getTimeDuration(); dateRange.setResolution( val); //synchUI(true); } }); } // listen for outside changes actionSource = new ActionSourceListener(actionName) { public void actionPerformed(ActionValueEvent e) { if (debugEvent) System.out.println(" actionSource event " + e); //?? setSelectedByName( e.getValue().toString()); } }; // catch resize events on the slider /* minSlider.addComponentListener( new ComponentAdapter() { public void componentResized( ComponentEvent e) { setLabels(); } }); */ } private DateFormatter formatter = new DateFormatter(); public boolean validate( Field fld, Object editValue, StringBuffer errMessages) { if (!useLimits) return true; DateType checkVal; if (fld == durationField) { TimeDuration duration = (TimeDuration) editValue; if (dateRange.getEnd().isPresent()) checkVal = dateRange.getEnd().subtract( duration); else checkVal = dateRange.getStart().add( duration); } else checkVal = (DateType) editValue; // otherwise its one of the dates // have to be inside the limits Date d = checkVal.getDate(); if (d.after(maxLimit.getDate()) || d.before(minLimit.getDate())) { errMessages.append( "Date "); errMessages.append( formatter.toDateTimeString(d)); errMessages.append(" must be between "); errMessages.append( minLimit.getText()); errMessages.append( " and "); errMessages.append( maxLimit.getText()); return false; } return true; } // set values on the UI private void synchUI(boolean slidersOK) { eventOK = false; if (slidersOK) minSlider.setValue(scale.world2slider(dateRange.getStart())); minField.setValue(dateRange.getStart()); if (maxField != null) { if (slidersOK) maxSlider.setValue(scale.world2slider(dateRange.getEnd())); maxField.setValue(dateRange.getEnd()); } if (durationField != null) durationField.setValue(dateRange.getDuration()); eventOK = true; } public void setDateRange(DateRange dateRange) { this.dateRange = dateRange; this.minLimit = new DateType( dateRange.getStart()); this.maxLimit = new DateType( dateRange.getEnd()); this.scale = new Scale( dateRange); minLabel.setText(" "+minLimit.getText()+" "); maxLabel.setText(" "+maxLimit.getText()+" "); if (isPointOnly) { minField.setValue( dateRange.getStart()); } else { minField.setValue( dateRange.getStart()); maxField.setValue( dateRange.getEnd()); durationField.setValue( dateRange.getDuration()); resolutionField.setValue( dateRange.getResolution()); } } public DateField getMinDateField() { return minField; } public DateField getMaxDateField() { return maxField; } public DurationField getDurationField() { return durationField; } public DurationField getResolutionField() { return resolutionField; } public boolean isEnabled() { return (null == disableButton) || !disableButton.getModel().isSelected(); } public DateRange getDateRange() { if (!pp.accept()) return null; return dateRange; } public void sendEvent() { // gotta do this after the dust settles SwingUtilities.invokeLater(new Runnable() { public void run() { System.out.println("event range= "+dateRange); actionSource.fireActionValueEvent(actionName, this); } // run }); // invokeLater */ } /** add ActionValueListener listener */ public void addActionValueListener( ActionValueListener l) { actionSource.addActionValueListener(l); } /** remove ActionValueListener listener */ public void removeActionValueListener( ActionValueListener l) { actionSource.removeActionValueListener(l); } private static class Scale { private double min; // secs private double scale; // pixels / secs Scale( DateRange dateRange) { this.min = .001 * dateRange.getStart().getDate().getTime(); //this.max = .001 * dateRange.getEnd().getDate().getTime(); //scale = SLIDER_RESOLUTION / (this.max - this.min); scale = SLIDER_RESOLUTION / dateRange.getDuration().getValueInSeconds(); // System.out.println("slider scale= "+scale); } private int world2slider(DateType val) { double msecs = .001 * val.getDate().getTime() - min; return (int) (scale * msecs); } private DateType slider2world(int pval) { double val = pval / scale; // secs //double floor = Math.floor(val / resolution); //double incr = floor * resolution; double msecs = 1000 * (min + val); return new DateType(false, new java.util.Date( (long) msecs)); } } public static void main(String args[]) throws Exception { JFrame frame = new JFrame("Test Date Range Selector"); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); RangeDateSelector rs1 = new RangeDateSelector("Date Range", "1990-01-01T01:00:00", "1990-01-02T02:00:00", null, "15 minute", true, true, "i think im fallin", false); // RangeDateSelector rs2 = new RangeDateSelector("Date", "1990-01-01", "1991-01-01", null, "1 day", false, true, // "i think im fallin\n in love with youuuu ", false); // RangeDateSelector rs3 = new RangeDateSelector("Date", "1990-01-01", "1991-01-01", null, "10 days", true, true, // null, false); // RangeDateSelector rs4 = new RangeDateSelector("Date", "1990-01-01", "1991-01-01", null, "10 days", false, false, // null, true); // RangeDateSelector rs5 = new RangeDateSelector("Date", null, "present", "10 days", "1 day", true, false, // null, false); // simulate what we do in PointObsViewer DateRange range = null; try { range = new DateRange(); // phony } catch (Exception e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } RangeDateSelector rs6 = new RangeDateSelector(null, range, false, true, null, false, false); DateUnit start = new DateUnit("0 secs since 2005-05-02 23:00:00"); DateUnit end = new DateUnit("0 secs since 2005-05-02 23:59:59"); rs6.setDateRange( new DateRange( start.getDate(), end.getDate())); Box main = new Box( BoxLayout.Y_AXIS); frame.getContentPane().add(main); //main.setPreferredSize(new Dimension(400, 200)); //main.add( new JSlider(), BorderLayout.NORTH); main.add( rs1); /*main.add( rs2); main.add( rs3); main.add( rs4); main.add( rs5); */ main.add( rs6); frame.pack(); frame.setLocation(400, 300); frame.setVisible(true); } }