001 // License: GPL. See LICENSE file for details. 002 package org.openstreetmap.josm.gui; 003 004 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005 import static org.openstreetmap.josm.tools.I18n.tr; 006 007 import java.awt.AWTEvent; 008 import java.awt.Color; 009 import java.awt.Component; 010 import java.awt.Cursor; 011 import java.awt.Dimension; 012 import java.awt.EventQueue; 013 import java.awt.Font; 014 import java.awt.GridBagLayout; 015 import java.awt.Point; 016 import java.awt.SystemColor; 017 import java.awt.Toolkit; 018 import java.awt.event.AWTEventListener; 019 import java.awt.event.InputEvent; 020 import java.awt.event.KeyAdapter; 021 import java.awt.event.KeyEvent; 022 import java.awt.event.MouseAdapter; 023 import java.awt.event.MouseEvent; 024 import java.awt.event.MouseListener; 025 import java.awt.event.MouseMotionListener; 026 import java.util.ArrayList; 027 import java.util.Collection; 028 import java.util.ConcurrentModificationException; 029 import java.util.List; 030 031 import javax.swing.BorderFactory; 032 import javax.swing.JLabel; 033 import javax.swing.JPanel; 034 import javax.swing.JProgressBar; 035 import javax.swing.JScrollPane; 036 import javax.swing.JTextField; 037 import javax.swing.Popup; 038 import javax.swing.PopupFactory; 039 import javax.swing.UIManager; 040 041 import org.openstreetmap.josm.Main; 042 import org.openstreetmap.josm.data.coor.CoordinateFormat; 043 import org.openstreetmap.josm.data.coor.LatLon; 044 import org.openstreetmap.josm.data.osm.DataSet; 045 import org.openstreetmap.josm.data.osm.OsmPrimitive; 046 import org.openstreetmap.josm.gui.help.Helpful; 047 import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor; 048 import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor.ProgressMonitorDialog; 049 import org.openstreetmap.josm.gui.util.GuiHelper; 050 import org.openstreetmap.josm.tools.GBC; 051 import org.openstreetmap.josm.tools.ImageProvider; 052 053 /** 054 * A component that manages some status information display about the map. 055 * It keeps a status line below the map up to date and displays some tooltip 056 * information if the user hold the mouse long enough at some point. 057 * 058 * All this is done in background to not disturb other processes. 059 * 060 * The background thread does not alter any data of the map (read only thread). 061 * Also it is rather fail safe. In case of some error in the data, it just does 062 * nothing instead of whining and complaining. 063 * 064 * @author imi 065 */ 066 public class MapStatus extends JPanel implements Helpful { 067 068 /** 069 * The MapView this status belongs to. 070 */ 071 final MapView mv; 072 final Collector collector; 073 074 /** 075 * A small user interface component that consists of an image label and 076 * a fixed text content to the right of the image. 077 */ 078 static class ImageLabel extends JPanel { 079 static Color backColor = Color.decode("#b8cfe5"); 080 static Color backColorActive = Color.decode("#aaff5e"); 081 082 private JLabel tf; 083 private int chars; 084 public ImageLabel(String img, String tooltip, int chars) { 085 super(); 086 setLayout(new GridBagLayout()); 087 setBackground(backColor); 088 add(new JLabel(ImageProvider.get("statusline/"+img+".png")), GBC.std().anchor(GBC.WEST).insets(0,1,1,0)); 089 add(tf = new JLabel(), GBC.std().fill(GBC.BOTH).anchor(GBC.WEST).insets(2,1,1,0)); 090 setToolTipText(tooltip); 091 this.chars = chars; 092 } 093 public void setText(String t) { 094 tf.setText(t); 095 } 096 @Override public Dimension getPreferredSize() { 097 return new Dimension(25 + chars*tf.getFontMetrics(tf.getFont()).charWidth('0'), super.getPreferredSize().height); 098 } 099 @Override public Dimension getMinimumSize() { 100 return new Dimension(25 + chars*tf.getFontMetrics(tf.getFont()).charWidth('0'), super.getMinimumSize().height); 101 } 102 } 103 104 public class BackgroundProgressMonitor implements ProgressMonitorDialog { 105 106 private String title; 107 private String customText; 108 109 private void updateText() { 110 if (customText != null && !customText.isEmpty()) { 111 progressBar.setToolTipText(tr("{0} ({1})", title, customText)); 112 } else { 113 progressBar.setToolTipText(title); 114 } 115 } 116 117 public void setVisible(boolean visible) { 118 progressBar.setVisible(visible); 119 } 120 121 public void updateProgress(int progress) { 122 progressBar.setValue(progress); 123 MapStatus.this.doLayout(); 124 } 125 126 public void setCustomText(String text) { 127 this.customText = text; 128 updateText(); 129 } 130 131 public void setCurrentAction(String text) { 132 this.title = text; 133 updateText(); 134 } 135 136 public void setIndeterminate(boolean newValue) { 137 UIManager.put("ProgressBar.cycleTime", UIManager.getInt("ProgressBar.repaintInterval") * 100); 138 progressBar.setIndeterminate(newValue); 139 } 140 141 @Override 142 public void appendLogMessage(String message) { 143 if (message != null && !message.isEmpty()) { 144 System.out.println("appendLogMessage not implemented for background tasks. Message was: " + message); 145 } 146 } 147 148 } 149 150 final ImageLabel lonText = new ImageLabel("lon", tr("The geographic longitude at the mouse pointer."), 11); 151 final ImageLabel nameText = new ImageLabel("name", tr("The name of the object at the mouse pointer."), 20); 152 final JTextField helpText = new JTextField(); 153 final ImageLabel latText = new ImageLabel("lat", tr("The geographic latitude at the mouse pointer."), 11); 154 final ImageLabel angleText = new ImageLabel("angle", tr("The angle between the previous and the current way segment."), 6); 155 final ImageLabel headingText = new ImageLabel("heading", tr("The (compass) heading of the line segment being drawn."), 6); 156 final ImageLabel distText = new ImageLabel("dist", tr("The length of the new way segment being drawn."), 10); 157 final JProgressBar progressBar = new JProgressBar(); 158 public final BackgroundProgressMonitor progressMonitor = new BackgroundProgressMonitor(); 159 160 /** 161 * This is the thread that runs in the background and collects the information displayed. 162 * It gets destroyed by MapFrame.java/destroy() when the MapFrame itself is destroyed. 163 */ 164 public Thread thread; 165 166 private final List<StatusTextHistory> statusText = new ArrayList<StatusTextHistory>(); 167 168 private static class StatusTextHistory { 169 final Object id; 170 final String text; 171 172 public StatusTextHistory(Object id, String text) { 173 this.id = id; 174 this.text = text; 175 } 176 177 @Override 178 public boolean equals(Object obj) { 179 return obj instanceof StatusTextHistory && ((StatusTextHistory)obj).id == id; 180 } 181 182 @Override 183 public int hashCode() { 184 return System.identityHashCode(id); 185 } 186 } 187 188 /** 189 * The collector class that waits for notification and then update 190 * the display objects. 191 * 192 * @author imi 193 */ 194 private final class Collector implements Runnable { 195 /** 196 * the mouse position of the previous iteration. This is used to show 197 * the popup until the cursor is moved. 198 */ 199 private Point oldMousePos; 200 /** 201 * Contains the labels that are currently shown in the information 202 * popup 203 */ 204 private List<JLabel> popupLabels = null; 205 /** 206 * The popup displayed to show additional information 207 */ 208 private Popup popup; 209 210 private MapFrame parent; 211 212 public Collector(MapFrame parent) { 213 this.parent = parent; 214 } 215 216 /** 217 * Execution function for the Collector. 218 */ 219 public void run() { 220 registerListeners(); 221 try { 222 for (;;) { 223 224 final MouseState ms = new MouseState(); 225 synchronized (this) { 226 // TODO Would be better if the timeout wasn't necessary 227 try {wait(1000);} catch (InterruptedException e) {} 228 ms.modifiers = mouseState.modifiers; 229 ms.mousePos = mouseState.mousePos; 230 } 231 if (parent != Main.map) 232 return; // exit, if new parent. 233 234 // Do nothing, if required data is missing 235 if(ms.mousePos == null || mv.center == null) { 236 continue; 237 } 238 239 try { 240 EventQueue.invokeAndWait(new Runnable() { 241 242 @Override 243 public void run() { 244 // Freeze display when holding down CTRL 245 if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) { 246 // update the information popup's labels though, because 247 // the selection might have changed from the outside 248 popupUpdateLabels(); 249 return; 250 } 251 252 // This try/catch is a hack to stop the flooding bug reports about this. 253 // The exception needed to handle with in the first place, means that this 254 // access to the data need to be restarted, if the main thread modifies 255 // the data. 256 DataSet ds = null; 257 // The popup != null check is required because a left-click 258 // produces several events as well, which would make this 259 // variable true. Of course we only want the popup to show 260 // if the middle mouse button has been pressed in the first 261 // place 262 boolean mouseNotMoved = oldMousePos != null 263 && oldMousePos.equals(ms.mousePos); 264 boolean isAtOldPosition = mouseNotMoved && popup != null; 265 boolean middleMouseDown = (ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0; 266 try { 267 ds = mv.getCurrentDataSet(); 268 if (ds != null) { 269 // This is not perfect, if current dataset was changed during execution, the lock would be useless 270 if(isAtOldPosition && middleMouseDown) { 271 // Write lock is necessary when selecting in popupCycleSelection 272 // locks can not be upgraded -> if do read lock here and write lock later (in OsmPrimitive.updateFlags) 273 // then always occurs deadlock (#5814) 274 ds.beginUpdate(); 275 } else { 276 ds.getReadLock().lock(); 277 } 278 } 279 280 // Set the text label in the bottom status bar 281 // "if mouse moved only" was added to stop heap growing 282 if (!mouseNotMoved) statusBarElementUpdate(ms); 283 284 285 // Popup Information 286 // display them if the middle mouse button is pressed and 287 // keep them until the mouse is moved 288 if (middleMouseDown || isAtOldPosition) 289 { 290 Collection<OsmPrimitive> osms = mv.getAllNearest(ms.mousePos, OsmPrimitive.isUsablePredicate); 291 292 if (osms == null) 293 return; 294 295 final JPanel c = new JPanel(new GridBagLayout()); 296 final JLabel lbl = new JLabel( 297 "<html>"+tr("Middle click again to cycle through.<br>"+ 298 "Hold CTRL to select directly from this list with the mouse.<hr>")+"</html>", 299 null, 300 JLabel.HORIZONTAL 301 ); 302 lbl.setHorizontalAlignment(JLabel.LEFT); 303 c.add(lbl, GBC.eol().insets(2, 0, 2, 0)); 304 305 // Only cycle if the mouse has not been moved and the 306 // middle mouse button has been pressed at least twice 307 // (the reason for this is the popup != null check for 308 // isAtOldPosition, see above. This is a nice side 309 // effect though, because it does not change selection 310 // of the first middle click) 311 if(isAtOldPosition && middleMouseDown) { 312 // Hand down mouse modifiers so the SHIFT mod can be 313 // handled correctly (see funcion) 314 popupCycleSelection(osms, ms.modifiers); 315 } 316 317 // These labels may need to be updated from the outside 318 // so collect them 319 List<JLabel> lbls = new ArrayList<JLabel>(); 320 for (final OsmPrimitive osm : osms) { 321 JLabel l = popupBuildPrimitiveLabels(osm); 322 lbls.add(l); 323 c.add(l, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 0, 2, 2)); 324 } 325 326 popupShowPopup(popupCreatePopup(c, ms), lbls); 327 } else { 328 popupHidePopup(); 329 } 330 331 oldMousePos = ms.mousePos; 332 } catch (ConcurrentModificationException x) { 333 //x.printStackTrace(); 334 } catch (NullPointerException x) { 335 //x.printStackTrace(); 336 } finally { 337 if (ds != null) { 338 if(isAtOldPosition && middleMouseDown) { 339 ds.endUpdate(); 340 } else { 341 ds.getReadLock().unlock(); 342 } 343 } 344 } 345 } 346 }); 347 } catch (Exception e) { 348 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 final 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 final 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 413 * @param mouse modifiers 414 */ 415 private final 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 * @param popup 454 */ 455 private final void popupHidePopup() { 456 popupLabels = null; 457 if(popup == null) 458 return; 459 final Popup staticPopup = popup; 460 popup = null; 461 EventQueue.invokeLater(new Runnable(){ 462 public void run() { staticPopup.hide(); }}); 463 } 464 465 /** 466 * Tries to show the given popup, can be hidden using popupHideOldPopup 467 * If an old popup exists, it will be automatically hidden 468 * @param popup 469 */ 470 private final void popupShowPopup(Popup newPopup, List<JLabel> lbls) { 471 final Popup staticPopup = newPopup; 472 if(this.popup != null) { 473 // If an old popup exists, remove it when the new popup has been 474 // drawn to keep flickering to a minimum 475 final Popup staticOldPopup = this.popup; 476 EventQueue.invokeLater(new Runnable(){ 477 public void run() { 478 staticPopup.show(); 479 staticOldPopup.hide(); 480 } 481 }); 482 } else { 483 // There is no old popup 484 EventQueue.invokeLater(new Runnable(){ 485 public void run() { staticPopup.show(); }}); 486 } 487 this.popupLabels = lbls; 488 this.popup = newPopup; 489 } 490 491 /** 492 * This method should be called if the selection may have changed from 493 * outside of this class. This is the case when CTRL is pressed and the 494 * user clicks on the map instead of the popup. 495 */ 496 private final void popupUpdateLabels() { 497 if(this.popup == null || this.popupLabels == null) 498 return; 499 for(JLabel l : this.popupLabels) { 500 l.validate(); 501 } 502 } 503 504 /** 505 * Sets the colors for the given label depending on the selected status of 506 * the given OsmPrimitive 507 * 508 * @param lbl The label to color 509 * @param osm The primitive to derive the colors from 510 */ 511 private final void popupSetLabelColors(JLabel lbl, OsmPrimitive osm) { 512 DataSet ds = Main.main.getCurrentDataSet(); 513 if(ds.isSelected(osm)) { 514 lbl.setBackground(SystemColor.textHighlight); 515 lbl.setForeground(SystemColor.textHighlightText); 516 } else { 517 lbl.setBackground(SystemColor.control); 518 lbl.setForeground(SystemColor.controlText); 519 } 520 } 521 522 /** 523 * Builds the labels with all necessary listeners for the info popup for the 524 * given OsmPrimitive 525 * @param osm The primitive to create the label for 526 * @return 527 */ 528 private final JLabel popupBuildPrimitiveLabels(final OsmPrimitive osm) { 529 final StringBuilder text = new StringBuilder(); 530 String name = osm.getDisplayName(DefaultNameFormatter.getInstance()); 531 if (osm.isNewOrUndeleted() || osm.isModified()) { 532 name = "<i><b>"+ name + "*</b></i>"; 533 } 534 text.append(name); 535 536 boolean idShown = Main.pref.getBoolean("osm-primitives.showid"); 537 // fix #7557 - do not show ID twice 538 539 if (!osm.isNew() && !idShown) { 540 text.append(" [id="+osm.getId()+"]"); 541 } 542 543 if(osm.getUser() != null) { 544 text.append(" [" + tr("User:") + " " + osm.getUser().getName() + "]"); 545 } 546 547 for (String key : osm.keySet()) { 548 text.append("<br>" + key + "=" + osm.get(key)); 549 } 550 551 final JLabel l = new JLabel( 552 "<html>" +text.toString() + "</html>", 553 ImageProvider.get(osm.getDisplayType()), 554 JLabel.HORIZONTAL 555 ) { 556 // This is necessary so the label updates its colors when the 557 // selection is changed from the outside 558 @Override public void validate() { 559 super.validate(); 560 popupSetLabelColors(this, osm); 561 } 562 }; 563 l.setOpaque(true); 564 popupSetLabelColors(l, osm); 565 l.setFont(l.getFont().deriveFont(Font.PLAIN)); 566 l.setVerticalTextPosition(JLabel.TOP); 567 l.setHorizontalAlignment(JLabel.LEFT); 568 l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 569 l.addMouseListener(new MouseAdapter(){ 570 @Override public void mouseEntered(MouseEvent e) { 571 l.setBackground(SystemColor.info); 572 l.setForeground(SystemColor.infoText); 573 } 574 @Override public void mouseExited(MouseEvent e) { 575 popupSetLabelColors(l, osm); 576 } 577 @Override public void mouseClicked(MouseEvent e) { 578 DataSet ds = Main.main.getCurrentDataSet(); 579 // Let the user toggle the selection 580 ds.toggleSelected(osm); 581 l.validate(); 582 } 583 }); 584 // Sometimes the mouseEntered event is not catched, thus the label 585 // will not be highlighted, making it confusing. The MotionListener 586 // can correct this defect. 587 l.addMouseMotionListener(new MouseMotionListener() { 588 public void mouseMoved(MouseEvent e) { 589 l.setBackground(SystemColor.info); 590 l.setForeground(SystemColor.infoText); 591 } 592 public void mouseDragged(MouseEvent e) { 593 l.setBackground(SystemColor.info); 594 l.setForeground(SystemColor.infoText); 595 } 596 }); 597 return l; 598 } 599 } 600 601 /** 602 * Everything, the collector is interested of. Access must be synchronized. 603 * @author imi 604 */ 605 static class MouseState { 606 Point mousePos; 607 int modifiers; 608 } 609 /** 610 * The last sent mouse movement event. 611 */ 612 MouseState mouseState = new MouseState(); 613 614 private AWTEventListener awtListener = new AWTEventListener() { 615 public void eventDispatched(AWTEvent event) { 616 if (event instanceof InputEvent && 617 ((InputEvent)event).getComponent() == mv) { 618 synchronized (collector) { 619 mouseState.modifiers = ((InputEvent)event).getModifiersEx(); 620 if (event instanceof MouseEvent) { 621 mouseState.mousePos = ((MouseEvent)event).getPoint(); 622 } 623 collector.notify(); 624 } 625 } 626 } 627 }; 628 629 private MouseMotionListener mouseMotionListener = new MouseMotionListener() { 630 public void mouseMoved(MouseEvent e) { 631 synchronized (collector) { 632 mouseState.modifiers = e.getModifiersEx(); 633 mouseState.mousePos = e.getPoint(); 634 collector.notify(); 635 } 636 } 637 638 public void mouseDragged(MouseEvent e) { 639 mouseMoved(e); 640 } 641 }; 642 643 private KeyAdapter keyAdapter = new KeyAdapter() { 644 @Override public void keyPressed(KeyEvent e) { 645 synchronized (collector) { 646 mouseState.modifiers = e.getModifiersEx(); 647 collector.notify(); 648 } 649 } 650 651 @Override public void keyReleased(KeyEvent e) { 652 keyPressed(e); 653 } 654 }; 655 656 private void registerListeners() { 657 // Listen to keyboard/mouse events for pressing/releasing alt key and 658 // inform the collector. 659 try { 660 Toolkit.getDefaultToolkit().addAWTEventListener(awtListener, 661 AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); 662 } catch (SecurityException ex) { 663 mv.addMouseMotionListener(mouseMotionListener); 664 mv.addKeyListener(keyAdapter); 665 } 666 } 667 668 private void unregisterListeners() { 669 try { 670 Toolkit.getDefaultToolkit().removeAWTEventListener(awtListener); 671 } catch (SecurityException e) { 672 // Don't care, awtListener probably wasn't registered anyway 673 } 674 mv.removeMouseMotionListener(mouseMotionListener); 675 mv.removeKeyListener(keyAdapter); 676 } 677 678 679 /** 680 * Construct a new MapStatus and attach it to the map view. 681 * @param mapFrame The MapFrame the status line is part of. 682 */ 683 public MapStatus(final MapFrame mapFrame) { 684 this.mv = mapFrame.mapView; 685 this.collector = new Collector(mapFrame); 686 687 lonText.addMouseListener(Main.main.menu.jumpToAct); 688 latText.addMouseListener(Main.main.menu.jumpToAct); 689 690 // Listen for mouse movements and set the position text field 691 mv.addMouseMotionListener(new MouseMotionListener(){ 692 public void mouseDragged(MouseEvent e) { 693 mouseMoved(e); 694 } 695 public void mouseMoved(MouseEvent e) { 696 if (mv.center == null) 697 return; 698 // Do not update the view if ctrl is pressed. 699 if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == 0) { 700 CoordinateFormat mCord = CoordinateFormat.getDefaultFormat(); 701 LatLon p = mv.getLatLon(e.getX(),e.getY()); 702 latText.setText(p.latToString(mCord)); 703 lonText.setText(p.lonToString(mCord)); 704 } 705 } 706 }); 707 708 setLayout(new GridBagLayout()); 709 setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 710 711 add(latText, GBC.std()); 712 add(lonText, GBC.std().insets(3,0,0,0)); 713 add(headingText, GBC.std().insets(3,0,0,0)); 714 add(angleText, GBC.std().insets(3,0,0,0)); 715 add(distText, GBC.std().insets(3,0,0,0)); 716 717 helpText.setEditable(false); 718 add(nameText, GBC.std().insets(3,0,0,0)); 719 add(helpText, GBC.std().insets(3,0,0,0).fill(GBC.HORIZONTAL)); 720 721 progressBar.setMaximum(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX); 722 progressBar.setVisible(false); 723 GBC gbc = GBC.eol(); 724 gbc.ipadx = 100; 725 add(progressBar,gbc); 726 progressBar.addMouseListener(new MouseAdapter() { 727 @Override 728 public void mouseClicked(MouseEvent e) { 729 PleaseWaitProgressMonitor monitor = Main.currentProgressMonitor; 730 if (monitor != null) { 731 monitor.showForegroundDialog(); 732 } 733 } 734 }); 735 736 // The background thread 737 thread = new Thread(collector, "Map Status Collector"); 738 thread.setDaemon(true); 739 thread.start(); 740 } 741 742 public JPanel getAnglePanel() { 743 return angleText; 744 } 745 746 public String helpTopic() { 747 return ht("/Statusline"); 748 } 749 750 @Override 751 public synchronized void addMouseListener(MouseListener ml) { 752 //super.addMouseListener(ml); 753 lonText.addMouseListener(ml); 754 latText.addMouseListener(ml); 755 } 756 757 public void setHelpText(String t) { 758 setHelpText(null, t); 759 } 760 public void setHelpText(Object id, final String text) { 761 762 StatusTextHistory entry = new StatusTextHistory(id, text); 763 764 statusText.remove(entry); 765 statusText.add(entry); 766 767 GuiHelper.runInEDT(new Runnable() { 768 @Override 769 public void run() { 770 helpText.setText(text); 771 helpText.setToolTipText(text); 772 } 773 }); 774 } 775 public void resetHelpText(Object id) { 776 if (statusText.isEmpty()) 777 return; 778 779 StatusTextHistory entry = new StatusTextHistory(id, null); 780 if (statusText.get(statusText.size() - 1).equals(entry)) { 781 if (statusText.size() == 1) { 782 setHelpText(""); 783 } else { 784 StatusTextHistory history = statusText.get(statusText.size() - 2); 785 setHelpText(history.id, history.text); 786 } 787 } 788 statusText.remove(entry); 789 } 790 public void setAngle(double a) { 791 angleText.setText(a < 0 ? "--" : Math.round(a*10)/10.0 + " \u00B0"); 792 } 793 public void setHeading(double h) { 794 headingText.setText(h < 0 ? "--" : Math.round(h*10)/10.0 + " \u00B0"); 795 } 796 public void setDist(double dist) { 797 distText.setText(dist < 0 ? "--" : NavigatableComponent.getDistText(dist)); 798 } 799 public void activateAnglePanel(boolean activeFlag) { 800 angleText.setBackground(activeFlag ? ImageLabel.backColorActive : ImageLabel.backColor); 801 } 802 }