001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.plugin; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trc; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.awt.BorderLayout; 009import java.awt.Component; 010import java.awt.GridBagConstraints; 011import java.awt.GridBagLayout; 012import java.awt.GridLayout; 013import java.awt.Insets; 014import java.awt.event.ActionEvent; 015import java.awt.event.ComponentAdapter; 016import java.awt.event.ComponentEvent; 017import java.lang.reflect.InvocationTargetException; 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Set; 024import java.util.regex.Pattern; 025 026import javax.swing.AbstractAction; 027import javax.swing.BorderFactory; 028import javax.swing.ButtonGroup; 029import javax.swing.DefaultListModel; 030import javax.swing.JButton; 031import javax.swing.JCheckBox; 032import javax.swing.JLabel; 033import javax.swing.JList; 034import javax.swing.JOptionPane; 035import javax.swing.JPanel; 036import javax.swing.JRadioButton; 037import javax.swing.JScrollPane; 038import javax.swing.JTabbedPane; 039import javax.swing.JTextArea; 040import javax.swing.SwingUtilities; 041import javax.swing.UIManager; 042 043import org.openstreetmap.josm.actions.ExpertToggleAction; 044import org.openstreetmap.josm.data.Preferences; 045import org.openstreetmap.josm.data.Version; 046import org.openstreetmap.josm.gui.HelpAwareOptionPane; 047import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; 048import org.openstreetmap.josm.gui.MainApplication; 049import org.openstreetmap.josm.gui.help.HelpUtil; 050import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting; 051import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 052import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory; 053import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; 054import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferencePanel; 055import org.openstreetmap.josm.gui.util.GuiHelper; 056import org.openstreetmap.josm.gui.widgets.FilterField; 057import org.openstreetmap.josm.plugins.PluginDownloadTask; 058import org.openstreetmap.josm.plugins.PluginHandler; 059import org.openstreetmap.josm.plugins.PluginInformation; 060import org.openstreetmap.josm.plugins.ReadLocalPluginInformationTask; 061import org.openstreetmap.josm.plugins.ReadRemotePluginInformationTask; 062import org.openstreetmap.josm.spi.preferences.Config; 063import org.openstreetmap.josm.tools.GBC; 064import org.openstreetmap.josm.tools.ImageProvider; 065import org.openstreetmap.josm.tools.Logging; 066 067/** 068 * Preference settings for plugins. 069 * @since 168 070 */ 071public final class PluginPreference extends DefaultTabPreferenceSetting { 072 073 /** 074 * Factory used to create a new {@code PluginPreference}. 075 */ 076 public static class Factory implements PreferenceSettingFactory { 077 @Override 078 public PreferenceSetting createPreferenceSetting() { 079 return new PluginPreference(); 080 } 081 } 082 083 private PluginListPanel pnlPluginPreferences; 084 private PluginPreferencesModel model; 085 private JScrollPane spPluginPreferences; 086 private PluginUpdatePolicyPanel pnlPluginUpdatePolicy; 087 088 /** 089 * is set to true if this preference pane has been selected by the user 090 */ 091 private boolean pluginPreferencesActivated; 092 093 private PluginPreference() { 094 super(/* ICON(preferences/) */ "plugin", tr("Plugins"), tr("Configure available plugins."), false, new JTabbedPane()); 095 } 096 097 /** 098 * Returns the download summary string to be shown. 099 * @param task The plugin download task that has completed 100 * @return the download summary string to be shown. Contains summary of success/failed plugins. 101 */ 102 public static String buildDownloadSummary(PluginDownloadTask task) { 103 Collection<PluginInformation> downloaded = task.getDownloadedPlugins(); 104 Collection<PluginInformation> failed = task.getFailedPlugins(); 105 Exception exception = task.getLastException(); 106 StringBuilder sb = new StringBuilder(); 107 if (!downloaded.isEmpty()) { 108 sb.append(trn( 109 "The following plugin has been downloaded <strong>successfully</strong>:", 110 "The following {0} plugins have been downloaded <strong>successfully</strong>:", 111 downloaded.size(), 112 downloaded.size() 113 )); 114 sb.append("<ul>"); 115 for (PluginInformation pi: downloaded) { 116 sb.append("<li>").append(pi.name).append(" (").append(pi.version).append(")</li>"); 117 } 118 sb.append("</ul>"); 119 } 120 if (!failed.isEmpty()) { 121 sb.append(trn( 122 "Downloading the following plugin has <strong>failed</strong>:", 123 "Downloading the following {0} plugins has <strong>failed</strong>:", 124 failed.size(), 125 failed.size() 126 )); 127 sb.append("<ul>"); 128 for (PluginInformation pi: failed) { 129 sb.append("<li>").append(pi.name).append("</li>"); 130 } 131 sb.append("</ul>"); 132 } 133 if (exception != null) { 134 // Same i18n string in ExceptionUtil.explainBadRequest() 135 sb.append(tr("<br>Error message(untranslated): {0}", exception.getMessage())); 136 } 137 return sb.toString(); 138 } 139 140 /** 141 * Notifies user about result of a finished plugin download task. 142 * @param parent The parent component 143 * @param task The finished plugin download task 144 * @param restartRequired true if a restart is required 145 * @since 6797 146 */ 147 public static void notifyDownloadResults(final Component parent, PluginDownloadTask task, boolean restartRequired) { 148 final Collection<PluginInformation> failed = task.getFailedPlugins(); 149 final StringBuilder sb = new StringBuilder(); 150 sb.append("<html>") 151 .append(buildDownloadSummary(task)); 152 if (restartRequired) { 153 sb.append(tr("Please restart JOSM to activate the downloaded plugins.")); 154 } 155 sb.append("</html>"); 156 GuiHelper.runInEDTAndWait(() -> HelpAwareOptionPane.showOptionDialog( 157 parent, 158 sb.toString(), 159 tr("Update plugins"), 160 !failed.isEmpty() ? JOptionPane.WARNING_MESSAGE : JOptionPane.INFORMATION_MESSAGE, 161 HelpUtil.ht("/Preferences/Plugins") 162 )); 163 } 164 165 private JPanel buildSearchFieldPanel() { 166 JPanel pnl = new JPanel(new GridBagLayout()); 167 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 168 GridBagConstraints gc = new GridBagConstraints(); 169 170 gc.anchor = GridBagConstraints.NORTHWEST; 171 gc.fill = GridBagConstraints.HORIZONTAL; 172 gc.weightx = 0.0; 173 gc.insets = new Insets(0, 0, 0, 3); 174 pnl.add(GBC.glue(0, 0)); 175 176 gc.weightx = 1.0; 177 ButtonGroup bg = new ButtonGroup(); 178 JPanel radios = new JPanel(); 179 addRadioButton(bg, radios, new JRadioButton(trc("plugins", "All"), true), gc, PluginInstallation.ALL); 180 addRadioButton(bg, radios, new JRadioButton(trc("plugins", "Installed")), gc, PluginInstallation.INSTALLED); 181 addRadioButton(bg, radios, new JRadioButton(trc("plugins", "Available")), gc, PluginInstallation.AVAILABLE); 182 pnl.add(radios, gc); 183 184 gc.gridx = 0; 185 gc.weightx = 0.0; 186 pnl.add(new JLabel(tr("Search:")), gc); 187 188 gc.gridx = 1; 189 gc.weightx = 1.0; 190 pnl.add(new FilterField().filter(expr -> { 191 model.filterDisplayedPlugins(expr); 192 pnlPluginPreferences.refreshView(); 193 }), gc); 194 return pnl; 195 } 196 197 private void addRadioButton(ButtonGroup bg, JPanel pnl, JRadioButton rb, GridBagConstraints gc, PluginInstallation value) { 198 bg.add(rb); 199 pnl.add(rb, gc); 200 rb.addActionListener(e -> { 201 model.filterDisplayedPlugins(value); 202 pnlPluginPreferences.refreshView(); 203 }); 204 } 205 206 private static Component addButton(JPanel pnl, JButton button, String buttonName) { 207 button.setName(buttonName); 208 return pnl.add(button); 209 } 210 211 private JPanel buildActionPanel() { 212 JPanel pnl = new JPanel(new GridLayout(1, 4)); 213 214 // assign some component names to these as we go to aid testing 215 addButton(pnl, new JButton(new DownloadAvailablePluginsAction()), "downloadListButton"); 216 addButton(pnl, new JButton(new UpdateSelectedPluginsAction()), "updatePluginsButton"); 217 ExpertToggleAction.addVisibilitySwitcher(addButton(pnl, new JButton(new SelectByListAction()), "loadFromListButton")); 218 ExpertToggleAction.addVisibilitySwitcher(addButton(pnl, new JButton(new ConfigureSitesAction()), "configureSitesButton")); 219 return pnl; 220 } 221 222 private JPanel buildPluginListPanel() { 223 JPanel pnl = new JPanel(new BorderLayout()); 224 pnl.add(buildSearchFieldPanel(), BorderLayout.NORTH); 225 model = new PluginPreferencesModel(); 226 pnlPluginPreferences = new PluginListPanel(model); 227 spPluginPreferences = GuiHelper.embedInVerticalScrollPane(pnlPluginPreferences); 228 spPluginPreferences.getVerticalScrollBar().addComponentListener( 229 new ComponentAdapter() { 230 @Override 231 public void componentShown(ComponentEvent e) { 232 spPluginPreferences.setBorder(UIManager.getBorder("ScrollPane.border")); 233 } 234 235 @Override 236 public void componentHidden(ComponentEvent e) { 237 spPluginPreferences.setBorder(null); 238 } 239 } 240 ); 241 242 pnl.add(spPluginPreferences, BorderLayout.CENTER); 243 pnl.add(buildActionPanel(), BorderLayout.SOUTH); 244 return pnl; 245 } 246 247 private JTabbedPane buildContentPane() { 248 JTabbedPane pane = getTabPane(); 249 pnlPluginUpdatePolicy = new PluginUpdatePolicyPanel(); 250 pane.addTab(tr("Plugins"), buildPluginListPanel()); 251 pane.addTab(tr("Plugin update policy"), pnlPluginUpdatePolicy); 252 return pane; 253 } 254 255 @Override 256 public void addGui(final PreferenceTabbedPane gui) { 257 GridBagConstraints gc = new GridBagConstraints(); 258 gc.weightx = 1.0; 259 gc.weighty = 1.0; 260 gc.anchor = GridBagConstraints.NORTHWEST; 261 gc.fill = GridBagConstraints.BOTH; 262 PreferencePanel plugins = gui.createPreferenceTab(this); 263 plugins.add(buildContentPane(), gc); 264 readLocalPluginInformation(); 265 pluginPreferencesActivated = true; 266 } 267 268 private void configureSites() { 269 ButtonSpec[] options = { 270 new ButtonSpec( 271 tr("OK"), 272 new ImageProvider("ok"), 273 tr("Accept the new plugin sites and close the dialog"), 274 null /* no special help topic */ 275 ), 276 new ButtonSpec( 277 tr("Cancel"), 278 new ImageProvider("cancel"), 279 tr("Close the dialog"), 280 null /* no special help topic */ 281 ) 282 }; 283 PluginConfigurationSitesPanel pnl = new PluginConfigurationSitesPanel(); 284 285 int answer = HelpAwareOptionPane.showOptionDialog( 286 pnlPluginPreferences, 287 pnl, 288 tr("Configure Plugin Sites"), 289 JOptionPane.QUESTION_MESSAGE, 290 null, 291 options, 292 options[0], 293 null /* no help topic */ 294 ); 295 if (answer != 0 /* OK */) 296 return; 297 Preferences.main().setPluginSites(pnl.getUpdateSites()); 298 } 299 300 /** 301 * Replies the set of plugins waiting for update or download 302 * 303 * @return the set of plugins waiting for update or download 304 */ 305 public Set<PluginInformation> getPluginsScheduledForUpdateOrDownload() { 306 return model != null ? model.getPluginsScheduledForUpdateOrDownload() : null; 307 } 308 309 /** 310 * Replies the list of plugins which have been added by the user to the set of activated plugins 311 * 312 * @return the list of newly activated plugins 313 */ 314 public List<PluginInformation> getNewlyActivatedPlugins() { 315 return model != null ? model.getNewlyActivatedPlugins() : null; 316 } 317 318 @Override 319 public boolean ok() { 320 if (!pluginPreferencesActivated) 321 return false; 322 pnlPluginUpdatePolicy.rememberInPreferences(); 323 if (model.isActivePluginsChanged()) { 324 List<String> l = new LinkedList<>(model.getSelectedPluginNames()); 325 Collections.sort(l); 326 Config.getPref().putList("plugins", l); 327 List<PluginInformation> deactivatedPlugins = model.getNewlyDeactivatedPlugins(); 328 if (!deactivatedPlugins.isEmpty()) { 329 boolean requiresRestart = PluginHandler.removePlugins(deactivatedPlugins); 330 if (requiresRestart) 331 return requiresRestart; 332 } 333 for (PluginInformation pi : model.getNewlyActivatedPlugins()) { 334 if (!pi.canloadatruntime) 335 return true; 336 } 337 } 338 return false; 339 } 340 341 /** 342 * Reads locally available information about plugins from the local file system. 343 * Scans cached plugin lists from plugin download sites and locally available 344 * plugin jar files. 345 * 346 */ 347 public void readLocalPluginInformation() { 348 final ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(); 349 Runnable r = () -> { 350 if (!task.isCanceled()) { 351 SwingUtilities.invokeLater(() -> { 352 model.setAvailablePlugins(task.getAvailablePlugins()); 353 pnlPluginPreferences.refreshView(); 354 }); 355 } 356 }; 357 MainApplication.worker.submit(task); 358 MainApplication.worker.submit(r); 359 } 360 361 /** 362 * The action for downloading the list of available plugins 363 */ 364 class DownloadAvailablePluginsAction extends AbstractAction { 365 366 /** 367 * Constructs a new {@code DownloadAvailablePluginsAction}. 368 */ 369 DownloadAvailablePluginsAction() { 370 putValue(NAME, tr("Download list")); 371 putValue(SHORT_DESCRIPTION, tr("Download the list of available plugins")); 372 new ImageProvider("download").getResource().attachImageIcon(this); 373 } 374 375 @Override 376 public void actionPerformed(ActionEvent e) { 377 Collection<String> pluginSites = Preferences.main().getOnlinePluginSites(); 378 if (pluginSites.isEmpty()) { 379 return; 380 } 381 final ReadRemotePluginInformationTask task = new ReadRemotePluginInformationTask(pluginSites); 382 Runnable continuation = () -> { 383 if (!task.isCanceled()) { 384 SwingUtilities.invokeLater(() -> { 385 model.updateAvailablePlugins(task.getAvailablePlugins()); 386 pnlPluginPreferences.refreshView(); 387 Config.getPref().putInt("pluginmanager.version", Version.getInstance().getVersion()); // fix #7030 388 }); 389 } 390 }; 391 MainApplication.worker.submit(task); 392 MainApplication.worker.submit(continuation); 393 } 394 } 395 396 /** 397 * The action for updating the list of selected plugins 398 */ 399 class UpdateSelectedPluginsAction extends AbstractAction { 400 UpdateSelectedPluginsAction() { 401 putValue(NAME, tr("Update plugins")); 402 putValue(SHORT_DESCRIPTION, tr("Update the selected plugins")); 403 new ImageProvider("dialogs", "refresh").getResource().attachImageIcon(this); 404 } 405 406 protected void alertNothingToUpdate() { 407 try { 408 SwingUtilities.invokeAndWait(() -> HelpAwareOptionPane.showOptionDialog( 409 pnlPluginPreferences, 410 tr("All installed plugins are up to date. JOSM does not have to download newer versions."), 411 tr("Plugins up to date"), 412 JOptionPane.INFORMATION_MESSAGE, 413 null // FIXME: provide help context 414 )); 415 } catch (InterruptedException | InvocationTargetException e) { 416 Logging.error(e); 417 } 418 } 419 420 @Override 421 public void actionPerformed(ActionEvent e) { 422 final List<PluginInformation> toUpdate = model.getSelectedPlugins(); 423 // the async task for downloading plugins 424 final PluginDownloadTask pluginDownloadTask = new PluginDownloadTask( 425 pnlPluginPreferences, 426 toUpdate, 427 tr("Update plugins") 428 ); 429 // the async task for downloading plugin information 430 final ReadRemotePluginInformationTask pluginInfoDownloadTask = new ReadRemotePluginInformationTask( 431 Preferences.main().getOnlinePluginSites()); 432 433 // to be run asynchronously after the plugin download 434 // 435 final Runnable pluginDownloadContinuation = () -> { 436 if (pluginDownloadTask.isCanceled()) 437 return; 438 boolean restartRequired = false; 439 for (PluginInformation pi : pluginDownloadTask.getDownloadedPlugins()) { 440 if (!model.getNewlyActivatedPlugins().contains(pi) || !pi.canloadatruntime) { 441 restartRequired = true; 442 break; 443 } 444 } 445 notifyDownloadResults(pnlPluginPreferences, pluginDownloadTask, restartRequired); 446 model.refreshLocalPluginVersion(pluginDownloadTask.getDownloadedPlugins()); 447 model.clearPendingPlugins(pluginDownloadTask.getDownloadedPlugins()); 448 GuiHelper.runInEDT(pnlPluginPreferences::refreshView); 449 }; 450 451 // to be run asynchronously after the plugin list download 452 // 453 final Runnable pluginInfoDownloadContinuation = () -> { 454 if (pluginInfoDownloadTask.isCanceled()) 455 return; 456 model.updateAvailablePlugins(pluginInfoDownloadTask.getAvailablePlugins()); 457 // select plugins which actually have to be updated 458 // 459 toUpdate.removeIf(pi -> !pi.isUpdateRequired()); 460 if (toUpdate.isEmpty()) { 461 alertNothingToUpdate(); 462 return; 463 } 464 pluginDownloadTask.setPluginsToDownload(toUpdate); 465 MainApplication.worker.submit(pluginDownloadTask); 466 MainApplication.worker.submit(pluginDownloadContinuation); 467 }; 468 469 MainApplication.worker.submit(pluginInfoDownloadTask); 470 MainApplication.worker.submit(pluginInfoDownloadContinuation); 471 } 472 } 473 474 /** 475 * The action for configuring the plugin download sites 476 * 477 */ 478 class ConfigureSitesAction extends AbstractAction { 479 ConfigureSitesAction() { 480 putValue(NAME, tr("Configure sites...")); 481 putValue(SHORT_DESCRIPTION, tr("Configure the list of sites where plugins are downloaded from")); 482 new ImageProvider("dialogs", "settings").getResource().attachImageIcon(this); 483 } 484 485 @Override 486 public void actionPerformed(ActionEvent e) { 487 configureSites(); 488 } 489 } 490 491 /** 492 * The action for selecting the plugins given by a text file compatible to JOSM bug report. 493 * @author Michael Zangl 494 */ 495 class SelectByListAction extends AbstractAction { 496 SelectByListAction() { 497 putValue(NAME, tr("Load from list...")); 498 putValue(SHORT_DESCRIPTION, tr("Load plugins from a list of plugins")); 499 } 500 501 @Override 502 public void actionPerformed(ActionEvent e) { 503 JTextArea textField = new JTextArea(10, 0); 504 JCheckBox deleteNotInList = new JCheckBox(tr("Disable all other plugins")); 505 506 JLabel helpLabel = new JLabel("<html>" + String.join("<br/>", 507 tr("Enter a list of plugins you want to download."), 508 tr("You should add one plugin id per line, version information is ignored."), 509 tr("You can copy+paste the list of a status report here.")) + "</html>"); 510 511 if (JOptionPane.OK_OPTION == JOptionPane.showConfirmDialog(GuiHelper.getFrameForComponent(getTabPane()), 512 new Object[] {helpLabel, new JScrollPane(textField), deleteNotInList}, 513 tr("Load plugins from list"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE)) { 514 activatePlugins(textField, deleteNotInList.isSelected()); 515 } 516 } 517 518 private void activatePlugins(JTextArea textField, boolean deleteNotInList) { 519 String[] lines = textField.getText().split("\n"); 520 List<String> toActivate = new ArrayList<>(); 521 List<String> notFound = new ArrayList<>(); 522 // This pattern matches the default list format JOSM uses for bug reports. 523 // It removes a list item mark at the beginning of the line: +, -, * 524 // It removes the version number after the plugin, like: 123, (123), (v5.7alpha3), (1b3), (v1-SNAPSHOT-1)... 525 Pattern regex = Pattern.compile("^[-+\\*\\s]*|\\s[\\d\\s]*(\\([^\\(\\)\\[\\]]*\\))?[\\d\\s]*$"); 526 for (String line : lines) { 527 String name = regex.matcher(line).replaceAll(""); 528 if (name.isEmpty()) { 529 continue; 530 } 531 PluginInformation plugin = model.getPluginInformation(name); 532 if (plugin == null) { 533 notFound.add(name); 534 } else { 535 toActivate.add(name); 536 } 537 } 538 539 if (notFound.isEmpty() || confirmIgnoreNotFound(notFound)) { 540 activatePlugins(toActivate, deleteNotInList); 541 } 542 } 543 544 private void activatePlugins(List<String> toActivate, boolean deleteNotInList) { 545 if (deleteNotInList) { 546 for (String name : model.getSelectedPluginNames()) { 547 if (!toActivate.contains(name)) { 548 model.setPluginSelected(name, false); 549 } 550 } 551 } 552 for (String name : toActivate) { 553 model.setPluginSelected(name, true); 554 } 555 pnlPluginPreferences.refreshView(); 556 } 557 558 private boolean confirmIgnoreNotFound(List<String> notFound) { 559 String list = "<ul><li>" + String.join("</li><li>", notFound) + "</li></ul>"; 560 String message = "<html>" + tr("The following plugins were not found. Continue anyway?") + list + "</html>"; 561 return JOptionPane.showConfirmDialog(GuiHelper.getFrameForComponent(getTabPane()), 562 message) == JOptionPane.OK_OPTION; 563 } 564 } 565 566 private static class PluginConfigurationSitesPanel extends JPanel { 567 568 private final DefaultListModel<String> model = new DefaultListModel<>(); 569 570 PluginConfigurationSitesPanel() { 571 super(new GridBagLayout()); 572 add(new JLabel(tr("Add JOSM Plugin description URL.")), GBC.eol()); 573 for (String s : Preferences.main().getPluginSites()) { 574 model.addElement(s); 575 } 576 final JList<String> list = new JList<>(model); 577 add(new JScrollPane(list), GBC.std().fill()); 578 JPanel buttons = new JPanel(new GridBagLayout()); 579 buttons.add(new JButton(new AbstractAction(tr("Add")) { 580 @Override 581 public void actionPerformed(ActionEvent e) { 582 String s = JOptionPane.showInputDialog( 583 GuiHelper.getFrameForComponent(PluginConfigurationSitesPanel.this), 584 tr("Add JOSM Plugin description URL."), 585 tr("Enter URL"), 586 JOptionPane.QUESTION_MESSAGE 587 ); 588 if (s != null && !s.isEmpty()) { 589 model.addElement(s); 590 } 591 } 592 }), GBC.eol().fill(GBC.HORIZONTAL)); 593 buttons.add(new JButton(new AbstractAction(tr("Edit")) { 594 @Override 595 public void actionPerformed(ActionEvent e) { 596 if (list.getSelectedValue() == null) { 597 JOptionPane.showMessageDialog( 598 GuiHelper.getFrameForComponent(PluginConfigurationSitesPanel.this), 599 tr("Please select an entry."), 600 tr("Warning"), 601 JOptionPane.WARNING_MESSAGE 602 ); 603 return; 604 } 605 String s = (String) JOptionPane.showInputDialog( 606 MainApplication.getMainFrame(), 607 tr("Edit JOSM Plugin description URL."), 608 tr("JOSM Plugin description URL"), 609 JOptionPane.QUESTION_MESSAGE, 610 null, 611 null, 612 list.getSelectedValue() 613 ); 614 if (s != null && !s.isEmpty()) { 615 model.setElementAt(s, list.getSelectedIndex()); 616 } 617 } 618 }), GBC.eol().fill(GBC.HORIZONTAL)); 619 buttons.add(new JButton(new AbstractAction(tr("Delete")) { 620 @Override 621 public void actionPerformed(ActionEvent event) { 622 if (list.getSelectedValue() == null) { 623 JOptionPane.showMessageDialog( 624 GuiHelper.getFrameForComponent(PluginConfigurationSitesPanel.this), 625 tr("Please select an entry."), 626 tr("Warning"), 627 JOptionPane.WARNING_MESSAGE 628 ); 629 return; 630 } 631 model.removeElement(list.getSelectedValue()); 632 } 633 }), GBC.eol().fill(GBC.HORIZONTAL)); 634 add(buttons, GBC.eol()); 635 } 636 637 protected List<String> getUpdateSites() { 638 if (model.getSize() == 0) 639 return Collections.emptyList(); 640 List<String> ret = new ArrayList<>(model.getSize()); 641 for (int i = 0; i < model.getSize(); i++) { 642 ret.add(model.get(i)); 643 } 644 return ret; 645 } 646 } 647 648 @Override 649 public String getHelpContext() { 650 return HelpUtil.ht("/Preferences/Plugins"); 651 } 652}