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

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