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