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

Last change on this file since 4609 was 4604, checked in by jttt, 12 years ago

Multikey action improvements (see #5515):

  • show text in status bar after shortcut is pressed
  • number layers from 1
  • do not allow Repeat for last layer if layer index conflicts with shortcut
  • Property svn:eol-style set to native
File size: 28.9 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.Color;
9import java.awt.Component;
10import java.awt.Cursor;
11import java.awt.Dimension;
12import java.awt.EventQueue;
13import java.awt.Font;
14import java.awt.GridBagLayout;
15import java.awt.Point;
16import java.awt.SystemColor;
17import java.awt.Toolkit;
18import java.awt.event.AWTEventListener;
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.util.ArrayList;
27import java.util.Collection;
28import java.util.ConcurrentModificationException;
29import java.util.List;
30
31import javax.swing.BorderFactory;
32import javax.swing.JLabel;
33import javax.swing.JPanel;
34import javax.swing.JScrollPane;
35import javax.swing.JTextField;
36import javax.swing.Popup;
37import javax.swing.PopupFactory;
38
39import org.openstreetmap.josm.Main;
40import org.openstreetmap.josm.data.coor.CoordinateFormat;
41import org.openstreetmap.josm.data.coor.LatLon;
42import org.openstreetmap.josm.data.osm.DataSet;
43import org.openstreetmap.josm.data.osm.OsmPrimitive;
44import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
45import org.openstreetmap.josm.gui.help.Helpful;
46import org.openstreetmap.josm.tools.GBC;
47import org.openstreetmap.josm.tools.ImageProvider;
48
49/**
50 * A component that manages some status information display about the map.
51 * It keeps a status line below the map up to date and displays some tooltip
52 * information if the user hold the mouse long enough at some point.
53 *
54 * All this is done in background to not disturb other processes.
55 *
56 * The background thread does not alter any data of the map (read only thread).
57 * Also it is rather fail safe. In case of some error in the data, it just does
58 * nothing instead of whining and complaining.
59 *
60 * @author imi
61 */
62public class MapStatus extends JPanel implements Helpful {
63
64 /**
65 * The MapView this status belongs to.
66 */
67 final MapView mv;
68 final Collector collector;
69
70 /**
71 * A small user interface component that consists of an image label and
72 * a fixed text content to the right of the image.
73 */
74 static class ImageLabel extends JPanel {
75 private JLabel tf;
76 private int chars;
77 public ImageLabel(String img, String tooltip, int chars) {
78 super();
79 setLayout(new GridBagLayout());
80 setBackground(Color.decode("#b8cfe5"));
81 add(new JLabel(ImageProvider.get("statusline/"+img+".png")), GBC.std().anchor(GBC.WEST).insets(0,1,1,0));
82 add(tf = new JLabel(), GBC.std().fill(GBC.BOTH).anchor(GBC.WEST).insets(2,1,1,0));
83 setToolTipText(tooltip);
84 this.chars = chars;
85 }
86 public void setText(String t) {
87 tf.setText(t);
88 }
89 @Override public Dimension getPreferredSize() {
90 return new Dimension(25 + chars*tf.getFontMetrics(tf.getFont()).charWidth('0'), super.getPreferredSize().height);
91 }
92 @Override public Dimension getMinimumSize() {
93 return new Dimension(25 + chars*tf.getFontMetrics(tf.getFont()).charWidth('0'), super.getMinimumSize().height);
94 }
95 }
96
97 final ImageLabel lonText = new ImageLabel("lon", tr("The geographic longitude at the mouse pointer."), 11);
98 final ImageLabel nameText = new ImageLabel("name", tr("The name of the object at the mouse pointer."), 20);
99 final JTextField helpText = new JTextField();
100 final ImageLabel latText = new ImageLabel("lat", tr("The geographic latitude at the mouse pointer."), 11);
101 final ImageLabel angleText = new ImageLabel("angle", tr("The angle between the previous and the current way segment."), 6);
102 final ImageLabel headingText = new ImageLabel("heading", tr("The (compass) heading of the line segment being drawn."), 6);
103 final ImageLabel distText = new ImageLabel("dist", tr("The length of the new way segment being drawn."), 10);
104
105 /**
106 * This is the thread that runs in the background and collects the information displayed.
107 * It gets destroyed by MapFrame.java/destroy() when the MapFrame itself is destroyed.
108 */
109 public Thread thread;
110
111 private final List<StatusTextHistory> statusText = new ArrayList<StatusTextHistory>();
112
113 private static class StatusTextHistory {
114 final Object id;
115 final String text;
116
117 public StatusTextHistory(Object id, String text) {
118 this.id = id;
119 this.text = text;
120 }
121
122 @Override
123 public boolean equals(Object obj) {
124 return obj instanceof StatusTextHistory && ((StatusTextHistory)obj).id == id;
125 }
126
127 @Override
128 public int hashCode() {
129 return System.identityHashCode(id);
130 }
131 }
132
133 /**
134 * The collector class that waits for notification and then update
135 * the display objects.
136 *
137 * @author imi
138 */
139 private final class Collector implements Runnable {
140 /**
141 * the mouse position of the previous iteration. This is used to show
142 * the popup until the cursor is moved.
143 */
144 private Point oldMousePos;
145 /**
146 * Contains the labels that are currently shown in the information
147 * popup
148 */
149 private List<JLabel> popupLabels = null;
150 /**
151 * The popup displayed to show additional information
152 */
153 private Popup popup;
154
155 private MapFrame parent;
156
157 public Collector(MapFrame parent) {
158 this.parent = parent;
159 }
160
161 /**
162 * Execution function for the Collector.
163 */
164 public void run() {
165 registerListeners();
166 try {
167 for (;;) {
168
169 final MouseState ms = new MouseState();
170 synchronized (this) {
171 // TODO Would be better if the timeout wasn't necessary
172 try {wait(1000);} catch (InterruptedException e) {}
173 ms.modifiers = mouseState.modifiers;
174 ms.mousePos = mouseState.mousePos;
175 }
176 if (parent != Main.map)
177 return; // exit, if new parent.
178
179 // Do nothing, if required data is missing
180 if(ms.mousePos == null || mv.center == null) {
181 continue;
182 }
183
184 try {
185 EventQueue.invokeAndWait(new Runnable() {
186
187 @Override
188 public void run() {
189 // Freeze display when holding down CTRL
190 if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) {
191 // update the information popup's labels though, because
192 // the selection might have changed from the outside
193 popupUpdateLabels();
194 return;
195 }
196
197 // This try/catch is a hack to stop the flooding bug reports about this.
198 // The exception needed to handle with in the first place, means that this
199 // access to the data need to be restarted, if the main thread modifies
200 // the data.
201 DataSet ds = null;
202 // The popup != null check is required because a left-click
203 // produces several events as well, which would make this
204 // variable true. Of course we only want the popup to show
205 // if the middle mouse button has been pressed in the first
206 // place
207 boolean isAtOldPosition = (oldMousePos != null
208 && oldMousePos.equals(ms.mousePos)
209 && popup != null);
210 boolean middleMouseDown = (ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0;
211 try {
212 ds = mv.getCurrentDataSet();
213 if (ds != null) {
214 // This is not perfect, if current dataset was changed during execution, the lock would be useless
215 if(isAtOldPosition && middleMouseDown) {
216 // Write lock is necessary when selecting in popupCycleSelection
217 // locks can not be upgraded -> if do read lock here and write lock later (in OsmPrimitive.updateFlags)
218 // then always occurs deadlock (#5814)
219 ds.beginUpdate();
220 } else {
221 ds.getReadLock().lock();
222 }
223 }
224
225 // Set the text label in the bottom status bar
226 statusBarElementUpdate(ms);
227
228
229 // Popup Information
230 // display them if the middle mouse button is pressed and
231 // keep them until the mouse is moved
232 if (middleMouseDown || isAtOldPosition)
233 {
234 Collection<OsmPrimitive> osms = mv.getAllNearest(ms.mousePos, OsmPrimitive.isUsablePredicate);
235
236 if (osms == null)
237 return;
238
239 final JPanel c = new JPanel(new GridBagLayout());
240 final JLabel lbl = new JLabel(
241 "<html>"+tr("Middle click again to cycle through.<br>"+
242 "Hold CTRL to select directly from this list with the mouse.<hr>")+"</html>",
243 null,
244 JLabel.HORIZONTAL
245 );
246 lbl.setHorizontalAlignment(JLabel.LEFT);
247 c.add(lbl, GBC.eol().insets(2, 0, 2, 0));
248
249 // Only cycle if the mouse has not been moved and the
250 // middle mouse button has been pressed at least twice
251 // (the reason for this is the popup != null check for
252 // isAtOldPosition, see above. This is a nice side
253 // effect though, because it does not change selection
254 // of the first middle click)
255 if(isAtOldPosition && middleMouseDown) {
256 // Hand down mouse modifiers so the SHIFT mod can be
257 // handled correctly (see funcion)
258 popupCycleSelection(osms, ms.modifiers);
259 }
260
261 // These labels may need to be updated from the outside
262 // so collect them
263 List<JLabel> lbls = new ArrayList<JLabel>();
264 for (final OsmPrimitive osm : osms) {
265 JLabel l = popupBuildPrimitiveLabels(osm);
266 lbls.add(l);
267 c.add(l, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 0, 2, 2));
268 }
269
270 popupShowPopup(popupCreatePopup(c, ms), lbls);
271 } else {
272 popupHidePopup();
273 }
274
275 oldMousePos = ms.mousePos;
276 } catch (ConcurrentModificationException x) {
277 //x.printStackTrace();
278 } catch (NullPointerException x) {
279 //x.printStackTrace();
280 } finally {
281 if (ds != null) {
282 if(isAtOldPosition && middleMouseDown) {
283 ds.endUpdate();
284 } else {
285 ds.getReadLock().unlock();
286 }
287 }
288 }
289 }
290 });
291 } catch (Exception e) {
292
293 }
294 }
295 } finally {
296 unregisterListeners();
297 }
298 }
299
300 /**
301 * Creates a popup for the given content next to the cursor. Tries to
302 * keep the popup on screen and shows a vertical scrollbar, if the
303 * screen is too small.
304 * @param content
305 * @param ms
306 * @return popup
307 */
308 private final Popup popupCreatePopup(Component content, MouseState ms) {
309 Point p = mv.getLocationOnScreen();
310 Dimension scrn = Toolkit.getDefaultToolkit().getScreenSize();
311
312 // Create a JScrollPane around the content, in case there's not
313 // enough space
314 JScrollPane sp = new JScrollPane(content);
315 sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
316 sp.setBorder(BorderFactory.createRaisedBevelBorder());
317 // Implement max-size content-independent
318 Dimension prefsize = sp.getPreferredSize();
319 int w = Math.min(prefsize.width, Math.min(800, (scrn.width/2) - 16));
320 int h = Math.min(prefsize.height, scrn.height - 10);
321 sp.setPreferredSize(new Dimension(w, h));
322
323 int xPos = p.x + ms.mousePos.x + 16;
324 // Display the popup to the left of the cursor if it would be cut
325 // off on its right, but only if more space is available
326 if(xPos + w > scrn.width && xPos > scrn.width/2) {
327 xPos = p.x + ms.mousePos.x - 4 - w;
328 }
329 int yPos = p.y + ms.mousePos.y + 16;
330 // Move the popup up if it would be cut off at its bottom but do not
331 // move it off screen on the top
332 if(yPos + h > scrn.height - 5) {
333 yPos = Math.max(5, scrn.height - h - 5);
334 }
335
336 PopupFactory pf = PopupFactory.getSharedInstance();
337 return pf.getPopup(mv, sp, xPos, yPos);
338 }
339
340 /**
341 * Calls this to update the element that is shown in the statusbar
342 * @param ms
343 */
344 private final void statusBarElementUpdate(MouseState ms) {
345 final OsmPrimitive osmNearest = mv.getNearestNodeOrWay(ms.mousePos, OsmPrimitive.isUsablePredicate, false);
346 if (osmNearest != null) {
347 nameText.setText(osmNearest.getDisplayName(DefaultNameFormatter.getInstance()));
348 } else {
349 nameText.setText(tr("(no object)"));
350 }
351 }
352
353 /**
354 * Call this with a set of primitives to cycle through them. Method
355 * will automatically select the next item and update the map
356 * @param osms
357 * @param mouse modifiers
358 */
359 private final void popupCycleSelection(Collection<OsmPrimitive> osms, int mods) {
360 DataSet ds = Main.main.getCurrentDataSet();
361 // Find some items that are required for cycling through
362 OsmPrimitive firstItem = null;
363 OsmPrimitive firstSelected = null;
364 OsmPrimitive nextSelected = null;
365 for (final OsmPrimitive osm : osms) {
366 if(firstItem == null) {
367 firstItem = osm;
368 }
369 if(firstSelected != null && nextSelected == null) {
370 nextSelected = osm;
371 }
372 if(firstSelected == null && ds.isSelected(osm)) {
373 firstSelected = osm;
374 }
375 }
376
377 // Clear previous selection if SHIFT (add to selection) is not
378 // pressed. Cannot use "setSelected()" because it will cause a
379 // fireSelectionChanged event which is unnecessary at this point.
380 if((mods & MouseEvent.SHIFT_DOWN_MASK) == 0) {
381 ds.clearSelection();
382 }
383
384 // This will cycle through the available items.
385 if(firstSelected == null) {
386 ds.addSelected(firstItem);
387 } else {
388 ds.clearSelection(firstSelected);
389 if(nextSelected != null) {
390 ds.addSelected(nextSelected);
391 }
392 }
393 }
394
395 /**
396 * Tries to hide the given popup
397 * @param popup
398 */
399 private final void popupHidePopup() {
400 popupLabels = null;
401 if(popup == null)
402 return;
403 final Popup staticPopup = popup;
404 popup = null;
405 EventQueue.invokeLater(new Runnable(){
406 public void run() { staticPopup.hide(); }});
407 }
408
409 /**
410 * Tries to show the given popup, can be hidden using popupHideOldPopup
411 * If an old popup exists, it will be automatically hidden
412 * @param popup
413 */
414 private final void popupShowPopup(Popup newPopup, List<JLabel> lbls) {
415 final Popup staticPopup = newPopup;
416 if(this.popup != null) {
417 // If an old popup exists, remove it when the new popup has been
418 // drawn to keep flickering to a minimum
419 final Popup staticOldPopup = this.popup;
420 EventQueue.invokeLater(new Runnable(){
421 public void run() {
422 staticPopup.show();
423 staticOldPopup.hide();
424 }
425 });
426 } else {
427 // There is no old popup
428 EventQueue.invokeLater(new Runnable(){
429 public void run() { staticPopup.show(); }});
430 }
431 this.popupLabels = lbls;
432 this.popup = newPopup;
433 }
434
435 /**
436 * This method should be called if the selection may have changed from
437 * outside of this class. This is the case when CTRL is pressed and the
438 * user clicks on the map instead of the popup.
439 */
440 private final void popupUpdateLabels() {
441 if(this.popup == null || this.popupLabels == null)
442 return;
443 for(JLabel l : this.popupLabels) {
444 l.validate();
445 }
446 }
447
448 /**
449 * Sets the colors for the given label depending on the selected status of
450 * the given OsmPrimitive
451 *
452 * @param lbl The label to color
453 * @param osm The primitive to derive the colors from
454 */
455 private final void popupSetLabelColors(JLabel lbl, OsmPrimitive osm) {
456 DataSet ds = Main.main.getCurrentDataSet();
457 if(ds.isSelected(osm)) {
458 lbl.setBackground(SystemColor.textHighlight);
459 lbl.setForeground(SystemColor.textHighlightText);
460 } else {
461 lbl.setBackground(SystemColor.control);
462 lbl.setForeground(SystemColor.controlText);
463 }
464 }
465
466 /**
467 * Builds the labels with all necessary listeners for the info popup for the
468 * given OsmPrimitive
469 * @param osm The primitive to create the label for
470 * @return
471 */
472 private final JLabel popupBuildPrimitiveLabels(final OsmPrimitive osm) {
473 final StringBuilder text = new StringBuilder();
474 String name = osm.getDisplayName(DefaultNameFormatter.getInstance());
475 if (osm.isNewOrUndeleted() || osm.isModified()) {
476 name = "<i><b>"+ name + "*</b></i>";
477 }
478 text.append(name);
479
480 if (!osm.isNew()) {
481 text.append(" [id="+osm.getId()+"]");
482 }
483
484 if(osm.getUser() != null) {
485 text.append(" [" + tr("User:") + " " + osm.getUser().getName() + "]");
486 }
487
488 for (String key : osm.keySet()) {
489 text.append("<br>" + key + "=" + osm.get(key));
490 }
491
492 final JLabel l = new JLabel(
493 "<html>" +text.toString() + "</html>",
494 ImageProvider.get(OsmPrimitiveType.from(osm)),
495 JLabel.HORIZONTAL
496 ) {
497 // This is necessary so the label updates its colors when the
498 // selection is changed from the outside
499 @Override public void validate() {
500 super.validate();
501 popupSetLabelColors(this, osm);
502 }
503 };
504 l.setOpaque(true);
505 popupSetLabelColors(l, osm);
506 l.setFont(l.getFont().deriveFont(Font.PLAIN));
507 l.setVerticalTextPosition(JLabel.TOP);
508 l.setHorizontalAlignment(JLabel.LEFT);
509 l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
510 l.addMouseListener(new MouseAdapter(){
511 @Override public void mouseEntered(MouseEvent e) {
512 l.setBackground(SystemColor.info);
513 l.setForeground(SystemColor.infoText);
514 }
515 @Override public void mouseExited(MouseEvent e) {
516 popupSetLabelColors(l, osm);
517 }
518 @Override public void mouseClicked(MouseEvent e) {
519 DataSet ds = Main.main.getCurrentDataSet();
520 // Let the user toggle the selection
521 ds.toggleSelected(osm);
522 l.validate();
523 }
524 });
525 // Sometimes the mouseEntered event is not catched, thus the label
526 // will not be highlighted, making it confusing. The MotionListener
527 // can correct this defect.
528 l.addMouseMotionListener(new MouseMotionListener() {
529 public void mouseMoved(MouseEvent e) {
530 l.setBackground(SystemColor.info);
531 l.setForeground(SystemColor.infoText);
532 }
533 public void mouseDragged(MouseEvent e) {
534 l.setBackground(SystemColor.info);
535 l.setForeground(SystemColor.infoText);
536 }
537 });
538 return l;
539 }
540 }
541
542 /**
543 * Everything, the collector is interested of. Access must be synchronized.
544 * @author imi
545 */
546 static class MouseState {
547 Point mousePos;
548 int modifiers;
549 }
550 /**
551 * The last sent mouse movement event.
552 */
553 MouseState mouseState = new MouseState();
554
555 private AWTEventListener awtListener = new AWTEventListener() {
556 public void eventDispatched(AWTEvent event) {
557 if (event instanceof InputEvent &&
558 ((InputEvent)event).getComponent() == mv) {
559 synchronized (collector) {
560 mouseState.modifiers = ((InputEvent)event).getModifiersEx();
561 if (event instanceof MouseEvent) {
562 mouseState.mousePos = ((MouseEvent)event).getPoint();
563 }
564 collector.notify();
565 }
566 }
567 }
568 };
569
570 private MouseMotionListener mouseMotionListener = new MouseMotionListener() {
571 public void mouseMoved(MouseEvent e) {
572 synchronized (collector) {
573 mouseState.modifiers = e.getModifiersEx();
574 mouseState.mousePos = e.getPoint();
575 collector.notify();
576 }
577 }
578
579 public void mouseDragged(MouseEvent e) {
580 mouseMoved(e);
581 }
582 };
583
584 private KeyAdapter keyAdapter = new KeyAdapter() {
585 @Override public void keyPressed(KeyEvent e) {
586 synchronized (collector) {
587 mouseState.modifiers = e.getModifiersEx();
588 collector.notify();
589 }
590 }
591
592 @Override public void keyReleased(KeyEvent e) {
593 keyPressed(e);
594 }
595 };
596
597 private void registerListeners() {
598 // Listen to keyboard/mouse events for pressing/releasing alt key and
599 // inform the collector.
600 try {
601 Toolkit.getDefaultToolkit().addAWTEventListener(awtListener,
602 AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
603 } catch (SecurityException ex) {
604 mv.addMouseMotionListener(mouseMotionListener);
605 mv.addKeyListener(keyAdapter);
606 }
607 }
608
609 private void unregisterListeners() {
610 try {
611 Toolkit.getDefaultToolkit().removeAWTEventListener(awtListener);
612 } catch (SecurityException e) {
613 // Don't care, awtListener probably wasn't registered anyway
614 }
615 mv.removeMouseMotionListener(mouseMotionListener);
616 mv.removeKeyListener(keyAdapter);
617 }
618
619
620 /**
621 * Construct a new MapStatus and attach it to the map view.
622 * @param mapFrame The MapFrame the status line is part of.
623 */
624 public MapStatus(final MapFrame mapFrame) {
625 this.mv = mapFrame.mapView;
626 this.collector = new Collector(mapFrame);
627
628 lonText.addMouseListener(Main.main.menu.jumpToAct);
629 latText.addMouseListener(Main.main.menu.jumpToAct);
630
631 // Listen for mouse movements and set the position text field
632 mv.addMouseMotionListener(new MouseMotionListener(){
633 public void mouseDragged(MouseEvent e) {
634 mouseMoved(e);
635 }
636 public void mouseMoved(MouseEvent e) {
637 if (mv.center == null)
638 return;
639 // Do not update the view if ctrl is pressed.
640 if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == 0) {
641 CoordinateFormat mCord = CoordinateFormat.getDefaultFormat();
642 LatLon p = mv.getLatLon(e.getX(),e.getY());
643 latText.setText(p.latToString(mCord));
644 lonText.setText(p.lonToString(mCord));
645 }
646 }
647 });
648
649 setLayout(new GridBagLayout());
650 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
651
652 add(latText, GBC.std());
653 add(lonText, GBC.std().insets(3,0,0,0));
654 add(headingText, GBC.std().insets(3,0,0,0));
655 add(angleText, GBC.std().insets(3,0,0,0));
656 add(distText, GBC.std().insets(3,0,0,0));
657
658 helpText.setEditable(false);
659 add(nameText, GBC.std().insets(3,0,0,0));
660 add(helpText, GBC.eol().insets(3,0,0,0).fill(GBC.HORIZONTAL));
661
662 // The background thread
663 thread = new Thread(collector, "Map Status Collector");
664 thread.setDaemon(true);
665 thread.start();
666 }
667
668 public String helpTopic() {
669 return ht("/Statusline");
670 }
671
672 @Override
673 public synchronized void addMouseListener(MouseListener ml) {
674 //super.addMouseListener(ml);
675 lonText.addMouseListener(ml);
676 latText.addMouseListener(ml);
677 }
678
679 public void setHelpText(String t) {
680 setHelpText(null, t);
681 }
682 public void setHelpText(Object id, String text) {
683
684 StatusTextHistory entry = new StatusTextHistory(id, text);
685
686 statusText.remove(entry);
687 statusText.add(entry);
688
689 helpText.setText(text);
690 helpText.setToolTipText(text);
691 }
692 public void resetHelpText(Object id) {
693 if (statusText.isEmpty())
694 return;
695
696 StatusTextHistory entry = new StatusTextHistory(id, null);
697 if (statusText.get(statusText.size() - 1).equals(entry)) {
698 if (statusText.size() == 1) {
699 setHelpText("");
700 } else {
701 StatusTextHistory history = statusText.get(statusText.size() - 2);
702 setHelpText(history.id, history.text);
703 }
704 }
705 statusText.remove(entry);
706 }
707 public void setAngle(double a) {
708 angleText.setText(a < 0 ? "--" : Math.round(a*10)/10.0 + " \u00B0");
709 }
710 public void setHeading(double h) {
711 headingText.setText(h < 0 ? "--" : Math.round(h*10)/10.0 + " \u00B0");
712 }
713 public void setDist(double dist) {
714 distText.setText(dist < 0 ? "--" : NavigatableComponent.getDistText(dist));
715 }
716}
Note: See TracBrowser for help on using the repository browser.