Index: trunk/src/org/openstreetmap/josm/actions/CreateCircleAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/CreateCircleAction.java	(revision 6123)
+++ trunk/src/org/openstreetmap/josm/actions/CreateCircleAction.java	(revision 6124)
@@ -24,4 +24,5 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.gui.Notification;
 import org.openstreetmap.josm.tools.Shortcut;
 
@@ -263,10 +264,8 @@
 
         } else {
-            JOptionPane.showMessageDialog(
-                    Main.parent,
-                    tr("Please select exactly two or three nodes or one way with exactly two or three nodes."),
-                    tr("Information"),
-                    JOptionPane.INFORMATION_MESSAGE
-            );
+            Notification note = new Notification();
+            note.setContent(tr("Please select exactly two or three nodes or one way with exactly two or three nodes."));
+            note.setIcon(JOptionPane.INFORMATION_MESSAGE);
+            note.show();
             return;
         }
Index: trunk/src/org/openstreetmap/josm/gui/Notification.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/Notification.java	(revision 6124)
+++ trunk/src/org/openstreetmap/josm/gui/Notification.java	(revision 6124)
@@ -0,0 +1,138 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui;
+
+import java.awt.Component;
+
+import javax.swing.Icon;
+import javax.swing.JOptionPane;
+import javax.swing.UIManager;
+
+/**
+ * A Notification Message similar to a popup window, but without disrupting the
+ * user's workflow.
+ *
+ * Non-modal info panel that vanishes after a certain time.
+ *
+ * This class only holds the data for a notification, {@link NotificationManager}
+ * is responsible for building the message panel and displaying it on screen.
+ *
+ * example:
+ * <pre>
+ *      Notification note = new Notification("Hi there!");
+ *      note.setDuration(4000); // optional
+ *      note.setIcon(JOptionPane.INFORMATION_MESSAGE); // optional
+ *      note.show();
+ * </pre>
+ */
+public class Notification {
+
+    public final static int DEFAULT_CONTENT_WIDTH = 350;
+
+    private Icon icon;
+    private int duration;
+    private Component content;
+
+    public Notification() {
+        duration = NotificationManager.defaultNotificationTime;
+    }
+
+    public Notification(String msg) {
+        this();
+        setContent(msg);
+    }
+
+    /**
+     * Set the content of the message.
+     *
+     * @param content any Component to be shown
+     *
+     * @see #setContent(java.lang.String)
+     * @return the current Object, for convenience
+     */
+    public Notification setContent(Component content) {
+        this.content = content;
+        return this;
+    }
+
+    /**
+     * Set the notification text. (Convenience method)
+     *
+     * @param msg the message String
+     *
+     * @see #Notification(java.lang.String)
+     * @return the current Object, for convenience
+     */
+    public Notification setContent(String msg) {
+        JMultilineLabel lbl = new JMultilineLabel(msg);
+        lbl.setMaxWidth(DEFAULT_CONTENT_WIDTH);
+        content = lbl;
+        return this;
+    }
+
+    /**
+     * Set the time after which the message is hidden.
+     *
+     * @param duration the time (in milliseconds)
+     * @return the current Object, for convenience
+     */
+    public Notification setDuration(int duration) {
+        this.duration = duration;
+        return this;
+    }
+
+    /**
+     * Set an icon to display on the left part of the message window.
+     *
+     * @param icon the icon (null means no icon is displayed)
+     * @return the current Object, for convenience
+     */
+    public Notification setIcon(Icon icon) {
+        this.icon = icon;
+        return this;
+    }
+
+    /**
+     * Set an icon to display on the left part of the message window by
+     * choosing from the default JOptionPane icons.
+     *
+     * @param messageType one of the following: JOptionPane.ERROR_MESSAGE,
+     * JOptionPane.INFORMATION_MESSAGE, JOptionPane.WARNING_MESSAGE,
+     * JOptionPane.QUESTION_MESSAGE, JOptionPane.PLAIN_MESSAGE
+     * @return the current Object, for convenience
+     */
+    public Notification setIcon(int messageType) {
+        switch (messageType) {
+            case JOptionPane.ERROR_MESSAGE:
+                return setIcon(UIManager.getIcon("OptionPane.errorIcon"));
+            case JOptionPane.INFORMATION_MESSAGE:
+                return setIcon(UIManager.getIcon("OptionPane.informationIcon"));
+            case JOptionPane.WARNING_MESSAGE:
+                return setIcon(UIManager.getIcon("OptionPane.warningIcon"));
+            case JOptionPane.QUESTION_MESSAGE:
+                return setIcon(UIManager.getIcon("OptionPane.questionIcon"));
+            case JOptionPane.PLAIN_MESSAGE:
+                return setIcon(null);
+            default:
+                throw new IllegalArgumentException("Unknown message type!");
+        }
+    }
+
+    public Component getContent() {
+        return content;
+    }
+
+    public int getDuration() {
+        return duration;
+    }
+
+    public Icon getIcon() {
+        return icon;
+    }
+
+    /**
+     * Display the notification.
+     */
+    public void show() {
+        NotificationManager.getInstance().showNotification(this);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/NotificationManager.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/NotificationManager.java	(revision 6124)
+++ trunk/src/org/openstreetmap/josm/gui/NotificationManager.java	(revision 6124)
@@ -0,0 +1,323 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.geom.RoundRectangle2D;
+import java.util.LinkedList;
+import java.util.Queue;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.GroupLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JLayeredPane;
+import javax.swing.JPanel;
+import javax.swing.JToolBar;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Manages {@link Notification}s, i.e.&nbsp;displays them on screen.
+ *
+ * Don't use this class directly, but use {@link Notification#show()}.
+ *
+ * If multiple messages are sent in a short period of time, they are put in
+ * a queue and displayed on after the other.
+ *
+ * The user can stop the timer (freeze the message) by moving the mouse cursor
+ * above the panel. As a visual cue, the background color changes from
+ * semi-transparent to opaque while the timer is frozen.
+ */
+class NotificationManager {
+
+    private Timer hideTimer; // started when message is shown, responsible for hiding the message
+    private Timer pauseTimer; // makes sure the is a small pause between to consecutive messages
+    private Timer unfreezeDelayTimer; // tiny delay before resuming the timer when mouse cursor
+                                      // is moved off the panel
+    private boolean running;
+
+    private Notification currentNotification;
+    private NotificationPanel currentNotificationPanel;
+    private final Queue<Notification> queue;
+
+    private static int pauseTime = Main.pref.getInteger("notification-default-pause-time-ms", 300); // milliseconds
+    static int defaultNotificationTime = Main.pref.getInteger("notification-default-time-ms", 5000); // milliseconds
+
+    private long displayTimeStart;
+    private long elapsedTime;
+
+    private static NotificationManager INSTANCE = null;
+
+    private final Color PANEL_SEMITRANSPARENT = new Color(224, 236, 249, 230);
+    private final Color PANEL_OPAQUE = new Color(224, 236, 249);
+
+    public static NotificationManager getInstance() {
+        if (INSTANCE == null) {
+            INSTANCE = new NotificationManager();
+        }
+        return INSTANCE;
+    }
+
+    public NotificationManager() {
+        queue = new LinkedList<Notification>();
+        hideTimer = new Timer(defaultNotificationTime, new HideEvent());
+        hideTimer.setRepeats(false);
+        pauseTimer = new Timer(pauseTime, new PauseFinishedEvent());
+        pauseTimer.setRepeats(false);
+        unfreezeDelayTimer = new Timer(10, new UnfreezeEvent());
+        unfreezeDelayTimer.setRepeats(false);
+    }
+
+    public void showNotification(Notification note) {
+        synchronized (queue) {
+            queue.add(note);
+            processQueue();
+        }
+    }
+
+    private void processQueue() {
+        if (running) return;
+
+        currentNotification = queue.poll();
+        if (currentNotification == null) return;
+        
+        currentNotificationPanel = new NotificationPanel(currentNotification);
+        currentNotificationPanel.validate();
+
+        int MARGIN = 5;
+        int x, y;
+        JFrame parentWindow = (JFrame) Main.parent;
+        Dimension size = currentNotificationPanel.getPreferredSize();
+        if (Main.isDisplayingMapView()) {
+            MapView mv = Main.map.mapView;
+            Point mapViewPos = SwingUtilities.convertPoint(mv.getParent(), mv.getX(), mv.getY(), Main.parent);
+            x = mapViewPos.x + MARGIN;
+            y = mapViewPos.y + mv.getHeight() - size.height - MARGIN;
+        } else {
+            x = MARGIN;
+            y = parentWindow.getHeight() - size.height - MARGIN;
+        }
+        parentWindow.getLayeredPane().add(currentNotificationPanel, JLayeredPane.POPUP_LAYER, 0);
+
+        currentNotificationPanel.setLocation(x, y);
+        currentNotificationPanel.setSize(size);
+
+        currentNotificationPanel.setVisible(true);
+
+        running = true;
+        elapsedTime = 0;
+
+        startHideTimer();
+    }
+
+    private void startHideTimer() {
+        int remaining = (int) (currentNotification.getDuration() - elapsedTime);
+        if (remaining < 300) {
+            remaining = 300;
+        }
+        displayTimeStart = System.currentTimeMillis();
+        hideTimer.setInitialDelay(remaining);
+        hideTimer.restart();
+    }
+
+    private class HideEvent implements ActionListener {
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            hideTimer.stop();
+            currentNotificationPanel.setVisible(false);
+            ((JFrame) Main.parent).getLayeredPane().remove(currentNotificationPanel);
+            currentNotificationPanel = null;
+            pauseTimer.restart();
+        }
+    }
+
+    private class PauseFinishedEvent implements ActionListener {
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            synchronized (queue) {
+                running = false;
+                processQueue();
+            }
+        }
+    }
+
+    private class UnfreezeEvent implements ActionListener {
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            currentNotificationPanel.setNotificationBackground(PANEL_SEMITRANSPARENT);
+            currentNotificationPanel.repaint();
+            startHideTimer();
+        }
+    }
+
+    private class NotificationPanel extends JPanel {
+
+        private JPanel innerPanel;
+
+        public NotificationPanel(Notification note) {
+            setVisible(false);
+            build(note);
+        }
+
+        public void setNotificationBackground(Color c) {
+            innerPanel.setBackground(c);
+        }
+
+        private void build(Notification note) {
+            JButton close = new JButton(new HideAction());
+            close.setPreferredSize(new Dimension(50, 50));
+            JToolBar tbClose = new JToolBar();
+            close.setMargin(new Insets(0, 0, 1, 1));
+            close.setContentAreaFilled(false);
+
+            tbClose.setFloatable(false);
+            tbClose.setBorderPainted(false);
+            tbClose.setOpaque(false);
+            tbClose.add(close);
+
+            setOpaque(false);
+            innerPanel = new RoundedPanel();
+            innerPanel.setBackground(PANEL_SEMITRANSPARENT);
+            innerPanel.setForeground(Color.BLACK);
+
+            GroupLayout layout = new GroupLayout(innerPanel);
+            innerPanel.setLayout(layout);
+            layout.setAutoCreateGaps(true);
+            layout.setAutoCreateContainerGaps(true);
+
+            innerPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+            add(innerPanel);
+
+            JLabel icon = null;
+            if (note.getIcon() != null) {
+                icon = new JLabel(note.getIcon());
+            }
+            Component content = note.getContent();
+            GroupLayout.SequentialGroup hgroup = layout.createSequentialGroup();
+            if (icon != null) {
+                hgroup.addComponent(icon);
+            }
+            hgroup.addComponent(content).addComponent(tbClose);
+            GroupLayout.ParallelGroup vgroup = layout.createParallelGroup();
+            if (icon != null) {
+                vgroup.addComponent(icon);
+            }
+            vgroup.addComponent(content).addComponent(tbClose);
+            layout.setHorizontalGroup(hgroup);
+            layout.setVerticalGroup(vgroup);
+
+            /*
+             * The timer stops when the mouse cursor is above the panel.
+             *
+             * This is not straightforward, because the JPanel will get a
+             * mouseExited event when the cursor moves on top of the JButton
+             * inside the panel.
+             *
+             * The current hacky solution is to register the freeze MouseListener
+             * not only to the panel, but to all the components inside the panel.
+             *
+             * Moving the mouse cursor from one component to the next would
+             * cause some flickering (timer is started and stopped for a fraction
+             * of a second, background color is switched twice), so there is
+             * a tiny delay before the timer really resumes.
+             */
+            MouseListener freeze = new FreezeMouseListener();
+            addMouseListenerToAllChildComponents(this, freeze);
+        }
+
+        private void addMouseListenerToAllChildComponents(Component comp, MouseListener listener) {
+            comp.addMouseListener(listener);
+            if (comp instanceof Container) {
+                for (Component c: ((Container)comp).getComponents()) {
+                    addMouseListenerToAllChildComponents(c, listener);
+                }
+            }
+        }
+
+        class HideAction extends AbstractAction {
+
+            public HideAction() {
+                putValue(SMALL_ICON, ImageProvider.get("misc", "grey_x"));
+            }
+            
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                new HideEvent().actionPerformed(null);
+            }
+        }
+
+        class FreezeMouseListener extends MouseAdapter {
+            @Override
+            public void mouseEntered(MouseEvent e) {
+                if (unfreezeDelayTimer.isRunning()) {
+                    unfreezeDelayTimer.stop();
+                } else {
+                    hideTimer.stop();
+                    elapsedTime += System.currentTimeMillis() - displayTimeStart;
+                    currentNotificationPanel.setNotificationBackground(PANEL_OPAQUE);
+                    currentNotificationPanel.repaint();
+                }
+            }
+
+            @Override
+            public void mouseExited(MouseEvent e) {
+                unfreezeDelayTimer.restart();
+            }
+        }
+    }
+
+    /**
+     * A panel with rounded edges and line border.
+     */
+    public static class RoundedPanel extends JPanel {
+
+        public RoundedPanel() {
+            super();
+            setOpaque(false);
+        }
+
+        @Override
+        protected void paintComponent(Graphics graphics) {
+            Graphics2D g = (Graphics2D) graphics;
+            g.setRenderingHint(
+                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+            g.setColor(getBackground());
+            float lineWidth = 1.4f;
+            Shape rect = new RoundRectangle2D.Float(
+                    lineWidth/2 + getInsets().left,
+                    lineWidth/2 + getInsets().top,
+                    getWidth() - lineWidth/2 - getInsets().left - getInsets().right,
+                    getHeight() - lineWidth/2 - getInsets().top - getInsets().bottom,
+                    20, 20);
+
+            g.fill(rect);
+            g.setColor(getForeground());
+            g.setStroke(new BasicStroke(lineWidth));
+            g.draw(rect);
+            super.paintComponent(graphics);
+        }
+    }
+}
