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.CardLayout;
24  import java.awt.event.*;
25  import java.awt.event.ContainerEvent;
26  import java.awt.event.ContainerListener;
27  import java.beans.PropertyChangeEvent;
28  import java.beans.PropertyChangeListener;
29  import java.io.File;
30  import java.io.IOException;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.LinkedList;
34  import java.util.List;
35  import java.util.Vector;
36  
37  import javax.swing.*;
38  import javax.swing.Action;
39  import javax.swing.DefaultComboBoxModel;
40  import javax.swing.JComponent;
41  import javax.swing.JLabel;
42  import javax.swing.JMenu;
43  import javax.swing.JPanel;
44  import javax.swing.JPopupMenu;
45  import javax.swing.JSplitPane;
46  import javax.swing.JTabbedPane;
47  import javax.swing.SwingConstants;
48  import javax.swing.SwingUtilities;
49  import javax.swing.event.ChangeEvent;
50  import javax.swing.event.ChangeListener;
51  
52  import org.apache.log4j.Logger;
53  import org.xnap.XNap;
54  import org.xnap.action.*;
55  import org.xnap.action.AbstractToggleReversedPrefAction;
56  import org.xnap.event.ListEvent;
57  import org.xnap.event.ListListener;
58  import org.xnap.gui.action.*;
59  import org.xnap.gui.shortcut.*;
60  import org.xnap.gui.component.CloseableTabbedPane;
61  import org.xnap.gui.menu.AbstractDynamicMenu;
62  import org.xnap.gui.menu.PeerMenu;
63  import org.xnap.gui.util.GUIHelper;
64  import org.xnap.gui.util.IconHelper;
65  import org.xnap.peer.Peer;
66  import org.xnap.plugin.news.NewsPanel;
67  import org.xnap.search.Search;
68  import org.xnap.search.SearchManager;
69  import org.xnap.search.SearchManagerListener;
70  import org.xnap.search.SearchProvider;
71  import org.xnap.search.SearchResult;
72  import org.xnap.search.SearchResultActionProvider;
73  import org.xnap.transfer.DownloadManager;
74  import org.xnap.transfer.UploadManager;
75  import org.xnap.util.FileHelper;
76  import org.xnap.util.Preferences;
77  
78  /***
79   * This class provides a search input panel and tabbed pane that contains 
80   * {@link SearchResultPanel SearchResultPanel} objects.
81   */
82  public class SearchPanel extends JPanel 
83      implements ActionProvider, ChangeListener, ListListener, PeerProvider,
84  			   PropertyChangeListener, SearchManagerListener {
85  
86      //--- Constant(s) ---
87  
88  	/***
89       * File that was used to save the search history.
90       */
91      public static final String HISTORY_FILENAME 
92  		= FileHelper.getHomeDir() + "search_history3";
93  
94      //--- Data field(s) ---
95  
96      protected static Preferences prefs = Preferences.getInstance();
97  
98      private JMenu jmPopup;
99      private SimpleSearchQueryPanel jpSimple;
100     private AdvancedSearchQueryPanel jpAdvanced;
101     private CloseableTabbedPane jtpSearches;
102     private JSplitPane jspH;
103     private JSplitPane jspV;
104 	private JTabbedPane jtpTransfer;
105 
106     private StopAction acStop = new StopAction();
107     private RequeryAction acRequery = new RequeryAction();
108     private Action acPeerMenu 
109 		= new MenuAction(new PeerMenu(this), "users.png");
110 	private Action acShowAdvanced
111 		= new ShowAdvancedSearchOptionsAction();
112 	private Action acShowSimple
113 		= new ShowSimpleSearchOptionsAction();
114 
115     private CardLayout clCenter;
116     private JPanel jpCenter;
117 	private NewsPanel newsPanel;
118 	
119 	private List actionProviders = new ArrayList();
120 
121 	private static Logger logger = Logger.getLogger(SearchPanel.class);
122     private JComponent logoPanel;
123 	
124     //--- Constructor(s) ---
125     
126     /***
127      * Constructs the search panel.
128      */
129     public SearchPanel() 
130     {
131 		initialize();
132 
133 		SearchManager.getInstance().setListener(this);
134 
135 		// register column menus
136 //		ColumnModel cm = TableColumnsMenu.toModel
137 //			(SearchTableModel.createDefaultColumns());
138 //		JMenu jm = new TableColumnsMenu
139 //			(XNap.tr("Search Table"), cm, Preferences.getInstance(), "search",
140 //			 2);
141 		//XNapFrame.getInstance().getMainMenuBar().addTableColumnsMenu(jm);
142 //		jm = new TableColumnsMenu
143 //			(XNap.tr("Browse Table"), cm, Preferences.getInstance(), "browse",
144 //			 2);
145 		//XNapFrame.getInstance().getMainMenuBar().addTableColumnsMenu(jm);
146 
147 		SearchManager.getInstance().addListListener(this);
148 
149 		prefs.addPropertyChangeListener("showAdvancedSearchOptions", this);
150 		propertyChange(null);
151 
152 		updateActions();
153     }
154     
155     //--- Method(s) ---
156 
157 	public void registerActionProvider(SearchResultActionProvider provider) 
158 	{
159 		actionProviders.add(provider);	
160 	}
161 	
162 	public void deregisterActionProvider(SearchResultActionProvider provider)
163 	{
164 		actionProviders.remove(provider); 
165 	}
166 	
167 	public SearchResultActionProvider[] getActionProviders()
168 	{
169 		return (SearchResultActionProvider[]) 
170 			actionProviders.toArray(new SearchResultActionProvider[] {});	
171 	}
172 	
173     private void initialize() 
174     {
175 		// popup menu
176 		jmPopup = new SearchResultMenu();
177 
178 		DefaultComboBoxModel dcm = readHistoryFile();
179 
180 		// simple search query panel
181 		jpSimple = new SimpleSearchQueryPanel(dcm);
182 
183 		// advanced search query panel
184 		jpAdvanced = new AdvancedSearchQueryPanel(dcm);
185 		//GridBagHelper.add(jpAdvanced, new XNapButton(new ShowSimpleAction()));
186 
187 		// tabbed pane
188 		jtpSearches = new CloseableTabbedPane();
189 		jtpSearches.setBorder(GUIHelper.createLoweredBorder());
190 		jtpSearches.addContainerListener(new TabListener());
191 		jtpSearches.addChangeListener(this);
192 
193 		// center panel
194 		clCenter = new CardLayout();
195 		jpCenter = new JPanel();
196 		jpCenter.setBorder(GUIHelper.createEmptyBorder());
197 		jpCenter.setLayout(clCenter);
198 		jpCenter.add(jtpSearches, "Search");
199 
200 		setLogoPanel(createDefaultLogoPanel());
201 		
202 		// transfer panel
203 		jtpTransfer = new JTabbedPane();
204 		jtpTransfer.setBorder(GUIHelper.createLoweredBorder());
205 
206 		// download panel
207 		TransferManagerPanel jpDownloads = new TransferManagerPanel
208 			(DownloadManager.getInstance(), "filteredDownload", true);
209 		jtpTransfer.addTab(XNap.tr("Downloads"), 
210 						   IconHelper.getTabTitleIcon("down.png"), 
211 						   jpDownloads);
212 
213 		// upload panel
214 		TransferManagerPanel jpUploads = new TransferManagerPanel
215 			(UploadManager.getInstance(), "filteredUpload", true);
216 		jtpTransfer.addTab(XNap.tr("Uploads"),
217 						   IconHelper.getTabTitleIcon("up.png"), 
218 						   jpUploads);
219 
220 		// split panel
221 		jspV = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
222 		jspV.setDividerLocation(prefs.getSearchDividerLocation());
223 		jspV.setOneTouchExpandable(true);
224 		jspV.setResizeWeight(1);
225 		jspV.setBorder(GUIHelper.createEmptyBorder());
226 		jspV.add(jpCenter, JSplitPane.TOP);
227 		jspV.add(jtpTransfer, JSplitPane.BOTTOM);
228 
229 		jspH = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
230 		jspH.setDividerLocation(prefs.getInt("searchHorizontalDividerLocation"));
231 		jspH.setBorder(GUIHelper.createEmptyBorder());
232 		jspH.setDividerSize(2);
233 		jspH.add(jpAdvanced);
234 		jspH.add(jspV);
235 
236 		setBorder(GUIHelper.createEmptyBorder());
237 		setLayout(new BorderLayout());
238 		add(jpSimple, BorderLayout.NORTH);
239 		add(jspH, BorderLayout.CENTER);
240 
241 		// shortcuts
242 		ShortcutManager.getInstance().add(acRequery, this);
243 		ShortcutManager.getInstance().add(acStop, this);
244     }
245 
246     /***
247 	 * @return
248 	 */
249 	public static JComponent createDefaultLogoPanel()
250 	{
251 		// logo
252 		JLabel logoPanel = new JLabel(IconHelper.getImage("xnap_logo.png"));
253 		logoPanel.setHorizontalAlignment(SwingConstants.CENTER);
254 		logoPanel.setVerticalAlignment(SwingConstants.CENTER);
255 		return logoPanel;
256 	}
257 
258 	public void handle(Search search)
259     {
260 		SearchResultPanel ssp = (search.showTree())
261 			? new SearchResultTreePanel(this, search)
262 				: new SearchResultPanel(this, search);
263 
264 		if (search.getFilter() != null) {
265 			jpSimple.addToHistory(search.getFilter());
266 		}
267 		jtpSearches.addTab(ssp.getTitle(), ssp);
268     }
269 
270     /***
271      * Invoked when a {@link SearchProvider} has been added.
272      */
273     public void itemAdded(final ListEvent event)
274     {
275 		Runnable runner = new Runnable()
276 			{
277 				public void run()
278 				{
279 					jpAdvanced.providerAdded((SearchProvider)event.getItem());
280 					jpSimple.providerAdded((SearchProvider)event.getItem());
281 				}
282 			};
283 		SwingUtilities.invokeLater(runner);
284     }
285 
286     /***
287      * Invoked when a {@link SearchProvider} has been removed.
288      */
289     public void itemRemoved(final ListEvent event)
290     {
291 		Runnable runner = new Runnable()
292 			{
293 				public void run()
294 				{
295 					jpAdvanced.providerRemoved
296 						((SearchProvider)event.getItem());
297 					jpSimple.providerRemoved
298 						((SearchProvider)event.getItem());
299 				}
300 			};
301 		SwingUtilities.invokeLater(runner);
302     }
303 
304     /***
305      * Returns the advanced search query panel.
306      */
307     public AdvancedSearchQueryPanel getAdvancedPanel()
308     {
309 		return jpAdvanced;
310     }
311 
312 	/***
313 	 * Returns the transfer panel.
314 	 */
315 	public JTabbedPane getSearchResultPanel()
316 	{
317 		return jtpSearches;
318 	}
319 
320 	/***
321 	 * Returns the transfer panel.
322 	 */
323 	public JTabbedPane getTransferPanel()
324 	{
325 		return jtpTransfer;
326 	}
327 
328     /***
329      *
330      */
331     public SimpleSearchQueryPanel getSimple()
332     {
333 		return jpSimple;
334     }
335 
336     public Action[] getActions()
337     {
338 		return new Action[] {
339 			acStop, acRequery, null, acPeerMenu, null, 
340 			acShowSimple, acShowAdvanced,
341 		};
342     }
343 
344     public JPopupMenu getPopupMenu()
345     {
346 		return jmPopup.getPopupMenu();
347     }
348 
349     public void savePrefs()
350     {
351 		if (jspH.isVisible()) {
352 			prefs.set("searchHorizontalDividerLocation", 
353 					  jspH.getDividerLocation());
354 		}
355 		prefs.setSearchDividerLocation(jspV.getDividerLocation());
356 		writeHistoryFile();
357     }
358 
359     /***
360      * Returns the currently selected search result panel.
361      *
362      * @return null, if no panel is visible; the panel, otherwise
363      */
364     public SearchResultPanel getSelectedTab()
365     {
366 		return (SearchResultPanel)jtpSearches.getSelectedComponent();
367     }
368 
369     /***
370      * Called when a tab is selected.
371      */
372     public void stateChanged(ChangeEvent e)
373     {
374 		updateActions();
375     }
376    
377     /***
378      * Sets the title of <code>tab</code> to <code>title</code>.
379      *
380      * @param tab the search result panel
381      * @param title the title
382      */
383     public void setTabTitle(SearchResultPanel tab, String title)
384     {
385 		int i = jtpSearches.indexOfComponent(tab);
386 		if (i != -1) {
387 			jtpSearches.setTitleAt(i, title);
388 		}
389     }
390 
391     /***
392      * Returns the currently selected {@link Peer} objects.
393      *
394      * @return null, if nothing is selected; the peers, otherwise
395      */
396     public Peer[] getPeers()
397     {
398 		SearchResultPanel sp = getSelectedTab();
399 		if (sp != null) {
400 			SearchResult results[] = sp.getSelectedResults();
401 			if (results != null) {
402 				LinkedList l = new LinkedList();
403 				for (int i = 0; i < results.length; i++) {
404 					if (results[i].getPeer() != null) {
405 						l.add(results[i].getPeer());
406 					}
407 				}
408 				return (Peer[])l.toArray(new Peer[0]);
409 			}
410 		}
411 
412 		return null;
413     }
414 
415     public void propertyChange(PropertyChangeEvent e)
416 	{
417 		if (jpAdvanced.isVisible()) {
418 			prefs.set("searchHorizontalDividerLocation", 
419 					  jspH.getDividerLocation());
420 		}
421 
422 		jpSimple.setVisible(!prefs.getBoolean("showAdvancedSearchOptions"));
423 		jpAdvanced.setVisible(prefs.getBoolean("showAdvancedSearchOptions"));
424 
425 		if (jpAdvanced.isVisible()) {
426 			int size 
427 				= Math.max(prefs.getInt("searchHorizontalDividerLocation"),
428 						   (int)jpAdvanced.getMinimumSize().getWidth());
429 			jspH.setDividerLocation(size);
430 		}
431 	}
432 
433     public void updateActions()
434     {
435 		SearchResultPanel sp = getSelectedTab();
436 		if (sp != null) {
437 			acStop.setEnabled(!sp.getController().isDone());
438 			acRequery.setEnabled(sp.getController().isDone());
439 		}
440 		else {
441 			acStop.setEnabled(false);
442 			acRequery.setEnabled(false);
443 		}
444     }
445 
446     public void setLogoPanel(JComponent panel)
447     {
448     	if (logoPanel != null) {
449     		jpCenter.remove(logoPanel);
450     	}
451     	
452    		logoPanel = (panel != null) ? panel : createDefaultLogoPanel(); 
453    		jpCenter.add(logoPanel, "Logo");
454    		
455    		if (jtpSearches.getComponentCount() == 0) {
456 			clCenter.show(jpCenter, "Logo");
457    		}
458     }
459     
460 	/***
461 	 * Reads history file into a new combobox model.
462 	 */
463 	private DefaultComboBoxModel readHistoryFile()
464 	{
465 		Vector v = new Vector();
466 		try {
467 			FileHelper.readBinary(new File(HISTORY_FILENAME), v);
468 		}
469 		catch (IOException ie) {
470 			logger.debug("Error reading history file" , ie);
471 		}
472 		return new DefaultComboBoxModel(v);
473 	}
474 	
475 	/***
476 	 * Stores history items.
477 	 */
478 	private void writeHistoryFile()
479 	{
480 		List l = Arrays.asList(jpAdvanced.getHistoryItems());
481 		
482  		try {
483  			FileHelper.writeBinary(new File(HISTORY_FILENAME), l);
484  		}
485  		catch (IOException ie) {
486 			logger.debug("Error writing history file", ie);
487  		}
488 	}
489 
490     /***
491      * Makes sure the XNap logo is shown once all sub panels are closed.
492      */
493     private class TabListener implements ContainerListener
494     {
495 		public void componentAdded(ContainerEvent e) 
496 		{
497 			updateLayout(e.getID());
498 		}
499 
500 		public void componentRemoved(ContainerEvent e)
501 		{
502 			updateLayout(e.getID());
503 		}
504 
505 		private void updateLayout(int id)
506 		{
507 			if (id == ContainerEvent.COMPONENT_REMOVED
508 				&& jtpSearches.getComponentCount() == 0) {
509 				clCenter.show(jpCenter, "Logo");
510 			}
511 			else {
512 				clCenter.show(jpCenter, "Search");
513 			}
514 		}
515     }
516 
517     /***
518      * Stops the search in the currently focused tab.
519      */
520     private class StopAction extends AbstractAction {
521 	
522         public StopAction() 
523 		{
524             putValue(Action.NAME, XNap.tr("Stop"));
525             putValue(Action.SHORT_DESCRIPTION, XNap.tr("Stops search."));
526 			putValue(IconHelper.XNAP_ICON, "stop.png");
527 			putValue(AbstractXNapAction.SHORTCUT_CATEGORY, XNap.tr("Search"));
528 			putValue(Action.ACTION_COMMAND_KEY, "stopSearch");
529         }
530 	
531         public void actionPerformed(ActionEvent event) 
532 		{
533 			SearchResultPanel srp = getSelectedTab();
534 			if (srp != null) {
535 				setEnabled(false);
536 				srp.getController().stop();
537 			}
538 			updateActions();
539         }
540     }
541 
542 
543     /***
544      * Reperforms a search.
545      */
546     private class RequeryAction extends AbstractAction
547 	{
548 
549         public RequeryAction() 
550 		{
551             putValue(Action.NAME, XNap.tr("Requery"));
552             putValue(Action.SHORT_DESCRIPTION,
553 					 XNap.tr("Perform search again"));
554 			putValue(IconHelper.XNAP_ICON, "reload3.png");
555 			putValue(AbstractXNapAction.DEFAULT_KEYSTROKE,
556 					 KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0));
557 			putValue(AbstractXNapAction.SHORTCUT_CATEGORY, XNap.tr("Search"));
558 			putValue(Action.ACTION_COMMAND_KEY, "requerySearch");
559         }
560 
561         public void actionPerformed(ActionEvent event) 
562 		{
563 			SearchResultPanel srp = getSelectedTab();
564 			if (srp != null) {
565 				jtpSearches.remove(srp);
566 				// FIX: we should requery in the same panel
567 				SearchManager.getInstance().handle
568 					(srp.getController().getSearch());
569 			}
570         }
571     }
572 
573     /***
574      * Switches between simple and advanced view.
575      */
576     private class ShowAdvancedSearchOptionsAction 
577 		extends AbstractTogglePrefAction {
578 	
579         public ShowAdvancedSearchOptionsAction() 
580 		{
581 			super("showAdvancedSearchOptions");
582 
583             putValue(Action.NAME, XNap.tr("Advanced"));
584             putValue(Action.SHORT_DESCRIPTION, 
585 					 XNap.tr("Displays advanced search options panel."));
586 			putValue(IconHelper.XNAP_ICON, "view_left_right.png");
587         }
588 
589 		public void toggled(boolean selected)
590 		{
591 			// handled by propertyChange()
592 		}
593 	
594     }
595 
596     /***
597      * Switches between simple and advanced view.
598      */
599     private class ShowSimpleSearchOptionsAction 
600 		extends AbstractToggleReversedPrefAction {
601 	
602         public ShowSimpleSearchOptionsAction() 
603 		{
604 			super("showAdvancedSearchOptions");
605 
606             putValue(Action.NAME, XNap.tr("Simple"));
607             putValue(Action.SHORT_DESCRIPTION, 
608 					 XNap.tr("Displays simple search options panel."));
609 			putValue(IconHelper.XNAP_ICON, "view_top_bottom.png");
610         }
611 
612 		public void toggled(boolean selected)
613 		{
614 			// handled by propertyChange()
615 		}
616 	
617     }
618 
619     /***
620      * Provides the popup menu.
621      */
622     private class SearchResultMenu extends AbstractDynamicMenu
623     {
624 
625 		public SearchResultMenu()
626 		{
627 			super(XNap.tr("Results"));
628 		}
629 
630 		/***
631 		 * Builds the intersection of all actions of all selected
632 		 * search results and adds them to the top of the menu.  */
633 		protected void willBecomeVisible()
634 		{
635 			removeAllTemporaries();
636 	    
637 			SearchResult[] results = getSelectedTab().getSelectedResults();
638 			int count1 = ActionHelper.addCommonActions
639 				(this, results, new SearchResultActionProvider(), 0);
640 
641 			int count2 = PeerMenu.addActions(this, getPeers());
642 
643 			for (int i = 0; i < actionProviders.size(); i++) {
644 				Action[] acts = 
645 					((org.xnap.search.SearchResultActionProvider)actionProviders.get(i))
646 						.getActions(results);
647 				if (acts != null) {
648 					for (int j = 0; j < acts.length; j++) {
649 						if (acts[j] != null) {
650 							addTemporary(ActionHelper.createMenuItem(acts[j]));	
651 						}
652 					}
653 				}
654 			}
655 			
656 			if (count1 > 0 && count2 > 0) {
657 				addTemporary(new JPopupMenu.Separator(), count1);
658 			}
659 		}
660 	    
661 		private class SearchResultActionProvider 
662 			implements ActionHelper.ActionExtractor
663 		{
664 	    
665 			public Action[] getActions(Object o) 
666 			{
667 				return ((SearchResult)o).getActions();
668 			}
669 
670 		}
671 	    
672     }
673 
674 
675 }