001    package org.LiveGraph.gui;
002    
003    import java.awt.BorderLayout;
004    import java.awt.FontMetrics;
005    import java.awt.GridBagConstraints;
006    import java.awt.GridBagLayout;
007    import java.awt.Insets;
008    import java.awt.event.ActionEvent;
009    import java.awt.event.ActionListener;
010    import java.awt.event.WindowAdapter;
011    import java.awt.event.WindowEvent;
012    
013    import javax.swing.Box;
014    import javax.swing.ButtonGroup;
015    import javax.swing.JFileChooser;
016    import javax.swing.JFrame;
017    import javax.swing.JPanel;
018    import javax.swing.JRadioButton;
019    import javax.swing.JScrollBar;
020    import javax.swing.WindowConstants;
021    import javax.swing.JScrollPane;
022    import java.awt.Dimension;
023    import java.io.File;
024    import java.util.Arrays;
025    
026    import javax.swing.JTextArea;
027    import javax.swing.JLabel;
028    import javax.swing.JButton;
029    
030    import javax.swing.JSlider;
031    import javax.swing.BorderFactory;
032    import javax.swing.JCheckBox;
033    import javax.swing.event.ChangeEvent;
034    import javax.swing.event.ChangeListener;
035    import javax.swing.filechooser.FileFilter;
036    
037    import org.LiveGraph.LiveGraph;
038    import org.LiveGraph.dataCache.CacheObserver;
039    import org.LiveGraph.dataCache.DataCache;
040    import org.LiveGraph.dataCache.UpdateInvoker;
041    import org.LiveGraph.dataCache.UpdateInvokerObserver;
042    import org.LiveGraph.settings.DataFileSettings;
043    import org.LiveGraph.settings.ErrorWhileSettingHasChangedProcessingException;
044    import org.LiveGraph.settings.ObservableSettings;
045    import org.LiveGraph.settings.SettingsObserver;
046    
047    import com.softnetConsult.utils.swing.SwingTools;
048    
049    /**
050     * The "Data File Settings" window of the application.
051     * 
052     * <p style="font-size:smaller;">This product includes software developed by the
053     *    <strong>LiveGraph</strong> project and its contributors.<br />
054     *    (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br />
055     *    Copyright (c) 2007 G. Paperin.<br />
056     *    All rights reserved.
057     * </p>
058     * <p style="font-size:smaller;">File: DataFileSettingsWindow.java</p> 
059     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
060     *    without modification, are permitted provided that the following terms and conditions are met:
061     * </p>
062     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
063     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
064     *    this list of conditions and the following disclaimer.<br />
065     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
066     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
067     *    and the following disclaimer in the documentation and/or other materials provided with
068     *    the distribution.<br />
069     *    3. All advertising materials mentioning features or use of this software or any derived
070     *    software must display the following acknowledgement:<br />
071     *    <em>This product includes software developed by the LiveGraph project and its
072     *    contributors.<br />(http://www.live-graph.org)</em><br />
073     *    4. All advertising materials distributed in form of HTML pages or any other technology
074     *    permitting active hyper-links that mention features or use of this software or any
075     *    derived software must display the acknowledgment specified in condition 3 of this
076     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
077     *    homepage (http://www.live-graph.org).
078     * </p>
079     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
080     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
081     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
082     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
083     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
084     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
085     * </p>
086     * 
087     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
088     * @version {@value org.LiveGraph.LiveGraph#version}
089     */
090    public class DataFileSettingsWindow extends JFrame
091                                                                            implements UpdateInvokerObserver, SettingsObserver, CacheObserver {
092    
093    private JLabel intervalLabel = null;
094    private JTextArea fileInfoArea = null;
095    private JLabel fileNameLabel = null;
096    private JSlider updateIntervallSlider = null;
097    private JLabel nextUpdateLabel = null;
098    private JCheckBox dontCacheBox = null;
099    private JRadioButton showTailDataButton = null;
100    private JRadioButton showAllDataButton = null;
101    private JFileChooser openFileDialog = null;
102    
103    private static final String [] updateIntervalLabels = {"every 1 second.",
104                                                                                                               "every 2 seconds.", "every 3 seconds.",
105                                                                                                               "every 5 seconds.", "every 10 seconds.",
106                                                                                                               "every 15 seconds.", "every 20 seconds.",                                                                                                       
107                                                                                                               "every 30 seconds.", "every 45 seconds.",
108                                                                                                               "every 1 minute.", "every 90 seconds (1.5 minutes).",
109                                                                                                               "every 2 minutes.", "every 3 minutes.",
110                                                                                                               "every 5 minutes.", "every 10 minutes.",
111                                                                                                               "every 15 minutes.", "every 20 minutes.",
112                                                                                                               "every 30 minutes.", "every 45 minutes.",
113                                                                                                               "every 1 hour.", "only manual update."};
114    private static final long [] updateIntervalValues = {1000, 2000, 3000, 5000, 10000, 15000,
115                                                                                                             20000, 30000, 45000, 60000, 90000, 120000, 180000,
116                                                                                                             300000, 600000, 900000, 1200000, 1800000, 2700000,
117                                                                                                             3600000, -1};
118    static {
119            if (updateIntervalLabels.length != updateIntervalValues.length)
120                    throw new Error("The arrays \"updateIntervalLabels\" and \"updateIntervalValues\" are not of the same size!");
121    }
122    
123    
124    /**
125     * This is the default constructor.
126     */
127    public DataFileSettingsWindow() {
128            super();
129            initialize();
130    }
131    
132    
133    /**
134     * This method initializes the data file settings window.
135     */
136    private void initialize() {
137            
138            // Window settings:
139            
140            final DataFileSettingsWindow DATAFILE_WIN = this;
141            this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);     
142            Dimension frameDim = new Dimension(470, 300);
143            this.setPreferredSize(frameDim);
144            this.setBounds(5, 5, frameDim.width, frameDim.height);  
145            this.setTitle("Data file settings (LiveGraph)");
146            getContentPane().setLayout(new BorderLayout());
147            
148            // Hide-show listener:
149            
150            this.addWindowListener(new WindowAdapter() {
151                    @Override public void windowClosing(WindowEvent e) {
152                            LiveGraph.application().setDisplayDataFileSettingsWindow(false);
153                    }
154            });
155            
156            // Layout:      
157            
158            //JPanel panel = null;
159            JButton button = null;
160            Dimension dim = null;
161            
162            // Settings controls:
163            
164            JPanel settingsPanel = new JPanel(new GridBagLayout()); 
165            settingsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
166            getContentPane().add(settingsPanel, BorderLayout.NORTH);        
167            settingsPanel.add(new Box.Filler((dim = new Dimension(1, 1)), dim, dim),
168                                              new GridBagConstraints(3, 0, 1, 1, 1, 0,
169                                                                                             GridBagConstraints.WEST,
170                                                                                             GridBagConstraints.BOTH,
171                                                                                             new Insets(0, 0, 0, 0),
172                                                                                             0, 0));
173            
174            // File name input:     
175            
176            settingsPanel.add(new JLabel("Data file:"), org.LiveGraph.gui.Tools.createGridBagConstraints(0, 0, 3, 1));
177            
178            openFileDialog = new JFileChooser();
179            openFileDialog.addChoosableFileFilter(new FileFilter() {
180            @Override public boolean accept(File f) {
181                    if (null == f) return false;
182                    if (f.isDirectory()) return true;
183                    int p = f.getName().lastIndexOf(".");        
184                    return p < 0 ? false : f.getName().substring(p).equalsIgnoreCase(".csv");
185                }
186            @Override public String getDescription() { return "Comma separated values (*.csv)"; }
187            });
188            openFileDialog.addChoosableFileFilter(new FileFilter() {
189                    @Override public boolean accept(File f) {
190                    if (null == f) return false;
191                    if (f.isDirectory()) return true;
192                    int p = f.getName().lastIndexOf(".");        
193                    return p < 0 ? false : f.getName().substring(p).equalsIgnoreCase(".dat");
194                }
195                    @Override public String getDescription() { return "Generic data files (*.dat)"; }
196            });
197            openFileDialog.setCurrentDirectory(new File(System.getProperty("user.dir")));
198            
199            fileNameLabel = new JLabel("- no data file selected -");
200            fileNameLabel.setFont(SwingTools.getPlainFont(fileNameLabel));
201            settingsPanel.add(fileNameLabel, Tools.createGridBagConstraints(0, 1, 4, 1));
202            settingsPanel.add((button = new JButton("Open...")), Tools.createGridBagConstraints(4, 1, 1, 1));
203            button.addActionListener(new ActionListener() {
204                    public void actionPerformed(ActionEvent e) {
205                            
206                            if (JFileChooser.APPROVE_OPTION != openFileDialog.showOpenDialog(DATAFILE_WIN))
207                                    return;
208                            if (!openFileDialog.getSelectedFile().exists())
209                                    return;
210                            
211                            String filePath = openFileDialog.getSelectedFile().getAbsolutePath();
212                            try {
213                                    LiveGraph.application().getDataFileSettings().setDataFile(filePath);
214                                    LiveGraph.application().logSuccessLn("New source data file set: \"" + filePath + "\".");
215                                    
216                            } catch (ErrorWhileSettingHasChangedProcessingException ex) {
217                                                                    
218                                    setFileNameLabel(null);
219                                    String extraInfo = ".";
220                                    if (null != ex.getCause())
221                                            extraInfo = ": \n    (" + ex.getCause().getMessage() + ").";
222                                            
223                                    LiveGraph.application().logErrorLn("Error setting source data file to \"" + filePath + "\""
224                                                                                                     + extraInfo);                                                                                           
225                                    
226                                    try { LiveGraph.application().getDataFileSettings().setDataFile(""); }
227                                    catch (Exception aex) {}
228                                    LiveGraph.application().logInfoLn("Source data file re-set to: \"\".");
229                            }                                                                       
230            }               
231            });
232            
233            // Cache options:
234            
235            ButtonGroup bGroup = new ButtonGroup();
236            bGroup.add(showAllDataButton = new JRadioButton("Show all data", true));
237            bGroup.add(showTailDataButton = new JRadioButton("Show tail data", false));
238            showAllDataButton.addActionListener(new ActionListener() {
239                    public void actionPerformed(ActionEvent e) {
240                            LiveGraph.application().getDataFileSettings().setShowOnlyTailData(false);
241            }               
242            });     
243            showTailDataButton.addActionListener(new ActionListener() {
244                    public void actionPerformed(ActionEvent e) {
245                            LiveGraph.application().getDataFileSettings().setShowOnlyTailData(true);
246            }               
247            });
248            settingsPanel.add(showAllDataButton, Tools.createGridBagConstraints(0, 3, 1, 1));
249            settingsPanel.add(showTailDataButton, Tools.createGridBagConstraints(1, 3, 1, 1));
250            
251            dontCacheBox = new JCheckBox("Do not cache data", false);
252            dontCacheBox.addActionListener(new ActionListener() {
253                    public void actionPerformed(ActionEvent e) {
254                            LiveGraph.application().getDataFileSettings().setDoNotCacheData(dontCacheBox.isSelected());                     
255            }               
256            });
257            settingsPanel.add(dontCacheBox, Tools.createGridBagConstraints(2, 3, 3, 1));
258            
259            // Update interval slider:
260            
261            settingsPanel.add(new JLabel("Update frequency:"), Tools.createGridBagConstraints(0, 4, 3, 1));
262            updateIntervallSlider = new JSlider(0, updateIntervalLabels.length - 1, updateIntervalLabels.length - 1);
263            updateIntervallSlider.setMinorTickSpacing(1);   
264            updateIntervallSlider.setSnapToTicks(true);
265            updateIntervallSlider.setPaintTicks(true);
266            updateIntervallSlider.setPaintTrack(true);
267            updateIntervallSlider.setPaintLabels(false);
268            updateIntervallSlider.setMajorTickSpacing(1);
269            updateIntervallSlider.addChangeListener(new ChangeListener() {
270                    public void stateChanged(ChangeEvent e) {
271                            int v = updateIntervallSlider.getValue();                       
272                            LiveGraph.application().getDataFileSettings().setUpdateFrequency(updateIntervalValues[v]);
273            }               
274            });
275            settingsPanel.add(updateIntervallSlider, Tools.createGridBagConstraints(0, 5, 5, 1));
276            
277            intervalLabel = new JLabel(updateIntervalLabels[updateIntervalLabels.length - 1]);
278            intervalLabel.setFont(SwingTools.getPlainFont(intervalLabel));
279            settingsPanel.add(intervalLabel, Tools.createGridBagConstraints(0, 6, 4, 1));
280            
281            // Update buttons & cache settings:
282    
283            nextUpdateLabel = new JLabel("Next update: manual. ");
284            settingsPanel.add(nextUpdateLabel, Tools.createGridBagConstraints(0, 7, 4, 1));
285            
286            settingsPanel.add((button = new JButton("Update now")), Tools.createGridBagConstraints(4, 7, 1, 1));
287            button.addActionListener(new ActionListener() {
288                    public void actionPerformed(ActionEvent e) {
289                            LiveGraph.application().initiateDataUpdate();
290            }               
291            });     
292            
293            
294            // File info text field:
295            
296            this.fileInfoArea = new JTextArea();
297            this.fileInfoArea.setEditable(false);
298            JPanel fileInfoPanel = new JPanel(new BorderLayout(5, 5));
299            fileInfoPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
300            fileInfoPanel.add(new JLabel("File info:"), BorderLayout.NORTH);
301            fileInfoPanel.add(new JScrollPane(this.fileInfoArea), BorderLayout.CENTER);
302            getContentPane().add(fileInfoPanel, BorderLayout.CENTER);
303            
304    }
305    
306    /**
307     * Updates an apropriate label as the invoker counts down the time until the next update.
308     */
309    public void timerTick(UpdateInvoker source) {
310            long remaining = source.getRemainingMillis();
311            if (remaining < 0) {
312                    nextUpdateLabel.setText("Next update: on button click.");
313                    return;
314            }
315            
316            long h = remaining / 3600000;
317            long m = (remaining % 3600000) / 60000;
318            double s = ((remaining % 3600000) % 60000) / 1000.;
319            
320            StringBuffer t = new StringBuffer("Next update: ");
321            if (h > 0) {
322                    t.append(h);
323                    t.append(h == 1 ? " hour " : " hours ");
324            }
325            
326            if (h > 0 || m > 0) {
327                    t.append(m);
328                    t.append(m == 1 ? " minute " : " minutes ");
329            }
330            
331            t.append(s);
332            t.append(" seconds.");
333            
334            nextUpdateLabel.setText(t.toString());
335    }
336    
337    /**
338     * Displays an apropriate label when the invoker has started an update from the data file.
339     */
340    public void updateStarted(UpdateInvoker source) {
341            nextUpdateLabel.setText("Update in progress."); 
342    }
343    
344    /**
345     * Displays an apropriate label when the invoker has finished an update from the data file. 
346     */
347    public void updateFinished(UpdateInvoker source, String errorMsg) {
348            
349            if (null == errorMsg) {
350                    nextUpdateLabel.setText("Update finished successfully.");
351                    return;
352            }
353            
354            String out = "Problem: " + errorMsg.trim();
355            if (!out.endsWith("."))
356                    out = out + ".";
357            
358            nextUpdateLabel.setText(out);
359    }
360    
361    /**
362     * Sets the file name label in the window. If the label is too long, the baginning it stripped off.
363     * @param fileName Data file name.
364     */
365    private void setFileNameLabel(String fileName) {
366            if (null == fileName || 0 == fileName.trim().length()) {
367                    fileNameLabel.setText("- no data file selected -");
368                    return;
369            }
370            fileName = fileName.trim();
371            FontMetrics fm = fileNameLabel.getFontMetrics(fileNameLabel.getFont());
372            if (fm.stringWidth(fileName) > fileNameLabel.getWidth() - 10) {
373                    while (fm.stringWidth("..." + fileName) > fileNameLabel.getWidth() - 10) {
374                            fileName = fileName.substring(1);       
375                    }
376                    fileName = "..." + fileName;
377            }                                       
378            fileNameLabel.setText(fileName);
379    }
380    
381    /**
382     * Updates the view when the settings were loaded from a file.
383     * @param settings The settings.
384     * @param info Event info.
385     */
386    public void settingHasChanged(ObservableSettings settings, Object info) {
387            
388            if (null == settings)
389                    return;
390            
391            if (settings instanceof DataFileSettings) {
392                    settingHasChanged((DataFileSettings) settings, info);
393                    return;
394            }
395    }
396    
397    /**
398     * Updates the view when the settings were loaded from a file.
399     * @param settings The settings.
400     * @param info Event info.
401     */
402    public void settingHasChanged(DataFileSettings settings, Object info) {
403            
404            if (null == info || !(info instanceof String))
405                    return;
406            
407            String event = (String) info;
408            
409            if (event.equals("DataFile") || event.equals("load")) {
410                    setFileNameLabel(settings.getDataFile());
411            }
412            
413            if (event.equals("ShowOnlyTailData") || event.equals("load")) {
414                    showAllDataButton.setSelected(!settings.getShowOnlyTailData());
415                    showTailDataButton.setSelected(settings.getShowOnlyTailData());
416            }
417            
418            if (event.equals("DoNotCacheData") || event.equals("load")) {
419                    dontCacheBox.setSelected(settings.getDoNotCacheData());
420            }
421            
422            if (event.equals("UpdateFrequency") || event.equals("load")) {          
423                    long f = settings.getUpdateFrequency();
424                    int p = (0 >= f ? updateIntervalValues.length - 1 : Arrays.binarySearch(updateIntervalValues, f));
425                    String lab;
426                    if (0 > p)
427                            lab = "every " + f + " milliseconds.";
428                    else
429                            lab = updateIntervalLabels[p];
430                    updateIntervallSlider.setValue(p);
431                    intervalLabel.setText(lab);             
432            }       
433    }
434    
435    /**
436     * Displayes data file info.
437     * @param text Info.
438     */
439    private void setDataFileInfoText(String text) {
440            fileInfoArea.setText(text + "\n ");
441            JScrollBar sb = ((JScrollPane) fileInfoArea.getParent().getParent()).getVerticalScrollBar();
442            if (null != sb)
443                    sb.setValue(sb.getMaximum());
444    }
445    
446    /**
447     * Updates data file info when the cache changes.
448     */
449    public void cacheEventFired(DataCache cache, CacheEvent event) {
450            
451            switch(event) {
452                    case UpdateLabels:                      
453                    case ChangeMode:
454                    case UpdateData:
455                            break;
456                    case UpdateDataFileInfo:
457                            setDataFileInfoText(cache.getDataFileInfo());
458                            break;
459                    default:
460                            throw new Error("This case is impossible!");
461                            
462            }
463    }
464    
465    }