1   /**
2    * JHylaFax - A java client for HylaFAX.
3    *
4    * Copyright (C) 2005 by Steffen Pingel <steffenp@gmx.de>
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation; either version 2 of the License, or
9    * (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with this program; if not, write to the Free Software
18   * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19   */
20  package net.sf.jhylafax;
21  
22  import gnu.hylafax.HylaFAXClient;
23  import gnu.inet.ftp.ConnectionEvent;
24  import gnu.inet.ftp.ConnectionListener;
25  import gnu.inet.ftp.ServerResponseException;
26  
27  import java.awt.BorderLayout;
28  import java.awt.Font;
29  import java.awt.Image;
30  import java.awt.MenuItem;
31  import java.awt.event.ActionEvent;
32  import java.awt.event.KeyEvent;
33  import java.awt.event.WindowAdapter;
34  import java.awt.event.WindowEvent;
35  import java.beans.PropertyChangeEvent;
36  import java.beans.PropertyChangeListener;
37  import java.io.BufferedInputStream;
38  import java.io.BufferedOutputStream;
39  import java.io.File;
40  import java.io.FileNotFoundException;
41  import java.io.FileOutputStream;
42  import java.io.IOException;
43  import java.io.InputStream;
44  import java.lang.reflect.InvocationTargetException;
45  import java.net.URL;
46  import java.util.List;
47  import java.util.Locale;
48  import java.util.MissingResourceException;
49  import java.util.Properties;
50  
51  import javax.swing.Action;
52  import javax.swing.JComponent;
53  import javax.swing.JDialog;
54  import javax.swing.JFrame;
55  import javax.swing.JLabel;
56  import javax.swing.JMenu;
57  import javax.swing.JMenuBar;
58  import javax.swing.JMenuItem;
59  import javax.swing.JPanel;
60  import javax.swing.JTabbedPane;
61  import javax.swing.JTextArea;
62  import javax.swing.JToolBar;
63  import javax.swing.KeyStroke;
64  import javax.swing.SwingUtilities;
65  import javax.swing.UIManager;
66  import javax.swing.plaf.basic.BasicBorders;
67  
68  import net.sf.jhylafax.JobHelper.StatusResponse;
69  import net.sf.jhylafax.addressbook.AddressBook;
70  
71  import org.apache.commons.logging.Log;
72  import org.apache.commons.logging.LogFactory;
73  import org.apache.log4j.BasicConfigurator;
74  import org.apache.log4j.PropertyConfigurator;
75  import org.xnap.commons.gui.AboutDialog;
76  import org.xnap.commons.gui.Builder;
77  import org.xnap.commons.gui.Dialogs;
78  import org.xnap.commons.gui.ErrorDialog;
79  import org.xnap.commons.gui.action.AbstractXNapAction;
80  import org.xnap.commons.gui.factory.DefaultFactory;
81  import org.xnap.commons.gui.shortcut.ActionShortcut;
82  import org.xnap.commons.gui.util.GUIHelper;
83  import org.xnap.commons.gui.util.IconHelper;
84  import org.xnap.commons.gui.util.LabelUpdater;
85  import org.xnap.commons.gui.util.WhatsThisAction;
86  import org.xnap.commons.i18n.I18n;
87  import org.xnap.commons.i18n.I18nFactory;
88  import org.xnap.commons.i18n.I18nManager;
89  import org.xnap.commons.io.Job;
90  import org.xnap.commons.io.ProgressMonitor;
91  import org.xnap.commons.io.UserAbortException;
92  import org.xnap.commons.settings.SettingStore;
93  import org.xnap.commons.util.FileHelper;
94  
95  /**
96   * The main class, that provides the main window as well as the network connection.
97   * 
98   * @author Steffen Pingel
99   */
100 @SuppressWarnings("serial")
101 public class JHylaFAX extends JFrame implements LocaleChangeListener {
102 
103 	public final static I18n i18n = I18nFactory.getI18n(JHylaFAX.class);
104 	public final static String JDK14_LOG_CONFIG_KEY = "java.util.logging.config.file";
105 	public final static Locale[] SUPPORTED_LOCALES = { 
106 		Locale.ENGLISH,
107 		/*new Locale("bg"),*/
108         new Locale("ca"), 
109         new Locale("cs"), 
110         new Locale("de"),
111         new Locale("es"), 
112         new Locale("fr"), 
113         new Locale("it"),
114         /*new Locale("hu"),*/
115         new Locale("nl"), 
116         /* new Locale("ko"), */
117         new Locale("pl"), 
118         new Locale("pt", "BR"), 
119         new Locale("ru"), 
120         new Locale("sv"),
121         new Locale("tr"), 
122 	};
123 	private final static Log logger = LogFactory.getLog(JHylaFAX.class);
124 	private static final int BUFFER_SIZE = 1024 * 512;
125 	private static JHylaFAX app;
126 	private static String version;
127 	private JTabbedPane mainTabbedPane;
128 	private JMenu fileMenu;
129 	private SettingsDialogAction settingsDialogAction;
130 	private JPanel statusBarPanel;
131 	private JLabel statusLabel;
132 	private JLabel serverInfoLabel;
133 	private ExitAction exitAction;
134 	private UpdateStatusAction updateStatusAction;
135 	private HylaFAXClient connection;
136 	private JToolBar mainToolBar;
137 	private SendAction sendAction;
138 	private PollAction pollAction;
139 	private JMenu helpMenu;
140 	private AboutAction aboutAction;
141 	private String password;
142 	private String adminPassword;
143 	private ConnectionHandler connectionHandler = new ConnectionHandler();
144 	private ActionShortcut sendActionShortcut;
145 	private AddressBook addressBook;
146 	private AddressBookAction addressBookAction;
147 	private ActionShortcut updateStatusActionShortcut;
148 	private ReceiveQueuePanel recvqPanel;
149 	private JobQueuePanel sendqPanel;
150 	private JobQueuePanel pollqPanel;
151 	private JobQueuePanel doneqPanel;
152 	private DocumentQueuePanel docqPanel;
153 	private SettingsWizardAction settingsWizardAction;
154 	private JobQueue jobQueue = new JobQueue();
155 	private NotificationTimer notificationTimer = new NotificationTimer();
156 	private File addressBookFile;
157 	private Object connectionLock = new Object();
158 	private FaxTray tray;
159     
160 	public JHylaFAX() {
161 		app = this;
162 		
163 		try {
164 			Settings.load(getSettingsFile());
165 			// XXX load does not trigger a property change event, therefore
166 			// this needs to be updated manually
167 			Settings.DEFAULT_COMPLETION_MODE.updateMode();
168 		}
169 		catch (FileNotFoundException e) {
170 			logger.debug("Error loading settings", e);
171 			// ignore, probably the first time
172 		}
173 		catch (IOException e) {
174 			logger.debug("Error loading settings", e);
175 			ErrorDialog.showError(this, i18n.tr("Could not load settings"),
176 					i18n.tr("JHylaFAX Error"),
177 					e);
178 		}		
179 
180         updateResourceBundle();
181         I18nManager.getInstance().addLocaleChangeListener(new LabelUpdater());
182 	
183 		initializeToolkit();
184 		initialize();
185 		
186 		restoreLayout();
187 		
188 		PropertyChangeListener disconnector = new PropertyChangeListener() {
189 			public void propertyChange(PropertyChangeEvent evt) {
190 				if (connection != null) {
191 					try {
192 						connection.quit();
193 					}
194 					catch (Exception e) {
195 						logger.debug("Error closing connection", e);
196 					}
197 					connection = null;
198 				}
199 			}			
200 		};
201 		Settings.HOSTNAME.addPropertyChangeListener(disconnector);
202 		Settings.PORT.addPropertyChangeListener(disconnector);
203 		Settings.USE_PASSIVE.addPropertyChangeListener(disconnector);
204 		Settings.USERNAME.addPropertyChangeListener(disconnector);
205 		
206 		Settings.LOCALE.addPropertyChangeListener(new PropertyChangeListener() {
207 			public void propertyChange(PropertyChangeEvent evt) {
208 				updateResourceBundle();
209 			}
210 		});
211 		
212 		notificationTimer.settingsUpdated();
213 	}
214 	
215 	private void restoreLayout() {
216 		SettingStore store = new SettingStore(Settings.backstore);
217 		store.restoreWindow("window.main", 40, 40, 540, 400, this);
218 
219 		recvqPanel.restoreLayout(store, new String[] { "sender", "pages", "time", "filename", "filesize", "error" });
220 		sendqPanel.restoreLayout(store, new String[] { "id", "priority", "sender", "number", "dials", "pages", "error", "state" });
221 		pollqPanel.restoreLayout(store, new String[] { "id", "priority", "sender", "number", "dials", "pages", "error", "state" });
222 		doneqPanel.restoreLayout(store, new String[] { "id", "priority", "sender", "number", "dials", "pages", "error", "state" });
223 		docqPanel.restoreLayout(store, new String[] { "permissions", "owner", "modified", "filename", "filesize", "time" });
224 	}
225 	
226 	private void saveLayout() {
227 		SettingStore store = new SettingStore(Settings.backstore);
228 		store.saveWindow("window.main", this);
229 
230 		recvqPanel.saveLayout(store);
231 		sendqPanel.saveLayout(store);
232 		pollqPanel.saveLayout(store);
233 		doneqPanel.saveLayout(store);
234 		docqPanel.saveLayout(store);
235 	}
236 	
237 	private void updateResourceBundle() {
238 		try {
239 			I18nManager.getInstance().setDefaultLocale(Settings.LOCALE.getValue());
240         }
241 		catch (MissingResourceException e) {
242 			logger.warn("Error loading resource bundle", e);
243 		}
244 	}
245 
246 	public static void initializeToolkit()
247 	{
248 		// configure UI properties
249 		UIManager.put("swing.boldMetal", Boolean.FALSE);
250 		UIManager.put("SplitPaneDivider.border", 
251 					  new BasicBorders.MarginBorder());	
252 		UIManager.put("TitledBorder.font", UIManager.getFont("Label.font").deriveFont(Font.BOLD));
253 		UIManager.put("TitledBorder.titleColor", UIManager.getColor("Label.foreground").brighter());
254 		
255 		Builder.setProperty(DefaultFactory.ENHANCED_TEXT_FIELD_MENU_KEY, true);
256 	}
257 	
258 	private void initialize() {
259 		initializeIcon();
260 		initializeActions();
261 		initializeShortCuts();
262 		initializeMenuBar();
263 		initializeContent();
264 		initializeToolBar();
265 		initializeStatusBar();
266 		initializeSystemTray();
267 		
268 		updateLabels();		
269 		updateServerInfo();
270 	}
271 
272 	private void initializeIcon() {
273 		List<? extends Image> images = IconHelper.getApplicationIcons("kdeprintfax.png");
274 		if (images != null) {
275 			setIconImages(images);
276 		}
277 	}
278 
279 	private void initializeActions() {
280 		updateStatusAction = new UpdateStatusAction();
281 		settingsDialogAction = new SettingsDialogAction();
282 		settingsWizardAction = new SettingsWizardAction();
283 		addressBookAction = new AddressBookAction();
284 		exitAction = new ExitAction();
285 		sendAction = new SendAction();
286 		pollAction = new PollAction();
287 		aboutAction = new AboutAction();
288 	}
289 	
290 	private void initializeShortCuts() {
291 		sendActionShortcut = new ActionShortcut(sendAction);
292 		sendActionShortcut.setKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));
293 
294 		updateStatusActionShortcut = new ActionShortcut(updateStatusAction);
295 		updateStatusActionShortcut.setKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0));
296 	}
297 	
298 	private void initializeContent() {
299 		setTitle("JHylaFAX");
300 		setLayout(new BorderLayout());
301 		addWindowListener(new WindowAdapter() {
302 			public void windowClosing(WindowEvent event) {
303 				exit();
304 			}
305 		});
306 		
307 		mainTabbedPane = new JTabbedPane();
308 		mainTabbedPane.setBorder(GUIHelper.createEmptyBorder(10));
309 		getContentPane().add(mainTabbedPane, BorderLayout.CENTER);
310 
311 		recvqPanel = new ReceiveQueuePanel("recvq");
312 		mainTabbedPane.addTab("", IconHelper.getTabTitleIcon("folder_inbox.png"), recvqPanel);
313 		
314 		sendqPanel = new JobQueuePanel("sendq");
315 		mainTabbedPane.addTab("", IconHelper.getTabTitleIcon("folder_outbox.png"), sendqPanel);
316 
317 		doneqPanel = new JobQueuePanel("doneq");
318 		mainTabbedPane.addTab("", IconHelper.getTabTitleIcon("folder_sent_mail.png"), doneqPanel);
319 
320 		pollqPanel = new JobQueuePanel("pollq");
321 		if (Settings.SHOW_POLLQ.getValue()) {
322 			mainTabbedPane.addTab("", IconHelper.getTabTitleIcon("folder_print.png"), pollqPanel);
323 		}
324 
325 		docqPanel = new DocumentQueuePanel("docq");
326 		mainTabbedPane.addTab("", IconHelper.getTabTitleIcon("folder_txt.png"), docqPanel);
327 	}
328 	
329 	private void initializeSystemTray()	{
330 		tray = new FaxTray();
331 		
332 		if (tray.isSupported()) {
333 			tray.getPopupMenu().add(new MenuItem((String) exitAction.getValue(Action.NAME)));
334 		}
335 	}
336 
337 	public void exit()
338 	{
339 		if (addressBook != null) {
340 			try {
341 				addressBook.saveLayout(new SettingStore(Settings.backstore));
342 				addressBook.store(addressBookFile);
343 			}
344 			catch (Exception e) {
345 				logger.debug("Error storing addressbook", e);
346 				ErrorDialog.showError(JHylaFAX.this, 
347 						i18n.tr("Could not store addressbook"), 
348 						i18n.tr("JHylaFAX Error"), 
349 						e);					
350 			}
351 		}
352 		
353 		saveLayout();
354 		
355 		try {
356 			Settings.store(getSettingsFile());
357 		}
358 		catch (IOException e) {
359 			logger.debug("Error storing settings", e);
360 			ErrorDialog.showError(JHylaFAX.this, 
361 					i18n.tr("Could not store settings"), 
362 					i18n.tr("JHylaFAX Error"), 
363 					e);	
364 		}
365 		System.exit(0);
366 	}
367 
368 	private void initializeToolBar() {
369 		mainToolBar = new JToolBar();
370 		//mainToolBar.setBorderPainted(false);
371 		//mainToolBar.setRollover(true);
372 		getContentPane().add(mainToolBar, BorderLayout.NORTH);
373 		
374 		mainToolBar.add(Builder.createToolBarButton(sendAction));
375 		mainToolBar.addSeparator();
376 		mainToolBar.add(Builder.createToolBarButton(updateStatusAction));
377 		mainToolBar.addSeparator();
378 		mainToolBar.add(Builder.createToolBarButton(addressBookAction));
379 		mainToolBar.addSeparator();
380 		mainToolBar.add(Builder.createToolBarButton(settingsDialogAction));
381 	}
382 	
383 	private void initializeMenuBar() {
384 		JMenuBar menuBar = new JMenuBar();
385 		setJMenuBar(menuBar);
386 		
387 		fileMenu = new JMenu();
388 		menuBar.add(fileMenu);
389 		JComponent item = Builder.createMenuItem(sendAction);
390 		sendActionShortcut.setMenuItem((JMenuItem)item);
391 		fileMenu.add(item);
392 		fileMenu.add(Builder.createMenuItem(pollAction));
393 		fileMenu.addSeparator();
394 		item = Builder.createMenuItem(updateStatusAction);
395 		fileMenu.add(item);
396 		updateStatusActionShortcut.setMenuItem((JMenuItem)item);
397 		fileMenu.addSeparator();
398 		fileMenu.add(Builder.createMenuItem(settingsDialogAction));
399 		fileMenu.add(Builder.createMenuItem(settingsWizardAction));
400 		fileMenu.addSeparator();
401 		fileMenu.add(Builder.createMenuItem(addressBookAction));
402 		fileMenu.addSeparator();
403 		fileMenu.add(Builder.createMenuItem(exitAction));
404 		
405 		helpMenu = new JMenu();
406 		menuBar.add(helpMenu);
407 		helpMenu.add(Builder.createMenuItem(new WhatsThisAction()));
408 		helpMenu.addSeparator();
409 		helpMenu.add(Builder.createMenuItem(aboutAction));
410 	}
411 	
412 	private void initializeStatusBar() {
413 		statusBarPanel = new JPanel(new BorderLayout());
414 		getContentPane().add(statusBarPanel, BorderLayout.SOUTH);
415 		
416 		statusLabel = new JLabel();
417 		statusBarPanel.add(statusLabel, BorderLayout.CENTER);
418 
419 		serverInfoLabel = new JLabel();
420 		statusBarPanel.add(serverInfoLabel, BorderLayout.EAST);
421 	}
422 	
423 	public static JHylaFAX getInstance() {
424 		return app;
425 	}
426 		
427 	public HylaFAXClient getConnection(ProgressMonitor monitor) throws IOException, ServerResponseException {
428         synchronized (connectionLock) {
429             monitor.setText(i18n.tr("Connecting to {0}", Settings.HOSTNAME.getValue() + ":" + Settings.PORT.getValue()));
430 
431             if (connection == null) {
432                 connection = new HylaFAXClient();
433                 connection.addConnectionListener(connectionHandler);
434                 connection.setPassive(Settings.USE_PASSIVE.getValue());
435             }
436 
437             logger.info("Connecting to HylaFAX server at " + Settings.HOSTNAME.getValue() + ":" + Settings.PORT.getValue());
438             connection.open(Settings.HOSTNAME.getValue(), Settings.PORT.getValue());
439 
440             // authenticate until successful or aborted
441             if (password == null) {
442                 password = Settings.PASSWORD.getValue();
443             }
444             while (true) {
445                 if (connection.user(Settings.USERNAME.getValue())) {
446                     if (password.length() == 0) {
447                         password = requestPassword(monitor, false);
448                         if (password == null) {
449                             throw new UserAbortException();
450                         }
451                     }
452                     try {
453                         connection.pass(password);
454                     } catch (ServerResponseException e) {
455                         // wrong password, try again
456                         password = "";
457                         continue;
458                     }
459                 }
460                 break;
461             }
462 
463             // establish admin mode
464             if (adminPassword == null) {
465                 adminPassword = Settings.ADMIN_PASSWORD.getValue();
466             }
467             if (Settings.ADMIN_MODE.getValue()) {
468                 while (true) {
469                     if (adminPassword.length() == 0) {
470                         adminPassword = requestPassword(monitor, true);
471                         if (adminPassword == null) {
472                             // just skip the admin command
473                             break;
474                         }
475                     }
476                     try {
477                         connection.admin(adminPassword);
478                     } catch (ServerResponseException e) {
479                         // wrong password, try again
480                         adminPassword = "";
481                         continue;
482                     }
483                     break;
484                 }
485             }
486 
487             return connection;
488         }
489 	}
490 
491 	private String requestPassword(final ProgressMonitor monitor, final boolean admin) throws ServerResponseException {
492 		final String[] password = new String[1];
493 		Runnable runner = new Runnable() {
494 			public void run() {
495 				String host = Settings.USERNAME.getValue() + "@" 
496 					+ Settings.HOSTNAME.getValue();
497 				password[0] = Dialogs.requestPassword(monitor.getComponent(),
498 						(admin)
499 						? i18n.tr("Enter admin password for {0}:", host)
500 						: i18n.tr("Enter password for {0}:", host),
501 						i18n.tr("JHylaFAX Connection"));
502 			}
503 		};
504 		try {
505 			SwingUtilities.invokeAndWait(runner);
506 			return password[0];
507 		}
508 		catch (InterruptedException e) {
509 			logger.error("Unexpected exception while waiting for password", e);
510 			throw new ServerResponseException(i18n.tr("Abort by user."));
511 		}
512 		catch (InvocationTargetException e) {
513 			logger.error("Unexpected exception while waiting for password", e);
514 			throw new ServerResponseException(i18n.tr("Abort by user."));
515 		}
516 	}
517 	
518 	public static File getSettingsFile() throws IOException {
519 		return new File(FileHelper.getHomeDir("jhylafax"), "jhlafax.prefs");
520 	}
521 
522 	public static File getAddressBookFile() throws IOException {
523 		if (Settings.CUSTOMIZE_ADDRESS_BOOK_FILENAME.getValue()) {
524 			return new File(Settings.ADDRESS_BOOK_FILENAME.getValue());
525 		}
526 		else {
527 			return new File(FileHelper.getHomeDir("jhylafax"), 
528 					Settings.ADDRESS_BOOK_FILENAME.getDefaultValue());
529 		}
530 	}
531 
532 	public <T> T runJob(final Job<T> job) throws Exception {
533 		return jobQueue.runJob(null, job);
534 	}
535 
536 	public <T> T runJob(JDialog owner, final Job<T> job) throws Exception {
537 		return jobQueue.runJob(owner, job);
538 	}
539 
540 	public void updateServerInfo() {
541 		serverInfoLabel.setText(
542 				Settings.HOSTNAME.getValue() + ":" + Settings.PORT.getValue());
543 	}
544 	
545 	public void updateLabels() {
546 		fileMenu.setText(i18n.tr("File"));
547 		helpMenu.setText(i18n.tr("Help"));
548 		
549 		for (int i = 0; i < mainTabbedPane.getTabCount(); i++) {
550 			AbstractQueuePanel panel = (AbstractQueuePanel)mainTabbedPane.getComponent(i);
551 			panel.updateLabels();
552 			String queueName = panel.getQueueName();
553 			if (queueName.equals("recvq")) { mainTabbedPane.setTitleAt(i, i18n.tr("Received")); }
554 			else if (queueName.equals("sendq")) { mainTabbedPane.setTitleAt(i, i18n.tr("Sending")); }
555 			else if (queueName.equals("pollq")) { mainTabbedPane.setTitleAt(i, i18n.tr("Pollable")); }
556 			else if (queueName.equals("doneq")) { mainTabbedPane.setTitleAt(i, i18n.tr("Done")); }
557 			else if (queueName.equals("docq")) { mainTabbedPane.setTitleAt(i, i18n.tr("Documents")); }
558 		}
559 
560 		sendAction.putValue(Action.NAME, i18n.tr("Send Fax..."));
561 		sendAction.putValue(Action.SHORT_DESCRIPTION, i18n.tr("Opens a dialog for sending a fax"));
562 
563 		pollAction.putValue(Action.NAME, i18n.tr("Poll Fax..."));
564 		pollAction.putValue(Action.SHORT_DESCRIPTION, i18n.tr("Opens a dialog for polling a fax"));
565 
566 		addressBookAction.putValue(Action.NAME, i18n.tr("Address Book"));
567 
568 		updateStatusAction.putValue(Action.NAME, i18n.tr("Update Status"));
569 		updateStatusAction.putValue(Action.SHORT_DESCRIPTION, i18n.tr("Queries the status from the server"));
570 		
571 		settingsDialogAction.putValue(Action.NAME, i18n.tr("Settings..."));
572 		settingsDialogAction.putValue(Action.SHORT_DESCRIPTION, i18n.tr("Displays the settings dialog"));
573 		settingsDialogAction.updateLabels();
574 
575 		settingsWizardAction.putValue(Action.NAME, i18n.tr("Setup Wizard..."));
576 		settingsWizardAction.putValue(Action.SHORT_DESCRIPTION, i18n.tr("Displays the settings wizard"));
577 		settingsWizardAction.updateLabels();
578 		
579 		exitAction.putValue(Action.NAME, i18n.tr("Exit"));
580 		exitAction.putValue(Action.SHORT_DESCRIPTION, i18n.tr("Closes the application"));
581 
582 		aboutAction.putValue(Action.NAME, i18n.tr("About..."));
583 		aboutAction.putValue(Action.SHORT_DESCRIPTION, i18n.tr("Opens a dialog that displays funny information"));
584 
585 		GUIHelper.setMnemonics(getJMenuBar());
586 	}
587 	
588 	/**
589 	 * @param args
590 	 */
591 	public static void main(final String[] args) {
592 		final ArgsHandler handler = new ArgsHandler();
593 		handler.evaluate(args);
594 		
595 		evaluateArgumentsPreVisible(handler);
596 		
597 		ThreadGroup tg = new ThreadGroup("JHylaFAXThreadGroup") {
598 			public void uncaughtException(Thread t,Throwable e) {
599 				e.printStackTrace();
600 			}
601 		};
602 
603 //		System.setProperty("sun.awt.exception.handler", 
604 //						   "xnap.util.XNapAWTExceptionHandler");
605 		Thread mainRunner = new Thread(tg, "JHylaFAXMain") {
606 			public void run() {
607 				setContextClassLoader(JHylaFAX.class.getClassLoader());
608 
609 				JHylaFAX app = new JHylaFAX();
610 				app.setVisible(true);
611 				
612 				app.evaluateArgumentsPostVisible(handler);
613 			}
614 		};
615 		mainRunner.start();		
616 	}
617 
618 	private static void evaluateArgumentsPreVisible(ArgsHandler handler)
619 	{
620         // configure logging
621         URL url = JHylaFAX.class.getResource(handler.getLogConfigFilename());
622         if (url != null) {
623             PropertyConfigurator.configure(url);
624         } else {
625             System.err.println("Could not find logging configuration: "
626                     + handler.getLogConfigFilename());
627             BasicConfigurator.configure();
628         }
629 
630 //        InputStream in = ClassLoader.getSystemResourceAsStream(handler.getLogConfigFilename());
631 //		if (in != null) {
632 //			try {
633 //				LogManager.getLogManager().readConfiguration(in);
634 //			}
635 //			catch (IOException e) {
636 //				logger.warn("Error reading logging configuration", e);
637 //				try {
638 //					in.close();
639 //				} catch (IOException e1) {}
640 //			}
641 //		}
642 //		else {
643 //			logger.warn("Could not find logging configuration: " + handler.getLogConfigFilename());
644 //		}
645 	}
646 
647 	protected void evaluateArgumentsPostVisible(ArgsHandler handler)
648 	{
649 		if (!Settings.HAS_SEEN_WIZARD.getValue()) {
650 			SettingsWizard wizard = new SettingsWizard(JHylaFAX.this);
651 			wizard.setModal(true);
652 			wizard.setLocationRelativeTo(JHylaFAX.this);
653 			wizard.setVisible(true);
654 		}
655 		
656 		if (Settings.UPDATE_ON_STARTUP.getValue()) {
657 			updateTables();
658 		}
659 		
660 		String[] filenames = handler.getFilenames();
661 		String[] numbers = handler.getNumbers();
662 		File tempFile = null;
663 		
664 		if (handler.getReadStdin()) {
665 			tempFile = saveStdInToFile();
666 			if (tempFile != null) {
667 				// prepend path of temp file to filename array
668 				String[] newFilenames = new String[filenames.length + 1];
669 				newFilenames[0] = tempFile.getAbsolutePath();
670 				System.arraycopy(filenames, 0, newFilenames, 1, filenames.length);
671 				filenames = newFilenames;
672 			}
673 		}
674 
675 		if (filenames.length > 0 || numbers.length > 0) {
676 			SendDialog dialog = new SendDialog(this);
677 
678 			for (String number : numbers) {
679 				// FIXME should invoke addNumber()
680 				dialog.setNumber(number);
681 			}
682 			
683 			if (filenames.length > 0) {
684 				dialog.setDocument(filenames[0]);
685 				for (int i = 1; i < filenames.length; i++) {
686 					dialog.addDocument(filenames[i]);
687 				}
688 			}
689 			
690 			dialog.setQuitAfterSending(handler.getQuitAfterSending());
691 			dialog.setLocationRelativeTo(JHylaFAX.this);
692 			dialog.setVisible(true);
693 		}
694 	}
695 
696 	private File saveStdInToFile()
697 	{
698 		File tempFile;
699 		try {
700 			tempFile = File.createTempFile("jhylafax", null);
701 			tempFile.deleteOnExit();
702 		}
703 		catch (IOException e) {
704 			logger.debug("Error creating temporary file", e);
705 			ErrorDialog.showError(JHylaFAX.getInstance(), 
706 					i18n.tr("Error creating temporary file"),
707 					i18n.tr("JHylaFAX Error"),
708 					e);
709 			return null;				
710 		}
711 
712 		// TODO use FileHelper.copy() and display progress
713 		
714 		try {
715 	        BufferedInputStream in = new BufferedInputStream(System.in);
716 	        try {
717 		        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile));
718 		        try {
719 		        	byte[] buf = new byte[BUFFER_SIZE];
720 		        	while (true) {
721 		        		int bytesRead = in.read(buf);
722 		        		if (bytesRead <= 0) {
723 		        			break;
724 		        		}
725 		        		out.write(buf, 0, bytesRead);
726 		        	}
727 		        }
728 		        finally {
729 		        	out.close();
730 		        }
731 	        }
732 	        finally {
733 	        	in.close();
734 	        }
735 		}
736 		catch (IOException e) {
737 			logger.debug("Error creating temporary file", e);
738 			ErrorDialog.showError(JHylaFAX.getInstance(), 
739 					i18n.tr("Error creating temporary file"),
740 					i18n.tr("JHylaFAX Error"),
741 					e);
742 			return null;				
743 		}
744 		return tempFile;
745 	}
746 
747 	public void updateTables()
748 	{
749 		updateTables(JobHelper.updateStatus());
750 	}
751 	
752 	public void updateTables(StatusResponse response)
753 	{
754 		jobQueue.setLastUpdate(System.currentTimeMillis());
755 		if (response != null) {
756 			updateServerInfo();
757 			statusLabel.setText(response.status);
758 			statusLabel.setToolTipText("<html><pre><b>" + response.verboseStatus + "</b></pre>");
759 			recvqPanel.setData(response.recvq);
760 			sendqPanel.setData(response.sendq);
761 			pollqPanel.setData(response.pollq);
762 			doneqPanel.setData(response.doneq);
763 			docqPanel.setData(response.docq);
764 		}
765 	}
766 
767 	private class AboutAction extends AbstractXNapAction {
768 		
769 		private AboutDialog dialog;
770 		
771 		public AboutAction() {
772 			putValue(ICON_FILENAME, "jhylafax.png");
773 		}
774 
775 		public void actionPerformed(ActionEvent e)
776 		{
777 			if (dialog == null) {
778 				dialog = new AboutDialog();
779 				dialog.setTitle(i18n.tr("About JHylaFax {0}", getVersion()));
780 				dialog.setLocationRelativeTo(JHylaFAX.this);
781 				dialog.addHTMLTab(i18n.tr("General Information"), "about.html", true);
782 				JTextArea textArea = dialog.addTab(i18n.tr("License"), "LICENSE.jhylafax");
783 				textArea.setFont(new Font("Monospaced", Font.PLAIN, 10));
784 
785 				dialog.addHTMLTab(i18n.tr("3rd Party Software"), "LICENSE.other.html", true);
786 				
787 				dialog.getTabbedPane().setSelectedIndex(0);
788 			}
789 			
790 			if (!dialog.isVisible()) {
791 				dialog.setVisible(true);
792 			}
793 		}
794 	
795 	}
796 
797 	private class AddressBookAction extends AbstractXNapAction {
798 		
799 		public AddressBookAction() {
800 			putValue(ICON_FILENAME, "contents.png");
801 		}
802 
803 		public void actionPerformed(ActionEvent e)
804 		{
805 			getAddressBook().setVisible(true);
806 		}
807 	
808 	}
809 
810 	private class SendAction extends AbstractXNapAction {
811 		
812 		public SendAction() {
813 			putValue(ICON_FILENAME, "mail_new.png");
814 		}
815 
816 		public void actionPerformed(ActionEvent e)
817 		{
818 			SendDialog dialog = new SendDialog(JHylaFAX.this);
819 			dialog.setLocationRelativeTo(JHylaFAX.this);
820 			dialog.setVisible(true);
821 		}
822 	
823 	}
824 
825 	private class PollAction extends AbstractXNapAction {
826 		
827 		public PollAction() {
828 			putValue(ICON_FILENAME, "mail_get.png");
829 		}
830 
831 		public void actionPerformed(ActionEvent e)
832 		{
833 			PollDialog dialog = new PollDialog(JHylaFAX.this);
834 			dialog.setLocationRelativeTo(JHylaFAX.this);
835 			dialog.setVisible(true);
836 		}
837 	
838 	}
839 
840 	private class SettingsDialogAction extends AbstractXNapAction 
841 			implements LocaleChangeListener {
842 		
843 		private SettingsDialog dialog;
844 		
845 		public SettingsDialogAction() {
846 			putValue(ICON_FILENAME, "configure.png");
847 		}
848 
849 		public void updateLabels()
850 		{
851 			if (dialog != null) {
852 				dialog.updateLabels();
853 			}
854 		}
855 
856 		public void actionPerformed(ActionEvent e)
857 		{
858 			if (dialog == null) {
859 				dialog = new SettingsDialog(JHylaFAX.this);
860 				dialog.setLocationRelativeTo(JHylaFAX.this);
861 			}
862 			
863 			if (!dialog.isVisible()) {
864 				dialog.revert();
865 				dialog.setVisible(true);
866 			}
867 		}
868 		
869 	}
870 
871 	private class SettingsWizardAction extends AbstractXNapAction 
872 	implements LocaleChangeListener {
873 		
874 		public SettingsWizardAction() {
875 			putValue(ICON_FILENAME, "wizard.png");
876 		}
877 		
878 		public void updateLabels() {
879 		}
880 		
881 		public void actionPerformed(ActionEvent e)
882 		{
883 			SettingsWizard wizard = new SettingsWizard(JHylaFAX.this);
884 			wizard.setLocationRelativeTo(JHylaFAX.this);
885 			wizard.setVisible(true);
886 		}
887 		
888 	}
889 	
890 	private class ExitAction extends AbstractXNapAction {
891 		
892 		public ExitAction() {
893 			putValue(ICON_FILENAME, "exit.png");
894 		}
895 
896 		public void actionPerformed(ActionEvent e)
897 		{
898 			exit();
899 		}
900 		
901 	}
902 
903 	private class UpdateStatusAction extends AbstractXNapAction {
904 		
905 		public UpdateStatusAction() {
906 			putValue(ICON_FILENAME, "reload.png");
907 		}
908 
909 		public void actionPerformed(ActionEvent e) {
910 			updateTables();
911 		}
912 		
913 	}
914 
915 	private class ConnectionHandler implements ConnectionListener {
916 
917 		public void connectionOpened(ConnectionEvent event) {
918 			logger.info("Connected to "
919 					+ event.getRemoteInetAddress().getHostAddress() 
920 					+ ":" + event.getRemotePort());
921 		}
922 		
923 		public void connectionClosed(ConnectionEvent event) {
924 			logger.info("Disconnected from "
925 					+ event.getRemoteInetAddress().getHostAddress() 
926 					+ ":" + event.getRemotePort());
927 			connection.removeConnectionListener(this);
928 			connection = null;
929 		}
930 		
931 		public void connectionFailed(Exception e) {
932 			logger.info("Connection failed", e);
933 			connection.removeConnectionListener(this);
934 			connection = null;
935 		}
936 		
937 	}
938 
939 	public AddressBook getAddressBook()
940 	{
941 		if (addressBook == null) {
942 			addressBook = new AddressBook();
943 			//addressBook.setLocationRelativeTo(JHylaFAX.this);
944 			addressBook.restoreLayout(new SettingStore(Settings.backstore));
945 			
946 			try {
947 				addressBookFile = getAddressBookFile();
948 				if (addressBookFile.exists()) {
949 					addressBook.load(addressBookFile);
950 				}
951 			}
952 			catch (Exception e) {
953 				logger.debug("Error loading addressbook", e);
954 				ErrorDialog.showError(JHylaFAX.this, 
955 						i18n.tr("Could not load addressbook"), 
956 						i18n.tr("JHylaFAX Error"), 
957 						e);					
958 			}
959 		}
960 		return addressBook;
961 	}
962 
963 	public void showError(String message, Exception e)
964 	{
965 		ErrorDialog.showError(this,	message, i18n.tr("JHylaFAX Error"),	e);
966 	}
967 
968 	public void showError(String message)
969 	{
970 		Dialogs.showError(this,	message, i18n.tr("JHylaFAX Error"));
971 	}
972 
973 	public void resetAllTables() {
974 		recvqPanel.resetTable();
975 		sendqPanel.resetTable();
976 		pollqPanel.resetTable();
977 		doneqPanel.resetTable();
978 		docqPanel.resetTable();		
979 	}
980 
981 	public void runNotification(Notification notification)
982 	{
983 		jobQueue.addNotification(notification);
984 	}
985 
986 	public void settingsUpdated()
987 	{
988 		notificationTimer.settingsUpdated();
989 	}
990 
991 	public synchronized static String getVersion() {
992 		if (version == null) {
993 			try {
994 				InputStream in = JHylaFAX.class.getResourceAsStream("version.properties");
995 				try {
996 					Properties p = new Properties();
997 					p.load(in);
998 					version = p.getProperty("jhylafax.version", "");
999 				} finally {
1000 					in.close();
1001 				}
1002 			} catch (IOException e) {
1003 				logger.debug("Error loading version properties", e);
1004 			}
1005 		}
1006 		return version;
1007 	}
1008 
1009 }