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

Last change on this file since 7668 was 7668, checked in by stoecker, 10 years ago

cleanup icons, mark undetected icons, set proper mimetype, delete unused icons, update geticons script

  • Property svn:eol-style set to native
File size: 22.9 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) {
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 sb.append(buildDownloadSummary(task));
131 if (!downloaded.isEmpty()) {
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 @Override
279 public boolean ok() {
280 if (! pluginPreferencesActivated)
281 return false;
282 pnlPluginUpdatePolicy.rememberInPreferences();
283 if (model.isActivePluginsChanged()) {
284 LinkedList<String> l = new LinkedList<>(model.getSelectedPluginNames());
285 Collections.sort(l);
286 Main.pref.putCollection("plugins", l);
287 return true;
288 }
289 return false;
290 }
291
292 /**
293 * Reads locally available information about plugins from the local file system.
294 * Scans cached plugin lists from plugin download sites and locally available
295 * plugin jar files.
296 *
297 */
298 public void readLocalPluginInformation() {
299 final ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask();
300 Runnable r = new Runnable() {
301 @Override
302 public void run() {
303 if (task.isCanceled()) return;
304 SwingUtilities.invokeLater(new Runnable() {
305 @Override
306 public void run() {
307 model.setAvailablePlugins(task.getAvailablePlugins());
308 pnlPluginPreferences.refreshView();
309 }
310 });
311 }
312 };
313 Main.worker.submit(task);
314 Main.worker.submit(r);
315 }
316
317 private static Collection<String> getOnlinePluginSites() {
318 Collection<String> pluginSites = new ArrayList<>(Main.pref.getPluginSites());
319 for (Iterator<String> it = pluginSites.iterator(); it.hasNext();) {
320 try {
321 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(it.next(), Main.getJOSMWebsite());
322 } catch (OfflineAccessException ex) {
323 Main.warn(ex.getMessage());
324 it.remove();
325 }
326 }
327 return pluginSites;
328 }
329
330 /**
331 * The action for downloading the list of available plugins
332 */
333 class DownloadAvailablePluginsAction extends AbstractAction {
334
335 public DownloadAvailablePluginsAction() {
336 putValue(NAME,tr("Download list"));
337 putValue(SHORT_DESCRIPTION, tr("Download the list of available plugins"));
338 putValue(SMALL_ICON, ImageProvider.get("download"));
339 }
340
341 @Override
342 public void actionPerformed(ActionEvent e) {
343 Collection<String> pluginSites = getOnlinePluginSites();
344 if (pluginSites.isEmpty()) {
345 return;
346 }
347 final ReadRemotePluginInformationTask task = new ReadRemotePluginInformationTask(pluginSites);
348 Runnable continuation = new Runnable() {
349 @Override
350 public void run() {
351 if (task.isCanceled()) return;
352 SwingUtilities.invokeLater(new Runnable() {
353 @Override
354 public void run() {
355 model.updateAvailablePlugins(task.getAvailablePlugins());
356 pnlPluginPreferences.refreshView();
357 Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion()); // fix #7030
358 }
359 });
360 }
361 };
362 Main.worker.submit(task);
363 Main.worker.submit(continuation);
364 }
365
366 }
367
368 /**
369 * The action for updating the list of selected plugins
370 */
371 class UpdateSelectedPluginsAction extends AbstractAction {
372 public UpdateSelectedPluginsAction() {
373 putValue(NAME,tr("Update plugins"));
374 putValue(SHORT_DESCRIPTION, tr("Update the selected plugins"));
375 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
376 }
377
378 protected void alertNothingToUpdate() {
379 try {
380 SwingUtilities.invokeAndWait(new Runnable() {
381 @Override
382 public void run() {
383 HelpAwareOptionPane.showOptionDialog(
384 pnlPluginPreferences,
385 tr("All installed plugins are up to date. JOSM does not have to download newer versions."),
386 tr("Plugins up to date"),
387 JOptionPane.INFORMATION_MESSAGE,
388 null // FIXME: provide help context
389 );
390 }
391 });
392 } catch (InterruptedException | InvocationTargetException e) {
393 Main.error(e);
394 }
395 }
396
397 @Override
398 public void actionPerformed(ActionEvent e) {
399 final List<PluginInformation> toUpdate = model.getSelectedPlugins();
400 // the async task for downloading plugins
401 final PluginDownloadTask pluginDownloadTask = new PluginDownloadTask(
402 pnlPluginPreferences,
403 toUpdate,
404 tr("Update plugins")
405 );
406 // the async task for downloading plugin information
407 final ReadRemotePluginInformationTask pluginInfoDownloadTask = new ReadRemotePluginInformationTask(getOnlinePluginSites());
408
409 // to be run asynchronously after the plugin download
410 //
411 final Runnable pluginDownloadContinuation = new Runnable() {
412 @Override
413 public void run() {
414 if (pluginDownloadTask.isCanceled())
415 return;
416 notifyDownloadResults(pnlPluginPreferences, pluginDownloadTask);
417 model.refreshLocalPluginVersion(pluginDownloadTask.getDownloadedPlugins());
418 model.clearPendingPlugins(pluginDownloadTask.getDownloadedPlugins());
419 GuiHelper.runInEDT(new Runnable() {
420 @Override
421 public void run() {
422 pnlPluginPreferences.refreshView(); }
423 });
424 }
425 };
426
427 // to be run asynchronously after the plugin list download
428 //
429 final Runnable pluginInfoDownloadContinuation = new Runnable() {
430 @Override
431 public void run() {
432 if (pluginInfoDownloadTask.isCanceled())
433 return;
434 model.updateAvailablePlugins(pluginInfoDownloadTask.getAvailablePlugins());
435 // select plugins which actually have to be updated
436 //
437 Iterator<PluginInformation> it = toUpdate.iterator();
438 while(it.hasNext()) {
439 PluginInformation pi = it.next();
440 if (!pi.isUpdateRequired()) {
441 it.remove();
442 }
443 }
444 if (toUpdate.isEmpty()) {
445 alertNothingToUpdate();
446 return;
447 }
448 pluginDownloadTask.setPluginsToDownload(toUpdate);
449 Main.worker.submit(pluginDownloadTask);
450 Main.worker.submit(pluginDownloadContinuation);
451 }
452 };
453
454 Main.worker.submit(pluginInfoDownloadTask);
455 Main.worker.submit(pluginInfoDownloadContinuation);
456 }
457 }
458
459
460 /**
461 * The action for configuring the plugin download sites
462 *
463 */
464 class ConfigureSitesAction extends AbstractAction {
465 public ConfigureSitesAction() {
466 putValue(NAME,tr("Configure sites..."));
467 putValue(SHORT_DESCRIPTION, tr("Configure the list of sites where plugins are downloaded from"));
468 putValue(SMALL_ICON, ImageProvider.get("dialogs", "settings"));
469 }
470
471 @Override
472 public void actionPerformed(ActionEvent e) {
473 configureSites();
474 }
475 }
476
477 /**
478 * Applies the current filter condition in the filter text field to the
479 * model
480 */
481 class SearchFieldAdapter implements DocumentListener {
482 public void filter() {
483 String expr = tfFilter.getText().trim();
484 if (expr.isEmpty()) {
485 expr = null;
486 }
487 model.filterDisplayedPlugins(expr);
488 pnlPluginPreferences.refreshView();
489 }
490
491 @Override
492 public void changedUpdate(DocumentEvent arg0) {
493 filter();
494 }
495
496 @Override
497 public void insertUpdate(DocumentEvent arg0) {
498 filter();
499 }
500
501 @Override
502 public void removeUpdate(DocumentEvent arg0) {
503 filter();
504 }
505 }
506
507 private static class PluginConfigurationSitesPanel extends JPanel {
508
509 private DefaultListModel<String> model;
510
511 protected final void build() {
512 setLayout(new GridBagLayout());
513 add(new JLabel(tr("Add JOSM Plugin description URL.")), GBC.eol());
514 model = new DefaultListModel<>();
515 for (String s : Main.pref.getPluginSites()) {
516 model.addElement(s);
517 }
518 final JList<String> list = new JList<>(model);
519 add(new JScrollPane(list), GBC.std().fill());
520 JPanel buttons = new JPanel(new GridBagLayout());
521 buttons.add(new JButton(new AbstractAction(tr("Add")){
522 @Override
523 public void actionPerformed(ActionEvent e) {
524 String s = JOptionPane.showInputDialog(
525 JOptionPane.getFrameForComponent(PluginConfigurationSitesPanel.this),
526 tr("Add JOSM Plugin description URL."),
527 tr("Enter URL"),
528 JOptionPane.QUESTION_MESSAGE
529 );
530 if (s != null) {
531 model.addElement(s);
532 }
533 }
534 }), GBC.eol().fill(GBC.HORIZONTAL));
535 buttons.add(new JButton(new AbstractAction(tr("Edit")){
536 @Override
537 public void actionPerformed(ActionEvent e) {
538 if (list.getSelectedValue() == null) {
539 JOptionPane.showMessageDialog(
540 JOptionPane.getFrameForComponent(PluginConfigurationSitesPanel.this),
541 tr("Please select an entry."),
542 tr("Warning"),
543 JOptionPane.WARNING_MESSAGE
544 );
545 return;
546 }
547 String s = (String)JOptionPane.showInputDialog(
548 Main.parent,
549 tr("Edit JOSM Plugin description URL."),
550 tr("JOSM Plugin description URL"),
551 JOptionPane.QUESTION_MESSAGE,
552 null,
553 null,
554 list.getSelectedValue()
555 );
556 if (s != null) {
557 model.setElementAt(s, list.getSelectedIndex());
558 }
559 }
560 }), GBC.eol().fill(GBC.HORIZONTAL));
561 buttons.add(new JButton(new AbstractAction(tr("Delete")){
562 @Override
563 public void actionPerformed(ActionEvent event) {
564 if (list.getSelectedValue() == null) {
565 JOptionPane.showMessageDialog(
566 JOptionPane.getFrameForComponent(PluginConfigurationSitesPanel.this),
567 tr("Please select an entry."),
568 tr("Warning"),
569 JOptionPane.WARNING_MESSAGE
570 );
571 return;
572 }
573 model.removeElement(list.getSelectedValue());
574 }
575 }), GBC.eol().fill(GBC.HORIZONTAL));
576 add(buttons, GBC.eol());
577 }
578
579 public PluginConfigurationSitesPanel() {
580 build();
581 }
582
583 public List<String> getUpdateSites() {
584 if (model.getSize() == 0) return Collections.emptyList();
585 List<String> ret = new ArrayList<>(model.getSize());
586 for (int i=0; i< model.getSize();i++){
587 ret.add(model.get(i));
588 }
589 return ret;
590 }
591 }
592}
Note: See TracBrowser for help on using the repository browser.