View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
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.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  package org.xnap.gui;
21  
22  import java.awt.BorderLayout;
23  import java.awt.Component;
24  import java.awt.event.WindowAdapter;
25  import java.awt.event.WindowEvent;
26  import java.beans.PropertyChangeEvent;
27  import java.beans.PropertyChangeListener;
28  import java.io.*;
29  
30  import javax.help.HelpBroker;
31  import javax.swing.Action;
32  import javax.swing.*;
33  import javax.swing.ImageIcon;
34  import javax.swing.JComponent;
35  import javax.swing.JFrame;
36  import javax.swing.JLabel;
37  import javax.swing.JSplitPane;
38  import javax.swing.JTabbedPane;
39  import javax.swing.JToolBar;
40  import javax.swing.SwingUtilities;
41  import javax.swing.ToolTipManager;
42  import javax.swing.UIManager;
43  import javax.swing.WindowConstants;
44  import javax.swing.event.ChangeEvent;
45  import javax.swing.event.ChangeListener;
46  
47  import org.apache.log4j.Logger;
48  import org.xnap.XNap;
49  import org.xnap.action.ToggleAction;
50  import org.xnap.gui.component.ToggleableIconPane;
51  import org.xnap.gui.component.XNapToolBarButton;
52  import org.xnap.gui.component.XNapToolBarToggleButton;
53  import org.xnap.gui.theme.ThemeManager;
54  import org.xnap.gui.util.HelpManager;
55  import org.xnap.gui.util.*;
56  import org.xnap.io.Library;
57  import org.xnap.plugin.PluginManager;
58  import org.xnap.util.Preferences;
59  import org.xnap.util.SystemHelper;
60  import org.xnap.util.Updater;
61  
62  import ziga.gui.WindowsDesktopIndicator;
63  
64  public class XNapFrame extends JFrame 
65      implements ChangeListener, PropertyChangeListener
66  {
67  
68      //--- Constant(s) ---
69  
70      //--- Data field(s) ---
71  
72      private static Logger logger = Logger.getLogger(XNapFrame.class);
73      private static Preferences prefs = Preferences.getInstance();
74      private static XNapFrame singleton = null;
75  
76      private ToggleableIconPane pane;
77  	private JSplitPane jspV;
78  
79      private MainToolBar jtbMain;
80  
81      private ChatPanel jpChat;
82      private HotlistPanel jpHotlist;
83      private LibraryPanel jpLibrary;
84      private SearchPanel jpSearch;
85      private TransferPanel jpTransfer;
86  
87      private WindowsDesktopIndicator wdi;
88  	
89  	//private HotlistDockAction acDockHotlist;
90      
91      //--- Constructor(s) ---
92  
93      public XNapFrame() 
94      {
95  		singleton = this;
96  		setContentPane(new JPanel());
97  
98  		// install support for system tray icon on Windows
99  		try {
100 			SplashWindow.incProgress(1, XNap.tr("Enabling Window system tray icon"));
101 
102 			if (SystemHelper.isZigaDllLoaded) {
103 				wdi = new WindowsDesktopIndicator(this);
104 				wdi.show("xnap.ico", XNap.tr("XNap"));
105 				wdi.hide();
106 			}
107 		}
108 		catch (Exception e) {
109 			logger.warn("Could not enable system tray through ziga.dll", e);
110 		}
111 
112 		// register native mac os x support for application menu
113 		if (SystemHelper.isMacOSX) {
114 			SplashWindow.incProgress(1, XNap.tr("Registering Mac OS handler"));
115 
116 			try {
117 				org.xnap.platform.macos.MacOS141Handler.register();
118 			}
119 			catch (Exception e) {
120 				logger.warn("Could not register MacOSX 1.4.1 handler", e);
121 
122 				try {
123 					org.xnap.platform.macos.MacOSHandler.register();
124 				}
125 				catch (Exception e2) {
126 					logger.warn("could not register MacOSX handler", e2);
127 				}
128 			}
129 		}
130 
131 		// swing initialization
132 		ToolTipManager.sharedInstance().setDismissDelay(60 * 1000);
133 
134         setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
135 
136         // moved to XNap.java
137 //		SplashWindow.incProgress(5, XNap.tr("Setting up Look & Feel"));
138 //		ThemeManager.initialize();
139 //        updateLookAndFeel();
140 
141 		// wizard
142 		if (!prefs.getSeenStartupWizard()) {
143 			SplashWindow.incProgress(5, XNap.tr("Loading Wizard"));
144 			StartupWizardDialog.showDialog(this, true);
145 		}
146 
147 		initialize();
148 
149 		SplashWindow.incProgress(5, XNap.tr("Enabling plugins"));
150 
151 		PluginManager.getInstance().guiStarted();
152 		Updater.guiStarted();
153 
154 		// show update notification
155 //  		if (prefs.getAutoVersionCheck() && prefs.shouldCheckForUpdate(7)) {
156 //  			UpdateDialogAction.doit(false);
157 //  		}
158 
159 		SplashWindow.setProgress(100, XNap.tr("Done"));
160     }
161 
162     //--- Method(s) ---
163 
164     private void initialize() 
165     {
166 
167 		SplashWindow.incProgress(5, XNap.tr("Setting up Toolbars"));
168 		initializeToolbars();
169 
170 		// the toolbars need to be setup before the menu for the 
171 		// Show...ToolBar actions to work
172 		SplashWindow.incProgress(5, XNap.tr("Setting up Menu"));
173 		initializeMenu();
174 
175 		SplashWindow.incProgress(5, XNap.tr("Setting up GUI"));
176 		initializePanels();
177 
178 		SplashWindow.incProgress(5, XNap.tr("Setting up Status Bar"));
179 		initializeStatusBar();
180 
181 		SplashWindow.incProgress(5, XNap.tr("Setting up Application"));
182         setTitle(XNap.tr("XNap - Java Peer-To-Peer Client"));
183 
184 		ImageIcon icon = IconHelper.getImage("16/xnap.png");
185 		if (icon != null) {
186 			setIconImage(icon.getImage());
187 		}
188 
189 		SplashWindow.incProgress(5, XNap.tr("Setting up Hotlist"));
190 
191 		jpHotlist.add(new UploadSettingsPanel(), BorderLayout.SOUTH);		
192 
193 		jspV = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
194 		jspV.setBorder(GUIHelper.createEmptyBorder());
195 		jspV.setDividerLocation(prefs.getHotlistDividerLocation());
196 		jspV.setDividerSize(1);
197 		// always resize pane
198 		jspV.setResizeWeight(1.0);
199 		jspV.add(pane);
200 		jspV.add(jpHotlist);
201 
202 		SplashWindow.incProgress(5, XNap.tr("Setting up Main Window"));
203 
204         getContentPane().setLayout(new BorderLayout());
205 		getContentPane().add(jspV, BorderLayout.CENTER);
206 		getContentPane().add(jtbMain, BorderLayout.NORTH);
207 		getContentPane().add(StatusBar.getInstance(), BorderLayout.SOUTH);
208 		pack();
209 
210         setBounds(prefs.getWindowX(), prefs.getWindowY(),
211                   prefs.getWindowWidth(), prefs.getWindowHeight());
212 
213         // close app if someone clicks on X
214         addWindowListener(new WindowListener());
215 
216 		HelpManager.enableHelpKeys(getRootPane(), "introduction", null);
217 
218 		// listen to a few properties
219         prefs.addPropertyChangeListener(this);
220     }
221 
222     private void initializeMenu() 
223     {
224 		setJMenuBar(new MainMenuBar());
225     }
226 
227     private void initializePanels()
228     {
229 		// tabbed pane / icon split pane
230 		pane = new ToggleableIconPane(prefs.getUseTabbedPane());
231 		pane.addChangeListener(this);
232 		updatePaneBorder();
233 
234 		// search
235 		jpSearch = new SearchPanel();
236 		jpSearch.setName("search");
237 		insertTab(XNap.tr("Search"), IconHelper.getListIcon("find.png"), 
238 				  jpSearch, 0);
239 
240 		SplashWindow.incProgress(5);
241 
242 		// transfer
243 		jpTransfer = new TransferPanel();
244 		jpTransfer.setName("transfer");
245 		insertTab(XNap.tr("Transfer"), 
246 				  IconHelper.getListIcon("connect_established.png"),
247 				  jpTransfer, 1);
248 
249 		SplashWindow.incProgress(5, XNap.tr("Setting up Chat Panel"));
250 
251 		// chat
252 		updateChatPanel();
253 
254 		SplashWindow.incProgress(5, XNap.tr("Setting up Library Panel"));
255 
256 		// library
257 		updateLibraryPanel();
258 
259 		// hotlist
260 		jpHotlist = new HotlistPanel();
261 		jpHotlist.setBorder(GUIHelper.createRaisedBorder());
262 		jpHotlist.setVisible(prefs.getBoolean("showHotlist"));
263     }
264 
265     private void initializeStatusBar() 
266     {
267 		JLabel jlPresence = new PresenceStatusPanel();
268 		jlPresence.setName(XNap.tr("Presence"));
269 		StatusBar.getInstance().appendComponent(jlPresence);
270 
271 		StatusPanel spLibrary = new StatusPanel();
272 		spLibrary.setIcon(IconHelper.getStatusBarIcon("contents.png"));
273 		spLibrary.setName(XNap.tr("Library"));
274 		Library.getInstance().setStatusListener(spLibrary);
275 		StatusBar.getInstance().appendComponent(spLibrary);
276     }
277 
278     private void initializeToolbars()
279     {
280 		// main toolbar
281 		jtbMain = new MainToolBar();
282 		jtbMain.setVisible(prefs.getBoolean("showMainToolbar"));
283     }
284     
285     /***
286      * Kills the instance of XNapFrame.
287      */
288     public static void disposeInstance()
289     {
290 		Library.getInstance().setStatusListener(null);
291 
292 		if (singleton.wdi != null) {
293 			singleton.wdi.hide();
294 			Thread.yield();
295 		}
296 
297 		singleton.dispose();
298 		singleton = null;
299     }
300 
301     /***
302      * Inserts a tab at <code>index</code>.
303      */
304     public void insertTab(String title, Icon icon, JComponent component, 
305 						  int index) 
306     {
307 		pane.insertTab(title, icon, component, index);
308 
309 		if (component instanceof JTabbedPane) {
310 			((JTabbedPane)component).addChangeListener(this);
311 		}
312     }
313 
314     /***
315      * Adds a tab above the chat panel.
316      */
317     public void insertTab(String title, Icon icon, JComponent component)
318     {
319 		insertTab(title, icon, component, pane.getTabCount());
320     }
321 
322     /***
323      * Removes <code>component</code>.
324      */
325     public void removeTab(Component component)
326     {
327 		int index = pane.indexOfComponent(component);
328 		if (index != -1) {
329 			pane.remove(component);
330 
331 			if (component instanceof JTabbedPane) {
332 				((JTabbedPane)component).removeChangeListener(this);
333 			}
334 		}
335     }
336 
337     /***
338      * Returns the instance of XNapFrame.
339 	 *
340 	 * @return null, if the gui was not started
341      */
342     public static XNapFrame getInstance()
343     {
344 		return singleton;
345     }
346 
347     /***
348      * Returns the chat panel.
349      */
350     public ChatPanel getChatPanel()
351     {
352 		if (jpChat == null) {
353 			jpChat = new ChatPanel();
354 			jpChat.setName("chat");
355 			jpChat.addChangeListener(this);
356 		}
357 		return jpChat;
358     }
359 
360     /***
361      * Returns the library panel.
362      */
363     public LibraryPanel getLibraryPanel()
364     {
365 		if (jpLibrary == null) {
366 			jpLibrary = new LibraryPanel();
367 			jpLibrary.setName("library");
368 		}
369 		return jpLibrary;
370     }
371 
372     /***
373      * Returns the hotlist panel.
374      */
375     public HotlistPanel getHotlistPanel()
376     {
377 		return jpHotlist;
378     }
379 
380     /***
381      * Returns the main menu bar.
382      */
383     public MainMenuBar getMainMenuBar()
384     {
385 		return (MainMenuBar)getJMenuBar();
386     }
387 
388     /***
389      * Returns the main tool bar.
390      */
391     public MainToolBar getMainToolBar()
392     {
393 		return jtbMain;
394     }
395 
396     /***
397      * Returns the main pane.
398      */
399     public ToggleableIconPane getPane()
400     {
401 		return pane;
402     }
403     
404     /***
405      * Returns the search panel.
406      */
407     public SearchPanel getSearchPanel()
408     {
409 		return jpSearch;
410     }
411     
412     /***
413      * Returns the transfer panel.
414      */
415     public TransferPanel getTransferPanel()
416     {
417 		return jpTransfer;
418     }
419 
420 	/***
421 	 * Invoked by {@link XNap#startGUI(boolean)} after gui is
422 	 * visible. Checks if important preferences like directory
423 	 * settings are valid.  
424 	 */
425 	public void guiVisible()
426 	{
427 		boolean showPrefs = false;
428 
429 		File f = new File(prefs.getDownloadDir());
430 		if (!f.isDirectory() && !f.mkdirs()) {
431 			Dialogs.error
432 				(this, 
433 				 XNap.tr("Could not create download directory."));
434 			showPrefs = true;
435 		}
436 
437 		f = new File(prefs.getIncompleteDir());
438 		if (!f.isDirectory() && !f.mkdirs()) {
439 			Dialogs.error
440 				(this, 
441 				 XNap.tr("Could not create incomplete directory."));
442 			showPrefs = true;
443 		}
444 
445 		if (showPrefs) {
446 			PreferencesDialog.showDialog(this);
447 		}
448 	}
449 
450     public void chatBlink()
451     {
452 		if (pane.getSelectedComponent() != jpChat) {
453 			int i = pane.indexOfComponent(jpChat);
454 			if (i != -1) {
455 				pane.blink(i, IconHelper.getListIcon("penguin.png"));
456 			}
457 		}
458     }
459 
460     public void propertyChange(PropertyChangeEvent e)
461     {
462 		String p = e.getPropertyName();
463 
464 		if (p.equals("lookAndFeel") || p.equals("theme")) {
465 			// updating the look and feel seems to break when using
466 			// the IconSplitPane view
467 			pane.setTabbed(true);
468 
469 			updateLookAndFeel();
470 
471 			// reset tabbed pane, see above
472 			pane.setTabbed(prefs.getUseTabbedPane());
473 		}
474 		else if (p.equals("useTabbedPane")) {
475 			pane.setTabbed(prefs.getUseTabbedPane());
476 		}
477 		else if (p.equals("showHotlist")) {
478 			if (jpHotlist.isVisible()) {
479 				prefs.setHotlistDividerLocation(jspV.getDividerLocation());
480 			}
481 			else {
482 				jspV.setDividerLocation(prefs.getHotlistDividerLocation());
483 			}
484 			jpHotlist.setVisible(prefs.getBoolean("showHotlist"));
485 
486 			if (jpHotlist.getWidth() 
487 				< jpHotlist.getMinimumSize().getWidth()) {
488 
489 				jspV.setDividerLocation
490 					((int)(jspV.getWidth() - jpHotlist.getMinimumSize().getWidth()));
491 			}
492 		}
493 		else if (p.equals("showChatPanel")) {
494 			updateChatPanel();
495 		}
496 		else if (p.equals("showLibraryPanel")) {
497 			updateLibraryPanel();
498 		}
499 		else if (p.equals("showMainToolbar")) {
500 			jtbMain.setVisible(prefs.getBoolean("showMainToolbar"));
501 		}
502     }
503 
504     /***
505      * Cleanly exits XNap.
506      *
507      * @param showCloseDialog if true, a confirmation dialog will be shown
508 	 * @return false, if exit was cancelled by user
509      */
510     public boolean exit(boolean showCloseDialog)
511     {
512 		if (showCloseDialog && !Dialogs.showCloseDialog(this)) {
513 			return false;
514 		}
515 
516 		XNap.exit();
517 
518 		return true;
519 	}
520 
521     /***
522      * Cleanly exits the application. Prompts the user for confirmation if
523      * confirmation is turned on.
524 	 *
525 	 * @return false, if exit was cancelled by user
526 	 * @see #exit(boolean)
527      */
528     public boolean exit()
529     {
530 		return exit(Dialogs.getShowDialog("Close"));
531     }
532 
533 	public void stop()
534 	{
535 		PluginManager.getInstance().guiStopped();
536 
537 		// save gui prefs
538 		jpSearch.savePrefs();
539 		jpTransfer.savePrefs();
540 		if (jpChat != null) {
541 			jpChat.savePrefs();
542 		}
543 		if (jpLibrary != null) {
544 			jpLibrary.savePrefs();
545 		}
546 
547 		// save hotlist layout
548 		if (jpHotlist.isVisible()) {
549 			prefs.setHotlistDividerLocation(jspV.getDividerLocation());
550 		}
551 
552 		// save window positions
553 		// it seems that XNap shrinks on every launch
554 		prefs.setWindowHeight(getBounds().getSize().height 
555 							  + prefs.getCorrectivePixels());
556 		prefs.setWindowWidth(getBounds().getSize().width);
557 		prefs.setWindowX(getBounds().getLocation().x);
558 		prefs.setWindowY(getBounds().getLocation().y);
559 
560 		disposeInstance();
561 	}
562 
563     /***
564      * Changes the icons on the main toolbar if the panel is switched.
565      */
566     public void stateChanged(ChangeEvent e)
567     {
568 		Object source = pane.getSelectedComponent();
569 
570 		// remove previous panel icons
571 		getMainToolBar().removeAllTemporaries();
572 
573 		if (source instanceof JTabbedPane) {
574 			if (((JTabbedPane)source).getSelectedComponent() != null) {
575 				source = ((JTabbedPane)source).getSelectedComponent();
576 			}
577 		}
578 		else if (source == jpChat) {
579 			if (pane.getSelectedComponent() != jpChat) {
580 				return;
581 			}
582 
583 			// FIX: the source of the event is set to jpChat instead of
584 			// jtp (which is a member of jpChat). Java bug?
585 			source = jpChat.getSelectedTab();
586 		}
587 
588 		if (source instanceof ActionProvider) {
589 			Action[] actions = ((ActionProvider)source).getActions();
590 			if (actions != null) {
591 				for (int i = 0; i < actions.length; i++) {
592 					if (actions[i] != null) {
593 						if (actions[i] instanceof ToggleAction) {
594 							getMainToolBar().addTemporary
595 								(new XNapToolBarToggleButton
596 									((ToggleAction)actions[i]));
597 						}
598 						else {
599 							getMainToolBar().addTemporary
600 								(new XNapToolBarButton(actions[i]));
601 						}
602 					}
603 					else {
604 						JSeparator js = new JToolBar.Separator();
605 						js.setOrientation(SwingConstants.VERTICAL);
606 						getMainToolBar().addTemporary(js);
607 					}
608 				}
609 			}
610 		}
611 
612 		// redraw
613 		getMainToolBar().updateUI();
614     }
615 
616 	private void updateChatPanel()
617 	{
618 		if (prefs.getBoolean("showChatPanel")) {
619 			insertTab(XNap.tr("Chat"), 
620 					  IconHelper.getListIcon("mail_generic2.png"),
621 					  getChatPanel(), 2);
622 		}
623 		else if (jpChat != null) {
624 			pane.remove(jpChat);
625 		}
626 	}
627 
628 	private void updateLibraryPanel()
629 	{
630 		if (prefs.getBoolean("showLibraryPanel")) {
631 			boolean chatVisible 
632 				= (jpChat != null && pane.indexOfComponent(jpChat) != -1);
633 			insertTab(XNap.tr("Library"), 
634 					  IconHelper.getListIcon("contents.png"),
635 					  getLibraryPanel(), chatVisible ? 3 : 2);
636 		}
637 		else if (jpLibrary != null) {
638 			pane.remove(jpLibrary);
639 		}
640 	}
641 
642     /***
643      * Switches the look and feel.  */
644     public static void updateLookAndFeel() 
645     {
646 		ThemeManager.setTheme(prefs.getIconTheme());
647 		if (!ThemeManager.setTheme(prefs.getTheme())) {
648 			ThemeManager.setTheme(Preferences.DEFAULT_THEME);
649 		}
650 
651 		try {
652 			Class.forName(prefs.getLookAndFeel());
653 			UIManager.setLookAndFeel(prefs.getLookAndFeel());
654 
655 			updatePaneBorder();
656 		}
657 		catch (Exception e) {
658 			logger.warn("could not change look and feel", e);
659 		}
660 
661 		if (getInstance() != null) {
662 			SwingUtilities.updateComponentTreeUI(getInstance());
663 		}
664 		SplashWindow.updateComponentTree();
665     }
666 
667 	/***
668 	 * The jgoodies l&fs use embedded tabs, so we need an extra border.
669 	 */
670 	private static void updatePaneBorder()
671 	{
672 		if (getInstance() != null && getInstance().pane != null) {
673 			getInstance().pane.setBorder
674 				(prefs.getLookAndFeel().startsWith("com.jgoodies.plaf") 
675 				 ? GUIHelper.createRaisedBorder()
676 				 : GUIHelper.createEmptyBorder());
677 		}
678 	}
679 
680     //--- Inner Class(es) ---
681 
682     /***
683      * Handles the Windows system tray icon.
684      */
685     private class WindowListener extends WindowAdapter
686     {
687 
688 		public void windowDeiconified(WindowEvent e) 
689 		{
690 		}
691 
692 		public void windowIconified(WindowEvent e) 
693 		{
694 			if (wdi != null) {
695 				wdi.show("xnap.ico", XNap.tr("XNap"));
696 				Thread.yield();
697 				wdi.hideFrame();
698 			}
699 		}
700 
701 		public void windowClosing (WindowEvent evt) 
702 		{
703 			exit();
704 		}
705 
706     }
707 
708 }