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

Last change on this file since 6388 was 6314, checked in by Don-vip, 11 years ago

fix #8512 - Do not display discardable keys by default. Expert users can display them by enabling "Display discardable keys" in OSM Data preferences, and change their display colour

  • Property svn:eol-style set to native
File size: 36.5 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.AWTEvent;
8import java.awt.Component;
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.SystemColor;
16import java.awt.Toolkit;
17import java.awt.event.AWTEventListener;
18import java.awt.event.ActionEvent;
19import java.awt.event.InputEvent;
20import java.awt.event.KeyAdapter;
21import java.awt.event.KeyEvent;
22import java.awt.event.MouseAdapter;
23import java.awt.event.MouseEvent;
24import java.awt.event.MouseListener;
25import java.awt.event.MouseMotionListener;
26import java.lang.reflect.InvocationTargetException;
27import java.util.ArrayList;
28import java.util.Collection;
29import java.util.ConcurrentModificationException;
30import java.util.List;
31import java.util.TreeSet;
32
33import javax.swing.AbstractAction;
34import javax.swing.BorderFactory;
35import javax.swing.JCheckBoxMenuItem;
36import javax.swing.JLabel;
37import javax.swing.JMenuItem;
38import javax.swing.JPanel;
39import javax.swing.JPopupMenu;
40import javax.swing.JProgressBar;
41import javax.swing.JScrollPane;
42import javax.swing.Popup;
43import javax.swing.PopupFactory;
44import javax.swing.UIManager;
45import javax.swing.event.PopupMenuEvent;
46import javax.swing.event.PopupMenuListener;
47
48import org.openstreetmap.josm.Main;
49import org.openstreetmap.josm.data.coor.CoordinateFormat;
50import org.openstreetmap.josm.data.coor.LatLon;
51import org.openstreetmap.josm.data.osm.DataSet;
52import org.openstreetmap.josm.data.osm.OsmPrimitive;
53import org.openstreetmap.josm.data.osm.Way;
54import org.openstreetmap.josm.gui.NavigatableComponent.SoMChangeListener;
55import org.openstreetmap.josm.gui.help.Helpful;
56import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
57import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
58import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor.ProgressMonitorDialog;
59import org.openstreetmap.josm.gui.util.GuiHelper;
60import org.openstreetmap.josm.gui.widgets.ImageLabel;
61import org.openstreetmap.josm.gui.widgets.JosmTextField;
62import org.openstreetmap.josm.tools.Destroyable;
63import org.openstreetmap.josm.tools.GBC;
64import org.openstreetmap.josm.tools.ImageProvider;
65
66/**
67 * A component that manages some status information display about the map.
68 * It keeps a status line below the map up to date and displays some tooltip
69 * information if the user hold the mouse long enough at some point.
70 *
71 * All this is done in background to not disturb other processes.
72 *
73 * The background thread does not alter any data of the map (read only thread).
74 * Also it is rather fail safe. In case of some error in the data, it just does
75 * nothing instead of whining and complaining.
76 *
77 * @author imi
78 */
79public class MapStatus extends JPanel implements Helpful, Destroyable {
80
81 /**
82 * The MapView this status belongs to.
83 */
84 final MapView mv;
85 final Collector collector;
86
87 public class BackgroundProgressMonitor implements ProgressMonitorDialog {
88
89 private String title;
90 private String customText;
91
92 private void updateText() {
93 if (customText != null && !customText.isEmpty()) {
94 progressBar.setToolTipText(tr("{0} ({1})", title, customText));
95 } else {
96 progressBar.setToolTipText(title);
97 }
98 }
99
100 @Override
101 public void setVisible(boolean visible) {
102 progressBar.setVisible(visible);
103 }
104
105 @Override
106 public void updateProgress(int progress) {
107 progressBar.setValue(progress);
108 progressBar.repaint();
109 MapStatus.this.doLayout();
110 }
111
112 @Override
113 public void setCustomText(String text) {
114 this.customText = text;
115 updateText();
116 }
117
118 @Override
119 public void setCurrentAction(String text) {
120 this.title = text;
121 updateText();
122 }
123
124 @Override
125 public void setIndeterminate(boolean newValue) {
126 UIManager.put("ProgressBar.cycleTime", UIManager.getInt("ProgressBar.repaintInterval") * 100);
127 progressBar.setIndeterminate(newValue);
128 }
129
130 @Override
131 public void appendLogMessage(String message) {
132 if (message != null && !message.isEmpty()) {
133 Main.info("appendLogMessage not implemented for background tasks. Message was: " + message);
134 }
135 }
136
137 }
138
139 final ImageLabel lonText = new ImageLabel("lon", tr("The geographic longitude at the mouse pointer."), 11);
140 final ImageLabel nameText = new ImageLabel("name", tr("The name of the object at the mouse pointer."), 20);
141 final JosmTextField helpText = new JosmTextField();
142 final ImageLabel latText = new ImageLabel("lat", tr("The geographic latitude at the mouse pointer."), 11);
143 final ImageLabel angleText = new ImageLabel("angle", tr("The angle between the previous and the current way segment."), 6);
144 final ImageLabel headingText = new ImageLabel("heading", tr("The (compass) heading of the line segment being drawn."), 6);
145 final ImageLabel distText = new ImageLabel("dist", tr("The length of the new way segment being drawn."), 10);
146 final JProgressBar progressBar = new JProgressBar();
147 public final BackgroundProgressMonitor progressMonitor = new BackgroundProgressMonitor();
148
149 private final SoMChangeListener somListener;
150
151 private double distValue; // Distance value displayed in distText, stored if refresh needed after a change of system of measurement
152
153 /**
154 * This is the thread that runs in the background and collects the information displayed.
155 * It gets destroyed by destroy() when the MapFrame itself is destroyed.
156 */
157 private Thread thread;
158
159 private final List<StatusTextHistory> statusText = new ArrayList<StatusTextHistory>();
160
161 private static class StatusTextHistory {
162 final Object id;
163 final String text;
164
165 public StatusTextHistory(Object id, String text) {
166 this.id = id;
167 this.text = text;
168 }
169
170 @Override
171 public boolean equals(Object obj) {
172 return obj instanceof StatusTextHistory && ((StatusTextHistory)obj).id == id;
173 }
174
175 @Override
176 public int hashCode() {
177 return System.identityHashCode(id);
178 }
179 }
180
181 /**
182 * The collector class that waits for notification and then update
183 * the display objects.
184 *
185 * @author imi
186 */
187 private final class Collector implements Runnable {
188 /**
189 * the mouse position of the previous iteration. This is used to show
190 * the popup until the cursor is moved.
191 */
192 private Point oldMousePos;
193 /**
194 * Contains the labels that are currently shown in the information
195 * popup
196 */
197 private List<JLabel> popupLabels = null;
198 /**
199 * The popup displayed to show additional information
200 */
201 private Popup popup;
202
203 private MapFrame parent;
204
205 public Collector(MapFrame parent) {
206 this.parent = parent;
207 }
208
209 /**
210 * Execution function for the Collector.
211 */
212 @Override
213 public void run() {
214 registerListeners();
215 try {
216 for (;;) {
217
218 final MouseState ms = new MouseState();
219 synchronized (this) {
220 // TODO Would be better if the timeout wasn't necessary
221 try {
222 wait(1000);
223 } catch (InterruptedException e) {
224 // Occurs frequently during JOSM shutdown, log set to debug only
225 Main.debug("InterruptedException in "+MapStatus.class.getSimpleName());
226 }
227 ms.modifiers = mouseState.modifiers;
228 ms.mousePos = mouseState.mousePos;
229 }
230 if (parent != Main.map)
231 return; // exit, if new parent.
232
233 // Do nothing, if required data is missing
234 if(ms.mousePos == null || mv.center == null) {
235 continue;
236 }
237
238 try {
239 EventQueue.invokeAndWait(new Runnable() {
240
241 @Override
242 public void run() {
243 // Freeze display when holding down CTRL
244 if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) {
245 // update the information popup's labels though, because
246 // the selection might have changed from the outside
247 popupUpdateLabels();
248 return;
249 }
250
251 // This try/catch is a hack to stop the flooding bug reports about this.
252 // The exception needed to handle with in the first place, means that this
253 // access to the data need to be restarted, if the main thread modifies
254 // the data.
255 DataSet ds = null;
256 // The popup != null check is required because a left-click
257 // produces several events as well, which would make this
258 // variable true. Of course we only want the popup to show
259 // if the middle mouse button has been pressed in the first
260 // place
261 boolean mouseNotMoved = oldMousePos != null
262 && oldMousePos.equals(ms.mousePos);
263 boolean isAtOldPosition = mouseNotMoved && popup != null;
264 boolean middleMouseDown = (ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0;
265 try {
266 ds = mv.getCurrentDataSet();
267 if (ds != null) {
268 // This is not perfect, if current dataset was changed during execution, the lock would be useless
269 if(isAtOldPosition && middleMouseDown) {
270 // Write lock is necessary when selecting in popupCycleSelection
271 // locks can not be upgraded -> if do read lock here and write lock later (in OsmPrimitive.updateFlags)
272 // then always occurs deadlock (#5814)
273 ds.beginUpdate();
274 } else {
275 ds.getReadLock().lock();
276 }
277 }
278
279 // Set the text label in the bottom status bar
280 // "if mouse moved only" was added to stop heap growing
281 if (!mouseNotMoved) {
282 statusBarElementUpdate(ms);
283 }
284
285
286 // Popup Information
287 // display them if the middle mouse button is pressed and
288 // keep them until the mouse is moved
289 if (middleMouseDown || isAtOldPosition) {
290 Collection<OsmPrimitive> osms = mv.getAllNearest(ms.mousePos, OsmPrimitive.isUsablePredicate);
291
292 final JPanel c = new JPanel(new GridBagLayout());
293 final JLabel lbl = new JLabel(
294 "<html>"+tr("Middle click again to cycle through.<br>"+
295 "Hold CTRL to select directly from this list with the mouse.<hr>")+"</html>",
296 null,
297 JLabel.HORIZONTAL
298 );
299 lbl.setHorizontalAlignment(JLabel.LEFT);
300 c.add(lbl, GBC.eol().insets(2, 0, 2, 0));
301
302 // Only cycle if the mouse has not been moved and the
303 // middle mouse button has been pressed at least twice
304 // (the reason for this is the popup != null check for
305 // isAtOldPosition, see above. This is a nice side
306 // effect though, because it does not change selection
307 // of the first middle click)
308 if(isAtOldPosition && middleMouseDown) {
309 // Hand down mouse modifiers so the SHIFT mod can be
310 // handled correctly (see funcion)
311 popupCycleSelection(osms, ms.modifiers);
312 }
313
314 // These labels may need to be updated from the outside
315 // so collect them
316 List<JLabel> lbls = new ArrayList<JLabel>(osms.size());
317 for (final OsmPrimitive osm : osms) {
318 JLabel l = popupBuildPrimitiveLabels(osm);
319 lbls.add(l);
320 c.add(l, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 0, 2, 2));
321 }
322
323 popupShowPopup(popupCreatePopup(c, ms), lbls);
324 } else {
325 popupHidePopup();
326 }
327
328 oldMousePos = ms.mousePos;
329 } catch (ConcurrentModificationException x) {
330 Main.warn(x);
331 } catch (NullPointerException x) {
332 Main.warn(x);
333 } finally {
334 if (ds != null) {
335 if(isAtOldPosition && middleMouseDown) {
336 ds.endUpdate();
337 } else {
338 ds.getReadLock().unlock();
339 }
340 }
341 }
342 }
343 });
344 } catch (InterruptedException e) {
345 // Occurs frequently during JOSM shutdown, log set to debug only
346 Main.debug("InterruptedException in "+MapStatus.class.getSimpleName());
347 } catch (InvocationTargetException e) {
348 Main.warn(e);
349 }
350 }
351 } finally {
352 unregisterListeners();
353 }
354 }
355
356 /**
357 * Creates a popup for the given content next to the cursor. Tries to
358 * keep the popup on screen and shows a vertical scrollbar, if the
359 * screen is too small.
360 * @param content
361 * @param ms
362 * @return popup
363 */
364 private Popup popupCreatePopup(Component content, MouseState ms) {
365 Point p = mv.getLocationOnScreen();
366 Dimension scrn = Toolkit.getDefaultToolkit().getScreenSize();
367
368 // Create a JScrollPane around the content, in case there's not
369 // enough space
370 JScrollPane sp = new JScrollPane(content);
371 sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
372 sp.setBorder(BorderFactory.createRaisedBevelBorder());
373 // Implement max-size content-independent
374 Dimension prefsize = sp.getPreferredSize();
375 int w = Math.min(prefsize.width, Math.min(800, (scrn.width/2) - 16));
376 int h = Math.min(prefsize.height, scrn.height - 10);
377 sp.setPreferredSize(new Dimension(w, h));
378
379 int xPos = p.x + ms.mousePos.x + 16;
380 // Display the popup to the left of the cursor if it would be cut
381 // off on its right, but only if more space is available
382 if(xPos + w > scrn.width && xPos > scrn.width/2) {
383 xPos = p.x + ms.mousePos.x - 4 - w;
384 }
385 int yPos = p.y + ms.mousePos.y + 16;
386 // Move the popup up if it would be cut off at its bottom but do not
387 // move it off screen on the top
388 if(yPos + h > scrn.height - 5) {
389 yPos = Math.max(5, scrn.height - h - 5);
390 }
391
392 PopupFactory pf = PopupFactory.getSharedInstance();
393 return pf.getPopup(mv, sp, xPos, yPos);
394 }
395
396 /**
397 * Calls this to update the element that is shown in the statusbar
398 * @param ms
399 */
400 private void statusBarElementUpdate(MouseState ms) {
401 final OsmPrimitive osmNearest = mv.getNearestNodeOrWay(ms.mousePos, OsmPrimitive.isUsablePredicate, false);
402 if (osmNearest != null) {
403 nameText.setText(osmNearest.getDisplayName(DefaultNameFormatter.getInstance()));
404 } else {
405 nameText.setText(tr("(no object)"));
406 }
407 }
408
409 /**
410 * Call this with a set of primitives to cycle through them. Method
411 * will automatically select the next item and update the map
412 * @param osms primitives to cycle through
413 * @param mods modifiers (i.e. control keys)
414 */
415 private void popupCycleSelection(Collection<OsmPrimitive> osms, int mods) {
416 DataSet ds = Main.main.getCurrentDataSet();
417 // Find some items that are required for cycling through
418 OsmPrimitive firstItem = null;
419 OsmPrimitive firstSelected = null;
420 OsmPrimitive nextSelected = null;
421 for (final OsmPrimitive osm : osms) {
422 if(firstItem == null) {
423 firstItem = osm;
424 }
425 if(firstSelected != null && nextSelected == null) {
426 nextSelected = osm;
427 }
428 if(firstSelected == null && ds.isSelected(osm)) {
429 firstSelected = osm;
430 }
431 }
432
433 // Clear previous selection if SHIFT (add to selection) is not
434 // pressed. Cannot use "setSelected()" because it will cause a
435 // fireSelectionChanged event which is unnecessary at this point.
436 if((mods & MouseEvent.SHIFT_DOWN_MASK) == 0) {
437 ds.clearSelection();
438 }
439
440 // This will cycle through the available items.
441 if(firstSelected == null) {
442 ds.addSelected(firstItem);
443 } else {
444 ds.clearSelection(firstSelected);
445 if(nextSelected != null) {
446 ds.addSelected(nextSelected);
447 }
448 }
449 }
450
451 /**
452 * Tries to hide the given popup
453 */
454 private void popupHidePopup() {
455 popupLabels = null;
456 if(popup == null)
457 return;
458 final Popup staticPopup = popup;
459 popup = null;
460 EventQueue.invokeLater(new Runnable(){
461 @Override
462 public void run() {
463 staticPopup.hide();
464 }});
465 }
466
467 /**
468 * Tries to show the given popup, can be hidden using {@link #popupHidePopup}
469 * If an old popup exists, it will be automatically hidden
470 * @param newPopup popup to show
471 * @param lbls lables to show (see {@link #popupLabels})
472 */
473 private void popupShowPopup(Popup newPopup, List<JLabel> lbls) {
474 final Popup staticPopup = newPopup;
475 if(this.popup != null) {
476 // If an old popup exists, remove it when the new popup has been
477 // drawn to keep flickering to a minimum
478 final Popup staticOldPopup = this.popup;
479 EventQueue.invokeLater(new Runnable(){
480 @Override public void run() {
481 staticPopup.show();
482 staticOldPopup.hide();
483 }
484 });
485 } else {
486 // There is no old popup
487 EventQueue.invokeLater(new Runnable(){
488 @Override public void run() { staticPopup.show(); }});
489 }
490 this.popupLabels = lbls;
491 this.popup = newPopup;
492 }
493
494 /**
495 * This method should be called if the selection may have changed from
496 * outside of this class. This is the case when CTRL is pressed and the
497 * user clicks on the map instead of the popup.
498 */
499 private void popupUpdateLabels() {
500 if(this.popup == null || this.popupLabels == null)
501 return;
502 for(JLabel l : this.popupLabels) {
503 l.validate();
504 }
505 }
506
507 /**
508 * Sets the colors for the given label depending on the selected status of
509 * the given OsmPrimitive
510 *
511 * @param lbl The label to color
512 * @param osm The primitive to derive the colors from
513 */
514 private void popupSetLabelColors(JLabel lbl, OsmPrimitive osm) {
515 DataSet ds = Main.main.getCurrentDataSet();
516 if(ds.isSelected(osm)) {
517 lbl.setBackground(SystemColor.textHighlight);
518 lbl.setForeground(SystemColor.textHighlightText);
519 } else {
520 lbl.setBackground(SystemColor.control);
521 lbl.setForeground(SystemColor.controlText);
522 }
523 }
524
525 /**
526 * Builds the labels with all necessary listeners for the info popup for the
527 * given OsmPrimitive
528 * @param osm The primitive to create the label for
529 * @return labels for info popup
530 */
531 private JLabel popupBuildPrimitiveLabels(final OsmPrimitive osm) {
532 final StringBuilder text = new StringBuilder();
533 String name = osm.getDisplayName(DefaultNameFormatter.getInstance());
534 if (osm.isNewOrUndeleted() || osm.isModified()) {
535 name = "<i><b>"+ name + "*</b></i>";
536 }
537 text.append(name);
538
539 boolean idShown = Main.pref.getBoolean("osm-primitives.showid");
540 // fix #7557 - do not show ID twice
541
542 if (!osm.isNew() && !idShown) {
543 text.append(" [id="+osm.getId()+"]");
544 }
545
546 if(osm.getUser() != null) {
547 text.append(" [" + tr("User:") + " " + osm.getUser().getName() + "]");
548 }
549
550 for (String key : osm.keySet()) {
551 text.append("<br>" + key + "=" + osm.get(key));
552 }
553
554 final JLabel l = new JLabel(
555 "<html>" +text.toString() + "</html>",
556 ImageProvider.get(osm.getDisplayType()),
557 JLabel.HORIZONTAL
558 ) {
559 // This is necessary so the label updates its colors when the
560 // selection is changed from the outside
561 @Override public void validate() {
562 super.validate();
563 popupSetLabelColors(this, osm);
564 }
565 };
566 l.setOpaque(true);
567 popupSetLabelColors(l, osm);
568 l.setFont(l.getFont().deriveFont(Font.PLAIN));
569 l.setVerticalTextPosition(JLabel.TOP);
570 l.setHorizontalAlignment(JLabel.LEFT);
571 l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
572 l.addMouseListener(new MouseAdapter(){
573 @Override public void mouseEntered(MouseEvent e) {
574 l.setBackground(SystemColor.info);
575 l.setForeground(SystemColor.infoText);
576 }
577 @Override public void mouseExited(MouseEvent e) {
578 popupSetLabelColors(l, osm);
579 }
580 @Override public void mouseClicked(MouseEvent e) {
581 DataSet ds = Main.main.getCurrentDataSet();
582 // Let the user toggle the selection
583 ds.toggleSelected(osm);
584 l.validate();
585 }
586 });
587 // Sometimes the mouseEntered event is not catched, thus the label
588 // will not be highlighted, making it confusing. The MotionListener
589 // can correct this defect.
590 l.addMouseMotionListener(new MouseMotionListener() {
591 @Override public void mouseMoved(MouseEvent e) {
592 l.setBackground(SystemColor.info);
593 l.setForeground(SystemColor.infoText);
594 }
595 @Override public void mouseDragged(MouseEvent e) {
596 l.setBackground(SystemColor.info);
597 l.setForeground(SystemColor.infoText);
598 }
599 });
600 return l;
601 }
602 }
603
604 /**
605 * Everything, the collector is interested of. Access must be synchronized.
606 * @author imi
607 */
608 static class MouseState {
609 Point mousePos;
610 int modifiers;
611 }
612 /**
613 * The last sent mouse movement event.
614 */
615 MouseState mouseState = new MouseState();
616
617 private AWTEventListener awtListener = new AWTEventListener() {
618 @Override
619 public void eventDispatched(AWTEvent event) {
620 if (event instanceof InputEvent &&
621 ((InputEvent)event).getComponent() == mv) {
622 synchronized (collector) {
623 mouseState.modifiers = ((InputEvent)event).getModifiersEx();
624 if (event instanceof MouseEvent) {
625 mouseState.mousePos = ((MouseEvent)event).getPoint();
626 }
627 collector.notify();
628 }
629 }
630 }
631 };
632
633 private MouseMotionListener mouseMotionListener = new MouseMotionListener() {
634 @Override
635 public void mouseMoved(MouseEvent e) {
636 synchronized (collector) {
637 mouseState.modifiers = e.getModifiersEx();
638 mouseState.mousePos = e.getPoint();
639 collector.notify();
640 }
641 }
642
643 @Override
644 public void mouseDragged(MouseEvent e) {
645 mouseMoved(e);
646 }
647 };
648
649 private KeyAdapter keyAdapter = new KeyAdapter() {
650 @Override public void keyPressed(KeyEvent e) {
651 synchronized (collector) {
652 mouseState.modifiers = e.getModifiersEx();
653 collector.notify();
654 }
655 }
656
657 @Override public void keyReleased(KeyEvent e) {
658 keyPressed(e);
659 }
660 };
661
662 private void registerListeners() {
663 // Listen to keyboard/mouse events for pressing/releasing alt key and
664 // inform the collector.
665 try {
666 Toolkit.getDefaultToolkit().addAWTEventListener(awtListener,
667 AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
668 } catch (SecurityException ex) {
669 mv.addMouseMotionListener(mouseMotionListener);
670 mv.addKeyListener(keyAdapter);
671 }
672 }
673
674 private void unregisterListeners() {
675 try {
676 Toolkit.getDefaultToolkit().removeAWTEventListener(awtListener);
677 } catch (SecurityException e) {
678 // Don't care, awtListener probably wasn't registered anyway
679 }
680 mv.removeMouseMotionListener(mouseMotionListener);
681 mv.removeKeyListener(keyAdapter);
682 }
683
684
685 /**
686 * Construct a new MapStatus and attach it to the map view.
687 * @param mapFrame The MapFrame the status line is part of.
688 */
689 public MapStatus(final MapFrame mapFrame) {
690 this.mv = mapFrame.mapView;
691 this.collector = new Collector(mapFrame);
692
693 // Context menu of status bar
694 setComponentPopupMenu(new JPopupMenu() {
695 JCheckBoxMenuItem doNotHide = new JCheckBoxMenuItem(new AbstractAction(tr("Do not hide status bar")) {
696 @Override public void actionPerformed(ActionEvent e) {
697 boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
698 Main.pref.put("statusbar.always-visible", sel);
699 }
700 });
701 JMenuItem jumpButton;
702 {
703 jumpButton = add(Main.main.menu.jumpToAct);
704 addPopupMenuListener(new PopupMenuListener() {
705 @Override
706 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
707 Component invoker = ((JPopupMenu)e.getSource()).getInvoker();
708 jumpButton.setVisible(invoker == latText || invoker == lonText);
709 doNotHide.setSelected(Main.pref.getBoolean("statusbar.always-visible", true));
710 }
711 @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}
712 @Override public void popupMenuCanceled(PopupMenuEvent e) {}
713 });
714 add(doNotHide);
715 }
716 });
717
718 // also show Jump To dialog on mouse click (except context menu)
719 MouseListener jumpToOnLeftClick = new MouseAdapter() {
720 @Override
721 public void mouseClicked(MouseEvent e) {
722 if (e.getButton() != MouseEvent.BUTTON3) {
723 Main.main.menu.jumpToAct.showJumpToDialog();
724 }
725 }
726 };
727
728 // Listen for mouse movements and set the position text field
729 mv.addMouseMotionListener(new MouseMotionListener(){
730 @Override
731 public void mouseDragged(MouseEvent e) {
732 mouseMoved(e);
733 }
734 @Override
735 public void mouseMoved(MouseEvent e) {
736 if (mv.center == null)
737 return;
738 // Do not update the view if ctrl is pressed.
739 if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == 0) {
740 CoordinateFormat mCord = CoordinateFormat.getDefaultFormat();
741 LatLon p = mv.getLatLon(e.getX(),e.getY());
742 latText.setText(p.latToString(mCord));
743 lonText.setText(p.lonToString(mCord));
744 }
745 }
746 });
747
748 setLayout(new GridBagLayout());
749 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
750
751 latText.setInheritsPopupMenu(true);
752 lonText.setInheritsPopupMenu(true);
753 headingText.setInheritsPopupMenu(true);
754 //angleText.setInheritsPopupMenu(true);
755 distText.setInheritsPopupMenu(true);
756 nameText.setInheritsPopupMenu(true);
757 //helpText.setInheritsPopupMenu(true);
758 //progressBar.setInheritsPopupMenu(true);
759
760 add(latText, GBC.std());
761 add(lonText, GBC.std().insets(3,0,0,0));
762 add(headingText, GBC.std().insets(3,0,0,0));
763 add(angleText, GBC.std().insets(3,0,0,0));
764 add(distText, GBC.std().insets(3,0,0,0));
765
766 distText.addMouseListener(new MouseAdapter() {
767 private final List<String> soms = new ArrayList<String>(new TreeSet<String>(NavigatableComponent.SYSTEMS_OF_MEASUREMENT.keySet()));
768
769 @Override
770 public void mouseClicked(MouseEvent e) {
771 String som = ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.get();
772 String newsom = soms.get((soms.indexOf(som)+1)%soms.size());
773 NavigatableComponent.setSystemOfMeasurement(newsom);
774 }
775 });
776
777 NavigatableComponent.addSoMChangeListener(somListener = new SoMChangeListener() {
778 @Override public void systemOfMeasurementChanged(String oldSoM, String newSoM) {
779 setDist(distValue);
780 }
781 });
782
783 latText.addMouseListener(jumpToOnLeftClick);
784 lonText.addMouseListener(jumpToOnLeftClick);
785
786 helpText.setEditable(false);
787 add(nameText, GBC.std().insets(3,0,0,0));
788 add(helpText, GBC.std().insets(3,0,0,0).fill(GBC.HORIZONTAL));
789
790 progressBar.setMaximum(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX);
791 progressBar.setVisible(false);
792 GBC gbc = GBC.eol();
793 gbc.ipadx = 100;
794 add(progressBar,gbc);
795 progressBar.addMouseListener(new MouseAdapter() {
796 @Override
797 public void mouseClicked(MouseEvent e) {
798 PleaseWaitProgressMonitor monitor = Main.currentProgressMonitor;
799 if (monitor != null) {
800 monitor.showForegroundDialog();
801 }
802 }
803 });
804
805 // The background thread
806 thread = new Thread(collector, "Map Status Collector");
807 thread.setDaemon(true);
808 thread.start();
809 }
810
811 public JPanel getAnglePanel() {
812 return angleText;
813 }
814
815 @Override
816 public String helpTopic() {
817 return ht("/Statusline");
818 }
819
820 @Override
821 public synchronized void addMouseListener(MouseListener ml) {
822 //super.addMouseListener(ml);
823 lonText.addMouseListener(ml);
824 latText.addMouseListener(ml);
825 }
826
827 public void setHelpText(String t) {
828 setHelpText(null, t);
829 }
830 public void setHelpText(Object id, final String text) {
831
832 StatusTextHistory entry = new StatusTextHistory(id, text);
833
834 statusText.remove(entry);
835 statusText.add(entry);
836
837 GuiHelper.runInEDT(new Runnable() {
838 @Override
839 public void run() {
840 helpText.setText(text);
841 helpText.setToolTipText(text);
842 }
843 });
844 }
845 public void resetHelpText(Object id) {
846 if (statusText.isEmpty())
847 return;
848
849 StatusTextHistory entry = new StatusTextHistory(id, null);
850 if (statusText.get(statusText.size() - 1).equals(entry)) {
851 if (statusText.size() == 1) {
852 setHelpText("");
853 } else {
854 StatusTextHistory history = statusText.get(statusText.size() - 2);
855 setHelpText(history.id, history.text);
856 }
857 }
858 statusText.remove(entry);
859 }
860 public void setAngle(double a) {
861 angleText.setText(a < 0 ? "--" : Math.round(a*10)/10.0 + " \u00B0");
862 }
863 public void setHeading(double h) {
864 headingText.setText(h < 0 ? "--" : Math.round(h*10)/10.0 + " \u00B0");
865 }
866 /**
867 * Sets the distance text to the given value
868 * @param dist The distance value to display, in meters
869 */
870 public void setDist(double dist) {
871 distValue = dist;
872 distText.setText(dist < 0 ? "--" : NavigatableComponent.getDistText(dist));
873 }
874 /**
875 * Sets the distance text to the total sum of given ways length
876 * @param ways The ways to consider for the total distance
877 * @since 5991
878 */
879 public void setDist(Collection<Way> ways) {
880 double dist = -1;
881 // Compute total length of selected way(s) until an arbitrary limit set to 250 ways
882 // in order to prevent performance issue if a large number of ways are selected (old behaviour kept in that case, see #8403)
883 int maxWays = Math.max(1, Main.pref.getInteger("selection.max-ways-for-statusline", 250));
884 if (!ways.isEmpty() && ways.size() <= maxWays) {
885 dist = 0.0;
886 for (Way w : ways) {
887 dist += w.getLength();
888 }
889 }
890 setDist(dist);
891 }
892 public void activateAnglePanel(boolean activeFlag) {
893 angleText.setBackground(activeFlag ? ImageLabel.backColorActive : ImageLabel.backColor);
894 }
895
896 @Override
897 public void destroy() {
898 NavigatableComponent.removeSoMChangeListener(somListener);
899
900 // MapFrame gets destroyed when the last layer is removed, but the status line background
901 // thread that collects the information doesn't get destroyed automatically.
902 if (thread != null) {
903 try {
904 thread.interrupt();
905 } catch (Exception e) {
906 e.printStackTrace();
907 }
908 }
909 }
910}
Note: See TracBrowser for help on using the repository browser.