[9] | 1 | package org.openstreetmap.josm.gui;
|
---|
| 2 |
|
---|
| 3 | import java.awt.AWTEvent;
|
---|
[39] | 4 | import java.awt.Cursor;
|
---|
[36] | 5 | import java.awt.Font;
|
---|
| 6 | import java.awt.GridBagLayout;
|
---|
[9] | 7 | import java.awt.Point;
|
---|
| 8 | import java.awt.Toolkit;
|
---|
| 9 | import java.awt.event.AWTEventListener;
|
---|
| 10 | import java.awt.event.InputEvent;
|
---|
[39] | 11 | import java.awt.event.MouseAdapter;
|
---|
[9] | 12 | import java.awt.event.MouseEvent;
|
---|
| 13 | import java.awt.event.MouseMotionListener;
|
---|
[17] | 14 | import java.beans.PropertyChangeEvent;
|
---|
| 15 | import java.beans.PropertyChangeListener;
|
---|
[36] | 16 | import java.util.Collection;
|
---|
[51] | 17 | import java.util.ConcurrentModificationException;
|
---|
[9] | 18 | import java.util.Map.Entry;
|
---|
| 19 |
|
---|
| 20 | import javax.swing.BorderFactory;
|
---|
| 21 | import javax.swing.BoxLayout;
|
---|
| 22 | import javax.swing.JLabel;
|
---|
| 23 | import javax.swing.JPanel;
|
---|
| 24 | import javax.swing.JTextField;
|
---|
| 25 | import javax.swing.Popup;
|
---|
| 26 | import javax.swing.PopupFactory;
|
---|
| 27 |
|
---|
[39] | 28 | import org.openstreetmap.josm.Main;
|
---|
[9] | 29 | import org.openstreetmap.josm.data.GeoPoint;
|
---|
| 30 | import org.openstreetmap.josm.data.osm.Key;
|
---|
| 31 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
| 32 | import org.openstreetmap.josm.data.osm.visitor.SelectionComponentVisitor;
|
---|
| 33 |
|
---|
| 34 | /**
|
---|
| 35 | * A component that manages some status information display about the map.
|
---|
| 36 | * It keeps a status line below the map up to date and displays some tooltip
|
---|
| 37 | * information if the user hold the mouse long enough at some point.
|
---|
| 38 | *
|
---|
| 39 | * All this is done in background to not disturb other processes.
|
---|
| 40 | *
|
---|
| 41 | * The background thread does not alter any data of the map (read only thread).
|
---|
| 42 | * Also it is rather fail safe. In case of some error in the data, it just do
|
---|
| 43 | * nothing instead of whining and complaining.
|
---|
| 44 | *
|
---|
| 45 | * @author imi
|
---|
| 46 | */
|
---|
| 47 | public class MapStatus extends JPanel {
|
---|
[56] | 48 |
|
---|
[9] | 49 | /**
|
---|
[16] | 50 | * The MapView this status belongs.
|
---|
[9] | 51 | */
|
---|
[16] | 52 | final MapView mv;
|
---|
[9] | 53 | /**
|
---|
| 54 | * The position of the mouse cursor.
|
---|
| 55 | */
|
---|
[22] | 56 | JTextField positionText = new JTextField("-000.00000000000000 -000.00000000000000".length());
|
---|
[9] | 57 | /**
|
---|
| 58 | * The field holding the name of the object under the mouse.
|
---|
| 59 | */
|
---|
[22] | 60 | JTextField nameText = new JTextField(30);
|
---|
[56] | 61 |
|
---|
[9] | 62 | /**
|
---|
| 63 | * The collector class that waits for notification and then update
|
---|
| 64 | * the display objects.
|
---|
| 65 | *
|
---|
| 66 | * @author imi
|
---|
| 67 | */
|
---|
| 68 | private final class Collector implements Runnable {
|
---|
| 69 | /**
|
---|
| 70 | * The last object displayed in status line.
|
---|
| 71 | */
|
---|
[36] | 72 | Collection<OsmPrimitive> osmStatus;
|
---|
[9] | 73 | /**
|
---|
| 74 | * The old modifiers, that was pressed the last time this collector ran.
|
---|
| 75 | */
|
---|
| 76 | private int oldModifiers;
|
---|
| 77 | /**
|
---|
| 78 | * The popup displayed to show additional information
|
---|
| 79 | */
|
---|
| 80 | private Popup popup;
|
---|
[17] | 81 | /**
|
---|
| 82 | * Signals the collector to shut down on next event.
|
---|
| 83 | */
|
---|
| 84 | boolean exitCollector = false;
|
---|
[56] | 85 |
|
---|
[9] | 86 | /**
|
---|
| 87 | * Execution function for the Collector.
|
---|
| 88 | */
|
---|
| 89 | public void run() {
|
---|
| 90 | for (;;) {
|
---|
| 91 | MouseState ms = new MouseState();
|
---|
| 92 | synchronized (this) {
|
---|
| 93 | try {wait();} catch (InterruptedException e) {}
|
---|
| 94 | ms.modifiers = mouseState.modifiers;
|
---|
| 95 | ms.mousePos = mouseState.mousePos;
|
---|
| 96 | }
|
---|
[17] | 97 | if (exitCollector)
|
---|
| 98 | return;
|
---|
[9] | 99 | if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0 || ms.mousePos == null)
|
---|
| 100 | continue; // freeze display when holding down ctrl
|
---|
[36] | 101 |
|
---|
[51] | 102 | // This try/catch is a hack to stop the flooding bug reports about this.
|
---|
| 103 | // The exception needed to handle with in the first place, means that this
|
---|
| 104 | // access to the data need to be restarted, if the main thread modifies
|
---|
| 105 | // the data.
|
---|
| 106 | try {
|
---|
[56] | 107 | Collection<OsmPrimitive> osms = mv.getAllNearest(ms.mousePos);
|
---|
| 108 |
|
---|
| 109 | if (osms == null && osmStatus == null && ms.modifiers == oldModifiers)
|
---|
| 110 | continue;
|
---|
| 111 | if (osms != null && osms.equals(osmStatus) && ms.modifiers == oldModifiers)
|
---|
| 112 | continue;
|
---|
| 113 |
|
---|
| 114 | osmStatus = osms;
|
---|
| 115 | oldModifiers = ms.modifiers;
|
---|
| 116 |
|
---|
| 117 | OsmPrimitive osmNearest = null;
|
---|
[51] | 118 | // Set the text label in the bottom status bar
|
---|
| 119 | osmNearest = mv.getNearest(ms.mousePos, (ms.modifiers & MouseEvent.ALT_DOWN_MASK) != 0);
|
---|
[56] | 120 | if (osmNearest != null) {
|
---|
| 121 | SelectionComponentVisitor visitor = new SelectionComponentVisitor();
|
---|
| 122 | osmNearest.visit(visitor);
|
---|
| 123 | nameText.setText(visitor.name);
|
---|
| 124 | } else
|
---|
| 125 | nameText.setText("");
|
---|
| 126 |
|
---|
| 127 | // Popup Information
|
---|
| 128 | if ((ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0 && osms != null) {
|
---|
| 129 | if (popup != null)
|
---|
| 130 | popup.hide();
|
---|
| 131 |
|
---|
| 132 | JPanel c = new JPanel(new GridBagLayout());
|
---|
| 133 | for (final OsmPrimitive osm : osms) {
|
---|
| 134 | SelectionComponentVisitor visitor = new SelectionComponentVisitor();
|
---|
| 135 | osm.visit(visitor);
|
---|
| 136 | final StringBuilder text = new StringBuilder();
|
---|
| 137 | if (osm.id == 0 || osm.modified || osm.modifiedProperties)
|
---|
| 138 | visitor.name = "<i><b>"+visitor.name+"*</b></i>";
|
---|
| 139 | text.append(visitor.name);
|
---|
| 140 | if (osm.id != 0)
|
---|
| 141 | text.append("<br>id="+osm.id);
|
---|
| 142 | if (osm.keys != null)
|
---|
| 143 | for (Entry<Key, String> e : osm.keys.entrySet())
|
---|
| 144 | text.append("<br>"+e.getKey().name+"="+e.getValue());
|
---|
| 145 | final JLabel l = new JLabel("<html>"+text.toString()+"</html>", visitor.icon, JLabel.HORIZONTAL);
|
---|
| 146 | l.setFont(l.getFont().deriveFont(Font.PLAIN));
|
---|
| 147 | l.setVerticalTextPosition(JLabel.TOP);
|
---|
| 148 | l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
---|
| 149 | l.addMouseListener(new MouseAdapter(){
|
---|
| 150 | @Override
|
---|
| 151 | public void mouseEntered(MouseEvent e) {
|
---|
| 152 | l.setText("<html><u color='blue'>"+text.toString()+"</u></html>");
|
---|
| 153 | }
|
---|
| 154 | @Override
|
---|
| 155 | public void mouseExited(MouseEvent e) {
|
---|
| 156 | l.setText("<html>"+text.toString()+"</html>");
|
---|
| 157 | }
|
---|
| 158 | @Override
|
---|
| 159 | public void mouseClicked(MouseEvent e) {
|
---|
| 160 | Main.main.ds.clearSelection();
|
---|
| 161 | osm.setSelected(true);
|
---|
| 162 | mv.repaint();
|
---|
| 163 | }
|
---|
| 164 | });
|
---|
| 165 | c.add(l, GBC.eol());
|
---|
| 166 | }
|
---|
| 167 |
|
---|
| 168 | Point p = mv.getLocationOnScreen();
|
---|
| 169 | popup = PopupFactory.getSharedInstance().getPopup(mv, c, p.x+ms.mousePos.x+16, p.y+ms.mousePos.y+16);
|
---|
| 170 | popup.show();
|
---|
| 171 | } else if (popup != null) {
|
---|
[9] | 172 | popup.hide();
|
---|
[56] | 173 | popup = null;
|
---|
[9] | 174 | }
|
---|
[56] | 175 | } catch (ConcurrentModificationException x) {
|
---|
[9] | 176 | }
|
---|
| 177 | }
|
---|
| 178 | }
|
---|
| 179 | }
|
---|
[56] | 180 |
|
---|
[9] | 181 | /**
|
---|
| 182 | * Everything, the collector is interested of. Access must be synchronized.
|
---|
| 183 | * @author imi
|
---|
| 184 | */
|
---|
| 185 | class MouseState {
|
---|
| 186 | Point mousePos;
|
---|
| 187 | int modifiers;
|
---|
| 188 | }
|
---|
| 189 | /**
|
---|
| 190 | * The last sent mouse movement event.
|
---|
| 191 | */
|
---|
[22] | 192 | MouseState mouseState = new MouseState();
|
---|
[9] | 193 |
|
---|
| 194 | /**
|
---|
[16] | 195 | * Construct a new MapStatus and attach it to the map view.
|
---|
| 196 | * @param mv The MapView the status line is part of.
|
---|
[9] | 197 | */
|
---|
[17] | 198 | public MapStatus(final MapFrame mapFrame) {
|
---|
| 199 | this.mv = mapFrame.mapView;
|
---|
[9] | 200 |
|
---|
| 201 | // Listen for mouse movements and set the position text field
|
---|
| 202 | mv.addMouseMotionListener(new MouseMotionListener(){
|
---|
| 203 | public void mouseDragged(MouseEvent e) {
|
---|
| 204 | mouseMoved(e);
|
---|
| 205 | }
|
---|
| 206 | public void mouseMoved(MouseEvent e) {
|
---|
| 207 | // Do not update the view, if ctrl is pressed.
|
---|
| 208 | if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == 0) {
|
---|
| 209 | GeoPoint p = mv.getPoint(e.getX(),e.getY(),true);
|
---|
| 210 | positionText.setText(p.lat+" "+p.lon);
|
---|
| 211 | }
|
---|
| 212 | }
|
---|
| 213 | });
|
---|
| 214 |
|
---|
[17] | 215 | positionText.setEditable(false);
|
---|
| 216 | nameText.setEditable(false);
|
---|
| 217 | setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
---|
| 218 | setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
|
---|
| 219 | add(new JLabel("Lat/Lon "));
|
---|
| 220 | add(positionText);
|
---|
| 221 | add(new JLabel(" Object "));
|
---|
| 222 | add(nameText);
|
---|
[56] | 223 |
|
---|
[17] | 224 | // The background thread
|
---|
| 225 | final Collector collector = new Collector();
|
---|
| 226 | new Thread(collector).start();
|
---|
[56] | 227 |
|
---|
[9] | 228 | // Listen to keyboard/mouse events for pressing/releasing alt key and
|
---|
| 229 | // inform the collector.
|
---|
| 230 | Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener(){
|
---|
| 231 | public void eventDispatched(AWTEvent event) {
|
---|
| 232 | synchronized (collector) {
|
---|
| 233 | mouseState.modifiers = ((InputEvent)event).getModifiersEx();
|
---|
| 234 | if (event instanceof MouseEvent)
|
---|
| 235 | mouseState.mousePos = ((MouseEvent)event).getPoint();
|
---|
| 236 | collector.notify();
|
---|
| 237 | }
|
---|
| 238 | }
|
---|
| 239 | }, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
|
---|
[17] | 240 |
|
---|
| 241 | // listen for shutdowns to cancel the background thread
|
---|
| 242 | mapFrame.addPropertyChangeListener("visible", new PropertyChangeListener(){
|
---|
| 243 | public void propertyChange(PropertyChangeEvent evt) {
|
---|
| 244 | if (evt.getNewValue() == Boolean.FALSE) {
|
---|
| 245 | collector.exitCollector = true;
|
---|
| 246 | synchronized (collector) {
|
---|
| 247 | collector.notify();
|
---|
| 248 | }
|
---|
| 249 | }
|
---|
| 250 | }
|
---|
| 251 | });
|
---|
[9] | 252 | }
|
---|
| 253 | }
|
---|