source: josm/trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java@ 6340

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

refactor of some GUI/widgets classes (impacts some plugins):

  • gui.BookmarkList moves to gui.download as it is only meant to be used by gui.download.BookmarkSelection
  • tools.UrlLabel moves to gui.widgets
  • gui.JMultilineLabel, gui.MultiplitLayout, gui.MultiSplitPane move to gui.widgets
  • Property svn:eol-style set to native
File size: 55.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Component;
9import java.awt.Font;
10import java.awt.GridBagConstraints;
11import java.awt.GridBagLayout;
12import java.awt.Insets;
13import java.awt.event.ActionEvent;
14import java.io.File;
15import java.io.FilenameFilter;
16import java.net.URL;
17import java.net.URLClassLoader;
18import java.security.AccessController;
19import java.security.PrivilegedAction;
20import java.util.ArrayList;
21import java.util.Arrays;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.Comparator;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.Iterator;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.Map;
31import java.util.Map.Entry;
32import java.util.Set;
33import java.util.TreeSet;
34import java.util.concurrent.ExecutionException;
35import java.util.concurrent.ExecutorService;
36import java.util.concurrent.Executors;
37import java.util.concurrent.Future;
38import java.util.jar.JarFile;
39
40import javax.swing.AbstractAction;
41import javax.swing.BorderFactory;
42import javax.swing.Box;
43import javax.swing.JButton;
44import javax.swing.JCheckBox;
45import javax.swing.JLabel;
46import javax.swing.JOptionPane;
47import javax.swing.JPanel;
48import javax.swing.JScrollPane;
49import javax.swing.UIManager;
50
51import org.openstreetmap.josm.Main;
52import org.openstreetmap.josm.data.Version;
53import org.openstreetmap.josm.gui.HelpAwareOptionPane;
54import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
55import org.openstreetmap.josm.gui.download.DownloadSelection;
56import org.openstreetmap.josm.gui.help.HelpUtil;
57import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
58import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
59import org.openstreetmap.josm.gui.progress.ProgressMonitor;
60import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
61import org.openstreetmap.josm.gui.widgets.JosmTextArea;
62import org.openstreetmap.josm.tools.CheckParameterUtil;
63import org.openstreetmap.josm.tools.GBC;
64import org.openstreetmap.josm.tools.I18n;
65import org.openstreetmap.josm.tools.ImageProvider;
66
67/**
68 * PluginHandler is basically a collection of static utility functions used to bootstrap
69 * and manage the loaded plugins.
70 * @since 1326
71 */
72public class PluginHandler {
73
74 /**
75 * Deprecated plugins that are removed on start
76 */
77 public final static Collection<DeprecatedPlugin> DEPRECATED_PLUGINS;
78 static {
79 String IN_CORE = tr("integrated into main program");
80
81 DEPRECATED_PLUGINS = Arrays.asList(new DeprecatedPlugin[] {
82 new DeprecatedPlugin("mappaint", IN_CORE),
83 new DeprecatedPlugin("unglueplugin", IN_CORE),
84 new DeprecatedPlugin("lang-de", IN_CORE),
85 new DeprecatedPlugin("lang-en_GB", IN_CORE),
86 new DeprecatedPlugin("lang-fr", IN_CORE),
87 new DeprecatedPlugin("lang-it", IN_CORE),
88 new DeprecatedPlugin("lang-pl", IN_CORE),
89 new DeprecatedPlugin("lang-ro", IN_CORE),
90 new DeprecatedPlugin("lang-ru", IN_CORE),
91 new DeprecatedPlugin("ewmsplugin", IN_CORE),
92 new DeprecatedPlugin("ywms", IN_CORE),
93 new DeprecatedPlugin("tways-0.2", IN_CORE),
94 new DeprecatedPlugin("geotagged", IN_CORE),
95 new DeprecatedPlugin("landsat", tr("replaced by new {0} plugin","lakewalker")),
96 new DeprecatedPlugin("namefinder", IN_CORE),
97 new DeprecatedPlugin("waypoints", IN_CORE),
98 new DeprecatedPlugin("slippy_map_chooser", IN_CORE),
99 new DeprecatedPlugin("tcx-support", tr("replaced by new {0} plugin","dataimport")),
100 new DeprecatedPlugin("usertools", IN_CORE),
101 new DeprecatedPlugin("AgPifoJ", IN_CORE),
102 new DeprecatedPlugin("utilsplugin", IN_CORE),
103 new DeprecatedPlugin("ghost", IN_CORE),
104 new DeprecatedPlugin("validator", IN_CORE),
105 new DeprecatedPlugin("multipoly", IN_CORE),
106 new DeprecatedPlugin("multipoly-convert", IN_CORE),
107 new DeprecatedPlugin("remotecontrol", IN_CORE),
108 new DeprecatedPlugin("imagery", IN_CORE),
109 new DeprecatedPlugin("slippymap", IN_CORE),
110 new DeprecatedPlugin("wmsplugin", IN_CORE),
111 new DeprecatedPlugin("ParallelWay", IN_CORE),
112 new DeprecatedPlugin("dumbutils", tr("replaced by new {0} plugin","utilsplugin2")),
113 new DeprecatedPlugin("ImproveWayAccuracy", IN_CORE),
114 new DeprecatedPlugin("Curves", tr("replaced by new {0} plugin","utilsplugin2")),
115 new DeprecatedPlugin("epsg31287", tr("replaced by new {0} plugin", "proj4j")),
116 new DeprecatedPlugin("licensechange", tr("no longer required")),
117 new DeprecatedPlugin("restart", IN_CORE)
118 });
119 }
120
121 /**
122 * Description of a deprecated plugin
123 */
124 public static class DeprecatedPlugin implements Comparable<DeprecatedPlugin> {
125 /** Plugin name */
126 public final String name;
127 /** Short explanation about deprecation, can be {@code null} */
128 public final String reason;
129 /** Code to run to perform migration, can be {@code null} */
130 private final Runnable migration;
131
132 /**
133 * Constructs a new {@code DeprecatedPlugin}.
134 * @param name The plugin name
135 */
136 public DeprecatedPlugin(String name) {
137 this(name, null, null);
138 }
139
140 /**
141 * Constructs a new {@code DeprecatedPlugin} with a given reason.
142 * @param name The plugin name
143 * @param reason The reason about deprecation
144 */
145 public DeprecatedPlugin(String name, String reason) {
146 this(name, reason, null);
147 }
148
149 /**
150 * Constructs a new {@code DeprecatedPlugin}.
151 * @param name The plugin name
152 * @param reason The reason about deprecation
153 * @param migration The code to run to perform migration
154 */
155 public DeprecatedPlugin(String name, String reason, Runnable migration) {
156 this.name = name;
157 this.reason = reason;
158 this.migration = migration;
159 }
160
161 /**
162 * Performs migration.
163 */
164 public void migrate() {
165 if (migration != null) {
166 migration.run();
167 }
168 }
169
170 @Override
171 public int compareTo(DeprecatedPlugin o) {
172 return name.compareTo(o.name);
173 }
174 }
175
176 /**
177 * List of unmaintained plugins. Not really up-to-date as the vast majority of plugins are not really maintained after a few months, sadly...
178 */
179 final public static String [] UNMAINTAINED_PLUGINS = new String[] {"gpsbabelgui", "Intersect_way"};
180
181 /**
182 * Default time-based update interval, in days (pluginmanager.time-based-update.interval)
183 */
184 public static final int DEFAULT_TIME_BASED_UPDATE_INTERVAL = 30;
185
186 /**
187 * All installed and loaded plugins (resp. their main classes)
188 */
189 public final static Collection<PluginProxy> pluginList = new LinkedList<PluginProxy>();
190
191 /**
192 * Add here all ClassLoader whose resource should be searched.
193 */
194 private static final List<ClassLoader> sources = new LinkedList<ClassLoader>();
195
196 static {
197 try {
198 sources.add(ClassLoader.getSystemClassLoader());
199 sources.add(org.openstreetmap.josm.gui.MainApplication.class.getClassLoader());
200 } catch (SecurityException ex) {
201 sources.add(ImageProvider.class.getClassLoader());
202 }
203 }
204
205 public static Collection<ClassLoader> getResourceClassLoaders() {
206 return Collections.unmodifiableCollection(sources);
207 }
208
209 /**
210 * Removes deprecated plugins from a collection of plugins. Modifies the
211 * collection <code>plugins</code>.
212 *
213 * Also notifies the user about removed deprecated plugins
214 *
215 * @param parent The parent Component used to display warning popup
216 * @param plugins the collection of plugins
217 */
218 private static void filterDeprecatedPlugins(Component parent, Collection<String> plugins) {
219 Set<DeprecatedPlugin> removedPlugins = new TreeSet<DeprecatedPlugin>();
220 for (DeprecatedPlugin depr : DEPRECATED_PLUGINS) {
221 if (plugins.contains(depr.name)) {
222 plugins.remove(depr.name);
223 Main.pref.removeFromCollection("plugins", depr.name);
224 removedPlugins.add(depr);
225 depr.migrate();
226 }
227 }
228 if (removedPlugins.isEmpty())
229 return;
230
231 // notify user about removed deprecated plugins
232 //
233 StringBuilder sb = new StringBuilder();
234 sb.append("<html>");
235 sb.append(trn(
236 "The following plugin is no longer necessary and has been deactivated:",
237 "The following plugins are no longer necessary and have been deactivated:",
238 removedPlugins.size()
239 ));
240 sb.append("<ul>");
241 for (DeprecatedPlugin depr: removedPlugins) {
242 sb.append("<li>").append(depr.name);
243 if (depr.reason != null) {
244 sb.append(" (").append(depr.reason).append(")");
245 }
246 sb.append("</li>");
247 }
248 sb.append("</ul>");
249 sb.append("</html>");
250 JOptionPane.showMessageDialog(
251 parent,
252 sb.toString(),
253 tr("Warning"),
254 JOptionPane.WARNING_MESSAGE
255 );
256 }
257
258 /**
259 * Removes unmaintained plugins from a collection of plugins. Modifies the
260 * collection <code>plugins</code>. Also removes the plugin from the list
261 * of plugins in the preferences, if necessary.
262 *
263 * Asks the user for every unmaintained plugin whether it should be removed.
264 *
265 * @param plugins the collection of plugins
266 */
267 private static void filterUnmaintainedPlugins(Component parent, Collection<String> plugins) {
268 for (String unmaintained : UNMAINTAINED_PLUGINS) {
269 if (!plugins.contains(unmaintained)) {
270 continue;
271 }
272 String msg = tr("<html>Loading of the plugin \"{0}\" was requested."
273 + "<br>This plugin is no longer developed and very likely will produce errors."
274 +"<br>It should be disabled.<br>Delete from preferences?</html>", unmaintained);
275 if (confirmDisablePlugin(parent, msg,unmaintained)) {
276 Main.pref.removeFromCollection("plugins", unmaintained);
277 plugins.remove(unmaintained);
278 }
279 }
280 }
281
282 /**
283 * Checks whether the locally available plugins should be updated and
284 * asks the user if running an update is OK. An update is advised if
285 * JOSM was updated to a new version since the last plugin updates or
286 * if the plugins were last updated a long time ago.
287 *
288 * @param parent the parent component relative to which the confirmation dialog
289 * is to be displayed
290 * @return true if a plugin update should be run; false, otherwise
291 */
292 public static boolean checkAndConfirmPluginUpdate(Component parent) {
293 String message = null;
294 String togglePreferenceKey = null;
295 int v = Version.getInstance().getVersion();
296 if (Main.pref.getInteger("pluginmanager.version", 0) < v) {
297 message =
298 "<html>"
299 + tr("You updated your JOSM software.<br>"
300 + "To prevent problems the plugins should be updated as well.<br><br>"
301 + "Update plugins now?"
302 )
303 + "</html>";
304 togglePreferenceKey = "pluginmanager.version-based-update.policy";
305 } else {
306 long tim = System.currentTimeMillis();
307 long last = Main.pref.getLong("pluginmanager.lastupdate", 0);
308 Integer maxTime = Main.pref.getInteger("pluginmanager.time-based-update.interval", DEFAULT_TIME_BASED_UPDATE_INTERVAL);
309 long d = (tim - last) / (24 * 60 * 60 * 1000L);
310 if ((last <= 0) || (maxTime <= 0)) {
311 Main.pref.put("pluginmanager.lastupdate", Long.toString(tim));
312 } else if (d > maxTime) {
313 message =
314 "<html>"
315 + tr("Last plugin update more than {0} days ago.", d)
316 + "</html>";
317 togglePreferenceKey = "pluginmanager.time-based-update.policy";
318 }
319 }
320 if (message == null) return false;
321
322 ButtonSpec [] options = new ButtonSpec[] {
323 new ButtonSpec(
324 tr("Update plugins"),
325 ImageProvider.get("dialogs", "refresh"),
326 tr("Click to update the activated plugins"),
327 null /* no specific help context */
328 ),
329 new ButtonSpec(
330 tr("Skip update"),
331 ImageProvider.get("cancel"),
332 tr("Click to skip updating the activated plugins"),
333 null /* no specific help context */
334 )
335 };
336
337 UpdatePluginsMessagePanel pnlMessage = new UpdatePluginsMessagePanel();
338 pnlMessage.setMessage(message);
339 pnlMessage.initDontShowAgain(togglePreferenceKey);
340
341 // check whether automatic update at startup was disabled
342 //
343 String policy = Main.pref.get(togglePreferenceKey, "ask");
344 policy = policy.trim().toLowerCase();
345 if (policy.equals("never")) {
346 if ("pluginmanager.version-based-update.policy".equals(togglePreferenceKey)) {
347 Main.info(tr("Skipping plugin update after JOSM upgrade. Automatic update at startup is disabled."));
348 } else if ("pluginmanager.time-based-update.policy".equals(togglePreferenceKey)) {
349 Main.info(tr("Skipping plugin update after elapsed update interval. Automatic update at startup is disabled."));
350 }
351 return false;
352 }
353
354 if (policy.equals("always")) {
355 if ("pluginmanager.version-based-update.policy".equals(togglePreferenceKey)) {
356 Main.info(tr("Running plugin update after JOSM upgrade. Automatic update at startup is enabled."));
357 } else if ("pluginmanager.time-based-update.policy".equals(togglePreferenceKey)) {
358 Main.info(tr("Running plugin update after elapsed update interval. Automatic update at startup is disabled."));
359 }
360 return true;
361 }
362
363 if (!policy.equals("ask")) {
364 Main.warn(tr("Unexpected value ''{0}'' for preference ''{1}''. Assuming value ''ask''.", policy, togglePreferenceKey));
365 }
366 int ret = HelpAwareOptionPane.showOptionDialog(
367 parent,
368 pnlMessage,
369 tr("Update plugins"),
370 JOptionPane.WARNING_MESSAGE,
371 null,
372 options,
373 options[0],
374 ht("/Preferences/Plugins#AutomaticUpdate")
375 );
376
377 if (pnlMessage.isRememberDecision()) {
378 switch(ret) {
379 case 0:
380 Main.pref.put(togglePreferenceKey, "always");
381 break;
382 case JOptionPane.CLOSED_OPTION:
383 case 1:
384 Main.pref.put(togglePreferenceKey, "never");
385 break;
386 }
387 } else {
388 Main.pref.put(togglePreferenceKey, "ask");
389 }
390 return ret == 0;
391 }
392
393 /**
394 * Alerts the user if a plugin required by another plugin is missing
395 *
396 * @param parent The parent Component used to display error popup
397 * @param plugin the plugin
398 * @param missingRequiredPlugin the missing required plugin
399 */
400 private static void alertMissingRequiredPlugin(Component parent, String plugin, Set<String> missingRequiredPlugin) {
401 StringBuilder sb = new StringBuilder();
402 sb.append("<html>");
403 sb.append(trn("Plugin {0} requires a plugin which was not found. The missing plugin is:",
404 "Plugin {0} requires {1} plugins which were not found. The missing plugins are:",
405 missingRequiredPlugin.size(),
406 plugin,
407 missingRequiredPlugin.size()
408 ));
409 sb.append("<ul>");
410 for (String p: missingRequiredPlugin) {
411 sb.append("<li>").append(p).append("</li>");
412 }
413 sb.append("</ul>").append("</html>");
414 JOptionPane.showMessageDialog(
415 parent,
416 sb.toString(),
417 tr("Error"),
418 JOptionPane.ERROR_MESSAGE
419 );
420 }
421
422 private static void alertJOSMUpdateRequired(Component parent, String plugin, int requiredVersion) {
423 HelpAwareOptionPane.showOptionDialog(
424 parent,
425 tr("<html>Plugin {0} requires JOSM version {1}. The current JOSM version is {2}.<br>"
426 +"You have to update JOSM in order to use this plugin.</html>",
427 plugin, Integer.toString(requiredVersion), Version.getInstance().getVersionString()
428 ),
429 tr("Warning"),
430 JOptionPane.WARNING_MESSAGE,
431 HelpUtil.ht("/Plugin/Loading#JOSMUpdateRequired")
432 );
433 }
434
435 /**
436 * Checks whether all preconditions for loading the plugin <code>plugin</code> are met. The
437 * current JOSM version must be compatible with the plugin and no other plugins this plugin
438 * depends on should be missing.
439 *
440 * @param parent The parent Component used to display error popup
441 * @param plugins the collection of all loaded plugins
442 * @param plugin the plugin for which preconditions are checked
443 * @return true, if the preconditions are met; false otherwise
444 */
445 public static boolean checkLoadPreconditions(Component parent, Collection<PluginInformation> plugins, PluginInformation plugin) {
446
447 // make sure the plugin is compatible with the current JOSM version
448 //
449 int josmVersion = Version.getInstance().getVersion();
450 if (plugin.localmainversion > josmVersion && josmVersion != Version.JOSM_UNKNOWN_VERSION) {
451 alertJOSMUpdateRequired(parent, plugin.name, plugin.localmainversion);
452 return false;
453 }
454
455 // Add all plugins already loaded (to include early plugins when checking late ones)
456 Collection<PluginInformation> allPlugins = new HashSet<PluginInformation>(plugins);
457 for (PluginProxy proxy : pluginList) {
458 allPlugins.add(proxy.getPluginInformation());
459 }
460
461 return checkRequiredPluginsPreconditions(parent, allPlugins, plugin, true);
462 }
463
464 /**
465 * Checks if required plugins preconditions for loading the plugin <code>plugin</code> are met.
466 * No other plugins this plugin depends on should be missing.
467 *
468 * @param parent The parent Component used to display error popup
469 * @param plugins the collection of all loaded plugins
470 * @param plugin the plugin for which preconditions are checked
471 * @param local Determines if the local or up-to-date plugin dependencies are to be checked.
472 * @return true, if the preconditions are met; false otherwise
473 * @since 5601
474 */
475 public static boolean checkRequiredPluginsPreconditions(Component parent, Collection<PluginInformation> plugins, PluginInformation plugin, boolean local) {
476
477 String requires = local ? plugin.localrequires : plugin.requires;
478
479 // make sure the dependencies to other plugins are not broken
480 //
481 if (requires != null) {
482 Set<String> pluginNames = new HashSet<String>();
483 for (PluginInformation pi: plugins) {
484 pluginNames.add(pi.name);
485 }
486 Set<String> missingPlugins = new HashSet<String>();
487 List<String> requiredPlugins = local ? plugin.getLocalRequiredPlugins() : plugin.getRequiredPlugins();
488 for (String requiredPlugin : requiredPlugins) {
489 if (!pluginNames.contains(requiredPlugin)) {
490 missingPlugins.add(requiredPlugin);
491 }
492 }
493 if (!missingPlugins.isEmpty()) {
494 alertMissingRequiredPlugin(parent, plugin.name, missingPlugins);
495 return false;
496 }
497 }
498 return true;
499 }
500
501 /**
502 * Creates a class loader for loading plugin code.
503 *
504 * @param plugins the collection of plugins which are going to be loaded with this
505 * class loader
506 * @return the class loader
507 */
508 public static ClassLoader createClassLoader(Collection<PluginInformation> plugins) {
509 // iterate all plugins and collect all libraries of all plugins:
510 List<URL> allPluginLibraries = new LinkedList<URL>();
511 File pluginDir = Main.pref.getPluginsDirectory();
512
513 // Add all plugins already loaded (to include early plugins in the classloader, allowing late plugins to rely on early ones)
514 Collection<PluginInformation> allPlugins = new HashSet<PluginInformation>(plugins);
515 for (PluginProxy proxy : pluginList) {
516 allPlugins.add(proxy.getPluginInformation());
517 }
518
519 for (PluginInformation info : allPlugins) {
520 if (info.libraries == null) {
521 continue;
522 }
523 allPluginLibraries.addAll(info.libraries);
524 File pluginJar = new File(pluginDir, info.name + ".jar");
525 I18n.addTexts(pluginJar);
526 URL pluginJarUrl = PluginInformation.fileToURL(pluginJar);
527 allPluginLibraries.add(pluginJarUrl);
528 }
529
530 // create a classloader for all plugins:
531 final URL[] jarUrls = allPluginLibraries.toArray(new URL[allPluginLibraries.size()]);
532 return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
533 public ClassLoader run() {
534 return new URLClassLoader(jarUrls, Main.class.getClassLoader());
535 }
536 });
537 }
538
539 /**
540 * Loads and instantiates the plugin described by <code>plugin</code> using
541 * the class loader <code>pluginClassLoader</code>.
542 *
543 * @param parent The parent component to be used for the displayed dialog
544 * @param plugin the plugin
545 * @param pluginClassLoader the plugin class loader
546 */
547 public static void loadPlugin(Component parent, PluginInformation plugin, ClassLoader pluginClassLoader) {
548 String msg = tr("Could not load plugin {0}. Delete from preferences?", plugin.name);
549 try {
550 Class<?> klass = plugin.loadClass(pluginClassLoader);
551 if (klass != null) {
552 Main.info(tr("loading plugin ''{0}'' (version {1})", plugin.name, plugin.localversion));
553 PluginProxy pluginProxy = plugin.load(klass);
554 pluginList.add(pluginProxy);
555 Main.addMapFrameListener(pluginProxy);
556 }
557 msg = null;
558 } catch (PluginException e) {
559 Main.error(e.getMessage());
560 Throwable cause = e.getCause();
561 if (cause != null) {
562 msg = cause.getLocalizedMessage();
563 if (msg != null) {
564 Main.error("Cause: " + cause.getClass().getName()+": " + msg);
565 } else {
566 cause.printStackTrace();
567 }
568 }
569 if (e.getCause() instanceof ClassNotFoundException) {
570 msg = tr("<html>Could not load plugin {0} because the plugin<br>main class ''{1}'' was not found.<br>"
571 + "Delete from preferences?</html>", plugin.name, plugin.className);
572 }
573 } catch (Throwable e) {
574 e.printStackTrace();
575 }
576 if (msg != null && confirmDisablePlugin(parent, msg, plugin.name)) {
577 Main.pref.removeFromCollection("plugins", plugin.name);
578 }
579 }
580
581 /**
582 * Loads the plugin in <code>plugins</code> from locally available jar files into
583 * memory.
584 *
585 * @param parent The parent component to be used for the displayed dialog
586 * @param plugins the list of plugins
587 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
588 */
589 public static void loadPlugins(Component parent,Collection<PluginInformation> plugins, ProgressMonitor monitor) {
590 if (monitor == null) {
591 monitor = NullProgressMonitor.INSTANCE;
592 }
593 try {
594 monitor.beginTask(tr("Loading plugins ..."));
595 monitor.subTask(tr("Checking plugin preconditions..."));
596 List<PluginInformation> toLoad = new LinkedList<PluginInformation>();
597 for (PluginInformation pi: plugins) {
598 if (checkLoadPreconditions(parent, plugins, pi)) {
599 toLoad.add(pi);
600 }
601 }
602 // sort the plugins according to their "staging" equivalence class. The
603 // lower the value of "stage" the earlier the plugin should be loaded.
604 //
605 Collections.sort(
606 toLoad,
607 new Comparator<PluginInformation>() {
608 @Override
609 public int compare(PluginInformation o1, PluginInformation o2) {
610 if (o1.stage < o2.stage) return -1;
611 if (o1.stage == o2.stage) return 0;
612 return 1;
613 }
614 }
615 );
616 if (toLoad.isEmpty())
617 return;
618
619 ClassLoader pluginClassLoader = createClassLoader(toLoad);
620 sources.add(0, pluginClassLoader);
621 monitor.setTicksCount(toLoad.size());
622 for (PluginInformation info : toLoad) {
623 monitor.setExtraText(tr("Loading plugin ''{0}''...", info.name));
624 loadPlugin(parent, info, pluginClassLoader);
625 monitor.worked(1);
626 }
627 } finally {
628 monitor.finishTask();
629 }
630 }
631
632 /**
633 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early}
634 * set to true.
635 *
636 * @param plugins the collection of plugins
637 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
638 */
639 public static void loadEarlyPlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
640 List<PluginInformation> earlyPlugins = new ArrayList<PluginInformation>(plugins.size());
641 for (PluginInformation pi: plugins) {
642 if (pi.early) {
643 earlyPlugins.add(pi);
644 }
645 }
646 loadPlugins(parent, earlyPlugins, monitor);
647 }
648
649 /**
650 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early}
651 * set to false.
652 *
653 * @param parent The parent component to be used for the displayed dialog
654 * @param plugins the collection of plugins
655 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
656 */
657 public static void loadLatePlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
658 List<PluginInformation> latePlugins = new ArrayList<PluginInformation>(plugins.size());
659 for (PluginInformation pi: plugins) {
660 if (!pi.early) {
661 latePlugins.add(pi);
662 }
663 }
664 loadPlugins(parent, latePlugins, monitor);
665 }
666
667 /**
668 * Loads locally available plugin information from local plugin jars and from cached
669 * plugin lists.
670 *
671 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
672 * @return the list of locally available plugin information
673 *
674 */
675 private static Map<String, PluginInformation> loadLocallyAvailablePluginInformation(ProgressMonitor monitor) {
676 if (monitor == null) {
677 monitor = NullProgressMonitor.INSTANCE;
678 }
679 try {
680 ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(monitor);
681 ExecutorService service = Executors.newSingleThreadExecutor();
682 Future<?> future = service.submit(task);
683 try {
684 future.get();
685 } catch(ExecutionException e) {
686 e.printStackTrace();
687 return null;
688 } catch(InterruptedException e) {
689 Main.warn("InterruptedException in "+PluginHandler.class.getSimpleName()+" while loading locally available plugin information");
690 return null;
691 }
692 HashMap<String, PluginInformation> ret = new HashMap<String, PluginInformation>();
693 for (PluginInformation pi: task.getAvailablePlugins()) {
694 ret.put(pi.name, pi);
695 }
696 return ret;
697 } finally {
698 monitor.finishTask();
699 }
700 }
701
702 private static void alertMissingPluginInformation(Component parent, Collection<String> plugins) {
703 StringBuilder sb = new StringBuilder();
704 sb.append("<html>");
705 sb.append(trn("JOSM could not find information about the following plugin:",
706 "JOSM could not find information about the following plugins:",
707 plugins.size()));
708 sb.append("<ul>");
709 for (String plugin: plugins) {
710 sb.append("<li>").append(plugin).append("</li>");
711 }
712 sb.append("</ul>");
713 sb.append(trn("The plugin is not going to be loaded.",
714 "The plugins are not going to be loaded.",
715 plugins.size()));
716 sb.append("</html>");
717 HelpAwareOptionPane.showOptionDialog(
718 parent,
719 sb.toString(),
720 tr("Warning"),
721 JOptionPane.WARNING_MESSAGE,
722 HelpUtil.ht("/Plugin/Loading#MissingPluginInfos")
723 );
724 }
725
726 /**
727 * Builds the set of plugins to load. Deprecated and unmaintained plugins are filtered
728 * out. This involves user interaction. This method displays alert and confirmation
729 * messages.
730 *
731 * @param parent The parent component to be used for the displayed dialog
732 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
733 * @return the set of plugins to load (as set of plugin names)
734 */
735 public static List<PluginInformation> buildListOfPluginsToLoad(Component parent, ProgressMonitor monitor) {
736 if (monitor == null) {
737 monitor = NullProgressMonitor.INSTANCE;
738 }
739 try {
740 monitor.beginTask(tr("Determine plugins to load..."));
741 Set<String> plugins = new HashSet<String>();
742 plugins.addAll(Main.pref.getCollection("plugins", new LinkedList<String>()));
743 if (System.getProperty("josm.plugins") != null) {
744 plugins.addAll(Arrays.asList(System.getProperty("josm.plugins").split(",")));
745 }
746 monitor.subTask(tr("Removing deprecated plugins..."));
747 filterDeprecatedPlugins(parent, plugins);
748 monitor.subTask(tr("Removing unmaintained plugins..."));
749 filterUnmaintainedPlugins(parent, plugins);
750 Map<String, PluginInformation> infos = loadLocallyAvailablePluginInformation(monitor.createSubTaskMonitor(1,false));
751 List<PluginInformation> ret = new LinkedList<PluginInformation>();
752 for (Iterator<String> it = plugins.iterator(); it.hasNext();) {
753 String plugin = it.next();
754 if (infos.containsKey(plugin)) {
755 ret.add(infos.get(plugin));
756 it.remove();
757 }
758 }
759 if (!plugins.isEmpty()) {
760 alertMissingPluginInformation(parent, plugins);
761 }
762 return ret;
763 } finally {
764 monitor.finishTask();
765 }
766 }
767
768 private static void alertFailedPluginUpdate(Component parent, Collection<PluginInformation> plugins) {
769 StringBuffer sb = new StringBuffer();
770 sb.append("<html>");
771 sb.append(trn(
772 "Updating the following plugin has failed:",
773 "Updating the following plugins has failed:",
774 plugins.size()
775 )
776 );
777 sb.append("<ul>");
778 for (PluginInformation pi: plugins) {
779 sb.append("<li>").append(pi.name).append("</li>");
780 }
781 sb.append("</ul>");
782 sb.append(trn(
783 "Please open the Preference Dialog after JOSM has started and try to update it manually.",
784 "Please open the Preference Dialog after JOSM has started and try to update them manually.",
785 plugins.size()
786 ));
787 sb.append("</html>");
788 HelpAwareOptionPane.showOptionDialog(
789 parent,
790 sb.toString(),
791 tr("Plugin update failed"),
792 JOptionPane.ERROR_MESSAGE,
793 HelpUtil.ht("/Plugin/Loading#FailedPluginUpdated")
794 );
795 }
796
797 private static Set<PluginInformation> findRequiredPluginsToDownload(
798 Collection<PluginInformation> pluginsToUpdate, List<PluginInformation> allPlugins, Set<PluginInformation> pluginsToDownload) {
799 Set<PluginInformation> result = new HashSet<PluginInformation>();
800 for (PluginInformation pi : pluginsToUpdate) {
801 for (String name : pi.getRequiredPlugins()) {
802 try {
803 PluginInformation installedPlugin = PluginInformation.findPlugin(name);
804 if (installedPlugin == null) {
805 // New required plugin is not installed, find its PluginInformation
806 PluginInformation reqPlugin = null;
807 for (PluginInformation pi2 : allPlugins) {
808 if (pi2.getName().equals(name)) {
809 reqPlugin = pi2;
810 break;
811 }
812 }
813 // Required plugin is known but not already on download list
814 if (reqPlugin != null && !pluginsToDownload.contains(reqPlugin)) {
815 result.add(reqPlugin);
816 }
817 }
818 } catch (PluginException e) {
819 Main.warn(tr("Failed to find plugin {0}", name));
820 e.printStackTrace();
821 }
822 }
823 }
824 return result;
825 }
826
827 /**
828 * Updates the plugins in <code>plugins</code>.
829 *
830 * @param parent the parent component for message boxes
831 * @param plugins the collection of plugins to update. Must not be null.
832 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
833 * @throws IllegalArgumentException thrown if plugins is null
834 */
835 public static List<PluginInformation> updatePlugins(Component parent,
836 List<PluginInformation> plugins, ProgressMonitor monitor)
837 throws IllegalArgumentException{
838 CheckParameterUtil.ensureParameterNotNull(plugins, "plugins");
839 if (monitor == null) {
840 monitor = NullProgressMonitor.INSTANCE;
841 }
842 try {
843 monitor.beginTask("");
844 ExecutorService service = Executors.newSingleThreadExecutor();
845
846 // try to download the plugin lists
847 //
848 ReadRemotePluginInformationTask task1 = new ReadRemotePluginInformationTask(
849 monitor.createSubTaskMonitor(1,false),
850 Main.pref.getPluginSites()
851 );
852 Future<?> future = service.submit(task1);
853 List<PluginInformation> allPlugins = null;
854
855 try {
856 future.get();
857 allPlugins = task1.getAvailablePlugins();
858 plugins = buildListOfPluginsToLoad(parent,monitor.createSubTaskMonitor(1, false));
859 } catch (ExecutionException e) {
860 Main.warn(tr("Failed to download plugin information list")+": ExecutionException");
861 e.printStackTrace();
862 // don't abort in case of error, continue with downloading plugins below
863 } catch (InterruptedException e) {
864 Main.warn(tr("Failed to download plugin information list")+": InterruptedException");
865 // don't abort in case of error, continue with downloading plugins below
866 }
867
868 // filter plugins which actually have to be updated
869 //
870 Collection<PluginInformation> pluginsToUpdate = new ArrayList<PluginInformation>();
871 for (PluginInformation pi: plugins) {
872 if (pi.isUpdateRequired()) {
873 pluginsToUpdate.add(pi);
874 }
875 }
876
877 if (!pluginsToUpdate.isEmpty()) {
878
879 Set<PluginInformation> pluginsToDownload = new HashSet<PluginInformation>(pluginsToUpdate);
880
881 if (allPlugins != null) {
882 // Updated plugins may need additional plugin dependencies currently not installed
883 //
884 Set<PluginInformation> additionalPlugins = findRequiredPluginsToDownload(pluginsToUpdate, allPlugins, pluginsToDownload);
885 pluginsToDownload.addAll(additionalPlugins);
886
887 // Iterate on required plugins, if they need themselves another plugins (i.e A needs B, but B needs C)
888 while (!additionalPlugins.isEmpty()) {
889 // Install the additional plugins to load them later
890 plugins.addAll(additionalPlugins);
891 additionalPlugins = findRequiredPluginsToDownload(additionalPlugins, allPlugins, pluginsToDownload);
892 pluginsToDownload.addAll(additionalPlugins);
893 }
894 }
895
896 // try to update the locally installed plugins
897 //
898 PluginDownloadTask task2 = new PluginDownloadTask(
899 monitor.createSubTaskMonitor(1,false),
900 pluginsToDownload,
901 tr("Update plugins")
902 );
903
904 future = service.submit(task2);
905 try {
906 future.get();
907 } catch(ExecutionException e) {
908 e.printStackTrace();
909 alertFailedPluginUpdate(parent, pluginsToUpdate);
910 return plugins;
911 } catch(InterruptedException e) {
912 Main.warn("InterruptedException in "+PluginHandler.class.getSimpleName()+" while updating plugins");
913 alertFailedPluginUpdate(parent, pluginsToUpdate);
914 return plugins;
915 }
916
917 // Update Plugin info for downloaded plugins
918 //
919 refreshLocalUpdatedPluginInfo(task2.getDownloadedPlugins());
920
921 // notify user if downloading a locally installed plugin failed
922 //
923 if (! task2.getFailedPlugins().isEmpty()) {
924 alertFailedPluginUpdate(parent, task2.getFailedPlugins());
925 return plugins;
926 }
927 }
928 } finally {
929 monitor.finishTask();
930 }
931 // remember the update because it was successful
932 //
933 Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion());
934 Main.pref.put("pluginmanager.lastupdate", Long.toString(System.currentTimeMillis()));
935 return plugins;
936 }
937
938 /**
939 * Ask the user for confirmation that a plugin shall be disabled.
940 *
941 * @param parent The parent component to be used for the displayed dialog
942 * @param reason the reason for disabling the plugin
943 * @param name the plugin name
944 * @return true, if the plugin shall be disabled; false, otherwise
945 */
946 public static boolean confirmDisablePlugin(Component parent, String reason, String name) {
947 ButtonSpec [] options = new ButtonSpec[] {
948 new ButtonSpec(
949 tr("Disable plugin"),
950 ImageProvider.get("dialogs", "delete"),
951 tr("Click to delete the plugin ''{0}''", name),
952 null /* no specific help context */
953 ),
954 new ButtonSpec(
955 tr("Keep plugin"),
956 ImageProvider.get("cancel"),
957 tr("Click to keep the plugin ''{0}''", name),
958 null /* no specific help context */
959 )
960 };
961 int ret = HelpAwareOptionPane.showOptionDialog(
962 parent,
963 reason,
964 tr("Disable plugin"),
965 JOptionPane.WARNING_MESSAGE,
966 null,
967 options,
968 options[0],
969 null // FIXME: add help topic
970 );
971 return ret == 0;
972 }
973
974 /**
975 * Returns the plugin of the specified name.
976 * @param name The plugin name
977 * @return The plugin of the specified name, if installed and loaded, or {@code null} otherwise.
978 */
979 public static Object getPlugin(String name) {
980 for (PluginProxy plugin : pluginList)
981 if (plugin.getPluginInformation().name.equals(name))
982 return plugin.plugin;
983 return null;
984 }
985
986 public static void addDownloadSelection(List<DownloadSelection> downloadSelections) {
987 for (PluginProxy p : pluginList) {
988 p.addDownloadSelection(downloadSelections);
989 }
990 }
991
992 public static void getPreferenceSetting(Collection<PreferenceSettingFactory> settings) {
993 for (PluginProxy plugin : pluginList) {
994 settings.add(new PluginPreferenceFactory(plugin));
995 }
996 }
997
998 /**
999 * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding
1000 * ".jar" files.
1001 *
1002 * If {@code dowarn} is true, this methods emits warning messages on the console if a downloaded
1003 * but not yet installed plugin .jar can't be be installed. If {@code dowarn} is false, the
1004 * installation of the respective plugin is sillently skipped.
1005 *
1006 * @param dowarn if true, warning messages are displayed; false otherwise
1007 */
1008 public static void installDownloadedPlugins(boolean dowarn) {
1009 File pluginDir = Main.pref.getPluginsDirectory();
1010 if (! pluginDir.exists() || ! pluginDir.isDirectory() || ! pluginDir.canWrite())
1011 return;
1012
1013 final File[] files = pluginDir.listFiles(new FilenameFilter() {
1014 @Override
1015 public boolean accept(File dir, String name) {
1016 return name.endsWith(".jar.new");
1017 }});
1018
1019 for (File updatedPlugin : files) {
1020 final String filePath = updatedPlugin.getPath();
1021 File plugin = new File(filePath.substring(0, filePath.length() - 4));
1022 String pluginName = updatedPlugin.getName().substring(0, updatedPlugin.getName().length() - 8);
1023 if (plugin.exists()) {
1024 if (!plugin.delete() && dowarn) {
1025 Main.warn(tr("Failed to delete outdated plugin ''{0}''.", plugin.toString()));
1026 Main.warn(tr("Failed to install already downloaded plugin ''{0}''. Skipping installation. JOSM is still going to load the old plugin version.", pluginName));
1027 continue;
1028 }
1029 }
1030 try {
1031 // Check the plugin is a valid and accessible JAR file before installing it (fix #7754)
1032 new JarFile(updatedPlugin).close();
1033 } catch (Exception e) {
1034 if (dowarn) {
1035 Main.warn(tr("Failed to install plugin ''{0}'' from temporary download file ''{1}''. {2}", plugin.toString(), updatedPlugin.toString(), e.getLocalizedMessage()));
1036 }
1037 continue;
1038 }
1039 // Install plugin
1040 if (!updatedPlugin.renameTo(plugin) && dowarn) {
1041 Main.warn(tr("Failed to install plugin ''{0}'' from temporary download file ''{1}''. Renaming failed.", plugin.toString(), updatedPlugin.toString()));
1042 Main.warn(tr("Failed to install already downloaded plugin ''{0}''. Skipping installation. JOSM is still going to load the old plugin version.", pluginName));
1043 }
1044 }
1045 return;
1046 }
1047
1048 /**
1049 * Determines if the specified file is a valid and accessible JAR file.
1050 * @param jar The fil to check
1051 * @return true if file can be opened as a JAR file.
1052 * @since 5723
1053 */
1054 public static boolean isValidJar(File jar) {
1055 if (jar != null && jar.exists() && jar.canRead()) {
1056 try {
1057 new JarFile(jar).close();
1058 } catch (Exception e) {
1059 return false;
1060 }
1061 return true;
1062 }
1063 return false;
1064 }
1065
1066 /**
1067 * Replies the updated jar file for the given plugin name.
1068 * @param name The plugin name to find.
1069 * @return the updated jar file for the given plugin name. null if not found or not readable.
1070 * @since 5601
1071 */
1072 public static File findUpdatedJar(String name) {
1073 File pluginDir = Main.pref.getPluginsDirectory();
1074 // Find the downloaded file. We have tried to install the downloaded plugins
1075 // (PluginHandler.installDownloadedPlugins). This succeeds depending on the platform.
1076 File downloadedPluginFile = new File(pluginDir, name + ".jar.new");
1077 if (!isValidJar(downloadedPluginFile)) {
1078 downloadedPluginFile = new File(pluginDir, name + ".jar");
1079 if (!isValidJar(downloadedPluginFile)) {
1080 return null;
1081 }
1082 }
1083 return downloadedPluginFile;
1084 }
1085
1086 /**
1087 * Refreshes the given PluginInformation objects with new contents read from their corresponding jar file.
1088 * @param updatedPlugins The PluginInformation objects to update.
1089 * @since 5601
1090 */
1091 public static void refreshLocalUpdatedPluginInfo(Collection<PluginInformation> updatedPlugins) {
1092 if (updatedPlugins == null) return;
1093 for (PluginInformation pi : updatedPlugins) {
1094 File downloadedPluginFile = findUpdatedJar(pi.name);
1095 if (downloadedPluginFile == null) {
1096 continue;
1097 }
1098 try {
1099 pi.updateFromJar(new PluginInformation(downloadedPluginFile, pi.name));
1100 } catch(PluginException e) {
1101 e.printStackTrace();
1102 }
1103 }
1104 }
1105
1106 private static boolean confirmDeactivatingPluginAfterException(PluginProxy plugin) {
1107 ButtonSpec [] options = new ButtonSpec[] {
1108 new ButtonSpec(
1109 tr("Disable plugin"),
1110 ImageProvider.get("dialogs", "delete"),
1111 tr("Click to disable the plugin ''{0}''", plugin.getPluginInformation().name),
1112 null /* no specific help context */
1113 ),
1114 new ButtonSpec(
1115 tr("Keep plugin"),
1116 ImageProvider.get("cancel"),
1117 tr("Click to keep the plugin ''{0}''",plugin.getPluginInformation().name),
1118 null /* no specific help context */
1119 )
1120 };
1121
1122 StringBuffer msg = new StringBuffer();
1123 msg.append("<html>");
1124 msg.append(tr("An unexpected exception occurred that may have come from the ''{0}'' plugin.", plugin.getPluginInformation().name));
1125 msg.append("<br>");
1126 if(plugin.getPluginInformation().author != null) {
1127 msg.append(tr("According to the information within the plugin, the author is {0}.", plugin.getPluginInformation().author));
1128 msg.append("<br>");
1129 }
1130 msg.append(tr("Try updating to the newest version of this plugin before reporting a bug."));
1131 msg.append("<br>");
1132 msg.append(tr("Should the plugin be disabled?"));
1133 msg.append("</html>");
1134
1135 int ret = HelpAwareOptionPane.showOptionDialog(
1136 Main.parent,
1137 msg.toString(),
1138 tr("Update plugins"),
1139 JOptionPane.QUESTION_MESSAGE,
1140 null,
1141 options,
1142 options[0],
1143 ht("/ErrorMessages#ErrorInPlugin")
1144 );
1145 return ret == 0;
1146 }
1147
1148 /**
1149 * Replies the plugin which most likely threw the exception <code>ex</code>.
1150 *
1151 * @param ex the exception
1152 * @return the plugin; null, if the exception probably wasn't thrown from a plugin
1153 */
1154 private static PluginProxy getPluginCausingException(Throwable ex) {
1155 PluginProxy err = null;
1156 StackTraceElement[] stack = ex.getStackTrace();
1157 /* remember the error position, as multiple plugins may be involved,
1158 we search the topmost one */
1159 int pos = stack.length;
1160 for (PluginProxy p : pluginList) {
1161 String baseClass = p.getPluginInformation().className;
1162 baseClass = baseClass.substring(0, baseClass.lastIndexOf('.'));
1163 for (int elpos = 0; elpos < pos; ++elpos) {
1164 if (stack[elpos].getClassName().startsWith(baseClass)) {
1165 pos = elpos;
1166 err = p;
1167 }
1168 }
1169 }
1170 return err;
1171 }
1172
1173 /**
1174 * Checks whether the exception <code>e</code> was thrown by a plugin. If so,
1175 * conditionally deactivates the plugin, but asks the user first.
1176 *
1177 * @param e the exception
1178 */
1179 public static void disablePluginAfterException(Throwable e) {
1180 PluginProxy plugin = null;
1181 // Check for an explicit problem when calling a plugin function
1182 if (e instanceof PluginException) {
1183 plugin = ((PluginException) e).plugin;
1184 }
1185 if (plugin == null) {
1186 plugin = getPluginCausingException(e);
1187 }
1188 if (plugin == null)
1189 // don't know what plugin threw the exception
1190 return;
1191
1192 Set<String> plugins = new HashSet<String>(
1193 Main.pref.getCollection("plugins",Collections.<String> emptySet())
1194 );
1195 if (! plugins.contains(plugin.getPluginInformation().name))
1196 // plugin not activated ? strange in this context but anyway, don't bother
1197 // the user with dialogs, skip conditional deactivation
1198 return;
1199
1200 if (!confirmDeactivatingPluginAfterException(plugin))
1201 // user doesn't want to deactivate the plugin
1202 return;
1203
1204 // deactivate the plugin
1205 plugins.remove(plugin.getPluginInformation().name);
1206 Main.pref.putCollection("plugins", plugins);
1207 JOptionPane.showMessageDialog(
1208 Main.parent,
1209 tr("The plugin has been removed from the configuration. Please restart JOSM to unload the plugin."),
1210 tr("Information"),
1211 JOptionPane.INFORMATION_MESSAGE
1212 );
1213 return;
1214 }
1215
1216 /**
1217 * Returns the list of loaded plugins as a {@code String} to be displayed in status report. Useful for bug reports.
1218 * @return The list of loaded plugins (one plugin per line)
1219 */
1220 public static String getBugReportText() {
1221 StringBuilder text = new StringBuilder();
1222 LinkedList <String> pl = new LinkedList<String>(Main.pref.getCollection("plugins", new LinkedList<String>()));
1223 for (final PluginProxy pp : pluginList) {
1224 PluginInformation pi = pp.getPluginInformation();
1225 pl.remove(pi.name);
1226 pl.add(pi.name + " (" + (pi.localversion != null && !pi.localversion.isEmpty()
1227 ? pi.localversion : "unknown") + ")");
1228 }
1229 Collections.sort(pl);
1230 for (String s : pl) {
1231 text.append("Plugin: ").append(s).append("\n");
1232 }
1233 return text.toString();
1234 }
1235
1236 /**
1237 * Returns the list of loaded plugins as a {@code JPanel} to be displayed in About dialog.
1238 * @return The list of loaded plugins (one "line" of Swing components per plugin)
1239 */
1240 public static JPanel getInfoPanel() {
1241 JPanel pluginTab = new JPanel(new GridBagLayout());
1242 for (final PluginProxy p : pluginList) {
1243 final PluginInformation info = p.getPluginInformation();
1244 String name = info.name
1245 + (info.version != null && !info.version.isEmpty() ? " Version: " + info.version : "");
1246 pluginTab.add(new JLabel(name), GBC.std());
1247 pluginTab.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
1248 pluginTab.add(new JButton(new AbstractAction(tr("Information")) {
1249 @Override
1250 public void actionPerformed(ActionEvent event) {
1251 StringBuilder b = new StringBuilder();
1252 for (Entry<String, String> e : info.attr.entrySet()) {
1253 b.append(e.getKey());
1254 b.append(": ");
1255 b.append(e.getValue());
1256 b.append("\n");
1257 }
1258 JosmTextArea a = new JosmTextArea(10, 40);
1259 a.setEditable(false);
1260 a.setText(b.toString());
1261 a.setCaretPosition(0);
1262 JOptionPane.showMessageDialog(Main.parent, new JScrollPane(a), tr("Plugin information"),
1263 JOptionPane.INFORMATION_MESSAGE);
1264 }
1265 }), GBC.eol());
1266
1267 JosmTextArea description = new JosmTextArea((info.description == null ? tr("no description available")
1268 : info.description));
1269 description.setEditable(false);
1270 description.setFont(new JLabel().getFont().deriveFont(Font.ITALIC));
1271 description.setLineWrap(true);
1272 description.setWrapStyleWord(true);
1273 description.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0));
1274 description.setBackground(UIManager.getColor("Panel.background"));
1275 description.setCaretPosition(0);
1276
1277 pluginTab.add(description, GBC.eop().fill(GBC.HORIZONTAL));
1278 }
1279 return pluginTab;
1280 }
1281
1282 static private class UpdatePluginsMessagePanel extends JPanel {
1283 private JMultilineLabel lblMessage;
1284 private JCheckBox cbDontShowAgain;
1285
1286 protected void build() {
1287 setLayout(new GridBagLayout());
1288 GridBagConstraints gc = new GridBagConstraints();
1289 gc.anchor = GridBagConstraints.NORTHWEST;
1290 gc.fill = GridBagConstraints.BOTH;
1291 gc.weightx = 1.0;
1292 gc.weighty = 1.0;
1293 gc.insets = new Insets(5,5,5,5);
1294 add(lblMessage = new JMultilineLabel(""), gc);
1295 lblMessage.setFont(lblMessage.getFont().deriveFont(Font.PLAIN));
1296
1297 gc.gridy = 1;
1298 gc.fill = GridBagConstraints.HORIZONTAL;
1299 gc.weighty = 0.0;
1300 add(cbDontShowAgain = new JCheckBox(tr("Do not ask again and remember my decision (go to Preferences->Plugins to change it later)")), gc);
1301 cbDontShowAgain.setFont(cbDontShowAgain.getFont().deriveFont(Font.PLAIN));
1302 }
1303
1304 public UpdatePluginsMessagePanel() {
1305 build();
1306 }
1307
1308 public void setMessage(String message) {
1309 lblMessage.setText(message);
1310 }
1311
1312 public void initDontShowAgain(String preferencesKey) {
1313 String policy = Main.pref.get(preferencesKey, "ask");
1314 policy = policy.trim().toLowerCase();
1315 cbDontShowAgain.setSelected(! policy.equals("ask"));
1316 }
1317
1318 public boolean isRememberDecision() {
1319 return cbDontShowAgain.isSelected();
1320 }
1321 }
1322}
Note: See TracBrowser for help on using the repository browser.