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.EventQueue;
34  import java.awt.GraphicsDevice;
35  import java.awt.GraphicsEnvironment;
36  import java.awt.IllegalComponentStateException;
37  import java.awt.Point;
38  import java.awt.Rectangle;
39  import java.awt.Toolkit;
40  import java.awt.event.ActionEvent;
41  import java.awt.event.ActionListener;
42  import java.awt.event.WindowEvent;
43  import java.io.File;
44  import java.io.IOException;
45  import java.io.FileInputStream;
46  import java.io.FileOutputStream;
47  import java.util.List;
48  import java.util.ArrayList;
49  import java.util.Locale;
50  import java.util.Properties;
51  import java.util.ResourceBundle;
52  
53  import javax.swing.AbstractAction;
54  import javax.swing.Action;
55  import javax.swing.JComponent;
56  import javax.swing.JFileChooser;
57  import javax.swing.JFrame;
58  import javax.swing.JMenu;
59  import javax.swing.JMenuBar;
60  import javax.swing.JMenuItem;
61  import javax.swing.JOptionPane;
62  import javax.swing.JPanel;
63  import javax.swing.KeyStroke;
64  import javax.swing.UIManager;
65  import javax.swing.UnsupportedLookAndFeelException;
66  import javax.swing.filechooser.FileFilter;
67  
68  import org.diyefi.openlogviewer.decoder.AbstractDecoder;
69  import org.diyefi.openlogviewer.decoder.CSVTypeLog;
70  import org.diyefi.openlogviewer.decoder.FreeEMSBin;
71  import org.diyefi.openlogviewer.filefilters.CSVFileFilter;
72  import org.diyefi.openlogviewer.filefilters.FreeEMSBinFileFilter;
73  import org.diyefi.openlogviewer.filefilters.FreeEMSLAFileFilter;
74  import org.diyefi.openlogviewer.filefilters.LogFileFilter;
75  import org.diyefi.openlogviewer.filefilters.MSTypeFileFilter;
76  import org.diyefi.openlogviewer.filefilters.FreeEMSFileFilter;
77  import org.diyefi.openlogviewer.genericlog.GenericLog;
78  import org.diyefi.openlogviewer.graphing.EntireGraphingPanel;
79  import org.diyefi.openlogviewer.graphing.MultiGraphLayeredPane;
80  import org.diyefi.openlogviewer.optionpanel.OptionFrameV2;
81  import org.diyefi.openlogviewer.propertypanel.PropertiesPane;
82  import org.diyefi.openlogviewer.propertypanel.SingleProperty;
83  import org.diyefi.openlogviewer.subframes.AboutFrame;
84  import org.diyefi.openlogviewer.subframes.MacOSAboutHandler;
85  import org.diyefi.openlogviewer.utils.Utilities;
86  
87  public final class OpenLogViewer extends JFrame {
88  	public static final String NEWLINE = System.getProperty(Keys.LINE_SEPARATOR);
89  
90  	private static final long serialVersionUID = 1L;
91  
92  	private static final Properties buildInfo = new Properties();
93  
94  	private static final String APPLICATION_NAME = OpenLogViewer.class.getSimpleName();
95  	private static final String SETTINGS_DIRECTORY = "." + APPLICATION_NAME;
96  
97  	private static final String GIT_DESCRIBE_KEY = "git.commit.id.describe";
98  
99  	// TODO localise and refactor these:
100 	private static final String PROPERTIES_FILENAME = "OLVAllProperties.olv";
101 
102 	private static final String NAME_OF_LAST_FILE_KEY = "lastFingFile";
103 	private static final String NAME_OF_LAST_DIR_KEY = "lastFingDir";
104 	private static final String NAME_OF_LAST_CHOOSER_CLASS = "chooserClass";
105 
106 	// Real vars start here, many will probably get ripped out later
107 	private static final int GRAPH_PANEL_WIDTH = 600;
108 	private static final int GRAPH_PANEL_HEIGHT = 420;
109 
110 	private static final String OS_NAME = System.getProperty(Keys.OS_NAME);
111 	private static final boolean IS_MAC_OS_X = OS_NAME.contains("OS X"); // TN2110
112 	private static final boolean IS_WINDOWS = OS_NAME.contains("Windows");
113 	private static final boolean IS_LINUX = OS_NAME.contains("Linux");
114 
115 	private static OpenLogViewer mainAppRef;
116 	private static ResourceBundle labels;
117 
118 	private final String applicationTitle;
119 	private final String applicationVersion;
120 	private final EntireGraphingPanel graphingPanel;
121 	private final FooterPanel footerPanel;
122 	private final OptionFrameV2 optionFrame;
123 	private final PropertiesPane prefFrame;
124 
125 	private final List<SingleProperty> properties;
126 	private AbstractDecoder decoderInUse;
127 	private final JMenuBar menuBar;
128 	private boolean fullscreen;
129 
130 	private int extendedState;
131 	private Point location;
132 	private Dimension size;
133 	private int containingDevice;
134 
135 	public OpenLogViewer() {
136 		try {
137 			buildInfo.loadFromXML(getClass().getClassLoader().getResourceAsStream("build/buildInfo.xml"));
138 		} catch (IOException e) {
139 			System.out.println("Uh oh, looks like a hacked copy! UNSUPPORTED VERSION! DO NOT USE!");
140 		} finally {
141 			String preliminaryVersion = buildInfo.getProperty(GIT_DESCRIBE_KEY);
142 			if (preliminaryVersion == null || "".equals(preliminaryVersion.trim())) {
143 				System.out.println("Application version not found! UNSUPPORTED VERSION! DO NOT USE!");
144 				applicationVersion = "UNSUPPORTED VERSION! DO NOT USE!";
145 				buildInfo.setProperty(GIT_DESCRIBE_KEY, applicationVersion);
146 			} else {
147 				applicationVersion = buildInfo.getProperty(GIT_DESCRIBE_KEY);
148 				buildInfo.setProperty(GIT_DESCRIBE_KEY, applicationVersion);
149 			}
150 		}
151 
152 		applicationTitle = APPLICATION_NAME + " " + applicationVersion;
153 		buildInfo.setProperty("application.title", applicationTitle);
154 
155 		prefFrame = new PropertiesPane(labels, SETTINGS_DIRECTORY);
156 		properties = new ArrayList<SingleProperty>();
157 		prefFrame.setProperties(properties);
158 
159 		footerPanel = new FooterPanel(labels);
160 		optionFrame = new OptionFrameV2(labels);
161 		graphingPanel = new EntireGraphingPanel(labels);
162 		graphingPanel.setPreferredSize(new Dimension(GRAPH_PANEL_WIDTH, GRAPH_PANEL_HEIGHT));
163 
164 		setDefaultCloseOperation(EXIT_ON_CLOSE);
165 		setTitle(applicationTitle);
166 		setLayout(new BorderLayout());
167 		setFocusable(true);
168 
169 		final JPanel mainPanel = new JPanel();
170 		mainPanel.setLayout(new BorderLayout());
171 		mainPanel.add(graphingPanel, BorderLayout.CENTER);
172 		mainPanel.add(footerPanel, BorderLayout.SOUTH);
173 		add(mainPanel, BorderLayout.CENTER);
174 
175 		final JMenuItem openFileMenuItem = new JMenuItem(labels.getString(Text.FILE_MENU_ITEM_OPEN_NAME));
176 		openFileMenuItem.setName(Text.FILE_MENU_ITEM_OPEN_NAME);
177 		openFileMenuItem.addActionListener(new ActionListener() {
178 			@Override
179 			public void actionPerformed(final ActionEvent e) {
180 				openChosenFile();
181 			}
182 		});
183 
184 		final JMenuItem reloadFileMenuItem = new JMenuItem(labels.getString(Text.FILE_MENU_ITEM_RELOAD_NAME));
185 		reloadFileMenuItem.setName(Text.FILE_MENU_ITEM_RELOAD_NAME);
186 		reloadFileMenuItem.addActionListener(new ActionListener() {
187 			@Override
188 			public void actionPerformed(final ActionEvent e) {
189 				openLastFile();
190 			}
191 		});
192 
193 		final JMenuItem quitFileMenuItem = new JMenuItem(labels.getString(Text.FILE_MENU_ITEM_QUIT_NAME));
194 		quitFileMenuItem.setName(Text.FILE_MENU_ITEM_QUIT_NAME);
195 		quitFileMenuItem.addActionListener(new ActionListener() {
196 			@Override
197 			public void actionPerformed(final ActionEvent e) {
198 				OpenLogViewer.getInstance().quit();
199 			}
200 		});
201 
202 		final JMenuItem fullScreenViewMenuItem = new JMenuItem(labels.getString(Text.VIEW_MENU_ITEM_FULL_SCREEN_NAME));
203 		fullScreenViewMenuItem.setName(Text.VIEW_MENU_ITEM_FULL_SCREEN_NAME);
204 		fullScreenViewMenuItem.addActionListener(new ActionListener() {
205 			@Override
206 			public void actionPerformed(final ActionEvent e) {
207 				enterFullScreen();
208 			}
209 		});
210 
211 		final JMenuItem scaleAndColorViewMenuItem = new JMenuItem(labels.getString(Text.VIEW_MENU_ITEM_SCALE_AND_COLOR_NAME));
212 		scaleAndColorViewMenuItem.setName(Text.VIEW_MENU_ITEM_SCALE_AND_COLOR_NAME);
213 		scaleAndColorViewMenuItem.addActionListener(new ActionListener() {
214 			@Override
215 			public void actionPerformed(final ActionEvent e) {
216 				prefFrame.setVisible(true);
217 			}
218 		});
219 
220 		final JMenuItem fieldsAndDivisionsViewMenuItem = new JMenuItem(labels.getString(Text.VIEW_MENU_ITEM_FIELDS_AND_DIVISIONS_NAME));
221 		fieldsAndDivisionsViewMenuItem.setName(Text.VIEW_MENU_ITEM_FIELDS_AND_DIVISIONS_NAME);
222 		fieldsAndDivisionsViewMenuItem.addActionListener(new ActionListener() {
223 			@Override
224 			public void actionPerformed(final ActionEvent e) {
225 				optionFrame.setVisible(true);
226 			}
227 		});
228 
229 		final JMenuItem aboutMenuItem = new JMenuItem(labels.getString(Text.HELP_MENU_ITEM_ABOUT_NAME));
230 		aboutMenuItem.setName(Text.HELP_MENU_ITEM_ABOUT_NAME);
231 		aboutMenuItem.addActionListener(new ActionListener() {
232 			@Override
233 			public void actionPerformed(final ActionEvent e) {
234 				AboutFrame.show(buildInfo);
235 			}
236 		});
237 
238 		/*
239 		 * 13 January 2012 Had Chick-fil-A #1 meal with no pickle and Dr Pepper for lunch.
240 		 * 		Dr Pepper with no period. "Dude didn't even get his degree." Either that or he is British.
241 		 * 1 November 2011 Migrated Gufi's menu from the pointless class it's in to here.
242 		 * 22 October 2011 Left Gufi's menu in place for future dev's enjoyment.
243 		 *
244 		 * 5 February 2011 meal for the night DO NOT EDIT MENU!
245 		 * Sesame chicken alacarte
246 		 * chicken lo mein alacarte
247 		 * orange chicken x2
248 		 */
249 
250 		final JMenu fileMenu = new JMenu(labels.getString(Text.FILE_MENU_NAME));
251 		fileMenu.setName(Text.FILE_MENU_NAME);
252 		fileMenu.add(openFileMenuItem);
253 		fileMenu.add(reloadFileMenuItem);
254 		if (!IS_MAC_OS_X) {
255 			fileMenu.add(quitFileMenuItem);
256 		}
257 
258 		final JMenu viewMenu = new JMenu(labels.getString(Text.VIEW_MENU_NAME));
259 		viewMenu.setName(Text.VIEW_MENU_NAME);
260 		viewMenu.add(fullScreenViewMenuItem);
261 		viewMenu.add(scaleAndColorViewMenuItem);
262 		viewMenu.add(fieldsAndDivisionsViewMenuItem);
263 
264 		final JMenu helpMenu = new JMenu(labels.getString(Text.HELP_MENU_NAME));
265 		helpMenu.setName(Text.HELP_MENU_NAME);
266 		helpMenu.add(aboutMenuItem);
267 
268 		menuBar = new JMenuBar();
269 		menuBar.add(fileMenu);
270 		menuBar.add(viewMenu);
271 		if (IS_MAC_OS_X) {
272 			new MacOSAboutHandler(buildInfo);
273 		} else {
274 			menuBar.add(helpMenu);
275 		}
276 		setJMenuBar(menuBar);
277 
278 		//Listener stuff
279 		addComponentListener(graphingPanel);
280 		setupWindowKeyBindings(this);
281 
282 		setName(applicationTitle);
283 
284 		pack();
285 
286 		setVisible(true);
287 	}
288 
289 	/**
290 	 * The entry point of OLV!
291 	 *
292 	 * @param args the command line arguments
293 	 */
294 	public static void main(final String[] args) {
295 		EventQueue.invokeLater(new Runnable() {
296 
297 			@Override
298 			public void run() {
299 				final Locale currentLocale = Locale.getDefault();
300 
301 				labels = ResourceBundle.getBundle(OpenLogViewer.class.getPackage().getName() + ".Labels", currentLocale);
302 
303 				final String lookAndFeel;
304 				final String systemLookAndFeel = UIManager.getSystemLookAndFeelClassName();
305 				if (IS_MAC_OS_X) {
306 					System.setProperty(Keys.APPLE_LAF_USE_SCREEN_MENU_BAR, Boolean.TRUE.toString());
307 				}
308 				lookAndFeel = systemLookAndFeel;
309 
310 				try {
311 					UIManager.setLookAndFeel(lookAndFeel);
312 				} catch (UnsupportedLookAndFeelException e) {
313 					e.printStackTrace();
314 					System.out.println(labels.getString(Text.LOOK_AND_FEEL_EXCEPTION_MESSAGE_ONE));
315 				} catch (ClassNotFoundException e) {
316 					e.printStackTrace();
317 					System.out.println(labels.getString(Text.LOOK_AND_FEEL_EXCEPTION_MESSAGE_TWO));
318 				} catch (InstantiationException e) {
319 					e.printStackTrace();
320 					System.out.println(labels.getString(Text.LOOK_AND_FEEL_EXCEPTION_MESSAGE_THREE));
321 				} catch (IllegalAccessException e) {
322 					e.printStackTrace();
323 					System.out.println(labels.getString(Text.LOOK_AND_FEEL_EXCEPTION_MESSAGE_FOUR));
324 				}
325 
326 				mainAppRef = new OpenLogViewer();
327 
328 				if (args.length > 0) {
329 					final File toOpen = new File(args[0]).getAbsoluteFile();
330 					if (toOpen.exists() && toOpen.isFile()) {
331 						if (args.length > 1) {
332 							System.out.println(args.length + labels.getString(Text.TOO_MANY_ARGUMENTS) + args[0]);
333 						} else {
334 							System.out.println(labels.getString(Text.ATTEMPTING_TO_OPEN_FILE) + args[0]);
335 						}
336 						final FileFilter ms = new MSTypeFileFilter(labels);
337 						final FileFilter fe = new FreeEMSFileFilter(labels);
338 						if (fe.accept(toOpen) || ms.accept(toOpen)) {
339 							mainAppRef.openFile(toOpen, mainAppRef.generateChooser());
340 						} else {
341 							System.out.println(labels.getString(Text.FILE_TYPE_NOT_SUPPORTED) + args[0]);
342 							mainAppRef.quit();
343 						}
344 					} else {
345 						System.out.println(labels.getString(Text.FILE_ARGUMENT_NOT_GOOD) + args[0]);
346 						mainAppRef.quit();
347 					}
348 				}
349 			}
350 		});
351 	}
352 
353 	public void quit() {
354 		final WindowEvent wev = new WindowEvent(this, WindowEvent.WINDOW_CLOSING);
355 		Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev);
356 	}
357 
358 	public void openChosenFile() {
359 		final JFileChooser fileChooser = generateChooser();
360 		final int acceptValue = fileChooser.showOpenDialog(this);
361 		if (acceptValue == JFileChooser.APPROVE_OPTION) {
362 			final File fileToOpen = fileChooser.getSelectedFile();
363 			if (!openFile(fileToOpen, fileChooser)) {
364 				JOptionPane.showMessageDialog(mainAppRef, labels.getObject(Text.OPEN_FILE_ERROR_MESSAGE)
365 						+ NEWLINE + fileToOpen.getAbsolutePath(),
366 						labels.getString(Text.OPEN_FILE_ERROR_TITLE),
367 						JOptionPane.ERROR_MESSAGE);
368 			}
369 		}
370 	}
371 
372 	public void openLastFile() {
373 		final String lastFingFile = getApplicationWideProperty(NAME_OF_LAST_FILE_KEY);
374 		final String chooserClass = getApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS);
375 		if (chooserClass != null && lastFingFile != null) {
376 			final File fileToOpen = new File(lastFingFile);
377 			final JFileChooser fileChooser = generateChooser();
378 			if (!openFile(fileToOpen, fileChooser)) {
379 				JOptionPane.showMessageDialog(mainAppRef, labels.getObject(Text.OPEN_LAST_FILE_ERROR_MESSAGE)
380 						+ NEWLINE + fileToOpen.getAbsolutePath(),
381 						labels.getString(Text.OPEN_LAST_FILE_ERROR_TITLE),
382 						JOptionPane.ERROR_MESSAGE);
383 			}
384 		} else {
385 			JOptionPane.showMessageDialog(mainAppRef, labels.getObject(Text.OPEN_LAST_FILE_MISSING_PROPERTY_MESSAGE),
386 					labels.getString(Text.OPEN_LAST_FILE_MISSING_PROPERTY_TITLE),
387 					JOptionPane.ERROR_MESSAGE);
388 		}
389 	}
390 
391 	public boolean openFile(final File fileToOpen, final JFileChooser fileChooser) {
392 		if (fileToOpen.exists()) {
393 			if (decoderInUse != null) {
394 				// 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!
395 				final GenericLog logInUse = decoderInUse.getDecodedLog();
396 				if (logInUse != null) {
397 					logInUse.clearOut(); // This is the wrong approach. The correct approach is to reuse the object, try that next...
398 				}
399 				decoderInUse = null;
400 				setLog(null);
401 			} // else haven't read in a log yet.
402 
403 			setTitle(applicationTitle + " - " + fileToOpen.getName());
404 			saveApplicationWideProperty(NAME_OF_LAST_DIR_KEY, fileToOpen.getParent());
405 			saveApplicationWideProperty(NAME_OF_LAST_FILE_KEY, fileToOpen.getPath());
406 			saveApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS, fileChooser.getFileFilter().getClass().getCanonicalName());
407 
408 			if (FileExtensions.BIN.equals(Utilities.getExtension(fileToOpen))
409 					|| FileExtensions.LA.equals(Utilities.getExtension(fileToOpen))
410 					|| (fileChooser.getFileFilter() instanceof FreeEMSFileFilter)) {
411 				decoderInUse = new FreeEMSBin(fileToOpen, labels);
412 			} else {
413 				decoderInUse = new CSVTypeLog(fileToOpen, labels);
414 			}
415 			return true;
416 		} else {
417 			setTitle(applicationTitle);
418 			return false;
419 		}
420 	}
421 
422 	public JFileChooser generateChooser() {
423 		final JFileChooser fileChooser = new JFileChooser();
424 		final String lastFingFile = getApplicationWideProperty(NAME_OF_LAST_FILE_KEY);
425 
426 		if (lastFingFile != null) {
427 			fileChooser.setSelectedFile(new File(lastFingFile));
428 		} else {
429 			final String lastFingDir = getApplicationWideProperty(NAME_OF_LAST_DIR_KEY);
430 			if (lastFingDir != null) {
431 				fileChooser.setCurrentDirectory(new File(lastFingDir));
432 			}
433 		}
434 
435 		fileChooser.addChoosableFileFilter(new FreeEMSFileFilter(labels));
436 		fileChooser.addChoosableFileFilter(new FreeEMSBinFileFilter());
437 		fileChooser.addChoosableFileFilter(new FreeEMSLAFileFilter());
438 		fileChooser.addChoosableFileFilter(new CSVFileFilter());
439 		fileChooser.addChoosableFileFilter(new LogFileFilter());
440 		fileChooser.addChoosableFileFilter(new MSTypeFileFilter(labels));
441 
442 		final String chooserClass = getApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS);
443 
444 		if (chooserClass != null) {
445 			try {
446 				final FileFilter[] existingFilters = fileChooser.getChoosableFileFilters();
447 				boolean alreadyHasSavedFilter = false;
448 				for (int i = 0; i < existingFilters.length; i++) {
449 					final String thisFilter = existingFilters[i].getClass().getCanonicalName();
450 					if (thisFilter.equals(chooserClass)) {
451 						alreadyHasSavedFilter = true;
452 						fileChooser.setFileFilter(existingFilters[i]); // If set to a new instance the list will contain two!
453 					}
454 				}
455 
456 				// 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...
457 				if (!alreadyHasSavedFilter) {
458 					final FileFilter savedFilter = (FileFilter) Class.forName(chooserClass).newInstance();
459 					fileChooser.setFileFilter(savedFilter);
460 				}
461 			} catch (ClassNotFoundException c) {
462 				removeApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS);
463 				System.out.println(labels.getString(Text.CLASS_NOT_FOUND) + NAME_OF_LAST_CHOOSER_CLASS + labels.getString(Text.REMOVED_FROM_PROPS));
464 			} catch (InstantiationException i) {
465 				removeApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS);
466 				System.out.println(labels.getString(Text.COULD_NOT_INSTANTIATE_CLASS) + NAME_OF_LAST_CHOOSER_CLASS + labels.getString(Text.REMOVED_FROM_PROPS));
467 			} catch (IllegalAccessException l) {
468 				removeApplicationWideProperty(NAME_OF_LAST_CHOOSER_CLASS);
469 				System.out.println(labels.getString(Text.COULD_NOT_ACCESS_CLASS) + NAME_OF_LAST_CHOOSER_CLASS + labels.getString(Text.REMOVED_FROM_PROPS));
470 			}
471 		}
472 		return fileChooser;
473 	}
474 
475 	private String getApplicationWideProperty(final String key) {
476 		final Properties appWide = new Properties();
477 		openAppWideProps(appWide);
478 		return appWide.getProperty(key);
479 	}
480 
481 	private void saveApplicationWideProperty(final String key, final String value) {
482 		FileOutputStream fos = null;
483 		try {
484 			final Properties appWide = new Properties();
485 			final File appWideFile = openAppWideProps(appWide);
486 			appWide.setProperty(key, value);
487 			fos = new FileOutputStream(appWideFile);
488 			appWide.store(fos, "saved");
489 		} catch (IOException e) {
490 			e.printStackTrace();
491 			throw new RuntimeException(labels.getString(Text.IO_ISSUE_SAVING_PROPERTY) + e.getMessage(), e);
492 		} finally {
493 			try {
494 				if (fos != null) {
495 					fos.close();
496 				}
497 			} catch (IOException ioe) {
498 				ioe.printStackTrace();
499 			}
500 		}
501 	}
502 
503 	private void removeApplicationWideProperty(final String key) {
504 		FileOutputStream fos = null;
505 		try {
506 			final Properties appWide = new Properties();
507 			final File appWideFile = openAppWideProps(appWide);
508 			appWide.remove(key);
509 			fos = new FileOutputStream(appWideFile);
510 			appWide.store(fos, "removed");
511 		} catch (IOException e) {
512 			e.printStackTrace();
513 			throw new RuntimeException(labels.getString(Text.IO_ISSUE_REMOVING_PROPERTY) + e.getMessage(), e);
514 		} finally {
515 			try {
516 				if (fos != null) {
517 					fos.close();
518 				}
519 			} catch (IOException ioe) {
520 				ioe.printStackTrace();
521 			}
522 		}
523 	}
524 
525 	private File openAppWideProps(final Properties appWide) {
526 		File appWideFile;
527 		appWideFile = new File(System.getProperty(Keys.USER_HOME));
528 
529 		if (!appWideFile.exists() || !appWideFile.canRead() || !appWideFile.canWrite()) {
530 			System.out.println(labels.getString(Text.HOME_DIRECTORY_NOT_ACCESSIBLE));
531 		} else {
532 			appWideFile = new File(appWideFile, SETTINGS_DIRECTORY);
533 		}
534 
535 		if (!appWideFile.exists()) {
536 			FileInputStream fis = null;
537 			try {
538 				if (appWideFile.mkdir()) {
539 					appWideFile = new File(appWideFile, PROPERTIES_FILENAME);
540 					if (appWideFile.createNewFile()) {
541 						fis = new FileInputStream(appWideFile);
542 						appWide.load(fis);
543 					}
544 				} else {
545 					throw new RuntimeException(labels.getString(Text.FAILED_TO_CREATE_DIRECTORY_MESSAGE));
546 					// 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...
547 				}
548 			} catch (IOException e) {
549 				System.out.print(e.getMessage());
550 			} finally {
551 				try {
552 					if (fis != null) {
553 						fis.close();
554 					}
555 				} catch (IOException ioe) {
556 					ioe.printStackTrace();
557 				}
558 			}
559 		} else {
560 			appWideFile = new File(appWideFile, PROPERTIES_FILENAME);
561 			FileInputStream fis = null;
562 			try {
563 				if (!appWideFile.createNewFile()) {
564 					fis = new FileInputStream(appWideFile);
565 					appWide.load(fis);
566 				}
567 			} catch (IOException ioe) {
568 				ioe.printStackTrace();
569 			} finally {
570 				try {
571 					if (fis != null) {
572 						fis.close();
573 					}
574 				} catch (IOException ioe) {
575 					ioe.printStackTrace();
576 				}
577 			}
578 		}
579 		return appWideFile;
580 	}
581 
582 	public static void setupWindowKeyBindings(final JFrame window) {
583 		final Action closeWindow = new AbstractAction() {
584 			private static final long serialVersionUID = 1L;
585 			public void actionPerformed(final ActionEvent e) {
586 				final WindowEvent wev = new WindowEvent(window, WindowEvent.WINDOW_CLOSING);
587 				Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev);
588 			}
589 		};
590 
591 		boolean isMainApp = false;
592 		if (window instanceof OpenLogViewer) {
593 			isMainApp = true;
594 		}
595 
596 		// Close any window
597 		if (IS_WINDOWS || IS_LINUX) {
598 			window.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(Keys.CONTROL_W), Keys.CLOSE_WINDOW);
599 		} else if (IS_MAC_OS_X) {
600 			window.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(Keys.COMMAND_W), Keys.CLOSE_WINDOW);
601 		}
602 
603 		// Just close the main app window
604 		if (IS_LINUX && isMainApp) {
605 			window.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(Keys.CONTROL_Q), Keys.CLOSE_WINDOW);
606 		}
607 
608 		window.getRootPane().getActionMap().put(Keys.CLOSE_WINDOW, closeWindow);
609 	}
610 
611 	public void enterFullScreen() {
612 		if (!fullscreen) {
613 			final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
614 			final GraphicsDevice[] device = ge.getScreenDevices();
615 
616 			for (int i = 0; i < device.length; i++) { // Cycle through available devices (monitors) looking for device that has center of app
617 				final Rectangle bounds = device[i].getDefaultConfiguration().getBounds();
618 				final int centerX = (int) Math.round(getBounds().getCenterX());
619 				final int centerY = (int) Math.round(getBounds().getCenterY());
620 				final Point center = new Point(centerX, centerY);
621 				if (bounds.contains(center)) { // Found the device (monitor) that contains the center of the app
622 					containingDevice = i;
623 					if (device[containingDevice].isFullScreenSupported()) {
624 						try {
625 							fullscreen = true;      // Remember so that we can react accordingly.
626 							saveScreenState();      // Save the current state of things to restore later when exiting fullscreen mode.
627 							setVisible(false);      // Hide how the sausage is made!
628 							setJMenuBar(null);      // Remove the menu bar for maximum space, load files with the buttons?
629 							dispose();              // Make the JFrame undisplayable so setUndecorated(true) will work!
630 							setUndecorated(true);   // Remove the window frame/bezel!
631 							setVisible(true);       // Make the JFrame displayable again!
632 //							setResizable(false);    // Fred: doesn't make sense and could be dangerous, according to oracle.
633 							                        // Ben: Removed setResizable(false) because it causes GNOME menu bar
634 							                        // and GNOME task bar to show in front of the app!
635 							device[containingDevice].setFullScreenWindow(this);
636 							validate();             // Required after rearranging component hierarchy
637 							toFront();              // Might as well
638 							requestFocusInWindow(); // Put keyboard focus here so toggling fullscreen works
639 							graphingPanel.moveGraphDueToResize(); // Done so centering still works on Mac
640 						} catch (IllegalComponentStateException e) {
641 							e.printStackTrace();
642 							System.out.println(labels.getString(Text.FAILED_TO_GO_FULLSCREEN_MESSAGE));
643 							fullscreen = false;
644 						}
645 					} else {
646 						System.out.println(labels.getString(Text.CANT_GO_FULLSCREEN_MESSAGE));
647 					}
648 				}
649 			}
650 		}
651 	}
652 
653 	public void exitFullScreen() {
654 		if (fullscreen) {
655 			fullscreen = false;
656 			final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
657 			final GraphicsDevice[] device = ge.getScreenDevices();
658 			// Do the reverse of what we did to put it into full screen!
659 			device[containingDevice].setFullScreenWindow(null); // Exit full screen
660 			dispose();              // Make the JFrame undisplayable so setUndecorated(false) will work
661 			setUndecorated(false);  // Restore the window frame/bezel
662 			setJMenuBar(menuBar);   // Remove the menu bar
663 			validate();             // Required after rearranging component hierarchy
664 			restoreScreenState();   // Size and place the window where it was before
665 			setVisible(true);       // Make the JFrame displayable again
666 			requestFocusInWindow(); // Put keyboard focus here so toggling fullscreen works
667 			graphingPanel.moveGraphDueToResize(); // Done so centering still works on Mac
668 		}
669 	}
670 
671 	public void toggleFullScreen() {
672 		if (fullscreen) {
673 			exitFullScreen();
674 		} else {
675 			enterFullScreen();
676 		}
677 	}
678 
679 	private void saveScreenState() {
680 		extendedState = getExtendedState();
681 		location = getLocation();
682 		size = getSize();
683 	}
684 
685 	private void restoreScreenState() {
686 		setExtendedState(extendedState);
687 		setLocation(location);
688 		setSize(size);
689 	}
690 
691 	public void setLog(final GenericLog genericLog) {
692 		graphingPanel.setLog(genericLog);
693 	}
694 
695 	public void defaultBrowserNotFound() {
696 		final Object message = labels.getObject(Text.DEFAULT_BROWSER_ERROR_MESSAGE);
697 		final String title = labels.getString(Text.DEFAULT_BROWSER_ERROR_TITLE);
698 		JOptionPane.showMessageDialog(mainAppRef, message, title, JOptionPane.ERROR_MESSAGE); // DIRTY
699 	}
700 
701 	// All of the references below are indicators of bad design, marking with DIRTY:
702 
703 	/**
704 	 * Returns the reference to this instance, it is meant to be a method to make getting the main frame simpler
705 	 * @return <code>this</code> instance
706 	 */
707 	public static OpenLogViewer getInstance() {
708 		return mainAppRef;
709 	}
710 
711 	public NavBarPanel getNavBarPanel() {
712 		return footerPanel.getNavBarPanel();
713 	}
714 
715 	public EntireGraphingPanel getEntireGraphingPanel() {
716 		return graphingPanel;
717 	}
718 
719 	public MultiGraphLayeredPane getMultiGraphLayeredPane() {
720 		return graphingPanel.getMultiGraphLayeredPane();
721 	}
722 
723 	public OptionFrameV2 getOptionFrame() {
724 		return optionFrame;
725 	}
726 
727 	public PropertiesPane getPropertyPane() {
728 		return prefFrame;
729 	}
730 
731 	public List<SingleProperty> getProperties() {
732 		return properties;
733 	}
734 }