/*
* XNap
*
* A pure java file sharing client.
*
* See AUTHORS for copyright information.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package xnap.gui;
import xnap.XNap;
import xnap.cmdl.Console;
import xnap.cmdl.ConsoleWriter;
import xnap.gui.action.MenuAction;
import xnap.gui.event.EraseAction;
import xnap.gui.event.ShowAction;
import xnap.gui.event.UserSupport;
import xnap.gui.menu.UserMenu;
import xnap.gui.util.GUIHelper;
import xnap.io.Repository;
import xnap.net.AutoDownload;
import xnap.net.IDownloadContainer;
import xnap.net.ISearchResult;
import xnap.net.IUser;
import xnap.net.SearchResultContainer;
import xnap.util.Browser;
import xnap.util.DownloadQueue;
import xnap.util.FileHelper;
import xnap.util.ISearchContainer;
import xnap.util.Preferences;
import xnap.util.SearchFilter;
import xnap.util.SearchFilterHelper;
import xnap.util.SearchManager;
import xnap.util.StringHelper;
import xnap.util.event.ListEvent;
import xnap.util.event.ListListener;
import xnap.util.event.SearchManagerListener;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.util.*;
import javax.accessibility.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.table.*;
import org.apache.log4j.Logger;
public class SearchPanel extends AbstractPanel
implements ListListener, ChangeListener, PropertyChangeListener,
SearchManagerListener, UserSupport {
//--- Constant(s) ---
public static final String HISTORY_FILENAME
= FileHelper.getHomeDir() + "search_history";
public static final String HISTORY_FILENAME_2
= FileHelper.getHomeDir() + "search_history2";
//--- Data field(s) ---
protected static Logger logger = Logger.getLogger(SearchPanel.class);
protected static Preferences prefs = Preferences.getInstance();
private JPopupMenu popup;
private EditableComboBox jcSearch;
private OptionsBox opBox;
private JComboBox jcMediaType;
private CloseableTabbedPane jtpSearches;
private JSplitPane jsp;
private JMenu jmBrowse;
private JPanel jpOptions;
private AbortAction acAbort = new AbortAction();
private AutoDownloadAction acAutoDownload = new AutoDownloadAction();
private EraseAction acErase = new EraseAction();
private QueryAction acQuery = new QueryAction();
private ResumeFromAction acResumeFrom = new ResumeFromAction();
private SearchRepositoryAction acSearchRepository
= new SearchRepositoryAction();
private MenuAction acMenu = new MenuAction(new UserMenu(this));
private CardLayout clCenter;
private Container jpCenter;
//--- Constructor(s) ---
public SearchPanel()
{
initialize();
addPropertyChangeListener("searchHistorySize", this);
SearchManager.getInstance().addListListener(this);
SearchManager.getInstance().setListener(this);
}
//--- Method(s) ---
private void initialize()
{
// popup menu
popup = new JPopupMenu();
popup.add(acAutoDownload);
popup.add(acResumeFrom);
popup.addSeparator();
popup.add(acSearchRepository);
if (prefs.getSearchResultOpenAction().length() > 0) {
popup.add(new SearchResultOpenAction());
}
popup.addSeparator();
popup.add(new UserMenu(this));
// first panel
Box boxOne = new Box(BoxLayout.X_AXIS);
// erase button
JButton jbErase = new JButton(acErase);
jbErase.setMargin(new Insets(1, 1, 1, 1));
boxOne.add(jbErase);
// search field
jcSearch = new EditableComboBox(acQuery, prefs.getSearchHistorySize());
File f = new File(HISTORY_FILENAME_2);
if (f.exists()) {
jcSearch.readBinaryHistoryFile(f);
}
else {
jcSearch.readHistoryFile(new File(HISTORY_FILENAME));
}
jcSearch.addActionListener(new FilterSelectionListener());
jcSearch.getJTextField().setColumns(40);
jcSearch.getJTextField().setMaximumSize
(new Dimension(jcSearch.getJTextField().getPreferredSize().width,
jcSearch.getMaximumSize().height));
boxOne.add(jcSearch);
acErase.setJTextField(jcSearch.getJTextField());
// media type
jcMediaType = new JComboBox(SearchFilter.media);
jcMediaType.addActionListener(new MediaTypeSelectionListener());
GUIHelper.limitSize(jcMediaType);
boxOne.add(jcMediaType);
// options panel
opBox = new OptionsBox(BoxLayout.X_AXIS);
/* this has to be done here after jcCompare and jcBitrate are created,
since the MediaTypeSelectionListener accesses these elements. */
jcMediaType.setSelectedIndex(prefs.getSearchMediaType());
jpOptions = new JPanel();
jpOptions.setLayout(new BoxLayout(jpOptions, BoxLayout.Y_AXIS));
jpOptions.add(Box.createVerticalStrut(2));
jpOptions.add(opBox);
jpOptions.setVisible(prefs.getShowSearchOptions());
// buttons
JButton jbQuery = new JButton(acQuery);
boxOne.add(jbQuery);
JButton jbOptions = new JButton(new ShowAction(this, jpOptions));
jbOptions.setMargin(new Insets(1, 1, 1, 1));
boxOne.add(jbOptions);
boxOne.add(Box.createHorizontalGlue());
/* tabbed pane */
jtpSearches = new CloseableTabbedPane();
jtpSearches.addContainerListener(new TabListener());
jtpSearches.addChangeListener(this);
/* logo */
JLabel jlLogo = new JLabel(XNapFrame.getImage("xnap_logo.png"));
/* content */
clCenter = new CardLayout();
jpCenter = new Container();
jpCenter.setLayout(clCenter);
jpCenter.add(jlLogo, "Logo");
jpCenter.add(jtpSearches, "Search");
JPanel jpTop = new JPanel();
jpTop.setLayout(new BoxLayout(jpTop, BoxLayout.Y_AXIS));
jpTop.setBorder(new TitledBorder(XNap.tr("Search", 1)));
jpTop.add(boxOne);
jpTop.add(jpOptions);
TransferSubPanel tsp = new TransferSubPanel(true, true);
tsp.setStatusListener(this);
// downloads
jsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
jsp.setDividerLocation(prefs.getSearchDividerLocation());
jsp.setOneTouchExpandable(true);
jsp.setResizeWeight(1);
jsp.add(jpCenter, JSplitPane.TOP);
jsp.add(tsp, JSplitPane.BOTTOM);
// me
setLayout(new BorderLayout());
add(jpTop, BorderLayout.NORTH);
add(jsp, BorderLayout.CENTER);
updateActions();
}
/**
* Convenience method for inner classes.
*/
public void download(SearchResultContainer c, SearchFilter filter,
File file)
{
if (filter == null) {
filter = new SearchFilter();
}
boolean autoDownload = false;
ISearchResult[] results = c.getSearchResults();
for (int i = 0; i < results.length; i++) {
if (results[i].canGroup()) {
autoDownload = true;
}
else if (!results[i].download(filter, file)) {
setStatus(XNap.tr("Already downloading that file."));
}
}
if (autoDownload) {
AutoDownload d = new AutoDownload(results, filter, file);
if (!DownloadQueue.getInstance().add(d)) {
setStatus(XNap.tr("Already downloading that file."));
}
}
}
public void elementAdded(ListEvent e)
{
ISearchContainer sc = (ISearchContainer)e.getElement();
SearchSubPanel ssp;
if (sc instanceof Browser) {
ssp = new SearchSubTreePanel(SearchPanel.this, sc);
}
else {
ssp = new SearchSubPanel(SearchPanel.this, sc);
}
jtpSearches.addTab(ssp.getTitle(), ssp);
XNapFrame.setFocusTo("search");
}
public void elementRemoved(ListEvent e)
{
}
public int getDividerLocation()
{
return jsp.getDividerLocation();
}
public void readyToSearch(final boolean ready)
{
Runnable runner = new Runnable()
{
public void run()
{
acQuery.setEnabled(ready);
}
};
SwingUtilities.invokeLater(runner);
}
public void setDividerLocation(int newValue)
{
if (newValue == -1) {
jsp.setDividerLocation(0.8);
}
else {
jsp.setDividerLocation(newValue);
}
}
public SearchFilter getSearchFilter()
{
SearchFilter f = new SearchFilter();
f.setSearchText(jcSearch.getText().trim());
if (opBox.isBitrateEnabled()) {
f.setBitrateCompare(opBox.getBitrateCompare());
f.setBitrate(opBox.getBitrate());
}
f.setMediaType(jcMediaType.getSelectedIndex());
f.setFilesizeCompare(opBox.getFilesizeCompare());
f.setFilesize(opBox.getFilesize());
return f;
}
public void setSearchFilter(SearchFilter f)
{
opBox.setBitrateCompare(f.getBitrateCompare());
opBox.setBitrate
(SearchFilterHelper.getIndexFromBitrate(f.getBitrate()));
jcMediaType.setSelectedIndex(f.getMediaType());
opBox.setFilesizeCompare(f.getFilesizeCompare());
opBox.setFilesize(f.getFilesize());
}
/**
* Used by sub panels for double click events.
*/
public AbstractAction getDefaultAction()
{
return acAutoDownload;
}
public AbstractAction[] getActions()
{
AbstractAction[] actions = new AbstractAction[] {
acAutoDownload, acResumeFrom, null, acAbort, null, acMenu
};
AbstractAction[] array = new AbstractAction[actions.length];
System.arraycopy(actions, 0, array, 0, actions.length);
return array;
}
public JPopupMenu getPopupMenu()
{
return popup;
}
public void propertyChange(PropertyChangeEvent e)
{
// maybe we should listen to searchHistorySize events
}
public void purgeHistory()
{
jcSearch.removeAllItems();
}
public void savePrefs()
{
prefs.setShowSearchOptions(jpOptions.isVisible());
prefs.setSearchBitrate(opBox.getBitrate());
prefs.setSearchCompare(opBox.getBitrateCompare());
prefs.setSearchMediaType(jcMediaType.getSelectedIndex());
jcSearch.setHistorySize(prefs.getSearchHistorySize());
jcSearch.writeBinaryHistoryFile(new File(HISTORY_FILENAME_2));
prefs.setSearchDividerLocation(getDividerLocation());
}
public SearchSubPanel getSelectedSubPanel()
{
return (SearchSubPanel)jtpSearches.getSelectedComponent();
}
/**
* Called when a tab is selected.
*/
public void stateChanged(ChangeEvent e)
{
updateActions();
}
public void setTitle(SearchSubPanel s, String newValue)
{
int i = jtpSearches.indexOfComponent(s);
if (i != -1) {
jtpSearches.setTitleAt(i, newValue);
}
}
public IUser[] getUsers()
{
SearchSubPanel sp = getSelectedSubPanel();
if (sp != null) {
SearchResultContainer containers[] = sp.getSelectedResults();
if (containers != null) {
LinkedList l = new LinkedList();
for (int j = 0; j < containers.length; j++) {
ISearchResult[] results = containers[j].getSearchResults();
for (int i = 0; i < results.length; i++) {
l.add(results[i].getUser());
}
}
IUser[] users = new IUser[l.size()];
System.arraycopy(l.toArray(), 0, users, 0, users.length);
return users;
}
}
return null;
}
public void updateActions()
{
SearchSubPanel sp = getSelectedSubPanel();
if (sp != null) {
acAbort.setEnabled(true);
acAutoDownload.setEnabled(true);
acResumeFrom.setEnabled(true);
}
else {
acAbort.setEnabled(false);
acAutoDownload.setEnabled(false);
acResumeFrom.setEnabled(false);
}
}
/**
* Makes sure the XNap logo is shown once all sub panels are closed.
*/
private class TabListener implements ContainerListener
{
public void componentAdded(ContainerEvent e)
{
updateLayout(e.getID());
}
public void componentRemoved(ContainerEvent e)
{
updateLayout(e.getID());
}
private void updateLayout(int id)
{
if (id == ContainerEvent.COMPONENT_REMOVED
&& jtpSearches.getComponentCount() == 0) {
clCenter.show(jpCenter, "Logo");
}
else {
clCenter.show(jpCenter, "Search");
}
}
}
/**
* Performs a search.
*/
private class QueryAction extends AbstractAction {
public QueryAction()
{
putValue(Action.NAME, XNap.tr("Query"));
putValue(Action.SHORT_DESCRIPTION, XNap.tr("Perform search"));
putValue(Action.MNEMONIC_KEY, new Integer('Q'));
}
public void actionPerformed(ActionEvent event)
{
SearchFilter filter = getSearchFilter();
if (filter.getSearchText().length() > 0) {
SearchManager.getInstance().search(filter);
jcSearch.addDistinctItemAtTop(filter);
}
else {
setStatus(XNap.tr("What are you trying to search for?"));
}
}
}
/**
* Aborts all selected transfers.
*/
private class AbortAction extends AbstractAction {
public AbortAction() {
putValue(Action.NAME, XNap.tr("Stop"));
putValue(Action.SHORT_DESCRIPTION, XNap.tr("Stop search"));
putValue(Action.SMALL_ICON, XNapFrame.getIcon("stop.png"));
putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_A));
}
public void actionPerformed(ActionEvent event)
{
SearchSubPanel sp = getSelectedSubPanel();
if (sp != null) {
sp.abort();
}
}
}
/**
* Spawns a new download.
*/
private class AutoDownloadAction extends AbstractAction {
public AutoDownloadAction()
{
putValue(Action.NAME, XNap.tr("Download"));
putValue(Action.SHORT_DESCRIPTION,
XNap.tr("Download selected file(s)"));
putValue(Action.SMALL_ICON, XNapFrame.getIcon("down.png"));
putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_D));
}
public void actionPerformed(ActionEvent event)
{
SearchSubPanel sp = getSelectedSubPanel();
if (sp != null) {
SearchResultContainer[] results = sp.getSelectedResults();
if (results.length == 0) {
setStatus(XNap.tr("Please select a row first."));
}
else {
for (int i = 0; i < results.length; i++) {
download(results[i], sp.getOriginalFilter(), null);
}
}
}
}
}
/**
* Spawns a new download, resuming from an existing file.
*/
private class ResumeFromAction extends AbstractAction {
public ResumeFromAction()
{
putValue(Action.NAME, XNap.tr("Resume from") + "...");
putValue(Action.SHORT_DESCRIPTION,
XNap.tr("Resume file with a selected search results"));
putValue(Action.SMALL_ICON,
XNapFrame.getIcon("reload.png"));
}
public void actionPerformed(ActionEvent event)
{
SearchSubPanel sp = getSelectedSubPanel();
if (sp != null) {
SearchResultContainer[] results = sp.getSelectedResults();
if (results.length != 1) {
setStatus(XNap.tr("Please select a single result."));
}
else {
JFileChooser chooser = new JFileChooser();
if (chooser.showOpenDialog(SearchPanel.this)
== JFileChooser.APPROVE_OPTION) {
download(results[0], sp.getOriginalFilter(),
chooser.getSelectedFile());
}
}
}
}
}
/**
*
*/
private class SearchRepositoryAction extends AbstractAction {
public SearchRepositoryAction()
{
putValue(Action.NAME, XNap.tr("Search Library"));
putValue(Action.SHORT_DESCRIPTION,
XNap.tr("Searches library for search result."));
putValue(Action.SMALL_ICON, XNapFrame.getIcon("find.png"));
putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_S));
}
public void actionPerformed(ActionEvent event)
{
SearchSubPanel sp = getSelectedSubPanel();
if (sp != null) {
SearchResultContainer results[] = sp.getSelectedResults();
if (results.length == 0) {
setStatus(XNap.tr("Please select a row first."));
}
else {
/* if CTRL is pressed search is filesize sensitive,
this only works for java 1.4 so far. */
boolean size_sensitive = (event.getModifiers()
& event.CTRL_MASK) > 0;
for (int i = 0; i < results.length; i++) {
String filename = results[i].getShortFilename();
filename = FileHelper.name(filename);
String s = StringHelper.stripExtra(filename);
File[] files = size_sensitive ?
Repository.getInstance().search(s, results[i].getFilesize())
: Repository.getInstance().search(s);
if (files != null) {
StringBuffer sb = new StringBuffer();
for (int j = 0; j < files.length; j++) {
sb.append(files[j].getName());
if ( j < files.length - 1) {
sb.append(", ");
}
}
results[i].setStatus(sb.toString());
}
else {
results[i].setStatus("-");
}
}
}
}
}
}
/**
*
*/
private class SearchResultOpenAction extends AbstractAction {
public SearchResultOpenAction()
{
putValue(Action.NAME, prefs.getSearchResultOpenAction());
putValue(Action.SHORT_DESCRIPTION, XNap.tr("Execute command"));
putValue(Action.SMALL_ICON, XNapFrame.getIcon("fileopen.png"));
putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
}
public void actionPerformed(ActionEvent event)
{
SearchSubPanel sp = getSelectedSubPanel();
if (sp != null) {
SearchResultContainer results[] = sp.getSelectedResults();
if (results.length == 0) {
setStatus(XNap.tr("Please select a row first."));
}
else {
for (int i = 0; i < results.length; i++) {
SearchResultOpener sro
= new SearchResultOpener(results[i]);
Thread t = new Thread(sro, "SearchResultOperner");
t.start();
}
}
}
}
}
public class SearchResultOpener extends ConsoleWriter {
SearchResultContainer c;
public SearchResultOpener(SearchResultContainer c)
{
this.c = c;
StringBuffer cmd = new StringBuffer();
cmd.append(prefs.getSearchResultOpenAction());
cmd.append(" ");
cmd.append(c.getShortFilename());
String[] args = StringHelper.toArray(cmd.toString(), " ");
Process p;
try {
p = Runtime.getRuntime().exec(args);
}
catch (IOException e) {
logger.warn("open failed", e);
return;
}
this.inStream = p.getInputStream();
Console.getInstance().add(p.getErrorStream());
}
public void write(String s)
{
super.write(s);
String output = c.getStatus();
c.setStatus((output == null) ? s : output + ", " + s);
}
}
private class FilterSelectionListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
Object o = jcSearch.getSelectedItem();
if (o != null && o instanceof SearchFilter) {
setSearchFilter((SearchFilter)o);
}
}
}
private class MediaTypeSelectionListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
int index = jcMediaType.getSelectedIndex();
if (index == SearchFilter.MEDIA_ANYTHING
|| index == SearchFilter.MEDIA_AUDIO) {
opBox.setBitrateEnabled(true);
}
else {
opBox.setBitrateEnabled(false);
}
}
}
}