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

Last change on this file was 18815, checked in by taylor.smock, 8 months ago

Improve paste behavior of large amounts of data

Both SelectionListDialog and MapStatus can use the consolidated events, which
drastically reduces the amount of time spent pasting large amounts of data. This
reduces the time spent in their listener methods by >99%.

The test paste of 41k objects took ~20s after the change and ~80s prior to the change.

A good chunk of the remaining time spent pasting is from checking to see whether
an upload is needed. Fixing that will take a lot more work, since there is
currently no concept of consolidated events for that specific listener type.

This was found while investigating #4145.

  • Property svn:eol-style set to native
File size: 50.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.AWTEvent;
9import java.awt.Color;
10import java.awt.Component;
11import java.awt.Cursor;
12import java.awt.Dimension;
13import java.awt.EventQueue;
14import java.awt.Font;
15import java.awt.GraphicsEnvironment;
16import java.awt.GridBagLayout;
17import java.awt.MouseInfo;
18import java.awt.Point;
19import java.awt.PointerInfo;
20import java.awt.SystemColor;
21import java.awt.Toolkit;
22import java.awt.event.AWTEventListener;
23import java.awt.event.ActionEvent;
24import java.awt.event.ComponentAdapter;
25import java.awt.event.ComponentEvent;
26import java.awt.event.InputEvent;
27import java.awt.event.KeyAdapter;
28import java.awt.event.KeyEvent;
29import java.awt.event.MouseAdapter;
30import java.awt.event.MouseEvent;
31import java.awt.event.MouseListener;
32import java.awt.event.MouseMotionListener;
33import java.lang.reflect.InvocationTargetException;
34import java.text.DecimalFormat;
35import java.util.ArrayList;
36import java.util.Collection;
37import java.util.Comparator;
38import java.util.ConcurrentModificationException;
39import java.util.Iterator;
40import java.util.List;
41import java.util.Objects;
42import java.util.concurrent.BlockingQueue;
43import java.util.concurrent.LinkedBlockingQueue;
44import java.util.stream.Collectors;
45
46import javax.swing.AbstractAction;
47import javax.swing.BorderFactory;
48import javax.swing.JCheckBoxMenuItem;
49import javax.swing.JLabel;
50import javax.swing.JMenuItem;
51import javax.swing.JPanel;
52import javax.swing.JPopupMenu;
53import javax.swing.JProgressBar;
54import javax.swing.JScrollPane;
55import javax.swing.JSeparator;
56import javax.swing.Popup;
57import javax.swing.PopupFactory;
58import javax.swing.UIManager;
59import javax.swing.event.PopupMenuEvent;
60import javax.swing.event.PopupMenuListener;
61
62import org.openstreetmap.josm.data.SystemOfMeasurement;
63import org.openstreetmap.josm.data.SystemOfMeasurement.SoMChangeListener;
64import org.openstreetmap.josm.data.coor.ILatLon;
65import org.openstreetmap.josm.data.coor.LatLon;
66import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager;
67import org.openstreetmap.josm.data.coor.conversion.DMSCoordinateFormat;
68import org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat;
69import org.openstreetmap.josm.data.coor.conversion.ProjectedCoordinateFormat;
70import org.openstreetmap.josm.data.osm.DataSelectionListener;
71import org.openstreetmap.josm.data.osm.DataSet;
72import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
73import org.openstreetmap.josm.data.osm.IPrimitive;
74import org.openstreetmap.josm.data.osm.OsmPrimitive;
75import org.openstreetmap.josm.data.osm.Way;
76import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
77import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
78import org.openstreetmap.josm.data.osm.event.DataSetListener;
79import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
80import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
81import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
82import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
83import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
84import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
85import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
86import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
87import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
88import org.openstreetmap.josm.data.preferences.AbstractProperty;
89import org.openstreetmap.josm.data.preferences.BooleanProperty;
90import org.openstreetmap.josm.data.preferences.DoubleProperty;
91import org.openstreetmap.josm.data.preferences.NamedColorProperty;
92import org.openstreetmap.josm.gui.NavigatableComponent.ZoomChangeListener;
93import org.openstreetmap.josm.gui.help.Helpful;
94import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
95import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor.ProgressMonitorDialog;
96import org.openstreetmap.josm.gui.util.GuiHelper;
97import org.openstreetmap.josm.gui.widgets.ImageLabel;
98import org.openstreetmap.josm.gui.widgets.JosmTextField;
99import org.openstreetmap.josm.spi.preferences.Config;
100import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
101import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
102import org.openstreetmap.josm.tools.ColorHelper;
103import org.openstreetmap.josm.tools.Destroyable;
104import org.openstreetmap.josm.tools.GBC;
105import org.openstreetmap.josm.tools.ImageProvider;
106import org.openstreetmap.josm.tools.Logging;
107import org.openstreetmap.josm.tools.SubclassFilteredCollection;
108import org.openstreetmap.josm.tools.Utils;
109
110/**
111 * A component that manages some status information display about the map.
112 * It keeps a status line below the map up to date and displays some tooltip
113 * information if the user hold the mouse long enough at some point.
114 *
115 * All this is done in background to not disturb other processes.
116 *
117 * The background thread does not alter any data of the map (read only thread).
118 * Also it is rather fail safe. In case of some error in the data, it just does
119 * nothing instead of whining and complaining.
120 *
121 * @author imi
122 */
123public final class MapStatus extends JPanel implements
124 Helpful, Destroyable, PreferenceChangedListener, SoMChangeListener, DataSelectionListener, DataSetListener, ZoomChangeListener {
125
126 private final DecimalFormat DECIMAL_FORMAT = new DecimalFormat(Config.getPref().get("statusbar.decimal-format", "0.00"));
127 private static final AbstractProperty<Double> DISTANCE_THRESHOLD = new DoubleProperty("statusbar.distance-threshold", 0.01).cached();
128
129 private static final AbstractProperty<Boolean> SHOW_ID = new BooleanProperty("osm-primitives.showid", false);
130
131 private static final List<SystemOfMeasurement> SORTED_SYSTEM_OF_MEASUREMENTS = SystemOfMeasurement.ALL_SYSTEMS.values().stream()
132 .sorted(Comparator.comparing(SystemOfMeasurement::toString))
133 .collect(Collectors.toList());
134
135 /**
136 * Property for map status background color.
137 * @since 6789
138 */
139 public static final NamedColorProperty PROP_BACKGROUND_COLOR = new NamedColorProperty(
140 marktr("Status bar background"), ColorHelper.html2color("#b8cfe5"));
141
142 /**
143 * Property for map status background color (active state).
144 * @since 6789
145 */
146 public static final NamedColorProperty PROP_ACTIVE_BACKGROUND_COLOR = new NamedColorProperty(
147 marktr("Status bar background: active"), ColorHelper.html2color("#aaff5e"));
148
149 /**
150 * Property for map status foreground color.
151 * @since 6789
152 */
153 public static final NamedColorProperty PROP_FOREGROUND_COLOR = new NamedColorProperty(
154 marktr("Status bar foreground"), Color.black);
155
156 /**
157 * Property for map status foreground color (active state).
158 * @since 6789
159 */
160 public static final NamedColorProperty PROP_ACTIVE_FOREGROUND_COLOR = new NamedColorProperty(
161 marktr("Status bar foreground: active"), Color.black);
162
163 /**
164 * The MapView this status belongs to.
165 */
166 private final MapView mv;
167 private final transient Collector collector;
168
169 static final class ShowMonitorDialogMouseAdapter extends MouseAdapter {
170 @Override
171 public void mouseClicked(MouseEvent e) {
172 PleaseWaitProgressMonitor monitor = PleaseWaitProgressMonitor.getCurrent();
173 if (monitor != null) {
174 monitor.showForegroundDialog();
175 }
176 }
177 }
178
179 static final class JumpToOnLeftClickMouseAdapter extends MouseAdapter {
180 @Override
181 public void mouseClicked(MouseEvent e) {
182 if (e.getButton() != MouseEvent.BUTTON3) {
183 MainApplication.getMenu().jumpToAct.showJumpToDialog();
184 }
185 }
186 }
187
188 /**
189 * The progress monitor that is used to display the progress if the user selects to run in background
190 */
191 public class BackgroundProgressMonitor implements ProgressMonitorDialog {
192
193 private String title;
194 private String customText;
195
196 private void updateText() {
197 if (!Utils.isEmpty(customText)) {
198 progressBar.setToolTipText(tr("{0} ({1})", title, customText));
199 } else {
200 progressBar.setToolTipText(title);
201 }
202 }
203
204 @Override
205 public void setVisible(boolean visible) {
206 progressBar.setVisible(visible);
207 }
208
209 @Override
210 public void updateProgress(int progress) {
211 progressBar.setValue(progress);
212 progressBar.repaint();
213 MapStatus.this.doLayout();
214 }
215
216 @Override
217 public void setCustomText(String text) {
218 this.customText = text;
219 updateText();
220 }
221
222 @Override
223 public void setCurrentAction(String text) {
224 this.title = text;
225 updateText();
226 }
227
228 @Override
229 public void setIndeterminate(boolean newValue) {
230 UIManager.put("ProgressBar.cycleTime", UIManager.getInt("ProgressBar.repaintInterval") * 100);
231 progressBar.setIndeterminate(newValue);
232 }
233
234 @Override
235 public void appendLogMessage(String message) {
236 if (!Utils.isEmpty(message)) {
237 Logging.info("appendLogMessage not implemented for background tasks. Message was: " + message);
238 }
239 }
240
241 }
242
243 /** The {@link ICoordinateFormat} set in the previous update */
244 private transient ICoordinateFormat previousCoordinateFormat;
245 private final ImageLabel latText = new ImageLabel("lat",
246 null, DMSCoordinateFormat.INSTANCE.latToString(LatLon.SOUTH_POLE).length(), PROP_BACKGROUND_COLOR.get());
247 private final ImageLabel lonText = new ImageLabel("lon",
248 null, DMSCoordinateFormat.INSTANCE.lonToString(new LatLon(0, 180)).length(), PROP_BACKGROUND_COLOR.get());
249 private final ImageLabel headingText = new ImageLabel("heading",
250 tr("The (compass) heading of the line segment being drawn."),
251 DECIMAL_FORMAT.format(360).length() + 1, PROP_BACKGROUND_COLOR.get());
252 private final ImageLabel angleText = new ImageLabel("angle",
253 tr("The angle between the previous and the current way segment."),
254 DECIMAL_FORMAT.format(360).length() + 1, PROP_BACKGROUND_COLOR.get());
255 private final ImageLabel distText = new ImageLabel("dist",
256 tr("The length of the new way segment being drawn."), 10, PROP_BACKGROUND_COLOR.get());
257 private final ImageLabel nameText = new ImageLabel("name",
258 tr("The name of the object at the mouse pointer."),
259 getNameLabelCharacterCount(MainApplication.getMainFrame()), PROP_BACKGROUND_COLOR.get());
260 private final JosmTextField helpText = new JosmTextField(null, null, 0, false);
261 private final JProgressBar progressBar = new JProgressBar();
262 private final transient ComponentAdapter mvComponentAdapter;
263 /**
264 * The progress monitor for displaying a background progress
265 */
266 public final transient BackgroundProgressMonitor progressMonitor = new BackgroundProgressMonitor();
267
268 // Distance value displayed in distText, stored if refresh needed after a change of system of measurement
269 private double distValue;
270
271 // Determines if angle panel is enabled or not
272 private boolean angleEnabled;
273
274 /**
275 * This is the thread that runs in the background and collects the information displayed.
276 * It gets destroyed by destroy() when the MapFrame itself is destroyed.
277 */
278 private final transient Thread thread;
279
280 private final transient List<StatusTextHistory> statusText = new ArrayList<>();
281
282 protected static final class StatusTextHistory {
283 private final Object id;
284 private final String text;
285
286 StatusTextHistory(Object id, String text) {
287 this.id = id;
288 this.text = text;
289 }
290
291 @Override
292 public boolean equals(Object obj) {
293 return obj instanceof StatusTextHistory && ((StatusTextHistory) obj).id == id;
294 }
295
296 @Override
297 public int hashCode() {
298 return System.identityHashCode(id);
299 }
300 }
301
302 /**
303 * The collector class that waits for notification and then update the display objects.
304 *
305 * @author imi
306 */
307 private final class Collector implements Runnable {
308 private final class CollectorWorker implements Runnable {
309 private final MouseState ms;
310
311 private CollectorWorker(MouseState ms) {
312 this.ms = ms;
313 }
314
315 @Override
316 public void run() {
317 // Freeze display when holding down CTRL
318 if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) {
319 // update the information popup's labels though, because the selection might have changed from the outside
320 popupUpdateLabels();
321 return;
322 }
323
324 // The popup != null check is required because a left-click produces several events as well,
325 // which would make this variable true. Of course we only want the popup to show
326 // if the middle mouse button has been pressed in the first place
327 boolean mouseNotMoved = oldMousePos != null && oldMousePos.equals(ms.mousePos);
328 boolean isAtOldPosition = mouseNotMoved && popup != null;
329 boolean middleMouseDown = (ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0;
330
331 DataSet ds = mv.getLayerManager().getActiveDataSet();
332 if (ds != null) {
333 // This is not perfect, if current dataset was changed during execution, the lock would be useless
334 if (isAtOldPosition && middleMouseDown) {
335 // Write lock is necessary when selecting in popupCycleSelection
336 // locks can not be upgraded -> if do read lock here and write lock later
337 // (in OsmPrimitive.updateFlags) then always occurs deadlock (#5814)
338 ds.beginUpdate();
339 } else {
340 ds.getReadLock().lock();
341 }
342 }
343 // This try/catch is a hack to stop the flooding bug reports about this.
344 // The exception needed to handle with in the first place, means that this
345 // access to the data need to be restarted, if the main thread modifies the data.
346 try {
347 // Set the text label in the bottom status bar
348 // "if mouse moved only" was added to stop heap growing
349 if (!mouseNotMoved) {
350 statusBarElementUpdate(ms);
351 }
352
353 // Popup Information
354 // display them if the middle mouse button is pressed and keep them until the mouse is moved
355 if (middleMouseDown || isAtOldPosition) {
356 Collection<OsmPrimitive> osms = mv.getAllNearest(ms.mousePos, OsmPrimitive::isSelectable);
357
358 final JPanel c = new JPanel(new GridBagLayout());
359 final JLabel lbl = new JLabel(
360 "<html>"+tr("Middle click again to cycle through.<br>"+
361 "Hold CTRL to select directly from this list with the mouse.<hr>")+"</html>",
362 null,
363 JLabel.HORIZONTAL
364 );
365 lbl.setHorizontalAlignment(JLabel.LEADING);
366 c.add(lbl, GBC.eol().insets(2, 0, 2, 0));
367
368 // Only cycle if the mouse has not been moved and the middle mouse button has been pressed at least
369 // twice (the reason for this is the popup != null check for isAtOldPosition, see above.
370 // This is a nice side effect though, because it does not change selection of the first middle click)
371 if (isAtOldPosition && middleMouseDown) {
372 // Hand down mouse modifiers so the SHIFT mod can be handled correctly (see function)
373 popupCycleSelection(osms, ms.modifiers);
374 }
375
376 // These labels may need to be updated from the outside so collect them
377 List<JLabel> lbls = new ArrayList<>(osms.size());
378 for (final OsmPrimitive osm : osms) {
379 JLabel l = popupBuildPrimitiveLabels(osm);
380 lbls.add(l);
381 c.add(l, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 0, 2, 2));
382 }
383
384 popupShowPopup(popupCreatePopup(c, ms), lbls);
385 } else {
386 popupHidePopup();
387 }
388
389 oldMousePos = ms.mousePos;
390 } catch (ConcurrentModificationException ex) {
391 Logging.warn(ex);
392 } finally {
393 if (ds != null) {
394 if (isAtOldPosition && middleMouseDown) {
395 ds.endUpdate();
396 } else {
397 ds.getReadLock().unlock();
398 }
399 }
400 }
401 }
402 }
403
404 /**
405 * the mouse position of the previous iteration. This is used to show
406 * the popup until the cursor is moved.
407 */
408 private Point oldMousePos;
409 /**
410 * Contains the labels that are currently shown in the information
411 * popup
412 */
413 private List<JLabel> popupLabels;
414 /**
415 * The popup displayed to show additional information
416 */
417 private Popup popup;
418
419 private final MapFrame parent;
420
421 private final BlockingQueue<MouseState> incomingMouseState = new LinkedBlockingQueue<>();
422
423 private Point lastMousePos;
424
425 Collector(MapFrame parent) {
426 this.parent = parent;
427 }
428
429 /**
430 * Execution function for the Collector.
431 */
432 @Override
433 public void run() {
434 registerListeners();
435 try {
436 for (;;) {
437 try {
438 final MouseState ms = incomingMouseState.take();
439 if (parent != MainApplication.getMap())
440 return; // exit, if new parent.
441
442 // Do nothing, if required data is missing
443 if (ms.mousePos == null || mv.getCenter() == null) {
444 continue;
445 }
446
447 EventQueue.invokeAndWait(new CollectorWorker(ms));
448 } catch (InvocationTargetException e) {
449 Logging.warn(e);
450 }
451 }
452 } catch (InterruptedException e) {
453 // Occurs frequently during JOSM shutdown, log set to trace only
454 Logging.trace("InterruptedException in "+MapStatus.class.getSimpleName());
455 Thread.currentThread().interrupt();
456 } finally {
457 unregisterListeners();
458 }
459 }
460
461 /**
462 * Creates a popup for the given content next to the cursor. Tries to
463 * keep the popup on screen and shows a vertical scrollbar, if the
464 * screen is too small.
465 * @param content popup content
466 * @param ms mouse state
467 * @return popup
468 */
469 private Popup popupCreatePopup(Component content, MouseState ms) {
470 Point p = mv.getLocationOnScreen();
471 Dimension scrn = GuiHelper.getScreenSize();
472
473 // Create a JScrollPane around the content, in case there's not enough space
474 JScrollPane sp = GuiHelper.embedInVerticalScrollPane(content);
475 sp.setBorder(BorderFactory.createRaisedBevelBorder());
476 // Implement max-size content-independent
477 Dimension prefsize = sp.getPreferredSize();
478 int w = Math.min(prefsize.width, Math.min(800, (scrn.width/2) - 16));
479 int h = Math.min(prefsize.height, scrn.height - 10);
480 sp.setPreferredSize(new Dimension(w, h));
481
482 int xPos = p.x + ms.mousePos.x + 16;
483 // Display the popup to the left of the cursor if it would be cut
484 // off on its right, but only if more space is available
485 if (xPos + w > scrn.width && xPos > scrn.width/2) {
486 xPos = p.x + ms.mousePos.x - 4 - w;
487 }
488 int yPos = p.y + ms.mousePos.y + 16;
489 // Move the popup up if it would be cut off at its bottom but do not
490 // move it off screen on the top
491 if (yPos + h > scrn.height - 5) {
492 yPos = Math.max(5, scrn.height - h - 5);
493 }
494
495 PopupFactory pf = PopupFactory.getSharedInstance();
496 return pf.getPopup(mv, sp, xPos, yPos);
497 }
498
499 /**
500 * Calls this to update the element that is shown in the statusbar
501 * @param ms mouse state
502 */
503 private void statusBarElementUpdate(MouseState ms) {
504 final OsmPrimitive osmNearest = mv.getNearestNodeOrWay(ms.mousePos, OsmPrimitive::isUsable, false);
505 if (osmNearest != null) {
506 nameText.setText(osmNearest.getDisplayName(DefaultNameFormatter.getInstance()));
507 } else {
508 nameText.setText(tr("(no object)"));
509 }
510 }
511
512 /**
513 * Call this with a set of primitives to cycle through them. Method
514 * will automatically select the next item and update the map
515 * @param osms primitives to cycle through
516 * @param mods modifiers (i.e. control keys)
517 */
518 private void popupCycleSelection(Collection<OsmPrimitive> osms, int mods) {
519 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
520 // Find some items that are required for cycling through
521 OsmPrimitive firstItem = null;
522 OsmPrimitive firstSelected = null;
523 OsmPrimitive nextSelected = null;
524 for (final OsmPrimitive osm : osms) {
525 if (firstItem == null) {
526 firstItem = osm;
527 }
528 if (firstSelected != null && nextSelected == null) {
529 nextSelected = osm;
530 }
531 if (firstSelected == null && ds.isSelected(osm)) {
532 firstSelected = osm;
533 }
534 }
535
536 // Clear previous selection if SHIFT (add to selection) is not
537 // pressed. Cannot use "setSelected()" because it will cause a
538 // fireSelectionChanged event which is unnecessary at this point.
539 if ((mods & MouseEvent.SHIFT_DOWN_MASK) == 0) {
540 ds.clearSelection();
541 }
542
543 // This will cycle through the available items.
544 if (firstSelected != null) {
545 ds.clearSelection(firstSelected);
546 if (nextSelected != null) {
547 ds.addSelected(nextSelected);
548 }
549 } else if (firstItem != null) {
550 ds.addSelected(firstItem);
551 }
552 }
553
554 /**
555 * Tries to hide the given popup
556 */
557 private void popupHidePopup() {
558 popupLabels = null;
559 if (popup == null)
560 return;
561 final Popup staticPopup = popup;
562 popup = null;
563 EventQueue.invokeLater(staticPopup::hide);
564 }
565
566 /**
567 * Tries to show the given popup, can be hidden using {@link #popupHidePopup}
568 * If an old popup exists, it will be automatically hidden
569 * @param newPopup popup to show
570 * @param lbls labels to show (see {@link #popupLabels})
571 */
572 private void popupShowPopup(Popup newPopup, List<JLabel> lbls) {
573 final Popup staticPopup = newPopup;
574 if (this.popup != null) {
575 // If an old popup exists, remove it when the new popup has been drawn to keep flickering to a minimum
576 final Popup staticOldPopup = this.popup;
577 EventQueue.invokeLater(() -> {
578 staticPopup.show();
579 staticOldPopup.hide();
580 });
581 } else {
582 // There is no old popup
583 EventQueue.invokeLater(staticPopup::show);
584 }
585 this.popupLabels = lbls;
586 this.popup = newPopup;
587 }
588
589 /**
590 * This method should be called if the selection may have changed from
591 * outside of this class. This is the case when CTRL is pressed and the
592 * user clicks on the map instead of the popup.
593 */
594 private void popupUpdateLabels() {
595 if (this.popup == null || this.popupLabels == null)
596 return;
597 for (JLabel l : this.popupLabels) {
598 l.validate();
599 }
600 }
601
602 /**
603 * Sets the colors for the given label depending on the selected status of
604 * the given OsmPrimitive
605 *
606 * @param lbl The label to color
607 * @param osm The primitive to derive the colors from
608 */
609 private void popupSetLabelColors(JLabel lbl, IPrimitive osm) {
610 if (osm.isSelected()) {
611 lbl.setBackground(SystemColor.textHighlight);
612 lbl.setForeground(SystemColor.textHighlightText);
613 } else {
614 lbl.setBackground(SystemColor.control);
615 lbl.setForeground(SystemColor.controlText);
616 }
617 }
618
619 /**
620 * Builds the labels with all necessary listeners for the info popup for the
621 * given OsmPrimitive
622 * @param osm The primitive to create the label for
623 * @return labels for info popup
624 */
625 private JLabel popupBuildPrimitiveLabels(final OsmPrimitive osm) {
626 final StringBuilder text = new StringBuilder(32);
627 String name = Utils.escapeReservedCharactersHTML(osm.getDisplayName(DefaultNameFormatter.getInstance()));
628 if (osm.isNewOrUndeleted() || osm.isModified()) {
629 name = "<i><b>"+ name + "*</b></i>";
630 }
631 text.append(name);
632
633 boolean idShown = SHOW_ID.get();
634 // fix #7557 - do not show ID twice
635
636 if (!osm.isNew() && !idShown) {
637 text.append(" [id=").append(osm.getId()).append(']');
638 }
639
640 if (osm.getUser() != null) {
641 text.append(" [").append(tr("User:")).append(' ')
642 .append(Utils.escapeReservedCharactersHTML(osm.getUser().getName())).append(']');
643 }
644
645 osm.visitKeys((primitive, key, value) -> text.append("<br>").append(key).append('=').append(value));
646
647 final JLabel l = new JLabel(
648 "<html>" + text.toString() + "</html>",
649 ImageProvider.get(osm.getDisplayType()),
650 JLabel.HORIZONTAL
651 ) {
652 // This is necessary so the label updates its colors when the
653 // selection is changed from the outside
654 @Override
655 public void validate() {
656 super.validate();
657 popupSetLabelColors(this, osm);
658 }
659 };
660 l.setOpaque(true);
661 popupSetLabelColors(l, osm);
662 l.setFont(l.getFont().deriveFont(Font.PLAIN));
663 l.setVerticalTextPosition(JLabel.TOP);
664 l.setHorizontalAlignment(JLabel.LEADING);
665 l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
666 l.addMouseListener(new MouseAdapter() {
667 @Override
668 public void mouseEntered(MouseEvent e) {
669 l.setBackground(SystemColor.info);
670 l.setForeground(SystemColor.infoText);
671 }
672
673 @Override
674 public void mouseExited(MouseEvent e) {
675 popupSetLabelColors(l, osm);
676 }
677
678 @Override
679 public void mouseClicked(MouseEvent e) {
680 DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
681 // Let the user toggle the selection
682 ds.toggleSelected(osm);
683 l.validate();
684 }
685 });
686 // Sometimes the mouseEntered event is not catched, thus the label
687 // will not be highlighted, making it confusing. The MotionListener can correct this defect.
688 l.addMouseMotionListener(new MouseMotionListener() {
689 @Override
690 public void mouseMoved(MouseEvent e) {
691 l.setBackground(SystemColor.info);
692 l.setForeground(SystemColor.infoText);
693 }
694
695 @Override
696 public void mouseDragged(MouseEvent e) {
697 mouseMoved(e);
698 }
699 });
700 return l;
701 }
702
703 /**
704 * Called whenever the mouse position or modifiers changed.
705 * @param mousePos The new mouse position. <code>null</code> if it did not change.
706 * @param modifiers The new modifiers.
707 */
708 public synchronized void updateMousePosition(Point mousePos, int modifiers) {
709 if (mousePos != null) {
710 lastMousePos = mousePos;
711 }
712 MouseState ms = new MouseState(lastMousePos, modifiers);
713 // remove mouse states that are in the queue. Our mouse state is newer.
714 incomingMouseState.clear();
715 if (!incomingMouseState.offer(ms)) {
716 Logging.warn("Unable to handle new MouseState: " + ms);
717 }
718 }
719 }
720
721 /**
722 * Everything, the collector is interested of. Access must be synchronized.
723 * @author imi
724 */
725 private static class MouseState {
726 private final Point mousePos;
727 private final int modifiers;
728
729 MouseState(Point mousePos, int modifiers) {
730 this.mousePos = mousePos;
731 this.modifiers = modifiers;
732 }
733 }
734
735 private final transient AWTEventListener awtListener;
736
737 private final transient MouseMotionListener mouseMotionListener = new MouseMotionListener() {
738 @Override
739 public void mouseMoved(MouseEvent e) {
740 synchronized (collector) {
741 collector.updateMousePosition(e.getPoint(), e.getModifiersEx());
742 }
743 }
744
745 @Override
746 public void mouseDragged(MouseEvent e) {
747 mouseMoved(e);
748 }
749 };
750
751 private final transient KeyAdapter keyAdapter = new KeyAdapter() {
752 @Override public void keyPressed(KeyEvent e) {
753 synchronized (collector) {
754 collector.updateMousePosition(null, e.getModifiersEx());
755 }
756 }
757
758 @Override public void keyReleased(KeyEvent e) {
759 keyPressed(e);
760 }
761 };
762
763 /** see #19887: determine if the {@code distValue} field should be filled with length of selected object */
764 private boolean autoLength = true;
765
766 private void registerListeners() {
767 // Listen to keyboard/mouse events for pressing/releasing alt key and inform the collector.
768 try {
769 Toolkit.getDefaultToolkit().addAWTEventListener(awtListener,
770 AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
771 } catch (SecurityException ex) {
772 Logging.trace(ex);
773 mv.addMouseMotionListener(mouseMotionListener);
774 mv.addKeyListener(keyAdapter);
775 }
776 }
777
778 private void unregisterListeners() {
779 try {
780 Toolkit.getDefaultToolkit().removeAWTEventListener(awtListener);
781 } catch (SecurityException e) {
782 // Don't care, awtListener probably wasn't registered anyway
783 Logging.trace(e);
784 }
785 mv.removeMouseMotionListener(mouseMotionListener);
786 mv.removeKeyListener(keyAdapter);
787 }
788
789 private class MapStatusPopupMenu extends JPopupMenu {
790
791 private final JMenuItem jumpButton = add(MainApplication.getMenu().jumpToAct);
792
793 /** Icons for selecting {@link SystemOfMeasurement} */
794 private final Collection<JCheckBoxMenuItem> somItems = new ArrayList<>();
795 /** Icons for selecting {@link ICoordinateFormat} */
796 private final Collection<JCheckBoxMenuItem> coordinateFormatItems = new ArrayList<>();
797
798 private final JSeparator separator = new JSeparator();
799
800 private final JMenuItem doNotHide = new JCheckBoxMenuItem(new AbstractAction(tr("Do not hide status bar")) {
801 @Override
802 public void actionPerformed(ActionEvent e) {
803 boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
804 Config.getPref().putBoolean("statusbar.always-visible", sel);
805 }
806 });
807
808 MapStatusPopupMenu() {
809 for (final SystemOfMeasurement som : SORTED_SYSTEM_OF_MEASUREMENTS) {
810 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new AbstractAction(som.toString()) {
811 @Override
812 public void actionPerformed(ActionEvent e) {
813 updateSystemOfMeasurement(som);
814 }
815 });
816 somItems.add(item);
817 add(item);
818 }
819 for (final ICoordinateFormat format : CoordinateFormatManager.getCoordinateFormats()) {
820 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new AbstractAction(format.getDisplayName()) {
821 @Override
822 public void actionPerformed(ActionEvent e) {
823 CoordinateFormatManager.setCoordinateFormat(format);
824 }
825 });
826 coordinateFormatItems.add(item);
827 add(item);
828 }
829
830 add(separator);
831 add(doNotHide);
832
833 addPopupMenuListener(new PopupMenuListener() {
834 @Override
835 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
836 Component invoker = ((JPopupMenu) e.getSource()).getInvoker();
837 jumpButton.setVisible(latText.equals(invoker) || lonText.equals(invoker));
838 String currentSOM = SystemOfMeasurement.getSystemOfMeasurement().toString();
839 for (JMenuItem item : somItems) {
840 item.setSelected(item.getText().equals(currentSOM));
841 item.setVisible(distText.equals(invoker));
842 }
843 final String currentCorrdinateFormat = CoordinateFormatManager.getDefaultFormat().getDisplayName();
844 for (JMenuItem item : coordinateFormatItems) {
845 item.setSelected(currentCorrdinateFormat.equals(item.getText()));
846 item.setVisible(latText.equals(invoker) || lonText.equals(invoker));
847 }
848 separator.setVisible(distText.equals(invoker) || latText.equals(invoker) || lonText.equals(invoker));
849 doNotHide.setSelected(Config.getPref().getBoolean("statusbar.always-visible", true));
850 }
851
852 @Override
853 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
854 // Do nothing
855 }
856
857 @Override
858 public void popupMenuCanceled(PopupMenuEvent e) {
859 // Do nothing
860 }
861 });
862 }
863 }
864
865 /**
866 * Construct a new MapStatus and attach it to the map view.
867 * @param mapFrame The MapFrame the status line is part of.
868 */
869 public MapStatus(final MapFrame mapFrame) {
870 this.mv = mapFrame.mapView;
871 this.collector = new Collector(mapFrame);
872 this.awtListener = event -> {
873 if (event instanceof InputEvent &&
874 ((InputEvent) event).getComponent() == mv) {
875 synchronized (collector) {
876 int modifiers = ((InputEvent) event).getModifiersEx();
877 Point mousePos = null;
878 if (event instanceof MouseEvent) {
879 mousePos = ((MouseEvent) event).getPoint();
880 }
881 collector.updateMousePosition(mousePos, modifiers);
882 }
883 }
884 };
885
886 // Context menu of status bar
887 setComponentPopupMenu(new MapStatusPopupMenu());
888
889 // also show Jump To dialog on mouse click (except context menu)
890 MouseListener jumpToOnLeftClick = new JumpToOnLeftClickMouseAdapter();
891
892 // Listen for mouse movements and set the position text field
893 mv.addMouseMotionListener(new MouseMotionListener() {
894 @Override
895 public void mouseDragged(MouseEvent e) {
896 mouseMoved(e);
897 }
898
899 @Override
900 public void mouseMoved(MouseEvent e) {
901 if (mv.getCenter() == null)
902 return;
903 // Do not update the view if ctrl or right button is pressed.
904 if ((e.getModifiersEx() & (MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) == 0) {
905 updateLatLonText(e.getX(), e.getY());
906 }
907 }
908 });
909
910 setLayout(new GridBagLayout());
911 setBorder(BorderFactory.createEmptyBorder(1, 2, 1, 2));
912
913 latText.setForeground(PROP_FOREGROUND_COLOR.get());
914 lonText.setForeground(PROP_FOREGROUND_COLOR.get());
915 headingText.setForeground(PROP_FOREGROUND_COLOR.get());
916 distText.setForeground(PROP_FOREGROUND_COLOR.get());
917 nameText.setForeground(PROP_FOREGROUND_COLOR.get());
918
919 latText.setInheritsPopupMenu(true);
920 lonText.setInheritsPopupMenu(true);
921 headingText.setInheritsPopupMenu(true);
922 distText.setInheritsPopupMenu(true);
923 nameText.setInheritsPopupMenu(true);
924
925 add(latText, GBC.std());
926 add(lonText, GBC.std().insets(3, 0, 0, 0));
927 add(headingText, GBC.std().insets(3, 0, 0, 0));
928 add(angleText, GBC.std().insets(3, 0, 0, 0));
929 add(distText, GBC.std().insets(3, 0, 0, 0));
930
931 if (Config.getPref().getBoolean("statusbar.change-system-of-measurement-on-click", true)) {
932 distText.addMouseListener(new MouseAdapter() {
933 @Override
934 public void mouseClicked(MouseEvent e) {
935 if (!e.isPopupTrigger() && e.getButton() == MouseEvent.BUTTON1) {
936 SystemOfMeasurement som = SystemOfMeasurement.getSystemOfMeasurement();
937 int i = (SORTED_SYSTEM_OF_MEASUREMENTS.indexOf(som) + 1) % SORTED_SYSTEM_OF_MEASUREMENTS.size();
938 SystemOfMeasurement newsom = SORTED_SYSTEM_OF_MEASUREMENTS.get(i);
939 updateSystemOfMeasurement(newsom);
940 }
941 }
942 });
943 }
944
945 SystemOfMeasurement.addSoMChangeListener(this);
946 NavigatableComponent.addZoomChangeListener(this);
947
948 latText.addMouseListener(jumpToOnLeftClick);
949 lonText.addMouseListener(jumpToOnLeftClick);
950
951 helpText.setEditable(false);
952 add(nameText, GBC.std().insets(3, 0, 0, 0));
953 add(helpText, GBC.std().insets(3, 0, 0, 0).fill(GBC.HORIZONTAL));
954
955 progressBar.setMaximum(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX);
956 progressBar.setVisible(false);
957 GBC gbc = GBC.eol();
958 gbc.ipadx = 100;
959 add(progressBar, gbc);
960 progressBar.addMouseListener(new ShowMonitorDialogMouseAdapter());
961
962 Config.getPref().addPreferenceChangeListener(this);
963 DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IN_EDT_CONSOLIDATED);
964 SelectionEventManager.getInstance().addSelectionListenerForEdt(this);
965
966 mvComponentAdapter = new ComponentAdapter() {
967 @Override
968 public void componentResized(ComponentEvent e) {
969 nameText.setCharCount(getNameLabelCharacterCount(MainApplication.getMainFrame()));
970 revalidate();
971 }
972 };
973 mv.addComponentListener(mvComponentAdapter);
974
975 // The background thread
976 thread = new Thread(collector, "Map Status Collector");
977 thread.setDaemon(true);
978 thread.start();
979 }
980
981 private void updateLatLonText(int x, int y) {
982 LatLon p = mv.getLatLon(x, y);
983 ICoordinateFormat mCord = CoordinateFormatManager.getDefaultFormat();
984 latText.setText(mCord.latToString(p));
985 lonText.setText(mCord.lonToString(p));
986 if (Objects.equals(previousCoordinateFormat, mCord)) {
987 // do nothing
988 } else if (ProjectedCoordinateFormat.INSTANCE.equals(mCord)) {
989 latText.setIcon("northing");
990 lonText.setIcon("easting");
991 latText.setToolTipText(tr("The northing at the mouse pointer."));
992 lonText.setToolTipText(tr("The easting at the mouse pointer."));
993 previousCoordinateFormat = mCord;
994 } else {
995 latText.setIcon("lat");
996 lonText.setIcon("lon");
997 latText.setToolTipText(tr("The geographic latitude at the mouse pointer."));
998 lonText.setToolTipText(tr("The geographic longitude at the mouse pointer."));
999 previousCoordinateFormat = mCord;
1000 }
1001 }
1002
1003 @Override
1004 public void systemOfMeasurementChanged(String oldSoM, String newSoM) {
1005 setDist(distValue);
1006 }
1007
1008 /**
1009 * Updates the system of measurement and displays a notification.
1010 * @param som The new system of measurement to set
1011 * @since 6960
1012 */
1013 public void updateSystemOfMeasurement(SystemOfMeasurement som) {
1014 SystemOfMeasurement.setSystemOfMeasurement(som);
1015 if (Config.getPref().getBoolean("statusbar.notify.change-system-of-measurement", true)) {
1016 new Notification(tr("System of measurement changed to {0}", som.toString()))
1017 .setDuration(Notification.TIME_SHORT)
1018 .show();
1019 }
1020 }
1021
1022 /**
1023 * Gets the panel that displays the angle
1024 * @return The angle panel
1025 */
1026 public JPanel getAnglePanel() {
1027 return angleText;
1028 }
1029
1030 @Override
1031 public String helpTopic() {
1032 return ht("/StatusBar");
1033 }
1034
1035 @Override
1036 public synchronized void addMouseListener(MouseListener ml) {
1037 lonText.addMouseListener(ml);
1038 latText.addMouseListener(ml);
1039 }
1040
1041 /**
1042 * Sets the help text in the status panel
1043 * @param text The text
1044 */
1045 public void setHelpText(String text) {
1046 setHelpText(null, text);
1047 }
1048
1049 /**
1050 * Sets the help status text to display
1051 * @param id The object that caused the status update (or a id object it selects). May be <code>null</code>
1052 * @param text The text
1053 */
1054 public synchronized void setHelpText(Object id, final String text) {
1055 StatusTextHistory entry = new StatusTextHistory(id, text);
1056
1057 statusText.remove(entry);
1058 statusText.add(entry);
1059
1060 GuiHelper.runInEDT(() -> {
1061 helpText.setText(text);
1062 helpText.setToolTipText(text);
1063 });
1064 }
1065
1066 /**
1067 * Removes a help text and restores the previous one
1068 * @param id The id passed to {@link #setHelpText(Object, String)}
1069 */
1070 public synchronized void resetHelpText(Object id) {
1071 if (statusText.isEmpty())
1072 return;
1073
1074 StatusTextHistory entry = new StatusTextHistory(id, null);
1075 if (statusText.get(statusText.size() - 1).equals(entry)) {
1076 if (statusText.size() == 1) {
1077 setHelpText("");
1078 } else {
1079 StatusTextHistory history = statusText.get(statusText.size() - 2);
1080 setHelpText(history.id, history.text);
1081 }
1082 }
1083 statusText.remove(entry);
1084 }
1085
1086 /**
1087 * Sets the angle to display in the angle panel. Values less than 0 yield "--".
1088 * @param a The angle
1089 * @see #setAngleNaN
1090 * @see #setAngleText
1091 */
1092 public void setAngle(double a) {
1093 angleText.setText(a < 0 ? "--" : DECIMAL_FORMAT.format(a) + " \u00B0");
1094 }
1095
1096 /**
1097 * Sets the angle to display in the angle panel. NaN yields "--".
1098 * @param a The angle
1099 * @see #setAngle
1100 * @see #setAngleText
1101 */
1102 public void setAngleNaN(double a) {
1103 angleText.setText(!Double.isFinite(a) ? "--" : DECIMAL_FORMAT.format(a) + " \u00B0");
1104 }
1105
1106 /**
1107 * Sets the angle to display in the angle panel
1108 * @param text The angle text
1109 */
1110 public void setAngleText(String text) {
1111 angleText.setText(text);
1112 }
1113
1114 /**
1115 * Sets the heading to display in the heading panel
1116 * @param h The heading
1117 */
1118 public void setHeading(double h) {
1119 headingText.setText(h < 0 ? "--" : DECIMAL_FORMAT.format(h) + " \u00B0");
1120 }
1121
1122 /**
1123 * Sets the distance text to the given value
1124 * @param dist The distance value to display, in meters
1125 */
1126 public void setDist(double dist) {
1127 distValue = dist;
1128 distText.setText(dist < 0 ? "--" : NavigatableComponent.getDistText(dist, DECIMAL_FORMAT, DISTANCE_THRESHOLD.get()));
1129 }
1130
1131 /**
1132 * Sets the distance text to the total sum of given ways length
1133 * @param ways The ways to consider for the total distance
1134 * @since 5991
1135 */
1136 public void setDist(Collection<Way> ways) {
1137 double dist = -1;
1138 // Compute total length of selected way(s) until an arbitrary limit set to 250 ways
1139 // in order to prevent performance issue if a large number of ways are selected (old behaviour kept in that case, see #8403)
1140 int maxWays = Math.max(1, Config.getPref().getInt("selection.max-ways-for-statusline", 250));
1141 if (!ways.isEmpty() && ways.size() <= maxWays) {
1142 dist = ways.stream().mapToDouble(Way::getLength).sum();
1143 }
1144 setDist(dist);
1145 }
1146
1147 /**
1148 * Activates the angle panel.
1149 * @param activeFlag {@code true} to activate it, {@code false} to deactivate it
1150 */
1151 public void activateAnglePanel(boolean activeFlag) {
1152 angleEnabled = activeFlag;
1153 refreshAnglePanel();
1154 }
1155
1156 private void refreshAnglePanel() {
1157 angleText.setBackground(angleEnabled ? PROP_ACTIVE_BACKGROUND_COLOR.get() : PROP_BACKGROUND_COLOR.get());
1158 angleText.setForeground(angleEnabled ? PROP_ACTIVE_FOREGROUND_COLOR.get() : PROP_FOREGROUND_COLOR.get());
1159 }
1160
1161 @Override
1162 public void destroy() {
1163 SystemOfMeasurement.removeSoMChangeListener(this);
1164 NavigatableComponent.removeZoomChangeListener(this);
1165 Config.getPref().removePreferenceChangeListener(this);
1166 DatasetEventManager.getInstance().removeDatasetListener(this);
1167 SelectionEventManager.getInstance().removeSelectionListener(this);
1168 mv.removeComponentListener(mvComponentAdapter);
1169
1170 // MapFrame gets destroyed when the last layer is removed, but the status line background
1171 // thread that collects the information doesn't get destroyed automatically.
1172 if (thread != null) {
1173 try {
1174 thread.interrupt();
1175 } catch (SecurityException e) {
1176 Logging.error(e);
1177 }
1178 }
1179 }
1180
1181 @Override
1182 public void preferenceChanged(PreferenceChangeEvent e) {
1183 String key = e.getKey();
1184 if (key.startsWith(NamedColorProperty.NAMED_COLOR_PREFIX)) {
1185 if (PROP_BACKGROUND_COLOR.getKey().equals(key) || PROP_FOREGROUND_COLOR.getKey().equals(key)) {
1186 for (ImageLabel il : new ImageLabel[]{latText, lonText, headingText, distText, nameText}) {
1187 il.setBackground(PROP_BACKGROUND_COLOR.get());
1188 il.setForeground(PROP_FOREGROUND_COLOR.get());
1189 }
1190 refreshAnglePanel();
1191 } else if (PROP_ACTIVE_BACKGROUND_COLOR.getKey().equals(key) || PROP_ACTIVE_FOREGROUND_COLOR.getKey().equals(key)) {
1192 refreshAnglePanel();
1193 }
1194 }
1195 }
1196
1197 /**
1198 * Loads all colors from preferences.
1199 * @since 6789
1200 */
1201 public static void getColors() {
1202 PROP_BACKGROUND_COLOR.get();
1203 PROP_FOREGROUND_COLOR.get();
1204 PROP_ACTIVE_BACKGROUND_COLOR.get();
1205 PROP_ACTIVE_FOREGROUND_COLOR.get();
1206 }
1207
1208 private static int getNameLabelCharacterCount(Component parent) {
1209 int w = parent != null ? parent.getWidth() : 800;
1210 return Math.min(80, 20 + Math.max(0, w-1280) * 60 / (1920-1280));
1211 }
1212
1213 private void refreshDistText(Collection<? extends OsmPrimitive> newSelection) {
1214 if (!autoLength) {
1215 return;
1216 }
1217
1218 if (newSelection.size() == 2) {
1219 Iterator<? extends OsmPrimitive> it = newSelection.iterator();
1220 OsmPrimitive n1 = it.next();
1221 OsmPrimitive n2 = it.next();
1222 // show distance between two selected nodes with coordinates
1223 if (n1 instanceof ILatLon && n2 instanceof ILatLon && ((ILatLon) n1).isLatLonKnown() && ((ILatLon) n2).isLatLonKnown()) {
1224 setDist(((ILatLon) n1).greatCircleDistance((ILatLon) n2));
1225 return;
1226 }
1227 }
1228 setDist(new SubclassFilteredCollection<OsmPrimitive, Way>(newSelection, Way.class::isInstance));
1229 }
1230
1231 @Override
1232 public void selectionChanged(SelectionChangeEvent event) {
1233 refreshDistText(event.getSelection());
1234 }
1235
1236 @Override
1237 public void zoomChanged() {
1238 if (!GraphicsEnvironment.isHeadless()) {
1239 try {
1240 PointerInfo pointerInfo = MouseInfo.getPointerInfo();
1241 if (pointerInfo != null) {
1242 Point mp = pointerInfo.getLocation();
1243 updateLatLonText(mp.x, mp.y);
1244 }
1245 } catch (SecurityException ex) {
1246 Logging.log(Logging.LEVEL_ERROR, "Unable to get mouse pointer info", ex);
1247 }
1248 }
1249 }
1250
1251 @Override
1252 public void wayNodesChanged(WayNodesChangedEvent event) {
1253 refreshDistText(event.getDataset().getSelected());
1254 }
1255
1256 @Override
1257 public void nodeMoved(NodeMovedEvent event) {
1258 refreshDistText(event.getDataset().getSelected());
1259 }
1260
1261 @Override
1262 public void primitivesAdded(PrimitivesAddedEvent event) {
1263 // Do nothing
1264 }
1265
1266 @Override
1267 public void primitivesRemoved(PrimitivesRemovedEvent event) {
1268 // Do nothing
1269 }
1270
1271 @Override
1272 public void tagsChanged(TagsChangedEvent event) {
1273 // Do nothing
1274 }
1275
1276 @Override
1277 public void relationMembersChanged(RelationMembersChangedEvent event) {
1278 // Do nothing
1279 }
1280
1281 @Override
1282 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
1283 // Do nothing
1284 }
1285
1286 @Override
1287 public void dataChanged(DataChangedEvent event) {
1288 if (event.getDataset() != null) {
1289 refreshDistText(event.getDataset().getSelected());
1290 }
1291 }
1292
1293 /**
1294 * Enable or disable the automatic refresh of the length field.
1295 * @param b if {@code true} the automatic refresh is enabled, else disabled
1296 * @since 17108
1297 */
1298 public void setAutoLength(boolean b) {
1299 autoLength = b;
1300 }
1301
1302}
Note: See TracBrowser for help on using the repository browser.