Changeset 33796 in osm


Ignore:
Timestamp:
2017-11-07T23:44:03+01:00 (7 years ago)
Author:
donvip
Message:

update to JOSM 12840

Location:
applications/editors/josm/plugins/geochat
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/geochat/build.xml

    r33602 r33796  
    55    <property name="commit.message" value="[josm_geochat] copypaste from keyboard, font size advanced parameters"/>
    66    <!-- enter the *lowest* JOSM version this plugin is currently compatible with -->
    7     <property name="plugin.main.version" value="12743"/>
     7    <property name="plugin.main.version" value="12840"/>
    88
    99    <property name="plugin.author" value="Ilya Zverev"/>
  • applications/editors/josm/plugins/geochat/src/geochat/ChatPaneManager.java

    r33602 r33796  
    3232 */
    3333class ChatPaneManager {
    34         private static final String PUBLIC_PANE = "Public Pane";
    35 
    36         private GeoChatPanel panel;
    37         private JTabbedPane tabs;
    38         private Map<String, ChatPane> chatPanes;
    39         private boolean collapsed;
    40 
    41         ChatPaneManager(GeoChatPanel panel, JTabbedPane tabs) {
    42                 this.panel = panel;
    43                 this.tabs = tabs;
    44                 this.collapsed = panel.isDialogInCollapsedView();
    45                 chatPanes = new HashMap<>();
    46                 createChatPane(null);
    47                 tabs.addChangeListener(new ChangeListener() {
    48                         @Override
    49                         public void stateChanged(ChangeEvent e) {
    50                                 updateActiveTabStatus();
    51                         }
    52                 });
    53         }
    54 
    55         public void setCollapsed(boolean collapsed) {
    56                 this.collapsed = collapsed;
    57                 updateActiveTabStatus();
    58         }
    59 
    60         public boolean hasUser(String user) {
    61                 return chatPanes.containsKey(user == null ? PUBLIC_PANE : user);
    62         }
    63 
    64         public Component getPublicChatComponent() {
    65                 return chatPanes.get(PUBLIC_PANE).component;
    66         }
    67 
    68         public int getNotifyLevel() {
    69                 int alarm = 0;
    70                 for (ChatPane entry : chatPanes.values()) {
    71                         if (entry.notify > alarm)
    72                                 alarm = entry.notify;
    73                 }
    74                 return alarm;
    75         }
    76 
    77         public void updateActiveTabStatus() {
    78                 if (tabs.getSelectedIndex() >= 0)
    79                         ((ChatTabTitleComponent) tabs.getTabComponentAt(tabs.getSelectedIndex())).updateAlarm();
    80         }
    81 
    82         public void notify(String user, int alarmLevel) {
    83                 if (alarmLevel <= 0 || !hasUser(user))
    84                         return;
    85                 ChatPane entry = chatPanes.get(user == null ? PUBLIC_PANE : user);
    86                 entry.notify = alarmLevel;
    87                 int idx = tabs.indexOfComponent(entry.component);
    88                 if (idx >= 0)
    89                         ((ChatTabTitleComponent) tabs.getTabComponentAt(idx)).updateAlarm();
    90         }
    91 
    92         public static int MESSAGE_TYPE_DEFAULT = 0;
    93         public static int MESSAGE_TYPE_INFORMATION = 1;
    94         public static int MESSAGE_TYPE_ATTENTION = 2;
    95         private static Color COLOR_ATTENTION = new Color(0, 0, 192);
    96 
    97         private void addLineToChatPane(String userName, String line, final int messageType) {
    98                 if (line.length() == 0)
    99                         return;
    100                 if (!chatPanes.containsKey(userName))
    101                         createChatPane(userName);
    102                 final String nline = line.startsWith("\n") ? line : "\n" + line;
    103                 final JTextPane thepane = chatPanes.get(userName).pane;
    104                 GuiHelper.runInEDT(new Runnable() {
    105                         @Override
    106                         public void run() {
    107                                 Document doc = thepane.getDocument();
    108                                 try {
    109                                         SimpleAttributeSet attrs = null;
    110                                         if (messageType != MESSAGE_TYPE_DEFAULT) {
    111                                                 attrs = new SimpleAttributeSet();
    112                                                 if (messageType == MESSAGE_TYPE_INFORMATION)
    113                                                         StyleConstants.setItalic(attrs, true);
    114                                                 else if (messageType == MESSAGE_TYPE_ATTENTION)
    115                                                         StyleConstants.setForeground(attrs, COLOR_ATTENTION);
    116                                         }
    117                                         doc.insertString(doc.getLength(), nline, attrs);
    118                                 } catch (BadLocationException ex) {
    119                                         Logging.warn(ex);
    120                                 }
    121                                 thepane.setCaretPosition(doc.getLength());
    122                         }
    123                 });
    124         }
    125 
    126         public void addLineToChatPane(String userName, String line) {
    127                 addLineToChatPane(userName, line, MESSAGE_TYPE_DEFAULT);
    128         }
    129 
    130         public void addLineToPublic(String line) {
    131                 addLineToChatPane(PUBLIC_PANE, line);
    132         }
    133 
    134         public void addLineToPublic(String line, int messageType) {
    135                 addLineToChatPane(PUBLIC_PANE, line, messageType);
    136         }
    137 
    138         public void clearPublicChatPane() {
    139                 chatPanes.get(PUBLIC_PANE).pane.setText("");
    140         }
    141 
    142         public void clearChatPane(String userName) {
    143                 if (userName == null || userName.equals(PUBLIC_PANE))
    144                         clearPublicChatPane();
    145                 else
    146                         chatPanes.get(userName).pane.setText("");
    147         }
    148 
    149         public void clearActiveChatPane() {
    150                 clearChatPane(getActiveChatPane());
    151         }
    152 
    153         public ChatPane createChatPane(String userName) {
    154                 JTextPane chatPane = new JTextPane();
    155                 chatPane.setEditable(false);
    156                 Font font = chatPane.getFont();
    157                 float size = Main.pref.getInteger("geochat.fontsize", -1);
    158                 if (size < 6)
    159                         size += font.getSize2D();
    160                 chatPane.setFont(font.deriveFont(size));
    161                 //        DefaultCaret caret = (DefaultCaret)chatPane.getCaret(); // does not work
    162                 //        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
    163                 JScrollPane scrollPane = new JScrollPane(chatPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    164                 chatPane.addMouseListener(new GeoChatPopupAdapter(panel));
    165 
    166                 ChatPane entry = new ChatPane();
    167                 entry.pane = chatPane;
    168                 entry.component = scrollPane;
    169                 entry.notify = 0;
    170                 entry.userName = userName;
    171                 entry.isPublic = userName == null;
    172                 chatPanes.put(userName == null ? PUBLIC_PANE : userName, entry);
    173 
    174                 tabs.addTab(null, scrollPane);
    175                 tabs.setTabComponentAt(tabs.getTabCount() - 1, new ChatTabTitleComponent(entry));
    176                 tabs.setSelectedComponent(scrollPane);
    177                 return entry;
    178         }
    179 
    180         /**
    181         * Returns key in chatPanes hash map for the currently active
    182         * chat pane, or null in case of an error.
    183         */
    184         public String getActiveChatPane() {
    185                 Component c = tabs.getSelectedComponent();
    186                 if (c == null)
    187                         return null;
    188                 for (String user : chatPanes.keySet()) {
    189                         if (c.equals(chatPanes.get(user).component))
    190                                 return user;
    191                 }
    192                 return null;
    193         }
    194 
    195         public String getRecipient() {
    196                 String user = getActiveChatPane();
    197                 return user == null || user.equals(PUBLIC_PANE) ? null : user;
    198         }
    199 
    200         public void closeChatPane(String user) {
    201                 if (user == null || user.equals(PUBLIC_PANE) || !chatPanes.containsKey(user))
    202                         return;
    203                 tabs.remove(chatPanes.get(user).component);
    204                 chatPanes.remove(user);
    205         }
    206 
    207         public void closeSelectedPrivatePane() {
    208                 String pane = getRecipient();
    209                 if (pane != null)
    210                         closeChatPane(pane);
    211         }
    212 
    213         public void closePrivateChatPanes() {
    214                 List<String> entries = new ArrayList<>(chatPanes.keySet());
    215                 for (String user : entries) {
    216                         if (!user.equals(PUBLIC_PANE))
    217                                 closeChatPane(user);
    218                 }
    219         }
    220 
    221         public boolean hasSelectedText() {
    222                 String user = getActiveChatPane();
    223                 if (user != null) {
    224                         JTextPane pane = chatPanes.get(user).pane;
    225                         return pane.getSelectedText() != null;
    226                 }
    227                 return false;
    228         }
    229 
    230         public void copySelectedText() {
    231                 String user = getActiveChatPane();
    232                 if (user != null)
    233                         chatPanes.get(user).pane.copy();
    234         }
    235 
    236         private class ChatTabTitleComponent extends JLabel {
    237                 private ChatPane entry;
    238 
    239                 ChatTabTitleComponent(ChatPane entry) {
    240                         super(entry.isPublic ? tr("Public") : entry.userName);
    241                         this.entry = entry;
    242                 }
    243 
    244                 private Font normalFont;
    245                 private Font boldFont;
    246 
    247                 public void updateAlarm() {
    248                         if (normalFont == null) {
    249                                 // prepare cached fonts
    250                                 normalFont = getFont().deriveFont(Font.PLAIN);
    251                                 boldFont = getFont().deriveFont(Font.BOLD);
    252                         }
    253                         if (entry.notify > 0 && !collapsed && tabs.getSelectedIndex() == tabs.indexOfComponent(entry.component))
    254                                 entry.notify = 0;
    255                         setFont(entry.notify > 0 ? boldFont : normalFont);
    256                         panel.updateTitleAlarm();
    257                 }
    258         }
    259 
    260         static class ChatPane {
    261                 public String userName;
    262                 public boolean isPublic;
    263                 public JTextPane pane;
    264                 public JScrollPane component;
    265                 public int notify;
    266 
    267         }
     34    private static final String PUBLIC_PANE = "Public Pane";
     35
     36    private GeoChatPanel panel;
     37    private JTabbedPane tabs;
     38    private Map<String, ChatPane> chatPanes;
     39    private boolean collapsed;
     40
     41    ChatPaneManager(GeoChatPanel panel, JTabbedPane tabs) {
     42        this.panel = panel;
     43        this.tabs = tabs;
     44        this.collapsed = panel.isDialogInCollapsedView();
     45        chatPanes = new HashMap<>();
     46        createChatPane(null);
     47        tabs.addChangeListener(new ChangeListener() {
     48            @Override
     49            public void stateChanged(ChangeEvent e) {
     50                updateActiveTabStatus();
     51            }
     52        });
     53    }
     54
     55    public void setCollapsed(boolean collapsed) {
     56        this.collapsed = collapsed;
     57        updateActiveTabStatus();
     58    }
     59
     60    public boolean hasUser(String user) {
     61        return chatPanes.containsKey(user == null ? PUBLIC_PANE : user);
     62    }
     63
     64    public Component getPublicChatComponent() {
     65        return chatPanes.get(PUBLIC_PANE).component;
     66    }
     67
     68    public int getNotifyLevel() {
     69        int alarm = 0;
     70        for (ChatPane entry : chatPanes.values()) {
     71            if (entry.notify > alarm)
     72                alarm = entry.notify;
     73        }
     74        return alarm;
     75    }
     76
     77    public void updateActiveTabStatus() {
     78        if (tabs.getSelectedIndex() >= 0)
     79            ((ChatTabTitleComponent) tabs.getTabComponentAt(tabs.getSelectedIndex())).updateAlarm();
     80    }
     81
     82    public void notify(String user, int alarmLevel) {
     83        if (alarmLevel <= 0 || !hasUser(user))
     84            return;
     85        ChatPane entry = chatPanes.get(user == null ? PUBLIC_PANE : user);
     86        entry.notify = alarmLevel;
     87        int idx = tabs.indexOfComponent(entry.component);
     88        if (idx >= 0)
     89            ((ChatTabTitleComponent) tabs.getTabComponentAt(idx)).updateAlarm();
     90    }
     91
     92    public static int MESSAGE_TYPE_DEFAULT = 0;
     93    public static int MESSAGE_TYPE_INFORMATION = 1;
     94    public static int MESSAGE_TYPE_ATTENTION = 2;
     95    private static Color COLOR_ATTENTION = new Color(0, 0, 192);
     96
     97    private void addLineToChatPane(String userName, String line, final int messageType) {
     98        if (line.length() == 0)
     99            return;
     100        if (!chatPanes.containsKey(userName))
     101            createChatPane(userName);
     102        final String nline = line.startsWith("\n") ? line : "\n" + line;
     103        final JTextPane thepane = chatPanes.get(userName).pane;
     104        GuiHelper.runInEDT(new Runnable() {
     105            @Override
     106            public void run() {
     107                Document doc = thepane.getDocument();
     108                try {
     109                    SimpleAttributeSet attrs = null;
     110                    if (messageType != MESSAGE_TYPE_DEFAULT) {
     111                        attrs = new SimpleAttributeSet();
     112                        if (messageType == MESSAGE_TYPE_INFORMATION)
     113                            StyleConstants.setItalic(attrs, true);
     114                        else if (messageType == MESSAGE_TYPE_ATTENTION)
     115                            StyleConstants.setForeground(attrs, COLOR_ATTENTION);
     116                    }
     117                    doc.insertString(doc.getLength(), nline, attrs);
     118                } catch (BadLocationException ex) {
     119                    Logging.warn(ex);
     120                }
     121                thepane.setCaretPosition(doc.getLength());
     122            }
     123        });
     124    }
     125
     126    public void addLineToChatPane(String userName, String line) {
     127        addLineToChatPane(userName, line, MESSAGE_TYPE_DEFAULT);
     128    }
     129
     130    public void addLineToPublic(String line) {
     131        addLineToChatPane(PUBLIC_PANE, line);
     132    }
     133
     134    public void addLineToPublic(String line, int messageType) {
     135        addLineToChatPane(PUBLIC_PANE, line, messageType);
     136    }
     137
     138    public void clearPublicChatPane() {
     139        chatPanes.get(PUBLIC_PANE).pane.setText("");
     140    }
     141
     142    public void clearChatPane(String userName) {
     143        if (userName == null || userName.equals(PUBLIC_PANE))
     144            clearPublicChatPane();
     145        else
     146            chatPanes.get(userName).pane.setText("");
     147    }
     148
     149    public void clearActiveChatPane() {
     150        clearChatPane(getActiveChatPane());
     151    }
     152
     153    public ChatPane createChatPane(String userName) {
     154        JTextPane chatPane = new JTextPane();
     155        chatPane.setEditable(false);
     156        Font font = chatPane.getFont();
     157        float size = Main.pref.getInt("geochat.fontsize", -1);
     158        if (size < 6)
     159            size += font.getSize2D();
     160        chatPane.setFont(font.deriveFont(size));
     161        //        DefaultCaret caret = (DefaultCaret)chatPane.getCaret(); // does not work
     162        //        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
     163        JScrollPane scrollPane = new JScrollPane(chatPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
     164        chatPane.addMouseListener(new GeoChatPopupAdapter(panel));
     165
     166        ChatPane entry = new ChatPane();
     167        entry.pane = chatPane;
     168        entry.component = scrollPane;
     169        entry.notify = 0;
     170        entry.userName = userName;
     171        entry.isPublic = userName == null;
     172        chatPanes.put(userName == null ? PUBLIC_PANE : userName, entry);
     173
     174        tabs.addTab(null, scrollPane);
     175        tabs.setTabComponentAt(tabs.getTabCount() - 1, new ChatTabTitleComponent(entry));
     176        tabs.setSelectedComponent(scrollPane);
     177        return entry;
     178    }
     179
     180    /**
     181    * Returns key in chatPanes hash map for the currently active
     182    * chat pane, or null in case of an error.
     183    */
     184    public String getActiveChatPane() {
     185        Component c = tabs.getSelectedComponent();
     186        if (c == null)
     187            return null;
     188        for (String user : chatPanes.keySet()) {
     189            if (c.equals(chatPanes.get(user).component))
     190                return user;
     191        }
     192        return null;
     193    }
     194
     195    public String getRecipient() {
     196        String user = getActiveChatPane();
     197        return user == null || user.equals(PUBLIC_PANE) ? null : user;
     198    }
     199
     200    public void closeChatPane(String user) {
     201        if (user == null || user.equals(PUBLIC_PANE) || !chatPanes.containsKey(user))
     202            return;
     203        tabs.remove(chatPanes.get(user).component);
     204        chatPanes.remove(user);
     205    }
     206
     207    public void closeSelectedPrivatePane() {
     208        String pane = getRecipient();
     209        if (pane != null)
     210            closeChatPane(pane);
     211    }
     212
     213    public void closePrivateChatPanes() {
     214        List<String> entries = new ArrayList<>(chatPanes.keySet());
     215        for (String user : entries) {
     216            if (!user.equals(PUBLIC_PANE))
     217                closeChatPane(user);
     218        }
     219    }
     220
     221    public boolean hasSelectedText() {
     222        String user = getActiveChatPane();
     223        if (user != null) {
     224            JTextPane pane = chatPanes.get(user).pane;
     225            return pane.getSelectedText() != null;
     226        }
     227        return false;
     228    }
     229
     230    public void copySelectedText() {
     231        String user = getActiveChatPane();
     232        if (user != null)
     233            chatPanes.get(user).pane.copy();
     234    }
     235
     236    private class ChatTabTitleComponent extends JLabel {
     237        private ChatPane entry;
     238
     239        ChatTabTitleComponent(ChatPane entry) {
     240            super(entry.isPublic ? tr("Public") : entry.userName);
     241            this.entry = entry;
     242        }
     243
     244        private Font normalFont;
     245        private Font boldFont;
     246
     247        public void updateAlarm() {
     248            if (normalFont == null) {
     249                // prepare cached fonts
     250                normalFont = getFont().deriveFont(Font.PLAIN);
     251                boldFont = getFont().deriveFont(Font.BOLD);
     252            }
     253            if (entry.notify > 0 && !collapsed && tabs.getSelectedIndex() == tabs.indexOfComponent(entry.component))
     254                entry.notify = 0;
     255            setFont(entry.notify > 0 ? boldFont : normalFont);
     256            panel.updateTitleAlarm();
     257        }
     258    }
     259
     260    static class ChatPane {
     261        public String userName;
     262        public boolean isPublic;
     263        public JTextPane pane;
     264        public JScrollPane component;
     265        public int notify;
     266
     267    }
    268268}
  • applications/editors/josm/plugins/geochat/src/geochat/ChatServerConnection.java

    r33545 r33796  
    2020
    2121import org.openstreetmap.josm.Main;
    22 import org.openstreetmap.josm.data.coor.CoordinateFormat;
    2322import org.openstreetmap.josm.data.coor.LatLon;
     23import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat;
    2424import org.openstreetmap.josm.data.projection.Projection;
    2525import org.openstreetmap.josm.gui.MainApplication;
     
    8888     */
    8989    public void autoLogin(final String userName) {
    90         final int uid = Main.pref.getInteger("geochat.lastuid", 0);
     90        final int uid = Main.pref.getInt("geochat.lastuid", 0);
    9191        if (uid <= 0) {
    9292            if (userName != null && userName.length() > 1)
     
    147147        try {
    148148            String nameAttr = token != null ? "&token=" + token : "&name=" + URLEncoder.encode(userName, "UTF-8");
    149             String query = "register&lat=" + pos.latToString(CoordinateFormat.DECIMAL_DEGREES)
    150             + "&lon=" + pos.lonToString(CoordinateFormat.DECIMAL_DEGREES)
     149            String query = "register&lat=" + DecimalDegreesCoordinateFormat.INSTANCE.latToString(pos)
     150            + "&lon=" + DecimalDegreesCoordinateFormat.INSTANCE.lonToString(pos)
    151151            + nameAttr;
    152152            JsonQueryUtil.queryAsync(query, new JsonQueryCallback() {
     
    173173        this.userId = userId;
    174174        this.userName = userName;
    175         Main.pref.putInteger("geochat.lastuid", userId);
     175        Main.pref.putInt("geochat.lastuid", userId);
    176176        for (ChatServerConnectionListener listener : listeners) {
    177177            listener.loggedIn(userName);
     
    252252        }
    253253        try {
    254             String query = "post&lat=" + pos.latToString(CoordinateFormat.DECIMAL_DEGREES)
    255             + "&lon=" + pos.lonToString(CoordinateFormat.DECIMAL_DEGREES)
     254            String query = "post&lat=" + DecimalDegreesCoordinateFormat.INSTANCE.latToString(pos)
     255            + "&lon=" + DecimalDegreesCoordinateFormat.INSTANCE.lonToString(pos)
    256256            + "&uid=" + userId
    257257            + "&message=" + URLEncoder.encode(message, "UTF8");
     
    329329        public void run() {
    330330            //            lastId = Main.pref.getLong("geochat.lastid", 0);
    331             int interval = Main.pref.getInteger("geochat.interval", 2);
     331            int interval = Main.pref.getInt("geochat.interval", 2);
    332332            while (!stopping) {
    333333                process();
     
    369369            lastPosition = pos;
    370370
    371             String query = "get&lat=" + pos.latToString(CoordinateFormat.DECIMAL_DEGREES)
    372             + "&lon=" + pos.lonToString(CoordinateFormat.DECIMAL_DEGREES)
     371            String query = "get&lat=" + DecimalDegreesCoordinateFormat.INSTANCE.latToString(pos)
     372            + "&lon=" + DecimalDegreesCoordinateFormat.INSTANCE.lonToString(pos)
    373373            + "&uid=" + userId + "&last=" + lastId;
    374374            JsonObject json;
  • applications/editors/josm/plugins/geochat/src/geochat/GeoChatPanel.java

    r33602 r33796  
    5353 */
    5454public class GeoChatPanel extends ToggleDialog implements ChatServerConnectionListener, MapViewPaintable {
    55         private JTextField input;
    56         private JTabbedPane tabs;
    57         private JComponent noData;
    58         private JPanel loginPanel;
    59         private JPanel gcPanel;
    60         private ChatServerConnection connection;
    61         // those fields should be visible to popup menu actions
    62         Map<String, LatLon> users;
    63         ChatPaneManager chatPanes;
    64         boolean userLayerActive;
    65 
    66         public GeoChatPanel() {
    67                 super(tr("GeoChat"), "geochat", tr("Open GeoChat panel"), null, 200, true);
    68 
    69                 noData = new JLabel(tr("Zoom in to see messages"), SwingConstants.CENTER);
    70 
    71                 tabs = new JTabbedPane();
    72                 tabs.addMouseListener(new GeoChatPopupAdapter(this));
    73                 chatPanes = new ChatPaneManager(this, tabs);
    74 
    75                 input = new JPanelTextField() {
    76                         @Override
    77                         protected void processEnter(String text) {
    78                                 connection.postMessage(text, chatPanes.getRecipient());
    79                         }
    80 
    81                         @Override
    82                         protected String autoComplete(String word, boolean atStart) {
    83                                 return autoCompleteUser(word, atStart);
    84                         }
    85                 };
    86 
    87                 String defaultUserName = constructUserName();
    88                 loginPanel = createLoginPanel(defaultUserName);
    89 
    90                 gcPanel = new JPanel(new BorderLayout());
    91                 gcPanel.add(loginPanel, BorderLayout.CENTER);
    92                 createLayout(gcPanel, false, null);
    93 
    94                 users = new TreeMap<>();
    95                 // Start threads
    96                 connection = ChatServerConnection.getInstance();
    97                 connection.addListener(this);
    98                 boolean autoLogin = Main.pref.get("geochat.username", null) == null ? false : Main.pref.getBoolean("geochat.autologin", true);
    99                 connection.autoLoginWithDelay(autoLogin ? defaultUserName : null);
    100                 updateTitleAlarm();
    101         }
    102 
    103         private String constructUserName() {
    104                 String userName = Main.pref.get("geochat.username", null); // so the default is null
    105                 if (userName == null)
    106                         userName = UserIdentityManager.getInstance().getUserName();
    107                 if (userName == null)
    108                         userName = "";
    109                 if (userName.contains("@"))
    110                         userName = userName.substring(0, userName.indexOf('@'));
    111                 userName = userName.replace(' ', '_');
    112                 return userName;
    113         }
    114 
    115         private JPanel createLoginPanel(String defaultUserName) {
    116                 final JTextField nameField = new JPanelTextField() {
    117                         @Override
    118                         protected void processEnter(String text) {
    119                                 connection.login(text);
    120                         }
    121                 };
    122                 nameField.setText(defaultUserName);
    123 
    124                 JButton loginButton = new JButton(tr("Login"));
    125                 loginButton.addActionListener(new ActionListener() {
    126                         @Override
    127                         public void actionPerformed(ActionEvent e) {
    128                                 connection.login(nameField.getText());
    129                         }
    130                 });
    131                 nameField.setPreferredSize(new Dimension(nameField.getPreferredSize().width, loginButton.getPreferredSize().height));
    132 
    133                 final JCheckBox autoLoginBox = new JCheckBox(tr("Enable autologin"), Main.pref.getBoolean("geochat.autologin", true));
    134                 autoLoginBox.addActionListener(new ActionListener() {
    135                         @Override
    136                         public void actionPerformed(ActionEvent e) {
    137                                 Main.pref.put("geochat.autologin", autoLoginBox.isSelected());
    138                         }
    139                 });
    140 
    141                 JPanel panel = new JPanel(new GridBagLayout());
    142                 panel.add(nameField, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(15, 0, 5, 0));
    143                 panel.add(loginButton, GBC.eol().fill(GridBagConstraints.NONE).insets(0, 0, 15, 0));
    144                 panel.add(autoLoginBox, GBC.std().insets(15, 0, 15, 0));
    145                 return panel;
    146         }
    147 
    148         protected void logout() {
    149                 connection.logout();
    150         }
    151 
    152         @Override
    153         public void destroy() {
    154                 try {
    155                         if (Main.pref.getBoolean("geochat.logout.on.close", true)) {
    156                                 connection.removeListener(this);
    157                                 connection.bruteLogout();
    158                         }
    159                 } catch (IOException e) {
    160                         Logging.warn("Failed to logout from geochat server: " + e.getMessage());
    161                 }
    162                 super.destroy();
    163         }
    164 
    165         private String autoCompleteUser(String word, boolean atStart) {
    166                 String result = null;
    167                 boolean singleUser = true;
    168                 for (String user : users.keySet()) {
    169                         if (user.startsWith(word)) {
    170                                 if (result == null)
    171                                         result = user;
    172                                 else {
    173                                         singleUser = false;
    174                                         int i = word.length();
    175                                         while (i < result.length() && i < user.length() && result.charAt(i) == user.charAt(i)) {
    176                                                 i++;
    177                                         }
    178                                         if (i < result.length())
    179                                                 result = result.substring(0, i);
    180                                 }
    181                         }
    182                 }
    183                 return result == null ? null : !singleUser ? result : atStart ? result + ": " : result + " ";
    184         }
    185 
    186         /**
    187         * This is implementation of a "temporary layer". It paints circles
    188         * for all users nearby.
    189         */
    190         @Override
    191         public void paint(Graphics2D g, MapView mv, Bounds bbox) {
    192                 Graphics2D g2d = (Graphics2D) g.create();
    193                 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    194                 Composite ac04 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f);
    195                 Composite ac10 = g2d.getComposite();
    196 
    197                 Font font = g2d.getFont().deriveFont(Font.BOLD, g2d.getFont().getSize2D() + 2.0f);
    198                 g2d.setFont(font);
    199                 FontMetrics fm = g2d.getFontMetrics();
    200 
    201                 for (String user : users.keySet()) {
    202                         int stringWidth = fm.stringWidth(user);
    203                         int radius = stringWidth / 2 + 10;
    204                         Point p = mv.getPoint(users.get(user));
    205 
    206                         g2d.setComposite(ac04);
    207                         g2d.setColor(Color.white);
    208                         g2d.fillOval(p.x - radius, p.y - radius, radius * 2 + 1, radius * 2 + 1);
    209 
    210                         g2d.setComposite(ac10);
    211                         g2d.setColor(Color.black);
    212                         g2d.drawString(user, p.x - stringWidth / 2, p.y + fm.getDescent());
    213                 }
    214         }
    215 
    216         /* ================== Notifications in the title ======================= */
    217 
    218         /**
    219         * Display number of users and notifications in the panel title.
    220         */
    221         protected void updateTitleAlarm() {
    222                 int alarmLevel = connection.isLoggedIn() ? chatPanes.getNotifyLevel() : 0;
    223                 if (!isDialogInCollapsedView() && alarmLevel > 1)
    224                         alarmLevel = 1;
    225 
    226                 String comment;
    227                 if (connection.isLoggedIn()) {
    228                         comment = trn("{0} user", "{0} users", users.size() + 1, users.size() + 1);
    229                 } else {
    230                         comment = tr("not logged in");
    231                 }
    232 
    233                 String title = tr("GeoChat");
    234                 if (comment != null)
    235                         title = title + " (" + comment + ")";
    236                 final String alarm = (alarmLevel <= 0 ? "" : alarmLevel == 1 ? "* " : "!!! ") + title;
    237                 GuiHelper.runInEDT(new Runnable() {
    238                         @Override
    239                         public void run() {
    240                                 setTitle(alarm);
    241                         }
    242                 });
    243         }
    244 
    245         /**
    246         * Track panel collapse events.
    247         */
    248         @Override
    249         protected void setIsCollapsed(boolean val) {
    250                 super.setIsCollapsed(val);
    251                 chatPanes.setCollapsed(val);
    252                 updateTitleAlarm();
    253         }
    254 
    255         /* ============ ChatServerConnectionListener methods ============= */
    256 
    257         @Override
    258         public void loggedIn(String userName) {
    259                 Main.pref.put("geochat.username", userName);
    260                 if (gcPanel.getComponentCount() == 1) {
    261                         GuiHelper.runInEDTAndWait(new Runnable() {
    262                                 @Override
    263                                 public void run() {
    264                                         gcPanel.remove(0);
    265                                         gcPanel.add(tabs, BorderLayout.CENTER);
    266                                         gcPanel.add(input, BorderLayout.SOUTH);
    267                                 }
    268                         });
    269                 }
    270                 updateTitleAlarm();
    271         }
    272 
    273         @Override
    274         public void notLoggedIn(final String reason) {
    275                 if (reason != null) {
    276                         GuiHelper.runInEDT(new Runnable() {
    277                                 @Override
    278                                 public void run() {
    279                                         new Notification(tr("Failed to log in to GeoChat:") + "\n" + reason).show();
    280                                 }
    281                         });
    282                 } else {
    283                         // regular logout
    284                         if (gcPanel.getComponentCount() > 1) {
    285                                 gcPanel.removeAll();
    286                                 gcPanel.add(loginPanel, BorderLayout.CENTER);
    287                         }
    288                 }
    289                 updateTitleAlarm();
    290         }
    291 
    292         @Override
    293         public void messageSendFailed(final String reason) {
    294                 GuiHelper.runInEDT(new Runnable() {
    295                         @Override
    296                         public void run() {
    297                                 new Notification(tr("Failed to send message:") + "\n" + reason).show();
    298                         }
    299                 });
    300         }
    301 
    302         @Override
    303         public void statusChanged(boolean active) {
    304                 // only the public tab, because private chats don't rely on coordinates
    305                 tabs.setComponentAt(0, active ? chatPanes.getPublicChatComponent() : noData);
    306                 repaint();
    307         }
    308 
    309         @Override
    310         public void updateUsers(Map<String, LatLon> newUsers) {
    311                 for (String uname : this.users.keySet()) {
    312                         if (!newUsers.containsKey(uname))
    313                                 chatPanes.addLineToPublic(tr("User {0} has left", uname), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
    314                 }
    315                 for (String uname : newUsers.keySet()) {
    316                         if (!this.users.containsKey(uname))
    317                                 chatPanes.addLineToPublic(tr("User {0} is mapping nearby", uname), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
    318                 }
    319                 this.users = newUsers;
    320                 updateTitleAlarm();
    321                 if (userLayerActive && MainApplication.isDisplayingMapView())
    322                         MainApplication.getMap().mapView.repaint();
    323         }
    324 
    325         private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm");
    326 
    327         private void formatMessage(StringBuilder sb, ChatMessage msg) {
    328                 sb.append("\n");
    329                 sb.append('[').append(TIME_FORMAT.format(msg.getTime())).append("] ");
    330                 sb.append(msg.getAuthor()).append(": ").append(msg.getMessage());
    331         }
    332 
    333         @Override
    334         public void receivedMessages(boolean replace, List<ChatMessage> messages) {
    335                 if (replace)
    336                         chatPanes.clearPublicChatPane();
    337                 if (!messages.isEmpty()) {
    338                         int alarm = 0;
    339                         StringBuilder sb = new StringBuilder();
    340                         for (ChatMessage msg : messages) {
    341                                 boolean important = msg.isIncoming() && containsName(msg.getMessage());
    342                                 if (msg.isIncoming() && alarm < 2) {
    343                                         alarm = important ? 2 : 1;
    344                                 }
    345                                 if (important) {
    346                                         // add buffer, then add current line with italic, then clear buffer
    347                                         chatPanes.addLineToPublic(sb.toString());
    348                                         sb.setLength(0);
    349                                         formatMessage(sb, msg);
    350                                         chatPanes.addLineToPublic(sb.toString(), ChatPaneManager.MESSAGE_TYPE_ATTENTION);
    351                                         sb.setLength(0);
    352                                 } else
    353                                         formatMessage(sb, msg);
    354                         }
    355                         chatPanes.addLineToPublic(sb.toString());
    356                         if (alarm > 0)
    357                                 chatPanes.notify(null, alarm);
    358                 }
    359                 if (replace)
    360                         showNearbyUsers();
    361         }
    362 
    363         private void showNearbyUsers() {
    364                 if (!users.isEmpty()) {
    365                         StringBuilder sb = new StringBuilder(tr("Users mapping nearby:"));
    366                         boolean first = true;
    367                         for (String user : users.keySet()) {
    368                                 sb.append(first ? " " : ", ");
    369                                 sb.append(user);
    370                         }
    371                         chatPanes.addLineToPublic(sb.toString(), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
    372                 }
    373         }
    374 
    375         private boolean containsName(String message) {
    376                 String userName = connection.getUserName();
    377                 int length = userName.length();
    378                 int i = message.indexOf(userName);
    379                 while (i >= 0) {
    380                         if ((i == 0 || !Character.isJavaIdentifierPart(message.charAt(i - 1)))
    381                                         && (i + length >= message.length() || !Character.isJavaIdentifierPart(message.charAt(i + length))))
    382                                 return true;
    383                         i = message.indexOf(userName, i + 1);
    384                 }
    385                 return false;
    386         }
    387 
    388         @Override
    389         public void receivedPrivateMessages(boolean replace, List<ChatMessage> messages) {
    390                 if (replace)
    391                         chatPanes.closePrivateChatPanes();
    392                 for (ChatMessage msg : messages) {
    393                         StringBuilder sb = new StringBuilder();
    394                         formatMessage(sb, msg);
    395                         chatPanes.addLineToChatPane(msg.isIncoming() ? msg.getAuthor() : msg.getRecipient(), sb.toString());
    396                         if (msg.isIncoming())
    397                                 chatPanes.notify(msg.getAuthor(), 2);
    398                 }
    399         }
     55    private JTextField input;
     56    private JTabbedPane tabs;
     57    private JComponent noData;
     58    private JPanel loginPanel;
     59    private JPanel gcPanel;
     60    private ChatServerConnection connection;
     61    // those fields should be visible to popup menu actions
     62    Map<String, LatLon> users;
     63    ChatPaneManager chatPanes;
     64    boolean userLayerActive;
     65
     66    public GeoChatPanel() {
     67        super(tr("GeoChat"), "geochat", tr("Open GeoChat panel"), null, 200, true);
     68
     69        noData = new JLabel(tr("Zoom in to see messages"), SwingConstants.CENTER);
     70
     71        tabs = new JTabbedPane();
     72        tabs.addMouseListener(new GeoChatPopupAdapter(this));
     73        chatPanes = new ChatPaneManager(this, tabs);
     74
     75        input = new JPanelTextField() {
     76            @Override
     77            protected void processEnter(String text) {
     78                connection.postMessage(text, chatPanes.getRecipient());
     79            }
     80
     81            @Override
     82            protected String autoComplete(String word, boolean atStart) {
     83                return autoCompleteUser(word, atStart);
     84            }
     85        };
     86
     87        String defaultUserName = constructUserName();
     88        loginPanel = createLoginPanel(defaultUserName);
     89
     90        gcPanel = new JPanel(new BorderLayout());
     91        gcPanel.add(loginPanel, BorderLayout.CENTER);
     92        createLayout(gcPanel, false, null);
     93
     94        users = new TreeMap<>();
     95        // Start threads
     96        connection = ChatServerConnection.getInstance();
     97        connection.addListener(this);
     98        boolean autoLogin = Main.pref.get("geochat.username", null) == null ? false : Main.pref.getBoolean("geochat.autologin", true);
     99        connection.autoLoginWithDelay(autoLogin ? defaultUserName : null);
     100        updateTitleAlarm();
     101    }
     102
     103    private String constructUserName() {
     104        String userName = Main.pref.get("geochat.username", null); // so the default is null
     105        if (userName == null)
     106            userName = UserIdentityManager.getInstance().getUserName();
     107        if (userName == null)
     108            userName = "";
     109        if (userName.contains("@"))
     110            userName = userName.substring(0, userName.indexOf('@'));
     111        userName = userName.replace(' ', '_');
     112        return userName;
     113    }
     114
     115    private JPanel createLoginPanel(String defaultUserName) {
     116        final JTextField nameField = new JPanelTextField() {
     117            @Override
     118            protected void processEnter(String text) {
     119                connection.login(text);
     120            }
     121        };
     122        nameField.setText(defaultUserName);
     123
     124        JButton loginButton = new JButton(tr("Login"));
     125        loginButton.addActionListener(new ActionListener() {
     126            @Override
     127            public void actionPerformed(ActionEvent e) {
     128                connection.login(nameField.getText());
     129            }
     130        });
     131        nameField.setPreferredSize(new Dimension(nameField.getPreferredSize().width, loginButton.getPreferredSize().height));
     132
     133        final JCheckBox autoLoginBox = new JCheckBox(tr("Enable autologin"), Main.pref.getBoolean("geochat.autologin", true));
     134        autoLoginBox.addActionListener(new ActionListener() {
     135            @Override
     136            public void actionPerformed(ActionEvent e) {
     137                Main.pref.putBoolean("geochat.autologin", autoLoginBox.isSelected());
     138            }
     139        });
     140
     141        JPanel panel = new JPanel(new GridBagLayout());
     142        panel.add(nameField, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(15, 0, 5, 0));
     143        panel.add(loginButton, GBC.eol().fill(GridBagConstraints.NONE).insets(0, 0, 15, 0));
     144        panel.add(autoLoginBox, GBC.std().insets(15, 0, 15, 0));
     145        return panel;
     146    }
     147
     148    protected void logout() {
     149        connection.logout();
     150    }
     151
     152    @Override
     153    public void destroy() {
     154        try {
     155            if (Main.pref.getBoolean("geochat.logout.on.close", true)) {
     156                connection.removeListener(this);
     157                connection.bruteLogout();
     158            }
     159        } catch (IOException e) {
     160            Logging.warn("Failed to logout from geochat server: " + e.getMessage());
     161        }
     162        super.destroy();
     163    }
     164
     165    private String autoCompleteUser(String word, boolean atStart) {
     166        String result = null;
     167        boolean singleUser = true;
     168        for (String user : users.keySet()) {
     169            if (user.startsWith(word)) {
     170                if (result == null)
     171                    result = user;
     172                else {
     173                    singleUser = false;
     174                    int i = word.length();
     175                    while (i < result.length() && i < user.length() && result.charAt(i) == user.charAt(i)) {
     176                        i++;
     177                    }
     178                    if (i < result.length())
     179                        result = result.substring(0, i);
     180                }
     181            }
     182        }
     183        return result == null ? null : !singleUser ? result : atStart ? result + ": " : result + " ";
     184    }
     185
     186    /**
     187    * This is implementation of a "temporary layer". It paints circles
     188    * for all users nearby.
     189    */
     190    @Override
     191    public void paint(Graphics2D g, MapView mv, Bounds bbox) {
     192        Graphics2D g2d = (Graphics2D) g.create();
     193        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     194        Composite ac04 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f);
     195        Composite ac10 = g2d.getComposite();
     196
     197        Font font = g2d.getFont().deriveFont(Font.BOLD, g2d.getFont().getSize2D() + 2.0f);
     198        g2d.setFont(font);
     199        FontMetrics fm = g2d.getFontMetrics();
     200
     201        for (String user : users.keySet()) {
     202            int stringWidth = fm.stringWidth(user);
     203            int radius = stringWidth / 2 + 10;
     204            Point p = mv.getPoint(users.get(user));
     205
     206            g2d.setComposite(ac04);
     207            g2d.setColor(Color.white);
     208            g2d.fillOval(p.x - radius, p.y - radius, radius * 2 + 1, radius * 2 + 1);
     209
     210            g2d.setComposite(ac10);
     211            g2d.setColor(Color.black);
     212            g2d.drawString(user, p.x - stringWidth / 2, p.y + fm.getDescent());
     213        }
     214    }
     215
     216    /* ================== Notifications in the title ======================= */
     217
     218    /**
     219    * Display number of users and notifications in the panel title.
     220    */
     221    protected void updateTitleAlarm() {
     222        int alarmLevel = connection.isLoggedIn() ? chatPanes.getNotifyLevel() : 0;
     223        if (!isDialogInCollapsedView() && alarmLevel > 1)
     224            alarmLevel = 1;
     225
     226        String comment;
     227        if (connection.isLoggedIn()) {
     228            comment = trn("{0} user", "{0} users", users.size() + 1, users.size() + 1);
     229        } else {
     230            comment = tr("not logged in");
     231        }
     232
     233        String title = tr("GeoChat");
     234        if (comment != null)
     235            title = title + " (" + comment + ")";
     236        final String alarm = (alarmLevel <= 0 ? "" : alarmLevel == 1 ? "* " : "!!! ") + title;
     237        GuiHelper.runInEDT(new Runnable() {
     238            @Override
     239            public void run() {
     240                setTitle(alarm);
     241            }
     242        });
     243    }
     244
     245    /**
     246    * Track panel collapse events.
     247    */
     248    @Override
     249    protected void setIsCollapsed(boolean val) {
     250        super.setIsCollapsed(val);
     251        chatPanes.setCollapsed(val);
     252        updateTitleAlarm();
     253    }
     254
     255    /* ============ ChatServerConnectionListener methods ============= */
     256
     257    @Override
     258    public void loggedIn(String userName) {
     259        Main.pref.put("geochat.username", userName);
     260        if (gcPanel.getComponentCount() == 1) {
     261            GuiHelper.runInEDTAndWait(new Runnable() {
     262                @Override
     263                public void run() {
     264                    gcPanel.remove(0);
     265                    gcPanel.add(tabs, BorderLayout.CENTER);
     266                    gcPanel.add(input, BorderLayout.SOUTH);
     267                }
     268            });
     269        }
     270        updateTitleAlarm();
     271    }
     272
     273    @Override
     274    public void notLoggedIn(final String reason) {
     275        if (reason != null) {
     276            GuiHelper.runInEDT(new Runnable() {
     277                @Override
     278                public void run() {
     279                    new Notification(tr("Failed to log in to GeoChat:") + "\n" + reason).show();
     280                }
     281            });
     282        } else {
     283            // regular logout
     284            if (gcPanel.getComponentCount() > 1) {
     285                gcPanel.removeAll();
     286                gcPanel.add(loginPanel, BorderLayout.CENTER);
     287            }
     288        }
     289        updateTitleAlarm();
     290    }
     291
     292    @Override
     293    public void messageSendFailed(final String reason) {
     294        GuiHelper.runInEDT(new Runnable() {
     295            @Override
     296            public void run() {
     297                new Notification(tr("Failed to send message:") + "\n" + reason).show();
     298            }
     299        });
     300    }
     301
     302    @Override
     303    public void statusChanged(boolean active) {
     304        // only the public tab, because private chats don't rely on coordinates
     305        tabs.setComponentAt(0, active ? chatPanes.getPublicChatComponent() : noData);
     306        repaint();
     307    }
     308
     309    @Override
     310    public void updateUsers(Map<String, LatLon> newUsers) {
     311        for (String uname : this.users.keySet()) {
     312            if (!newUsers.containsKey(uname))
     313                chatPanes.addLineToPublic(tr("User {0} has left", uname), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
     314        }
     315        for (String uname : newUsers.keySet()) {
     316            if (!this.users.containsKey(uname))
     317                chatPanes.addLineToPublic(tr("User {0} is mapping nearby", uname), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
     318        }
     319        this.users = newUsers;
     320        updateTitleAlarm();
     321        if (userLayerActive && MainApplication.isDisplayingMapView())
     322            MainApplication.getMap().mapView.repaint();
     323    }
     324
     325    private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm");
     326
     327    private void formatMessage(StringBuilder sb, ChatMessage msg) {
     328        sb.append("\n");
     329        sb.append('[').append(TIME_FORMAT.format(msg.getTime())).append("] ");
     330        sb.append(msg.getAuthor()).append(": ").append(msg.getMessage());
     331    }
     332
     333    @Override
     334    public void receivedMessages(boolean replace, List<ChatMessage> messages) {
     335        if (replace)
     336            chatPanes.clearPublicChatPane();
     337        if (!messages.isEmpty()) {
     338            int alarm = 0;
     339            StringBuilder sb = new StringBuilder();
     340            for (ChatMessage msg : messages) {
     341                boolean important = msg.isIncoming() && containsName(msg.getMessage());
     342                if (msg.isIncoming() && alarm < 2) {
     343                    alarm = important ? 2 : 1;
     344                }
     345                if (important) {
     346                    // add buffer, then add current line with italic, then clear buffer
     347                    chatPanes.addLineToPublic(sb.toString());
     348                    sb.setLength(0);
     349                    formatMessage(sb, msg);
     350                    chatPanes.addLineToPublic(sb.toString(), ChatPaneManager.MESSAGE_TYPE_ATTENTION);
     351                    sb.setLength(0);
     352                } else
     353                    formatMessage(sb, msg);
     354            }
     355            chatPanes.addLineToPublic(sb.toString());
     356            if (alarm > 0)
     357                chatPanes.notify(null, alarm);
     358        }
     359        if (replace)
     360            showNearbyUsers();
     361    }
     362
     363    private void showNearbyUsers() {
     364        if (!users.isEmpty()) {
     365            StringBuilder sb = new StringBuilder(tr("Users mapping nearby:"));
     366            boolean first = true;
     367            for (String user : users.keySet()) {
     368                sb.append(first ? " " : ", ");
     369                sb.append(user);
     370            }
     371            chatPanes.addLineToPublic(sb.toString(), ChatPaneManager.MESSAGE_TYPE_INFORMATION);
     372        }
     373    }
     374
     375    private boolean containsName(String message) {
     376        String userName = connection.getUserName();
     377        int length = userName.length();
     378        int i = message.indexOf(userName);
     379        while (i >= 0) {
     380            if ((i == 0 || !Character.isJavaIdentifierPart(message.charAt(i - 1)))
     381                    && (i + length >= message.length() || !Character.isJavaIdentifierPart(message.charAt(i + length))))
     382                return true;
     383            i = message.indexOf(userName, i + 1);
     384        }
     385        return false;
     386    }
     387
     388    @Override
     389    public void receivedPrivateMessages(boolean replace, List<ChatMessage> messages) {
     390        if (replace)
     391            chatPanes.closePrivateChatPanes();
     392        for (ChatMessage msg : messages) {
     393            StringBuilder sb = new StringBuilder();
     394            formatMessage(sb, msg);
     395            chatPanes.addLineToChatPane(msg.isIncoming() ? msg.getAuthor() : msg.getRecipient(), sb.toString());
     396            if (msg.isIncoming())
     397                chatPanes.notify(msg.getAuthor(), 2);
     398        }
     399    }
    400400}
Note: See TracChangeset for help on using the changeset viewer.