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 "AS IS", 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 }