source: osm/applications/editors/josm/plugins/geochat/src/geochat/GeoChatPanel.java@ 33796

Last change on this file since 33796 was 33796, checked in by donvip, 7 years ago

update to JOSM 12840

File size: 14.2 KB
Line 
1// License: WTFPL. For details, see LICENSE file.
2package geochat;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.AlphaComposite;
8import java.awt.BorderLayout;
9import java.awt.Color;
10import java.awt.Composite;
11import java.awt.Dimension;
12import java.awt.Font;
13import java.awt.FontMetrics;
14import java.awt.Graphics2D;
15import java.awt.GridBagConstraints;
16import java.awt.GridBagLayout;
17import java.awt.Point;
18import java.awt.RenderingHints;
19import java.awt.event.ActionEvent;
20import java.awt.event.ActionListener;
21import java.io.IOException;
22import java.text.SimpleDateFormat;
23import java.util.List;
24import java.util.Map;
25import java.util.TreeMap;
26
27import javax.swing.JButton;
28import javax.swing.JCheckBox;
29import javax.swing.JComponent;
30import javax.swing.JLabel;
31import javax.swing.JPanel;
32import javax.swing.JTabbedPane;
33import javax.swing.JTextField;
34import javax.swing.SwingConstants;
35
36import org.openstreetmap.josm.Main;
37import org.openstreetmap.josm.data.Bounds;
38import org.openstreetmap.josm.data.UserIdentityManager;
39import org.openstreetmap.josm.data.coor.LatLon;
40import org.openstreetmap.josm.gui.MainApplication;
41import org.openstreetmap.josm.gui.MapView;
42import org.openstreetmap.josm.gui.Notification;
43import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
44import org.openstreetmap.josm.gui.layer.MapViewPaintable;
45import org.openstreetmap.josm.gui.util.GuiHelper;
46import org.openstreetmap.josm.tools.GBC;
47import org.openstreetmap.josm.tools.Logging;
48
49/**
50 * Chat Panel. Contains of one public chat pane and multiple private ones.
51 *
52 * @author zverik
53 */
54public 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.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 }
400}
Note: See TracBrowser for help on using the repository browser.