/* * TimeRangeEditor.java * * Created on Jun 4, 2011, 9:33:57 AM */ package org.autoplot.datasource; import java.awt.CardLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.net.URISyntaxException; import java.text.ParseException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JSeparator; import javax.swing.SwingUtilities; import javax.swing.text.DefaultEditorKit; import org.das2.DasApplication; import org.das2.datum.DatumRange; import org.das2.datum.DatumRangeUtil; import org.das2.datum.LoggerManager; import org.das2.datum.UnitsUtil; import org.autoplot.datasource.ui.PromptComboBoxEditor; /** * Standard control for controlling a DatumRange containing times, with * next and previous buttons, and a launcher into the TimeRangeTool. * @author jbf */ public class TimeRangeEditor extends javax.swing.JPanel { private static final Logger logger= LoggerManager.getLogger("apdss.gui"); public TimeRangeEditor() { initComponents(); timeRangeToolButton.setActionCommand("timeRangeTool"); nextButton.setActionCommand("next"); prevButton.setActionCommand("previous"); browseButton.setActionCommand("inspect"); recentComboBox.setName("timeRangeEditor"); recentComboBox.setMinimumSize( new Dimension( 200,30) ); // long items in history cause problems. recentComboBox.setPreferenceNode(RecentComboBox.PREF_NODE_TIMERANGE); recentComboBox.setEditor( new PromptComboBoxEditor("Time range to view (e.g. 2010-01-01)") ); recentComboBox.setToolTipText("Recently entered time ranges"); ((JComponent)recentComboBox.getEditor().getEditorComponent()).setToolTipText("Time Range, right-click for examples"); recentComboBox.addFocusListener( new FocusAdapter() { @Override public void focusLost(java.awt.event.FocusEvent evt) { parseRange(); } }); recentComboBox.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { org.das2.util.LoggerManager.logGuiEvent(e); if ( !suppressRecentComboBoxActionEvents ) { parseRange(); } } }); recentComboBox.setVerifier( new TimeRangeVerifier() ); browseButton.setVisible(false); revalidate(); addMousePopupListener(); } DatumRange range= DatumRangeUtil.parseTimeRangeValid( "2010-01-01" ); /** * this is the range which is displayed, and will be the same as range * when rescale is "" or "0%,100%" */ DatumRange controlRange= range; public static final String PROP_USE_DOY= "useDoy"; /** * use DOY instead of Y-M-D */ boolean useDoy=false; /** * true if day of year should be used instead of year, month, day. * @return true if day of year should be used */ public boolean isUseDoy() { return useDoy; } /** * prefer use of day of year rather than year, month, day. * @param useDoy */ public void setUseDoy(boolean useDoy) { boolean old= this.useDoy; this.useDoy = useDoy; DatumRangeUtil.setUseDoy( useDoy ); firePropertyChange( PROP_USE_DOY,old,useDoy); } private String rescale = ""; public static final String PROP_RESCALE = "rescale"; public String getRescale() { return rescale; } /** * Add extra time to the range, and account for this extra time * with the next and previous buttons. Example values are: *
  • "" the default behavior *
  • 0%,100% also the default behavior *
  • -10%,110% add ten percent before and after the interval *
  • 0%-1hr,100%+1hr add an hour before and after the interval * Note the GUI will always report the 0%,100% time, without the context. * @param rescale */ public void setRescale(String rescale) { String oldRescale = this.rescale; this.rescale = rescale; DatumRange oldRange= this.range; try { this.range= DatumRangeUtil.rescale( controlRange, rescale ); } catch (ParseException ex) { logger.log(Level.SEVERE, null, ex); } firePropertyChange(PROP_RESCALE, oldRescale, rescale); firePropertyChange(PROP_RANGE, oldRange, range ); } public static final String PROP_RANGE= "range"; /** * get the timerange. * @return the timerange. */ public DatumRange getRange() { return range; } /** * avoid ringing and extra parsing caused by roundoff in parsing. */ private boolean suppressRecentComboBoxActionEvents= false; /** * set the range for the controller. Note that if the rescale range * is not "" or "0%,100%" then the controlRange will be different. * @param value */ public void setRange( final DatumRange value ) { if ( !UnitsUtil.isTimeLocation(value.getUnits()) ) return; final DatumRange oldValue= this.range; final DatumRange oldControlRange= this.controlRange; this.range= value; try { if ( this.rescale.length()==0 ) { this.controlRange= value; } else { this.controlRange= DatumRangeUtil.rescaleInverse(value, rescale ); } } catch (ParseException ex) { logger.log(Level.SEVERE, null, ex); } if (oldValue != value && oldValue != null && !oldValue.equals(value)) { if ( !suppressRecentComboBoxActionEvents ) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { TimeRangeEditor.super.firePropertyChange( PROP_RANGE, oldValue, value); TimeRangeEditor.super.firePropertyChange( PROP_CONTROL_RANGE, oldControlRange, TimeRangeEditor.this.controlRange); } } ); } } this.suppressRecentComboBoxActionEvents= true; if ( value==noOneListening ) { this.recentComboBox.setSelectedItem(""); } else { this.recentComboBox.setSelectedItem( controlRange.toString() ); } this.suppressRecentComboBoxActionEvents= false; } public static final String PROP_CONTROL_RANGE= "controlRange"; /** * set the range displayed, regardless of the rescaling. * @param value */ public void setControlRange( final DatumRange value ) { if ( !UnitsUtil.isTimeLocation(value.getUnits()) ) return; final DatumRange oldValue= this.controlRange; final DatumRange oldRange= this.range; this.controlRange= value; try { if ( this.rescale.length()==0 ) { this.range= value; } else { this.range= DatumRangeUtil.rescale(value, rescale ); } } catch (ParseException ex) { logger.log(Level.SEVERE, null, ex); } if (oldValue != value && oldValue != null && !oldValue.equals(value)) { if ( !suppressRecentComboBoxActionEvents ) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { TimeRangeEditor.super.firePropertyChange( PROP_CONTROL_RANGE, oldValue, value); TimeRangeEditor.super.firePropertyChange( PROP_RANGE, oldRange, TimeRangeEditor.this.range ); } } ); } } this.suppressRecentComboBoxActionEvents= true; if ( value==noOneListening ) { this.recentComboBox.setSelectedItem(""); } else { this.recentComboBox.setSelectedItem( value.toString() ); } this.suppressRecentComboBoxActionEvents= false; } DatumRange noOneListening= range; /** * special marker object indicates that the "no one listening" message should be shown. * @param dr */ public void setNoOneListeningRange( DatumRange dr ) { this.noOneListening= dr; } /** * special marker object indicates that the "no one listening" message should be shown. * @return special marker object */ public DatumRange getNoOneListeningRange() { return this.noOneListening; } /** * return true if the string appears to be a URI. * @param text * @return */ private static boolean isUri( String text ) { boolean isUri= false; if ( text.startsWith("/") ) { isUri= true; } if ( !isUri ) { int icolon= text.indexOf(':'); if ( icolon>-1 ) { String pref= text.substring(0,icolon); if ( Character.isLetter(pref.charAt(0)) && Pattern.matches( "[a-zA-Z_\\+0-9]*", pref ) ) { isUri= true; } } } return isUri; } private void parseRange() { DatumRange dr; DatumRange value= this.controlRange; String text= (String)recentComboBox.getSelectedItem(); if ( text==null || text.equals("") ) return; try { String rangeString= text; dr= DatumRangeUtil.parseTimeRange(rangeString); setControlRange(dr); } catch ( ParseException e ) { boolean isUri= isUri( text ); if ( isUri ) { if ( peer!=null ) { peer.setValue(text); peer.maybePlot(true); return; } else { showErrorUsage( text, "Appears to be a dataset location, and this expects timeranges" ); } } //timeRangeTextField.setText( range.toString() ); // I think we can just leave the value there. if ( UnitsUtil.isTimeLocation(value.getUnits()) ) { // go ahead and handle non-times. showErrorUsage( text, "" +e.getMessage() ); } else { //showErrorUsage( text, "unable to parse range" ); } } catch ( IllegalArgumentException e ) { if ( value!=null ) { setControlRange( value ); // cause reformat of old Datum if ( e.getMessage().contains("min > max") ) { showErrorUsage( text, "min cannot be greater than max" ); } else { showErrorUsage( text, e.getMessage() ); } } } } @Override public final void revalidate() { super.revalidate(); //To change body of generated methods, choose Tools | Templates. } /** * prevent displaying same message so many times... */ private String lastErrorText= null; private long lastErrorTime= 0; private void showErrorUsage( String text, String why ) { if ( !DasApplication.getDefaultApplication().isHeadless() ) { if ( text!=null && text.equals(lastErrorText) && (System.currentTimeMillis()-lastErrorTime)<5000 ) { return; } if ( why!=null ) { JOptionPane.showMessageDialog( this, "Unable to accept \""+text+"\"
    "+why+"" ); } else { JOptionPane.showMessageDialog( this, "Unable to accept \""+text+"\"" ); } lastErrorText= text; lastErrorTime= System.currentTimeMillis(); } } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { prevButton = new javax.swing.JButton(); nextButton = new javax.swing.JButton(); browseButton = new javax.swing.JButton(); recentComboBox = new org.autoplot.datasource.RecentComboBox(); timeRangeToolButton = new javax.swing.JButton(); setPreferredSize(new java.awt.Dimension(384, 39)); prevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/autoplot/datasource/prevPrev.png"))); // NOI18N prevButton.setToolTipText("Step to the previous interval"); prevButton.setMaximumSize(new java.awt.Dimension(34, 20)); prevButton.setMinimumSize(new java.awt.Dimension(34, 20)); prevButton.setPreferredSize(new java.awt.Dimension(34, 20)); prevButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { prevButtonActionPerformed(evt); } }); nextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/autoplot/datasource/nextNext.png"))); // NOI18N nextButton.setToolTipText("Step to the next interval"); nextButton.setMaximumSize(new java.awt.Dimension(34, 24)); nextButton.setMinimumSize(new java.awt.Dimension(34, 24)); nextButton.setPreferredSize(new java.awt.Dimension(34, 24)); nextButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { nextButtonActionPerformed(evt); } }); browseButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/autoplot/datasource/fileMag.png"))); // NOI18N browseButton.setToolTipText("Inspect data source"); browseButton.setEnabled(false); browseButton.setMargin(new java.awt.Insets(2, 2, 2, 2)); browseButton.setMaximumSize(new java.awt.Dimension(20, 20)); browseButton.setMinimumSize(new java.awt.Dimension(20, 20)); browseButton.setPreferredSize(new java.awt.Dimension(20, 20)); browseButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { browseButtonActionPerformed(evt); } }); recentComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "2010-01-01", "2010-01-02" })); recentComboBox.setMinimumSize(new java.awt.Dimension(120, 27)); timeRangeToolButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/autoplot/datasource/calendar.png"))); // NOI18N timeRangeToolButton.setToolTipText("Time Range Tool"); timeRangeToolButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { timeRangeToolButtonActionPerformed(evt); } }); org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup() .add(recentComboBox, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 196, Short.MAX_VALUE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(timeRangeToolButton) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(prevButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 40, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(nextButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 34, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(browseButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 30, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) ); layout.linkSize(new java.awt.Component[] {nextButton, prevButton}, org.jdesktop.layout.GroupLayout.HORIZONTAL); layout.setVerticalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(browseButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 27, Short.MAX_VALUE) .add(nextButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(prevButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(timeRangeToolButton, 0, 0, Short.MAX_VALUE) .add(recentComboBox, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) ); layout.linkSize(new java.awt.Component[] {browseButton, nextButton, prevButton, recentComboBox, timeRangeToolButton}, org.jdesktop.layout.GroupLayout.VERTICAL); }// //GEN-END:initComponents private void nextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextButtonActionPerformed org.das2.util.LoggerManager.logGuiEvent(evt); setControlRange( controlRange.next() ); }//GEN-LAST:event_nextButtonActionPerformed private void prevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevButtonActionPerformed org.das2.util.LoggerManager.logGuiEvent(evt); setControlRange( controlRange.previous() ); }//GEN-LAST:event_prevButtonActionPerformed DataSetSelector peer; /** * provide a shortcut to a DataSetSelector editor. * @param peer */ public void setDataSetSelectorPeer( DataSetSelector peer ) { this.peer= peer; if ( peer!=null ) { browseButton.setEnabled(true); browseButton.setVisible(true); } else { browseButton.setEnabled(false); browseButton.setVisible(false); } } private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed org.das2.util.LoggerManager.logGuiEvent(evt); if ( peer!=null ) { String surl = (String) peer.getLastValue().trim();//TODO:check if ( peer.isExpertMode() ) { peer.setValue(surl); } //boolean wasRejected= false; DataSourceEditorPanel edit; // hooks for browsing, such as "vap+internal" for (String browseTriggerRegex : peer.browseTriggers.keySet()) { if (Pattern.matches(browseTriggerRegex, surl)) { logger.finest("matches browse trigger"); Action action = peer.browseTriggers.get(browseTriggerRegex); action.actionPerformed( new ActionEvent(this, 123, "dataSetSelect") ); return; } } try { edit = DataSourceEditorPanelUtil.getDataSourceEditorPanel(DataSetURI.getURIValid(surl)); if ( edit!=null && edit.reject(surl) ) { edit= null; //wasRejected= true; } } catch (URISyntaxException ex) { logger.log(Level.SEVERE, ex.getMessage(), ex); edit= null; } catch ( Exception ex ) { logger.log(Level.SEVERE, ex.getMessage(), ex); edit= null; } if ( edit==null ) { JOptionPane.showMessageDialog( prevButton, "The selected plot element has no editor:
    "+surl, "no editor", JOptionPane.OK_OPTION ); } else { peer.browseSourceType(); } } }//GEN-LAST:event_browseButtonActionPerformed private void timeRangeToolButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_timeRangeToolButtonActionPerformed org.das2.util.LoggerManager.logGuiEvent(evt); TimeRangeTool t=new TimeRangeTool(); t.setSelectedRange(controlRange.toString());//TODO: goofy if ( JOptionPane.OK_OPTION==JOptionPane.showConfirmDialog( SwingUtilities.getWindowAncestor(this), t, "Select time range", JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, new javax.swing.ImageIcon(getClass().getResource("/org/autoplot/datasource/calendar.png"))) ) { String str= t.getSelectedRange(); try { setControlRange( DatumRangeUtil.parseTimeRangeValid(str) ); } catch ( IllegalArgumentException ex ) { logger.log(Level.FINE, "unable to parse time/orbit: {0}", str); } recentComboBox.actionPerformed( new ActionEvent(this,0,"triggerSaveRecent",0) ); } }//GEN-LAST:event_timeRangeToolButtonActionPerformed public static void main( String[] args ) { TimeRangeEditor p= new TimeRangeEditor(); p.addPropertyChangeListener(TimeRangeEditor.PROP_RANGE, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { System.err.println(evt.getOldValue()+" -> "+ evt.getNewValue() ); // logger okay } } ); JFrame f= new JFrame(); f.setContentPane( p ); f.pack(); f.setVisible(true); f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton browseButton; private javax.swing.JButton nextButton; private javax.swing.JButton prevButton; private org.autoplot.datasource.RecentComboBox recentComboBox; private javax.swing.JButton timeRangeToolButton; // End of variables declaration//GEN-END:variables public PropertyChangeListener getUriFocusListener() { return new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { browseButton.setToolTipText( "Edit data source
    "+evt.getNewValue().toString()+"" ); } }; } private String alternatePeer; private String alternatePeerCard=null; public void setAlternatePeer(String label, String card ) { this.alternatePeer= label; this.alternatePeerCard= card; } private void addMousePopupListener() { recentComboBox.getEditor().getEditorComponent().addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if ( e.isPopupTrigger() ) showPopup(e); } @Override public void mousePressed(MouseEvent e) { if ( e.isPopupTrigger() ) showPopup(e); } @Override public void mouseReleased(MouseEvent e) { if ( e.isPopupTrigger() ) showPopup(e); } }); } private void showPopup( MouseEvent e ) { getPopupMenu().show( this, e.getX(), e.getY() ); } private JMenuItem exampleTime( final String s, final String toolTip ) { JMenuItem mi= new JMenuItem( new AbstractAction(s) { @Override public void actionPerformed( ActionEvent e ) { recentComboBox.setSelectedItem(s); } }); mi.setToolTipText(toolTip); return mi; } private JPopupMenu getPopupMenu() { JPopupMenu result= new JPopupMenu(); JMenuItem cutItem = result.add(new DefaultEditorKit.CutAction()); cutItem.setText("Cut"); JMenuItem copyItem = result.add(new DefaultEditorKit.CopyAction()); copyItem.setText("Copy"); JMenuItem pasteItem = result.add(new DefaultEditorKit.PasteAction()); pasteItem.setText("Paste"); JMenu examplesMenu= new JMenu("Examples"); examplesMenu.add( exampleTime( "2010 Jan", "Month of January" ) ); examplesMenu.add( exampleTime( "2010-01-01", "January 1, 2010" ) ); examplesMenu.add( exampleTime( "2010-01-01/2010-01-04", "ISO8601 range" ) ); examplesMenu.add( exampleTime( "P5D", "Last 5 Days to now" ) ); examplesMenu.add( exampleTime( "orbit:rbspa-pp:30", "Orbit 30 from rbspa-pp orbits file" ) ); examplesMenu.add( exampleTime( "orbit:http://das2.org/wiki/index.php/Orbits/rbspa-pp:30", "Orbit 30 from any orbits file" ) ); result.add( examplesMenu ); if ( this.alternatePeerCard!=null ) { result.add( new JSeparator() ); result.add(new AbstractAction( alternatePeer ) { @Override public void actionPerformed(ActionEvent ev) { org.das2.util.LoggerManager.logGuiEvent(ev); Container trp= TimeRangeEditor.this.getParent(); if ( trp.getLayout() instanceof CardLayout ) { setCardSelected(false); } } } ); } return result; } private boolean cardSelected = false; public static final String PROP_CARDSELECTED = "cardSelected"; public boolean isCardSelected() { return cardSelected; } public void setCardSelected(boolean cardSelected) { boolean oldCardSelected = this.cardSelected; this.cardSelected = cardSelected; firePropertyChange(PROP_CARDSELECTED, oldCardSelected, cardSelected); } public void setCardSelectedNoEventKludge(boolean cardSelected) { boolean oldCardSelected = this.cardSelected; this.cardSelected = cardSelected; } /** * re-layout the GUI to make it thinner. */ public void makeThinner() { removeAll(); setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) ); add( recentComboBox ); javax.swing.JPanel p2= new javax.swing.JPanel(); p2.setLayout( new BoxLayout( p2, BoxLayout.X_AXIS ) ); p2.add( timeRangeToolButton ); p2.add( prevButton ); p2.add( nextButton ); recentComboBox.setMaximumSize( recentComboBox.getPreferredSize() ); prevButton.setPreferredSize(nextButton.getPreferredSize()); prevButton.setMinimumSize(nextButton.getMinimumSize()); prevButton.setMaximumSize(nextButton.getMaximumSize()); add( p2 ); setMinimumSize( new Dimension( recentComboBox.getMinimumSize().width, recentComboBox.getMinimumSize().height + prevButton.getMinimumSize().height + 5 )); setPreferredSize(getMinimumSize()); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); prevButton.setEnabled(enabled); nextButton.setEnabled(enabled); timeRangeToolButton.setEnabled(enabled); recentComboBox.setEnabled(enabled); } }