1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
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
125
126 /***
127 * Constructs the search panel.
128 */
129 public SearchPanel()
130 {
131 initialize();
132
133 SearchManager.getInstance().setListener(this);
134
135
136
137
138
139
140
141
142
143
144
145
146
147 SearchManager.getInstance().addListListener(this);
148
149 prefs.addPropertyChangeListener("showAdvancedSearchOptions", this);
150 propertyChange(null);
151
152 updateActions();
153 }
154
155
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
176 jmPopup = new SearchResultMenu();
177
178 DefaultComboBoxModel dcm = readHistoryFile();
179
180
181 jpSimple = new SimpleSearchQueryPanel(dcm);
182
183
184 jpAdvanced = new AdvancedSearchQueryPanel(dcm);
185
186
187
188 jtpSearches = new CloseableTabbedPane();
189 jtpSearches.setBorder(GUIHelper.createLoweredBorder());
190 jtpSearches.addContainerListener(new TabListener());
191 jtpSearches.addChangeListener(this);
192
193
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
203 jtpTransfer = new JTabbedPane();
204 jtpTransfer.setBorder(GUIHelper.createLoweredBorder());
205
206
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
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
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
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
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
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
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
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 }