com.limegroup.gnutella.gui.search.ResultPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.limegroup.gnutella.gui.search.ResultPanel.java

Source

package com.limegroup.gnutella.gui.search;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.OverlayLayout;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.limegroup.gnutella.BrowseHostHandler;
import com.limegroup.gnutella.FileDetails;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.MediaType;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.gui.BoxPanel;
import com.limegroup.gnutella.gui.FileDetailsProvider;
import com.limegroup.gnutella.gui.GUIConstants;
import com.limegroup.gnutella.gui.GUIMediator;
import com.limegroup.gnutella.gui.LicenseWindow;
import com.limegroup.gnutella.gui.PaddedPanel;
import com.limegroup.gnutella.gui.ProgTabUIFactory;
import com.limegroup.gnutella.gui.tables.AbstractTableMediator;
import com.limegroup.gnutella.gui.tables.ColumnPreferenceHandler;
import com.limegroup.gnutella.gui.tables.DataLine;
import com.limegroup.gnutella.gui.tables.LimeJTable;
import com.limegroup.gnutella.gui.tables.LimeTableColumn;
import com.limegroup.gnutella.gui.tables.TableSettings;
import com.limegroup.gnutella.licenses.License;
import com.limegroup.gnutella.licenses.VerificationListener;
import com.limegroup.gnutella.search.QueryHandler;
import com.limegroup.gnutella.settings.FilterSettings;
import com.limegroup.gnutella.xml.LimeXMLDocument;

public class ResultPanel extends AbstractTableMediator implements VerificationListener, FileDetailsProvider {

    private static final Log LOG = LogFactory.getLog(ResultPanel.class);

    /**
      * Flag that a search has been stopped with a random GUID
      */
    static final GUID STOPPED_GUID = new GUID(GUID.makeGuid());

    private static final DateRenderer DATE_RENDERER = new DateRenderer();
    private static final QualityRenderer QUALITY_RENDERER = new QualityRenderer();
    private static final EndpointRenderer ENDPOINT_RENDERER = new EndpointRenderer();
    private static final ResultSpeedRenderer RESULT_SPEED_RENDERER = new ResultSpeedRenderer();

    /**
     * The TableSettings that all ResultPanels will use.
     */
    static final TableSettings SEARCH_SETTINGS = new TableSettings("SEARCH_TABLE");

    /**
     * The search info of this class.
     */
    private final SearchInformation SEARCH_INFO;

    /**
     * The GUID of the last search. (Use this to match up results.)
     *  May be a DummyGUID for the empty result list hack.
     */
    private volatile GUID guid;

    /**
     * The time (in milliseconds) that we last received a Query Result
     */
    private long timeLastResultReceived;

    /**
     * The BrowseHostHandler if this is a Browse Host tab.
     */
    private BrowseHostHandler browseHandler = null;

    /**
     * Start time of the query that this specific ResultPane handles
     */
    private long startTime = System.currentTimeMillis();

    /**
     * The CompositeFilter for this ResultPanel.
     */
    private CompositeFilter FILTER;

    /**
     * The download listener.
     */
    ActionListener DOWNLOAD_LISTENER;

    /**
     * The "download as" listener.
     */
    ActionListener DOWNLOAD_AS_LISTENER;

    /**
     * The chat listener.
     */
    ActionListener CHAT_LISTENER;

    /**
     * The browse host listener.
     */
    ActionListener BROWSE_HOST_LISTENER;

    /**
     * The stop listener.
     */
    ActionListener STOP_LISTENER;

    /**
     * Specialized constructor for creating a "dummy" result panel.
     * This should only be called once at search window creation-time.
     */
    ResultPanel(JPanel overlay) {
        super("SEARCH_TABLE");
        setupFakeTable(overlay);
        SEARCH_INFO = SearchInformation.createKeywordSearch("", null, MediaType.getAnyTypeMediaType());

        FILTER = null;
        this.guid = STOPPED_GUID;
        setButtonEnabled(SearchButtons.STOP_BUTTON_INDEX, false);
    }

    /**
     * Constructs a new ResultPanel for search results.
     *
     * @param guid the guid of the query.  Used to match results.
     * @param info the info of the search
     */
    ResultPanel(GUID guid, SearchInformation info) {
        super("SEARCH_TABLE");
        SEARCH_INFO = info;
        this.guid = guid;
        setupRealTable();
    }

