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

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

remove extra whitespaces

  • Property svn:eol-style set to native
File size: 23.5 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(")</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> failed = task.getFailedPlugins();
127 final StringBuilder sb = new StringBuilder();
128 sb.append("<html>")
129 .append(buildDownloadSummary(task));
130 if (restartRequired) {
131 sb.append(tr("Please restart JOSM to activate the downloaded plugins."));
132 }
133 sb.append("</html>");
134 GuiHelper.runInEDTAndWait(new Runnable() {
135 @Override
136 public void run() {
137 HelpAwareOptionPane.showOptionDialog(
138 parent,
139 sb.toString(),
140 tr("Update plugins"),
141 !failed.isEmpty() ? JOptionPane.WARNING_MESSAGE : JOptionPane.INFORMATION_MESSAGE,
142 HelpUtil.ht("/Preferences/Plugins")
143 );
144 }
145 });
146 }
147
148 private JosmTextField tfFilter;
149 private PluginListPanel pnlPluginPreferences;
150 private PluginPreferencesModel model;
151 private JScrollPane spPluginPreferences;
152 private PluginUpdatePolicyPanel pnlPluginUpdatePolicy;
153
154 /**
155 * is set to true if this preference pane has been selected
156 * by the user
157 */
158 private boolean pluginPreferencesActivated = false;
159
160 protected JPanel buildSearchFieldPanel() {
161 JPanel pnl = new JPanel(new GridBagLayout());
162 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
163 GridBagConstraints gc = new GridBagConstraints();
164
165 gc.anchor = GridBagConstraints.NORTHWEST;
166 gc.fill = GridBagConstraints.HORIZONTAL;
167 gc.weightx = 0.0;
168 gc.insets = new Insets(0,0,0,3);
169 pnl.add(new JLabel(tr("Search:")), gc);
170
171 gc.gridx = 1;
172 gc.weightx = 1.0;
173 tfFilter = new JosmTextField();
174 pnl.add(tfFilter, gc);
175 tfFilter.setToolTipText(tr("Enter a search expression"));
176 SelectAllOnFocusGainedDecorator.decorate(tfFilter);
177 tfFilter.getDocument().addDocumentListener(new SearchFieldAdapter());
178 return pnl;
179 }
180
181 protected JPanel buildActionPanel() {
182 JPanel pnl = new JPanel(new GridLayout(1,3));
183
184 pnl.add(new JButton(new DownloadAvailablePluginsAction()));
185 pnl.add(new JButton(new UpdateSelectedPluginsAction()));
186 pnl.add(new JButton(new ConfigureSitesAction()));
187 return pnl;
188 }
189
190 protected JPanel buildPluginListPanel() {
191 JPanel pnl = new JPanel(new BorderLayout());
192 pnl.add(buildSearchFieldPanel(), BorderLayout.NORTH);
193 model = new PluginPreferencesModel();
194 pnlPluginPreferences = new PluginListPanel(model);
195 spPluginPreferences = GuiHelper.embedInVerticalScrollPane(pnlPluginPreferences);
196 spPluginPreferences.getVerticalScrollBar().addComponentListener(
197 new ComponentAdapter(){
198 @Override
199 public void componentShown(ComponentEvent e) {
200 spPluginPreferences.setBorder(UIManager.getBorder("ScrollPane.border"));
201 }
202 @Override
203 public void componentHidden(ComponentEvent e) {
204 spPluginPreferences.setBorder(null);
205 }
206 }
207 );
208
209 pnl.add(spPluginPreferences, BorderLayout.CENTER);
210 pnl.add(buildActionPanel(), BorderLayout.SOUTH);
211 return pnl;
212 }
213
214 protected JTabbedPane buildContentPane() {
215 JTabbedPane pane = getTabPane();
216 pnlPluginUpdatePolicy = new PluginUpdatePolicyPanel();
217 pane.addTab(tr("Plugins"), buildPluginListPanel());
218 pane.addTab(tr("Plugin update policy"), pnlPluginUpdatePolicy);
219 return pane;
220 }
221
222 @Override
223 public void addGui(final PreferenceTabbedPane gui) {
224 GridBagConstraints gc = new GridBagConstraints();
225 gc.weightx = 1.0;
226 gc.weighty = 1.0;
227 gc.anchor = GridBagConstraints.NORTHWEST;
228 gc.fill = GridBagConstraints.BOTH;
229 PreferencePanel plugins = gui.createPreferenceTab(this);
230 plugins.add(buildContentPane(), gc);
231 readLocalPluginInformation();
232 pluginPreferencesActivated = true;
233 }
234
235 private void configureSites() {
236 ButtonSpec[] options = new ButtonSpec[] {
237 new ButtonSpec(
238 tr("OK"),
239 ImageProvider.get("ok"),
240 tr("Accept the new plugin sites and close the dialog"),
241 null /* no special help topic */
242 ),
243 new ButtonSpec(
244 tr("Cancel"),
245 ImageProvider.get("cancel"),
246 tr("Close the dialog"),
247 null /* no special help topic */
248 )
249 };
250 PluginConfigurationSitesPanel pnl = new PluginConfigurationSitesPanel();
251
252 int answer = HelpAwareOptionPane.showOptionDialog(
253 pnlPluginPreferences,
254 pnl,
255 tr("Configure Plugin Sites"),
256 JOptionPane.QUESTION_MESSAGE,
257 null,
258 options,
259 options[0],
260 null /* no help topic */
261 );
262 if (answer != 0 /* OK */)
263 return;
264 List<String> sites = pnl.getUpdateSites();
265 Main.pref.setPluginSites(sites);
266 }
267
268 /**
269 * Replies the list of plugins waiting for update or download
270 *
271 * @return the list of plugins waiting for update or download
272 */
273 public List<PluginInformation> getPluginsScheduledForUpdateOrDownload() {
274 return model != null ? model.getPluginsScheduledForUpdateOrDownload() : null;
275 }
276
277 public List<PluginInformation> getNewlyActivatedPlugins() {
278 return model != null ? model.getNewlyActivatedPlugins() : null;
279 }
280
281 @Override
282 public boolean ok() {
283 if (!pluginPreferencesActivated)
284 return false;
285 pnlPluginUpdatePolicy.rememberInPreferences();
286 if (model.isActivePluginsChanged()) {
287 List<String> l = new LinkedList<>(model.getSelectedPluginNames());
288 Collections.sort(l);
289 Main.pref.putCollection("plugins", l);
290 if (!model.getNewlyDeactivatedPlugins().isEmpty()) return true;
291 for (PluginInformation pi : model.getNewlyActivatedPlugins()) {
292 if (!pi.canloadatruntime) return true;
293 }
294 }
295 return false;
296 }
297
298 /**
299 * Reads locally available information about plugins from the local file system.
300 * Scans cached plugin lists from plugin download sites and locally available
301 * plugin jar files.
302 *
303 */
304 public void readLocalPluginInformation() {
305 final ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask();
306 Runnable r = new Runnable() {
307 @Override
308 public void run() {
309 if (task.isCanceled()) return;
310 SwingUtilities.invokeLater(new Runnable() {
311 @Override
312 public void run() {
313 model.setAvailablePlugins(task.getAvailablePlugins());
314 pnlPluginPreferences.refreshView();
315 }
316 });
317 }
318 };
319 Main.worker.submit(task);
320 Main.worker.submit(r);
321 }
322
323 private static Collection<String> getOnlinePluginSites() {
324 Collection<String> pluginSites = new ArrayList<>(Main.pref.getPluginSites());
325 for (Iterator<String> it = pluginSites.iterator(); it.hasNext();) {
326 try {
327 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(it.next(), Main.getJOSMWebsite());
328 } catch (OfflineAccessException ex) {
329 Main.warn(ex.getMessage());
330 it.remove();
331 }
332 }
333 return pluginSites;
334 }
335
336 /**
337 * The action for downloading the list of available plugins
338 */
339 class DownloadAvailablePluginsAction extends AbstractAction {
340
341 public DownloadAvailablePluginsAction() {
342 putValue(NAME,tr("Download list"));
343 putValue(SHORT_DESCRIPTION, tr("Download the list of available plugins"));
344 putValue(SMALL_ICON, ImageProvider.get("download"));
345 }
346
347 @Override
348 public void actionPerformed(ActionEvent e) {
349 Collection<String> pluginSites = getOnlinePluginSites();
350 if (pluginSites.isEmpty()) {
351 return;
352 }
353 final ReadRemotePluginInformationTask task = new ReadRemotePluginInformationTask(pluginSites);
354 Runnable continuation = new Runnable() {
355 @Override
356 public void run() {
357 if (task.isCanceled()) return;
358 SwingUtilities.invokeLater(new Runnable() {
359 @Override
360 public void run() {
361 model.updateAvailablePlugins(task.getAvailablePlugins());
362 pnlPluginPreferences.refreshView();
363 Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion()); // fix #7030
364 }
365 });
366 }
367 };
368 Main.worker.submit(task);
369 Main.worker.submit(continuation);
370 }
371
372 }
373
374 /**
375 * The action for updating the list of selected plugins
376 */
377 class UpdateSelectedPluginsAction extends AbstractAction {
378 public UpdateSelectedPluginsAction() {
379 putValue(NAME,tr("Update plugins"));
380 putValue(SHORT_DESCRIPTION, tr("Update the selected plugins"));
381 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
382 }
383
384 protected void alertNothingToUpdate() {
385 try {
386 SwingUtilities.invokeAndWait(new Runnable() {
387 @Override
388 public void run() {
389 HelpAwareOptionPane.showOptionDialog(
390 pnlPluginPreferences,
391 tr("All installed plugins are up to date. JOSM does not have to download newer versions."),
392 tr("Plugins up to date"),
393 JOptionPane.INFORMATION_MESSAGE,
394 null // FIXME: provide help context
395 );
396 }
397 });
398 } catch (InterruptedException | InvocationTargetException e) {
399 Main.error(e);
400 }
401 }
402
403 @Override
404 public void actionPerformed(ActionEvent e) {
405 final List<PluginInformation> toUpdate = model.getSelectedPlugins();
406 // the async task for downloading plugins
407 final PluginDownloadTask pluginDownloadTask = new PluginDownloadTask(
408 pnlPluginPreferences,
409 toUpdate,
410 tr("Update plugins")
411 );
412 // the async task for downloading plugin information
413 final ReadRemotePluginInformationTask pluginInfoDownloadTask = new ReadRemotePluginInformationTask(getOnlinePluginSites());
414
415 // to be run asynchronously after the plugin download
416 //
417 final Runnable pluginDownloadContinuation = new Runnable() {
418 @Override
419 public void run() {
420 if (pluginDownloadTask.isCanceled())
421 return;
422 boolean restartRequired = false;
423 for (PluginInformation pi : pluginDownloadTask.getDownloadedPlugins()) {
424 if (!model.getNewlyActivatedPlugins().contains(pi) || !pi.canloadatruntime) {
425 restartRequired = true;
426 break;
427 }
428 }
429 notifyDownloadResults(pnlPluginPreferences, pluginDownloadTask, restartRequired);
430 model.refreshLocalPluginVersion(pluginDownloadTask.getDownloadedPlugins());
431 model.clearPendingPlugins(pluginDownloadTask.getDownloadedPlugins());
432 GuiHelper.runInEDT(new Runnable() {
433 @Override
434 public void run() {
435 pnlPluginPreferences.refreshView();
436 }
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.