Index: /applications/editors/josm/plugins/geochat/build.xml
===================================================================
--- /applications/editors/josm/plugins/geochat/build.xml	(revision 33601)
+++ /applications/editors/josm/plugins/geochat/build.xml	(revision 33602)
@@ -5,5 +5,5 @@
     <property name="commit.message" value="[josm_geochat] copypaste from keyboard, font size advanced parameters"/>
     <!-- enter the *lowest* JOSM version this plugin is currently compatible with -->
-    <property name="plugin.main.version" value="12643"/>
+    <property name="plugin.main.version" value="12743"/>
 
     <property name="plugin.author" value="Ilya Zverev"/>
Index: /applications/editors/josm/plugins/geochat/src/geochat/ChatPaneManager.java
===================================================================
--- /applications/editors/josm/plugins/geochat/src/geochat/ChatPaneManager.java	(revision 33601)
+++ /applications/editors/josm/plugins/geochat/src/geochat/ChatPaneManager.java	(revision 33602)
@@ -32,237 +32,237 @@
  */
 class ChatPaneManager {
-    private static final String PUBLIC_PANE = "Public Pane";
-
-    private GeoChatPanel panel;
-    private JTabbedPane tabs;
-    private Map<String, ChatPane> chatPanes;
-    private boolean collapsed;
-
-    ChatPaneManager(GeoChatPanel panel, JTabbedPane tabs) {
-        this.panel = panel;
-        this.tabs = tabs;
-        this.collapsed = panel.isDialogInCollapsedView();
-        chatPanes = new HashMap<>();
-        createChatPane(null);
-        tabs.addChangeListener(new ChangeListener() {
-            @Override
-            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 > alarm)
-                alarm = entry.notify;
-        }
-        return alarm;
-    }
-
-    public void updateActiveTabStatus() {
-        if (tabs.getSelectedIndex() >= 0)
-            ((ChatTabTitleComponent) tabs.getTabComponentAt(tabs.getSelectedIndex())).updateAlarm();
-    }
-
-    public void notify(String user, int alarmLevel) {
-        if (alarmLevel <= 0 || !hasUser(user))
-            return;
-        ChatPane entry = chatPanes.get(user == null ? PUBLIC_PANE : user);
-        entry.notify = alarmLevel;
-        int idx = tabs.indexOfComponent(entry.component);
-        if (idx >= 0)
-            ((ChatTabTitleComponent) tabs.getTabComponentAt(idx)).updateAlarm();
-    }
-
-    public static int MESSAGE_TYPE_DEFAULT = 0;
-    public static int MESSAGE_TYPE_INFORMATION = 1;
-    public static int MESSAGE_TYPE_ATTENTION = 2;
-    private static Color COLOR_ATTENTION = new Color(0, 0, 192);
-
-    private void addLineToChatPane(String userName, String line, final int messageType) {
-        if (line.length() == 0)
-            return;
-        if (!chatPanes.containsKey(userName))
-            createChatPane(userName);
-        final String nline = line.startsWith("\n") ? line : "\n" + line;
-        final JTextPane thepane = chatPanes.get(userName).pane;
-        GuiHelper.runInEDT(new Runnable() {
-            @Override
-            public void run() {
-                Document doc = thepane.getDocument();
-                try {
-                    SimpleAttributeSet attrs = null;
-                    if (messageType != MESSAGE_TYPE_DEFAULT) {
-                        attrs = new SimpleAttributeSet();
-                        if (messageType == MESSAGE_TYPE_INFORMATION)
-                            StyleConstants.setItalic(attrs, true);
-                        else if (messageType == MESSAGE_TYPE_ATTENTION)
-                            StyleConstants.setForeground(attrs, COLOR_ATTENTION);
-                    }
-                    doc.insertString(doc.getLength(), nline, attrs);
-                } catch (BadLocationException ex) {
-                    Logging.warn(ex);
-                }
-                thepane.setCaretPosition(doc.getLength());
-            }
-        });
-    }
-
-    public void addLineToChatPane(String userName, String line) {
-        addLineToChatPane(userName, line, MESSAGE_TYPE_DEFAULT);
-    }
-
-    public void addLineToPublic(String line) {
-        addLineToChatPane(PUBLIC_PANE, line);
-    }
-
-    public void addLineToPublic(String line, int messageType) {
-        addLineToChatPane(PUBLIC_PANE, line, messageType);
-    }
-
-    public void clearPublicChatPane() {
-        chatPanes.get(PUBLIC_PANE).pane.setText("");
-    }
-
-    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();
-        float size = Main.pref.getInteger("geochat.fontsize", -1);
-        if (size < 6)
-            size += font.getSize2D();
-        chatPane.setFont(font.deriveFont(size));
-        //        DefaultCaret caret = (DefaultCaret)chatPane.getCaret(); // does not work
-        //        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 = 0;
-        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<>(chatPanes.keySet());
-        for (String user : entries) {
-            if (!user.equals(PUBLIC_PANE))
-                closeChatPane(user);
-        }
-    }
-
-    public boolean hasSelectedText() {
-        String user = getActiveChatPane();
-        if (user != null) {
-            JTextPane pane = chatPanes.get(user).pane;
-            return pane.getSelectedText() != null;
-        }
-        return false;
-    }
-
-    public void copySelectedText() {
-        String user = getActiveChatPane();
-        if (user != null)
-            chatPanes.get(user).pane.copy();
-    }
-
-    private class ChatTabTitleComponent extends JLabel {
-        private ChatPane entry;
-
-        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);
-            }
-            if (entry.notify > 0 && !collapsed && tabs.getSelectedIndex() == tabs.indexOfComponent(entry.component))
-                entry.notify = 0;
-            setFont(entry.notify > 0 ? boldFont : normalFont);
-            panel.updateTitleAlarm();
-        }
-    }
-
-    class ChatPane {
-        public String userName;
-        public boolean isPublic;
-        public JTextPane pane;
-        public JScrollPane component;
-        public int notify;
-
-    }
+	private static final String PUBLIC_PANE = "Public Pane";
+
+	private GeoChatPanel panel;
+	private JTabbedPane tabs;
+	private Map<String, ChatPane> chatPanes;
+	private boolean collapsed;
+
+	ChatPaneManager(GeoChatPanel panel, JTabbedPane tabs) {
+		this.panel = panel;
+		this.tabs = tabs;
+		this.collapsed = panel.isDialogInCollapsedView();
+		chatPanes = new HashMap<>();
+		createChatPane(null);
+		tabs.addChangeListener(new ChangeListener() {
+			@Override
+			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 > alarm)
+				alarm = entry.notify;
+		}
+		return alarm;
+	}
+
+	public void updateActiveTabStatus() {
+		if (tabs.getSelectedIndex() >= 0)
+			((ChatTabTitleComponent) tabs.getTabComponentAt(tabs.getSelectedIndex())).updateAlarm();
+	}
+
+	public void notify(String user, int alarmLevel) {
+		if (alarmLevel <= 0 || !hasUser(user))
+			return;
+		ChatPane entry = chatPanes.get(user == null ? PUBLIC_PANE : user);
+		entry.notify = alarmLevel;
+		int idx = tabs.indexOfComponent(entry.component);
+		if (idx >= 0)
+			((ChatTabTitleComponent) tabs.getTabComponentAt(idx)).updateAlarm();
+	}
+
+	public static int MESSAGE_TYPE_DEFAULT = 0;
+	public static int MESSAGE_TYPE_INFORMATION = 1;
+	public static int MESSAGE_TYPE_ATTENTION = 2;
+	private static Color COLOR_ATTENTION = new Color(0, 0, 192);
+
+	private void addLineToChatPane(String userName, String line, final int messageType) {
+		if (line.length() == 0)
+			return;
+		if (!chatPanes.containsKey(userName))
+			createChatPane(userName);
+		final String nline = line.startsWith("\n") ? line : "\n" + line;
+		final JTextPane thepane = chatPanes.get(userName).pane;
+		GuiHelper.runInEDT(new Runnable() {
+			@Override
+			public void run() {
+				Document doc = thepane.getDocument();
+				try {
+					SimpleAttributeSet attrs = null;
+					if (messageType != MESSAGE_TYPE_DEFAULT) {
+						attrs = new SimpleAttributeSet();
+						if (messageType == MESSAGE_TYPE_INFORMATION)
+							StyleConstants.setItalic(attrs, true);
+						else if (messageType == MESSAGE_TYPE_ATTENTION)
+							StyleConstants.setForeground(attrs, COLOR_ATTENTION);
+					}
+					doc.insertString(doc.getLength(), nline, attrs);
+				} catch (BadLocationException ex) {
+					Logging.warn(ex);
+				}
+				thepane.setCaretPosition(doc.getLength());
+			}
+		});
+	}
+
+	public void addLineToChatPane(String userName, String line) {
+		addLineToChatPane(userName, line, MESSAGE_TYPE_DEFAULT);
+	}
+
+	public void addLineToPublic(String line) {
+		addLineToChatPane(PUBLIC_PANE, line);
+	}
+
+	public void addLineToPublic(String line, int messageType) {
+		addLineToChatPane(PUBLIC_PANE, line, messageType);
+	}
+
+	public void clearPublicChatPane() {
+		chatPanes.get(PUBLIC_PANE).pane.setText("");
+	}
+
+	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();
+		float size = Main.pref.getInteger("geochat.fontsize", -1);
+		if (size < 6)
+			size += font.getSize2D();
+		chatPane.setFont(font.deriveFont(size));
+		//        DefaultCaret caret = (DefaultCaret)chatPane.getCaret(); // does not work
+		//        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 = 0;
+		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<>(chatPanes.keySet());
+		for (String user : entries) {
+			if (!user.equals(PUBLIC_PANE))
+				closeChatPane(user);
+		}
+	}
+
+	public boolean hasSelectedText() {
+		String user = getActiveChatPane();
+		if (user != null) {
+			JTextPane pane = chatPanes.get(user).pane;
+			return pane.getSelectedText() != null;
+		}
+		return false;
+	}
+
+	public void copySelectedText() {
+		String user = getActiveChatPane();
+		if (user != null)
+			chatPanes.get(user).pane.copy();
+	}
+
+	private class ChatTabTitleComponent extends JLabel {
+		private ChatPane entry;
+
+		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);
+			}
+			if (entry.notify > 0 && !collapsed && tabs.getSelectedIndex() == tabs.indexOfComponent(entry.component))
+				entry.notify = 0;
+			setFont(entry.notify > 0 ? boldFont : normalFont);
+			panel.updateTitleAlarm();
+		}
+	}
+
+	static class ChatPane {
+		public String userName;
+		public boolean isPublic;
+		public JTextPane pane;
+		public JScrollPane component;
+		public int notify;
+
+	}
 }
