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

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

JOSM "multipoly-convert" plugin no longer required since this function has been integrated into core in r3704, 3 years ago

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