    /**
     * Sets the default renderers to be used in the table.
     */
    protected void setDefaultRenderers() {
        super.setDefaultRenderers();
        TABLE.setDefaultRenderer(QualityHolder.class, QUALITY_RENDERER);
        TABLE.setDefaultRenderer(EndpointHolder.class, ENDPOINT_RENDERER);
        TABLE.setDefaultRenderer(ResultSpeed.class, RESULT_SPEED_RENDERER);
        TABLE.setDefaultRenderer(Date.class, DATE_RENDERER);
    }

    /**
     * Does nothing.
     */
    protected void updateSplashScreen() {
    }

    /**
     * Simple inner class to allow a PaddedPanel to implement Progressor.
     * This is necessary for the ProgTabUIFactory to get the percentage
     * of its tabs.
     */
    private class PPP extends PaddedPanel implements ProgTabUIFactory.Progressor {
        public double calculatePercentage(long now) {
            return ResultPanel.this.calculatePercentage(now);
        }
    }

    /**
     * Sets up the constants:
     * FILTER, MAIN_PANEL, DATA_MODEL, TABLE, BUTTON_ROW.
     */
    protected void setupConstants() {
        FILTER = new CompositeFilter(3);
        MAIN_PANEL = new PPP();
        DATA_MODEL = new TableRowFilter(FILTER);
        TABLE = new LimeJTable(DATA_MODEL);
        ((ResultPanelModel) DATA_MODEL).setTable(TABLE);
        BUTTON_ROW = new SearchButtons(this).getComponent();
    }

    /**
     * Sets SETTINGS to be the static SEARCH_SETTINGS, instead
     * of constructing a new one for each ResultPanel.
     */
    protected void buildSettings() {
        SETTINGS = SEARCH_SETTINGS;
    }

    /**
     * Creates the specialized SearchColumnSelectionMenu menu,
     * which groups XML columns together.
     */
    protected JPopupMenu createColumnSelectionMenu() {
        return (new SearchColumnSelectionMenu(TABLE)).getComponent();
    }

    /**
     * Creates the specialized column preference handler for search columns.
     */
    protected ColumnPreferenceHandler createDefaultColumnPreferencesHandler() {
        return new SearchColumnPreferenceHandler(TABLE);
    }

