View Javadoc

1   /* OpenLogViewer
2    *
3    * Copyright 2011
4    *
5    * This file is part of the OpenLogViewer project.
6    *
7    * OpenLogViewer software is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation, either version 3 of the License, or
10   * (at your option) any later version.
11   *
12   * OpenLogViewer software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with any OpenLogViewer software.  If not, see http://www.gnu.org/licenses/
19   *
20   * I ask that if you make any changes to this file you fork the code on github.com!
21   *
22   */
23  
24  /*
25   * OpenLogViewerApp.java
26   *
27   * Created on Jan 26, 2011, 2:55:31 PM
28   */
29  package org.diyefi.openlogviewer;
30  
31  import java.awt.BorderLayout;
32  import java.awt.Dimension;
33  import java.awt.GraphicsDevice;
34  import java.awt.GraphicsEnvironment;
35  import java.awt.Toolkit;
36  import java.awt.event.ActionEvent;
37  import java.awt.event.ActionListener;
38  import java.awt.event.WindowEvent;
39  import java.io.File;
40  import java.io.IOException;
41  import java.io.FileInputStream;
42  import java.io.FileOutputStream;
43  import java.util.List;
44  import java.util.ArrayList;
45  import java.util.Locale;
46  import java.util.Properties;
47  import java.util.ResourceBundle;
48  
49  import javax.swing.JFileChooser;
50  import javax.swing.JFrame;
51  import javax.swing.JMenu;
52  import javax.swing.JMenuBar;
53  import javax.swing.JMenuItem;
54  import javax.swing.JPanel;
55  import javax.swing.UIManager;
56  import javax.swing.UnsupportedLookAndFeelException;
57  import javax.swing.WindowConstants;
58  import javax.swing.filechooser.FileFilter;
59  
60  import org.diyefi.openlogviewer.decoder.AbstractDecoder;
61  import org.diyefi.openlogviewer.decoder.CSVTypeLog;
62  import org.diyefi.openlogviewer.decoder.FreeEMSBin;
63  import org.diyefi.openlogviewer.filefilters.CSVFileFilter;
64  import org.diyefi.openlogviewer.filefilters.FreeEMSBinFileFilter;
65  import org.diyefi.openlogviewer.filefilters.FreeEMSLAFileFilter;
66  import org.diyefi.openlogviewer.filefilters.LogFileFilter;
67  import org.diyefi.openlogviewer.filefilters.MSTypeFileFilter;
68  import org.diyefi.openlogviewer.filefilters.FreeEMSFileFilter;
69  import org.diyefi.openlogviewer.genericlog.GenericLog;
70  import org.diyefi.openlogviewer.graphing.EntireGraphingPanel;
71  import org.diyefi.openlogviewer.graphing.MultiGraphLayeredPane;
72  import org.diyefi.openlogviewer.optionpanel.OptionFrameV2;
73  import org.diyefi.openlogviewer.propertypanel.PropertiesPane;
74  import org.diyefi.openlogviewer.propertypanel.SingleProperty;
75  import org.diyefi.openlogviewer.utils.Utilities;
76  
77  public final class OpenLogViewer extends JFrame {
78  	public static final String NEWLINE = System.getProperty("line.separator");
79  
80  	private static final long serialVersionUID = 1L;
81  
82  	private static final String APPLICATION_NAME = OpenLogViewer.class.getSimpleName();
83  	private static final String SETTINGS_DIRECTORY = "." + APPLICATION_NAME;
84  
85  	private static final String FILE_MENU_KEY = "FileMenuName";
86  	private static final String FILE_MENU_ITEM_OPEN_KEY = "FileMenuItemOpenName";
87  	private static final String FILE_MENU_ITEM_QUIT_KEY = "FileMenuItemQuitName";
88  
89  	private static final String VIEW_MENU_KEY = "ViewMenuName";
90  	private static final String VIEW_MENU_ITEM_FULL_SCREEN_KEY = "ViewMenuItemFullScreenName";
91  	private static final String VIEW_MENU_ITEM_SCALE_AND_COLOR_KEY = "ViewMenuItemScaleAndColorName";
92  	private static final String VIEW_MENU_ITEM_FIELDS_AND_DIVISIONS_KEY = "ViewMenuItemFieldsAndDivisionsName";
93  
94  	private static final String FAILED_TO_GO_FULLSCREEN_MESSAGE_KEY = "FailedToGoFullScreenMessage";
95  	private static final String CANT_GO_FULLSCREEN_MESSAGE_KEY = "CantGoFullScreenMessage";
96  
97  	// TODO localise and refactor these:
98  	private static final String PROPERTIES_FILENAME = "OLVAllProperties.olv";
99  
100 	private static final String NAME_OF_LAST_FILE_KEY = "lastFingFile";
101 	private static final String NAME_OF_LAST_DIR_KEY = "lastFingDir";
102 	private static final String NAME_OF_LAST_CHOOSER_CLASS = "chooserClass";
103 
104 	// Real vars start here, many will probably get ripped out later
105 	private static OpenLogViewer mainAppRef;
106 	private static ResourceBundle labels;
107 
108 	private JPanel mainPanel;
109 	private EntireGraphingPanel graphingPanel;
110 	private PlayBarPanel playBar;
111 	private OptionFrameV2 optionFrame;
112 	private PropertiesPane prefFrame;
113 
114 	private List<SingleProperty> properties;
115 	private AbstractDecoder decoderInUse;
116 	private JMenuBar menuBar;
117 	private boolean fullscreen;
118 
119 	public OpenLogViewer() {
120 		prefFrame = new PropertiesPane(labels.getString(VIEW_MENU_ITEM_SCALE_AND_COLOR_KEY));
121 		properties = new ArrayList<SingleProperty>();
122 		prefFrame.setProperties(properties);
123 
124 		playBar = new PlayBarPanel();
125 		optionFrame = new OptionFrameV2();
126 		graphingPanel = new EntireGraphingPanel();
127 		graphingPanel.setPreferredSize(new Dimension(600, 420));
128 
129 		this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
130 		this.setTitle(APPLICATION_NAME);
131 		this.setLayout(new BorderLayout());
132 		this.setFocusable(true);
133 
134 		mainPanel = new JPanel();
135 		mainPanel.setName("mainPanel");
136 		mainPanel.setLayout(new BorderLayout());
137 		mainPanel.add(graphingPanel, BorderLayout.CENTER);
138 		mainPanel.add(playBar, BorderLayout.SOUTH);
139 		this.add(mainPanel, BorderLayout.CENTER);
140 
141 		final JMenuItem openFileMenuItem = new JMenuItem(labels.getString(FILE_MENU_ITEM_OPEN_KEY));
142 		openFileMenuItem.setName(FILE_MENU_ITEM_OPEN_KEY);
143 		openFileMenuItem.addActionListener(new ActionListener() {
144 			@Override
145 			public void actionPerformed(final ActionEvent e) {
146 				openFile();
147 			}
148 		});
149 
150 		final JMenuItem quitFileMenuItem = new JMenuItem(labels.getString(FILE_MENU_ITEM_QUIT_KEY));
151 		quitFileMenuItem.setName(FILE_MENU_ITEM_QUIT_KEY);
152 		quitFileMenuItem.addActionListener(new ActionListener() {
153 			@Override
154 			public void actionPerformed(final ActionEvent e) {
155 				final WindowEvent wev = new WindowEvent(OpenLogViewer.getInstance(), WindowEvent.WINDOW_CLOSING);
156 				Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev);
157 			}
158 		});
159 
160 		final JMenuItem fullScreenViewMenuItem = new JMenuItem(labels.getString(VIEW_MENU_ITEM_FULL_SCREEN_KEY));
161 		fullScreenViewMenuItem.setName(VIEW_MENU_ITEM_FULL_SCREEN_KEY);
162 		fullScreenViewMenuItem.addActionListener(new ActionListener() {
163 			@Override
164 			public void actionPerformed(final ActionEvent e) {
165 				enterFullScreen();
166 			}
167 		});
168 
169 		final JMenuItem scaleAndColorViewMenuItem = new JMenuItem(labels.getString(VIEW_MENU_ITEM_SCALE_AND_COLOR_KEY));
170 		scaleAndColorViewMenuItem.setName(VIEW_MENU_ITEM_SCALE_AND_COLOR_KEY);
171 		scaleAndColorViewMenuItem.addActionListener(new ActionListener() {
172 			@Override
173 			public void actionPerformed(final ActionEvent e) {
174 				prefFrame.setVisible(true);
175 				prefFrame.setAlwaysOnTop(true);
176 				prefFrame.setAlwaysOnTop(false);
177 			}
178 		});
179 
180 		final JMenuItem fieldsAndDivisionsViewMenuItem = new JMenuItem(labels.getString(VIEW_MENU_ITEM_FIELDS_AND_DIVISIONS_KEY));
181 		fieldsAndDivisionsViewMenuItem.setName(VIEW_MENU_ITEM_FIELDS_AND_DIVISIONS_KEY);
182 		fieldsAndDivisionsViewMenuItem.addActionListener(new ActionListener() {
183 			@Override
184 			public void actionPerformed(final ActionEvent e) {
185 				optionFrame.setVisible(true);
186 				optionFrame.setAlwaysOnTop(true);
187 				optionFrame.setAlwaysOnTop(false);
188 			}
189 		});
190 
191 		/* 1 November 2011 Migrated Gufi's menu from the pointless class it's in to here.
192 		 * 22 October 2011 Left Gufi's menu in place for future dev's enjoyment.
193 		 *
194 		 * 5 February 2011 meal for the night DO NOT EDIT MENU!
195 		 * Sesame chicken alacarte
196 		 * chicken lo mein alacarte
197 		 * orange chicken x2
198 		 */
199 
200 		final JMenu fileMenu = new JMenu(labels.getString(FILE_MENU_KEY));
201 		fileMenu.setName(FILE_MENU_KEY);
202 		fileMenu.add(openFileMenuItem);
203 		fileMenu.add(quitFileMenuItem);
204 
205 		final JMenu viewMenu = new JMenu(labels.getString(VIEW_MENU_KEY));
206 		viewMenu.setName(VIEW_MENU_KEY);
207 		viewMenu.add(fullScreenViewMenuItem);
208 		viewMenu.add(scaleAndColorViewMenuItem);
209 		viewMenu.add(fieldsAndDivisionsViewMenuItem);
210 
211 		menuBar = new JMenuBar();
212 		menuBar.setName("menuBar");
213 		menuBar.add(fileMenu);
214 		menuBar.add(viewMenu);
215 		setJMenuBar(menuBar);
216 
217 		this.addKeyListener(graphingPanel);
218 		this.addComponentListener(graphingPanel);
219 
220 		pack();
221 
222 		this.setVisible(true);
223 	}
224 
225 	/**
226 	 * The entry point of OLV!
227 	 *
228 	 * @param args the command line arguments
229 	 */
230 	public static void main(final String[] args) {
231 		java.awt.EventQueue.invokeLater(new Runnable() {
232 
233 			@Override
234 			public void run() {
235 				final Locale currentLocale = Locale.getDefault();
236 				labels = ResourceBundle.getBundle(this.getClass().getPackage().getName() + ".Labels", currentLocale);
237 
238 				final String lookAndFeel;
239 				final String systemLookAndFeel = UIManager.getSystemLookAndFeelClassName();
240 				if ("com.apple.laf.AquaLookAndFeel".equals(systemLookAndFeel)) { // If Mac!
241 					System.setProperty("apple.laf.useScreenMenuBar", "true");
242 				}
243 				lookAndFeel = systemLookAndFeel;
244 
245 				try {
246 					UIManager.setLookAndFeel(lookAndFeel);
247 				} catch (UnsupportedLookAndFeelException e) {
248 					e.printStackTrace();
249 					System.out.println(labels.getString("LookAndFeelExceptionMessageOne"));
250 				} catch (ClassNotFoundException e) {
251 					e.printStackTrace();
252 					System.out.println(labels.getString("LookAndFeelExceptionMessageTwo"));
253 				} catch (InstantiationException e) {
254 					e.printStackTrace();
255 					System.out.println(labels.getString("LookAndFeelExceptionMessageThree"));
256 				} catch (IllegalAccessException e) {
257 					e.printStackTrace();
258 					System.out.println(labels.getString("LookAndFeelExceptionMessageFour"));
259 				}
260 
261 				mainAppRef = new OpenLogViewer();
262 			}
263 		});
264 	}
265 
266 	public void openFile() {
267 		final JFileChooser fileChooser = new JFileChooser();
268 		final String lastFingFile = getApplicationWideProperty(NAME_OF_LAST_FILE_KEY);
269 
270 		if (lastFingFile != null) {
271 			fileChooser.setSelectedFile(new File(lastFingFile));
272 		} else {
273 			final String lastFingDir = getApplicationWideProperty(NAME_OF_LAST_DIR_KEY);
274 			if (lastFingDir != null) {
275 				fileChooser.setCurrentDirectory(new File(lastFingDir));
276 			}
277 		}
278 
279 		fileChooser.addChoosableFileFilter(new FreeEMSFileFilter());
280 		fileChooser.addChoosableFileFilter(new FreeEMSBinFileFilter());
281 		fileChooser.addChoosableFileFilter(new FreeEMSLAFileFilter());
282 		fileChooser.addChoosableFileFilter(new CSVFileFilter());
283 		fileChooser.addChoosableFileFilter(new LogFileFilter());
284 		fileChooser.addChoosableFileFilter(new MSTypeFileFilter());
285 
286 		final String chooserClass = getApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS);
287 
288 		if (chooserClass != null) {
289 			try {
290 				final FileFilter[] existingFilters = fileChooser.getChoosableFileFilters();
291 				boolean alreadyHasSavedFilter = false;
292 				for (int i = 0; i < existingFilters.length; i++) {
293 					final String thisFilter = existingFilters[i].getClass().getCanonicalName();
294 					if (thisFilter.equals(chooserClass)) {
295 						alreadyHasSavedFilter = true;
296 						fileChooser.setFileFilter(existingFilters[i]); // If set to a new instance the list will contain two!
297 					}
298 				}
299 
300 				// If it's not one of ours, create a new one and set it, though that almost certainly means we'll throw an exception and clean up the prefs...
301 				if (!alreadyHasSavedFilter) {
302 					final FileFilter savedFilter = (FileFilter) Class.forName(chooserClass).newInstance();
303 					fileChooser.setFileFilter(savedFilter);
304 				}
305 			} catch (ClassNotFoundException c) {
306 				removeApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS);
307 				System.out.println("Class not found! chooserClass removed from props!");
308 			} catch (InstantiationException i) {
309 				removeApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS);
310 				System.out.println("Could not instantiate class! chooserClass removed from props!");
311 			} catch (IllegalAccessException l) {
312 				removeApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS);
313 				System.out.println("Could not access class! chooserClass removed from props!");
314 			}
315 		}
316 
317 		final int acceptValue = fileChooser.showOpenDialog(OpenLogViewer.getInstance());
318 		if (acceptValue == JFileChooser.APPROVE_OPTION) {
319 			if (decoderInUse != null) {
320 				// Clear out all references to data that we don't need and thereby ensure that we have lots of memory free for data we're about to gather!
321 				final GenericLog logInUse = decoderInUse.getDecodedLog();
322 				if (logInUse != null) {
323 					logInUse.clearOut(); // This is the wrong approach. The correct approach is to reuse the object, try that next...
324 				}
325 				decoderInUse = null;
326 				setLog(null);
327 			} // else haven't read in a log yet.
328 
329 			final File openFile = fileChooser.getSelectedFile();
330 			if ("bin".equals(Utilities.getExtension(openFile)) || "la".equals(Utilities.getExtension(openFile)) || (fileChooser.getFileFilter() instanceof FreeEMSFileFilter)) {
331 				decoderInUse = new FreeEMSBin(openFile);
332 			} else {
333 				decoderInUse = new CSVTypeLog(openFile);
334 			}
335 
336 			if (openFile != null) {
337 				OpenLogViewer.getInstance().setTitle(APPLICATION_NAME + " - " + openFile.getName());
338 				saveApplicationWideProperty(NAME_OF_LAST_DIR_KEY, openFile.getParent());
339 				saveApplicationWideProperty(NAME_OF_LAST_FILE_KEY, openFile.getPath());
340 				saveApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS, fileChooser.getFileFilter().getClass().getCanonicalName());
341 			}
342 		}
343 	}
344 
345 	private String getApplicationWideProperty(final String key) {
346 		try {
347 			final Properties AppWide = new Properties();
348 			final File AppWideFile = openAppWideProps(AppWide);
349 			if (AppWideFile != null) {
350 				return AppWide.getProperty(key);
351 			} else {
352 				throw new IllegalArgumentException("Problem getting property, got null instead of file!");
353 			}
354 		} catch (IOException e) {
355 			e.printStackTrace();
356 			throw new RuntimeException("IO issue: " + e.getMessage(), e);
357 		}
358 	}
359 
360 	private void saveApplicationWideProperty(final String key, final String value) {
361 		try {
362 			final Properties AppWide = new Properties();
363 			final File AppWideFile = openAppWideProps(AppWide);
364 			if (AppWideFile != null) {
365 				AppWide.setProperty(key, value);
366 				AppWide.store(new FileOutputStream(AppWideFile), "saved");
367 			} else {
368 				throw new IllegalArgumentException("Problem saving property, got null instead of file!");
369 			}
370 		} catch (IOException e) {
371 			e.printStackTrace();
372 			throw new RuntimeException("Another IO issue: " + e.getMessage(), e);
373 		}
374 	}
375 
376 	private void removeApplicationWideProperty(final String key) {
377 		try {
378 			final Properties AppWide = new Properties();
379 			final File AppWideFile = openAppWideProps(AppWide);
380 			if (AppWideFile != null) {
381 				AppWide.remove(key);
382 				AppWide.store(new FileOutputStream(AppWideFile), "removed");
383 			} else {
384 				throw new IllegalArgumentException("Problem removing property, got null instead of file!");
385 			}
386 		} catch (IOException e) {
387 			e.printStackTrace();
388 			throw new RuntimeException("YAIO issue: " + e.getMessage(), e);
389 		}
390 	}
391 
392 	private File openAppWideProps(final Properties AppWide) throws IOException {
393 		File AppWideFile;
394 		AppWideFile = new File(System.getProperty("user.home"));
395 
396 		if (!AppWideFile.exists() || !AppWideFile.canRead() || !AppWideFile.canWrite()) {
397 			System.out.println("Either you dont have a home director, or it isnt read/writeable... fix it!");
398 		} else {
399 			AppWideFile = new File(AppWideFile, SETTINGS_DIRECTORY);
400 		}
401 
402 		if (!AppWideFile.exists()) {
403 			try {
404 				if (AppWideFile.mkdir()) {
405 					AppWideFile = new File(AppWideFile, PROPERTIES_FILENAME);
406 					if (AppWideFile.createNewFile()) {
407 						AppWide.load(new FileInputStream(AppWideFile));
408 					}
409 				} else {
410 					throw new RuntimeException("Failed to create directory, no code to handle this at this time.");
411 					// This should be passed up to the GUI as a dialog that tells you it can't do what it has to be able to...
412 				}
413 			} catch (IOException IOE) {
414 				System.out.print(IOE.getMessage());
415 			}
416 		} else {
417 			AppWideFile = new File(AppWideFile, PROPERTIES_FILENAME);
418 			if (!AppWideFile.createNewFile()) {
419 				AppWide.load(new FileInputStream(AppWideFile));
420 			}
421 		}
422 		return AppWideFile;
423 	}
424 
425 	public void enterFullScreen() {
426 		if (!fullscreen) {
427 			final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
428 			final GraphicsDevice[] gs = ge.getScreenDevices();
429 			final GraphicsDevice gd = gs[0];
430 
431 			if (gd.isFullScreenSupported()) {
432 				try {
433 					setJMenuBar(null);    // remove the menu bar for maximum space, load files in non fullscreen mode! :-p
434 					removeNotify();       // without this we can't do the next thing
435 					setUndecorated(true); // remove the window frame!
436 					addNotify();          // turn things back on again!
437 					setResizable(false);  // doesn't make sense and could be dangerous, according to oracle.
438 					gd.setFullScreenWindow(this);
439 					validate();           // required after rearranging component hierarchy
440 					fullscreen = true;    // remember so that we don't do random things when escape is pushed at other times...
441 				} catch (Exception e) {
442 					e.printStackTrace();
443 					System.out.println(labels.getObject(FAILED_TO_GO_FULLSCREEN_MESSAGE_KEY));
444 				}
445 			} else {
446 				System.out.println(labels.getObject(CANT_GO_FULLSCREEN_MESSAGE_KEY));
447 			}
448 		}
449 	}
450 
451 	public void exitFullScreen() {
452 		if (fullscreen) {
453 			final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
454 			final GraphicsDevice[] gs = ge.getScreenDevices();
455 			// Do the reverse of what we did to put it into full screen!
456 			gs[0].setFullScreenWindow(null);
457 			removeNotify();
458 			setUndecorated(false);
459 			addNotify();
460 			setJMenuBar(menuBar);
461 			setResizable(true);
462 			pack();
463 			fullscreen = false;
464 		}
465 	}
466 
467 	public void toggleFullScreen() {
468 		if (fullscreen) {
469 			exitFullScreen();
470 		} else {
471 			enterFullScreen();
472 		}
473 	}
474 
475 	public void setLog(final GenericLog genericLog) {
476 		graphingPanel.setLog(genericLog);
477 	}
478 
479 	/**
480 	 * Returns the reference to this instance, it is meant to be a method to make getting the main frame simpler
481 	 * @return <code>this</code> instance
482 	 */
483 	public static OpenLogViewer getInstance() {
484 		return mainAppRef;
485 	}
486 
487 	public EntireGraphingPanel getEntireGraphingPanel() {
488 		return graphingPanel;
489 	}
490 
491 	public MultiGraphLayeredPane getMultiGraphLayeredPane() {
492 		return graphingPanel.getMultiGraphLayeredPane();
493 	}
494 
495 	public OptionFrameV2 getOptionFrame() {
496 		return optionFrame;
497 	}
498 
499 	public PropertiesPane getPropertyPane() {
500 		return prefFrame;
501 	}
502 
503 	public List<SingleProperty> getProperties() {
504 		return properties;
505 	}
506 }