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.preferences; 020 021import icy.gui.component.IcyTable; 022import icy.gui.component.IcyTextField; 023import icy.gui.component.IcyTextField.TextChangeListener; 024import icy.gui.plugin.PluginDetailPanel; 025import icy.gui.util.ComponentUtil; 026import icy.network.NetworkUtil; 027import icy.plugin.PluginDescriptor; 028import icy.preferences.RepositoryPreferences; 029import icy.preferences.RepositoryPreferences.RepositoryInfo; 030import icy.resource.ResourceUtil; 031import icy.system.thread.ThreadUtil; 032import icy.util.StringUtil; 033 034import java.awt.BorderLayout; 035import java.awt.Dimension; 036import java.awt.event.ActionEvent; 037import java.awt.event.ActionListener; 038import java.awt.event.MouseAdapter; 039import java.awt.event.MouseEvent; 040import java.util.ArrayList; 041import java.util.HashSet; 042import java.util.List; 043 044import javax.swing.BorderFactory; 045import javax.swing.Box; 046import javax.swing.BoxLayout; 047import javax.swing.ImageIcon; 048import javax.swing.JButton; 049import javax.swing.JComboBox; 050import javax.swing.JLabel; 051import javax.swing.JPanel; 052import javax.swing.JScrollPane; 053import javax.swing.JTable; 054import javax.swing.ListSelectionModel; 055import javax.swing.event.ListSelectionEvent; 056import javax.swing.event.ListSelectionListener; 057import javax.swing.table.AbstractTableModel; 058import javax.swing.table.TableColumn; 059import javax.swing.table.TableColumnModel; 060 061/** 062 * @author Stephane 063 */ 064public abstract class PluginListPreferencePanel extends PreferencePanel 065 implements TextChangeListener, ListSelectionListener 066{ 067 /** 068 * 069 */ 070 private static final long serialVersionUID = -2718763355377652489L; 071 072 static final String[] columnNames = {"", "Name", "Version", "State", "Enabled"}; 073 static final String[] columnIds = {"Icon", "Name", "Version", "State", "Enabled"}; 074 075 List<PluginDescriptor> plugins; 076 077 /** 078 * gui 079 */ 080 final AbstractTableModel tableModel; 081 final JTable table; 082 083 final JComboBox repository; 084 final JPanel repositoryPanel; 085 final IcyTextField filter; 086 final JButton refreshButton; 087 final JButton documentationButton; 088 final JButton detailButton; 089 final JButton action1Button; 090 final JButton action2Button; 091 092 private final Runnable buttonsStateUpdater; 093 private final Runnable tableDataRefresher; 094 private final Runnable pluginsListRefresher; 095 private final Runnable repositoriesUpdater; 096 097 final ActionListener repositoryActionListener; 098 099 PluginListPreferencePanel(PreferenceFrame parent, String nodeName, String parentName) 100 { 101 super(parent, nodeName, parentName); 102 103 plugins = new ArrayList<PluginDescriptor>(); 104 105 buttonsStateUpdater = new Runnable() 106 { 107 @Override 108 public void run() 109 { 110 // need to be done on EDT 111 ThreadUtil.invokeNow(new Runnable() 112 { 113 @Override 114 public void run() 115 { 116 updateButtonsStateInternal(); 117 } 118 }); 119 } 120 }; 121 tableDataRefresher = new Runnable() 122 { 123 @Override 124 public void run() 125 { 126 // need to be done on EDT 127 ThreadUtil.invokeNow(new Runnable() 128 { 129 @Override 130 public void run() 131 { 132 refreshTableDataInternal(); 133 } 134 }); 135 } 136 }; 137 pluginsListRefresher = new Runnable() 138 { 139 @Override 140 public void run() 141 { 142 refreshPluginsInternal(); 143 } 144 }; 145 repositoriesUpdater = new Runnable() 146 { 147 @Override 148 public void run() 149 { 150 // need to be done on EDT 151 ThreadUtil.invokeNow(new Runnable() 152 { 153 @Override 154 public void run() 155 { 156 updateRepositoriesInternal(); 157 } 158 }); 159 } 160 }; 161 repositoryActionListener = new ActionListener() 162 { 163 @Override 164 public void actionPerformed(ActionEvent e) 165 { 166 repositoryChanged(); 167 } 168 }; 169 170 repository = new JComboBox(); 171 repository.setToolTipText("Select a repository"); 172 repository.addActionListener(repositoryActionListener); 173 174 repositoryPanel = new JPanel(); 175 repositoryPanel.setLayout(new BoxLayout(repositoryPanel, BoxLayout.PAGE_AXIS)); 176 repositoryPanel.setVisible(false); 177 178 final JPanel internalRepPanel = new JPanel(); 179 internalRepPanel.setLayout(new BoxLayout(internalRepPanel, BoxLayout.LINE_AXIS)); 180 181 internalRepPanel.add(new JLabel("Repository :")); 182 internalRepPanel.add(Box.createHorizontalStrut(8)); 183 internalRepPanel.add(repository); 184 internalRepPanel.add(Box.createHorizontalGlue()); 185 186 repositoryPanel.add(internalRepPanel); 187 repositoryPanel.add(Box.createVerticalStrut(8)); 188 189 // need filter before load() 190 filter = new IcyTextField(); 191 filter.addTextChangeListener(this); 192 193 // build buttons panel 194 final Dimension buttonsDim = new Dimension(100, 24); 195 196 refreshButton = new JButton("Reload list"); 197 refreshButton.addActionListener(new ActionListener() 198 { 199 @Override 200 public void actionPerformed(ActionEvent e) 201 { 202 reloadPlugins(); 203 } 204 }); 205 ComponentUtil.setFixedSize(refreshButton, buttonsDim); 206 207 documentationButton = new JButton("Online doc"); 208 documentationButton.setToolTipText("Open the online documentation"); 209 documentationButton.addActionListener(new ActionListener() 210 { 211 @Override 212 public void actionPerformed(ActionEvent e) 213 { 214 final List<PluginDescriptor> selectedPlugins = getSelectedPlugins(); 215 216 // open plugin web page 217 if (selectedPlugins.size() == 1) 218 NetworkUtil.openBrowser(selectedPlugins.get(0).getWeb()); 219 } 220 }); 221 ComponentUtil.setFixedSize(documentationButton, buttonsDim); 222 223 detailButton = new JButton("Show detail"); 224 detailButton.addActionListener(new ActionListener() 225 { 226 @Override 227 public void actionPerformed(ActionEvent e) 228 { 229 final List<PluginDescriptor> selectedPlugins = getSelectedPlugins(); 230 231 // open the detail 232 if (selectedPlugins.size() == 1) 233 new PluginDetailPanel(selectedPlugins.get(0)); 234 } 235 }); 236 ComponentUtil.setFixedSize(detailButton, buttonsDim); 237 238 action1Button = new JButton("null"); 239 action1Button.addActionListener(new ActionListener() 240 { 241 @Override 242 public void actionPerformed(ActionEvent e) 243 { 244 doAction1(); 245 } 246 }); 247 action1Button.setVisible(false); 248 ComponentUtil.setFixedSize(action1Button, buttonsDim); 249 250 action2Button = new JButton("null"); 251 action2Button.addActionListener(new ActionListener() 252 { 253 @Override 254 public void actionPerformed(ActionEvent e) 255 { 256 doAction2(); 257 } 258 }); 259 action2Button.setVisible(false); 260 ComponentUtil.setFixedSize(action2Button, buttonsDim); 261 262 final JPanel buttonsPanel = new JPanel(); 263 buttonsPanel.setBorder(BorderFactory.createEmptyBorder(4, 8, 8, 8)); 264 buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.PAGE_AXIS)); 265 266 buttonsPanel.add(refreshButton); 267 buttonsPanel.add(Box.createVerticalStrut(34)); 268 buttonsPanel.add(documentationButton); 269 buttonsPanel.add(Box.createVerticalStrut(8)); 270 buttonsPanel.add(detailButton); 271 buttonsPanel.add(Box.createVerticalStrut(8)); 272 buttonsPanel.add(action1Button); 273 buttonsPanel.add(Box.createVerticalStrut(8)); 274 buttonsPanel.add(action2Button); 275 buttonsPanel.add(Box.createVerticalStrut(8)); 276 buttonsPanel.add(Box.createVerticalGlue()); 277 278 // build table 279 tableModel = new AbstractTableModel() 280 { 281 /** 282 * 283 */ 284 private static final long serialVersionUID = -8573364273165723214L; 285 286 @Override 287 public int getColumnCount() 288 { 289 return columnNames.length; 290 } 291 292 @Override 293 public String getColumnName(int column) 294 { 295 return columnNames[column]; 296 } 297 298 @Override 299 public int getRowCount() 300 { 301 return plugins.size(); 302 } 303 304 @Override 305 public Object getValueAt(int row, int column) 306 { 307 if (row < plugins.size()) 308 { 309 final PluginDescriptor plugin = plugins.get(row); 310 311 switch (column) 312 { 313 case 0: 314 if (plugin.isIconLoaded()) 315 return ResourceUtil.scaleIcon(plugin.getIcon(), 32); 316 317 loadIconAsync(plugin); 318 return ResourceUtil.scaleIcon(PluginDescriptor.DEFAULT_ICON, 32); 319 320 case 1: 321 return plugin.getName(); 322 323 case 2: 324 return plugin.getVersion().toString(); 325 326 case 3: 327 return getStateValue(plugin); 328 329 case 4: 330 return Boolean.valueOf(isActive(plugin)); 331 } 332 } 333 334 return ""; 335 } 336 337 @Override 338 public void setValueAt(Object aValue, int rowIndex, int columnIndex) 339 { 340 if (rowIndex < plugins.size()) 341 { 342 final PluginDescriptor plugin = plugins.get(rowIndex); 343 344 if (columnIndex == 4) 345 { 346 if (aValue instanceof Boolean) 347 setActive(plugin, ((Boolean) aValue).booleanValue()); 348 } 349 } 350 } 351 352 @Override 353 public boolean isCellEditable(int row, int column) 354 { 355 return (column == 4); 356 } 357 358 @Override 359 public Class<?> getColumnClass(int columnIndex) 360 { 361 switch (columnIndex) 362 { 363 case 0: 364 return ImageIcon.class; 365 case 4: 366 return Boolean.class; 367 default: 368 return String.class; 369 } 370 } 371 }; 372 373 table = new IcyTable(tableModel); 374 375 final TableColumnModel colModel = table.getColumnModel(); 376 TableColumn col; 377 378 // columns setting 379 col = colModel.getColumn(0); 380 col.setIdentifier(columnIds[0]); 381 col.setMinWidth(32); 382 col.setPreferredWidth(32); 383 col.setMaxWidth(32); 384 385 col = colModel.getColumn(1); 386 col.setIdentifier(columnIds[1]); 387 col.setMinWidth(120); 388 col.setPreferredWidth(200); 389 col.setMaxWidth(500); 390 391 col = colModel.getColumn(2); 392 col.setIdentifier(columnIds[2]); 393 col.setMinWidth(60); 394 col.setPreferredWidth(60); 395 col.setMaxWidth(60); 396 397 col = colModel.getColumn(3); 398 col.setIdentifier(columnIds[3]); 399 col.setMinWidth(70); 400 col.setPreferredWidth(90); 401 col.setMaxWidth(120); 402 403 col = colModel.getColumn(4); 404 col.setIdentifier(columnIds[4]); 405 col.setMinWidth(60); 406 col.setPreferredWidth(60); 407 col.setMaxWidth(60); 408 409 table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 410 table.getSelectionModel().addListSelectionListener(this); 411 table.setRowHeight(32); 412 table.setColumnSelectionAllowed(false); 413 table.setRowSelectionAllowed(true); 414 table.setShowVerticalLines(false); 415 table.setAutoCreateRowSorter(true); 416 // sort on name by default 417 table.getRowSorter().toggleSortOrder(1); 418 table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); 419 table.addMouseListener(new MouseAdapter() 420 { 421 @Override 422 public void mouseClicked(MouseEvent me) 423 { 424 if (!me.isConsumed()) 425 { 426 if (me.getClickCount() == 2) 427 { 428 // show detail 429 detailButton.doClick(); 430 me.consume(); 431 } 432 } 433 } 434 }); 435 436 final JPanel tableTopPanel = new JPanel(); 437 438 tableTopPanel.setLayout(new BoxLayout(tableTopPanel, BoxLayout.PAGE_AXIS)); 439 440 tableTopPanel.add(Box.createVerticalStrut(2)); 441 tableTopPanel.add(repositoryPanel); 442 tableTopPanel.add(filter); 443 tableTopPanel.add(Box.createVerticalStrut(8)); 444 tableTopPanel.add(table.getTableHeader()); 445 446 final JPanel tablePanel = new JPanel(); 447 448 tablePanel.setLayout(new BorderLayout()); 449 450 tablePanel.add(tableTopPanel, BorderLayout.NORTH); 451 tablePanel.add(new JScrollPane(table), BorderLayout.CENTER); 452 453 mainPanel.setLayout(new BorderLayout()); 454 455 mainPanel.add(tablePanel, BorderLayout.CENTER); 456 mainPanel.add(buttonsPanel, BorderLayout.EAST); 457 458 mainPanel.validate(); 459 } 460 461 protected void loadIconAsync(final PluginDescriptor plugin) 462 { 463 ThreadUtil.bgRun(new Runnable() 464 { 465 @Override 466 public void run() 467 { 468 // icon correctly loaded ? 469 if (plugin.loadIcon()) 470 refreshTableData(); 471 } 472 }); 473 } 474 475 @Override 476 protected void closed() 477 { 478 super.closed(); 479 480 // do not retains plugins when frame is closed 481 plugins.clear(); 482 } 483 484 private List<PluginDescriptor> filterList(List<PluginDescriptor> list, String filter) 485 { 486 final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>(); 487 final boolean empty = StringUtil.isEmpty(filter, true); 488 final String filterUp; 489 490 if (!empty) 491 filterUp = filter.toUpperCase(); 492 else 493 filterUp = ""; 494 495 for (PluginDescriptor plugin : list) 496 { 497 final String classname = plugin.getClassName().toUpperCase(); 498 final String name = plugin.getName().toUpperCase(); 499 final String desc = plugin.getDescription().toUpperCase(); 500 501 // search in name and description 502 if (empty || (classname.indexOf(filterUp) != -1) || (name.indexOf(filterUp) != -1) 503 || (desc.indexOf(filterUp) != -1)) 504 result.add(plugin); 505 } 506 507 return result; 508 } 509 510 protected boolean isActive(PluginDescriptor plugin) 511 { 512 return false; 513 } 514 515 protected void setActive(PluginDescriptor plugin, boolean value) 516 { 517 } 518 519 protected abstract void doAction1(); 520 521 protected abstract void doAction2(); 522 523 protected abstract void repositoryChanged(); 524 525 protected abstract void reloadPlugins(); 526 527 protected abstract String getStateValue(PluginDescriptor plugin); 528 529 protected abstract List<PluginDescriptor> getPlugins(); 530 531 protected int getPluginTableIndex(int rowIndex) 532 { 533 if (rowIndex == -1) 534 return rowIndex; 535 536 try 537 { 538 539 return table.convertRowIndexToView(rowIndex); 540 } 541 catch (IndexOutOfBoundsException e) 542 { 543 return -1; 544 } 545 } 546 547 protected int getPluginIndex(PluginDescriptor plugin) 548 { 549 return plugins.indexOf(plugin); 550 } 551 552 protected int getPluginModelIndex(PluginDescriptor plugin) 553 { 554 return getPluginIndex(plugin); 555 } 556 557 protected int getPluginTableIndex(PluginDescriptor plugin) 558 { 559 return getPluginTableIndex(getPluginModelIndex(plugin)); 560 } 561 562 protected int getPluginIndex(String pluginClassName) 563 { 564 for (int i = 0; i < plugins.size(); i++) 565 { 566 final PluginDescriptor plugin = plugins.get(i); 567 568 if (plugin.getClassName().equals(pluginClassName)) 569 return i; 570 } 571 572 return -1; 573 } 574 575 protected int getPluginModelIndex(String pluginClassName) 576 { 577 return getPluginIndex(pluginClassName); 578 } 579 580 protected int getPluginTableIndex(String pluginClassName) 581 { 582 return getPluginTableIndex(getPluginModelIndex(pluginClassName)); 583 } 584 585 List<PluginDescriptor> getSelectedPlugins() 586 { 587 final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>(); 588 589 final int[] rows = table.getSelectedRows(); 590 if (rows.length == 0) 591 return result; 592 593 final List<PluginDescriptor> cachedPlugins = plugins; 594 595 for (int i = 0; i < rows.length; i++) 596 { 597 try 598 { 599 final int index = table.convertRowIndexToModel(rows[i]); 600 if (index < cachedPlugins.size()) 601 result.add(cachedPlugins.get(index)); 602 } 603 catch (IndexOutOfBoundsException e) 604 { 605 // ignore as async process can cause it 606 } 607 } 608 609 return result; 610 } 611 612 /** 613 * Select the specified list of ROI in the ROI Table 614 */ 615 void setSelectedPlugins(HashSet<PluginDescriptor> newSelected) 616 { 617 final List<PluginDescriptor> modelPlugins = plugins; 618 final ListSelectionModel selectionModel = table.getSelectionModel(); 619 620 // start selection change 621 selectionModel.setValueIsAdjusting(true); 622 try 623 { 624 // start by clearing selection 625 selectionModel.clearSelection(); 626 627 for (int i = 0; i < modelPlugins.size(); i++) 628 { 629 final PluginDescriptor plugin = modelPlugins.get(i); 630 631 // HashSet provide fast "contains" 632 if (newSelected.contains(plugin)) 633 { 634 try 635 { 636 // convert model index to view index 637 final int ind = table.convertRowIndexToView(i); 638 if (ind != -1) 639 selectionModel.addSelectionInterval(ind, ind); 640 } 641 catch (IndexOutOfBoundsException e) 642 { 643 // ignore 644 } 645 } 646 } 647 } 648 finally 649 { 650 // end selection change 651 selectionModel.setValueIsAdjusting(false); 652 } 653 } 654 655 protected void refreshPluginsInternal() 656 { 657 plugins = filterList(getPlugins(), filter.getText()); 658 // refresh table data 659 refreshTableData(); 660 } 661 662 protected final void refreshPlugins() 663 { 664 ThreadUtil.runSingle(pluginsListRefresher); 665 } 666 667 protected void updateButtonsStateInternal() 668 { 669 final List<PluginDescriptor> selectedPlugins = getSelectedPlugins(); 670 final boolean singleSelection = (selectedPlugins.size() == 1); 671 final PluginDescriptor singlePlugin = singleSelection ? selectedPlugins.get(0) : null; 672 673 detailButton.setEnabled(singleSelection); 674 documentationButton.setEnabled(singleSelection && !StringUtil.isEmpty(singlePlugin.getWeb())); 675 } 676 677 protected final void updateButtonsState() 678 { 679 ThreadUtil.runSingle(buttonsStateUpdater); 680 } 681 682 protected void updateRepositoriesInternal() 683 { 684 // final RepositoryPreferencePanel panel = (RepositoryPreferencePanel) 685 // getPreferencePanel(RepositoryPreferencePanel.class); 686 // // refresh repositories list (use list from GUI) 687 // final ArrayList<RepositoryInfo> repositeries = panel.repositories; 688 689 // refresh repositories list 690 final List<RepositoryInfo> repositeries = RepositoryPreferences.getRepositeries(); 691 final RepositoryInfo savedRepository = (RepositoryInfo) repository.getSelectedItem(); 692 693 // needed to disable events during update time 694 repository.removeActionListener(repositoryActionListener); 695 696 repository.removeAllItems(); 697 for (RepositoryInfo repos : repositeries) 698 if (repos.isEnabled()) 699 repository.addItem(repos); 700 701 repository.addActionListener(repositoryActionListener); 702 703 boolean selected = false; 704 705 // try to set back the old selected repository 706 if (savedRepository != null) 707 { 708 final String repositoryName = savedRepository.getName(); 709 710 for (int ind = 0; ind < repository.getItemCount(); ind++) 711 { 712 final RepositoryInfo repo = (RepositoryInfo) repository.getItemAt(ind); 713 714 if ((repo != null) && (repo.getName().equals(repositoryName))) 715 { 716 repository.setSelectedIndex(ind); 717 selected = true; 718 break; 719 } 720 } 721 } 722 723 // manually launch the action 724 if (!selected) 725 repository.setSelectedIndex((repository.getItemCount() > 0) ? 0 : -1); 726 727 // avoid automatic minimum size here 728 repository.setMinimumSize(new Dimension(48, 18)); 729 } 730 731 protected final void updateRepositories() 732 { 733 ThreadUtil.runSingle(repositoriesUpdater); 734 } 735 736 protected void refreshTableDataInternal() 737 { 738 final List<PluginDescriptor> plugins = getSelectedPlugins(); 739 740 try 741 { 742 tableModel.fireTableDataChanged(); 743 } 744 catch (Throwable t) 745 { 746 // sometime sorting can throw exception, ignore them... 747 } 748 749 // restore previous selected plugins if possible 750 setSelectedPlugins(new HashSet<PluginDescriptor>(plugins)); 751 // update button state 752 buttonsStateUpdater.run(); 753 } 754 755 protected final void refreshTableData() 756 { 757 ThreadUtil.runSingle(tableDataRefresher); 758 } 759 760 protected void pluginsChanged() 761 { 762 refreshPlugins(); 763 } 764 765 @Override 766 protected void load() 767 { 768 769 } 770 771 @Override 772 protected void save() 773 { 774 // reload repositories as some parameter as beta flag can have changed 775 updateRepositories(); 776 } 777 778 @Override 779 public void textChanged(IcyTextField source, boolean validate) 780 { 781 pluginsChanged(); 782 } 783 784 @Override 785 public void valueChanged(ListSelectionEvent e) 786 { 787 final int selected = table.getSelectedRow(); 788 789 if (!e.getValueIsAdjusting() && (selected != -1)) 790 { 791 final int fi = e.getFirstIndex(); 792 final int li = e.getLastIndex(); 793 794 if ((fi == -1) || ((fi <= selected) && (li >= selected))) 795 updateButtonsState(); 796 } 797 } 798}