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

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

removed usage of tab stops

  • Property svn:eol-style set to native
File size: 14.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.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 int chars;
71 public ImageLabel(String img, String tooltip, int chars) {
72 super();
73 setLayout(new GridBagLayout());
74 setBackground(Color.decode("#b8cfe5"));
75 add(new JLabel(ImageProvider.get("statusline/"+img+".png")), GBC.std().anchor(GBC.WEST).insets(0,1,1,0));
76 add(tf = new JLabel(), GBC.std().fill(GBC.BOTH).anchor(GBC.WEST).insets(2,1,1,0));
77 setToolTipText(tooltip);
78 this.chars = chars;
79 }
80 public void setText(String t) {
81 tf.setText(t);
82 }
83 @Override public Dimension getPreferredSize() {
84 return new Dimension(25 + chars*tf.getFontMetrics(tf.getFont()).charWidth('0'), super.getPreferredSize().height);
85 }
86 @Override public Dimension getMinimumSize() {
87 return new Dimension(25 + chars*tf.getFontMetrics(tf.getFont()).charWidth('0'), super.getMinimumSize().height);
88 }
89 }
90
91 LatLon.CoordinateFormat mCord;
92
93 ImageLabel lonText = new ImageLabel("lon", tr("The geographic longitude at the mouse pointer."), 11);
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 geographic latitude at the mouse pointer."), 10);
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 // This try/catch is a hack to stop the flooding bug reports about this.
147 // The exception needed to handle with in the first place, means that this
148 // access to the data need to be restarted, if the main thread modifies
149 // the data.
150 try {
151 OsmPrimitive osmNearest = null;
152 // Set the text label in the bottom status bar
153 osmNearest = mv.getNearest(ms.mousePos);
154 if (osmNearest != null) {
155 NameVisitor visitor = new NameVisitor();
156 osmNearest.visit(visitor);
157 nameText.setText(visitor.name);
158 } else
159 nameText.setText(tr("(no object)"));
160
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 mapFrame The MapFrame the status line is part of.
254 */
255 public MapStatus(final MapFrame mapFrame) {
256 this.mv = mapFrame.mapView;
257
258 try {
259 mCord = LatLon.CoordinateFormat.valueOf(Main.pref.get("coordinates"));
260 } catch (IllegalArgumentException iae) {
261 mCord =LatLon.CoordinateFormat.DECIMAL_DEGREES;
262 }
263 // Listen for mouse movements and set the position text field
264 mv.addMouseMotionListener(new MouseMotionListener(){
265 public void mouseDragged(MouseEvent e) {
266 mouseMoved(e);
267 }
268 public void mouseMoved(MouseEvent e) {
269 if (mv.center == null)
270 return;
271 // Do not update the view if ctrl is pressed.
272 if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == 0) {
273 LatLon p = mv.getLatLon(e.getX(),e.getY());
274 latText.setText(p.latToString(mCord));
275 lonText.setText(p.lonToString(mCord));
276 }
277 }
278 });
279
280 setLayout(new GridBagLayout());
281 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
282
283 add(latText, GBC.std());
284 add(lonText, GBC.std().insets(3,0,0,0));
285 add(headingText, GBC.std().insets(3,0,0,0));
286 add(angleText, GBC.std().insets(3,0,0,0));
287 add(distText, GBC.std().insets(3,0,0,0));
288
289 helpText.setEditable(false);
290 add(nameText, GBC.std().insets(3,0,0,0));
291 add(helpText, GBC.eol().insets(3,0,0,0).fill(GBC.HORIZONTAL));
292
293 // The background thread
294 final Collector collector = new Collector(mapFrame);
295 new Thread(collector).start();
296
297 // Listen to keyboard/mouse events for pressing/releasing alt key and
298 // inform the collector.
299 try {
300 Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener(){
301 public void eventDispatched(AWTEvent event) {
302 synchronized (collector) {
303 mouseState.modifiers = ((InputEvent)event).getModifiersEx();
304 if (event instanceof MouseEvent)
305 mouseState.mousePos = ((MouseEvent)event).getPoint();
306 collector.notify();
307 }
308 }
309 }, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
310 } catch (SecurityException ex) {
311 mapFrame.mapView.addMouseMotionListener(new MouseMotionListener() {
312 public void mouseMoved(MouseEvent e) {
313 synchronized (collector) {
314 mouseState.modifiers = e.getModifiersEx();
315 mouseState.mousePos = e.getPoint();
316 collector.notify();
317 }
318 }
319
320 public void mouseDragged(MouseEvent e) {
321 mouseMoved(e);
322 }
323 });
324
325 mapFrame.mapView.addKeyListener(new KeyAdapter() {
326 @Override public void keyPressed(KeyEvent e) {
327 synchronized (collector) {
328 mouseState.modifiers = e.getModifiersEx();
329 collector.notify();
330 }
331 }
332
333 @Override public void keyReleased(KeyEvent e) {
334 keyPressed(e);
335 }
336 });
337 }
338 }
339
340 public String helpTopic() {
341 return "Statusline";
342 }
343
344 public void setHelpText(String t) {
345 helpText.setText(t);
346 helpText.setToolTipText(t);
347 }
348 public void setAngle(double a) {
349 angleText.setText(a < 0 ? "--" : Math.round(a*10)/10.0 + " °");
350 }
351 public void setHeading(double h) {
352 headingText.setText(h < 0 ? "--" : Math.round(h*10)/10.0 + " °");
353 }
354 public void setDist(double dist) {
355 String text = dist > 1000 ? (Math.round(dist/100)/10.0)+" km" : Math.round(dist*10)/10.0 +" m";
356 distText.setText(dist < 0 ? "--" : text);
357 }
358
359}
Note: See TracBrowser for help on using the repository browser.