001    package org.LiveGraph.gui.dfs;
002    
003    import java.awt.BorderLayout;
004    import java.awt.Color;
005    import java.awt.Dimension;
006    import java.awt.FontMetrics;
007    import java.awt.GridBagConstraints;
008    import java.awt.GridBagLayout;
009    import java.awt.Insets;
010    import java.awt.event.ActionEvent;
011    import java.awt.event.ActionListener;
012    import java.io.File;
013    import java.util.Arrays;
014    
015    import javax.swing.BorderFactory;
016    import javax.swing.Box;
017    import javax.swing.ButtonGroup;
018    import javax.swing.JButton;
019    import javax.swing.JCheckBox;
020    import javax.swing.JFileChooser;
021    import javax.swing.JLabel;
022    import javax.swing.JPanel;
023    import javax.swing.JRadioButton;
024    import javax.swing.JScrollBar;
025    import javax.swing.JScrollPane;
026    import javax.swing.JSlider;
027    import javax.swing.JTextArea;
028    import javax.swing.event.ChangeEvent;
029    import javax.swing.event.ChangeListener;
030    import javax.swing.filechooser.FileFilter;
031    
032    import org.LiveGraph.LiveGraph;
033    import org.LiveGraph.dataCache.CacheEvent;
034    import org.LiveGraph.dataCache.DataCache;
035    import org.LiveGraph.dataCache.UpdateInvoker;
036    import org.LiveGraph.dataCache.DataUpdateEvent;
037    import org.LiveGraph.events.Event;
038    import org.LiveGraph.events.EventType;
039    import org.LiveGraph.gui.LiveGraphSettingsPanel;
040    import org.LiveGraph.gui.Tools;
041    import org.LiveGraph.settings.DataFileSettings;
042    import org.LiveGraph.settings.SettingsEvent;
043    
044    import com.softnetConsult.utils.exceptions.Bug;
045    import com.softnetConsult.utils.exceptions.UnexpectedSwitchCase;
046    import com.softnetConsult.utils.files.FileTools;
047    import com.softnetConsult.utils.swing.SwingTools;
048    
049    
050    /**
051     * The data file settings panel of the application. This is the only component contained in
052     * the content pane of the application's data file settings window. API users may request
053     * {@link org.LiveGraph.gui.GUIManager} to create additional instances of a
054     * {@code DataFileSettingsPanel} if they wish to integrate the LiveGraph GUI into their application.
055     * 
056     * <p>
057     *   <strong>LiveGraph</strong>
058     *   (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>).
059     * </p> 
060     * <p>Copyright (c) 2007-2008 by G. Paperin.</p>
061     * <p>File: DataFileSettingsPanel.java</p>
062     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
063     *    without modification, are permitted provided that the following terms and conditions are met:
064     * </p>
065     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
066     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
067     *    this list of conditions and the following disclaimer.<br />
068     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
069     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
070     *    and the following disclaimer in the documentation and/or other materials provided with
071     *    the distribution.<br />
072     *    3. All advertising materials mentioning features or use of this software or any derived
073     *    software must display the following acknowledgement:<br />
074     *    <em>This product includes software developed by the LiveGraph project and its
075     *    contributors.<br />(http://www.live-graph.org)</em><br />
076     *    4. All advertising materials distributed in form of HTML pages or any other technology
077     *    permitting active hyper-links that mention features or use of this software or any
078     *    derived software must display the acknowledgment specified in condition 3 of this
079     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
080     *    homepage (http://www.live-graph.org).
081     * </p>
082     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
083     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
084     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
085     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
086     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
087     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
088     * </p>
089     * 
090     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
091     * @version {@value org.LiveGraph.LiveGraph#version}
092     *
093     */
094    public class DataFileSettingsPanel extends LiveGraphSettingsPanel {
095    
096    private static final String [] updateIntervalLabels = {
097                                                                            "100 Hz. (Expert mode). Never use \"Do not cache data\"!",
098                                                                            "50 Hz. (Expert mode). Never use \"Do not cache data\"!",
099                                                                            "10 Hz (10 times per second). Avoid using \"Do not cache data\".",
100                                                                            "2 Hz (twice per second). Avoid using \"Do not cache data\".",
101                                                                            "every 1 second (1 Hz). Consider avoiding using \"Do not cache data\".",
102                                                                            "every 2 seconds (0.5 Hz). Consider avoiding using \"Do not cache data\".",
103                                                                            "every 3 seconds (0.333 Hz). Consider avoiding using \"Do not cache data\".",
104                                                                            "every 5 seconds (0.2 Hz).",
105                                                                            "every 10 seconds (0.1 Hz).",
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    
115    private static final long [] updateIntervalValues = {
116                                                                            10, 20, 100, 500,
117                                                                            1000, 2000, 3000, 5000, 10000, 15000,
118                                                                            20000, 30000, 45000, 60000, 90000, 120000, 180000,
119                                                                            300000, 600000, 900000, 1200000, 1800000, 2700000,
120                                                                            3600000, -1};
121    static {
122            if (updateIntervalLabels.length != updateIntervalValues.length)
123                    throw new Bug("The arrays \"updateIntervalLabels\" and \"updateIntervalValues\" are"
124                                    + " not of the same size!");
125    }
126    
127    private JLabel intervalLabel = null;
128    private JTextArea fileInfoArea = null;
129    private JLabel fileNameLabel = null;
130    private JSlider updateIntervallSlider = null;
131    private JLabel nextUpdateLabel = null;
132    private Color nextUpdateLabelDefaultColour = null;
133    private JCheckBox dontCacheBox = null;
134    private JRadioButton showTailDataButton = null;
135    private JRadioButton showAllDataButton = null;
136    private JFileChooser openFileDialog = null;
137    private JButton openButton = null;
138    
139    /**
140     * This is the default constructor.
141     */
142    public DataFileSettingsPanel() {
143            super();
144            initialize();
145    }
146    
147    /**
148     * This method initializes the data file settings panel.
149     */
150    private void initialize() {
151            
152            // General settings:
153            
154            final JPanel thisPanel = this;  
155            Dimension panelDim = new Dimension(470, 300);
156            this.setPreferredSize(panelDim);
157            this.setSize(panelDim); 
158            thisPanel.setLayout(new BorderLayout());
159            
160            DataFileSettings dfSettings = LiveGraph.application().getDataFileSettings();
161            if (null == dfSettings)
162                    dfSettings = new DataFileSettings();
163            
164            // Layout:      
165            
166            JButton button = null;
167            Dimension dim = null;
168            
169            // Settings controls:
170            
171            JPanel settingsPanel = new JPanel(new GridBagLayout()); 
172            settingsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
173            thisPanel.add(settingsPanel, BorderLayout.NORTH);       
174            settingsPanel.add(new Box.Filler((dim = new Dimension(1, 1)), dim, dim),
175                                              new GridBagConstraints(3, 0, 1, 1, 1, 0,
176                                                                                             GridBagConstraints.WEST,
177                                                                                             GridBagConstraints.BOTH,
178                                                                                             new Insets(0, 0, 0, 0),
179                                                                                             0, 0));
180            
181            // File name input:     
182            
183            settingsPanel.add(new JLabel("Data file:"), org.LiveGraph.gui.Tools.createGridBagConstraints(0, 0, 3, 1));
184            
185            openFileDialog = null;
186            try {
187                    openFileDialog = new JFileChooser("");
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                            return ".csv".equalsIgnoreCase(FileTools.getExtension(f));
193                        }
194                    @Override public String getDescription() { return "Comma separated values (*.csv)"; }
195                    });
196                    openFileDialog.addChoosableFileFilter(new FileFilter() {
197                            @Override public boolean accept(File f) {
198                            if (null == f) return false;
199                            if (f.isDirectory()) return true;
200                            return ".dat".equalsIgnoreCase(FileTools.getExtension(f));
201                        }
202                            @Override public String getDescription() { return "Generic data files (*.dat)"; }
203                    });
204                    openFileDialog.addChoosableFileFilter(new FileFilter() {
205                            @Override public boolean accept(File f) {
206                            if (null == f) return false;
207                            if (f.isDirectory()) return true;
208                            return ".lgdat".equalsIgnoreCase(FileTools.getExtension(f));
209                        }
210                            @Override public String getDescription() { return "LiveGraph data files (*.lgdat)"; }
211                    });
212                    try {
213                            openFileDialog.setCurrentDirectory(new File(System.getProperty("user.dir")));
214                    } catch(SecurityException e) {
215                            openFileDialog.setCurrentDirectory(new File(System.getProperty("user.home")));
216                    }
217            } catch(SecurityException e) {
218                    openFileDialog = null;
219            }
220            
221            fileNameLabel = new JLabel("- no data file selected -");
222            setFileNameLabel(dfSettings.getDataFile());
223            fileNameLabel.setFont(SwingTools.getPlainFont(fileNameLabel));
224            settingsPanel.add(fileNameLabel, Tools.createGridBagConstraints(0, 1, 4, 1));
225            settingsPanel.add((openButton = new JButton("Open...")), Tools.createGridBagConstraints(4, 1, 1, 1));
226            openButton.addActionListener(new ActionListener() {
227                    public void actionPerformed(ActionEvent e) {
228                            
229                            if (JFileChooser.APPROVE_OPTION != openFileDialog.showOpenDialog(thisPanel))
230                                    return;
231                            if (!openFileDialog.getSelectedFile().exists())
232                                    return;
233                            
234                            String filePath = openFileDialog.getSelectedFile().getAbsolutePath();
235                            DataFileSettings dfs = LiveGraph.application().getDataFileSettings();
236                            dfs.setDataFile(filePath);
237                            LiveGraph.application().guiManager().logInfoLn("Data source file : \"" + dfs.getDataFile() + "\".");
238            }               
239            });
240            openButton.setEnabled(null != openFileDialog);
241            
242            // Cache options:
243            
244            ButtonGroup bGroup = new ButtonGroup();
245            bGroup.add(showAllDataButton = new JRadioButton("Show all data", !dfSettings.getShowOnlyTailData()));
246            bGroup.add(showTailDataButton = new JRadioButton("Show tail data", dfSettings.getShowOnlyTailData()));
247            showAllDataButton.addActionListener(new ActionListener() {
248                    public void actionPerformed(ActionEvent e) {
249                            DataFileSettings dfs = LiveGraph.application().getDataFileSettings();
250                            dfs.setShowOnlyTailData(!showAllDataButton.isSelected());
251                            showAllDataButton.setSelected(!dfs.getShowOnlyTailData());
252                            showTailDataButton.setSelected(dfs.getShowOnlyTailData());
253                            System.out.println("DFS:" + dfs.getShowOnlyTailData());
254            }               
255            });     
256            showTailDataButton.addActionListener(new ActionListener() {
257                    public void actionPerformed(ActionEvent e) {
258                            DataFileSettings dfs = LiveGraph.application().getDataFileSettings();
259                            dfs.setShowOnlyTailData(showTailDataButton.isSelected());
260                            showAllDataButton.setSelected(!dfs.getShowOnlyTailData());
261                            showTailDataButton.setSelected(dfs.getShowOnlyTailData());
262                            System.out.println("DFS:" + dfs.getShowOnlyTailData());
263            }               
264            });
265            settingsPanel.add(showAllDataButton, Tools.createGridBagConstraints(0, 3, 1, 1));
266            settingsPanel.add(showTailDataButton, Tools.createGridBagConstraints(1, 3, 1, 1));
267            
268            dontCacheBox = new JCheckBox("Do not cache data", dfSettings.getDoNotCacheData());
269            dontCacheBox.addActionListener(new ActionListener() {
270                    public void actionPerformed(ActionEvent e) {
271                            DataFileSettings dfs = LiveGraph.application().getDataFileSettings();
272                            dfs.setDoNotCacheData(dontCacheBox.isSelected());
273                            dontCacheBox.setSelected(dfs.getDoNotCacheData());
274            }               
275            });
276            settingsPanel.add(dontCacheBox, Tools.createGridBagConstraints(2, 3, 3, 1));
277            
278            // Update interval slider:
279            
280            settingsPanel.add(new JLabel("Update frequency:"), Tools.createGridBagConstraints(0, 4, 3, 1));
281            
282            intervalLabel = new JLabel(updateIntervalLabels[updateIntervalLabels.length - 1]);
283            intervalLabel.setFont(SwingTools.getPlainFont(intervalLabel));
284            settingsPanel.add(intervalLabel, Tools.createGridBagConstraints(0, 6, 5, 1));
285            
286            updateIntervallSlider = new JSlider(0, updateIntervalLabels.length - 1, updateIntervalLabels.length - 1);
287            updateIntervallSlider.setMinorTickSpacing(1);   
288            updateIntervallSlider.setSnapToTicks(true);
289            updateIntervallSlider.setPaintTicks(true);
290            updateIntervallSlider.setPaintTrack(true);
291            updateIntervallSlider.setPaintLabels(false);
292            updateIntervallSlider.setMajorTickSpacing(1);
293            settingsPanel.add(updateIntervallSlider, Tools.createGridBagConstraints(0, 5, 5, 1));
294            setUpdateFrequencyLabels(dfSettings.getUpdateFrequency());
295            updateIntervallSlider.addChangeListener(new ChangeListener() {
296                    public void stateChanged(ChangeEvent e) {
297                            int v = updateIntervallSlider.getValue();
298                            DataFileSettings dfs = LiveGraph.application().getDataFileSettings();
299                            dfs.setUpdateFrequency(updateIntervalValues[v]);
300                            setUpdateFrequencyLabels(dfs.getUpdateFrequency());
301            }               
302            });
303            
304            
305            // Update buttons & cache settings:
306    
307            nextUpdateLabel = new JLabel(formatNextUpdateLabelString(dfSettings.getUpdateFrequency()));
308            settingsPanel.add(nextUpdateLabel, Tools.createGridBagConstraints(0, 7, 4, 1));
309            nextUpdateLabelDefaultColour = nextUpdateLabel.getForeground();
310            
311            settingsPanel.add((button = new JButton("Update now")), Tools.createGridBagConstraints(4, 7, 1, 1));
312            button.addActionListener(new ActionListener() {
313                    public void actionPerformed(ActionEvent e) {
314                            LiveGraph.application().updateInvoker().requestUpdate();
315            }               
316            });     
317            
318            
319            // File info text field:
320            
321            this.fileInfoArea = new JTextArea();
322            this.fileInfoArea.setEditable(false);
323            JPanel fileInfoPanel = new JPanel(new BorderLayout(5, 5));
324            fileInfoPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
325            fileInfoPanel.add(new JLabel("File info:"), BorderLayout.NORTH);
326            fileInfoPanel.add(new JScrollPane(this.fileInfoArea), BorderLayout.CENTER);
327            thisPanel.add(fileInfoPanel, BorderLayout.CENTER);
328            
329    }  // private void initialize()
330    
331    /**
332     * Displayes data file info.
333     * @param text Info.
334     */
335    private void setDataFileInfoText(String text) {
336            fileInfoArea.setText(text + "\n ");
337            JScrollBar sb = ((JScrollPane) fileInfoArea.getParent().getParent()).getVerticalScrollBar();
338            if (null != sb)
339                    sb.setValue(sb.getMaximum());
340    } // private void setDataFileInfoText
341    
342    
343    /**
344     * Sets the file name label in the window. If the label is too long, the baginning it stripped off.
345     * @param fileName Data file name.
346     */
347    private void setFileNameLabel(String fileName) {
348            final String noDataFileLabel = "- no data file selected -"; 
349            if (null == fileName || 0 == fileName.trim().length()) {
350                    fileNameLabel.setText(noDataFileLabel);
351                    return;
352            }
353            fileName = fileName.trim();
354            FontMetrics fm = fileNameLabel.getFontMetrics(fileNameLabel.getFont());
355            if (fm.stringWidth(fileName) > fileNameLabel.getWidth() - 10) {
356                    while (fileName.length() > noDataFileLabel.length()
357                                    && fm.stringWidth("..." + fileName) > fileNameLabel.getWidth() - 10) {
358                            fileName = fileName.substring(1);       
359                    }
360                    fileName = "..." + fileName;
361            }                                       
362            fileNameLabel.setText(fileName);
363    } // private void setFileNameLabel
364    
365    
366    /**
367     * Updates the view of the update inverval slider and label according to the specified update frequency.
368     * @param f Update frequency.
369     */
370    private void setUpdateFrequencyLabels(long f) {
371            int p = (0 >= f ? updateIntervalValues.length - 1 : Arrays.binarySearch(updateIntervalValues, f));
372            String lab;
373            if (0 > p)
374                    lab = "every " + f + " milliseconds.";
375            else
376                    lab = updateIntervalLabels[p];
377            updateIntervallSlider.setValue(p);
378            intervalLabel.setText(lab);
379    }
380    
381    /**
382     * Formats the text for the {@code nextUpdateLabel}.
383     * 
384     * @param remaining Milliseconds.
385     * @return A formatted string.
386     */
387    private String formatNextUpdateLabelString(long remaining) {
388            
389            if (remaining < 0) {
390                    return "Next update: on button click.";
391            }
392            
393            long h = remaining / 3600000;
394            long m = (remaining % 3600000) / 60000;
395            long s = ((remaining % 3600000) % 60000) / 1000;
396            long mill = ((remaining % 3600000) % 60000) % 1000;
397            
398            StringBuffer t = new StringBuffer("Next update: ");
399            if (h > 0) {
400                    t.append(h);
401                    t.append(h == 1 ? " hour " : " hours ");
402            }
403            
404            if (h > 0 || m > 0) {
405                    t.append(m);
406                    t.append(m == 1 ? " minute " : " minutes ");
407            }
408            
409            t.append(s);
410            t.append(".");
411            
412            if (mill < 10)
413                    t.append("00");
414            else if (mill < 100) {
415                    t.append("0");
416            }
417            
418            t.append(mill);
419            t.append(" seconds.");
420            
421            return t.toString();
422    }
423    
424    /**
425     * Processes events.
426     * 
427     * @param event Event to process.
428     */
429    @Override
430    public void eventRaised(Event<? extends EventType> event) {
431            
432            super.eventRaised(event);
433            
434            if (event.getDomain() == CacheEvent.class) {
435                    processCacheEvent(event.cast(CacheEvent.class));
436                    return;
437            }
438            
439            if (event.getDomain() == DataUpdateEvent.class) {
440                    processDataUpdateEvent(event.cast(DataUpdateEvent.class));
441                    return;
442            }
443    }
444    
445    /**
446     * Updates the view when data file settings change.
447     * 
448     * @param event Describes the change event.
449     */
450    @Override
451    protected void processSettingsEvent(Event<SettingsEvent> event) {
452    
453            final DataFileSettings settings = LiveGraph.application().getDataFileSettings();
454            final boolean loadEvent = (SettingsEvent.DFS_Load == event.getType());
455            
456            if (SettingsEvent.DFS_DataFile == event.getType() || loadEvent) {
457                    setFileNameLabel(settings.getDataFile());
458            }
459            
460            if (SettingsEvent.DFS_ShowOnlyTailData == event.getType() || loadEvent) {
461                    showAllDataButton.setSelected(!settings.getShowOnlyTailData());
462                    showTailDataButton.setSelected(settings.getShowOnlyTailData());
463            }
464            
465            if (SettingsEvent.DFS_DoNotCacheData == event.getType() || loadEvent) {
466                    dontCacheBox.setSelected(settings.getDoNotCacheData());
467            }
468            
469            if (SettingsEvent.DFS_UpdateFrequency == event.getType() || loadEvent) {
470                    setUpdateFrequencyLabels(settings.getUpdateFrequency());
471            }
472    }
473    
474    
475    /**
476     * Updates data file info when the cache changes.
477     * 
478     * @param event The cache event.
479     */
480    private void processCacheEvent(Event<CacheEvent> event) {
481            
482            switch(event.getType()) {
483                    case CACHE_UpdatedLabels:                       
484                    case CACHE_ChangedMode:
485                    case CACHE_UpdatedData:
486                            break;
487                    case CACHE_UpdatedDataFileInfo:
488                            setDataFileInfoText(((DataCache) event.getProducer()).getDataFileInfo());
489                            break;
490                    default:
491                            throw new UnexpectedSwitchCase(event.getType());
492                            
493            }
494    }
495    
496    /**
497     * Updates the panel when an {@code UpdateInvoker} event occured.
498     * 
499     * @param event The event.
500     */
501    private void processDataUpdateEvent(Event<DataUpdateEvent> event) {
502            
503            switch(event.getType()) {
504                    
505                    case UPDIN_TimerTick:
506                            nextUpdateLabel.setForeground(nextUpdateLabelDefaultColour);
507                            long remaining = ((UpdateInvoker) event.getProducer()).getRemainingMillis();
508                            nextUpdateLabel.setText(formatNextUpdateLabelString(remaining));
509                            break;
510                            
511                    case UPDIN_UpdateStart:
512                            nextUpdateLabel.setForeground(nextUpdateLabelDefaultColour);
513                            nextUpdateLabel.setText("Update in progress.");
514                            break;
515                            
516                    case UPDIN_UpdateFinishSuccess:
517                            nextUpdateLabel.setForeground(nextUpdateLabelDefaultColour);
518                            nextUpdateLabel.setText("Update finished successfully.");
519                            break;
520                            
521                    case UPDIN_CannotInitiateUpdate:
522                    case UPDIN_UpdateFinishError:
523                            nextUpdateLabel.setForeground(Color.RED);
524                            String out = ((String) event.getInfoObject()).trim();
525                            nextUpdateLabel.setText(out);
526                            break;
527                            
528                    case UPDIN_StartMemoryStreamMode:
529                            openButton.setEnabled(false);
530                            dontCacheBox.setSelected(LiveGraph.application().getDataFileSettings().getDoNotCacheData());
531                            showAllDataButton.setEnabled(false);
532                            showTailDataButton.setEnabled(false);
533                            dontCacheBox.setEnabled(false);
534                            setFileNameLabel("Data loaded directly from main memory.");
535                            break;
536                            
537                    case UPDIN_EndMemoryStreamMode:                 
538                            openButton.setEnabled(true);                    
539                            showAllDataButton.setEnabled(true);
540                            showTailDataButton.setEnabled(true);
541                            dontCacheBox.setEnabled(true);
542                            DataFileSettings dfs = LiveGraph.application().getDataFileSettings();
543                            dontCacheBox.setSelected(dfs.getDoNotCacheData());
544                            setFileNameLabel(dfs.getDataFile());
545                            break;
546                    
547                    default:
548                            break;                  
549            }
550    }
551    
552    } // public class DataFileSettingsPanel