Index: src/org/openstreetmap/josm/Main.java
===================================================================
--- src/org/openstreetmap/josm/Main.java	(revision 148)
+++ src/org/openstreetmap/josm/Main.java	(revision 149)
@@ -57,5 +57,7 @@
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener;
-import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginException;
+import org.openstreetmap.josm.plugins.PluginLoader;
+import org.openstreetmap.josm.plugins.PluginProxy;
 import org.openstreetmap.josm.tools.ImageProvider;
 
@@ -94,5 +96,5 @@
 	 * All installed and loaded plugins (resp. their main classes)
 	 */
-	public final Collection<Plugin> plugins = new LinkedList<Plugin>();
+	public final static Collection<PluginProxy> plugins = new LinkedList<PluginProxy>();
 
 	/**
@@ -127,6 +129,6 @@
 		redoUndoListener.commandChanged(0,0);
 
-		for (Plugin plugin : plugins)
-			plugin.mapFrameInitialized(old, map);
+		for (PluginProxy plugin : plugins)
+            plugin.mapFrameInitialized(old, map);
 	}
 
@@ -248,13 +250,19 @@
 		contentPane.updateUI();
 		
+
 		// Plugins
-		if (pref.hasKey("plugins")) {
-			for (String pluginName : pref.get("plugins").split(",")) {
+		if (Main.pref.hasKey("plugins")) {
+			PluginLoader loader = new PluginLoader();
+			for (String pluginName : Main.pref.get("plugins").split(",")) {
 				try {
-	                plugins.add((Plugin)Class.forName(pluginName).newInstance());
-                } catch (Exception e) {
-                	e.printStackTrace();
-                	JOptionPane.showMessageDialog(parent, tr("Could not load plugin {0}.", pluginName));
-                }
+					File pluginFile = new File(pref.getPreferencesDir()+"plugins/"+pluginName+".jar");
+					if (pluginFile.exists())
+						plugins.add(loader.loadPlugin(loader.loadClassName(pluginFile), pluginFile));
+					else
+						JOptionPane.showMessageDialog(Main.parent, tr("Plugin not found: {0}.", pluginName));
+				} catch (PluginException e) {
+					e.printStackTrace();
+					JOptionPane.showMessageDialog(Main.parent, tr("Could not load plugin {0}.", pluginName));
+				}
 			}
 		}
Index: src/org/openstreetmap/josm/data/Preferences.java
===================================================================
--- src/org/openstreetmap/josm/data/Preferences.java	(revision 148)
+++ src/org/openstreetmap/josm/data/Preferences.java	(revision 149)
@@ -47,13 +47,13 @@
 	}
 
-	synchronized final public boolean hasKey(final String key) {
+	synchronized public boolean hasKey(final String key) {
 		return properties.containsKey(key);
 	}
-	synchronized final public String get(final String key) {
+	synchronized public String get(final String key) {
 		if (!properties.containsKey(key))
 			return "";
 		return properties.get(key);
 	}
-	synchronized final public String get(final String key, final String def) {
+	synchronized public String get(final String key, final String def) {
 		final String prop = properties.get(key);
 		if (prop == null || prop.equals(""))
@@ -61,5 +61,5 @@
 		return prop;
 	}
-	synchronized final public Map<String, String> getAllPrefix(final String prefix) {
+	synchronized public Map<String, String> getAllPrefix(final String prefix) {
 		final Map<String,String> all = new TreeMap<String,String>();
 		for (final Entry<String,String> e : properties.entrySet())
@@ -68,13 +68,13 @@
 		return all;
 	}
-	synchronized final public boolean getBoolean(final String key) {
+	synchronized public boolean getBoolean(final String key) {
 		return getBoolean(key, false);
 	}
-	synchronized final public boolean getBoolean(final String key, final boolean def) {
+	synchronized public boolean getBoolean(final String key, final boolean def) {
 		return properties.containsKey(key) ? Boolean.parseBoolean(properties.get(key)) : def;
 	}
 
 
-	synchronized final public void put(final String key, final String value) {
+	synchronized public void put(final String key, final String value) {
 		if (value == null)
 			properties.remove(key);
@@ -84,5 +84,5 @@
 		firePreferenceChanged(key, value);
 	}
-	synchronized final public void put(final String key, final boolean value) {
+	synchronized public void put(final String key, final boolean value) {
 		properties.put(key, Boolean.toString(value));
 		save();
Index: src/org/openstreetmap/josm/gui/PreferenceDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/PreferenceDialog.java	(revision 148)
+++ src/org/openstreetmap/josm/gui/PreferenceDialog.java	(revision 149)
@@ -10,4 +10,8 @@
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
@@ -44,7 +48,9 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.plugins.PluginProxy;
 import org.openstreetmap.josm.tools.ColorHelper;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.UrlLabel;
 
 /**
@@ -190,4 +196,5 @@
 	private JComboBox projectionCombo = new JComboBox(Projection.allProjections);
 	private JList annotationSources = new JList(new DefaultListModel());
+	private Map<PluginProxy, Boolean> pluginMap = new HashMap<PluginProxy, Boolean>();
 
 
@@ -268,5 +275,28 @@
 			((DefaultListModel)annotationSources.getModel()).addElement(st.nextToken());
 
-
+		Box pluginPanel = Box.createVerticalBox();
+		Collection<String> availablePlugins = new HashSet<String>();
+		for (File f : new File(Main.pref.getPreferencesDir()+"plugins").listFiles()) {
+			if (!f.isFile() || !f.getName().endsWith(".jar"))
+				continue;
+			availablePlugins.add(f.getName().substring(0, f.getName().length()-4));
+		}
+		for (PluginProxy plugin : Main.plugins) {
+			boolean available = availablePlugins.contains(plugin.name);
+			JCheckBox pluginCheck = new JCheckBox(plugin.name, available);
+			String desc = plugin.getDescription();
+			pluginPanel.add(pluginCheck);
+			if (desc != null) {
+				pluginCheck.setToolTipText(desc);
+				JLabel label = new JLabel("<html><i>"+desc+"</i></html>");
+				label.setBorder(BorderFactory.createEmptyBorder(0,20,0,0));
+				pluginPanel.add(label);
+				pluginPanel.add(Box.createVerticalStrut(5));
+			}
+			pluginMap.put(plugin, available);
+		}
+		JScrollPane pluginPane = new JScrollPane(pluginPanel);
+		pluginPane.setBorder(null);
+		
 		Map<String,String> allColors = new TreeMap<String, String>(Main.pref.getAllPrefix("color."));
 
@@ -436,4 +466,10 @@
 		map.add(Box.createVerticalGlue(), GBC.eol().fill(GBC.VERTICAL));
 
+		
+		// Plugin tab
+		JPanel plugin = createPreferenceTab("plugin", tr("Plugins"), tr("Configure available Plugins."));
+		plugin.add(pluginPane, GBC.eol().fill(GBC.BOTH));
+		plugin.add(GBC.glue(0,10), GBC.eol());
+		plugin.add(new UrlLabel("http://josm.eigenheimstrasse.de/wiki/Plugins", "Get more plugins"), GBC.std());
 
 		tabPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
@@ -467,4 +503,5 @@
 	private JPanel createPreferenceTab(String icon, String title, String desc) {
 		JPanel p = new JPanel(new GridBagLayout());
+		p.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
 		p.add(new JLabel(title), GBC.eol().anchor(GBC.CENTER).insets(0,5,0,10));
 
Index: src/org/openstreetmap/josm/plugins/Plugin.java
===================================================================
--- src/org/openstreetmap/josm/plugins/Plugin.java	(revision 148)
+++ src/org/openstreetmap/josm/plugins/Plugin.java	(revision 149)
@@ -1,3 +1,6 @@
 package org.openstreetmap.josm.plugins;
+
+import java.net.URL;
+import java.net.URLClassLoader;
 
 import org.openstreetmap.josm.Main;
@@ -5,28 +8,64 @@
 
 /**
- * All plugins must have at least one class implementing this interface. 
+ * All plugins *must* have an standard constructor taking no arguments.
+ * This constructor is called at JOSM startup, after all Main-objects have been initialized.
+ * For all purposes of loading dynamic resources, the Plugin's class loader should be used
+ * (or else, the plugin jar will not be within the class path).
+ *
+ * All plugins should have at least one class subclassing this abstract base class. 
  * 
- * All plugins must have an default constructor (taking no arguments). This constructor
- * is called at JOSM startup, after all Main-objects have been initialized.
+ * The actual implementation of this interface is optional, as all functions will be called
+ * via reflection. This is to be able to change this interface without the need of recompiling
+ * or even breaking the plugins. If your class does not provide a function here (or does
+ * provide a function with a mismatching signature), it will not be called. That simple.
  *
- * The pluginname is also the name of the directory to store the plugin's
- * own stuff (located under the josm preferences directory)
+ * Or in other words: See this base class as an documentation of what functions are provided.
+ * Subclassing it and overriding some functions makes it easy for you to keep sync with the
+ * correct actual plugin architecture of JOSM.
+ *
+ *
+ * The pluginname provided to the constructor is also the name of the directory to 
+ * store the plugin's own stuff (located under the josm preferences directory)
+ *
  * @author Immanuel.Scholz
  */
 public abstract class Plugin {
 
-	private final String name;
+	private String name;
 
-	public Plugin(String pluginName) {
-		this.name = pluginName;
+	public Plugin() {
+		URL[] urls = ((URLClassLoader)getClass().getClassLoader()).getURLs();
+		String s = urls[urls.length-1].toString();
+		int lastSlash = s.lastIndexOf('/');
+		name = s.substring(lastSlash+1, s.length()-4);
+    }
+	
+	/**
+	 * @return The name of this plugin. This is the name of the .jar file.
+	 */
+	public final String getName() {
+		return name;
 	}
-
+	/**
+	 * @return The directory for the plugin to store all kind of stuff.
+	 */
 	public final String getPluginDir() {
 		return Main.pref.getPreferencesDir()+"plugins/"+name+"/";
 	}
 
+	
+
 	/**
 	 * Called after Main.mapFrame is initalized. (After the first data is loaded).
+	 * You can use this callback to tweak the newFrame to your needs, as example install
+	 * an alternative Painter.
 	 */
 	public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {}
+
+	/**
+	 * Called to retrieve a one-liner description of what this plugin does for tooltips.
+	 * @return <code>null</code>, which means: "no description available".
+	 */
+	public String getDescription() {return null;}
+
 }
Index: src/org/openstreetmap/josm/plugins/PluginException.java
===================================================================
--- src/org/openstreetmap/josm/plugins/PluginException.java	(revision 149)
+++ src/org/openstreetmap/josm/plugins/PluginException.java	(revision 149)
@@ -0,0 +1,28 @@
+package org.openstreetmap.josm.plugins;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * Exception that wraps any exception thrown by plugins. It is used in the JOSM main system
+ * and there is no particular reason to use this within the plugin itself (although there
+ * is also no reason against this.. ;)
+ * 
+ * @author Immanuel.Scholz
+ */
+public class PluginException extends RuntimeException {
+	private final PluginProxy plugin;
+	private final String name;
+
+	public PluginException(PluginProxy plugin, String name, Throwable cause) {
+	    super(tr("An error occoured in plugin {0}", name), cause);
+		this.plugin = plugin;
+		this.name = name;
+    }
+	
+	public PluginProxy getPlugin() {
+		return plugin;
+	}
+	public String getName() {
+		return name;
+	}
+}
Index: src/org/openstreetmap/josm/plugins/PluginLoader.java
===================================================================
--- src/org/openstreetmap/josm/plugins/PluginLoader.java	(revision 149)
+++ src/org/openstreetmap/josm/plugins/PluginLoader.java	(revision 149)
@@ -0,0 +1,53 @@
+package org.openstreetmap.josm.plugins;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+/**
+ * Helper class to detect and load plugins.
+ * 
+ * @author Immanuel.Scholz
+ */
+public class PluginLoader {
+
+	/**
+	 * @return The class name from the manifest of the jar given as parameter.
+	 */
+	public String loadClassName(File pluginFile) throws PluginException {
+	    try {
+	        JarInputStream jar = new JarInputStream(new FileInputStream(pluginFile));
+	        Manifest manifest = jar.getManifest();
+	        String pluginClass = manifest.getMainAttributes().getValue("Plugin-Class");
+	        jar.close();
+	        return pluginClass;
+        } catch (IOException e) {
+        	String name = pluginFile.getName();
+        	if (name.endsWith(".jar"))
+        		name = name.substring(0, name.length()-4);
+        	throw new PluginException(null, name, e);
+        }
+    }
+
+	/**
+	 * Load and instantiate the plugin
+	 */
+	public PluginProxy loadPlugin(String pluginClass, File pluginFile) throws PluginException {
+		String name = pluginFile.getName();
+		if (name.endsWith(".jar"))
+			name = name.substring(0, name.length()-4);
+		try {
+			ClassLoader loader = URLClassLoader.newInstance(
+					new URL[]{new URL("file:/"+pluginFile.getAbsolutePath())},
+					getClass().getClassLoader());
+			Object plugin = Class.forName(pluginClass, true, loader).newInstance();
+			return new PluginProxy(plugin, name);
+        } catch (Exception e) {
+        	throw new PluginException(null, name, e);
+        }
+	}
+}
Index: src/org/openstreetmap/josm/plugins/PluginProxy.java
===================================================================
--- src/org/openstreetmap/josm/plugins/PluginProxy.java	(revision 149)
+++ src/org/openstreetmap/josm/plugins/PluginProxy.java	(revision 149)
@@ -0,0 +1,43 @@
+package org.openstreetmap.josm.plugins;
+
+import org.openstreetmap.josm.gui.MapFrame;
+
+
+/**
+ * Helper class for the JOSM system to communicate with the plugin.
+ * 
+ * This class should be of no interest for sole plugin writer.
+ * 
+ * @author Immanuel.Scholz
+ */
+public class PluginProxy extends Plugin {
+
+	public final Object plugin;
+	public final String name;
+	public boolean misbehaving = false;
+
+	public PluginProxy(Object plugin, String name) {
+		this.plugin = plugin;
+		this.name = name;
+    }
+
+
+	
+	@Override public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+		try {
+	        plugin.getClass().getMethod("mapFrameInitialized", MapFrame.class, MapFrame.class).invoke(plugin, oldFrame, newFrame);
+        } catch (Exception e) {
+        	throw new PluginException(this, name, e);
+        }
+    }
+
+	@Override public String getDescription() {
+	    try {
+	        return (String)plugin.getClass().getMethod("getDescription").invoke(plugin);
+        } catch (NoSuchMethodException e) {
+        	return super.getDescription();
+        } catch (Exception e) {
+        	throw new PluginException(this, name, e);
+        }
+    }
+}
Index: src/org/openstreetmap/josm/tools/BugReportExceptionHandler.java
===================================================================
--- src/org/openstreetmap/josm/tools/BugReportExceptionHandler.java	(revision 148)
+++ src/org/openstreetmap/josm/tools/BugReportExceptionHandler.java	(revision 149)
@@ -21,4 +21,6 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.PluginException;
+import org.openstreetmap.josm.plugins.PluginProxy;
 
 /**
@@ -35,4 +37,13 @@
 				JOptionPane.showMessageDialog(Main.parent, "You are out of memory. Strange things may happen.\nPlease restart JOSM and load smaller data sets.");
 				return;
+			}
+			
+			if (e instanceof PluginException) {
+				PluginProxy plugin = ((PluginException)e).getPlugin();
+				if (plugin != null && !plugin.misbehaving) {
+					JOptionPane.showMessageDialog(Main.parent, tr("The plugin {0} throwed an exception: {1}\nIt may be outdated. Please contact the plugin's autor.\nThis message will not shown again until JOSM is restarted.", plugin.name, e.getMessage()));
+					plugin.misbehaving = true;
+					return;
+				}
 			}
 			
Index: src/org/openstreetmap/josm/tools/OpenBrowser.java
===================================================================
--- src/org/openstreetmap/josm/tools/OpenBrowser.java	(revision 148)
+++ src/org/openstreetmap/josm/tools/OpenBrowser.java	(revision 149)
@@ -14,10 +14,15 @@
 	public static String displayUrl(String url) {
 		String os = System.getProperty("os.name");
+		if (os == null)
+			return "unknown operating system";
 		try {
 			if (os != null && os.startsWith("Windows"))
-				Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
-			else {
-				//...
-			}
+				windows(url);
+			else if (os.equals("Linux") || os.equals("Solaris") || os.equals("SunOS") || os.equals("AIX") || os.equals("FreeBSD"))
+				linux(url);
+			else if (os.equals("Mac OS") || os.equals("Mac OS X"))
+				mac(url);
+			else
+				return "unknown operating system";
 		} catch (IOException e) {
 			return e.getMessage();
@@ -25,3 +30,19 @@
 		return null;
 	}
+
+	private static void windows(String url) throws IOException {
+		Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
+	}
+
+	private static void linux(String url) throws IOException {
+		try {
+	        Runtime.getRuntime().exec("gnome-open " + url);
+        } catch (IOException e) {
+        	Runtime.getRuntime().exec("kfmclient openURL " + url);
+        }
+	}
+
+	private static void mac(String url) throws IOException {
+		Runtime.getRuntime().exec("open " + url);
+	}
 }
Index: src/org/openstreetmap/josm/tools/UrlLabel.java
===================================================================
--- src/org/openstreetmap/josm/tools/UrlLabel.java	(revision 148)
+++ src/org/openstreetmap/josm/tools/UrlLabel.java	(revision 149)
@@ -14,7 +14,11 @@
 
 	public UrlLabel(String url) {
+		this (url, url);
+	}
+
+	public UrlLabel(String url, String description) {
 		this.url = url;
 		setContentType("text/html");
-		setText("<html><a href=\""+url+"\">"+url+"</a></html>");
+		setText("<html><a href=\""+url+"\">"+description+"</a></html>");
 		setEditable(false);
 		setOpaque(false);
