001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.gui.menu.search; 020 021import java.awt.BorderLayout; 022import java.awt.Dimension; 023import java.awt.Insets; 024import java.awt.Point; 025import java.awt.Rectangle; 026import java.awt.event.MouseAdapter; 027import java.awt.event.MouseEvent; 028import java.util.ArrayList; 029import java.util.List; 030 031import javax.swing.JScrollPane; 032import javax.swing.JTable; 033import javax.swing.JWindow; 034import javax.swing.ListSelectionModel; 035import javax.swing.Popup; 036import javax.swing.PopupFactory; 037import javax.swing.ScrollPaneConstants; 038import javax.swing.SwingUtilities; 039import javax.swing.event.ListSelectionEvent; 040import javax.swing.event.ListSelectionListener; 041import javax.swing.table.TableColumn; 042import javax.swing.table.TableColumnModel; 043 044import org.pushingpixels.flamingo.api.common.RichTooltip; 045import org.pushingpixels.flamingo.internal.ui.common.JRichTooltipPanel; 046 047import icy.main.Icy; 048import icy.search.SearchEngine; 049import icy.search.SearchResult; 050import icy.system.thread.ThreadUtil; 051 052/** 053 * This class is the most important part of this plugin: it will handle and 054 * display all local and online requests when characters are being typed in the {@link SearchBar}. 055 * 056 * @author Thomas Provoost & Stephane 057 */ 058public class SearchResultPanel extends JWindow implements ListSelectionListener 059{ 060 /** 061 * 062 */ 063 private static final long serialVersionUID = -7794681892496197765L; 064 065 private static final int ROW_HEIGHT = 48; 066 private static final int MAX_ROW = 15; 067 068 /** Associated Search Bar */ 069 final SearchBar searchBar; 070 071 /** PopupMenu */ 072 JRichTooltipPanel tooltipPanel; 073 Popup tooltip; 074 SearchResult toolTipResult; 075 boolean toolTipForceRefresh; 076 077 /** GUI */ 078 final SearchResultTableModel tableModel; 079 final JTable table; 080 final JScrollPane scrollPane; 081 082 /** 083 * Internals 084 */ 085 private final Runnable refresher; 086 private final Runnable toolTipRefresher; 087 088 public SearchResultPanel(final SearchBar sb) 089 { 090 super(Icy.getMainInterface().getMainFrame()); 091 092 searchBar = sb; 093 094 tooltipPanel = null; 095 tooltip = null; 096 toolTipResult = null; 097 toolTipForceRefresh = false; 098 099 refresher = new Runnable() 100 { 101 @Override 102 public void run() 103 { 104 refreshInternal(); 105 } 106 }; 107 108 toolTipRefresher = new Runnable() 109 { 110 @Override 111 public void run() 112 { 113 updateToolTip(); 114 } 115 }; 116 117 // build table (display 15 rows max) 118 tableModel = new SearchResultTableModel(-1); 119 120 table = new JTable(tableModel); 121 122 // sets the different column values and renderers 123 final TableColumnModel colModel = table.getColumnModel(); 124 TableColumn col; 125 126 // provider name column 127 col = colModel.getColumn(0); 128 col.setCellRenderer(new SearchProducerTableCellRenderer()); 129 col.setPreferredWidth(140); 130 131 // result text column 132 col = colModel.getColumn(1); 133 col.setCellRenderer(new SearchResultTableCellRenderer()); 134 col.setPreferredWidth(600); 135 136 // sets the table properties 137 table.setIntercellSpacing(new Dimension(0, 0)); 138 table.setShowVerticalLines(false); 139 table.setShowHorizontalLines(false); 140 table.setColumnSelectionAllowed(false); 141 table.setTableHeader(null); 142 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 143 table.getSelectionModel().addListSelectionListener(this); 144 table.addMouseListener(new MouseAdapter() 145 { 146 @Override 147 public void mouseClicked(MouseEvent e) 148 { 149 final SearchResult result = getResultAtPosition(e.getPoint()); 150 151 if ((result != null) && result.isEnabled()) 152 { 153 if (SwingUtilities.isLeftMouseButton(e)) 154 result.execute(); 155 else 156 result.executeAlternate(); 157 158 close(true); 159 e.consume(); 160 } 161 } 162 163 @Override 164 public void mouseExited(MouseEvent e) 165 { 166 // clear selection 167 table.getSelectionModel().removeSelectionInterval(0, table.getRowCount() - 1); 168 } 169 170 @Override 171 public void mouseEntered(MouseEvent e) 172 { 173 // select row under mouse position 174 final int row = table.rowAtPoint(e.getPoint()); 175 176 if (row != -1) 177 table.getSelectionModel().setSelectionInterval(row, row); 178 else 179 table.getSelectionModel().removeSelectionInterval(0, table.getRowCount() - 1); 180 } 181 }); 182 table.addMouseMotionListener(new MouseAdapter() 183 { 184 @Override 185 public void mouseMoved(MouseEvent e) 186 { 187 // select row under mouse position 188 final int row = table.rowAtPoint(e.getPoint()); 189 190 if (row != -1) 191 table.getSelectionModel().setSelectionInterval(row, row); 192 else 193 table.getSelectionModel().removeSelectionInterval(0, table.getRowCount() - 1); 194 } 195 }); 196 197 scrollPane = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 198 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); 199 200 // window used to display quick result list 201 setLayout(new BorderLayout()); 202 add(scrollPane, BorderLayout.CENTER); 203 setPreferredSize(new Dimension(600, 400)); 204 setAlwaysOnTop(true); 205 setVisible(false); 206 } 207 208 protected SearchEngine getSearchEngine() 209 { 210 return searchBar.getSearchEngine(); 211 } 212 213 /** 214 * Returns SearchResult located at specified index. 215 */ 216 protected SearchResult getResult(int index) 217 { 218 if ((index >= 0) && (index < table.getRowCount())) 219 return (SearchResult) table.getValueAt(index, SearchResultTableModel.COL_RESULT_OBJECT); 220 221 return null; 222 } 223 224 /** 225 * Returns the index in the table for the specified SearchResult (-1 if not found) 226 */ 227 protected int getRowIndex(SearchResult result) 228 { 229 if (result != null) 230 { 231 for (int i = 0; i < table.getRowCount(); i++) 232 if (result == table.getValueAt(i, SearchResultTableModel.COL_RESULT_OBJECT)) 233 return i; 234 } 235 236 return -1; 237 } 238 239 /** 240 * Returns SearchResult located at specified point position. 241 */ 242 protected SearchResult getResultAtPosition(Point pt) 243 { 244 return getResult(table.rowAtPoint(pt)); 245 } 246 247 /** 248 * Returns selected result 249 */ 250 public SearchResult getSelectedResult() 251 { 252 return getResult(table.getSelectedRow()); 253 } 254 255 /** 256 * Set selected result 257 */ 258 public void setSelectedResult(SearchResult result) 259 { 260 final int row = getRowIndex(result); 261 262 if (row != -1) 263 table.getSelectionModel().setSelectionInterval(row, row); 264 else 265 table.getSelectionModel().removeSelectionInterval(0, table.getRowCount() - 1); 266 } 267 268 void hideToolTip() 269 { 270 if (tooltip != null) 271 { 272 tooltip.hide(); 273 tooltip = null; 274 toolTipResult = null; 275 } 276 } 277 278 /** 279 * Calculates and returns panel height. 280 */ 281 int getPanelHeight() 282 { 283 final Insets margin = getInsets(); 284 final Insets marginSC = scrollPane.getInsets(); 285 final Insets marginT = table.getInsets(); 286 int result; 287 288 result = Math.min(table.getRowCount(), MAX_ROW) * ROW_HEIGHT; 289 result += (margin.top + margin.bottom) + (marginSC.top + marginSC.bottom) + (marginT.top + marginT.bottom); 290 291 return result; 292 } 293 294 /** 295 * Updates the popup menu: asks the tablemodel for the right popupmenu and 296 * displays it. 297 */ 298 void updateToolTip() 299 { 300 final SearchResult searchResult = getSelectedResult(); 301 302 // need to be done on EDT 303 ThreadUtil.invokeNow(new Runnable() 304 { 305 @Override 306 public void run() 307 { 308 if (!isVisible() || (searchResult == null)) 309 { 310 hideToolTip(); 311 return; 312 } 313 314 final RichTooltip rtp = searchResult.getRichToolTip(); 315 316 if (rtp == null) 317 { 318 hideToolTip(); 319 return; 320 } 321 322 // tool tip is not yet visible or result changed --> refresh the tool tip 323 if ((tooltip == null) || (searchResult != toolTipResult) || toolTipForceRefresh) 324 { 325 // hide out dated tool tip 326 hideToolTip(); 327 328 final Rectangle bounds = getBounds(); 329 330 tooltipPanel = new JRichTooltipPanel(rtp); 331 332 int x = bounds.x + bounds.width; 333 int y = bounds.y + (ROW_HEIGHT * table.getSelectedRow()); 334 335 // adjust vertical position 336 y -= scrollPane.getVerticalScrollBar().getValue(); 337 338 // show tooltip 339 tooltip = PopupFactory.getSharedInstance().getPopup(Icy.getMainInterface().getMainFrame(), 340 tooltipPanel, x, y); 341 tooltip.show(); 342 343 toolTipResult = searchResult; 344 toolTipForceRefresh = false; 345 } 346 } 347 }); 348 } 349 350 /** 351 * Close the results panel.<br> 352 * If <code>reset</code> is true that also reset search. 353 */ 354 public void close(boolean reset) 355 { 356 // reset search 357 if (reset) 358 searchBar.cancelSearch(); 359 360 // hide popup and panel 361 setVisible(false); 362 hideToolTip(); 363 } 364 365 /** 366 * Execute selected result. 367 * Return false if we don't have any selected result. 368 */ 369 public void executeSelected() 370 { 371 final SearchResult sr = getSelectedResult(); 372 373 if ((sr != null) && sr.isEnabled()) 374 { 375 sr.execute(); 376 close(true); 377 } 378 } 379 380 /** 381 * Update display 382 */ 383 public void refresh() 384 { 385 ThreadUtil.runSingle(refresher); 386 } 387 388 /** 389 * Update display internal 390 */ 391 void refreshInternal() 392 { 393 final SearchEngine searchEngine = getSearchEngine(); 394 final List<SearchResult> results; 395 final int resultCount; 396 final SearchResult selected; 397 398 // quick cancel 399 if (searchEngine.getLastSearch().isEmpty()) 400 { 401 results = new ArrayList<SearchResult>(); 402 resultCount = 0; 403 selected = null; 404 } 405 else 406 { 407 results = searchEngine.getResults(); 408 resultCount = results.size(); 409 // save selected 410 selected = getSelectedResult(); 411 } 412 413 // free a bit of time 414 ThreadUtil.sleep(1); 415 416 // need to be done on EDT 417 ThreadUtil.invokeNow(new Runnable() 418 { 419 @Override 420 public void run() 421 { 422 if (resultCount == 0) 423 { 424 close(false); 425 return; 426 } 427 428 // fix row height (can be changed on LAF change) 429 table.setRowHeight(ROW_HEIGHT); 430 // refresh data model 431 tableModel.setResults(results); 432 tableModel.fireTableDataChanged(); 433 434 // restore selected 435 setSelectedResult(selected); 436 437 // update bounds and display window 438 final Point p = searchBar.getLocationOnScreen(); 439 setBounds(p.x, p.y + searchBar.getHeight(), 600, getPanelHeight()); 440 441 // show the result list 442 setVisible(true); 443 444 // update tooltip 445 updateToolTip(); 446 } 447 }); 448 } 449 450 /** 451 * Selection movement in the table: up or down. 452 * 453 * @param direction 454 * : should be 1 or -1. 455 */ 456 public void moveSelection(int direction) 457 { 458 final int rowCount = table.getRowCount(); 459 460 if (rowCount == 0) 461 return; 462 463 final int rowIndex = table.getSelectedRow(); 464 final int newIndex; 465 466 if (rowIndex == -1) 467 { 468 if (direction > 0) 469 newIndex = 0; 470 else 471 newIndex = rowCount - 1; 472 } 473 else 474 newIndex = Math.abs((rowIndex + direction) % rowCount); 475 476 table.setRowSelectionInterval(newIndex, newIndex); 477 } 478 479 @Override 480 public void valueChanged(ListSelectionEvent e) 481 { 482 // selection changed --> update tooltip 483 ThreadUtil.runSingle(toolTipRefresher); 484 } 485 486 public void resultChanged(SearchResult result) 487 { 488 if (isVisible()) 489 { 490 try 491 { 492 // only update the specified result 493 final int rowIndex = getRowIndex(result); 494 495 if (rowIndex != -1) 496 tableModel.fireTableRowsUpdated(rowIndex, rowIndex); 497 } 498 catch (Exception e) 499 { 500 // ignore possible exception here 501 } 502 503 // refresh toolTip if needed 504 if (result == getSelectedResult()) 505 { 506 toolTipForceRefresh = true; 507 ThreadUtil.runSingle(toolTipRefresher); 508 } 509 } 510 } 511 512 public void resultsChanged() 513 { 514 // refresh table 515 refresh(); 516 } 517}