/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy 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 3 of the License, or * (at your option) any later version. * * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.gui.menu.search; import icy.main.Icy; import icy.search.SearchEngine; import icy.search.SearchResult; import icy.system.thread.ThreadUtil; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; import javax.swing.JButton; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JWindow; import javax.swing.ListSelectionModel; import javax.swing.Popup; import javax.swing.PopupFactory; import javax.swing.ScrollPaneConstants; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import org.pushingpixels.flamingo.api.common.RichTooltip; import org.pushingpixels.flamingo.internal.ui.common.JRichTooltipPanel; /** * This class is the most important part of this plugin: it will handle and * display all local and online requests when characters are being typed in the {@link SearchBar}. * * @author Thomas Provoost & Stephane */ public class SearchResultPanel extends JWindow implements ListSelectionListener { /** * */ private static final long serialVersionUID = -7794681892496197765L; private static final int ROW_HEIGHT = 48; private static final int MAX_ROW = 15; /** Associated Search Bar */ final SearchBar searchBar; /** PopupMenu */ JRichTooltipPanel tooltipPanel; Popup tooltip; SearchResult toolTipResult; boolean toolTipForceRefresh; /** GUI */ final SearchResultTableModel tableModel; final JTable table; final JButton moreResultBtn; final JScrollPane scrollPane; /** * Internals */ private final Runnable refresher; private final Runnable toolTipRefresher; boolean firstResultsDisplay; public SearchResultPanel(final SearchBar sb) { super(Icy.getMainInterface().getMainFrame()); searchBar = sb; tooltipPanel = null; tooltip = null; toolTipResult = null; toolTipForceRefresh = false; firstResultsDisplay = true; refresher = new Runnable() { @Override public void run() { refreshInternal(); } }; toolTipRefresher = new Runnable() { @Override public void run() { updateToolTip(); } }; // build table (display 15 rows max) tableModel = new SearchResultTableModel(MAX_ROW); table = new JTable(tableModel); // sets the different column values and renderers final TableColumnModel colModel = table.getColumnModel(); TableColumn col; // provider name column col = colModel.getColumn(0); col.setCellRenderer(new SearchProducerTableCellRenderer()); col.setPreferredWidth(140); // result text column col = colModel.getColumn(1); col.setCellRenderer(new SearchResultTableCellRenderer()); col.setPreferredWidth(600); // sets the table properties table.setIntercellSpacing(new Dimension(0, 0)); table.setShowVerticalLines(false); table.setShowHorizontalLines(false); table.setColumnSelectionAllowed(false); table.setTableHeader(null); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.getSelectionModel().addListSelectionListener(this); table.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { final SearchResult result = getResultAtPosition(e.getPoint()); if ((result != null) && result.isEnabled()) { if (SwingUtilities.isLeftMouseButton(e)) result.execute(); else result.executeAlternate(); close(true); e.consume(); } } @Override public void mouseExited(MouseEvent e) { // clear selection table.getSelectionModel().removeSelectionInterval(0, table.getRowCount() - 1); } @Override public void mouseEntered(MouseEvent e) { // select row under mouse position final int row = table.rowAtPoint(e.getPoint()); if (row != -1) table.getSelectionModel().setSelectionInterval(row, row); else table.getSelectionModel().removeSelectionInterval(0, table.getRowCount() - 1); } }); table.addMouseMotionListener(new MouseAdapter() { @Override public void mouseMoved(MouseEvent e) { // select row under mouse position final int row = table.rowAtPoint(e.getPoint()); if (row != -1) table.getSelectionModel().setSelectionInterval(row, row); else table.getSelectionModel().removeSelectionInterval(0, table.getRowCount() - 1); } }); // build GUI moreResultBtn = new JButton(""); moreResultBtn.setHorizontalAlignment(SwingConstants.RIGHT); moreResultBtn.setVerticalAlignment(SwingConstants.CENTER); moreResultBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { showCompleteList(); } }); scrollPane = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); // window used to display quick result list setLayout(new BorderLayout()); add(scrollPane, BorderLayout.CENTER); add(moreResultBtn, BorderLayout.SOUTH); setPreferredSize(new Dimension(600, 400)); setAlwaysOnTop(true); setVisible(false); } protected SearchEngine getSearchEngine() { return searchBar.getSearchEngine(); } /** * Returns SearchResult located at specified index. */ protected SearchResult getResult(int index) { if ((index >= 0) && (index < table.getRowCount())) return (SearchResult) table.getValueAt(index, SearchResultTableModel.COL_RESULT_OBJECT); return null; } /** * Returns the index in the table for the specified SearchResult (-1 if not found) */ protected int getRowIndex(SearchResult result) { if (result != null) { for (int i = 0; i < table.getRowCount(); i++) if (result == table.getValueAt(i, SearchResultTableModel.COL_RESULT_OBJECT)) return i; } return -1; } /** * Returns SearchResult located at specified point position. */ protected SearchResult getResultAtPosition(Point pt) { return getResult(table.rowAtPoint(pt)); } /** * Returns selected result */ public SearchResult getSelectedResult() { return getResult(table.getSelectedRow()); } /** * Set selected result */ public void setSelectedResult(SearchResult result) { final int row = getRowIndex(result); if (row != -1) table.getSelectionModel().setSelectionInterval(row, row); else table.getSelectionModel().removeSelectionInterval(0, table.getRowCount() - 1); } void showCompleteList() { // no more limit on row count tableModel.setMaxRowCount(-1); // hide button moreResultBtn.setVisible(false); // update size setSize(600, getPanelHeight()); } void hideToolTip() { if (tooltip != null) { tooltip.hide(); tooltip = null; toolTipResult = null; } } /** * Calculates and returns panel height. */ int getPanelHeight() { final Insets margin = getInsets(); final Insets marginSC = scrollPane.getInsets(); final Insets marginT = table.getInsets(); int result; result = Math.min(table.getRowCount(), MAX_ROW) * ROW_HEIGHT; result += (margin.top + margin.bottom) + (marginSC.top + marginSC.bottom) + (marginT.top + marginT.bottom); if (moreResultBtn.isVisible()) result += moreResultBtn.getPreferredSize().height; return result; } /** * Updates the popup menu: asks the tablemodel for the right popupmenu and * displays it. */ void updateToolTip() { final SearchResult searchResult = getSelectedResult(); // need to be done on EDT ThreadUtil.invokeNow(new Runnable() { @Override public void run() { if (!isVisible() || (searchResult == null)) { hideToolTip(); return; } final RichTooltip rtp = searchResult.getRichToolTip(); if (rtp == null) { hideToolTip(); return; } // tool tip is not yet visible or result changed --> refresh the tool tip if ((tooltip == null) || (searchResult != toolTipResult) || toolTipForceRefresh) { // hide out dated tool tip hideToolTip(); final Rectangle bounds = getBounds(); tooltipPanel = new JRichTooltipPanel(rtp); int x = bounds.x + bounds.width; int y = bounds.y + (ROW_HEIGHT * table.getSelectedRow()); // adjust vertical position y -= scrollPane.getVerticalScrollBar().getValue(); // show tooltip tooltip = PopupFactory.getSharedInstance().getPopup(Icy.getMainInterface().getMainFrame(), tooltipPanel, x, y); tooltip.show(); toolTipResult = searchResult; toolTipForceRefresh = false; } } }); } /** * Close the results panel.<br> * If <code>reset</br> is true that also reset search. */ public void close(boolean reset) { // reset search if (reset) searchBar.cancelSearch(); // hide popup and panel setVisible(false); hideToolTip(); } /** * Execute selected result. * Return false if we don't have any selected result. */ public void executeSelected() { final SearchResult sr = getSelectedResult(); if ((sr != null) && sr.isEnabled()) { sr.execute(); close(true); } } /** * Update display */ public void refresh() { ThreadUtil.runSingle(refresher); } /** * Update display internal */ void refreshInternal() { final SearchEngine searchEngine = getSearchEngine(); final List<SearchResult> results; final int resultCount; final SearchResult selected; // quick cancel if (searchEngine.getLastSearch().isEmpty()) { results = new ArrayList<SearchResult>(); resultCount = 0; selected = null; } else { results = searchEngine.getResults(); resultCount = results.size(); // save selected selected = getSelectedResult(); } // free a bit of time ThreadUtil.sleep(1); // need to be done on EDT ThreadUtil.invokeNow(new Runnable() { @Override public void run() { if (resultCount == 0) { close(false); return; } // fix row height (can be changed on LAF change) table.setRowHeight(ROW_HEIGHT); // refresh data model tableModel.setResults(results); if (firstResultsDisplay) { // limit result list size to MAX_ROW and refresh table data if (tableModel.getMaxRowCount() != MAX_ROW) tableModel.setMaxRowCount(MAX_ROW); else tableModel.fireTableDataChanged(); // no more need to re init the limited display firstResultsDisplay = false; } else tableModel.fireTableDataChanged(); // restore selected setSelectedResult(selected); final int maxRow = tableModel.getMaxRowCount(); // result list do not display all results ? if ((maxRow > 0) && (resultCount > maxRow)) { moreResultBtn.setText(maxRow + " / " + resultCount + " (show all)"); moreResultBtn.setVisible(true); } else moreResultBtn.setVisible(false); // update bounds and display window final Point p = searchBar.getLocationOnScreen(); setBounds(p.x, p.y + searchBar.getHeight(), 600, getPanelHeight()); // show the result list setVisible(true); // update tooltip updateToolTip(); } }); } /** * Selection movement in the table: up or down. * * @param direction * : should be 1 or -1. */ public void moveSelection(int direction) { final int rowCount = table.getRowCount(); if (rowCount == 0) return; final int rowIndex = table.getSelectedRow(); final int newIndex; if (rowIndex == -1) { if (direction > 0) newIndex = 0; else newIndex = rowCount - 1; } else newIndex = Math.abs((rowIndex + direction) % rowCount); table.setRowSelectionInterval(newIndex, newIndex); } @Override public void valueChanged(ListSelectionEvent e) { // selection changed --> update tooltip ThreadUtil.runSingle(toolTipRefresher); } public void searchStarted() { firstResultsDisplay = true; } public void resultChanged(SearchResult result) { if (isVisible()) { try { // only update the specified result final int rowIndex = getRowIndex(result); if (rowIndex != -1) tableModel.fireTableRowsUpdated(rowIndex, rowIndex); } catch (Exception e) { // ignore possible exception here } // refresh toolTip if needed if (result == getSelectedResult()) { toolTipForceRefresh = true; ThreadUtil.runSingle(toolTipRefresher); } } } public void resultsChanged() { // refresh table refresh(); } }