source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/plugin/PluginPreference.java@ 8379

Last change on this file since 8379 was 8379, checked in by Don-vip, 9 years ago

Consecutively calls to StringBuffer/StringBuilder .append should reuse the target object

  • Property svn:eol-style set to native
File size: 23.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.preferences.plugin;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.GridBagConstraints;
10import java.awt.GridBagLayout;
11import java.awt.GridLayout;
12import java.awt.Insets;
13import java.awt.event.ActionEvent;
14import java.awt.event.ComponentAdapter;
15import java.awt.event.ComponentEvent;
16import java.lang.reflect.InvocationTargetException;
17import java.util.ArrayList;
18import java.util.Collection;
19import java.util.Collections;
20import java.util.Iterator;
21import java.util.LinkedList;
22import java.util.List;
23
24import javax.swing.AbstractAction;
25import javax.swing.BorderFactory;
26import javax.swing.DefaultListModel;
27import javax.swing.JButton;
28import javax.swing.JLabel;
29import javax.swing.JList;
30import javax.swing.JOptionPane;
31import javax.swing.JPanel;
32import javax.swing.JScrollPane;
33import javax.swing.JTabbedPane;
34import javax.swing.SwingUtilities;
35import javax.swing.UIManager;
36import javax.swing.event.DocumentEvent;
37import javax.swing.event.DocumentListener;
38
39import org.openstreetmap.josm.Main;
40import org.openstreetmap.josm.data.Version;
41import org.openstreetmap.josm.gui.HelpAwareOptionPane;
42import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
43import org.openstreetmap.josm.gui.help.HelpUtil;
44import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
45import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
46import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
47import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
48import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferencePanel;
49import org.openstreetmap.josm.gui.util.GuiHelper;
50import org.openstreetmap.josm.gui.widgets.JosmTextField;
51import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
52import org.openstreetmap.josm.io.OfflineAccessException;
53import org.openstreetmap.josm.io.OnlineResource;
54import org.openstreetmap.josm.plugins.PluginDownloadTask;
55import org.openstreetmap.josm.plugins.PluginInformation;
56import org.openstreetmap.josm.plugins.ReadLocalPluginInformationTask;
57import org.openstreetmap.josm.plugins.ReadRemotePluginInformationTask;
58import org.openstreetmap.josm.tools.GBC;
59import org.openstreetmap.josm.tools.ImageProvider;
60
61/**
62 * Preference settings for plugins.
63 * @since 168
64 */
65public final class PluginPreference extends DefaultTabPreferenceSetting {
66
67 /**
68 * Factory used to create a new {@code PluginPreference}.
69 */
70 public static class Factory implements PreferenceSettingFactory {
71 @Override
72 public PreferenceSetting createPreferenceSetting() {
73 return new PluginPreference();
74 }
75 }
76
77 private PluginPreference() {
78 super(/* ICON(preferences/) */ "plugin", tr("Plugins"), tr("Configure available plugins."), false, new JTabbedPane());
79 }
80
81 /**
82 * Returns the download summary string to be shown.
83 * @param task The plugin download task that has completed
84 * @return the download summary string to be shown. Contains summary of success/failed plugins.
85 */
86 public static String buildDownloadSummary(PluginDownloadTask task) {
87 Collection<PluginInformation> downloaded = task.getDownloadedPlugins();
88 Collection<PluginInformation> failed = task.getFailedPlugins();
89 StringBuilder sb = new StringBuilder();
90 if (! downloaded.isEmpty()) {
91 sb.append(trn(
92 "The following plugin has been downloaded <strong>successfully</strong>:",
93 "The following {0} plugins have been downloaded <strong>successfully</strong>:",
94 downloaded.size(),
95 downloaded.size()
96 ));
97 sb.append("<ul>");
98 for(PluginInformation pi: downloaded) {
99 sb.append("<li>").append(pi.name).append(" (").append(pi.version).append(")").append("</li>");
100 }
101 sb.append("</ul>");
102 }
103 if (! failed.isEmpty()) {
104 sb.append(trn(
105 "Downloading the following plugin has <strong>failed</strong>:",
106 "Downloading the following {0} plugins has <strong>failed</strong>:",
107 failed.size(),
108 failed.size()
109 ));
110 sb.append("<ul>");
111 for(PluginInformation pi: failed) {
112 sb.append("<li>").append(pi.name).append("</li>");
113 }
114 sb.append("</ul>");
115 }
116 return sb.toString();
117 }
118
119 /**
120 * Notifies user about result of a finished plugin download task.
121 * @param parent The parent component
122 * @param task The finished plugin download task
123 * @since 6797
124 */
125 public static void notifyDownloadResults(final Component parent, PluginDownloadTask task, boolean restartRequired) {
126 final Collection<PluginInformation> downloaded = task.getDownloadedPlugins();
127 final Collection<PluginInformation> failed = task.getFailedPlugins();
128 final StringBuilder sb = new StringBuilder();
129 sb.append("<html>")
130 .append(buildDownloadSummary(task));
131 if (restartRequired) {
132 sb.append(tr("Please restart JOSM to activate the downloaded plugins."));
133 }
134 sb.append("</html>");
135 GuiHelper.runInEDTAndWait(new Runnable() {
136 @Override
137 public void run() {
138 HelpAwareOptionPane.showOptionDialog(
139 parent,
140 sb.toString(),
141 tr("Update plugins"),
142 !failed.isEmpty() ? JOptionPane.WARNING_MESSAGE : JOptionPane.INFORMATION_MESSAGE,
143 HelpUtil.ht("/Preferences/Plugins")
144 );
145 }
146 });
147 }
148
149 private JosmTextField tfFilter;
150 private PluginListPanel pnlPluginPreferences;
151 private PluginPreferencesModel model;
152 private JScrollPane spPluginPreferences;
153 private PluginUpdatePolicyPanel pnlPluginUpdatePolicy;
154
155 /**
156 * is set to true if this preference pane has been selected
157 * by the user
158 */
159 private boolean pluginPreferencesActivated = false;
160
161 protected JPanel buildSearchFieldPanel() {
162 JPanel pnl = new JPanel(new GridBagLayout());
163 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
164 GridBagConstraints gc = new GridBagConstraints();
165
166 gc.anchor = GridBagConstraints.NORTHWEST;
167 gc.fill = GridBagConstraints.HORIZONTAL;
168 gc.weightx = 0.0;
169 gc.insets = new Insets(0,0,0,3);
170 pnl.add(new JLabel(tr("Search:")), gc);
171
172 gc.gridx = 1;
173 gc.weightx = 1.0;
174 tfFilter = new JosmTextField();
175 pnl.add(tfFilter, gc);
176 tfFilter.setToolTipText(tr("Enter a search expression"));
177 SelectAllOnFocusGainedDecorator.decorate(tfFilter);
178 tfFilter.getDocument().addDocumentListener(new SearchFieldAdapter());
179 return pnl;
180 }
181
182 protected JPanel buildActionPanel() {
183 JPanel pnl = new JPanel(new GridLayout(1,3));
184
185 pnl.add(new JButton(new DownloadAvailablePluginsAction()));
186 pnl.add(new JButton(new UpdateSelectedPluginsAction()));
187 pnl.add(new JButton(new ConfigureSitesAction()));
188 return pnl;
189 }
190
191 protected JPanel buildPluginListPanel() {
192 JPanel pnl = new JPanel(new BorderLayout());
193 pnl.add(buildSearchFieldPanel(), BorderLayout.NORTH);
194 model = new PluginPreferencesModel();
195 pnlPluginPreferences = new PluginListPanel(model);
196 spPluginPreferences = GuiHelper.embedInVerticalScrollPane(pnlPluginPreferences);
197 spPluginPreferences.getVerticalScrollBar().addComponentListener(
198 new ComponentAdapter(){
199 @Override
200 public void componentShown(ComponentEvent e) {
201 spPluginPreferences.setBorder(UIManager.getBorder("ScrollPane.border"));
202 }
203 @Override
204 public void componentHidden(ComponentEvent e) {
205 spPluginPreferences.setBorder(null);
206 }
207 }
208 );
209
210 pnl.add(spPluginPreferences, BorderLayout.CENTER);
211 pnl.add(buildActionPanel(), BorderLayout.SOUTH);
212 return pnl;
213 }
214
215 protected JTabbedPane buildContentPane() {
216 JTabbedPane pane = getTabPane();
217 pnlPluginUpdatePolicy = new PluginUpdatePolicyPanel();
218 pane.addTab(tr("Plugins"), buildPluginListPanel());
219 pane.addTab(tr("Plugin update policy"), pnlPluginUpdatePolicy);
220 return pane;
221 }
222
223 @Override
224 public void addGui(final PreferenceTabbedPane gui) {
225 GridBagConstraints gc = new GridBagConstraints();
226 gc.weightx = 1.0;
227 gc.weighty = 1.0;
228 gc.anchor = GridBagConstraints.NORTHWEST;
229 gc.fill = GridBagConstraints.BOTH;
230 PreferencePanel plugins = gui.createPreferenceTab(this);
231 plugins.add(buildContentPane(), gc);
232 readLocalPluginInformation();
233 pluginPreferencesActivated = true;
234 }
235
236 private void configureSites() {
237 ButtonSpec[] options = new ButtonSpec[] {
238 new ButtonSpec(
239 tr("OK"),
240 ImageProvider.get("ok"),
241 tr("Accept the new plugin sites and close the dialog"),
242 null /* no special help topic */
243 ),
244 new ButtonSpec(
245 tr("Cancel"),
246 ImageProvider.get("cancel"),
247 tr("Close the dialog"),
248 null /* no special help topic */
249 )
250 };
251 PluginConfigurationSitesPanel pnl = new PluginConfigurationSitesPanel();
252
253 int answer = HelpAwareOptionPane.showOptionDialog(
254 pnlPluginPreferences,
255 pnl,
256 tr("Configure Plugin Sites"),
257 JOptionPane.QUESTION_MESSAGE,
258 null,
259 options,
260 options[0],
261 null /* no help topic */
262 );
263 if (answer != 0 /* OK */)
264 return;
265 List<String> sites = pnl.getUpdateSites();
266 Main.pref.setPluginSites(sites);
267 }
268
269 /**
270 * Replies the list of plugins waiting for update or download
271 *
272 * @return the list of plugins waiting for update or download
273 */
274 public List<PluginInformation> getPluginsScheduledForUpdateOrDownload() {
275 return model != null ? model.getPluginsScheduledForUpdateOrDownload() : null;
276 }
277
278 public List<PluginInformation> getNewlyActivatedPlugins() {
279 return model != null ? model.getNewlyActivatedPlugins() : null;
280 }
281
282 @Override
283 public boolean ok() {
284 if (! pluginPreferencesActivated)
285 return false;
286 pnlPluginUpdatePolicy.rememberInPreferences();
287 if (model.isActivePluginsChanged()) {
288 List<String> l = new LinkedList<>(model.getSelectedPluginNames());
289 Collections.sort(l);
290 Main.pref.putCollection("plugins", l);
291 if (!model.getNewlyDeactivatedPlugins().isEmpty()) return true;
292 for (PluginInformation pi : model.getNewlyActivatedPlugins()) {
293 if (!pi.canloadatruntime) return true;
294 }
295 }
296 return false;
297 }
298
299 /**
300 * Reads locally available information about plugins from the local file system.
301 * Scans cached plugin lists from plugin download sites and locally available
302 * plugin jar files.
303 *
304 */
305 public void readLocalPluginInformation() {
306 final ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask();
307 Runnable r = new Runnable() {
308 @Override
309 public void run() {
310 if (task.isCanceled()) return;
311 SwingUtilities.invokeLater(new Runnable() {
312 @Override
313 public void run() {
314 model.setAvailablePlugins(task.getAvailablePlugins());
315 pnlPluginPreferences.refreshView();
316 }
317 });
318 }
319 };
320 Main.worker.submit(task);
321 Main.worker.submit(r);
322 }
323
324 private static Collection<String> getOnlinePluginSites() {
325 Collection<String> pluginSites = new ArrayList<>(Main.pref.getPluginSites());
326 for (Iterator<String> it = pluginSites.iterator(); it.hasNext();) {
327 try {
328 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(it.next(), Main.getJOSMWebsite());
329 } catch (OfflineAccessException ex) {
330 Main.warn(ex.getMessage());
331 it.remove();
332 }
333 }
334 return pluginSites;
335 }
336
337 /**
338 * The action for downloading the list of available plugins
339 */
340 class DownloadAvailablePluginsAction extends AbstractAction {
341
342 public DownloadAvailablePluginsAction() {
343 putValue(NAME,tr("Download list"));
344 putValue(SHORT_DESCRIPTION, tr("Download the list of available plugins"));
345 putValue(SMALL_ICON, ImageProvider.get("download"));
346 }
347
348 @Override
349 public void actionPerformed(ActionEvent e) {
350 Collection<String> pluginSites = getOnlinePluginSites();
351 if (pluginSites.isEmpty()) {
352 return;
353 }
354 final ReadRemotePluginInformationTask task = new ReadRemotePluginInformationTask(pluginSites);
355 Runnable continuation = new Runnable() {
356 @Override
357 public void run() {
358 if (task.isCanceled()) return;
359 SwingUtilities.invokeLater(new Runnable() {
360 @Override
361 public void run() {
362 model.updateAvailablePlugins(task.getAvailablePlugins());
363 pnlPluginPreferences.refreshView();
364 Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion()); // fix #7030
365 }
366 });
367 }
368 };
369 Main.worker.submit(task);
370 Main.worker.submit(continuation);
371 }
372
373 }
374
375 /**
376 * The action for updating the list of selected plugins
377 */
378 class UpdateSelectedPluginsAction extends AbstractAction {
379 public UpdateSelectedPluginsAction() {
380 putValue(NAME,tr("Update plugins"));
381 putValue(SHORT_DESCRIPTION, tr("Update the selected plugins"));
382 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
383 }
384
385 protected void alertNothingToUpdate() {
386 try {
387 SwingUtilities.invokeAndWait(new Runnable() {
388 @Override
389 public void run() {
390 HelpAwareOptionPane.showOptionDialog(
391 pnlPluginPreferences,
392 tr("All installed plugins are up to date. JOSM does not have to download newer versions."),
393 tr("Plugins up to date"),
394 JOptionPane.INFORMATION_MESSAGE,
395 null // FIXME: provide help context
396 );
397 }
398 });
399 } catch (InterruptedException | InvocationTargetException e) {
400 Main.error(e);
401 }
402 }
403
404 @Override
405 public void actionPerformed(ActionEvent e) {
406 final List<PluginInformation> toUpdate = model.getSelectedPlugins();
407 // the async task for downloading plugins
408 final PluginDownloadTask pluginDownloadTask = new PluginDownloadTask(
409 pnlPluginPreferences,
410 toUpdate,
411 tr("Update plugins")
412 );
413 // the async task for downloading plugin information
414 final ReadRemotePluginInformationTask pluginInfoDownloadTask = new ReadRemotePluginInformationTask(getOnlinePluginSites());
415
416 // to be run asynchronously after the plugin download
417 //
418 final Runnable pluginDownloadContinuation = new Runnable() {
419 @Override
420 public void run() {
421 if (pluginDownloadTask.isCanceled())
422 return;
423 boolean restartRequired = false;
424 for (PluginInformation pi : pluginDownloadTask.getDownloadedPlugins()) {
425 if (!model.getNewlyActivatedPlugins().contains(pi) || !pi.canloadatruntime) {
426 restartRequired = true;
427 break;
428 }
429 }
430 notifyDownloadResults(pnlPluginPreferences, pluginDownloadTask, restartRequired);
431 model.refreshLocalPluginVersion(pluginDownloadTask.getDownloadedPlugins());
432 model.clearPendingPlugins(pluginDownloadTask.getDownloadedPlugins());
433 GuiHelper.runInEDT(new Runnable() {
434 @Override
435 public void run() {
436 pnlPluginPreferences.refreshView(); }
437 });
438 }
439 };
440
441 // to be run asynchronously after the plugin list download
442 //
443 final Runnable pluginInfoDownloadContinuation = new Runnable() {
444 @Override
445 public void run() {
446 if (pluginInfoDownloadTask.isCanceled())
447 return;
448 model.updateAvailablePlugins(pluginInfoDownloadTask.getAvailablePlugins());
449 // select plugins which actually have to be updated
450 //
451 Iterator<PluginInformation> it = toUpdate.iterator();
452 while (it.hasNext()) {
453 PluginInformation pi = it.next();
454 if (!pi.isUpdateRequired()) {
455 it.remove();
456 }
457 }
458 if (toUpdate.isEmpty()) {
459 alertNothingToUpdate();
460 return;
461 }
462 pluginDownloadTask.setPluginsToDownload(toUpdate);
463 Main.worker.submit(pluginDownloadTask);
464 Main.worker.submit(pluginDownloadContinuation);
465 }
466 };
467
468 Main.worker.submit(pluginInfoDownloadTask);
469 Main.worker.submit(pluginInfoDownloadContinuation);
470 }
471 }
472
473
474 /**
475 * The action for configuring the plugin download sites
476 *
477 */
478 class ConfigureSitesAction extends AbstractAction {
479 public ConfigureSitesAction() {
480 putValue(NAME,tr("Configure sites..."));
481 putValue(SHORT_DESCRIPTION, tr("Configure the list of sites where plugins are downloaded from"));
482 putValue(SMALL_ICON, ImageProvider.get("dialogs", "settings"));
483 }
484
485 @Override
486 public void actionPerformed(ActionEvent e) {
487 configureSites();
488 }
489 }
490
491 /**
492 * Applies the current filter condition in the filter text field to the
493 * model
494 */
495 class SearchFieldAdapter implements DocumentListener {
496 public void filter() {
497 String expr = tfFilter.getText().trim();
498 if (expr.isEmpty()) {
499 expr = null;
500 }
501 model.filterDisplayedPlugins(expr);
502 pnlPluginPreferences.refreshView();
503 }
504
505 @Override
506 public void changedUpdate(DocumentEvent arg0) {
507 filter();
508 }
509
510 @Override
511 public void insertUpdate(DocumentEvent arg0) {
512 filter();
513 }
514
515 @Override
516 public void removeUpdate(DocumentEvent arg0) {
517 filter();
518 }
519 }
520
521 private static class PluginConfigurationSitesPanel extends JPanel {
522
523 private DefaultListModel<String> model;
524
525 protected final void build() {
526 setLayout(new GridBagLayout());
527 add(new JLabel(tr("Add JOSM Plugin description URL.")), GBC.eol());
528 model = new DefaultListModel<>();
529 for (String s : Main.pref.getPluginSites()) {
530 model.addElement(s);
531 }
532 final JList<String> list = new JList<>(model);
533 add(new JScrollPane(list), GBC.std().fill());
534 JPanel buttons = new JPanel(new GridBagLayout());
535 buttons.add(new JButton(new AbstractAction(tr("Add")){
536 @Override
537 public void actionPerformed(ActionEvent e) {
538 String s = JOptionPane.showInputDialog(
539 JOptionPane.getFrameForComponent(PluginConfigurationSitesPanel.this),
540 tr("Add JOSM Plugin description URL."),
541 tr("Enter URL"),
542 JOptionPane.QUESTION_MESSAGE
543 );
544 if (s != null) {
545 model.addElement(s);
546 }
547 }
548 }), GBC.eol().fill(GBC.HORIZONTAL));
549 buttons.add(new JButton(new AbstractAction(tr("Edit")){
550 @Override
551 public void actionPerformed(ActionEvent e) {
552 if (list.getSelectedValue() == null) {
553 JOptionPane.showMessageDialog(
554 JOptionPane.getFrameForComponent(PluginConfigurationSitesPanel.this),
555 tr("Please select an entry."),
556 tr("Warning"),
557 JOptionPane.WARNING_MESSAGE
558 );
559 return;
560 }
561 String s = (String)JOptionPane.showInputDialog(
562 Main.parent,
563 tr("Edit JOSM Plugin description URL."),
564 tr("JOSM Plugin description URL"),
565 JOptionPane.QUESTION_MESSAGE,
566 null,
567 null,
568 list.getSelectedValue()
569 );
570 if (s != null) {
571 model.setElementAt(s, list.getSelectedIndex());
572 }
573 }
574 }), GBC.eol().fill(GBC.HORIZONTAL));
575 buttons.add(new JButton(new AbstractAction(tr("Delete")){
576 @Override
577 public void actionPerformed(ActionEvent event) {
578 if (list.getSelectedValue() == null) {
579 JOptionPane.showMessageDialog(
580 JOptionPane.getFrameForComponent(PluginConfigurationSitesPanel.this),
581 tr("Please select an entry."),
582 tr("Warning"),
583 JOptionPane.WARNING_MESSAGE
584 );
585 return;
586 }
587 model.removeElement(list.getSelectedValue());
588 }
589 }), GBC.eol().fill(GBC.HORIZONTAL));
590 add(buttons, GBC.eol());
591 }
592
593 public PluginConfigurationSitesPanel() {
594 build();
595 }
596
597 public List<String> getUpdateSites() {
598 if (model.getSize() == 0) return Collections.emptyList();
599 List<String> ret = new ArrayList<>(model.getSize());
600 for (int i=0; i< model.getSize();i++){
601 ret.add(model.get(i));
602 }
603 return ret;
604 }
605 }
606
607
608}
Note: See TracBrowser for help on using the repository browser.