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

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

Better error messages when loading plugins

  • Property svn:eol-style set to native
File size: 52.7 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.download.DownloadSelection;
55import org.openstreetmap.josm.gui.help.HelpUtil;
56import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
57import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
58import org.openstreetmap.josm.gui.progress.ProgressMonitor;
59import org.openstreetmap.josm.gui.widgets.JosmTextArea;
60import org.openstreetmap.josm.tools.CheckParameterUtil;
61import org.openstreetmap.josm.tools.GBC;
62import org.openstreetmap.josm.tools.I18n;
63import org.openstreetmap.josm.tools.ImageProvider;
64
65/**
66 * PluginHandler is basically a collection of static utility functions used to bootstrap
67 * and manage the loaded plugins.
68 *
69 */
70public class PluginHandler {
71
72 /**
73 * deprecated plugins that are removed on start
74 */
75 public final static Collection<DeprecatedPlugin> DEPRECATED_PLUGINS;
76 static {
77 String IN_CORE = tr("integrated into main program");
78
79 DEPRECATED_PLUGINS = Arrays.asList(new DeprecatedPlugin[] {
80 new DeprecatedPlugin("mappaint", IN_CORE),
81 new DeprecatedPlugin("unglueplugin", IN_CORE),
82 new DeprecatedPlugin("lang-de", IN_CORE),
83 new DeprecatedPlugin("lang-en_GB", IN_CORE),
84 new DeprecatedPlugin("lang-fr", IN_CORE),
85 new DeprecatedPlugin("lang-it", IN_CORE),
86 new DeprecatedPlugin("lang-pl", IN_CORE),
87 new DeprecatedPlugin("lang-ro", IN_CORE),
88 new DeprecatedPlugin("lang-ru", IN_CORE),
89 new DeprecatedPlugin("ewmsplugin", IN_CORE),
90 new DeprecatedPlugin("ywms", IN_CORE),
91 new DeprecatedPlugin("tways-0.2", IN_CORE),
92 new DeprecatedPlugin("geotagged", IN_CORE),
93 new DeprecatedPlugin("landsat", tr("replaced by new {0} plugin","lakewalker")),
94 new DeprecatedPlugin("namefinder", IN_CORE),
95 new DeprecatedPlugin("waypoints", IN_CORE),
96 new DeprecatedPlugin("slippy_map_chooser", IN_CORE),
97 new DeprecatedPlugin("tcx-support", tr("replaced by new {0} plugin","dataimport")),
98 new DeprecatedPlugin("usertools", IN_CORE),
99 new DeprecatedPlugin("AgPifoJ", IN_CORE),
100 new DeprecatedPlugin("utilsplugin", IN_CORE),
101 new DeprecatedPlugin("ghost", IN_CORE),
102 new DeprecatedPlugin("validator", IN_CORE),
103 new DeprecatedPlugin("multipoly", IN_CORE),
104 new DeprecatedPlugin("remotecontrol", IN_CORE),
105 new DeprecatedPlugin("imagery", IN_CORE),
106 new DeprecatedPlugin("slippymap", IN_CORE),
107 new DeprecatedPlugin("wmsplugin", IN_CORE),
108 new DeprecatedPlugin("ParallelWay", IN_CORE),
109 new DeprecatedPlugin("dumbutils", tr("replaced by new {0} plugin","utilsplugin2")),
110 new DeprecatedPlugin("ImproveWayAccuracy", IN_CORE),
111 new DeprecatedPlugin("Curves", tr("replaced by new {0} plugin","utilsplugin2")),
112 new DeprecatedPlugin("epsg31287", tr("replaced by new {0} plugin", "proj4j")),
113 new DeprecatedPlugin("licensechange", tr("no longer required")),
114 new DeprecatedPlugin("restart", IN_CORE)
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 // Add all plugins already loaded (to include early plugins when checking late ones)
428 Collection<PluginInformation> allPlugins = new HashSet<PluginInformation>(plugins);
429 for (PluginProxy proxy : pluginList) {
430 allPlugins.add(proxy.getPluginInformation());
431 }
432
433 return checkRequiredPluginsPreconditions(parent, allPlugins, plugin, true);
434 }
435
436 /**
437 * Checks if required plugins preconditions for loading the plugin <code>plugin</code> are met.
438 * No other plugins this plugin depends on should be missing.
439 *
440 * @param parent The parent Component used to display error popup
441 * @param plugins the collection of all loaded plugins
442 * @param plugin the plugin for which preconditions are checked
443 * @param local Determines if the local or up-to-date plugin dependencies are to be checked.
444 * @return true, if the preconditions are met; false otherwise
445 * @since 5601
446 */
447 public static boolean checkRequiredPluginsPreconditions(Component parent, Collection<PluginInformation> plugins, PluginInformation plugin, boolean local) {
448
449 String requires = local ? plugin.localrequires : plugin.requires;
450
451 // make sure the dependencies to other plugins are not broken
452 //
453 if (requires != null) {
454 Set<String> pluginNames = new HashSet<String>();
455 for (PluginInformation pi: plugins) {
456 pluginNames.add(pi.name);
457 }
458 Set<String> missingPlugins = new HashSet<String>();
459 List<String> requiredPlugins = local ? plugin.getLocalRequiredPlugins() : plugin.getRequiredPlugins();
460 for (String requiredPlugin : requiredPlugins) {
461 if (!pluginNames.contains(requiredPlugin)) {
462 missingPlugins.add(requiredPlugin);
463 }
464 }
465 if (!missingPlugins.isEmpty()) {
466 alertMissingRequiredPlugin(parent, plugin.name, missingPlugins);
467 return false;
468 }
469 }
470 return true;
471 }
472
473 /**
474 * Creates a class loader for loading plugin code.
475 *
476 * @param plugins the collection of plugins which are going to be loaded with this
477 * class loader
478 * @return the class loader
479 */
480 public static ClassLoader createClassLoader(Collection<PluginInformation> plugins) {
481 // iterate all plugins and collect all libraries of all plugins:
482 List<URL> allPluginLibraries = new LinkedList<URL>();
483 File pluginDir = Main.pref.getPluginsDirectory();
484 for (PluginInformation info : plugins) {
485 if (info.libraries == null) {
486 continue;
487 }
488 allPluginLibraries.addAll(info.libraries);
489 File pluginJar = new File(pluginDir, info.name + ".jar");
490 I18n.addTexts(pluginJar);
491 URL pluginJarUrl = PluginInformation.fileToURL(pluginJar);
492 allPluginLibraries.add(pluginJarUrl);
493 }
494
495 // create a classloader for all plugins:
496 URL[] jarUrls = new URL[allPluginLibraries.size()];
497 jarUrls = allPluginLibraries.toArray(jarUrls);
498 URLClassLoader pluginClassLoader = new URLClassLoader(jarUrls, Main.class.getClassLoader());
499 return pluginClassLoader;
500 }
501
502 /**
503 * Loads and instantiates the plugin described by <code>plugin</code> using
504 * the class loader <code>pluginClassLoader</code>.
505 *
506 * @param plugin the plugin
507 * @param pluginClassLoader the plugin class loader
508 */
509 public static void loadPlugin(Component parent, PluginInformation plugin, ClassLoader pluginClassLoader) {
510 String msg = tr("Could not load plugin {0}. Delete from preferences?", plugin.name);
511 try {
512 Class<?> klass = plugin.loadClass(pluginClassLoader);
513 if (klass != null) {
514 System.out.println(tr("loading plugin ''{0}'' (version {1})", plugin.name, plugin.localversion));
515 PluginProxy pluginProxy = plugin.load(klass);
516 pluginList.add(pluginProxy);
517 Main.addMapFrameListener(pluginProxy);
518 }
519 msg = null;
520 } catch (PluginException e) {
521 System.err.println(e.getMessage());
522 Throwable cause = e.getCause();
523 if (cause != null) {
524 msg = cause.getLocalizedMessage();
525 if (msg != null) {
526 System.err.println("Cause: " + cause.getClass().getName()+": " + msg);
527 } else {
528 cause.printStackTrace();
529 }
530 }
531 if (e.getCause() instanceof ClassNotFoundException) {
532 msg = tr("<html>Could not load plugin {0} because the plugin<br>main class ''{1}'' was not found.<br>"
533 + "Delete from preferences?</html>", plugin.name, plugin.className);
534 }
535 } catch (Throwable e) {
536 e.printStackTrace();
537 }
538 if(msg != null && confirmDisablePlugin(parent, msg, plugin.name)) {
539 Main.pref.removeFromCollection("plugins", plugin.name);
540 }
541 }
542
543 /**
544 * Loads the plugin in <code>plugins</code> from locally available jar files into
545 * memory.
546 *
547 * @param plugins the list of plugins
548 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
549 */
550 public static void loadPlugins(Component parent,Collection<PluginInformation> plugins, ProgressMonitor monitor) {
551 if (monitor == null) {
552 monitor = NullProgressMonitor.INSTANCE;
553 }
554 try {
555 monitor.beginTask(tr("Loading plugins ..."));
556 monitor.subTask(tr("Checking plugin preconditions..."));
557 List<PluginInformation> toLoad = new LinkedList<PluginInformation>();
558 for (PluginInformation pi: plugins) {
559 if (checkLoadPreconditions(parent, plugins, pi)) {
560 toLoad.add(pi);
561 }
562 }
563 // sort the plugins according to their "staging" equivalence class. The
564 // lower the value of "stage" the earlier the plugin should be loaded.
565 //
566 Collections.sort(
567 toLoad,
568 new Comparator<PluginInformation>() {
569 public int compare(PluginInformation o1, PluginInformation o2) {
570 if (o1.stage < o2.stage) return -1;
571 if (o1.stage == o2.stage) return 0;
572 return 1;
573 }
574 }
575 );
576 if (toLoad.isEmpty())
577 return;
578
579 ClassLoader pluginClassLoader = createClassLoader(toLoad);
580 sources.add(0, pluginClassLoader);
581 monitor.setTicksCount(toLoad.size());
582 for (PluginInformation info : toLoad) {
583 monitor.setExtraText(tr("Loading plugin ''{0}''...", info.name));
584 loadPlugin(parent, info, pluginClassLoader);
585 monitor.worked(1);
586 }
587 } finally {
588 monitor.finishTask();
589 }
590 }
591
592 /**
593 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early}
594 * set to true.
595 *
596 * @param plugins the collection of plugins
597 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
598 */
599 public static void loadEarlyPlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
600 List<PluginInformation> earlyPlugins = new ArrayList<PluginInformation>(plugins.size());
601 for (PluginInformation pi: plugins) {
602 if (pi.early) {
603 earlyPlugins.add(pi);
604 }
605 }
606 loadPlugins(parent, earlyPlugins, monitor);
607 }
608
609 /**
610 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early}
611 * set to false.
612 *
613 * @param plugins the collection of plugins
614 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
615 */
616 public static void loadLatePlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
617 List<PluginInformation> latePlugins = new ArrayList<PluginInformation>(plugins.size());
618 for (PluginInformation pi: plugins) {
619 if (!pi.early) {
620 latePlugins.add(pi);
621 }
622 }
623 loadPlugins(parent, latePlugins, monitor);
624 }
625
626 /**
627 * Loads locally available plugin information from local plugin jars and from cached
628 * plugin lists.
629 *
630 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
631 * @return the list of locally available plugin information
632 *
633 */
634 private static Map<String, PluginInformation> loadLocallyAvailablePluginInformation(ProgressMonitor monitor) {
635 if (monitor == null) {
636 monitor = NullProgressMonitor.INSTANCE;
637 }
638 try {
639 ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(monitor);
640 ExecutorService service = Executors.newSingleThreadExecutor();
641 Future<?> future = service.submit(task);
642 try {
643 future.get();
644 } catch(ExecutionException e) {
645 e.printStackTrace();
646 return null;
647 } catch(InterruptedException e) {
648 e.printStackTrace();
649 return null;
650 }
651 HashMap<String, PluginInformation> ret = new HashMap<String, PluginInformation>();
652 for (PluginInformation pi: task.getAvailablePlugins()) {
653 ret.put(pi.name, pi);
654 }
655 return ret;
656 } finally {
657 monitor.finishTask();
658 }
659 }
660
661 private static void alertMissingPluginInformation(Component parent, Collection<String> plugins) {
662 StringBuilder sb = new StringBuilder();
663 sb.append("<html>");
664 sb.append(trn("JOSM could not find information about the following plugin:",
665 "JOSM could not find information about the following plugins:",
666 plugins.size()));
667 sb.append("<ul>");
668 for (String plugin: plugins) {
669 sb.append("<li>").append(plugin).append("</li>");
670 }
671 sb.append("</ul>");
672 sb.append(trn("The plugin is not going to be loaded.",
673 "The plugins are not going to be loaded.",
674 plugins.size()));
675 sb.append("</html>");
676 HelpAwareOptionPane.showOptionDialog(
677 parent,
678 sb.toString(),
679 tr("Warning"),
680 JOptionPane.WARNING_MESSAGE,
681 HelpUtil.ht("/Plugin/Loading#MissingPluginInfos")
682 );
683 }
684
685 /**
686 * Builds the set of plugins to load. Deprecated and unmaintained plugins are filtered
687 * out. This involves user interaction. This method displays alert and confirmation
688 * messages.
689 *
690 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
691 * @return the set of plugins to load (as set of plugin names)
692 */
693 public static List<PluginInformation> buildListOfPluginsToLoad(Component parent, ProgressMonitor monitor) {
694 if (monitor == null) {
695 monitor = NullProgressMonitor.INSTANCE;
696 }
697 try {
698 monitor.beginTask(tr("Determine plugins to load..."));
699 Set<String> plugins = new HashSet<String>();
700 plugins.addAll(Main.pref.getCollection("plugins", new LinkedList<String>()));
701 if (System.getProperty("josm.plugins") != null) {
702 plugins.addAll(Arrays.asList(System.getProperty("josm.plugins").split(",")));
703 }
704 monitor.subTask(tr("Removing deprecated plugins..."));
705 filterDeprecatedPlugins(parent, plugins);
706 monitor.subTask(tr("Removing unmaintained plugins..."));
707 filterUnmaintainedPlugins(parent, plugins);
708 Map<String, PluginInformation> infos = loadLocallyAvailablePluginInformation(monitor.createSubTaskMonitor(1,false));
709 List<PluginInformation> ret = new LinkedList<PluginInformation>();
710 for (Iterator<String> it = plugins.iterator(); it.hasNext();) {
711 String plugin = it.next();
712 if (infos.containsKey(plugin)) {
713 ret.add(infos.get(plugin));
714 it.remove();
715 }
716 }
717 if (!plugins.isEmpty()) {
718 alertMissingPluginInformation(parent, plugins);
719 }
720 return ret;
721 } finally {
722 monitor.finishTask();
723 }
724 }
725
726 private static void alertFailedPluginUpdate(Component parent, Collection<PluginInformation> plugins) {
727 StringBuffer sb = new StringBuffer();
728 sb.append("<html>");
729 sb.append(trn(
730 "Updating the following plugin has failed:",
731 "Updating the following plugins has failed:",
732 plugins.size()
733 )
734 );
735 sb.append("<ul>");
736 for (PluginInformation pi: plugins) {
737 sb.append("<li>").append(pi.name).append("</li>");
738 }
739 sb.append("</ul>");
740 sb.append(trn(
741 "Please open the Preference Dialog after JOSM has started and try to update it manually.",
742 "Please open the Preference Dialog after JOSM has started and try to update them manually.",
743 plugins.size()
744 ));
745 sb.append("</html>");
746 HelpAwareOptionPane.showOptionDialog(
747 parent,
748 sb.toString(),
749 tr("Plugin update failed"),
750 JOptionPane.ERROR_MESSAGE,
751 HelpUtil.ht("/Plugin/Loading#FailedPluginUpdated")
752 );
753 }
754
755 private static Set<PluginInformation> findRequiredPluginsToDownload(
756 Collection<PluginInformation> pluginsToUpdate, List<PluginInformation> allPlugins, Set<PluginInformation> pluginsToDownload) {
757 Set<PluginInformation> result = new HashSet<PluginInformation>();
758 for (PluginInformation pi : pluginsToUpdate) {
759 for (String name : pi.getRequiredPlugins()) {
760 try {
761 PluginInformation installedPlugin = PluginInformation.findPlugin(name);
762 if (installedPlugin == null) {
763 // New required plugin is not installed, find its PluginInformation
764 PluginInformation reqPlugin = null;
765 for (PluginInformation pi2 : allPlugins) {
766 if (pi2.getName().equals(name)) {
767 reqPlugin = pi2;
768 break;
769 }
770 }
771 // Required plugin is known but not already on download list
772 if (reqPlugin != null && !pluginsToDownload.contains(reqPlugin)) {
773 result.add(reqPlugin);
774 }
775 }
776 } catch (PluginException e) {
777 System.out.println(tr("Warning: failed to find plugin {0}", name));
778 e.printStackTrace();
779 }
780 }
781 }
782 return result;
783 }
784
785 /**
786 * Updates the plugins in <code>plugins</code>.
787 *
788 * @param parent the parent component for message boxes
789 * @param plugins the collection of plugins to update. Must not be null.
790 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
791 * @throws IllegalArgumentException thrown if plugins is null
792 */
793 public static List<PluginInformation> updatePlugins(Component parent,
794 List<PluginInformation> plugins, ProgressMonitor monitor)
795 throws IllegalArgumentException{
796 CheckParameterUtil.ensureParameterNotNull(plugins, "plugins");
797 if (monitor == null) {
798 monitor = NullProgressMonitor.INSTANCE;
799 }
800 try {
801 monitor.beginTask("");
802 ExecutorService service = Executors.newSingleThreadExecutor();
803
804 // try to download the plugin lists
805 //
806 ReadRemotePluginInformationTask task1 = new ReadRemotePluginInformationTask(
807 monitor.createSubTaskMonitor(1,false),
808 Main.pref.getPluginSites()
809 );
810 Future<?> future = service.submit(task1);
811 List<PluginInformation> allPlugins = null;
812
813 try {
814 future.get();
815 allPlugins = task1.getAvailablePlugins();
816 plugins = buildListOfPluginsToLoad(parent,monitor.createSubTaskMonitor(1, false));
817 } catch(ExecutionException e) {
818 System.out.println(tr("Warning: failed to download plugin information list"));
819 e.printStackTrace();
820 // don't abort in case of error, continue with downloading plugins below
821 } catch(InterruptedException e) {
822 System.out.println(tr("Warning: failed to download plugin information list"));
823 e.printStackTrace();
824 // don't abort in case of error, continue with downloading plugins below
825 }
826
827 // filter plugins which actually have to be updated
828 //
829 Collection<PluginInformation> pluginsToUpdate = new ArrayList<PluginInformation>();
830 for(PluginInformation pi: plugins) {
831 if (pi.isUpdateRequired()) {
832 pluginsToUpdate.add(pi);
833 }
834 }
835
836 if (!pluginsToUpdate.isEmpty()) {
837
838 Set<PluginInformation> pluginsToDownload = new HashSet<PluginInformation>(pluginsToUpdate);
839
840 if (allPlugins != null) {
841 // Updated plugins may need additional plugin dependencies currently not installed
842 //
843 Set<PluginInformation> additionalPlugins = findRequiredPluginsToDownload(pluginsToUpdate, allPlugins, pluginsToDownload);
844 pluginsToDownload.addAll(additionalPlugins);
845
846 // Iterate on required plugins, if they need themselves another plugins (i.e A needs B, but B needs C)
847 while (!additionalPlugins.isEmpty()) {
848 // Install the additional plugins to load them later
849 plugins.addAll(additionalPlugins);
850 additionalPlugins = findRequiredPluginsToDownload(additionalPlugins, allPlugins, pluginsToDownload);
851 pluginsToDownload.addAll(additionalPlugins);
852 }
853 }
854
855 // try to update the locally installed plugins
856 //
857 PluginDownloadTask task2 = new PluginDownloadTask(
858 monitor.createSubTaskMonitor(1,false),
859 pluginsToDownload,
860 tr("Update plugins")
861 );
862
863 future = service.submit(task2);
864 try {
865 future.get();
866 } catch(ExecutionException e) {
867 e.printStackTrace();
868 alertFailedPluginUpdate(parent, pluginsToUpdate);
869 return plugins;
870 } catch(InterruptedException e) {
871 e.printStackTrace();
872 alertFailedPluginUpdate(parent, pluginsToUpdate);
873 return plugins;
874 }
875
876 // Update Plugin info for downloaded plugins
877 //
878 refreshLocalUpdatedPluginInfo(task2.getDownloadedPlugins());
879
880 // notify user if downloading a locally installed plugin failed
881 //
882 if (! task2.getFailedPlugins().isEmpty()) {
883 alertFailedPluginUpdate(parent, task2.getFailedPlugins());
884 return plugins;
885 }
886 }
887 } finally {
888 monitor.finishTask();
889 }
890 // remember the update because it was successful
891 //
892 Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion());
893 Main.pref.put("pluginmanager.lastupdate", Long.toString(System.currentTimeMillis()));
894 return plugins;
895 }
896
897 /**
898 * Ask the user for confirmation that a plugin shall be disabled.
899 *
900 * @param reason the reason for disabling the plugin
901 * @param name the plugin name
902 * @return true, if the plugin shall be disabled; false, otherwise
903 */
904 public static boolean confirmDisablePlugin(Component parent, String reason, String name) {
905 ButtonSpec [] options = new ButtonSpec[] {
906 new ButtonSpec(
907 tr("Disable plugin"),
908 ImageProvider.get("dialogs", "delete"),
909 tr("Click to delete the plugin ''{0}''", name),
910 null /* no specific help context */
911 ),
912 new ButtonSpec(
913 tr("Keep plugin"),
914 ImageProvider.get("cancel"),
915 tr("Click to keep the plugin ''{0}''", name),
916 null /* no specific help context */
917 )
918 };
919 int ret = HelpAwareOptionPane.showOptionDialog(
920 parent,
921 reason,
922 tr("Disable plugin"),
923 JOptionPane.WARNING_MESSAGE,
924 null,
925 options,
926 options[0],
927 null // FIXME: add help topic
928 );
929 return ret == 0;
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 JosmTextArea a = new JosmTextArea(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 JosmTextArea description = new JosmTextArea((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.