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