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

Last change on this file since 1329 was 1258, checked in by stoecker, 15 years ago

fixed bug #2018. Patch by markb ordern com

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