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

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

offer to automatically download missing required plugins and restart. i18n impact, but this needs to be done before releasing the new tested because of the new dependence on commons-imaging, to avoid future bug reports.

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