Index: /applications/editors/josm/plugins/geochat/src/geochat/GeoChatPanel.java
===================================================================
--- /applications/editors/josm/plugins/geochat/src/geochat/GeoChatPanel.java	(revision 33601)
+++ /applications/editors/josm/plugins/geochat/src/geochat/GeoChatPanel.java	(revision 33602)
@@ -36,6 +36,9 @@
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.UserIdentityManager;
 import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.gui.*;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.Notification;
 import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
 import org.openstreetmap.josm.gui.layer.MapViewPaintable;
@@ -50,348 +53,348 @@
  */
 public class GeoChatPanel extends ToggleDialog implements ChatServerConnectionListener, MapViewPaintable {
-    private JTextField input;
-    private JTabbedPane tabs;
-    private JComponent noData;
-    private JPanel loginPanel;
-    private JPanel gcPanel;
-    private ChatServerConnection connection;
-    // those fields should be visible to popup menu actions
-    Map<String, LatLon> users;
-    ChatPaneManager chatPanes;
-    boolean userLayerActive;
-
-    public GeoChatPanel() {
-        super(tr("GeoChat"), "geochat", tr("Open GeoChat panel"), null, 200, true);
-
-        noData = new JLabel(tr("Zoom in to see messages"), SwingConstants.CENTER);
-
-        tabs = new JTabbedPane();
-        tabs.addMouseListener(new GeoChatPopupAdapter(this));
-        chatPanes = new ChatPaneManager(this, tabs);
-
-        input = new JPanelTextField() {
-            @Override
-            protected void processEnter(String text) {
-                connection.postMessage(text, chatPanes.getRecipient());
-            }
-
-            @Override
-            protected String autoComplete(String word, boolean atStart) {
-                return autoCompleteUser(word, atStart);
-            }
-        };
-
-        String defaultUserName = constructUserName();
-        loginPanel = createLoginPanel(defaultUserName);
-
-        gcPanel = new JPanel(new BorderLayout());
-        gcPanel.add(loginPanel, BorderLayout.CENTER);
-        createLayout(gcPanel, false, null);
-
-        users = new TreeMap<>();
-        // Start threads
-        connection = ChatServerConnection.getInstance();
-        connection.addListener(this);
-        boolean autoLogin = Main.pref.get("geochat.username", null) == null ? false : Main.pref.getBoolean("geochat.autologin", true);
-        connection.autoLoginWithDelay(autoLogin ? defaultUserName : null);
-        updateTitleAlarm();
-    }
-
-    private String constructUserName() {
-        String userName = Main.pref.get("geochat.username", null); // so the default is null
-        if (userName == null)
-            userName = JosmUserIdentityManager.getInstance().getUserName();
-        if (userName == null)
-            userName = "";
-        if (userName.contains("@"))
-            userName = userName.substring(0, userName.indexOf('@'));
-        userName = userName.replace(' ', '_');
-        return userName;
-    }
-
-    private JPanel createLoginPanel(String defaultUserName) {
-        final JTextField nameField = new JPanelTextField() {
-            @Override
-            protected void processEnter(String text) {
-                connection.login(text);
-            }
-        };
-        nameField.setText(defaultUserName);
-
-        JButton loginButton = new JButton(tr("Login"));
-        loginButton.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                connection.login(nameField.getText());
-            }
-        });
-        nameField.setPreferredSize(new Dimension(nameField.getPreferredSize().width, loginButton.getPreferredSize().height));
-
-        final JCheckBox autoLoginBox = new JCheckBox(tr("Enable autologin"), Main.pref.getBoolean("geochat.autologin", true));
-        autoLoginBox.addActionListener(new ActionListener() {
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                Main.pref.put("geochat.autologin", autoLoginBox.isSelected());
-            }
-        });
-
-        JPanel panel = new JPanel(new GridBagLayout());
-        panel.add(nameField, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(15, 0, 5, 0));
-        panel.add(loginButton, GBC.eol().fill(GridBagConstraints.NONE).insets(0, 0, 15, 0));
-        panel.add(autoLoginBox, GBC.std().insets(15, 0, 15, 0));
-        return panel;
-    }
-
-    protected void logout() {
-        connection.logout();
-    }
-
-    @Override
-    public void destroy() {
-        try {
-            if (Main.pref.getBoolean("geochat.logout.on.close", true)) {
-                connection.removeListener(this);
-                connection.bruteLogout();
-            }
-        } catch (IOException e) {
-            Logging.warn("Failed to logout from geochat server: " + e.getMessage());
-        }
-        super.destroy();
-    }
-
-    private String autoCompleteUser(String word, boolean atStart) {
-        String result = null;
-        boolean singleUser = true;
-        for (String user : users.keySet()) {
-            if (user.startsWith(word)) {
-                if (result == null)
-                    result = user;
-                else {
-                    singleUser = false;
-                    int i = word.length();
-                    while (i < result.length() && i < user.length() && result.charAt(i) == user.charAt(i)) {
-                        i++;
-                    }
-                    if (i < result.length())
-                        result = result.substring(0, i);
-                }
-            }
-        }
-        return result == null ? null : !singleUser ? result : atStart ? result + ": " : result + " ";
-    }
-
-    /**
-     * This is implementation of a "temporary layer". It paints circles
-     * for all users nearby.
-     */
-    @Override
-    public void paint(Graphics2D g, MapView mv, Bounds bbox) {
-        Graphics2D g2d = (Graphics2D) g.create();
-        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-        Composite ac04 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f);
-        Composite ac10 = g2d.getComposite();
-
-        Font font = g2d.getFont().deriveFont(Font.BOLD, g2d.getFont().getSize2D() + 2.0f);
-        g2d.setFont(font);
-        FontMetrics fm = g2d.getFontMetrics();
-
-        for (String user : users.keySet()) {
-            int stringWidth = fm.stringWidth(user);
-            int radius = stringWidth / 2 + 10;
-            Point p = mv.getPoint(users.get(user));
-
-            g2d.setComposite(ac04);
-            g2d.setColor(Color.white);
-            g2d.fillOval(p.x - radius, p.y - radius, radius * 2 + 1, radius * 2 + 1);
-
-            g2d.setComposite(ac10);
-            g2d.setColor(Color.black);
-            g2d.drawString(user, p.x - stringWidth / 2, p.y + fm.getDescent());
-        }
-    }
-
-    /* ================== Notifications in the title ======================= */
-
-    /**
-     * Display number of users and notifications in the panel title.
-     */
-    protected void updateTitleAlarm() {
-        int alarmLevel = connection.isLoggedIn() ? chatPanes.getNotifyLevel() : 0;
-        if (!isDialogInCollapsedView() && alarmLevel > 1)
-            alarmLevel = 1;
-
-        String comment;
-        if (connection.isLoggedIn()) {
-            comment = trn("{0} user", "{0} users", users.size() + 1, users.size() + 1);
-        } else {
-            comment = tr("not logged in");
-        }
-
-        String title = tr("GeoChat");
-        if (comment != null)
-            title = title + " (" + comment + ")";
-        final String alarm = (alarmLevel <= 0 ? "" : alarmLevel == 1 ? "* " : "!!! ") + title;
-        GuiHelper.runInEDT(new Runnable() {
-            @Override
-            public void run() {
-                setTitle(alarm);
-            }
-        });
-    }
-
-    /**
-     * Track panel collapse events.
-     */
-    @Override
-    protected void setIsCollapsed(boolean val) {
-        super.setIsCollapsed(val);
-        chatPanes.setCollapsed(val);
-        updateTitleAlarm();
-    }
-
-    /* ============ ChatServerConnectionListener methods ============= */
-
-    @Override
-    public void loggedIn(String userName) {
-        Main.pref.put("geochat.username", userName);
-        if (gcPanel.getComponentCount() == 1) {
-            GuiHelper.runInEDTAndWait(new Runnable() {
-                @Override
-                public void run() {
-                    gcPanel.remove(0);
-                    gcPanel.add(tabs, BorderLayout.CENTER);
-                    gcPanel.add(input, BorderLayout.SOUTH);
-                }
-            });
-        }
-        updateTitleAlarm();
-    }
-
-    @Override
-    public void notLoggedIn(final String reason) {
-        if (reason != null) {
-            GuiHelper.runInEDT(new Runnable() {
-                @Override
-                public void run() {
-                    new Notification(tr("Failed to log in to GeoChat:") + "\n" + reason).show();
-                }
-            });
-        } else {
-            // regular logout
-            if (gcPanel.getComponentCount() > 1) {
-                gcPanel.removeAll();
-                gcPanel.add(loginPanel, BorderLayout.CENTER);
-            }
-        }
-        updateTitleAlarm();
-    }
-
-    @Override
-    public void messageSendFailed(final String reason) {
-        GuiHelper.runInEDT(new Runnable() {
-            @Override
-            public void run() {
-                new Notification(tr("Failed to send message:") + "\n" + reason).show();
-            }
-        });
-    }
-
-    @Override
-    public void statusChanged(boolean active) {
-        // only the public tab, because private chats don't rely on coordinates
-        tabs.setComponentAt(0, active ? chatPanes.getPublicChatComponent() : noData);
-        repaint();
-    }
-
-    @Override
-    public void updateUsers(Map<String, LatLon> newUsers) {
-        for (String uname : this.users.keySet()) {
-            if (!newUsers.containsKey(uname))
-                chatPanes.addLineToPublic(tr("User {0} has left", uname), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
-        }
-        for (String uname : newUsers.keySet()) {
-            if (!this.users.containsKey(uname))
-                chatPanes.addLineToPublic(tr("User {0} is mapping nearby", uname), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
-        }
-        this.users = newUsers;
-        updateTitleAlarm();
-        if (userLayerActive && MainApplication.isDisplayingMapView())
-            MainApplication.getMap().mapView.repaint();
-    }
-
-    private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm");
-
-    private void formatMessage(StringBuilder sb, ChatMessage msg) {
-        sb.append("\n");
-        sb.append('[').append(TIME_FORMAT.format(msg.getTime())).append("] ");
-        sb.append(msg.getAuthor()).append(": ").append(msg.getMessage());
-    }
-
-    @Override
-    public void receivedMessages(boolean replace, List<ChatMessage> messages) {
-        if (replace)
-            chatPanes.clearPublicChatPane();
-        if (!messages.isEmpty()) {
-            int alarm = 0;
-            StringBuilder sb = new StringBuilder();
-            for (ChatMessage msg : messages) {
-                boolean important = msg.isIncoming() && containsName(msg.getMessage());
-                if (msg.isIncoming() && alarm < 2) {
-                    alarm = important ? 2 : 1;
-                }
-                if (important) {
-                    // add buffer, then add current line with italic, then clear buffer
-                    chatPanes.addLineToPublic(sb.toString());
-                    sb.setLength(0);
-                    formatMessage(sb, msg);
-                    chatPanes.addLineToPublic(sb.toString(), ChatPaneManager.MESSAGE_TYPE_ATTENTION);
-                    sb.setLength(0);
-                } else
-                    formatMessage(sb, msg);
-            }
-            chatPanes.addLineToPublic(sb.toString());
-            if (alarm > 0)
-                chatPanes.notify(null, alarm);
-        }
-        if (replace)
-            showNearbyUsers();
-    }
-
-    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);
-            }
-            chatPanes.addLineToPublic(sb.toString(), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
-        }
-    }
-
-    private boolean containsName(String message) {
-        String userName = connection.getUserName();
-        int length = userName.length();
-        int i = message.indexOf(userName);
-        while (i >= 0) {
-            if ((i == 0 || !Character.isJavaIdentifierPart(message.charAt(i - 1)))
-                    && (i + length >= message.length() || !Character.isJavaIdentifierPart(message.charAt(i + length))))
-                return true;
-            i = message.indexOf(userName, i + 1);
-        }
-        return false;
-    }
-
-    @Override
-    public void receivedPrivateMessages(boolean replace, List<ChatMessage> messages) {
-        if (replace)
-            chatPanes.closePrivateChatPanes();
-        for (ChatMessage msg : messages) {
-            StringBuilder sb = new StringBuilder();
-            formatMessage(sb, msg);
-            chatPanes.addLineToChatPane(msg.isIncoming() ? msg.getAuthor() : msg.getRecipient(), sb.toString());
-            if (msg.isIncoming())
-                chatPanes.notify(msg.getAuthor(), 2);
-        }
-    }
+	private JTextField input;
+	private JTabbedPane tabs;
+	private JComponent noData;
+	private JPanel loginPanel;
+	private JPanel gcPanel;
+	private ChatServerConnection connection;
+	// those fields should be visible to popup menu actions
+	Map<String, LatLon> users;
+	ChatPaneManager chatPanes;
+	boolean userLayerActive;
+
+	public GeoChatPanel() {
+		super(tr("GeoChat"), "geochat", tr("Open GeoChat panel"), null, 200, true);
+
+		noData = new JLabel(tr("Zoom in to see messages"), SwingConstants.CENTER);
+
+		tabs = new JTabbedPane();
+		tabs.addMouseListener(new GeoChatPopupAdapter(this));
+		chatPanes = new ChatPaneManager(this, tabs);
+
+		input = new JPanelTextField() {
+			@Override
+			protected void processEnter(String text) {
+				connection.postMessage(text, chatPanes.getRecipient());
+			}
+
+			@Override
+			protected String autoComplete(String word, boolean atStart) {
+				return autoCompleteUser(word, atStart);
+			}
+		};
+
+		String defaultUserName = constructUserName();
+		loginPanel = createLoginPanel(defaultUserName);
+
+		gcPanel = new JPanel(new BorderLayout());
+		gcPanel.add(loginPanel, BorderLayout.CENTER);
+		createLayout(gcPanel, false, null);
+
+		users = new TreeMap<>();
+		// Start threads
+		connection = ChatServerConnection.getInstance();
+		connection.addListener(this);
+		boolean autoLogin = Main.pref.get("geochat.username", null) == null ? false : Main.pref.getBoolean("geochat.autologin", true);
+		connection.autoLoginWithDelay(autoLogin ? defaultUserName : null);
+		updateTitleAlarm();
+	}
+
+	private String constructUserName() {
+		String userName = Main.pref.get("geochat.username", null); // so the default is null
+		if (userName == null)
+			userName = UserIdentityManager.getInstance().getUserName();
+		if (userName == null)
+			userName = "";
+		if (userName.contains("@"))
+			userName = userName.substring(0, userName.indexOf('@'));
+		userName = userName.replace(' ', '_');
+		return userName;
+	}
+
+	private JPanel createLoginPanel(String defaultUserName) {
+		final JTextField nameField = new JPanelTextField() {
+			@Override
+			protected void processEnter(String text) {
+				connection.login(text);
+			}
+		};
+		nameField.setText(defaultUserName);
+
+		JButton loginButton = new JButton(tr("Login"));
+		loginButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				connection.login(nameField.getText());
+			}
+		});
+		nameField.setPreferredSize(new Dimension(nameField.getPreferredSize().width, loginButton.getPreferredSize().height));
+
+		final JCheckBox autoLoginBox = new JCheckBox(tr("Enable autologin"), Main.pref.getBoolean("geochat.autologin", true));
+		autoLoginBox.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				Main.pref.put("geochat.autologin", autoLoginBox.isSelected());
+			}
+		});
+
+		JPanel panel = new JPanel(new GridBagLayout());
+		panel.add(nameField, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(15, 0, 5, 0));
+		panel.add(loginButton, GBC.eol().fill(GridBagConstraints.NONE).insets(0, 0, 15, 0));
+		panel.add(autoLoginBox, GBC.std().insets(15, 0, 15, 0));
+		return panel;
+	}
+
+	protected void logout() {
+		connection.logout();
+	}
+
+	@Override
+	public void destroy() {
+		try {
+			if (Main.pref.getBoolean("geochat.logout.on.close", true)) {
+				connection.removeListener(this);
+				connection.bruteLogout();
+			}
+		} catch (IOException e) {
+			Logging.warn("Failed to logout from geochat server: " + e.getMessage());
+		}
+		super.destroy();
+	}
+
+	private String autoCompleteUser(String word, boolean atStart) {
+		String result = null;
+		boolean singleUser = true;
+		for (String user : users.keySet()) {
+			if (user.startsWith(word)) {
+				if (result == null)
+					result = user;
+				else {
+					singleUser = false;
+					int i = word.length();
+					while (i < result.length() && i < user.length() && result.charAt(i) == user.charAt(i)) {
+						i++;
+					}
+					if (i < result.length())
+						result = result.substring(0, i);
+				}
+			}
+		}
+		return result == null ? null : !singleUser ? result : atStart ? result + ": " : result + " ";
+	}
+
+	/**
+	 * This is implementation of a "temporary layer". It paints circles
+	 * for all users nearby.
+	 */
+	@Override
+	public void paint(Graphics2D g, MapView mv, Bounds bbox) {
+		Graphics2D g2d = (Graphics2D) g.create();
+		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+		Composite ac04 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f);
+		Composite ac10 = g2d.getComposite();
+
+		Font font = g2d.getFont().deriveFont(Font.BOLD, g2d.getFont().getSize2D() + 2.0f);
+		g2d.setFont(font);
+		FontMetrics fm = g2d.getFontMetrics();
+
+		for (String user : users.keySet()) {
+			int stringWidth = fm.stringWidth(user);
+			int radius = stringWidth / 2 + 10;
+			Point p = mv.getPoint(users.get(user));
+
+			g2d.setComposite(ac04);
+			g2d.setColor(Color.white);
+			g2d.fillOval(p.x - radius, p.y - radius, radius * 2 + 1, radius * 2 + 1);
+
+			g2d.setComposite(ac10);
+			g2d.setColor(Color.black);
+			g2d.drawString(user, p.x - stringWidth / 2, p.y + fm.getDescent());
+		}
+	}
+
+	/* ================== Notifications in the title ======================= */
+
+	/**
+	 * Display number of users and notifications in the panel title.
+	 */
+	protected void updateTitleAlarm() {
+		int alarmLevel = connection.isLoggedIn() ? chatPanes.getNotifyLevel() : 0;
+		if (!isDialogInCollapsedView() && alarmLevel > 1)
+			alarmLevel = 1;
+
+		String comment;
+		if (connection.isLoggedIn()) {
+			comment = trn("{0} user", "{0} users", users.size() + 1, users.size() + 1);
+		} else {
+			comment = tr("not logged in");
+		}
+
+		String title = tr("GeoChat");
+		if (comment != null)
+			title = title + " (" + comment + ")";
+		final String alarm = (alarmLevel <= 0 ? "" : alarmLevel == 1 ? "* " : "!!! ") + title;
+		GuiHelper.runInEDT(new Runnable() {
+			@Override
+			public void run() {
+				setTitle(alarm);
+			}
+		});
+	}
+
+	/**
+	 * Track panel collapse events.
+	 */
+	@Override
+	protected void setIsCollapsed(boolean val) {
+		super.setIsCollapsed(val);
+		chatPanes.setCollapsed(val);
+		updateTitleAlarm();
+	}
+
+	/* ============ ChatServerConnectionListener methods ============= */
+
+	@Override
+	public void loggedIn(String userName) {
+		Main.pref.put("geochat.username", userName);
+		if (gcPanel.getComponentCount() == 1) {
+			GuiHelper.runInEDTAndWait(new Runnable() {
+				@Override
+				public void run() {
+					gcPanel.remove(0);
+					gcPanel.add(tabs, BorderLayout.CENTER);
+					gcPanel.add(input, BorderLayout.SOUTH);
+				}
+			});
+		}
+		updateTitleAlarm();
+	}
+
+	@Override
+	public void notLoggedIn(final String reason) {
+		if (reason != null) {
+			GuiHelper.runInEDT(new Runnable() {
+				@Override
+				public void run() {
+					new Notification(tr("Failed to log in to GeoChat:") + "\n" + reason).show();
+				}
+			});
+		} else {
+			// regular logout
+			if (gcPanel.getComponentCount() > 1) {
+				gcPanel.removeAll();
+				gcPanel.add(loginPanel, BorderLayout.CENTER);
+			}
+		}
+		updateTitleAlarm();
+	}
+
+	@Override
+	public void messageSendFailed(final String reason) {
+		GuiHelper.runInEDT(new Runnable() {
+			@Override
+			public void run() {
+				new Notification(tr("Failed to send message:") + "\n" + reason).show();
+			}
+		});
+	}
+
+	@Override
+	public void statusChanged(boolean active) {
+		// only the public tab, because private chats don't rely on coordinates
+		tabs.setComponentAt(0, active ? chatPanes.getPublicChatComponent() : noData);
+		repaint();
+	}
+
+	@Override
+	public void updateUsers(Map<String, LatLon> newUsers) {
+		for (String uname : this.users.keySet()) {
+			if (!newUsers.containsKey(uname))
+				chatPanes.addLineToPublic(tr("User {0} has left", uname), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
+		}
+		for (String uname : newUsers.keySet()) {
+			if (!this.users.containsKey(uname))
+				chatPanes.addLineToPublic(tr("User {0} is mapping nearby", uname), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
+		}
+		this.users = newUsers;
+		updateTitleAlarm();
+		if (userLayerActive && MainApplication.isDisplayingMapView())
+			MainApplication.getMap().mapView.repaint();
+	}
+
+	private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm");
+
+	private void formatMessage(StringBuilder sb, ChatMessage msg) {
+		sb.append("\n");
+		sb.append('[').append(TIME_FORMAT.format(msg.getTime())).append("] ");
+		sb.append(msg.getAuthor()).append(": ").append(msg.getMessage());
+	}
+
+	@Override
+	public void receivedMessages(boolean replace, List<ChatMessage> messages) {
+		if (replace)
+			chatPanes.clearPublicChatPane();
+		if (!messages.isEmpty()) {
+			int alarm = 0;
+			StringBuilder sb = new StringBuilder();
+			for (ChatMessage msg : messages) {
+				boolean important = msg.isIncoming() && containsName(msg.getMessage());
+				if (msg.isIncoming() && alarm < 2) {
+					alarm = important ? 2 : 1;
+				}
+				if (important) {
+					// add buffer, then add current line with italic, then clear buffer
+					chatPanes.addLineToPublic(sb.toString());
+					sb.setLength(0);
+					formatMessage(sb, msg);
+					chatPanes.addLineToPublic(sb.toString(), ChatPaneManager.MESSAGE_TYPE_ATTENTION);
+					sb.setLength(0);
+				} else
+					formatMessage(sb, msg);
+			}
+			chatPanes.addLineToPublic(sb.toString());
+			if (alarm > 0)
+				chatPanes.notify(null, alarm);
+		}
+		if (replace)
+			showNearbyUsers();
+	}
+
+	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);
+			}
+			chatPanes.addLineToPublic(sb.toString(), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
+		}
+	}
+
+	private boolean containsName(String message) {
+		String userName = connection.getUserName();
+		int length = userName.length();
+		int i = message.indexOf(userName);
+		while (i >= 0) {
+			if ((i == 0 || !Character.isJavaIdentifierPart(message.charAt(i - 1)))
+					&& (i + length >= message.length() || !Character.isJavaIdentifierPart(message.charAt(i + length))))
+				return true;
+			i = message.indexOf(userName, i + 1);
+		}
+		return false;
+	}
+
+	@Override
+	public void receivedPrivateMessages(boolean replace, List<ChatMessage> messages) {
+		if (replace)
+			chatPanes.closePrivateChatPanes();
+		for (ChatMessage msg : messages) {
+			StringBuilder sb = new StringBuilder();
+			formatMessage(sb, msg);
+			chatPanes.addLineToChatPane(msg.isIncoming() ? msg.getAuthor() : msg.getRecipient(), sb.toString());
+			if (msg.isIncoming())
+				chatPanes.notify(msg.getAuthor(), 2);
+		}
+	}
 }
