Index: /applications/editors/josm/plugins/geochat/src/geochat/ChatMessage.java
===================================================================
--- /applications/editors/josm/plugins/geochat/src/geochat/ChatMessage.java	(revision 29567)
+++ /applications/editors/josm/plugins/geochat/src/geochat/ChatMessage.java	(revision 29568)
@@ -17,6 +17,7 @@
     private long id;
     private boolean priv;
+    private boolean incoming;
 
-    public ChatMessage( long id, LatLon pos, String author, String message, Date time ) {
+    public ChatMessage( long id, LatLon pos, String author, boolean incoming, String message, Date time ) {
         this.id = id;
         this.author = author;
@@ -24,4 +25,5 @@
         this.pos = pos;
         this.time = time;
+        this.incoming = incoming;
         this.priv = false;
         this.recipient = null;
@@ -63,4 +65,8 @@
     }
 
+    public boolean isIncoming() {
+        return incoming;
+    }
+
     public Date getTime() {
         return time;
Index: /applications/editors/josm/plugins/geochat/src/geochat/ChatPaneManager.java
===================================================================
--- /applications/editors/josm/plugins/geochat/src/geochat/ChatPaneManager.java	(revision 29568)
+++ /applications/editors/josm/plugins/geochat/src/geochat/ChatPaneManager.java	(revision 29568)
@@ -0,0 +1,225 @@
+package geochat;
+
+import java.awt.Component;
+import java.awt.Font;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultCaret;
+import javax.swing.text.Document;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ *
+ * @author zverik
+ */
+class ChatPaneManager {
+    private static final String PUBLIC_PANE = "Public Pane";
+
+    private GeoChatPanel panel;
+    private JTabbedPane tabs;
+    private Map<String, ChatPane> chatPanes;
+    private boolean collapsed;
+
+    public ChatPaneManager( GeoChatPanel panel, JTabbedPane tabs ) {
+        this.panel = panel;
+        this.tabs = tabs;
+        this.collapsed = panel.isDialogInCollapsedView();
+        chatPanes = new HashMap<String, ChatPane>();
+        createChatPane(null);
+        tabs.addChangeListener(new ChangeListener() {
+            public void stateChanged( ChangeEvent e ) {
+                updateActiveTabStatus();
+            }
+        });
+    }
+
+    public void setCollapsed( boolean collapsed ) {
+        this.collapsed = collapsed;
+        updateActiveTabStatus();
+    }
+
+    public boolean hasUser( String user ) {
+        return chatPanes.containsKey(user == null ? PUBLIC_PANE : user);
+    }
+
+    public Component getPublicChatComponent() {
+        return chatPanes.get(PUBLIC_PANE).component;
+    }
+
+    public int getNotifyLevel() {
+        int alarm = 0;
+        for( ChatPane entry : chatPanes.values() ) {
+            if( entry.notify ) {
+                if( entry.isPublic && alarm < 1 )
+                    alarm = 1;
+                else if( !entry.isPublic )
+                    alarm = 2;
+            }
+        }
+        return alarm;
+    }
+
+    public void updateActiveTabStatus() {
+        if( tabs.getSelectedIndex() >= 0 )
+            ((ChatTabTitleComponent)tabs.getTabComponentAt(tabs.getSelectedIndex())).updateAlarm();
+    }
+
+    public void notify( String user, boolean really ) {
+//        if( user == null && !really && !collapsed )
+//            return;
+        if( !hasUser(user) )
+            return;
+        ChatPane entry = chatPanes.get(user == null ? PUBLIC_PANE : user);
+        System.out.println("Notifying " + user);
+        entry.notify = true;
+        int idx = tabs.indexOfComponent(entry.component);
+        if( idx >= 0 )
+            ((ChatTabTitleComponent)tabs.getTabComponentAt(idx)).updateAlarm();
+    }
+
+    public void addLineToChatPane( String userName, String line ) {
+        if( !chatPanes.containsKey(userName) )
+            createChatPane(userName);
+        if( !line.startsWith("\n") )
+            line = "\n" + line;
+        Document doc = chatPanes.get(userName).pane.getDocument();
+        try {
+            doc.insertString(doc.getLength(), line, null);
+        } catch( BadLocationException ex ) {
+            // whatever
+        }
+    }
+
+    public void addLineToPublic( String line ) {
+        addLineToChatPane(PUBLIC_PANE, line);
+    }
+
+    public void clearPublicChatPane() {
+        chatPanes.get(PUBLIC_PANE).pane.setText("");
+        showNearbyUsers();
+    }
+
+    private void showNearbyUsers() {
+        if( !panel.users.isEmpty() ) {
+            StringBuilder sb = new StringBuilder(tr("Users mapping nearby:"));
+            boolean first = true;
+            for( String user : panel.users.keySet() ) {
+                sb.append(first ? " " : ", ");
+                sb.append(user);
+            }
+            addLineToPublic(sb.toString());
+        }
+    }
+
+    public void clearChatPane( String userName) {
+        if( userName == null || userName.equals(PUBLIC_PANE) )
+            clearPublicChatPane();
+        else
+            chatPanes.get(userName).pane.setText("");
+    }
+
+    public void clearActiveChatPane() {
+        clearChatPane(getActiveChatPane());
+    }
+
+    public ChatPane createChatPane( String userName ) {
+        JTextPane chatPane = new JTextPane();
+        chatPane.setEditable(false);
+        Font font = chatPane.getFont();
+        chatPane.setFont(font.deriveFont(font.getSize2D() - 2));
+        DefaultCaret caret = (DefaultCaret)chatPane.getCaret();
+        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
+        JScrollPane scrollPane = new JScrollPane(chatPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+        chatPane.addMouseListener(new GeoChatPopupAdapter(panel));
+
+        ChatPane entry = new ChatPane();
+        entry.pane = chatPane;
+        entry.component = scrollPane;
+        entry.notify = false;
+        entry.userName = userName;
+        entry.isPublic = userName == null;
+        chatPanes.put(userName == null ? PUBLIC_PANE : userName, entry);
+
+        tabs.addTab(null, scrollPane);
+        tabs.setTabComponentAt(tabs.getTabCount() - 1, new ChatTabTitleComponent(entry));
+        tabs.setSelectedComponent(scrollPane);
+        return entry;
+    }
+
+    /**
+     * Returns key in chatPanes hash map for the currently active
+     * chat pane, or null in case of an error.
+     */
+    public String getActiveChatPane() {
+        Component c = tabs.getSelectedComponent();
+        if( c == null )
+            return null;
+        for( String user : chatPanes.keySet() )
+            if( c.equals(chatPanes.get(user).component) )
+                return user;
+        return null;
+    }
+
+    public String getRecipient() {
+        String user = getActiveChatPane();
+        return user == null || user.equals(PUBLIC_PANE) ? null : user;
+    }
+
+    public void closeChatPane( String user ) {
+        if( user == null || user.equals(PUBLIC_PANE) || !chatPanes.containsKey(user) )
+            return;
+        tabs.remove(chatPanes.get(user).component);
+        chatPanes.remove(user);
+    }
+
+    public void closeSelectedPrivatePane() {
+        String pane = getRecipient();
+        if( pane != null )
+            closeChatPane(pane);
+    }
+
+    public void closePrivateChatPanes() {
+        List<String> entries = new ArrayList<String>(chatPanes.keySet());
+        for( String user : entries )
+            if( !user.equals(PUBLIC_PANE) )
+                closeChatPane(user);
+    }
+    
+
+    private class ChatTabTitleComponent extends JLabel {
+        private ChatPane entry;
+
+        public ChatTabTitleComponent( ChatPane entry ) {
+            super(entry.isPublic ? tr("Public") : entry.userName);
+            this.entry = entry;
+        }
+
+        private Font normalFont;
+        private Font boldFont;
+
+        public void updateAlarm() {
+            if( normalFont == null ) {
+                // prepare cached fonts
+                normalFont = getFont().deriveFont(Font.PLAIN);
+                boldFont = getFont().deriveFont(Font.BOLD);
+            }
+            System.out.println("clauses: collapsed=" + collapsed + ", tabs:" + tabs.getSelectedIndex() + "=" + tabs.indexOfComponent(entry.component));
+            if( entry.notify && !collapsed && tabs.getSelectedIndex() == tabs.indexOfComponent(entry.component) )
+                entry.notify = false;
+            setFont(entry.notify ? boldFont : normalFont);
+            panel.updateTitleAlarm();
+        }
+    }
+
+    class ChatPane {
+        public String userName;
+        public boolean isPublic;
+        public JTextPane pane;
+        public JScrollPane component;
+        public boolean notify;
+
+    }
+}
Index: /applications/editors/josm/plugins/geochat/src/geochat/ChatServerConnection.java
===================================================================
--- /applications/editors/josm/plugins/geochat/src/geochat/ChatServerConnection.java	(revision 29567)
+++ /applications/editors/josm/plugins/geochat/src/geochat/ChatServerConnection.java	(revision 29568)
@@ -337,7 +337,9 @@
                     String author = msg.getString("author");
                     String message = msg.getString("message");
-                    ChatMessage cm = new ChatMessage(id, new LatLon(lat, lon), author, message, new Date(timeStamp * 1000));
+                    boolean incoming = msg.getBoolean("incoming");
+                    ChatMessage cm = new ChatMessage(id, new LatLon(lat, lon), author,
+                            incoming, message, new Date(timeStamp * 1000));
                     cm.setPrivate(priv);
-                    if( msg.has("recipient") && !msg.getBoolean("incoming") )
+                    if( msg.has("recipient") && !incoming )
                         cm.setRecipient(msg.getString("recipient"));
                     result.add(cm);
Index: /applications/editors/josm/plugins/geochat/src/geochat/GeoChatPanel.java
===================================================================
--- /applications/editors/josm/plugins/geochat/src/geochat/GeoChatPanel.java	(revision 29567)
+++ /applications/editors/josm/plugins/geochat/src/geochat/GeoChatPanel.java	(revision 29568)
@@ -8,7 +8,4 @@
 import java.util.List;
 import javax.swing.*;
-import javax.swing.text.BadLocationException;
-import javax.swing.text.DefaultCaret;
-import javax.swing.text.Document;
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
@@ -21,13 +18,11 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 import static org.openstreetmap.josm.tools.I18n.trn;
-import org.openstreetmap.josm.tools.ImageProvider;
 
 /**
+ * Chat Panel. Contains of one public chat pane and multiple private ones.
  *
  * @author zverik
  */
 public class GeoChatPanel extends ToggleDialog implements ChatServerConnectionListener, MapViewPaintable {
-    private static final String PUBLIC_PANE = "Public Pane";
-
     private JTextField input;
     private JTabbedPane tabs;
@@ -36,6 +31,8 @@
     private JPanel gcPanel;
     private ChatServerConnection connection;
-    private Map<String, LatLon> users;
-    private Map<String, ChatLogEntry> chatPanes;
+    // those fields should be visible to popup menu actions
+    protected Map<String, LatLon> users;
+    protected ChatPaneManager chatPanes;
+    protected boolean userLayerActive;
     
     public GeoChatPanel() {
@@ -44,22 +41,34 @@
         noData = new JLabel(tr("Zoom in to see messages"), SwingConstants.CENTER);
 
-        chatPanes = new HashMap<String, ChatLogEntry>();
         tabs = new JTabbedPane();
-        createChatPane(null);
-
-        tabs.addMouseListener(new PopupAdapter());
+        tabs.addMouseListener(new GeoChatPopupAdapter(this));
+        chatPanes = new ChatPaneManager(this, tabs);
 
         input = new JPanelTextField() {
             @Override
             protected void processEnter( String text ) {
-                connection.postMessage(text, getRecipient());
+                connection.postMessage(text, chatPanes.getRecipient());
             }
 
             @Override
             protected String autoComplete( String word ) {
-                return word;
+                return autoCompleteUser(word);
             }
         };
 
+        loginPanel = createLoginPanel();
+
+        gcPanel = new JPanel(new BorderLayout());
+        gcPanel.add(loginPanel, BorderLayout.CENTER);
+        createLayout(gcPanel, false, null);
+
+        users = new TreeMap<String, LatLon>();
+        // Start threads
+        connection = ChatServerConnection.getInstance();
+        connection.addListener(this);
+        connection.checkLogin();
+    }
+
+    private JPanel createLoginPanel() {
         final JTextField nameField = new JPanelTextField() {
             @Override
@@ -73,4 +82,5 @@
         if( userName.contains("@") )
             userName = userName.substring(0, userName.indexOf('@'));
+        userName = userName.replace(' ', '_');
         nameField.setText(userName);
 
@@ -83,125 +93,22 @@
         nameField.setPreferredSize(new Dimension(nameField.getPreferredSize().width, loginButton.getPreferredSize().height));
 
-        loginPanel = new JPanel(new GridBagLayout());
-        loginPanel.add(nameField, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(15, 0, 5, 0));
-        loginPanel.add(loginButton, GBC.std().fill(GridBagConstraints.NONE).insets(0, 0, 15, 0));
-
-        gcPanel = new JPanel(new BorderLayout());
-        gcPanel.add(loginPanel, BorderLayout.CENTER);
-        createLayout(gcPanel, false, null);
-
-        users = new TreeMap<String, LatLon>();
-        // Start threads
-        connection = ChatServerConnection.getInstance();
-        connection.addListener(this);
-        connection.checkLogin();
-    }
-
-    private void addLineToChatPane( String userName, String line ) {
-        if( !chatPanes.containsKey(userName) )
-            createChatPane(userName);
-        if( !line.startsWith("\n") )
-            line = "\n" + line;
-        Document doc = chatPanes.get(userName).pane.getDocument();
-        try {
-            doc.insertString(doc.getLength(), line, null);
-        } catch( BadLocationException ex ) {
-            // whatever
-        }
-    }
-
-    private void addLineToPublic( String line ) {
-        addLineToChatPane(PUBLIC_PANE, line);
-    }
-
-    private void clearPublicChatPane() {
-        chatPanes.get(PUBLIC_PANE).pane.setText("");
-        showNearbyUsers();
-    }
-
-    private void clearChatPane( String userName) {
-        if( userName == null || userName.equals(PUBLIC_PANE) )
-            clearPublicChatPane();
-        else
-            chatPanes.get(userName).pane.setText("");
-    }
-
-    private ChatLogEntry createChatPane( String userName ) {
-        JTextPane chatPane = new JTextPane();
-        chatPane.setEditable(false);
-        Font font = chatPane.getFont();
-        chatPane.setFont(font.deriveFont(font.getSize2D() - 2));
-        DefaultCaret caret = (DefaultCaret)chatPane.getCaret();
-        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
-        JScrollPane scrollPane = new JScrollPane(chatPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
-        chatPane.addMouseListener(new PopupAdapter());
-
-        ChatLogEntry entry = new ChatLogEntry();
-        entry.pane = chatPane;
-        entry.component = scrollPane;
-        entry.notify = false;
-        entry.userName = userName;
-        entry.isPublic = userName == null;
-        chatPanes.put(userName == null ? PUBLIC_PANE : userName, entry);
-
-        tabs.addTab(userName == null ? tr("Public") : userName, scrollPane);
-        tabs.setSelectedComponent(scrollPane);
-        return entry;
+        JPanel panel = new JPanel(new GridBagLayout());
+        panel.add(nameField, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(15, 0, 5, 0));
+        panel.add(loginButton, GBC.std().fill(GridBagConstraints.NONE).insets(0, 0, 15, 0));
+        return panel;
+    }
+
+    protected void logout() {
+        connection.logout();
+    }
+
+    private String autoCompleteUser( String word ) {
+        return word; // todo: write autocomplete
     }
 
     /**
-     * Returns key in chatPanes hash map for the currently active
-     * chat pane, or null in case of an error.
+     * This is implementation of a "temporary layer". It paints circles
+     * for all users nearby.
      */
-    private String getActiveChatPane() {
-        Component c = tabs.getSelectedComponent();
-        if( c == null )
-            return null;
-        for( String user : chatPanes.keySet() )
-            if( c.equals(chatPanes.get(user).component) )
-                return user;
-        return null;
-    }
-
-    private String getRecipient() {
-        String user = getActiveChatPane();
-        return user == null || user.equals(PUBLIC_PANE) ? null : user;
-    }
-
-    private void closeChatPane( String user ) {
-        if( user == null || user.equals(PUBLIC_PANE) || !chatPanes.containsKey(user) )
-            return;
-        tabs.remove(chatPanes.get(user).component);
-        chatPanes.remove(user);
-    }
-
-    private void closePrivateChatPanes() {
-        List<String> entries = new ArrayList<String>(chatPanes.keySet());
-        for( String user : entries )
-            if( !user.equals(PUBLIC_PANE) )
-                closeChatPane(user);
-    }
-
-    private String cachedTitle = "";
-    private int cachedAlarm = 0;
-
-    @Override
-    public void setTitle( String title ) {
-        setTitle(title, -1);
-    }
-
-    private void setTitleAlarm( int alarmLevel ) {
-        setTitle(null, alarmLevel);
-    }
-
-    private void setTitle( String title, int alarmLevel ) {
-        if( title != null )
-            cachedTitle = title;
-        if( alarmLevel >= 0 )
-            cachedAlarm = alarmLevel;
-        String alarm = cachedAlarm <= 0 ? "" : cachedAlarm == 1 ? "* " : "[!] ";
-        super.setTitle(alarm + cachedTitle);
-    }
-
     public void paint( Graphics2D g, MapView mv, Bounds bbox ) {
         Graphics2D g2d = (Graphics2D)g.create();
@@ -227,4 +134,43 @@
             g2d.drawString(user, p.x - Math.round(rect.getWidth() / 2), p.y);
         }
+    }
+
+    /* ================== Notifications in the title ======================= */
+
+    private String cachedTitle = null;
+    private int cachedAlarm = 0;
+
+    @Override
+    public void setTitle( String title ) {
+        setTitle(title, -1);
+    }
+
+    private void setTitle( String title, int alarmLevel ) {
+        boolean changed = false;
+        if( title != null && (cachedTitle == null || !cachedTitle.equals(title)) ) {
+            cachedTitle = title;
+            changed = true;
+        }
+        if( alarmLevel >= 0 && cachedAlarm != alarmLevel ) {
+            cachedAlarm = alarmLevel;
+            changed = true;
+        }
+        if( changed ) {
+            String alarm = cachedAlarm <= 0 ? "" : cachedAlarm == 1 ? "* " : "!!! ";
+            super.setTitle(alarm + cachedTitle);
+            // todo: title becomes cut off
+        }
+    }
+
+    protected void updateTitleAlarm() {
+        int notifyLevel = chatPanes.getNotifyLevel();
+        setTitle(null, isDialogInCollapsedView() ? notifyLevel : Math.min(1, notifyLevel));
+    }
+
+    @Override
+    protected void setIsCollapsed( boolean val ) {
+        super.setIsCollapsed(val);
+        chatPanes.setCollapsed(val);
+        updateTitleAlarm();
     }
 
@@ -265,5 +211,5 @@
     public void statusChanged( boolean active ) {
         // only the public tab, because private chats don't rely on coordinates
-        tabs.setComponentAt(0, active ? chatPanes.get(PUBLIC_PANE).component : noData);
+        tabs.setComponentAt(0, active ? chatPanes.getPublicChatComponent() : noData);
         repaint();
     }
@@ -272,25 +218,14 @@
         for( String uname : this.users.keySet() ) {
             if( !newUsers.containsKey(uname) )
-                addLineToPublic(tr("User {0} has left", uname));
+                chatPanes.addLineToPublic(tr("User {0} has left", uname));
         }
         for( String uname : newUsers.keySet() ) {
             if( !this.users.containsKey(uname) )
-                addLineToPublic(tr("User {0} is mapping nearby", uname));
+                chatPanes.addLineToPublic(tr("User {0} is mapping nearby", uname));
         }
         setTitle(trn("GeoChat ({0} user)", "GeoChat ({0} users)", newUsers.size(), newUsers.size()));
-        // todo: update users location
         this.users = newUsers;
-    }
-
-    private void showNearbyUsers() {
-        if( !users.isEmpty() ) {
-            StringBuilder sb = new StringBuilder(tr("Users mapping nearby:"));
-            boolean first = true;
-            for( String user : users.keySet() ) {
-                sb.append(first ? " " : ", ");
-                sb.append(user);
-            }
-            addLineToPublic(sb.toString());
-        }
+        if( userLayerActive && Main.map.mapView != null )
+            Main.map.mapView.repaint();
     }
 
@@ -305,10 +240,18 @@
     public void receivedMessages( boolean replace, List<ChatMessage> messages ) {
         if( replace )
-            clearPublicChatPane();
+            chatPanes.clearPublicChatPane();
         if( !messages.isEmpty() ) {
+            int alarm = 0;
             StringBuilder sb = new StringBuilder();
-            for( ChatMessage msg : messages )
+            for( ChatMessage msg : messages ) {
                 formatMessage(sb, msg);
-            addLineToPublic(sb.toString());
+                if( msg.isIncoming() ) {
+                    // todo: alarm=2 for private messages
+                    alarm = 1;
+                }
+            }
+            chatPanes.addLineToPublic(sb.toString());
+            if( alarm > 0 )
+                chatPanes.notify(null, alarm > 1);
         }
     }
@@ -316,151 +259,11 @@
     public void receivedPrivateMessages( boolean replace, List<ChatMessage> messages ) {
         if( replace )
-            closePrivateChatPanes();
+            chatPanes.closePrivateChatPanes();
         for( ChatMessage msg : messages ) {
             StringBuilder sb = new StringBuilder();
             formatMessage(sb, msg);
-            addLineToChatPane(msg.getRecipient() != null ? msg.getRecipient() : msg.getAuthor(), sb.toString());
-        }
-    }
-
-    /* =================== Service classes ==================== */
-
-    private class JPanelTextField extends JTextField {
-        @Override
-        protected void processKeyEvent( KeyEvent e ) {
-            if( e.getID() == KeyEvent.KEY_PRESSED ) {
-                int code = e.getKeyCode();
-                if( code == KeyEvent.VK_ENTER ) {
-                    String text = input.getText();
-                    if( text.length() > 0 ) {
-                        processEnter(text);
-                        input.setText("");
-                    }
-                } else if( code == KeyEvent.VK_TAB ) {
-                    autoComplete(""); // todo
-                } else if( code == KeyEvent.VK_ESCAPE ) {
-                    if( Main.map != null && Main.map.mapView != null )
-                        Main.map.mapView.requestFocus();
-                }
-                // Do not pass other events to JOSM
-                if( code != KeyEvent.VK_LEFT && code != KeyEvent.VK_HOME && code != KeyEvent.VK_RIGHT
-                        && code != KeyEvent.VK_END && code != KeyEvent.VK_BACK_SPACE && code != KeyEvent.VK_DELETE )
-                    e.consume();
-            }
-            super.processKeyEvent(e);
-        }
-
-        protected void processEnter( String text ) { }
-
-        protected String autoComplete( String word ) { return word; }
-    }
-
-    private class ChatLogEntry {
-        public String userName;
-        public boolean isPublic;
-        public JTextPane pane;
-        public JScrollPane component;
-        public boolean notify;
-    }
-
-    /* ================= Actions for popup menu ==================== */
-
-
-    private JPopupMenu createPopupMenu() {
-        JMenu userMenu = new JMenu(tr("Private chat"));
-        for( String user : users.keySet() ) {
-            if( !chatPanes.containsKey(user) )
-                userMenu.add(new PrivateChatAction(user));
-        }
-
-        JPopupMenu menu = new JPopupMenu();
-        menu.add(new JCheckBoxMenuItem(new ToggleUserLayerAction()));
-        if( userMenu.getItemCount() > 0 )
-            menu.add(userMenu);
-        if( getRecipient() != null )
-            menu.add(new CloseTabAction());
-//        menu.add(new ClearPaneAction());
-//        menu.add(new LogoutAction());
-        return menu;
-    }
-
-    private class PopupAdapter extends MouseAdapter {
-        @Override public void mousePressed( MouseEvent e ) { check(e); }
-        @Override public void mouseReleased( MouseEvent e ) { check(e); }
-
-        private void check( MouseEvent e ) {
-            if( e.isPopupTrigger() ) {
-                createPopupMenu().show(tabs, e.getX(), e.getY());
-            }
-        }
-    }
-
-    private class PrivateChatAction extends AbstractAction {
-        private String userName;
-
-        public PrivateChatAction( String userName ) {
-            super(userName);
-            this.userName = userName;
-        }
-
-        public void actionPerformed( ActionEvent e ) {
-            if( !chatPanes.containsKey(userName) ) {
-                ChatLogEntry entry = createChatPane(userName);
-            }
-        }
-    }
-
-    private class CloseTabAction extends AbstractAction {
-        public CloseTabAction() {
-            super(tr("Close tab"));
-//            putValue(SMALL_ICON, ImageProvider.get("help"));
-        }
-
-        public void actionPerformed( ActionEvent e ) {
-            String pane = getActiveChatPane();
-            if( pane != null && !pane.equals(PUBLIC_PANE) )
-                closeChatPane(pane);
-        }
-    }
-
-    private class LogoutAction extends AbstractAction {
-        public LogoutAction() {
-            super(tr("Logout"));
-//            putValue(SMALL_ICON, ImageProvider.get("help"));
-        }
-
-        public void actionPerformed( ActionEvent e ) {
-            connection.logout();
-        }
-    }
-
-    private class ClearPaneAction extends AbstractAction {
-        public ClearPaneAction() {
-            super(tr("Clear log"));
-//            putValue(SMALL_ICON, ImageProvider.get("help"));
-        }
-
-        public void actionPerformed( ActionEvent e ) {
-            clearChatPane(getActiveChatPane());
-        }
-    }
-
-    private class ToggleUserLayerAction extends AbstractAction {
-        public ToggleUserLayerAction() {
-            super(tr("Show users on map"));
-//            putValue(SMALL_ICON, ImageProvider.get("help"));
-        }
-
-        public void actionPerformed( ActionEvent e ) {
-            if( Main.map == null || Main.map.mapView == null )
-                return;
-            boolean wasAdded = Main.map.mapView.addTemporaryLayer(GeoChatPanel.this);
-            if( !wasAdded )
-                Main.map.mapView.removeTemporaryLayer(GeoChatPanel.this);
-            Main.map.mapView.repaint();
-            if( e.getSource() != null )
-                System.out.println("toggle source: " + e.getSource().getClass().getName());
-            if( e.getSource() instanceof JCheckBoxMenuItem )
-                ((JCheckBoxMenuItem)e.getSource()).setSelected(wasAdded);
+            chatPanes.addLineToChatPane(msg.isIncoming() ? msg.getAuthor() : msg.getRecipient(), sb.toString());
+            if( msg.isIncoming() )
+                chatPanes.notify(msg.getAuthor(), true);
         }
     }
Index: /applications/editors/josm/plugins/geochat/src/geochat/GeoChatPopupAdapter.java
===================================================================
--- /applications/editors/josm/plugins/geochat/src/geochat/GeoChatPopupAdapter.java	(revision 29568)
+++ /applications/editors/josm/plugins/geochat/src/geochat/GeoChatPopupAdapter.java	(revision 29568)
@@ -0,0 +1,120 @@
+package geochat;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import javax.swing.*;
+import org.openstreetmap.josm.Main;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ *
+ * @author zverik
+ */
+class GeoChatPopupAdapter extends MouseAdapter {
+    private GeoChatPanel panel;
+
+    public GeoChatPopupAdapter( GeoChatPanel panel ) {
+        this.panel = panel;
+    }
+
+    @Override
+    public void mousePressed( MouseEvent e ) {
+        check(e);
+    }
+
+    @Override
+    public void mouseReleased( MouseEvent e ) {
+        check(e);
+    }
+
+    private void check( MouseEvent e ) {
+        if( e.isPopupTrigger() ) {
+            createPopupMenu().show(e.getComponent(), e.getX(), e.getY());
+        }
+    }
+
+    private JPopupMenu createPopupMenu() {
+        JMenu userMenu = new JMenu(tr("Private chat"));
+        for( String user : panel.users.keySet() ) {
+            if( !panel.chatPanes.hasUser(user) )
+                userMenu.add(new PrivateChatAction(user));
+        }
+
+        JPopupMenu menu = new JPopupMenu();
+        menu.add(new JCheckBoxMenuItem(new ToggleUserLayerAction()));
+        if( userMenu.getItemCount() > 0 )
+            menu.add(userMenu);
+        if( panel.chatPanes.getRecipient() != null )
+            menu.add(new CloseTabAction());
+//        menu.add(new ClearPaneAction());
+//        menu.add(new LogoutAction());
+        return menu;
+    }
+
+    private class PrivateChatAction extends AbstractAction {
+        private String userName;
+
+        public PrivateChatAction( String userName ) {
+            super(userName);
+            this.userName = userName;
+        }
+
+        public void actionPerformed( ActionEvent e ) {
+            if( !panel.chatPanes.hasUser(userName) ) {
+                panel.chatPanes.createChatPane(userName);
+            }
+        }
+    }
+
+    private class CloseTabAction extends AbstractAction {
+        public CloseTabAction() {
+            super(tr("Close tab"));
+//            putValue(SMALL_ICON, ImageProvider.get("help"));
+        }
+
+        public void actionPerformed( ActionEvent e ) {
+            panel.chatPanes.closeSelectedPrivatePane();
+        }
+    }
+
+    private class LogoutAction extends AbstractAction {
+        public LogoutAction() {
+            super(tr("Logout"));
+//            putValue(SMALL_ICON, ImageProvider.get("help"));
+        }
+
+        public void actionPerformed( ActionEvent e ) {
+            panel.logout();
+        }
+    }
+
+    private class ClearPaneAction extends AbstractAction {
+        public ClearPaneAction() {
+            super(tr("Clear log"));
+//            putValue(SMALL_ICON, ImageProvider.get("help"));
+        }
+
+        public void actionPerformed( ActionEvent e ) {
+            panel.chatPanes.clearActiveChatPane();
+        }
+    }
+
+    private class ToggleUserLayerAction extends AbstractAction {
+        public ToggleUserLayerAction() {
+            super(tr("Show users on map"));
+//            putValue(SMALL_ICON, ImageProvider.get("help"));
+            putValue(SELECTED_KEY, Boolean.valueOf(panel.userLayerActive));
+        }
+
+        public void actionPerformed( ActionEvent e ) {
+            if( Main.map == null || Main.map.mapView == null )
+                return;
+            boolean wasAdded = Main.map.mapView.addTemporaryLayer(panel);
+            if( !wasAdded )
+                Main.map.mapView.removeTemporaryLayer(panel);
+            putValue(SELECTED_KEY, Boolean.valueOf(panel.userLayerActive));
+            Main.map.mapView.repaint();
+        }
+    }
+}
Index: /applications/editors/josm/plugins/geochat/src/geochat/JPanelTextField.java
===================================================================
--- /applications/editors/josm/plugins/geochat/src/geochat/JPanelTextField.java	(revision 29568)
+++ /applications/editors/josm/plugins/geochat/src/geochat/JPanelTextField.java	(revision 29568)
@@ -0,0 +1,57 @@
+package geochat;
+
+import java.awt.event.KeyEvent;
+import javax.swing.JTextField;
+import org.openstreetmap.josm.Main;
+
+/**
+ * JTextField tweaked to work in a JOSM panel. It prevents unwanted keystrokes
+ * to be caught by the editor.
+ * 
+ * @author zverik
+ */
+public class JPanelTextField extends JTextField {
+
+    @Override
+    protected void processKeyEvent( KeyEvent e ) {
+        if( e.getID() == KeyEvent.KEY_PRESSED ) {
+            int code = e.getKeyCode();
+            if( code == KeyEvent.VK_ENTER ) {
+                String text = getText();
+                if( text.length() > 0 ) {
+                    processEnter(text);
+                    setText("");
+                }
+            } else if( code == KeyEvent.VK_TAB ) {
+                String word = ""; // todo: get the word
+                String complete = word == null ? null : autoComplete(word);
+                if( complete != null && !complete.equals(word) ) {
+                    // todo: replace the word
+                }
+            } else if( code == KeyEvent.VK_ESCAPE ) {
+                if( Main.map != null && Main.map.mapView != null )
+                    Main.map.mapView.requestFocus();
+            }
+            // Do not pass other events to JOSM
+            if( code != KeyEvent.VK_LEFT && code != KeyEvent.VK_HOME && code != KeyEvent.VK_RIGHT && code != KeyEvent.VK_END && code != KeyEvent.VK_BACK_SPACE && code != KeyEvent.VK_DELETE )
+                e.consume();
+        }
+        super.processKeyEvent(e);
+    }
+
+    /**
+     * Process VK_ENTER. Override this to submit the text.
+     * 
+     * @param text Contents of the text field.
+     */
+    protected void processEnter( String text ) { }
+
+    /**
+     * Autocomplete the word.
+     * @param word Partly typed word.
+     * @return The whole word.
+     */
+    protected String autoComplete( String word ) {
+        return word;
+    }
+}
