source: josm/trunk/src/org/openstreetmap/josm/gui/MapStatus.java@ 1861

Last change on this file since 1861 was 1814, checked in by Gubaer, 15 years ago

removed dependencies to Main.ds, removed Main.ds
removed AddVisitor, NameVisitor, DeleteVisitor - unnecessary double dispatching for these simple cases

  • Property svn:eol-style set to native
File size: 15.3 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui;
4
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.AWTEvent;
8import java.awt.Color;
9import java.awt.Cursor;
10import java.awt.Dimension;
11import java.awt.EventQueue;
12import java.awt.Font;
13import java.awt.GridBagLayout;
14import java.awt.Point;
15import java.awt.Toolkit;
16import java.awt.event.AWTEventListener;
17import java.awt.event.ComponentEvent;
18import java.awt.event.InputEvent;
19import java.awt.event.KeyAdapter;
20import java.awt.event.KeyEvent;
21import java.awt.event.MouseAdapter;
22import java.awt.event.MouseEvent;
23import java.awt.event.MouseListener;
24import java.awt.event.MouseMotionListener;
25import java.lang.reflect.InvocationTargetException;
26import java.util.Collection;
27import java.util.ConcurrentModificationException;
28import java.util.Map.Entry;
29
30import javax.swing.BorderFactory;
31import javax.swing.JLabel;
32import javax.swing.JPanel;
33import javax.swing.JTextField;
34import javax.swing.Popup;
35import javax.swing.PopupFactory;
36
37import org.openstreetmap.josm.Main;
38import org.openstreetmap.josm.actions.HelpAction.Helpful;
39import org.openstreetmap.josm.data.coor.LatLon;
40import org.openstreetmap.josm.data.coor.LatLon.CoordinateFormat;
41import org.openstreetmap.josm.data.osm.Node;
42import org.openstreetmap.josm.data.osm.OsmPrimitive;
43import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
44import org.openstreetmap.josm.tools.GBC;
45import org.openstreetmap.josm.tools.ImageProvider;
46
47/**
48 * A component that manages some status information display about the map.
49 * It keeps a status line below the map up to date and displays some tooltip
50 * information if the user hold the mouse long enough at some point.
51 *
52 * All this is done in background to not disturb other processes.
53 *
54 * The background thread does not alter any data of the map (read only thread).
55 * Also it is rather fail safe. In case of some error in the data, it just does
56 * nothing instead of whining and complaining.
57 *
58 * @author imi
59 */
60public class MapStatus extends JPanel implements Helpful {
61 private static final PrimitiveNameFormatter NAME_FORMATTER = new PrimitiveNameFormatter();
62
63 /**
64 * The MapView this status belongs to.
65 */
66 final MapView mv;
67
68 /**
69 * A small user interface component that consists of an image label and
70 * a fixed text content to the right of the image.
71 */
72 class ImageLabel extends JPanel {
73 private JLabel tf;
74 private int chars;
75 public ImageLabel(String img, String tooltip, int chars) {
76 super();
77 setLayout(new GridBagLayout());
78 setBackground(Color.decode("#b8cfe5"));
79 add(new JLabel(ImageProvider.get("statusline/"+img+".png")), GBC.std().anchor(GBC.WEST).insets(0,1,1,0));
80 add(tf = new JLabel(), GBC.std().fill(GBC.BOTH).anchor(GBC.WEST).insets(2,1,1,0));
81 setToolTipText(tooltip);
82 this.chars = chars;
83 }
84 public void setText(String t) {
85 tf.setText(t);
86 }
87 @Override public Dimension getPreferredSize() {
88 return new Dimension(25 + chars*tf.getFontMetrics(tf.getFont()).charWidth('0'), super.getPreferredSize().height);
89 }
90 @Override public Dimension getMinimumSize() {
91 return new Dimension(25 + chars*tf.getFontMetrics(tf.getFont()).charWidth('0'), super.getMinimumSize().height);
92 }
93 }
94
95 ImageLabel lonText = new ImageLabel("lon", tr("The geographic longitude at the mouse pointer."), 11);
96 ImageLabel nameText = new ImageLabel("name", tr("The name of the object at the mouse pointer."), 20);
97 JTextField helpText = new JTextField();
98 ImageLabel latText = new ImageLabel("lat", tr("The geographic latitude at the mouse pointer."), 10);
99 ImageLabel angleText = new ImageLabel("angle", tr("The angle between the previous and the current way segment."), 6);
100 ImageLabel headingText = new ImageLabel("heading", tr("The (compass) heading of the line segment being drawn."), 6);
101 ImageLabel distText = new ImageLabel("dist", tr("The length of the new way segment being drawn."), 8);
102
103 /**
104 * This is the thread that runs in the background and collects the information displayed.
105 * It gets destroyed by MapFrame.java/destroy() when the MapFrame itself is destroyed.
106 */
107 public Thread thread;
108
109 /**
110 * The collector class that waits for notification and then update
111 * the display objects.
112 *
113 * @author imi
114 */
115 private final class Collector implements Runnable {
116 /**
117 * The last object displayed in status line.
118 */
119 Collection<OsmPrimitive> osmStatus;
120 /**
121 * The old modifiers that was pressed the last time this collector ran.
122 */
123 private int oldModifiers;
124 /**
125 * The popup displayed to show additional information
126 */
127 private Popup popup;
128
129 private MapFrame parent;
130
131 public Collector(MapFrame parent) {
132 this.parent = parent;
133 }
134
135 /**
136 * Execution function for the Collector.
137 */
138 public void run() {
139 for (;;) {
140 MouseState ms = new MouseState();
141 synchronized (this) {
142 try {wait();} catch (InterruptedException e) {}
143 ms.modifiers = mouseState.modifiers;
144 ms.mousePos = mouseState.mousePos;
145 }
146 if (parent != Main.map)
147 return; // exit, if new parent.
148 if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0 || ms.mousePos == null) {
149 continue; // freeze display when holding down ctrl
150 }
151
152 if (mv.center == null) {
153 continue;
154 }
155
156 // This try/catch is a hack to stop the flooding bug reports about this.
157 // The exception needed to handle with in the first place, means that this
158 // access to the data need to be restarted, if the main thread modifies
159 // the data.
160 try {
161 OsmPrimitive osmNearest = null;
162 // Set the text label in the bottom status bar
163 osmNearest = mv.getNearest(ms.mousePos);
164 if (osmNearest != null) {
165 nameText.setText(NAME_FORMATTER.getName(osmNearest));
166 } else {
167 nameText.setText(tr("(no object)"));
168 }
169
170 // Popup Information
171 if ((ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0 ) {
172 Collection<OsmPrimitive> osms = mv.getAllNearest(ms.mousePos);
173
174 if (osms == null) {
175 continue;
176 }
177 if (osms != null && osms.equals(osmStatus) && ms.modifiers == oldModifiers) {
178 continue;
179 }
180
181 if (popup != null) {
182 try {
183 EventQueue.invokeAndWait(new Runnable() {
184 public void run() {
185 popup.hide();
186 }
187 });
188 } catch (InterruptedException e) {
189 } catch (InvocationTargetException e) {
190 throw new RuntimeException(e);
191 }
192 }
193
194 JPanel c = new JPanel(new GridBagLayout());
195 for (final OsmPrimitive osm : osms) {
196 final StringBuilder text = new StringBuilder();
197 String name = NAME_FORMATTER.getName(osm);
198 if (osm.id == 0 || osm.modified) {
199 name = "<i><b>"+ new PrimitiveNameFormatter().getName(osm)+"*</b></i>";
200 }
201 text.append(name);
202 if (osm.id != 0) {
203 text.append("<br>id="+osm.id);
204 }
205 for (Entry<String, String> e : osm.entrySet()) {
206 text.append("<br>"+e.getKey()+"="+e.getValue());
207 }
208 final JLabel l = new JLabel(
209 "<html>"+text.toString()+"</html>",
210 ImageProvider.get(OsmPrimitiveType.from(osm)),
211 JLabel.HORIZONTAL
212 );
213 l.setFont(l.getFont().deriveFont(Font.PLAIN));
214 l.setVerticalTextPosition(JLabel.TOP);
215 l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
216 l.addMouseListener(new MouseAdapter(){
217 @Override public void mouseEntered(MouseEvent e) {
218 l.setText("<html><u color='blue'>"+text.toString()+"</u></html>");
219 }
220 @Override public void mouseExited(MouseEvent e) {
221 l.setText("<html>"+text.toString()+"</html>");
222 }
223 @Override public void mouseClicked(MouseEvent e) {
224 Main.main.getCurrentDataSet().setSelected(osm);
225 mv.repaint();
226 }
227 });
228 c.add(l, GBC.eol());
229 }
230
231 Point p = mv.getLocationOnScreen();
232 popup = PopupFactory.getSharedInstance().getPopup(mv, c, p.x+ms.mousePos.x+16, p.y+ms.mousePos.y+16);
233 final Popup staticPopup = popup;
234 EventQueue.invokeLater(new Runnable(){
235 public void run() {
236 staticPopup.show();
237 }
238 });
239 } else if (popup != null) {
240 final Popup staticPopup = popup;
241 popup = null;
242 EventQueue.invokeLater(new Runnable(){
243 public void run() {
244 staticPopup.hide();
245 }
246 });
247 }
248 } catch (ConcurrentModificationException x) {
249 } catch (NullPointerException x) {
250 }
251 }
252 }
253 }
254
255 /**
256 * Everything, the collector is interested of. Access must be synchronized.
257 * @author imi
258 */
259 class MouseState {
260 Point mousePos;
261 int modifiers;
262 }
263 /**
264 * The last sent mouse movement event.
265 */
266 MouseState mouseState = new MouseState();
267
268 /**
269 * Construct a new MapStatus and attach it to the map view.
270 * @param mapFrame The MapFrame the status line is part of.
271 */
272 public MapStatus(final MapFrame mapFrame) {
273 this.mv = mapFrame.mapView;
274
275 // Listen for mouse movements and set the position text field
276 mv.addMouseMotionListener(new MouseMotionListener(){
277 public void mouseDragged(MouseEvent e) {
278 mouseMoved(e);
279 }
280 public void mouseMoved(MouseEvent e) {
281 if (mv.center == null)
282 return;
283 // Do not update the view if ctrl is pressed.
284 if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == 0) {
285 CoordinateFormat mCord = Node.getCoordinateFormat();
286 LatLon p = mv.getLatLon(e.getX(),e.getY());
287 latText.setText(p.latToString(mCord));
288 lonText.setText(p.lonToString(mCord));
289 }
290 }
291 });
292
293 setLayout(new GridBagLayout());
294 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
295
296 add(latText, GBC.std());
297 add(lonText, GBC.std().insets(3,0,0,0));
298 add(headingText, GBC.std().insets(3,0,0,0));
299 add(angleText, GBC.std().insets(3,0,0,0));
300 add(distText, GBC.std().insets(3,0,0,0));
301
302 helpText.setEditable(false);
303 add(nameText, GBC.std().insets(3,0,0,0));
304 add(helpText, GBC.eol().insets(3,0,0,0).fill(GBC.HORIZONTAL));
305
306 // The background thread
307 final Collector collector = new Collector(mapFrame);
308 thread = new Thread(collector, "Map Status Collector");
309 thread.setDaemon(true);
310 thread.start();
311
312 // Listen to keyboard/mouse events for pressing/releasing alt key and
313 // inform the collector.
314 try {
315 Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener(){
316 public void eventDispatched(AWTEvent event) {
317 if (event instanceof ComponentEvent &&
318 ((ComponentEvent)event).getComponent() == mapFrame.mapView) {
319 synchronized (collector) {
320 mouseState.modifiers = ((InputEvent)event).getModifiersEx();
321 if (event instanceof MouseEvent) {
322 mouseState.mousePos = ((MouseEvent)event).getPoint();
323 }
324 collector.notify();
325 }
326 }
327 }
328 }, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
329 } catch (SecurityException ex) {
330 mapFrame.mapView.addMouseMotionListener(new MouseMotionListener() {
331 public void mouseMoved(MouseEvent e) {
332 synchronized (collector) {
333 mouseState.modifiers = e.getModifiersEx();
334 mouseState.mousePos = e.getPoint();
335 collector.notify();
336 }
337 }
338
339 public void mouseDragged(MouseEvent e) {
340 mouseMoved(e);
341 }
342 });
343
344 mapFrame.mapView.addKeyListener(new KeyAdapter() {
345 @Override public void keyPressed(KeyEvent e) {
346 synchronized (collector) {
347 mouseState.modifiers = e.getModifiersEx();
348 collector.notify();
349 }
350 }
351
352 @Override public void keyReleased(KeyEvent e) {
353 keyPressed(e);
354 }
355 });
356 }
357 }
358
359 public String helpTopic() {
360 return "Statusline";
361 }
362
363 @Override
364 public void addMouseListener(MouseListener ml) {
365 //super.addMouseListener(ml);
366 lonText.addMouseListener(ml);
367 latText.addMouseListener(ml);
368 }
369
370 public void setHelpText(String t) {
371 helpText.setText(t);
372 helpText.setToolTipText(t);
373 }
374 public void setAngle(double a) {
375 angleText.setText(a < 0 ? "--" : Math.round(a*10)/10.0 + " °");
376 }
377 public void setHeading(double h) {
378 headingText.setText(h < 0 ? "--" : Math.round(h*10)/10.0 + " °");
379 }
380 public void setDist(double dist) {
381 String text = dist > 1000 ? (Math.round(dist/100)/10.0)+" km" : Math.round(dist*10)/10.0 +" m";
382 distText.setText(dist < 0 ? "--" : text);
383 }
384}
Note: See TracBrowser for help on using the repository browser.