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

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

New interface MapFrameListener to allow core classes to listen to MapFrame changes (previously only plugins could be notified of these changes)

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