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

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

Large rework in projection handling - now allows only switching and more specific projections
TODO:

  • allow subprojections (i.e. settings for projections)
  • setup preferences for subprojections
  • better support of the new projection depending world bounds (how to handle valid data outside of world)
  • do not allow to zoom out of the world - zoom should stop when whole world is displayed
  • fix Lambert and SwissGrid to handle new OutOfWorld style and subprojections
  • fix new UTM projection
  • handle layers with fixed projection on projection change
  • allow easier projection switching (e.g. in menu)

NOTE:
This checkin very likely will cause problems. Please report or fix them. Older plugins may have trouble. The SVN plugins
have been fixed but may have problems nevertheless. This is a BIG change, but will make JOSMs internal structure much cleaner
and reduce lots of projection related problems.

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