    /**
     * Sets DOWNLOAD_LISTENER, CHAT_LISTENER, BROWSE_HOST_LISTENER,
     * and STOP_LISTENER.
     */
    protected void buildListeners() {
        super.buildListeners();

        DOWNLOAD_LISTENER = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                SearchMediator.doDownload(ResultPanel.this);
            }
        };

        DOWNLOAD_AS_LISTENER = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                SearchMediator.doDownloadAs(ResultPanel.this);
            }
        };

        CHAT_LISTENER = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                doChat();
            }
        };

        BROWSE_HOST_LISTENER = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                SearchMediator.doBrowseHost(ResultPanel.this);
            }
        };

        STOP_LISTENER = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                stopSearch();
            }
        };
    }

    /**
     * Creates the specialized SearchResultMenu for right-click popups.
     *
     * Upgraded access from protected to public for SearchResultDisplayer.
     */
    public JPopupMenu createPopupMenu() {
        //  do not return a menu if right-clicking on the dummy panel
        if (!isKillable())
            return null;
        TableLine[] lines = getAllSelectedLines();
        return (new SearchResultMenu(this)).createMenu(lines);
    }

    /**
     * Adds a single result.
     *
     * Also marks the last time a result was received.
     */
    public void add(Object o) {
        super.add(o);
        timeLastResultReceived = System.currentTimeMillis();
    }

    /**
     * Do not allow removal of rows.
     */
    public void removeSelection() {
    }

    /**
     * Clears the table and converts the download button into a
     * wishlist button.
     */
    public void clearTable() {
        super.clearTable();
    }

    /**
     * Sets the appropriate buttons to be disabled.
     */
    public void handleNoSelection() {
        setButtonEnabled(SearchButtons.DOWNLOAD_BUTTON_INDEX, false);
        setButtonEnabled(SearchButtons.BROWSE_BUTTON_INDEX, false);
    }

    /**
     * Sets the appropriate buttons to be enabled.
     */
    public void handleSelection(int i) {
        setButtonEnabled(SearchButtons.DOWNLOAD_BUTTON_INDEX, true);

        TableLine line = (TableLine) DATA_MODEL.get(i);
        setButtonEnabled(SearchButtons.BROWSE_BUTTON_INDEX, line.isBrowseHostEnabled());
    }

    /**
     * Forwards the event to DOWNLOAD_LISTENER.
     */
    public void handleActionKey() {
        DOWNLOAD_LISTENER.actionPerformed(null);
    }

    /**
     * Gets the SearchInformation of this search.
     */
    SearchInformation getSearchInformation() {
        return SEARCH_INFO;
    }

    /**
     * Gets the query of the search.
     */
    String getQuery() {
        return SEARCH_INFO.getQuery();
    }

    /**
     * Returns the title of the search.
     * @return
     */
    String getTitle() {
        return SEARCH_INFO.getTitle();
    }

    /**
     * Gets the rich query of the search.
     */
    String getRichQuery() {
        return SEARCH_INFO.getXML();
    }

    /**
     * Stops this result panel from receiving more results.
     */
    void stopSearch() {
        final GUID guidToStop = guid;
        GUIMediator.instance().schedule(new Runnable() {
            public void run() {
                RouterService.stopQuery(guidToStop);
            }
        });
        setGUID(STOPPED_GUID);
        SearchMediator.checkToStopLime();
        setButtonEnabled(SearchButtons.STOP_BUTTON_INDEX, false);
    }

    /**
     * Chats with the host chat-enabled host in the selected
     * TableLine.
     */
    void doChat() {
        TableLine line = getSelectedLine();
        if (line == null)
            return;
        if (!line.isChatEnabled())
            return;
        line.doChat();
    }

    /**
     * Blocks the host that sent the selected result.
     */
    void blockHost() {
        TableLine line = getSelectedLine();
        if (line == null)
            return;

        String host = line.getHostname();
        int answer = GUIMediator.showYesNoMessage("SEARCH_BLOCK_HOST", " " + host + "?");
        if (answer == GUIMediator.YES_OPTION && host != null) {
            String[] bannedIps = FilterSettings.BLACK_LISTED_IP_ADDRESSES.getValue();
            // Ignore if this host is already banned.
            for (int i = 0; i < bannedIps.length; i++)
                if (host.equalsIgnoreCase(bannedIps[i]))
                    return;
            String[] newBannedIps = new String[bannedIps.length + 1];
            System.arraycopy(bannedIps, 0, newBannedIps, 0, bannedIps.length);
            newBannedIps[bannedIps.length] = host;
            FilterSettings.BLACK_LISTED_IP_ADDRESSES.setValue(newBannedIps);
            RouterService.adjustSpamFilters();
        }
    }

    /**
     * Shows a LicenseWindow for the selected line.
     */
    void showLicense() {
        TableLine line = getSelectedLine();
        if (line == null)
            return;

        URN urn = line.getSHA1Urn();
        LimeXMLDocument doc = line.getXMLDocument();
        LicenseWindow window = LicenseWindow.create(line.getLicense(), urn, doc, this);
        window.setVisible(true);
    }

    public void licenseVerified(License license) {
        // if it was valid at all, refresh.
        if (license.isValid(null))
            ((ResultPanelModel) DATA_MODEL).slowRefresh();
    }

    /**
     * Determines whether or not this panel is stopped.
     */
    boolean isStopped() {
        return guid.equals(STOPPED_GUID);
    }

    /**
     * Determines if this is empty.
     */
    boolean isEmpty() {
        return DATA_MODEL.getRowCount() == 0;
    }

    /**
     * Determines if this can be removed.
     */
    boolean isKillable() {
        // the dummy panel has a null filter, and is the only one not killable
        return FILTER != null;
    }

    /**
     * Notification that a filter on this panel has changed.
     *
     * Updates the data model with the new list, maintains the selection,
     * and moves the viewport to the first still visible selected row.
     *
     * Note that the viewport moving cannot be done by just storing the first
     * visible row, because after the filters change, the row might not exist
     * anymore.  Thus, it is necessary to store all visible rows and move to
     * the first still-visible one.
     */
    boolean filterChanged(TableLineFilter filter, int depth) {
        if (!FILTER.setFilter(depth, filter))
            return false;

        // store the selection & visible rows
        int[] rows = TABLE.getSelectedRows();
        DataLine[] lines = new DataLine[rows.length];
        List inView = new LinkedList();
        for (int i = 0; i < rows.length; i++) {
            int row = rows[i];
            DataLine line = DATA_MODEL.get(row);
            lines[i] = line;
            if (TABLE.isRowVisible(row))
                inView.add(line);
        }

        // change the table.
        ((TableRowFilter) DATA_MODEL).filtersChanged();

        // reselect & move the viewpoint to the first still visible row.
        for (int i = 0; i < rows.length; i++) {
            DataLine line = lines[i];
            int row = DATA_MODEL.getRow(line);
            if (row != -1) {
                TABLE.addRowSelectionInterval(row, row);
                if (inView != null && inView.contains(line)) {
                    TABLE.ensureRowVisible(row);
                    inView = null;
                }
            }
        }

        // update the tab count.
        SearchMediator.setTabDisplayCount(this);
        return true;
    }

    /**
     * Returns the total number of sources found for this search.
     */
    int totalSources() {
        return ((ResultPanelModel) DATA_MODEL).getTotalSources();
    }

    /**
     * Returns the total number of filtered source found for this search.
     */
    int filteredSources() {
        return ((TableRowFilter) DATA_MODEL).getFilteredSources();
    }

    /**
     * Determines whether or not repeat search is currently enabled.
     * Repeat search will be disabled if, for example, the original
     * search was performed too recently.
     *
     * @return <tt>true</tt> if the repeat search feature is currently
     *  enabled, otherwise <tt>false</tt>
     */
    boolean isRepeatSearchEnabled() {
        return FILTER != null;
    }

    void repeatSearch() {
        clearTable();
        FILTER.reset();
        startTime = System.currentTimeMillis();
        SearchMediator.setTabDisplayCount(this);
        SearchMediator.repeatSearch(this, SEARCH_INFO);
    }

    /**
     * Gets the MetadataModel used for results.
     */
    MetadataModel getMetadataModel() {
        return ((ResultPanelModel) DATA_MODEL).getMetadataModel();
    }

    /** Returns true if this is responsible for results with the given GUID */
    boolean matches(GUID otherGuid) {
        return this.guid.equals(otherGuid);
    }

    /**
     * @modifies this
     * @effects sets this' guid.  This is needed for browse host functionality.
     */
    void setGUID(GUID guid) {
        this.guid = guid;
    }

    /** Returns the guid this is responsible for. */
    byte[] getGUID() {
        return guid.bytes();
    }

    /** Returns the media type this is responsible for. */
    MediaType getMediaType() {
        return SEARCH_INFO.getMediaType();
    }

    /**
     * Sets the BrowseHostHandler.
     */
    void setBrowseHostHandler(BrowseHostHandler bhh) {
        browseHandler = bhh;
    }

    /**
     * Gets all currently selected TableLines.
     * 
     * @return empty array if no lines are selected.
     */
    TableLine[] getAllSelectedLines() {
        int[] rows = TABLE.getSelectedRows();
        if (rows == null)
            return new TableLine[0];

        TableLine[] lines = new TableLine[rows.length];
        for (int i = 0; i < rows.length; i++)
            lines[i] = (TableLine) DATA_MODEL.get(rows[i]);
        return lines;
    }

    /**
     * Gets the currently selected TableLine.
     * 
     * @return null if there is no selected line.
     */
    TableLine getSelectedLine() {
        int selected = TABLE.getSelectedRow();
        if (selected != -1)
            return (TableLine) DATA_MODEL.get(selected);
        else
            return null;
    }

    /**
     * Calculates the percentange of results that have been received for this
     * ResultPanel.
     */
    double calculatePercentage(long currentTime) {
        if (guid.equals(STOPPED_GUID))
            return 1d;

        if (SEARCH_INFO.isBrowseHostSearch()) {
            if (browseHandler != null)
                return browseHandler.getPercentComplete(currentTime);
            else
                return 0d;
        }

        // first calculate the percentage solely based on 
        // the number of results we've received.
        int ideal = QueryHandler.ULTRAPEER_RESULTS;
        double resultPerc = (double) totalSources() / ideal;

        // then calculate the percentage solely based on
        // the time we've spent querying.
        long spent = currentTime - startTime;
        double timePerc = (double) spent / QueryHandler.MAX_QUERY_TIME;

        // If the results are already enough to fill it up, just use that.
        if (resultPerc >= 1)
            return 1d;

        // Otherwise, the time percentage should fill up what remains in
        // the progress.
        timePerc = timePerc * (1 - resultPerc);

        // Return the results received + time spent.
        return resultPerc + timePerc;
    }

    /**
     * Sets extra values for non dummy ResultPanels.
     * (Used for all tables that will have results.)
     *
     * Currently:
     * - Sorts the count column, if it is visible & real-time sorting is on.
     * - Adds listeners, so the filters can be displayed when necessary.
     */
    private void setupRealTable() {
        SearchTableColumns columns = ((ResultPanelModel) DATA_MODEL).getColumns();
        LimeTableColumn countColumn = columns.getColumn(SearchTableColumns.COUNT_IDX);
        if (SETTINGS.REAL_TIME_SORT.getValue() && TABLE.isColumnVisible(countColumn.getId())) {
            DATA_MODEL.sort(SearchTableColumns.COUNT_IDX); // ascending
            DATA_MODEL.sort(SearchTableColumns.COUNT_IDX); // descending
        }

        MouseListener filterDisplayer = new MouseListener() {
            public void mouseClicked(MouseEvent e) {
                if (e.isConsumed())
                    return;
                e.consume();
                SearchMediator.panelSelected(ResultPanel.this);
            }

            public void mousePressed(MouseEvent e) {
            }

            public void mouseReleased(MouseEvent e) {
            }

            public void mouseEntered(MouseEvent e) {
            }

            public void mouseExited(MouseEvent e) {
            }
        };
        // catches around the button area.
        MAIN_PANEL.addMouseListener(filterDisplayer);
        // catches the blank area before results fill in
        SCROLL_PANE.addMouseListener(filterDisplayer);
        // catches selections on the table
        TABLE.addMouseListener(filterDisplayer);
        // catches the table header
        TABLE.getTableHeader().addMouseListener(filterDisplayer);
    }

    /**
     * Adds the overlay panel into the table & converts the button
     * to 'download'.
     */
    private void setupFakeTable(JPanel overlay) {
        MAIN_PANEL.removeAll();

        JPanel background = new JPanel();
        background.setLayout(new OverlayLayout(background));
        JPanel overlayPanel = new BoxPanel(BoxPanel.Y_AXIS);
        overlayPanel.setOpaque(false);
        overlayPanel.add(Box.createVerticalStrut(20));
        overlayPanel.add(overlay);
        overlayPanel.setMinimumSize(new Dimension(0, 0));
        JComponent table = getScrolledTablePane();
        table.setOpaque(false);
        background.add(overlayPanel);
        background.add(table);

        MAIN_PANEL.add(background);
        if (BUTTON_ROW != null) {
            MAIN_PANEL.add(Box.createVerticalStrut(GUIConstants.SEPARATOR));
            MAIN_PANEL.add(BUTTON_ROW);
        }
        MAIN_PANEL.setMinimumSize(ZERO_DIMENSION);
    }

    public FileDetails[] getFileDetails() {
        int[] sel = TABLE.getSelectedRows();
        ArrayList list = new ArrayList(sel.length);
        for (int i = 0; i < sel.length; i++) {
            TableLine line = (TableLine) DATA_MODEL.get(sel[i]);
            // prefer non-firewalled rfds for the magnet action
            RemoteFileDesc rfd = line.getNonFirewalledRFD();
            if (rfd != null) {
                list.add(rfd);
            } else {
                // fall back on first rfd
                rfd = line.getRemoteFileDesc();
                if (rfd != null) {
                    list.add(rfd);
                }
            }
        }
        if (list.isEmpty()) {
            return new FileDetails[0];
        }
        return (FileDetails[]) list.toArray(new FileDetails[0]);
    }

}