source: josm/trunk/src/org/openstreetmap/josm/Main.java@ 10971

Last change on this file since 10971 was 10962, checked in by Don-vip, 8 years ago

improve unit tests

  • Property svn:eol-style set to native
File size: 58.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.Window;
9import java.awt.event.KeyEvent;
10import java.awt.event.WindowAdapter;
11import java.awt.event.WindowEvent;
12import java.io.File;
13import java.io.IOException;
14import java.lang.ref.WeakReference;
15import java.net.URI;
16import java.net.URISyntaxException;
17import java.net.URL;
18import java.text.MessageFormat;
19import java.util.ArrayList;
20import java.util.Arrays;
21import java.util.Collection;
22import java.util.Collections;
23import java.util.EnumSet;
24import java.util.HashMap;
25import java.util.Iterator;
26import java.util.List;
27import java.util.Locale;
28import java.util.Map;
29import java.util.Objects;
30import java.util.Set;
31import java.util.StringTokenizer;
32import java.util.concurrent.Callable;
33import java.util.concurrent.ExecutionException;
34import java.util.concurrent.ExecutorService;
35import java.util.concurrent.Executors;
36import java.util.concurrent.Future;
37
38import javax.swing.Action;
39import javax.swing.InputMap;
40import javax.swing.JComponent;
41import javax.swing.JOptionPane;
42import javax.swing.JPanel;
43import javax.swing.KeyStroke;
44import javax.swing.LookAndFeel;
45import javax.swing.UIManager;
46import javax.swing.UnsupportedLookAndFeelException;
47
48import org.openstreetmap.gui.jmapviewer.FeatureAdapter;
49import org.openstreetmap.josm.actions.JosmAction;
50import org.openstreetmap.josm.actions.OpenFileAction;
51import org.openstreetmap.josm.actions.OpenLocationAction;
52import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask;
53import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
54import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
55import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
56import org.openstreetmap.josm.actions.mapmode.DrawAction;
57import org.openstreetmap.josm.actions.search.SearchAction;
58import org.openstreetmap.josm.data.Bounds;
59import org.openstreetmap.josm.data.Preferences;
60import org.openstreetmap.josm.data.ProjectionBounds;
61import org.openstreetmap.josm.data.UndoRedoHandler;
62import org.openstreetmap.josm.data.ViewportData;
63import org.openstreetmap.josm.data.cache.JCSCacheManager;
64import org.openstreetmap.josm.data.coor.CoordinateFormat;
65import org.openstreetmap.josm.data.coor.LatLon;
66import org.openstreetmap.josm.data.osm.DataSet;
67import org.openstreetmap.josm.data.osm.OsmPrimitive;
68import org.openstreetmap.josm.data.projection.Projection;
69import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
70import org.openstreetmap.josm.data.validation.OsmValidator;
71import org.openstreetmap.josm.gui.GettingStarted;
72import org.openstreetmap.josm.gui.MainFrame;
73import org.openstreetmap.josm.gui.MainMenu;
74import org.openstreetmap.josm.gui.MainPanel;
75import org.openstreetmap.josm.gui.MapFrame;
76import org.openstreetmap.josm.gui.MapFrameListener;
77import org.openstreetmap.josm.gui.ProgramArguments;
78import org.openstreetmap.josm.gui.ProgramArguments.Option;
79import org.openstreetmap.josm.gui.io.SaveLayersDialog;
80import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
81import org.openstreetmap.josm.gui.layer.Layer;
82import org.openstreetmap.josm.gui.layer.MainLayerManager;
83import org.openstreetmap.josm.gui.layer.OsmDataLayer;
84import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener;
85import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
86import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;
87import org.openstreetmap.josm.gui.preferences.map.MapPaintPreference;
88import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
89import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
90import org.openstreetmap.josm.gui.progress.ProgressMonitorExecutor;
91import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
92import org.openstreetmap.josm.gui.util.GuiHelper;
93import org.openstreetmap.josm.gui.util.RedirectInputMap;
94import org.openstreetmap.josm.io.FileWatcher;
95import org.openstreetmap.josm.io.OnlineResource;
96import org.openstreetmap.josm.io.OsmApi;
97import org.openstreetmap.josm.io.OsmApiInitializationException;
98import org.openstreetmap.josm.io.OsmTransferCanceledException;
99import org.openstreetmap.josm.plugins.PluginHandler;
100import org.openstreetmap.josm.tools.CheckParameterUtil;
101import org.openstreetmap.josm.tools.I18n;
102import org.openstreetmap.josm.tools.ImageProvider;
103import org.openstreetmap.josm.tools.Logging;
104import org.openstreetmap.josm.tools.OpenBrowser;
105import org.openstreetmap.josm.tools.OsmUrlToBounds;
106import org.openstreetmap.josm.tools.PlatformHook;
107import org.openstreetmap.josm.tools.PlatformHookOsx;
108import org.openstreetmap.josm.tools.PlatformHookUnixoid;
109import org.openstreetmap.josm.tools.PlatformHookWindows;
110import org.openstreetmap.josm.tools.Shortcut;
111import org.openstreetmap.josm.tools.Utils;
112
113/**
114 * Abstract class holding various static global variables and methods used in large parts of JOSM application.
115 * @since 98
116 */
117public abstract class Main {
118
119 /**
120 * The JOSM website URL.
121 * @since 6897 (was public from 6143 to 6896)
122 */
123 private static final String JOSM_WEBSITE = "https://josm.openstreetmap.de";
124
125 /**
126 * The OSM website URL.
127 * @since 6897 (was public from 6453 to 6896)
128 */
129 private static final String OSM_WEBSITE = "https://www.openstreetmap.org";
130
131 /**
132 * Replies true if JOSM currently displays a map view. False, if it doesn't, i.e. if
133 * it only shows the MOTD panel.
134 * <p>
135 * You do not need this when accessing the layer manager. The layer manager will be empty if no map view is shown.
136 *
137 * @return <code>true</code> if JOSM currently displays a map view
138 */
139 public static boolean isDisplayingMapView() {
140 return map != null && map.mapView != null;
141 }
142
143 /**
144 * Global parent component for all dialogs and message boxes
145 */
146 public static Component parent;
147
148 /**
149 * Global application.
150 */
151 public static volatile Main main;
152
153 /**
154 * Command-line arguments used to run the application.
155 */
156 protected static final List<String> COMMAND_LINE_ARGS = new ArrayList<>();
157
158 /**
159 * The worker thread slave. This is for executing all long and intensive
160 * calculations. The executed runnables are guaranteed to be executed separately
161 * and sequential.
162 */
163 public static final ExecutorService worker = new ProgressMonitorExecutor("main-worker-%d", Thread.NORM_PRIORITY);
164
165 /**
166 * Global application preferences
167 */
168 public static final Preferences pref = new Preferences();
169
170 /**
171 * The MapFrame. Use {@link Main#setMapFrame} to set or clear it.
172 * <p>
173 * There should be no need to access this to access any map data. Use {@link #layerManager} instead.
174 *
175 * @see MainPanel
176 */
177 public static MapFrame map;
178
179 /**
180 * Provides access to the layers displayed in the main view.
181 * @since 10271
182 */
183 private static final MainLayerManager layerManager = new MainLayerManager();
184
185 /**
186 * The toolbar preference control to register new actions.
187 */
188 public static volatile ToolbarPreferences toolbar;
189
190 /**
191 * The commands undo/redo handler.
192 */
193 public final UndoRedoHandler undoRedo = new UndoRedoHandler();
194
195 /**
196 * The progress monitor being currently displayed.
197 */
198 public static PleaseWaitProgressMonitor currentProgressMonitor;
199
200 /**
201 * The main menu bar at top of screen.
202 */
203 public MainMenu menu;
204
205 /**
206 * The file watcher service.
207 */
208 public static final FileWatcher fileWatcher = new FileWatcher();
209
210 /**
211 * The MOTD Layer.
212 * @deprecated Do not access this. It will be removed soon. You should not need to access the GettingStarted panel.
213 */
214 @Deprecated
215 public final GettingStarted gettingStarted = mainPanel.getGettingStarted();
216
217 protected static final Map<String, Throwable> NETWORK_ERRORS = new HashMap<>();
218
219 private static final Set<OnlineResource> OFFLINE_RESOURCES = EnumSet.noneOf(OnlineResource.class);
220
221 /**
222 * Logging level (5 = trace, 4 = debug, 3 = info, 2 = warn, 1 = error, 0 = none).
223 * @since 6248
224 * @deprecated Use {@link Logging} class.
225 */
226 @Deprecated
227 public static int logLevel = 3;
228
229 /**
230 * The real main panel. This field may be removed any time and made private to {@link MainFrame}
231 * @see #panel
232 */
233 protected static final MainPanel mainPanel = new MainPanel(getLayerManager());
234
235 /**
236 * Replies the first lines of last 5 error and warning messages, used for bug reports
237 * @return the first lines of last 5 error and warning messages
238 * @since 7420
239 */
240 public static final Collection<String> getLastErrorAndWarnings() {
241 return Logging.getLastErrorAndWarnings();
242 }
243
244 /**
245 * Clears the list of last error and warning messages.
246 * @since 8959
247 */
248 public static void clearLastErrorAndWarnings() {
249 Logging.clearLastErrorAndWarnings();
250 }
251
252 /**
253 * Prints an error message if logging is on.
254 * @param msg The message to print.
255 * @since 6248
256 */
257 public static void error(String msg) {
258 Logging.error(msg);
259 }
260
261 /**
262 * Prints a warning message if logging is on.
263 * @param msg The message to print.
264 */
265 public static void warn(String msg) {
266 Logging.warn(msg);
267 }
268
269 /**
270 * Prints an informational message if logging is on.
271 * @param msg The message to print.
272 */
273 public static void info(String msg) {
274 Logging.info(msg);
275 }
276
277 /**
278 * Prints a debug message if logging is on.
279 * @param msg The message to print.
280 */
281 public static void debug(String msg) {
282 Logging.debug(msg);
283 }
284
285 /**
286 * Prints a trace message if logging is on.
287 * @param msg The message to print.
288 */
289 public static void trace(String msg) {
290 Logging.trace(msg);
291 }
292
293 /**
294 * Determines if debug log level is enabled.
295 * Useful to avoid costly construction of debug messages when not enabled.
296 * @return {@code true} if log level is at least debug, {@code false} otherwise
297 * @since 6852
298 */
299 public static boolean isDebugEnabled() {
300 return Logging.isLoggingEnabled(Logging.LEVEL_DEBUG);
301 }
302
303 /**
304 * Determines if trace log level is enabled.
305 * Useful to avoid costly construction of trace messages when not enabled.
306 * @return {@code true} if log level is at least trace, {@code false} otherwise
307 * @since 6852
308 */
309 public static boolean isTraceEnabled() {
310 return Logging.isLoggingEnabled(Logging.LEVEL_TRACE);
311 }
312
313 /**
314 * Prints a formatted error message if logging is on. Calls {@link MessageFormat#format}
315 * function to format text.
316 * @param msg The formatted message to print.
317 * @param objects The objects to insert into format string.
318 * @since 6248
319 */
320 public static void error(String msg, Object... objects) {
321 Logging.error(msg, objects);
322 }
323
324 /**
325 * Prints a formatted warning message if logging is on. Calls {@link MessageFormat#format}
326 * function to format text.
327 * @param msg The formatted message to print.
328 * @param objects The objects to insert into format string.
329 */
330 public static void warn(String msg, Object... objects) {
331 Logging.warn(msg, objects);
332 }
333
334 /**
335 * Prints a formatted informational message if logging is on. Calls {@link MessageFormat#format}
336 * function to format text.
337 * @param msg The formatted message to print.
338 * @param objects The objects to insert into format string.
339 */
340 public static void info(String msg, Object... objects) {
341 Logging.info(msg, objects);
342 }
343
344 /**
345 * Prints a formatted debug message if logging is on. Calls {@link MessageFormat#format}
346 * function to format text.
347 * @param msg The formatted message to print.
348 * @param objects The objects to insert into format string.
349 */
350 public static void debug(String msg, Object... objects) {
351 Logging.debug(msg, objects);
352 }
353
354 /**
355 * Prints a formatted trace message if logging is on. Calls {@link MessageFormat#format}
356 * function to format text.
357 * @param msg The formatted message to print.
358 * @param objects The objects to insert into format string.
359 */
360 public static void trace(String msg, Object... objects) {
361 Logging.trace(msg, objects);
362 }
363
364 /**
365 * Prints an error message for the given Throwable.
366 * @param t The throwable object causing the error
367 * @since 6248
368 */
369 public static void error(Throwable t) {
370 Logging.logWithStackTrace(Logging.LEVEL_ERROR, t);
371 }
372
373 /**
374 * Prints a warning message for the given Throwable.
375 * @param t The throwable object causing the error
376 * @since 6248
377 */
378 public static void warn(Throwable t) {
379 Logging.logWithStackTrace(Logging.LEVEL_WARN, t);
380 }
381
382 /**
383 * Prints a debug message for the given Throwable. Useful for exceptions usually ignored
384 * @param t The throwable object causing the error
385 * @since 10420
386 */
387 public static void debug(Throwable t) {
388 Logging.log(Logging.LEVEL_DEBUG, t);
389 }
390
391 /**
392 * Prints a trace message for the given Throwable. Useful for exceptions usually ignored
393 * @param t The throwable object causing the error
394 * @since 10420
395 */
396 public static void trace(Throwable t) {
397 Logging.log(Logging.LEVEL_TRACE, t);
398 }
399
400 /**
401 * Prints an error message for the given Throwable.
402 * @param t The throwable object causing the error
403 * @param stackTrace {@code true}, if the stacktrace should be displayed
404 * @since 6642
405 */
406 public static void error(Throwable t, boolean stackTrace) {
407 if (stackTrace) {
408 Logging.log(Logging.LEVEL_ERROR, t);
409 } else {
410 Logging.logWithStackTrace(Logging.LEVEL_ERROR, t);
411 }
412 }
413
414 /**
415 * Prints an error message for the given Throwable.
416 * @param t The throwable object causing the error
417 * @param message additional error message
418 * @since 10420
419 */
420 public static void error(Throwable t, String message) {
421 Logging.log(Logging.LEVEL_ERROR, message, t);
422 }
423
424 /**
425 * Prints a warning message for the given Throwable.
426 * @param t The throwable object causing the error
427 * @param stackTrace {@code true}, if the stacktrace should be displayed
428 * @since 6642
429 */
430 public static void warn(Throwable t, boolean stackTrace) {
431 if (stackTrace) {
432 Logging.log(Logging.LEVEL_WARN, t);
433 } else {
434 Logging.logWithStackTrace(Logging.LEVEL_WARN, t);
435 }
436 }
437
438 /**
439 * Prints a warning message for the given Throwable.
440 * @param t The throwable object causing the error
441 * @param message additional error message
442 * @since 10420
443 */
444 public static void warn(Throwable t, String message) {
445 Logging.log(Logging.LEVEL_WARN, message, t);
446 }
447
448 /**
449 * Returns a human-readable message of error, also usable for developers.
450 * @param t The error
451 * @return The human-readable error message
452 * @since 6642
453 */
454 public static String getErrorMessage(Throwable t) {
455 if (t == null) {
456 return null;
457 } else {
458 return Logging.getErrorMessage(t);
459 }
460 }
461
462 /**
463 * Platform specific code goes in here.
464 * Plugins may replace it, however, some hooks will be called before any plugins have been loeaded.
465 * So if you need to hook into those early ones, split your class and send the one with the early hooks
466 * to the JOSM team for inclusion.
467 */
468 public static volatile PlatformHook platform;
469
470 /**
471 * Set or clear (if passed <code>null</code>) the map.
472 * <p>
473 * To be removed any time
474 * @param map The map to set {@link Main#map} to. Can be null.
475 * @deprecated This is done automatically by {@link MainPanel}
476 */
477 @Deprecated
478 public final void setMapFrame(final MapFrame map) {
479 Main.warn("setMapFrame call was ignored.");
480 }
481
482 /**
483 * Remove the specified layer from the map. If it is the last layer,
484 * remove the map as well.
485 * <p>
486 * To be removed end of 2016
487 * @param layer The layer to remove
488 * @deprecated You can remove the layer using {@link #getLayerManager()}
489 */
490 @Deprecated
491 public final synchronized void removeLayer(final Layer layer) {
492 if (map != null) {
493 getLayerManager().removeLayer(layer);
494 }
495 }
496
497 private static volatile InitStatusListener initListener;
498
499 public interface InitStatusListener {
500
501 Object updateStatus(String event);
502
503 void finish(Object status);
504 }
505
506 public static void setInitStatusListener(InitStatusListener listener) {
507 CheckParameterUtil.ensureParameterNotNull(listener);
508 initListener = listener;
509 }
510
511 /**
512 * Constructs new {@code Main} object.
513 * @see #initialize()
514 */
515 public Main() {
516 main = this;
517 mainPanel.addMapFrameListener((o, n) -> redoUndoListener.commandChanged(0, 0));
518 }
519
520 /**
521 * Initializes the main object. A lot of global variables are initialized here.
522 * @since 10340
523 */
524 public void initialize() {
525 fileWatcher.start();
526
527 new InitializationTask(tr("Executing platform startup hook"), platform::startupHook).call();
528
529 new InitializationTask(tr("Building main menu"), this::initializeMainWindow).call();
530
531 undoRedo.addCommandQueueListener(redoUndoListener);
532
533 // creating toolbar
534 contentPanePrivate.add(toolbar.control, BorderLayout.NORTH);
535
536 registerActionShortcut(menu.help, Shortcut.registerShortcut("system:help", tr("Help"),
537 KeyEvent.VK_F1, Shortcut.DIRECT));
538
539 // contains several initialization tasks to be executed (in parallel) by a ExecutorService
540 List<Callable<Void>> tasks = new ArrayList<>();
541
542 tasks.add(new InitializationTask(tr("Initializing OSM API"), () -> {
543 // We try to establish an API connection early, so that any API
544 // capabilities are already known to the editor instance. However
545 // if it goes wrong that's not critical at this stage.
546 try {
547 OsmApi.getOsmApi().initialize(null, true);
548 } catch (OsmTransferCanceledException | OsmApiInitializationException e) {
549 Main.warn(getErrorMessage(Utils.getRootCause(e)));
550 }
551 }));
552
553 tasks.add(new InitializationTask(tr("Initializing validator"), OsmValidator::initialize));
554
555 tasks.add(new InitializationTask(tr("Initializing presets"), TaggingPresets::initialize));
556
557 tasks.add(new InitializationTask(tr("Initializing map styles"), MapPaintPreference::initialize));
558
559 tasks.add(new InitializationTask(tr("Loading imagery preferences"), ImageryPreference::initialize));
560
561 try {
562 ExecutorService service = Executors.newFixedThreadPool(
563 Runtime.getRuntime().availableProcessors(), Utils.newThreadFactory("main-init-%d", Thread.NORM_PRIORITY));
564 for (Future<Void> i : service.invokeAll(tasks)) {
565 i.get();
566 }
567 service.shutdown();
568 } catch (InterruptedException | ExecutionException ex) {
569 throw new RuntimeException(ex);
570 }
571
572 // hooks for the jmapviewer component
573 FeatureAdapter.registerBrowserAdapter(OpenBrowser::displayUrl);
574 FeatureAdapter.registerTranslationAdapter(I18n.getTranslationAdapter());
575 FeatureAdapter.registerLoggingAdapter(name -> Logging.getLogger());
576
577 new InitializationTask(tr("Updating user interface"), () -> {
578 toolbar.refreshToolbarControl();
579 toolbar.control.updateUI();
580 contentPanePrivate.updateUI();
581 }).call();
582 }
583
584 /**
585 * Called once at startup to initialize the main window content.
586 * Should set {@link #menu}
587 */
588 protected void initializeMainWindow() {
589 // can be implementd by subclasses
590 }
591
592 private static class InitializationTask implements Callable<Void> {
593
594 private final String name;
595 private final Runnable task;
596
597 protected InitializationTask(String name, Runnable task) {
598 this.name = name;
599 this.task = task;
600 }
601
602 @Override
603 public Void call() {
604 Object status = null;
605 if (initListener != null) {
606 status = initListener.updateStatus(name);
607 }
608 task.run();
609 if (initListener != null) {
610 initListener.finish(status);
611 }
612 return null;
613 }
614 }
615
616 /**
617 * Returns the main layer manager that is used by the map view.
618 * @return The layer manager. The value returned will never change.
619 * @since 10279
620 */
621 public static MainLayerManager getLayerManager() {
622 return layerManager;
623 }
624
625 /**
626 * Add a new layer to the map.
627 *
628 * If no map exists, create one.
629 * <p>
630 * To be removed end of 2016
631 *
632 * @param layer the layer
633 *
634 * @see #addLayer(Layer, ProjectionBounds)
635 * @see #addLayer(Layer, ViewportData)
636 * @deprecated You can add the layer to the layer manager: {@link #getLayerManager()}
637 */
638 @Deprecated
639 public final void addLayer(final Layer layer) {
640 addLayer(layer, (ViewportData) null);
641 }
642
643 /**
644 * Add a new layer to the map.
645 *
646 * If no map exists, create one.
647 *
648 * @param layer the layer
649 * @param bounds the bounds of the layer (target zoom area); can be null, then
650 * the viewport isn't changed
651 */
652 public final void addLayer(Layer layer, ProjectionBounds bounds) {
653 addLayer(layer, bounds == null ? null : new ViewportData(bounds));
654 }
655
656 /**
657 * Add a new layer to the map.
658 *
659 * If no map exists, create one.
660 *
661 * @param layer the layer
662 * @param viewport the viewport to zoom to; can be null, then the viewport isn't changed
663 */
664 public final void addLayer(Layer layer, ViewportData viewport) {
665 getLayerManager().addLayer(layer);
666 if (viewport != null && Main.map.mapView != null) {
667 // MapView may be null in headless mode here.
668 Main.map.mapView.scheduleZoomTo(viewport);
669 }
670 }
671
672 /**
673 * Creates the map frame. Call only in EDT Thread.
674 * <p>
675 * To be removed any time
676 * @param firstLayer The first layer that was added.
677 * @param viewportData The initial viewport. Can be <code>null</code> to be automatically computed.
678 * @deprecated Not supported. MainPanel does this automatically.
679 */
680 @Deprecated
681 public synchronized void createMapFrame(Layer firstLayer, ViewportData viewportData) {
682 GuiHelper.assertCallFromEdt();
683 Main.error("createMapFrame() not supported any more.");
684 }
685
686 /**
687 * Replies <code>true</code> if there is an edit layer
688 * <p>
689 * To be removed end of 2016
690 *
691 * @return <code>true</code> if there is an edit layer
692 * @deprecated You can get the edit layer using the layer manager and then check if it is not null: {@link #getLayerManager()}
693 */
694 @Deprecated
695 public boolean hasEditLayer() {
696 if (getEditLayer() == null) return false;
697 return true;
698 }
699
700 /**
701 * Replies the current edit layer
702 * <p>
703 * To be removed end of 2016
704 *
705 * @return the current edit layer. <code>null</code>, if no current edit layer exists
706 * @deprecated You can get the edit layer using the layer manager: {@link #getLayerManager()}
707 */
708 @Deprecated
709 public OsmDataLayer getEditLayer() {
710 return getLayerManager().getEditLayer();
711 }
712
713 /**
714 * Replies the current data set.
715 * <p>
716 * To be removed end of 2016
717 *
718 * @return the current data set. <code>null</code>, if no current data set exists
719 * @deprecated You can get the data set using the layer manager: {@link #getLayerManager()}
720 */
721 @Deprecated
722 public DataSet getCurrentDataSet() {
723 return getLayerManager().getEditDataSet();
724 }
725
726 /**
727 * Replies the current selected primitives, from a end-user point of view.
728 * It is not always technically the same collection of primitives than {@link DataSet#getSelected()}.
729 * Indeed, if the user is currently in drawing mode, only the way currently being drawn is returned,
730 * see {@link DrawAction#getInProgressSelection()}.
731 *
732 * @return The current selected primitives, from a end-user point of view. Can be {@code null}.
733 * @since 6546
734 */
735 public Collection<OsmPrimitive> getInProgressSelection() {
736 if (map != null && map.mapMode instanceof DrawAction) {
737 return ((DrawAction) map.mapMode).getInProgressSelection();
738 } else {
739 DataSet ds = getLayerManager().getEditDataSet();
740 if (ds == null) return null;
741 return ds.getSelected();
742 }
743 }
744
745 /**
746 * Returns the currently active layer
747 * <p>
748 * To be removed end of 2016
749 *
750 * @return the currently active layer. <code>null</code>, if currently no active layer exists
751 * @deprecated You can get the layer using the layer manager: {@link #getLayerManager()}
752 */
753 @Deprecated
754 public Layer getActiveLayer() {
755 return getLayerManager().getActiveLayer();
756 }
757
758 protected static final JPanel contentPanePrivate = new JPanel(new BorderLayout());
759
760 public static void redirectToMainContentPane(JComponent source) {
761 RedirectInputMap.redirect(source, contentPanePrivate);
762 }
763
764 public static void registerActionShortcut(JosmAction action) {
765 registerActionShortcut(action, action.getShortcut());
766 }
767
768 public static void registerActionShortcut(Action action, Shortcut shortcut) {
769 KeyStroke keyStroke = shortcut.getKeyStroke();
770 if (keyStroke == null)
771 return;
772
773 InputMap inputMap = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
774 Object existing = inputMap.get(keyStroke);
775 if (existing != null && !existing.equals(action)) {
776 info(String.format("Keystroke %s is already assigned to %s, will be overridden by %s", keyStroke, existing, action));
777 }
778 inputMap.put(keyStroke, action);
779
780 contentPanePrivate.getActionMap().put(action, action);
781 }
782
783 public static void unregisterShortcut(Shortcut shortcut) {
784 contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(shortcut.getKeyStroke());
785 }
786
787 public static void unregisterActionShortcut(JosmAction action) {
788 unregisterActionShortcut(action, action.getShortcut());
789 }
790
791 public static void unregisterActionShortcut(Action action, Shortcut shortcut) {
792 unregisterShortcut(shortcut);
793 contentPanePrivate.getActionMap().remove(action);
794 }
795
796 /**
797 * Replies the registered action for the given shortcut
798 * @param shortcut The shortcut to look for
799 * @return the registered action for the given shortcut
800 * @since 5696
801 */
802 public static Action getRegisteredActionShortcut(Shortcut shortcut) {
803 KeyStroke keyStroke = shortcut.getKeyStroke();
804 if (keyStroke == null)
805 return null;
806 Object action = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).get(keyStroke);
807 if (action instanceof Action)
808 return (Action) action;
809 return null;
810 }
811
812 ///////////////////////////////////////////////////////////////////////////
813 // Implementation part
814 ///////////////////////////////////////////////////////////////////////////
815
816 /**
817 * Global panel.
818 */
819 public static final JPanel panel = mainPanel;
820
821 private final CommandQueueListener redoUndoListener = (queueSize, redoSize) -> {
822 menu.undo.setEnabled(queueSize > 0);
823 menu.redo.setEnabled(redoSize > 0);
824 };
825
826 /**
827 * Should be called before the main constructor to setup some parameter stuff
828 */
829 public static void preConstructorInit() {
830 ProjectionPreference.setProjection();
831
832 String defaultlaf = platform.getDefaultStyle();
833 String laf = Main.pref.get("laf", defaultlaf);
834 try {
835 UIManager.setLookAndFeel(laf);
836 } catch (final NoClassDefFoundError | ClassNotFoundException e) {
837 // Try to find look and feel in plugin classloaders
838 Main.trace(e);
839 Class<?> klass = null;
840 for (ClassLoader cl : PluginHandler.getResourceClassLoaders()) {
841 try {
842 klass = cl.loadClass(laf);
843 break;
844 } catch (ClassNotFoundException ex) {
845 Main.trace(ex);
846 }
847 }
848 if (klass != null && LookAndFeel.class.isAssignableFrom(klass)) {
849 try {
850 UIManager.setLookAndFeel((LookAndFeel) klass.getConstructor().newInstance());
851 } catch (ReflectiveOperationException ex) {
852 warn(ex, "Cannot set Look and Feel: " + laf + ": "+ex.getMessage());
853 } catch (UnsupportedLookAndFeelException ex) {
854 info("Look and Feel not supported: " + laf);
855 Main.pref.put("laf", defaultlaf);
856 trace(ex);
857 }
858 } else {
859 info("Look and Feel not found: " + laf);
860 Main.pref.put("laf", defaultlaf);
861 }
862 } catch (UnsupportedLookAndFeelException e) {
863 info("Look and Feel not supported: " + laf);
864 Main.pref.put("laf", defaultlaf);
865 trace(e);
866 } catch (InstantiationException | IllegalAccessException e) {
867 error(e);
868 }
869 toolbar = new ToolbarPreferences();
870 contentPanePrivate.updateUI();
871 panel.updateUI();
872
873 UIManager.put("OptionPane.okIcon", ImageProvider.get("ok"));
874 UIManager.put("OptionPane.yesIcon", UIManager.get("OptionPane.okIcon"));
875 UIManager.put("OptionPane.cancelIcon", ImageProvider.get("cancel"));
876 UIManager.put("OptionPane.noIcon", UIManager.get("OptionPane.cancelIcon"));
877 // Ensures caret color is the same than text foreground color, see #12257
878 // See http://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/synth/doc-files/componentProperties.html
879 for (String p : Arrays.asList(
880 "EditorPane", "FormattedTextField", "PasswordField", "TextArea", "TextField", "TextPane")) {
881 UIManager.put(p+".caretForeground", UIManager.getColor(p+".foreground"));
882 }
883
884 I18n.translateJavaInternalMessages();
885
886 // init default coordinate format
887 //
888 try {
889 CoordinateFormat.setCoordinateFormat(CoordinateFormat.valueOf(Main.pref.get("coordinates")));
890 } catch (IllegalArgumentException iae) {
891 Main.trace(iae);
892 CoordinateFormat.setCoordinateFormat(CoordinateFormat.DECIMAL_DEGREES);
893 }
894 }
895
896 protected static void postConstructorProcessCmdLine(ProgramArguments args) {
897 List<File> fileList = new ArrayList<>();
898 for (String s : args.get(Option.DOWNLOAD)) {
899 DownloadParamType.paramType(s).download(s, fileList);
900 }
901 if (!fileList.isEmpty()) {
902 OpenFileAction.openFiles(fileList, true);
903 }
904 for (String s : args.get(Option.DOWNLOADGPS)) {
905 DownloadParamType.paramType(s).downloadGps(s);
906 }
907 for (String s : args.get(Option.SELECTION)) {
908 SearchAction.search(s, SearchAction.SearchMode.add);
909 }
910 }
911
912 /**
913 * Asks user to perform "save layer" operations (save on disk and/or upload data to server) for all
914 * {@link AbstractModifiableLayer} before JOSM exits.
915 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations.
916 * {@code false} if the user cancels.
917 * @since 2025
918 */
919 public static boolean saveUnsavedModifications() {
920 if (!isDisplayingMapView()) return true;
921 return saveUnsavedModifications(getLayerManager().getLayersOfType(AbstractModifiableLayer.class), true);
922 }
923
924 /**
925 * Asks user to perform "save layer" operations (save on disk and/or upload data to server) before data layers deletion.
926 *
927 * @param selectedLayers The layers to check. Only instances of {@link AbstractModifiableLayer} are considered.
928 * @param exit {@code true} if JOSM is exiting, {@code false} otherwise.
929 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations.
930 * {@code false} if the user cancels.
931 * @since 5519
932 */
933 public static boolean saveUnsavedModifications(Iterable<? extends Layer> selectedLayers, boolean exit) {
934 SaveLayersDialog dialog = new SaveLayersDialog(parent);
935 List<AbstractModifiableLayer> layersWithUnmodifiedChanges = new ArrayList<>();
936 for (Layer l: selectedLayers) {
937 if (!(l instanceof AbstractModifiableLayer)) {
938 continue;
939 }
940 AbstractModifiableLayer odl = (AbstractModifiableLayer) l;
941 if (odl.isModified() &&
942 ((!odl.isSavable() && !odl.isUploadable()) ||
943 odl.requiresSaveToFile() ||
944 (odl.requiresUploadToServer() && !odl.isUploadDiscouraged()))) {
945 layersWithUnmodifiedChanges.add(odl);
946 }
947 }
948 if (exit) {
949 dialog.prepareForSavingAndUpdatingLayersBeforeExit();
950 } else {
951 dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
952 }
953 if (!layersWithUnmodifiedChanges.isEmpty()) {
954 dialog.getModel().populate(layersWithUnmodifiedChanges);
955 dialog.setVisible(true);
956 switch(dialog.getUserAction()) {
957 case PROCEED: return true;
958 case CANCEL:
959 default: return false;
960 }
961 }
962
963 return true;
964 }
965
966 /**
967 * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
968 * If there are some unsaved data layers, asks first for user confirmation.
969 * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
970 * @param exitCode The return code
971 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation.
972 * @since 3378
973 */
974 public static boolean exitJosm(boolean exit, int exitCode) {
975 if (Main.saveUnsavedModifications()) {
976 if (Main.main != null) {
977 Main.main.shutdown();
978 }
979
980 if (exit) {
981 System.exit(exitCode);
982 }
983 return true;
984 }
985 return false;
986 }
987
988 protected void shutdown() {
989 worker.shutdown();
990 ImageProvider.shutdown(false);
991 JCSCacheManager.shutdown();
992 if (map != null) {
993 map.rememberToggleDialogWidth();
994 }
995 // Remove all layers because somebody may rely on layerRemoved events (like AutosaveTask)
996 getLayerManager().resetState();
997 try {
998 pref.saveDefaults();
999 } catch (IOException ex) {
1000 Main.warn(ex, tr("Failed to save default preferences."));
1001 }
1002 worker.shutdownNow();
1003 ImageProvider.shutdown(true);
1004 }
1005
1006 /**
1007 * The type of a command line parameter, to be used in switch statements.
1008 * @see #paramType
1009 */
1010 enum DownloadParamType {
1011 httpUrl {
1012 @Override
1013 void download(String s, Collection<File> fileList) {
1014 new OpenLocationAction().openUrl(false, s);
1015 }
1016
1017 @Override
1018 void downloadGps(String s) {
1019 final Bounds b = OsmUrlToBounds.parse(s);
1020 if (b == null) {
1021 JOptionPane.showMessageDialog(
1022 Main.parent,
1023 tr("Ignoring malformed URL: \"{0}\"", s),
1024 tr("Warning"),
1025 JOptionPane.WARNING_MESSAGE
1026 );
1027 return;
1028 }
1029 downloadFromParamBounds(true, b);
1030 }
1031 }, fileUrl {
1032 @Override
1033 void download(String s, Collection<File> fileList) {
1034 File f = null;
1035 try {
1036 f = new File(new URI(s));
1037 } catch (URISyntaxException e) {
1038 Main.warn(e);
1039 JOptionPane.showMessageDialog(
1040 Main.parent,
1041 tr("Ignoring malformed file URL: \"{0}\"", s),
1042 tr("Warning"),
1043 JOptionPane.WARNING_MESSAGE
1044 );
1045 }
1046 if (f != null) {
1047 fileList.add(f);
1048 }
1049 }
1050 }, bounds {
1051
1052 /**
1053 * Download area specified on the command line as bounds string.
1054 * @param rawGps Flag to download raw GPS tracks
1055 * @param s The bounds parameter
1056 */
1057 private void downloadFromParamBounds(final boolean rawGps, String s) {
1058 final StringTokenizer st = new StringTokenizer(s, ",");
1059 if (st.countTokens() == 4) {
1060 Bounds b = new Bounds(
1061 new LatLon(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken())),
1062 new LatLon(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken()))
1063 );
1064 Main.downloadFromParamBounds(rawGps, b);
1065 }
1066 }
1067
1068 @Override
1069 void download(String param, Collection<File> fileList) {
1070 downloadFromParamBounds(false, param);
1071 }
1072
1073 @Override
1074 void downloadGps(String param) {
1075 downloadFromParamBounds(true, param);
1076 }
1077 }, fileName {
1078 @Override
1079 void download(String s, Collection<File> fileList) {
1080 fileList.add(new File(s));
1081 }
1082 };
1083
1084 /**
1085 * Performs the download
1086 * @param param represents the object to be downloaded
1087 * @param fileList files which shall be opened, should be added to this collection
1088 */
1089 abstract void download(String param, Collection<File> fileList);
1090
1091 /**
1092 * Performs the GPS download
1093 * @param param represents the object to be downloaded
1094 */
1095 void downloadGps(String param) {
1096 JOptionPane.showMessageDialog(
1097 Main.parent,
1098 tr("Parameter \"downloadgps\" does not accept file names or file URLs"),
1099 tr("Warning"),
1100 JOptionPane.WARNING_MESSAGE
1101 );
1102 }
1103
1104 /**
1105 * Guess the type of a parameter string specified on the command line with --download= or --downloadgps.
1106 *
1107 * @param s A parameter string
1108 * @return The guessed parameter type
1109 */
1110 static DownloadParamType paramType(String s) {
1111 if (s.startsWith("http:") || s.startsWith("https:")) return DownloadParamType.httpUrl;
1112 if (s.startsWith("file:")) return DownloadParamType.fileUrl;
1113 String coorPattern = "\\s*[+-]?[0-9]+(\\.[0-9]+)?\\s*";
1114 if (s.matches(coorPattern + "(," + coorPattern + "){3}")) return DownloadParamType.bounds;
1115 // everything else must be a file name
1116 return DownloadParamType.fileName;
1117 }
1118 }
1119
1120 /**
1121 * Download area specified as Bounds value.
1122 * @param rawGps Flag to download raw GPS tracks
1123 * @param b The bounds value
1124 */
1125 private static void downloadFromParamBounds(final boolean rawGps, Bounds b) {
1126 DownloadTask task = rawGps ? new DownloadGpsTask() : new DownloadOsmTask();
1127 // asynchronously launch the download task ...
1128 Future<?> future = task.download(true, b, null);
1129 // ... and the continuation when the download is finished (this will wait for the download to finish)
1130 Main.worker.execute(new PostDownloadHandler(task, future));
1131 }
1132
1133 /**
1134 * Identifies the current operating system family and initializes the platform hook accordingly.
1135 * @since 1849
1136 */
1137 public static void determinePlatformHook() {
1138 String os = System.getProperty("os.name");
1139 if (os == null) {
1140 warn("Your operating system has no name, so I'm guessing its some kind of *nix.");
1141 platform = new PlatformHookUnixoid();
1142 } else if (os.toLowerCase(Locale.ENGLISH).startsWith("windows")) {
1143 platform = new PlatformHookWindows();
1144 } else if ("Linux".equals(os) || "Solaris".equals(os) ||
1145 "SunOS".equals(os) || "AIX".equals(os) ||
1146 "FreeBSD".equals(os) || "NetBSD".equals(os) || "OpenBSD".equals(os)) {
1147 platform = new PlatformHookUnixoid();
1148 } else if (os.toLowerCase(Locale.ENGLISH).startsWith("mac os x")) {
1149 platform = new PlatformHookOsx();
1150 } else {
1151 warn("I don't know your operating system '"+os+"', so I'm guessing its some kind of *nix.");
1152 platform = new PlatformHookUnixoid();
1153 }
1154 }
1155
1156 /* ----------------------------------------------------------------------------------------- */
1157 /* projection handling - Main is a registry for a single, global projection instance */
1158 /* */
1159 /* TODO: For historical reasons the registry is implemented by Main. An alternative approach */
1160 /* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class. */
1161 /* ----------------------------------------------------------------------------------------- */
1162 /**
1163 * The projection method used.
1164 * use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
1165 * Use {@link #setProjection(Projection)} in order to trigger a projection change event.
1166 */
1167 private static volatile Projection proj;
1168
1169 /**
1170 * Replies the current projection.
1171 *
1172 * @return the currently active projection
1173 */
1174 public static Projection getProjection() {
1175 return proj;
1176 }
1177
1178 /**
1179 * Sets the current projection
1180 *
1181 * @param p the projection
1182 */
1183 public static void setProjection(Projection p) {
1184 CheckParameterUtil.ensureParameterNotNull(p);
1185 Projection oldValue = proj;
1186 Bounds b = isDisplayingMapView() ? map.mapView.getRealBounds() : null;
1187 proj = p;
1188 fireProjectionChanged(oldValue, proj, b);
1189 }
1190
1191 /*
1192 * Keep WeakReferences to the listeners. This relieves clients from the burden of
1193 * explicitly removing the listeners and allows us to transparently register every
1194 * created dataset as projection change listener.
1195 */
1196 private static final List<WeakReference<ProjectionChangeListener>> listeners = new ArrayList<>();
1197
1198 private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) {
1199 if ((newValue == null ^ oldValue == null)
1200 || (newValue != null && oldValue != null && !Objects.equals(newValue.toCode(), oldValue.toCode()))) {
1201 if (Main.map != null) {
1202 // This needs to be called first
1203 Main.map.mapView.fixProjection();
1204 }
1205 synchronized (Main.class) {
1206 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
1207 while (it.hasNext()) {
1208 WeakReference<ProjectionChangeListener> wr = it.next();
1209 ProjectionChangeListener listener = wr.get();
1210 if (listener == null) {
1211 it.remove();
1212 continue;
1213 }
1214 listener.projectionChanged(oldValue, newValue);
1215 }
1216 }
1217 if (newValue != null && oldBounds != null) {
1218 Main.map.mapView.zoomTo(oldBounds);
1219 }
1220 /* TODO - remove layers with fixed projection */
1221 }
1222 }
1223
1224 /**
1225 * Register a projection change listener.
1226 *
1227 * @param listener the listener. Ignored if <code>null</code>.
1228 */
1229 public static void addProjectionChangeListener(ProjectionChangeListener listener) {
1230 if (listener == null) return;
1231 synchronized (Main.class) {
1232 for (WeakReference<ProjectionChangeListener> wr : listeners) {
1233 // already registered ? => abort
1234 if (wr.get() == listener) return;
1235 }
1236 listeners.add(new WeakReference<>(listener));
1237 }
1238 }
1239
1240 /**
1241 * Removes a projection change listener.
1242 *
1243 * @param listener the listener. Ignored if <code>null</code>.
1244 */
1245 public static void removeProjectionChangeListener(ProjectionChangeListener listener) {
1246 if (listener == null) return;
1247 synchronized (Main.class) {
1248 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
1249 while (it.hasNext()) {
1250 WeakReference<ProjectionChangeListener> wr = it.next();
1251 // remove the listener - and any other listener which got garbage
1252 // collected in the meantime
1253 if (wr.get() == null || wr.get() == listener) {
1254 it.remove();
1255 }
1256 }
1257 }
1258 }
1259
1260 /**
1261 * Listener for window switch events.
1262 *
1263 * These are events, when the user activates a window of another application
1264 * or comes back to JOSM. Window switches from one JOSM window to another
1265 * are not reported.
1266 */
1267 public interface WindowSwitchListener {
1268 /**
1269 * Called when the user activates a window of another application.
1270 */
1271 void toOtherApplication();
1272
1273 /**
1274 * Called when the user comes from a window of another application back to JOSM.
1275 */
1276 void fromOtherApplication();
1277 }
1278
1279 private static final List<WeakReference<WindowSwitchListener>> windowSwitchListeners = new ArrayList<>();
1280
1281 /**
1282 * Register a window switch listener.
1283 *
1284 * @param listener the listener. Ignored if <code>null</code>.
1285 */
1286 public static void addWindowSwitchListener(WindowSwitchListener listener) {
1287 if (listener == null) return;
1288 synchronized (Main.class) {
1289 for (WeakReference<WindowSwitchListener> wr : windowSwitchListeners) {
1290 // already registered ? => abort
1291 if (wr.get() == listener) return;
1292 }
1293 boolean wasEmpty = windowSwitchListeners.isEmpty();
1294 windowSwitchListeners.add(new WeakReference<>(listener));
1295 if (wasEmpty) {
1296 // The following call will have no effect, when there is no window
1297 // at the time. Therefore, MasterWindowListener.setup() will also be
1298 // called, as soon as the main window is shown.
1299 MasterWindowListener.setup();
1300 }
1301 }
1302 }
1303
1304 /**
1305 * Removes a window switch listener.
1306 *
1307 * @param listener the listener. Ignored if <code>null</code>.
1308 */
1309 public static void removeWindowSwitchListener(WindowSwitchListener listener) {
1310 if (listener == null) return;
1311 synchronized (Main.class) {
1312 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1313 while (it.hasNext()) {
1314 WeakReference<WindowSwitchListener> wr = it.next();
1315 // remove the listener - and any other listener which got garbage
1316 // collected in the meantime
1317 if (wr.get() == null || wr.get() == listener) {
1318 it.remove();
1319 }
1320 }
1321 if (windowSwitchListeners.isEmpty()) {
1322 MasterWindowListener.teardown();
1323 }
1324 }
1325 }
1326
1327 /**
1328 * WindowListener, that is registered on all Windows of the application.
1329 *
1330 * Its purpose is to notify WindowSwitchListeners, that the user switches to
1331 * another application, e.g. a browser, or back to JOSM.
1332 *
1333 * When changing from JOSM to another application and back (e.g. two times
1334 * alt+tab), the active Window within JOSM may be different.
1335 * Therefore, we need to register listeners to <strong>all</strong> (visible)
1336 * Windows in JOSM, and it does not suffice to monitor the one that was
1337 * deactivated last.
1338 *
1339 * This class is only "active" on demand, i.e. when there is at least one
1340 * WindowSwitchListener registered.
1341 */
1342 protected static class MasterWindowListener extends WindowAdapter {
1343
1344 private static MasterWindowListener INSTANCE;
1345
1346 public static synchronized MasterWindowListener getInstance() {
1347 if (INSTANCE == null) {
1348 INSTANCE = new MasterWindowListener();
1349 }
1350 return INSTANCE;
1351 }
1352
1353 /**
1354 * Register listeners to all non-hidden windows.
1355 *
1356 * Windows that are created later, will be cared for in {@link #windowDeactivated(WindowEvent)}.
1357 */
1358 public static void setup() {
1359 if (!windowSwitchListeners.isEmpty()) {
1360 for (Window w : Window.getWindows()) {
1361 if (w.isShowing() && !Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
1362 w.addWindowListener(getInstance());
1363 }
1364 }
1365 }
1366 }
1367
1368 /**
1369 * Unregister all listeners.
1370 */
1371 public static void teardown() {
1372 for (Window w : Window.getWindows()) {
1373 w.removeWindowListener(getInstance());
1374 }
1375 }
1376
1377 @Override
1378 public void windowActivated(WindowEvent e) {
1379 if (e.getOppositeWindow() == null) { // we come from a window of a different application
1380 // fire WindowSwitchListeners
1381 synchronized (Main.class) {
1382 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1383 while (it.hasNext()) {
1384 WeakReference<WindowSwitchListener> wr = it.next();
1385 WindowSwitchListener listener = wr.get();
1386 if (listener == null) {
1387 it.remove();
1388 continue;
1389 }
1390 listener.fromOtherApplication();
1391 }
1392 }
1393 }
1394 }
1395
1396 @Override
1397 public void windowDeactivated(WindowEvent e) {
1398 // set up windows that have been created in the meantime
1399 for (Window w : Window.getWindows()) {
1400 if (!w.isShowing()) {
1401 w.removeWindowListener(getInstance());
1402 } else {
1403 if (!Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
1404 w.addWindowListener(getInstance());
1405 }
1406 }
1407 }
1408 if (e.getOppositeWindow() == null) { // we go to a window of a different application
1409 // fire WindowSwitchListeners
1410 synchronized (Main.class) {
1411 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1412 while (it.hasNext()) {
1413 WeakReference<WindowSwitchListener> wr = it.next();
1414 WindowSwitchListener listener = wr.get();
1415 if (listener == null) {
1416 it.remove();
1417 continue;
1418 }
1419 listener.toOtherApplication();
1420 }
1421 }
1422 }
1423 }
1424 }
1425
1426 /**
1427 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
1428 * @param listener The MapFrameListener
1429 * @param fireWhenMapViewPresent If true, will fire an initial mapFrameInitialized event
1430 * when the MapFrame is present. Otherwise will only fire when the MapFrame is created
1431 * or destroyed.
1432 * @return {@code true} if the listeners collection changed as a result of the call
1433 */
1434 public static boolean addMapFrameListener(MapFrameListener listener, boolean fireWhenMapViewPresent) {
1435 if (fireWhenMapViewPresent) {
1436 return mainPanel.addAndFireMapFrameListener(listener);
1437 } else {
1438 return mainPanel.addMapFrameListener(listener);
1439 }
1440 }
1441
1442 /**
1443 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
1444 * @param listener The MapFrameListener
1445 * @return {@code true} if the listeners collection changed as a result of the call
1446 * @since 5957
1447 */
1448 public static boolean addMapFrameListener(MapFrameListener listener) {
1449 return mainPanel.addMapFrameListener(listener);
1450 }
1451
1452 /**
1453 * Unregisters the given {@code MapFrameListener} from MapFrame changes
1454 * @param listener The MapFrameListener
1455 * @return {@code true} if the listeners collection changed as a result of the call
1456 * @since 5957
1457 */
1458 public static boolean removeMapFrameListener(MapFrameListener listener) {
1459 return mainPanel.removeMapFrameListener(listener);
1460 }
1461
1462 /**
1463 * Adds a new network error that occur to give a hint about broken Internet connection.
1464 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
1465 *
1466 * @param url The accessed URL that caused the error
1467 * @param t The network error
1468 * @return The previous error associated to the given resource, if any. Can be {@code null}
1469 * @since 6642
1470 */
1471 public static Throwable addNetworkError(URL url, Throwable t) {
1472 if (url != null && t != null) {
1473 Throwable old = addNetworkError(url.toExternalForm(), t);
1474 if (old != null) {
1475 Main.warn("Already here "+old);
1476 }
1477 return old;
1478 }
1479 return null;
1480 }
1481
1482 /**
1483 * Adds a new network error that occur to give a hint about broken Internet connection.
1484 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
1485 *
1486 * @param url The accessed URL that caused the error
1487 * @param t The network error
1488 * @return The previous error associated to the given resource, if any. Can be {@code null}
1489 * @since 6642
1490 */
1491 public static Throwable addNetworkError(String url, Throwable t) {
1492 if (url != null && t != null) {
1493 return NETWORK_ERRORS.put(url, t);
1494 }
1495 return null;
1496 }
1497
1498 /**
1499 * Returns the network errors that occured until now.
1500 * @return the network errors that occured until now, indexed by URL
1501 * @since 6639
1502 */
1503 public static Map<String, Throwable> getNetworkErrors() {
1504 return new HashMap<>(NETWORK_ERRORS);
1505 }
1506
1507 /**
1508 * Returns the command-line arguments used to run the application.
1509 * @return the command-line arguments used to run the application
1510 * @since 8356
1511 */
1512 public static List<String> getCommandLineArgs() {
1513 return Collections.unmodifiableList(COMMAND_LINE_ARGS);
1514 }
1515
1516 /**
1517 * Returns the JOSM website URL.
1518 * @return the josm website URL
1519 * @since 6897
1520 */
1521 public static String getJOSMWebsite() {
1522 if (Main.pref != null)
1523 return Main.pref.get("josm.url", JOSM_WEBSITE);
1524 return JOSM_WEBSITE;
1525 }
1526
1527 /**
1528 * Returns the JOSM XML URL.
1529 * @return the josm XML URL
1530 * @since 6897
1531 */
1532 public static String getXMLBase() {
1533 // Always return HTTP (issues reported with HTTPS)
1534 return "http://josm.openstreetmap.de";
1535 }
1536
1537 /**
1538 * Returns the OSM website URL.
1539 * @return the OSM website URL
1540 * @since 6897
1541 */
1542 public static String getOSMWebsite() {
1543 if (Main.pref != null)
1544 return Main.pref.get("osm.url", OSM_WEBSITE);
1545 return OSM_WEBSITE;
1546 }
1547
1548 /**
1549 * Replies the base URL for browsing information about a primitive.
1550 * @return the base URL, i.e. https://www.openstreetmap.org
1551 * @since 7678
1552 */
1553 public static String getBaseBrowseUrl() {
1554 if (Main.pref != null)
1555 return Main.pref.get("osm-browse.url", getOSMWebsite());
1556 return getOSMWebsite();
1557 }
1558
1559 /**
1560 * Replies the base URL for browsing information about a user.
1561 * @return the base URL, i.e. https://www.openstreetmap.org/user
1562 * @since 7678
1563 */
1564 public static String getBaseUserUrl() {
1565 if (Main.pref != null)
1566 return Main.pref.get("osm-user.url", getOSMWebsite() + "/user");
1567 return getOSMWebsite() + "/user";
1568 }
1569
1570 /**
1571 * Determines if we are currently running on OSX.
1572 * @return {@code true} if we are currently running on OSX
1573 * @since 6957
1574 */
1575 public static boolean isPlatformOsx() {
1576 return Main.platform instanceof PlatformHookOsx;
1577 }
1578
1579 /**
1580 * Determines if we are currently running on Windows.
1581 * @return {@code true} if we are currently running on Windows
1582 * @since 7335
1583 */
1584 public static boolean isPlatformWindows() {
1585 return Main.platform instanceof PlatformHookWindows;
1586 }
1587
1588 /**
1589 * Determines if the given online resource is currently offline.
1590 * @param r the online resource
1591 * @return {@code true} if {@code r} is offline and should not be accessed
1592 * @since 7434
1593 */
1594 public static boolean isOffline(OnlineResource r) {
1595 return OFFLINE_RESOURCES.contains(r) || OFFLINE_RESOURCES.contains(OnlineResource.ALL);
1596 }
1597
1598 /**
1599 * Sets the given online resource to offline state.
1600 * @param r the online resource
1601 * @return {@code true} if {@code r} was not already offline
1602 * @since 7434
1603 */
1604 public static boolean setOffline(OnlineResource r) {
1605 return OFFLINE_RESOURCES.add(r);
1606 }
1607
1608 /**
1609 * Sets the given online resource to online state.
1610 * @param r the online resource
1611 * @return {@code true} if {@code r} was offline
1612 * @since 8506
1613 */
1614 public static boolean setOnline(OnlineResource r) {
1615 return OFFLINE_RESOURCES.remove(r);
1616 }
1617
1618 /**
1619 * Replies the set of online resources currently offline.
1620 * @return the set of online resources currently offline
1621 * @since 7434
1622 */
1623 public static Set<OnlineResource> getOfflineResources() {
1624 return EnumSet.copyOf(OFFLINE_RESOURCES);
1625 }
1626}
Note: See TracBrowser for help on using the repository browser.