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

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

include early plugins in the plugin classloader, allowing late plugins to rely on early ones

  • Property svn:eol-style set to native
File size: 53.0 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
485 // Add all plugins already loaded (to include early plugins in the classloader, allowing late plugins to rely on early ones)
486 Collection<PluginInformation> allPlugins = new HashSet<PluginInformation>(plugins);
487 for (PluginProxy proxy : pluginList) {
488 allPlugins.add(proxy.getPluginInformation());
489 }
490
491 for (PluginInformation info : allPlugins) {
492 if (info.libraries == null) {
493 continue;
494 }
495 allPluginLibraries.addAll(info.libraries);
496 File pluginJar = new File(pluginDir, info.name + ".jar");
497 I18n.addTexts(pluginJar);
498 URL pluginJarUrl = PluginInformation.fileToURL(pluginJar);
499 allPluginLibraries.add(pluginJarUrl);
500 }
501
502 // create a classloader for all plugins:
503 URL[] jarUrls = new URL[allPluginLibraries.size()];
504 jarUrls = allPluginLibraries.toArray(jarUrls);
505 URLClassLoader pluginClassLoader = new URLClassLoader(jarUrls, Main.class.getClassLoader());
506 return pluginClassLoader;
507 }
508
509 /**
510 * Loads and instantiates the plugin described by <code>plugin</code> using
511 * the class loader <code>pluginClassLoader</code>.
512 *
513 * @param plugin the plugin
514 * @param pluginClassLoader the plugin class loader
515 */
516 public static void loadPlugin(Component parent, PluginInformation plugin, ClassLoader pluginClassLoader) {
517 String msg = tr("Could not load plugin {0}. Delete from preferences?", plugin.name);
518 try {
519 Class<?> klass = plugin.loadClass(pluginClassLoader);
520 if (klass != null) {
521 System.out.println(tr("loading plugin ''{0}'' (version {1})", plugin.name, plugin.localversion));
522 PluginProxy pluginProxy = plugin.load(klass);
523 pluginList.add(pluginProxy);
524 Main.addMapFrameListener(pluginProxy);
525 }
526 msg = null;
527 } catch (PluginException e) {
528 System.err.println(e.getMessage());
529 Throwable cause = e.getCause();
530 if (cause != null) {
531 msg = cause.getLocalizedMessage();
532 if (msg != null) {
533 System.err.println("Cause: " + cause.getClass().getName()+": " + msg);
534 } else {
535 cause.printStackTrace();
536 }
537 }
538 if (e.getCause() instanceof ClassNotFoundException) {
539 msg = tr("<html>Could not load plugin {0} because the plugin<br>main class ''{1}'' was not found.<br>"
540 + "Delete from preferences?</html>", plugin.name, plugin.className);
541 }
542 } catch (Throwable e) {
543 e.printStackTrace();
544 }
545 if(msg != null && confirmDisablePlugin(parent, msg, plugin.name)) {
546 Main.pref.removeFromCollection("plugins", plugin.name);
547 }
548 }
549
550 /**
551 * Loads the plugin in <code>plugins</code> from locally available jar files into
552 * memory.
553 *
554 * @param plugins the list of plugins
555 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
556 */
557 public static void loadPlugins(Component parent,Collection<PluginInformation> plugins, ProgressMonitor monitor) {
558 if (monitor == null) {
559 monitor = NullProgressMonitor.INSTANCE;
560 }
561 try {
562 monitor.beginTask(tr("Loading plugins ..."));
563 monitor.subTask(tr("Checking plugin preconditions..."));
564 List<PluginInformation> toLoad = new LinkedList<PluginInformation>();
565 for (PluginInformation pi: plugins) {
566 if (checkLoadPreconditions(parent, plugins, pi)) {
567 toLoad.add(pi);
568 }
569 }
570 // sort the plugins according to their "staging" equivalence class. The
571 // lower the value of "stage" the earlier the plugin should be loaded.
572 //
573 Collections.sort(
574 toLoad,
575 new Comparator<PluginInformation>() {
576 public int compare(PluginInformation o1, PluginInformation o2) {
577 if (o1.stage < o2.stage) return -1;
578 if (o1.stage == o2.stage) return 0;
579 return 1;
580 }
581 }
582 );
583 if (toLoad.isEmpty())
584 return;
585
586 ClassLoader pluginClassLoader = createClassLoader(toLoad);
587 sources.add(0, pluginClassLoader);
588 monitor.setTicksCount(toLoad.size());
589 for (PluginInformation info : toLoad) {
590 monitor.setExtraText(tr("Loading plugin ''{0}''...", info.name));
591 loadPlugin(parent, info, pluginClassLoader);
592 monitor.worked(1);
593 }
594 } finally {
595 monitor.finishTask();
596 }
597 }
598
599 /**
600 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early}
601 * set to true.
602 *
603 * @param plugins the collection of plugins
604 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
605 */
606 public static void loadEarlyPlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
607 List<PluginInformation> earlyPlugins = new ArrayList<PluginInformation>(plugins.size());
608 for (PluginInformation pi: plugins) {
609 if (pi.early) {
610 earlyPlugins.add(pi);
611 }
612 }
613 loadPlugins(parent, earlyPlugins, monitor);
614 }
615
616 /**
617 * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early}
618 * set to false.
619 *
620 * @param plugins the collection of plugins
621 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
622 */
623 public static void loadLatePlugins(Component parent, Collection<PluginInformation> plugins, ProgressMonitor monitor) {
624 List<PluginInformation> latePlugins = new ArrayList<PluginInformation>(plugins.size());
625 for (PluginInformation pi: plugins) {
626 if (!pi.early) {
627 latePlugins.add(pi);
628 }
629 }
630 loadPlugins(parent, latePlugins, monitor);
631 }
632
633 /**
634 * Loads locally available plugin information from local plugin jars and from cached
635 * plugin lists.
636 *
637 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
638 * @return the list of locally available plugin information
639 *
640 */
641 private static Map<String, PluginInformation> loadLocallyAvailablePluginInformation(ProgressMonitor monitor) {
642 if (monitor == null) {
643 monitor = NullProgressMonitor.INSTANCE;
644 }
645 try {
646 ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(monitor);
647 ExecutorService service = Executors.newSingleThreadExecutor();
648 Future<?> future = service.submit(task);
649 try {
650 future.get();
651 } catch(ExecutionException e) {
652 e.printStackTrace();
653 return null;
654 } catch(InterruptedException e) {
655 e.printStackTrace();
656 return null;
657 }
658 HashMap<String, PluginInformation> ret = new HashMap<String, PluginInformation>();
659 for (PluginInformation pi: task.getAvailablePlugins()) {
660 ret.put(pi.name, pi);
661 }
662 return ret;
663 } finally {
664 monitor.finishTask();
665 }
666 }
667
668 private static void alertMissingPluginInformation(Component parent, Collection<String> plugins) {
669 StringBuilder sb = new StringBuilder();
670 sb.append("<html>");
671 sb.append(trn("JOSM could not find information about the following plugin:",
672 "JOSM could not find information about the following plugins:",
673 plugins.size()));
674 sb.append("<ul>");
675 for (String plugin: plugins) {
676 sb.append("<li>").append(plugin).append("</li>");
677 }
678 sb.append("</ul>");
679 sb.append(trn("The plugin is not going to be loaded.",
680 "The plugins are not going to be loaded.",
681 plugins.size()));
682 sb.append("</html>");
683 HelpAwareOptionPane.showOptionDialog(
684 parent,
685 sb.toString(),
686 tr("Warning"),
687 JOptionPane.WARNING_MESSAGE,
688 HelpUtil.ht("/Plugin/Loading#MissingPluginInfos")
689 );
690 }
691
692 /**
693 * Builds the set of plugins to load. Deprecated and unmaintained plugins are filtered
694 * out. This involves user interaction. This method displays alert and confirmation
695 * messages.
696 *
697 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
698 * @return the set of plugins to load (as set of plugin names)
699 */
700 public static List<PluginInformation> buildListOfPluginsToLoad(Component parent, ProgressMonitor monitor) {
701 if (monitor == null) {
702 monitor = NullProgressMonitor.INSTANCE;
703 }
704 try {
705 monitor.beginTask(tr("Determine plugins to load..."));
706 Set<String> plugins = new HashSet<String>();
707 plugins.addAll(Main.pref.getCollection("plugins", new LinkedList<String>()));
708 if (System.getProperty("josm.plugins") != null) {
709 plugins.addAll(Arrays.asList(System.getProperty("josm.plugins").split(",")));
710 }
711 monitor.subTask(tr("Removing deprecated plugins..."));
712 filterDeprecatedPlugins(parent, plugins);
713 monitor.subTask(tr("Removing unmaintained plugins..."));
714 filterUnmaintainedPlugins(parent, plugins);
715 Map<String, PluginInformation> infos = loadLocallyAvailablePluginInformation(monitor.createSubTaskMonitor(1,false));
716 List<PluginInformation> ret = new LinkedList<PluginInformation>();
717 for (Iterator<String> it = plugins.iterator(); it.hasNext();) {
718 String plugin = it.next();
719 if (infos.containsKey(plugin)) {
720 ret.add(infos.get(plugin));
721 it.remove();
722 }
723 }
724 if (!plugins.isEmpty()) {
725 alertMissingPluginInformation(parent, plugins);
726 }
727 return ret;
728 } finally {
729 monitor.finishTask();
730 }
731 }
732
733 private static void alertFailedPluginUpdate(Component parent, Collection<PluginInformation> plugins) {
734 StringBuffer sb = new StringBuffer();
735 sb.append("<html>");
736 sb.append(trn(
737 "Updating the following plugin has failed:",
738 "Updating the following plugins has failed:",
739 plugins.size()
740 )
741 );
742 sb.append("<ul>");
743 for (PluginInformation pi: plugins) {
744 sb.append("<li>").append(pi.name).append("</li>");
745 }
746 sb.append("</ul>");
747 sb.append(trn(
748 "Please open the Preference Dialog after JOSM has started and try to update it manually.",
749 "Please open the Preference Dialog after JOSM has started and try to update them manually.",
750 plugins.size()
751 ));
752 sb.append("</html>");
753 HelpAwareOptionPane.showOptionDialog(
754 parent,
755 sb.toString(),
756 tr("Plugin update failed"),
757 JOptionPane.ERROR_MESSAGE,
758 HelpUtil.ht("/Plugin/Loading#FailedPluginUpdated")
759 );
760 }
761
762 private static Set<PluginInformation> findRequiredPluginsToDownload(
763 Collection<PluginInformation> pluginsToUpdate, List<PluginInformation> allPlugins, Set<PluginInformation> pluginsToDownload) {
764 Set<PluginInformation> result = new HashSet<PluginInformation>();
765 for (PluginInformation pi : pluginsToUpdate) {
766 for (String name : pi.getRequiredPlugins()) {
767 try {
768 PluginInformation installedPlugin = PluginInformation.findPlugin(name);
769 if (installedPlugin == null) {
770 // New required plugin is not installed, find its PluginInformation
771 PluginInformation reqPlugin = null;
772 for (PluginInformation pi2 : allPlugins) {
773 if (pi2.getName().equals(name)) {
774 reqPlugin = pi2;
775 break;
776 }
777 }
778 // Required plugin is known but not already on download list
779 if (reqPlugin != null && !pluginsToDownload.contains(reqPlugin)) {
780 result.add(reqPlugin);
781 }
782 }
783 } catch (PluginException e) {
784 System.out.println(tr("Warning: failed to find plugin {0}", name));
785 e.printStackTrace();
786 }
787 }
788 }
789 return result;
790 }
791
792 /**
793 * Updates the plugins in <code>plugins</code>.
794 *
795 * @param parent the parent component for message boxes
796 * @param plugins the collection of plugins to update. Must not be null.
797 * @param monitor the progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null.
798 * @throws IllegalArgumentException thrown if plugins is null
799 */
800 public static List<PluginInformation> updatePlugins(Component parent,
801 List<PluginInformation> plugins, ProgressMonitor monitor)
802 throws IllegalArgumentException{
803 CheckParameterUtil.ensureParameterNotNull(plugins, "plugins");
804 if (monitor == null) {
805 monitor = NullProgressMonitor.INSTANCE;
806 }
807 try {
808 monitor.beginTask("");
809 ExecutorService service = Executors.newSingleThreadExecutor();
810
811 // try to download the plugin lists
812 //
813 ReadRemotePluginInformationTask task1 = new ReadRemotePluginInformationTask(
814 monitor.createSubTaskMonitor(1,false),
815 Main.pref.getPluginSites()
816 );
817 Future<?> future = service.submit(task1);
818 List<PluginInformation> allPlugins = null;
819
820 try {
821 future.get();
822 allPlugins = task1.getAvailablePlugins();
823 plugins = buildListOfPluginsToLoad(parent,monitor.createSubTaskMonitor(1, false));
824 } catch(ExecutionException e) {
825 System.out.println(tr("Warning: failed to download plugin information list"));
826 e.printStackTrace();
827 // don't abort in case of error, continue with downloading plugins below
828 } catch(InterruptedException e) {
829 System.out.println(tr("Warning: failed to download plugin information list"));
830 e.printStackTrace();
831 // don't abort in case of error, continue with downloading plugins below
832 }
833
834 // filter plugins which actually have to be updated
835 //
836 Collection<PluginInformation> pluginsToUpdate = new ArrayList<PluginInformation>();
837 for(PluginInformation pi: plugins) {
838 if (pi.isUpdateRequired()) {
839 pluginsToUpdate.add(pi);
840 }
841 }
842
843 if (!pluginsToUpdate.isEmpty()) {
844
845 Set<PluginInformation> pluginsToDownload = new HashSet<PluginInformation>(pluginsToUpdate);
846
847 if (allPlugins != null) {
848 // Updated plugins may need additional plugin dependencies currently not installed
849 //
850 Set<PluginInformation> additionalPlugins = findRequiredPluginsToDownload(pluginsToUpdate, allPlugins, pluginsToDownload);
851 pluginsToDownload.addAll(additionalPlugins);
852
853 // Iterate on required plugins, if they need themselves another plugins (i.e A needs B, but B needs C)
854 while (!additionalPlugins.isEmpty()) {
855 // Install the additional plugins to load them later
856 plugins.addAll(additionalPlugins);
857 additionalPlugins = findRequiredPluginsToDownload(additionalPlugins, allPlugins, pluginsToDownload);
858 pluginsToDownload.addAll(additionalPlugins);
859 }
860 }
861
862 // try to update the locally installed plugins
863 //
864 PluginDownloadTask task2 = new PluginDownloadTask(
865 monitor.createSubTaskMonitor(1,false),
866 pluginsToDownload,
867 tr("Update plugins")
868 );
869
870 future = service.submit(task2);
871 try {
872 future.get();
873 } catch(ExecutionException e) {
874 e.printStackTrace();
875 alertFailedPluginUpdate(parent, pluginsToUpdate);
876 return plugins;
877 } catch(InterruptedException e) {
878 e.printStackTrace();
879 alertFailedPluginUpdate(parent, pluginsToUpdate);
880 return plugins;
881 }
882
883 // Update Plugin info for downloaded plugins
884 //
885 refreshLocalUpdatedPluginInfo(task2.getDownloadedPlugins());
886
887 // notify user if downloading a locally installed plugin failed
888 //
889 if (! task2.getFailedPlugins().isEmpty()) {
890 alertFailedPluginUpdate(parent, task2.getFailedPlugins());
891 return plugins;
892 }
893 }
894 } finally {
895 monitor.finishTask();
896 }
897 // remember the update because it was successful
898 //
899 Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion());
900 Main.pref.put("pluginmanager.lastupdate", Long.toString(System.currentTimeMillis()));
901 return plugins;
902 }
903
904 /**
905 * Ask the user for confirmation that a plugin shall be disabled.
906 *
907 * @param reason the reason for disabling the plugin
908 * @param name the plugin name
909 * @return true, if the plugin shall be disabled; false, otherwise
910 */
911 public static boolean confirmDisablePlugin(Component parent, String reason, String name) {
912 ButtonSpec [] options = new ButtonSpec[] {
913 new ButtonSpec(
914 tr("Disable plugin"),
915 ImageProvider.get("dialogs", "delete"),
916 tr("Click to delete the plugin ''{0}''", name),
917 null /* no specific help context */
918 ),
919 new ButtonSpec(
920 tr("Keep plugin"),
921 ImageProvider.get("cancel"),
922 tr("Click to keep the plugin ''{0}''", name),
923 null /* no specific help context */
924 )
925 };
926 int ret = HelpAwareOptionPane.showOptionDialog(
927 parent,
928 reason,
929 tr("Disable plugin"),
930 JOptionPane.WARNING_MESSAGE,
931 null,
932 options,
933 options[0],
934 null // FIXME: add help topic
935 );
936 return ret == 0;
937 }
938
939 public static Object getPlugin(String name) {
940 for (PluginProxy plugin : pluginList)
941 if(plugin.getPluginInformation().name.equals(name))
942 return plugin.plugin;
943 return null;
944 }
945
946 public static void addDownloadSelection(List<DownloadSelection> downloadSelections) {
947 for (PluginProxy p : pluginList) {
948 p.addDownloadSelection(downloadSelections);
949 }
950 }
951
952 public static void getPreferenceSetting(Collection<PreferenceSettingFactory> settings) {
953 for (PluginProxy plugin : pluginList) {
954 settings.add(new PluginPreferenceFactory(plugin));
955 }
956 }
957
958 /**
959 * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding
960 * ".jar" files.
961 *
962 * If {@code dowarn} is true, this methods emits warning messages on the console if a downloaded
963 * but not yet installed plugin .jar can't be be installed. If {@code dowarn} is false, the
964 * installation of the respective plugin is sillently skipped.
965 *
966 * @param dowarn if true, warning messages are displayed; false otherwise
967 */
968 public static void installDownloadedPlugins(boolean dowarn) {
969 File pluginDir = Main.pref.getPluginsDirectory();
970 if (! pluginDir.exists() || ! pluginDir.isDirectory() || ! pluginDir.canWrite())
971 return;
972
973 final File[] files = pluginDir.listFiles(new FilenameFilter() {
974 public boolean accept(File dir, String name) {
975 return name.endsWith(".jar.new");
976 }});
977
978 for (File updatedPlugin : files) {
979 final String filePath = updatedPlugin.getPath();
980 File plugin = new File(filePath.substring(0, filePath.length() - 4));
981 String pluginName = updatedPlugin.getName().substring(0, updatedPlugin.getName().length() - 8);
982 if (plugin.exists()) {
983 if (!plugin.delete() && dowarn) {
984 System.err.println(tr("Warning: failed to delete outdated plugin ''{0}''.", plugin.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 continue;
987 }
988 }
989 try {
990 // Check the plugin is a valid and accessible JAR file before installing it (fix #7754)
991 new JarFile(updatedPlugin).close();
992 } catch (Exception e) {
993 if (dowarn) {
994 System.err.println(tr("Warning: failed to install plugin ''{0}'' from temporary download file ''{1}''. {2}", plugin.toString(), updatedPlugin.toString(), e.getLocalizedMessage()));
995 }
996 continue;
997 }
998 // Install plugin
999 if (!updatedPlugin.renameTo(plugin) && dowarn) {
1000 System.err.println(tr("Warning: failed to install plugin ''{0}'' from temporary download file ''{1}''. Renaming failed.", plugin.toString(), updatedPlugin.toString()));
1001 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));
1002 }
1003 }
1004 return;
1005 }
1006
1007 /**
1008 * Determines if the specified file is a valid and accessible JAR file.
1009 * @param jar The fil to check
1010 * @return true if file can be opened as a JAR file.
1011 * @since 5723
1012 */
1013 public static boolean isValidJar(File jar) {
1014 if (jar != null && jar.exists() && jar.canRead()) {
1015 try {
1016 new JarFile(jar).close();
1017 } catch (Exception e) {
1018 return false;
1019 }
1020 return true;
1021 }
1022 return false;
1023 }
1024
1025 /**
1026 * Replies the updated jar file for the given plugin name.
1027 * @param name The plugin name to find.
1028 * @return the updated jar file for the given plugin name. null if not found or not readable.
1029 * @since 5601
1030 */
1031 public static File findUpdatedJar(String name) {
1032 File pluginDir = Main.pref.getPluginsDirectory();
1033 // Find the downloaded file. We have tried to install the downloaded plugins
1034 // (PluginHandler.installDownloadedPlugins). This succeeds depending on the platform.
1035 File downloadedPluginFile = new File(pluginDir, name + ".jar.new");
1036 if (!isValidJar(downloadedPluginFile)) {
1037 downloadedPluginFile = new File(pluginDir, name + ".jar");
1038 if (!isValidJar(downloadedPluginFile)) {
1039 return null;
1040 }
1041 }
1042 return downloadedPluginFile;
1043 }
1044
1045 /**
1046 * Refreshes the given PluginInformation objects with new contents read from their corresponding jar file.
1047 * @param updatedPlugins The PluginInformation objects to update.
1048 * @since 5601
1049 */
1050 public static void refreshLocalUpdatedPluginInfo(Collection<PluginInformation> updatedPlugins) {
1051 if (updatedPlugins == null) return;
1052 for (PluginInformation pi : updatedPlugins) {
1053 File downloadedPluginFile = findUpdatedJar(pi.name);
1054 if (downloadedPluginFile == null) {
1055 continue;
1056 }
1057 try {
1058 pi.updateFromJar(new PluginInformation(downloadedPluginFile, pi.name));
1059 } catch(PluginException e) {
1060 e.printStackTrace();
1061 }
1062 }
1063 }
1064
1065 private static boolean confirmDeactivatingPluginAfterException(PluginProxy plugin) {
1066 ButtonSpec [] options = new ButtonSpec[] {
1067 new ButtonSpec(
1068 tr("Disable plugin"),
1069 ImageProvider.get("dialogs", "delete"),
1070 tr("Click to disable the plugin ''{0}''", plugin.getPluginInformation().name),
1071 null /* no specific help context */
1072 ),
1073 new ButtonSpec(
1074 tr("Keep plugin"),
1075 ImageProvider.get("cancel"),
1076 tr("Click to keep the plugin ''{0}''",plugin.getPluginInformation().name),
1077 null /* no specific help context */
1078 )
1079 };
1080
1081 StringBuffer msg = new StringBuffer();
1082 msg.append("<html>");
1083 msg.append(tr("An unexpected exception occurred that may have come from the ''{0}'' plugin.", plugin.getPluginInformation().name));
1084 msg.append("<br>");
1085 if(plugin.getPluginInformation().author != null) {
1086 msg.append(tr("According to the information within the plugin, the author is {0}.", plugin.getPluginInformation().author));
1087 msg.append("<br>");
1088 }
1089 msg.append(tr("Try updating to the newest version of this plugin before reporting a bug."));
1090 msg.append("<br>");
1091 msg.append(tr("Should the plugin be disabled?"));
1092 msg.append("</html>");
1093
1094 int ret = HelpAwareOptionPane.showOptionDialog(
1095 Main.parent,
1096 msg.toString(),
1097 tr("Update plugins"),
1098 JOptionPane.QUESTION_MESSAGE,
1099 null,
1100 options,
1101 options[0],
1102 ht("/ErrorMessages#ErrorInPlugin")
1103 );
1104 return ret == 0;
1105 }
1106
1107 /**
1108 * Replies the plugin which most likely threw the exception <code>ex</code>.
1109 *
1110 * @param ex the exception
1111 * @return the plugin; null, if the exception probably wasn't thrown from a plugin
1112 */
1113 private static PluginProxy getPluginCausingException(Throwable ex) {
1114 PluginProxy err = null;
1115 StackTraceElement[] stack = ex.getStackTrace();
1116 /* remember the error position, as multiple plugins may be involved,
1117 we search the topmost one */
1118 int pos = stack.length;
1119 for (PluginProxy p : pluginList) {
1120 String baseClass = p.getPluginInformation().className;
1121 baseClass = baseClass.substring(0, baseClass.lastIndexOf("."));
1122 for (int elpos = 0; elpos < pos; ++elpos) {
1123 if (stack[elpos].getClassName().startsWith(baseClass)) {
1124 pos = elpos;
1125 err = p;
1126 }
1127 }
1128 }
1129 return err;
1130 }
1131
1132 /**
1133 * Checks whether the exception <code>e</code> was thrown by a plugin. If so,
1134 * conditionally deactivates the plugin, but asks the user first.
1135 *
1136 * @param e the exception
1137 */
1138 public static void disablePluginAfterException(Throwable e) {
1139 PluginProxy plugin = null;
1140 // Check for an explicit problem when calling a plugin function
1141 if (e instanceof PluginException) {
1142 plugin = ((PluginException) e).plugin;
1143 }
1144 if (plugin == null) {
1145 plugin = getPluginCausingException(e);
1146 }
1147 if (plugin == null)
1148 // don't know what plugin threw the exception
1149 return;
1150
1151 Set<String> plugins = new HashSet<String>(
1152 Main.pref.getCollection("plugins",Collections.<String> emptySet())
1153 );
1154 if (! plugins.contains(plugin.getPluginInformation().name))
1155 // plugin not activated ? strange in this context but anyway, don't bother
1156 // the user with dialogs, skip conditional deactivation
1157 return;
1158
1159 if (!confirmDeactivatingPluginAfterException(plugin))
1160 // user doesn't want to deactivate the plugin
1161 return;
1162
1163 // deactivate the plugin
1164 plugins.remove(plugin.getPluginInformation().name);
1165 Main.pref.putCollection("plugins", plugins);
1166 JOptionPane.showMessageDialog(
1167 Main.parent,
1168 tr("The plugin has been removed from the configuration. Please restart JOSM to unload the plugin."),
1169 tr("Information"),
1170 JOptionPane.INFORMATION_MESSAGE
1171 );
1172 return;
1173 }
1174
1175 public static String getBugReportText() {
1176 String text = "";
1177 LinkedList <String> pl = new LinkedList<String>(Main.pref.getCollection("plugins", new LinkedList<String>()));
1178 for (final PluginProxy pp : pluginList) {
1179 PluginInformation pi = pp.getPluginInformation();
1180 pl.remove(pi.name);
1181 pl.add(pi.name + " (" + (pi.localversion != null && !pi.localversion.equals("")
1182 ? pi.localversion : "unknown") + ")");
1183 }
1184 Collections.sort(pl);
1185 for (String s : pl) {
1186 text += "Plugin: " + s + "\n";
1187 }
1188 return text;
1189 }
1190
1191 public static JPanel getInfoPanel() {
1192 JPanel pluginTab = new JPanel(new GridBagLayout());
1193 for (final PluginProxy p : pluginList) {
1194 final PluginInformation info = p.getPluginInformation();
1195 String name = info.name
1196 + (info.version != null && !info.version.equals("") ? " Version: " + info.version : "");
1197 pluginTab.add(new JLabel(name), GBC.std());
1198 pluginTab.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
1199 pluginTab.add(new JButton(new AbstractAction(tr("Information")) {
1200 public void actionPerformed(ActionEvent event) {
1201 StringBuilder b = new StringBuilder();
1202 for (Entry<String, String> e : info.attr.entrySet()) {
1203 b.append(e.getKey());
1204 b.append(": ");
1205 b.append(e.getValue());
1206 b.append("\n");
1207 }
1208 JosmTextArea a = new JosmTextArea(10, 40);
1209 a.setEditable(false);
1210 a.setText(b.toString());
1211 a.setCaretPosition(0);
1212 JOptionPane.showMessageDialog(Main.parent, new JScrollPane(a), tr("Plugin information"),
1213 JOptionPane.INFORMATION_MESSAGE);
1214 }
1215 }), GBC.eol());
1216
1217 JosmTextArea description = new JosmTextArea((info.description == null ? tr("no description available")
1218 : info.description));
1219 description.setEditable(false);
1220 description.setFont(new JLabel().getFont().deriveFont(Font.ITALIC));
1221 description.setLineWrap(true);
1222 description.setWrapStyleWord(true);
1223 description.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0));
1224 description.setBackground(UIManager.getColor("Panel.background"));
1225 description.setCaretPosition(0);
1226
1227 pluginTab.add(description, GBC.eop().fill(GBC.HORIZONTAL));
1228 }
1229 return pluginTab;
1230 }
1231
1232 static private class UpdatePluginsMessagePanel extends JPanel {
1233 private JMultilineLabel lblMessage;
1234 private JCheckBox cbDontShowAgain;
1235
1236 protected void build() {
1237 setLayout(new GridBagLayout());
1238 GridBagConstraints gc = new GridBagConstraints();
1239 gc.anchor = GridBagConstraints.NORTHWEST;
1240 gc.fill = GridBagConstraints.BOTH;
1241 gc.weightx = 1.0;
1242 gc.weighty = 1.0;
1243 gc.insets = new Insets(5,5,5,5);
1244 add(lblMessage = new JMultilineLabel(""), gc);
1245 lblMessage.setFont(lblMessage.getFont().deriveFont(Font.PLAIN));
1246
1247 gc.gridy = 1;
1248 gc.fill = GridBagConstraints.HORIZONTAL;
1249 gc.weighty = 0.0;
1250 add(cbDontShowAgain = new JCheckBox(tr("Do not ask again and remember my decision (go to Preferences->Plugins to change it later)")), gc);
1251 cbDontShowAgain.setFont(cbDontShowAgain.getFont().deriveFont(Font.PLAIN));
1252 }
1253
1254 public UpdatePluginsMessagePanel() {
1255 build();
1256 }
1257
1258 public void setMessage(String message) {
1259 lblMessage.setText(message);
1260 }
1261
1262 public void initDontShowAgain(String preferencesKey) {
1263 String policy = Main.pref.get(preferencesKey, "ask");
1264 policy = policy.trim().toLowerCase();
1265 cbDontShowAgain.setSelected(! policy.equals("ask"));
1266 }
1267
1268 public boolean isRememberDecision() {
1269 return cbDontShowAgain.isSelected();
1270 }
1271 }
1272}
Note: See TracBrowser for help on using the repository browser.