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

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

fix #7754 - plugin management: unable to activate plugin after aborted download + fix EDT violations

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