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 progressBar.repaint(); 124 MapStatus.this.doLayout(); 125 } 126 127 public void setCustomText(String text) { 128 this.customText = text; 129 updateText(); 130 } 131 132 public void setCurrentAction(String text) { 133 this.title = text; 134 updateText(); 135 } 136 137 public void setIndeterminate(boolean newValue) { 138 UIManager.put("ProgressBar.cycleTime", UIManager.getInt("ProgressBar.repaintInterval") * 100); 139 progressBar.setIndeterminate(newValue); 140 } 141 142 @Override 143 public void appendLogMessage(String message) { 144 if (message != null && !message.isEmpty()) { 145 System.out.println("appendLogMessage not implemented for background tasks. Message was: " + message); 146 } 147 } 148 149 } 150 151 final ImageLabel lonText = new ImageLabel("lon", tr("The geographic longitude at the mouse pointer."), 11); 152 final ImageLabel nameText = new ImageLabel("name", tr("The name of the object at the mouse pointer."), 20); 153 final JTextField helpText = new JTextField(); 154 final ImageLabel latText = new ImageLabel("lat", tr("The geographic latitude at the mouse pointer."), 11); 155 final ImageLabel angleText = new ImageLabel("angle", tr("The angle between the previous and the current way segment."), 6); 156 final ImageLabel headingText = new ImageLabel("heading", tr("The (compass) heading of the line segment being drawn."), 6); 157 final ImageLabel distText = new ImageLabel("dist", tr("The length of the new way segment being drawn."), 10); 158 final JProgressBar progressBar = new JProgressBar(); 159 public final BackgroundProgressMonitor progressMonitor = new BackgroundProgressMonitor(); 160 161 /** 162 * This is the thread that runs in the background and collects the information displayed. 163 * It gets destroyed by MapFrame.java/destroy() when the MapFrame itself is destroyed. 164 */ 165 public Thread thread; 166 167 private final List<StatusTextHistory> statusText = new ArrayList<StatusTextHistory>(); 168 169 private static class StatusTextHistory { 170 final Object id; 171 final String text; 172 173 public StatusTextHistory(Object id, String text) { 174 this.id = id; 175 this.text = text; 176 } 177 178 @Override 179 public boolean equals(Object obj) { 180 return obj instanceof StatusTextHistory && ((StatusTextHistory)obj).id == id; 181 } 182 183 @Override 184 public int hashCode() { 185 return System.identityHashCode(id); 186 } 187 } 188 189 /** 190 * The collector class that waits for notification and then update 191 * the display objects. 192 * 193 * @author imi 194 */ 195 private final class Collector implements Runnable { 196 /** 197 * the mouse position of the previous iteration. This is used to show 198 * the popup until the cursor is moved. 199 */ 200 private Point oldMousePos; 201 /** 202 * Contains the labels that are currently shown in the information 203 * popup 204 */ 205 private List<JLabel> popupLabels = null; 206 /** 207 * The popup displayed to show additional information 208 */ 209 private Popup popup; 210 211 private MapFrame parent; 212 213 public Collector(MapFrame parent) { 214 this.parent = parent; 215 } 216 217 /** 218 * Execution function for the Collector. 219 */ 220 public void run() { 221 registerListeners(); 222 try { 223 for (;;) { 224 225 final MouseState ms = new MouseState(); 226 synchronized (this) { 227 // TODO Would be better if the timeout wasn't necessary 228 try {wait(1000);} catch (InterruptedException e) {} 229 ms.modifiers = mouseState.modifiers; 230 ms.mousePos = mouseState.mousePos; 231 } 232 if (parent != Main.map) 233 return; // exit, if new parent. 234 235 // Do nothing, if required data is missing 236 if(ms.mousePos == null || mv.center == null) { 237 continue; 238 } 239 240 try { 241 EventQueue.invokeAndWait(new Runnable() { 242 243 @Override 244 public void run() { 245 // Freeze display when holding down CTRL 246 if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) { 247 // update the information popup's labels though, because 248 // the selection might have changed from the outside 249 popupUpdateLabels(); 250 return; 251 } 252 253 // This try/catch is a hack to stop the flooding bug reports about this. 254 // The exception needed to handle with in the first place, means that this 255 // access to the data need to be restarted, if the main thread modifies 256 // the data. 257 DataSet ds = null; 258 // The popup != null check is required because a left-click 259 // produces several events as well, which would make this 260 // variable true. Of course we only want the popup to show 261 // if the middle mouse button has been pressed in the first 262 // place 263 boolean mouseNotMoved = oldMousePos != null 264 && oldMousePos.equals(ms.mousePos); 265 boolean isAtOldPosition = mouseNotMoved && popup != null; 266 boolean middleMouseDown = (ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0; 267 try { 268 ds = mv.getCurrentDataSet(); 269 if (ds != null) { 270 // This is not perfect, if current dataset was changed during execution, the lock would be useless 271 if(isAtOldPosition && middleMouseDown) { 272 // Write lock is necessary when selecting in popupCycleSelection 273 // locks can not be upgraded -> if do read lock here and write lock later (in OsmPrimitive.updateFlags) 274 // then always occurs deadlock (#5814) 275 ds.beginUpdate(); 276 } else { 277 ds.getReadLock().lock(); 278 } 279 } 280 281 // Set the text label in the bottom status bar 282 // "if mouse moved only" was added to stop heap growing 283 if (!mouseNotMoved) statusBarElementUpdate(ms); 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 { 291 Collection<OsmPrimitive> osms = mv.getAllNearest(ms.mousePos, OsmPrimitive.isUsablePredicate); 292 293 if (osms == null) 294 return; 295 296 final JPanel c = new JPanel(new GridBagLayout()); 297 final JLabel lbl = new JLabel( 298 "<html>"+tr("Middle click again to cycle through.<br>"+ 299 "Hold CTRL to select directly from this list with the mouse.<hr>")+"</html>", 300 null, 301 JLabel.HORIZONTAL 302 ); 303 lbl.setHorizontalAlignment(JLabel.LEFT); 304 c.add(lbl, GBC.eol().insets(2, 0, 2, 0)); 305 306 // Only cycle if the mouse has not been moved and the 307 // middle mouse button has been pressed at least twice 308 // (the reason for this is the popup != null check for 309 // isAtOldPosition, see above. This is a nice side 310 // effect though, because it does not change selection 311 // of the first middle click) 312 if(isAtOldPosition && middleMouseDown) { 313 // Hand down mouse modifiers so the SHIFT mod can be 314 // handled correctly (see funcion) 315 popupCycleSelection(osms, ms.modifiers); 316 } 317 318 // These labels may need to be updated from the outside 319 // so collect them 320 List<JLabel> lbls = new ArrayList<JLabel>(); 321 for (final OsmPrimitive osm : osms) { 322 JLabel l = popupBuildPrimitiveLabels(osm); 323 lbls.add(l); 324 c.add(l, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 0, 2, 2)); 325 } 326 327 popupShowPopup(popupCreatePopup(c, ms), lbls); 328 } else { 329 popupHidePopup(); 330 } 331 332 oldMousePos = ms.mousePos; 333 } catch (ConcurrentModificationException x) { 334 //x.printStackTrace(); 335 } catch (NullPointerException x) { 336 //x.printStackTrace(); 337 } finally { 338 if (ds != null) { 339 if(isAtOldPosition && middleMouseDown) { 340 ds.endUpdate(); 341 } else { 342 ds.getReadLock().unlock(); 343 } 344 } 345 } 346 } 347 }); 348 } catch (Exception e) { 349 350 } 351 } 352 } finally { 353 unregisterListeners(); 354 } 355 } 356 357 /** 358 * Creates a popup for the given content next to the cursor. Tries to 359 * keep the popup on screen and shows a vertical scrollbar, if the 360 * screen is too small. 361 * @param content 362 * @param ms 363 * @return popup 364 */ 365 private final Popup popupCreatePopup(Component content, MouseState ms) { 366 Point p = mv.getLocationOnScreen(); 367 Dimension scrn = Toolkit.getDefaultToolkit().getScreenSize(); 368 369 // Create a JScrollPane around the content, in case there's not 370 // enough space 371 JScrollPane sp = new JScrollPane(content); 372 sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 373 sp.setBorder(BorderFactory.createRaisedBevelBorder()); 374 // Implement max-size content-independent 375 Dimension prefsize = sp.getPreferredSize(); 376 int w = Math.min(prefsize.width, Math.min(800, (scrn.width/2) - 16)); 377 int h = Math.min(prefsize.height, scrn.height - 10); 378 sp.setPreferredSize(new Dimension(w, h)); 379 380 int xPos = p.x + ms.mousePos.x + 16; 381 // Display the popup to the left of the cursor if it would be cut 382 // off on its right, but only if more space is available 383 if(xPos + w > scrn.width && xPos > scrn.width/2) { 384 xPos = p.x + ms.mousePos.x - 4 - w; 385 } 386 int yPos = p.y + ms.mousePos.y + 16; 387 // Move the popup up if it would be cut off at its bottom but do not 388 // move it off screen on the top 389 if(yPos + h > scrn.height - 5) { 390 yPos = Math.max(5, scrn.height - h - 5); 391 } 392 393 PopupFactory pf = PopupFactory.getSharedInstance(); 394 return pf.getPopup(mv, sp, xPos, yPos); 395 } 396 397 /** 398 * Calls this to update the element that is shown in the statusbar 399 * @param ms 400 */ 401 private final void statusBarElementUpdate(MouseState ms) { 402 final OsmPrimitive osmNearest = mv.getNearestNodeOrWay(ms.mousePos, OsmPrimitive.isUsablePredicate, false); 403 if (osmNearest != null) { 404 nameText.setText(osmNearest.getDisplayName(DefaultNameFormatter.getInstance())); 405 } else { 406 nameText.setText(tr("(no object)")); 407 } 408 } 409 410 /** 411 * Call this with a set of primitives to cycle through them. Method 412 * will automatically select the next item and update the map 413 * @param osms 414 * @param mouse modifiers 415 */ 416 private final void popupCycleSelection(Collection<OsmPrimitive> osms, int mods) { 417 DataSet ds = Main.main.getCurrentDataSet(); 418 // Find some items that are required for cycling through 419 OsmPrimitive firstItem = null; 420 OsmPrimitive firstSelected = null; 421 OsmPrimitive nextSelected = null; 422 for (final OsmPrimitive osm : osms) { 423 if(firstItem == null) { 424 firstItem = osm; 425 } 426 if(firstSelected != null && nextSelected == null) { 427 nextSelected = osm; 428 } 429 if(firstSelected == null && ds.isSelected(osm)) { 430 firstSelected = osm; 431 } 432 } 433 434 // Clear previous selection if SHIFT (add to selection) is not 435 // pressed. Cannot use "setSelected()" because it will cause a 436 // fireSelectionChanged event which is unnecessary at this point. 437 if((mods & MouseEvent.SHIFT_DOWN_MASK) == 0) { 438 ds.clearSelection(); 439 } 440 441 // This will cycle through the available items. 442 if(firstSelected == null) { 443 ds.addSelected(firstItem); 444 } else { 445 ds.clearSelection(firstSelected); 446 if(nextSelected != null) { 447 ds.addSelected(nextSelected); 448 } 449 } 450 } 451 452 /** 453 * Tries to hide the given popup 454 * @param popup 455 */ 456 private final void popupHidePopup() { 457 popupLabels = null; 458 if(popup == null) 459 return; 460 final Popup staticPopup = popup; 461 popup = null; 462 EventQueue.invokeLater(new Runnable(){ 463 public void run() { staticPopup.hide(); }}); 464 } 465 466 /** 467 * Tries to show the given popup, can be hidden using popupHideOldPopup 468 * If an old popup exists, it will be automatically hidden 469 * @param popup 470 */ 471 private final void popupShowPopup(Popup newPopup, List<JLabel> lbls) { 472 final Popup staticPopup = newPopup; 473 if(this.popup != null) { 474 // If an old popup exists, remove it when the new popup has been 475 // drawn to keep flickering to a minimum 476 final Popup staticOldPopup = this.popup; 477 EventQueue.invokeLater(new Runnable(){ 478 public void run() { 479 staticPopup.show(); 480 staticOldPopup.hide(); 481 } 482 }); 483 } else { 484 // There is no old popup 485 EventQueue.invokeLater(new Runnable(){ 486 public void run() { staticPopup.show(); }}); 487 } 488 this.popupLabels = lbls; 489 this.popup = newPopup; 490 } 491 492 /** 493 * This method should be called if the selection may have changed from 494 * outside of this class. This is the case when CTRL is pressed and the 495 * user clicks on the map instead of the popup. 496 */ 497 private final void popupUpdateLabels() { 498 if(this.popup == null || this.popupLabels == null) 499 return; 500 for(JLabel l : this.popupLabels) { 501 l.validate(); 502 } 503 } 504 505 /** 506 * Sets the colors for the given label depending on the selected status of 507 * the given OsmPrimitive 508 * 509 * @param lbl The label to color 510 * @param osm The primitive to derive the colors from 511 */ 512 private final void popupSetLabelColors(JLabel lbl, OsmPrimitive osm) { 513 DataSet ds = Main.main.getCurrentDataSet(); 514 if(ds.isSelected(osm)) { 515 lbl.setBackground(SystemColor.textHighlight); 516 lbl.setForeground(SystemColor.textHighlightText); 517 } else { 518 lbl.setBackground(SystemColor.control); 519 lbl.setForeground(SystemColor.controlText); 520 } 521 } 522 523 /** 524 * Builds the labels with all necessary listeners for the info popup for the 525 * given OsmPrimitive 526 * @param osm The primitive to create the label for 527 * @return 528 */ 529 private final JLabel popupBuildPrimitiveLabels(final OsmPrimitive osm) { 530 final StringBuilder text = new StringBuilder(); 531 String name = osm.getDisplayName(DefaultNameFormatter.getInstance()); 532 if (osm.isNewOrUndeleted() || osm.isModified()) { 533 name = "<i><b>"+ name + "*</b></i>"; 534 } 535 text.append(name); 536 537 boolean idShown = Main.pref.getBoolean("osm-primitives.showid"); 538 // fix #7557 - do not show ID twice 539 540 if (!osm.isNew() && !idShown) { 541 text.append(" [id="+osm.getId()+"]"); 542 } 543 544 if(osm.getUser() != null) { 545 text.append(" [" + tr("User:") + " " + osm.getUser().getName() + "]"); 546 } 547 548 for (String key : osm.keySet()) { 549 text.append("<br>" + key + "=" + osm.get(key)); 550 } 551 552 final JLabel l = new JLabel( 553 "<html>" +text.toString() + "</html>", 554 ImageProvider.get(osm.getDisplayType()), 555 JLabel.HORIZONTAL 556 ) { 557 // This is necessary so the label updates its colors when the 558 // selection is changed from the outside 559 @Override public void validate() { 560 super.validate(); 561 popupSetLabelColors(this, osm); 562 } 563 }; 564 l.setOpaque(true); 565 popupSetLabelColors(l, osm); 566 l.setFont(l.getFont().deriveFont(Font.PLAIN)); 567 l.setVerticalTextPosition(JLabel.TOP); 568 l.setHorizontalAlignment(JLabel.LEFT); 569 l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 570 l.addMouseListener(new MouseAdapter(){ 571 @Override public void mouseEntered(MouseEvent e) { 572 l.setBackground(SystemColor.info); 573 l.setForeground(SystemColor.infoText); 574 } 575 @Override public void mouseExited(MouseEvent e) { 576 popupSetLabelColors(l, osm); 577 } 578 @Override public void mouseClicked(MouseEvent e) { 579 DataSet ds = Main.main.getCurrentDataSet(); 580 // Let the user toggle the selection 581 ds.toggleSelected(osm); 582 l.validate(); 583 } 584 }); 585 // Sometimes the mouseEntered event is not catched, thus the label 586 // will not be highlighted, making it confusing. The MotionListener 587 // can correct this defect. 588 l.addMouseMotionListener(new MouseMotionListener() { 589 public void mouseMoved(MouseEvent e) { 590 l.setBackground(SystemColor.info); 591 l.setForeground(SystemColor.infoText); 592 } 593 public void mouseDragged(MouseEvent e) { 594 l.setBackground(SystemColor.info); 595 l.setForeground(SystemColor.infoText); 596 } 597 }); 598 return l; 599 } 600 } 601 602 /** 603 * Everything, the collector is interested of. Access must be synchronized. 604 * @author imi 605 */ 606 static class MouseState { 607 Point mousePos; 608 int modifiers; 609 } 610 /** 611 * The last sent mouse movement event. 612 */ 613 MouseState mouseState = new MouseState(); 614 615 private AWTEventListener awtListener = new AWTEventListener() { 616 public void eventDispatched(AWTEvent event) { 617 if (event instanceof InputEvent && 618 ((InputEvent)event).getComponent() == mv) { 619 synchronized (collector) { 620 mouseState.modifiers = ((InputEvent)event).getModifiersEx(); 621 if (event instanceof MouseEvent) { 622 mouseState.mousePos = ((MouseEvent)event).getPoint(); 623 } 624 collector.notify(); 625 } 626 } 627 } 628 }; 629 630 private MouseMotionListener mouseMotionListener = new MouseMotionListener() { 631 public void mouseMoved(MouseEvent e) { 632 synchronized (collector) { 633 mouseState.modifiers = e.getModifiersEx(); 634 mouseState.mousePos = e.getPoint(); 635 collector.notify(); 636 } 637 } 638 639 public void mouseDragged(MouseEvent e) { 640 mouseMoved(e); 641 } 642 }; 643 644 private KeyAdapter keyAdapter = new KeyAdapter() { 645 @Override public void keyPressed(KeyEvent e) { 646 synchronized (collector) { 647 mouseState.modifiers = e.getModifiersEx(); 648 collector.notify(); 649 } 650 } 651 652 @Override public void keyReleased(KeyEvent e) { 653 keyPressed(e); 654 } 655 }; 656 657 private void registerListeners() { 658 // Listen to keyboard/mouse events for pressing/releasing alt key and 659 // inform the collector. 660 try { 661 Toolkit.getDefaultToolkit().addAWTEventListener(awtListener, 662 AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); 663 } catch (SecurityException ex) { 664 mv.addMouseMotionListener(mouseMotionListener); 665 mv.addKeyListener(keyAdapter); 666 } 667 } 668 669 private void unregisterListeners() { 670 try { 671 Toolkit.getDefaultToolkit().removeAWTEventListener(awtListener); 672 } catch (SecurityException e) { 673 // Don't care, awtListener probably wasn't registered anyway 674 } 675 mv.removeMouseMotionListener(mouseMotionListener); 676 mv.removeKeyListener(keyAdapter); 677 } 678 679 680 /** 681 * Construct a new MapStatus and attach it to the map view. 682 * @param mapFrame The MapFrame the status line is part of. 683 */ 684 public MapStatus(final MapFrame mapFrame) { 685 this.mv = mapFrame.mapView; 686 this.collector = new Collector(mapFrame); 687 688 lonText.addMouseListener(Main.main.menu.jumpToAct); 689 latText.addMouseListener(Main.main.menu.jumpToAct); 690 691 // Listen for mouse movements and set the position text field 692 mv.addMouseMotionListener(new MouseMotionListener(){ 693 public void mouseDragged(MouseEvent e) { 694 mouseMoved(e); 695 } 696 public void mouseMoved(MouseEvent e) { 697 if (mv.center == null) 698 return; 699 // Do not update the view if ctrl is pressed. 700 if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == 0) { 701 CoordinateFormat mCord = CoordinateFormat.getDefaultFormat(); 702 LatLon p = mv.getLatLon(e.getX(),e.getY()); 703 latText.setText(p.latToString(mCord)); 704 lonText.setText(p.lonToString(mCord)); 705 } 706 } 707 }); 708 709 setLayout(new GridBagLayout()); 710 setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 711 712 add(latText, GBC.std()); 713 add(lonText, GBC.std().insets(3,0,0,0)); 714 add(headingText, GBC.std().insets(3,0,0,0)); 715 add(angleText, GBC.std().insets(3,0,0,0)); 716 add(distText, GBC.std().insets(3,0,0,0)); 717 718 helpText.setEditable(false); 719 add(nameText, GBC.std().insets(3,0,0,0)); 720 add(helpText, GBC.std().insets(3,0,0,0).fill(GBC.HORIZONTAL)); 721 722 progressBar.setMaximum(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX); 723 progressBar.setVisible(false); 724 GBC gbc = GBC.eol(); 725 gbc.ipadx = 100; 726 add(progressBar,gbc); 727 progressBar.addMouseListener(new MouseAdapter() { 728 @Override 729 public void mouseClicked(MouseEvent e) { 730 PleaseWaitProgressMonitor monitor = Main.currentProgressMonitor; 731 if (monitor != null) { 732 monitor.showForegroundDialog(); 733 } 734 } 735 }); 736 737 // The background thread 738 thread = new Thread(collector, "Map Status Collector"); 739 thread.setDaemon(true); 740 thread.start(); 741 } 742 743 public JPanel getAnglePanel() { 744 return angleText; 745 } 746 747 public String helpTopic() { 748 return ht("/Statusline"); 749 } 750 751 @Override 752 public synchronized void addMouseListener(MouseListener ml) { 753 //super.addMouseListener(ml); 754 lonText.addMouseListener(ml); 755 latText.addMouseListener(ml); 756 } 757 758 public void setHelpText(String t) { 759 setHelpText(null, t); 760 } 761 public void setHelpText(Object id, final String text) { 762 763 StatusTextHistory entry = new StatusTextHistory(id, text); 764 765 statusText.remove(entry); 766 statusText.add(entry); 767 768 GuiHelper.runInEDT(new Runnable() { 769 @Override 770 public void run() { 771 helpText.setText(text); 772 helpText.setToolTipText(text); 773 } 774 }); 775 } 776 public void resetHelpText(Object id) { 777 if (statusText.isEmpty()) 778 return; 779 780 StatusTextHistory entry = new StatusTextHistory(id, null); 781 if (statusText.get(statusText.size() - 1).equals(entry)) { 782 if (statusText.size() == 1) { 783 setHelpText(""); 784 } else { 785 StatusTextHistory history = statusText.get(statusText.size() - 2); 786 setHelpText(history.id, history.text); 787 } 788 } 789 statusText.remove(entry); 790 } 791 public void setAngle(double a) { 792 angleText.setText(a < 0 ? "--" : Math.round(a*10)/10.0 + " \u00B0"); 793 } 794 public void setHeading(double h) { 795 headingText.setText(h < 0 ? "--" : Math.round(h*10)/10.0 + " \u00B0"); 796 } 797 public void setDist(double dist) { 798 distText.setText(dist < 0 ? "--" : NavigatableComponent.getDistText(dist)); 799 } 800 public void activateAnglePanel(boolean activeFlag) { 801 angleText.setBackground(activeFlag ? ImageLabel.backColorActive : ImageLabel.backColor); 802 } 803 }