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

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

sonar

  • Property svn:eol-style set to native
File size: 58.3 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 * @param args The parsed argument list.
829 */
830 public static void preConstructorInit(ProgramArguments args) {
831 ProjectionPreference.setProjection();
832
833 String defaultlaf = platform.getDefaultStyle();
834 String laf = Main.pref.get("laf", defaultlaf);
835 try {
836 UIManager.setLookAndFeel(laf);
837 } catch (final NoClassDefFoundError | ClassNotFoundException e) {
838 // Try to find look and feel in plugin classloaders
839 Main.trace(e);
840 Class<?> klass = null;
841 for (ClassLoader cl : PluginHandler.getResourceClassLoaders()) {
842 try {
843 klass = cl.loadClass(laf);
844 break;
845 } catch (ClassNotFoundException ex) {
846 Main.trace(ex);
847 }
848 }
849 if (klass != null && LookAndFeel.class.isAssignableFrom(klass)) {
850 try {
851 UIManager.setLookAndFeel((LookAndFeel) klass.getConstructor().newInstance());
852 } catch (ReflectiveOperationException ex) {
853 warn(ex, "Cannot set Look and Feel: " + laf + ": "+ex.getMessage());
854 } catch (UnsupportedLookAndFeelException ex) {
855 info("Look and Feel not supported: " + laf);
856 Main.pref.put("laf", defaultlaf);
857 trace(ex);
858 }
859 } else {
860 info("Look and Feel not found: " + laf);
861 Main.pref.put("laf", defaultlaf);
862 }
863 } catch (UnsupportedLookAndFeelException e) {
864 info("Look and Feel not supported: " + laf);
865 Main.pref.put("laf", defaultlaf);
866 trace(e);
867 } catch (InstantiationException | IllegalAccessException e) {
868 error(e);
869 }
870 toolbar = new ToolbarPreferences();
871 contentPanePrivate.updateUI();
872 panel.updateUI();
873
874 UIManager.put("OptionPane.okIcon", ImageProvider.get("ok"));
875 UIManager.put("OptionPane.yesIcon", UIManager.get("OptionPane.okIcon"));
876 UIManager.put("OptionPane.cancelIcon", ImageProvider.get("cancel"));
877 UIManager.put("OptionPane.noIcon", UIManager.get("OptionPane.cancelIcon"));
878 // Ensures caret color is the same than text foreground color, see #12257
879 // See http://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/synth/doc-files/componentProperties.html
880 for (String p : Arrays.asList(
881 "EditorPane", "FormattedTextField", "PasswordField", "TextArea", "TextField", "TextPane")) {
882 UIManager.put(p+".caretForeground", UIManager.getColor(p+".foreground"));
883 }
884
885 I18n.translateJavaInternalMessages();
886
887 // init default coordinate format
888 //
889 try {
890 CoordinateFormat.setCoordinateFormat(CoordinateFormat.valueOf(Main.pref.get("coordinates")));
891 } catch (IllegalArgumentException iae) {
892 Main.trace(iae);
893 CoordinateFormat.setCoordinateFormat(CoordinateFormat.DECIMAL_DEGREES);
894 }
895 }
896
897 protected static void postConstructorProcessCmdLine(ProgramArguments args) {
898 List<File> fileList = new ArrayList<>();
899 for (String s : args.get(Option.DOWNLOAD)) {
900 DownloadParamType.paramType(s).download(s, fileList);
901 }
902 if (!fileList.isEmpty()) {
903 OpenFileAction.openFiles(fileList, true);
904 }
905 for (String s : args.get(Option.DOWNLOADGPS)) {
906 DownloadParamType.paramType(s).downloadGps(s);
907 }
908 for (String s : args.get(Option.SELECTION)) {
909 SearchAction.search(s, SearchAction.SearchMode.add);
910 }
911 }
912
913 /**
914 * Asks user to perform "save layer" operations (save on disk and/or upload data to server) for all
915 * {@link AbstractModifiableLayer} before JOSM exits.
916 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations.
917 * {@code false} if the user cancels.
918 * @since 2025
919 */
920 public static boolean saveUnsavedModifications() {
921 if (!isDisplayingMapView()) return true;
922 return saveUnsavedModifications(getLayerManager().getLayersOfType(AbstractModifiableLayer.class), true);
923 }
924
925 /**
926 * Asks user to perform "save layer" operations (save on disk and/or upload data to server) before data layers deletion.
927 *
928 * @param selectedLayers The layers to check. Only instances of {@link AbstractModifiableLayer} are considered.
929 * @param exit {@code true} if JOSM is exiting, {@code false} otherwise.
930 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations.
931 * {@code false} if the user cancels.
932 * @since 5519
933 */
934 public static boolean saveUnsavedModifications(Iterable<? extends Layer> selectedLayers, boolean exit) {
935 SaveLayersDialog dialog = new SaveLayersDialog(parent);
936 List<AbstractModifiableLayer> layersWithUnmodifiedChanges = new ArrayList<>();
937 for (Layer l: selectedLayers) {
938 if (!(l instanceof AbstractModifiableLayer)) {
939 continue;
940 }
941 AbstractModifiableLayer odl = (AbstractModifiableLayer) l;
942 if (odl.isModified() &&
943 ((!odl.isSavable() && !odl.isUploadable()) ||
944 odl.requiresSaveToFile() ||
945 (odl.requiresUploadToServer() && !odl.isUploadDiscouraged()))) {
946 layersWithUnmodifiedChanges.add(odl);
947 }
948 }
949 if (exit) {
950 dialog.prepareForSavingAndUpdatingLayersBeforeExit();
951 } else {
952 dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
953 }
954 if (!layersWithUnmodifiedChanges.isEmpty()) {
955 dialog.getModel().populate(layersWithUnmodifiedChanges);
956 dialog.setVisible(true);
957 switch(dialog.getUserAction()) {
958 case PROCEED: return true;
959 case CANCEL:
960 default: return false;
961 }
962 }
963
964 return true;
965 }
966
967 /**
968 * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
969 * If there are some unsaved data layers, asks first for user confirmation.
970 * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
971 * @param exitCode The return code
972 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation.
973 * @since 3378
974 */
975 public static boolean exitJosm(boolean exit, int exitCode) {
976 if (Main.saveUnsavedModifications()) {
977 if (Main.main != null) {
978 Main.main.shutdown();
979 }
980
981 if (exit) {
982 System.exit(exitCode);
983 }
984 return true;
985 }
986 return false;
987 }
988
989 protected void shutdown() {
990 worker.shutdown();
991 ImageProvider.shutdown(false);
992 JCSCacheManager.shutdown();
993 if (map != null) {
994 map.rememberToggleDialogWidth();
995 }
996 // Remove all layers because somebody may rely on layerRemoved events (like AutosaveTask)
997 getLayerManager().resetState();
998 try {
999 pref.saveDefaults();
1000 } catch (IOException ex) {
1001 Main.warn(ex, tr("Failed to save default preferences."));
1002 }
1003 worker.shutdownNow();
1004 ImageProvider.shutdown(true);
1005 }
1006
1007 /**
1008 * The type of a command line parameter, to be used in switch statements.
1009 * @see #paramType
1010 */
1011 enum DownloadParamType {
1012 httpUrl {
1013 @Override
1014 void download(String s, Collection<File> fileList) {
1015 new OpenLocationAction().openUrl(false, s);
1016 }
1017
1018 @Override
1019 void downloadGps(String s) {
1020 final Bounds b = OsmUrlToBounds.parse(s);
1021 if (b == null) {
1022 JOptionPane.showMessageDialog(
1023 Main.parent,
1024 tr("Ignoring malformed URL: \"{0}\"", s),
1025 tr("Warning"),
1026 JOptionPane.WARNING_MESSAGE
1027 );
1028 return;
1029 }
1030 downloadFromParamBounds(true, b);
1031 }
1032 }, fileUrl {
1033 @Override
1034 void download(String s, Collection<File> fileList) {
1035 File f = null;
1036 try {
1037 f = new File(new URI(s));
1038 } catch (URISyntaxException e) {
1039 Main.warn(e);
1040 JOptionPane.showMessageDialog(
1041 Main.parent,
1042 tr("Ignoring malformed file URL: \"{0}\"", s),
1043 tr("Warning"),
1044 JOptionPane.WARNING_MESSAGE
1045 );
1046 }
1047 if (f != null) {
1048 fileList.add(f);
1049 }
1050 }
1051 }, bounds {
1052
1053 /**
1054 * Download area specified on the command line as bounds string.
1055 * @param rawGps Flag to download raw GPS tracks
1056 * @param s The bounds parameter
1057 */
1058 private void downloadFromParamBounds(final boolean rawGps, String s) {
1059 final StringTokenizer st = new StringTokenizer(s, ",");
1060 if (st.countTokens() == 4) {
1061 Bounds b = new Bounds(
1062 new LatLon(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken())),
1063 new LatLon(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken()))
1064 );
1065 Main.downloadFromParamBounds(rawGps, b);
1066 }
1067 }
1068
1069 @Override
1070 void download(String param, Collection<File> fileList) {
1071 downloadFromParamBounds(false, param);
1072 }
1073
1074 @Override
1075 void downloadGps(String param) {
1076 downloadFromParamBounds(true, param);
1077 }
1078 }, fileName {
1079 @Override
1080 void download(String s, Collection<File> fileList) {
1081 fileList.add(new File(s));
1082 }
1083 };
1084
1085 /**
1086 * Performs the download
1087 * @param param represents the object to be downloaded
1088 * @param fileList files which shall be opened, should be added to this collection
1089 */
1090 abstract void download(String param, Collection<File> fileList);
1091
1092 /**
1093 * Performs the GPS download
1094 * @param param represents the object to be downloaded
1095 */
1096 void downloadGps(String param) {
1097 JOptionPane.showMessageDialog(
1098 Main.parent,
1099 tr("Parameter \"downloadgps\" does not accept file names or file URLs"),
1100 tr("Warning"),
1101 JOptionPane.WARNING_MESSAGE
1102 );
1103 }
1104
1105 /**
1106 * Guess the type of a parameter string specified on the command line with --download= or --downloadgps.
1107 *
1108 * @param s A parameter string
1109 * @return The guessed parameter type
1110 */
1111 static DownloadParamType paramType(String s) {
1112 if (s.startsWith("http:") || s.startsWith("https:")) return DownloadParamType.httpUrl;
1113 if (s.startsWith("file:")) return DownloadParamType.fileUrl;
1114 String coorPattern = "\\s*[+-]?[0-9]+(\\.[0-9]+)?\\s*";
1115 if (s.matches(coorPattern + "(," + coorPattern + "){3}")) return DownloadParamType.bounds;
1116 // everything else must be a file name
1117 return DownloadParamType.fileName;
1118 }
1119 }
1120
1121 /**
1122 * Download area specified as Bounds value.
1123 * @param rawGps Flag to download raw GPS tracks
1124 * @param b The bounds value
1125 */
1126 private static void downloadFromParamBounds(final boolean rawGps, Bounds b) {
1127 DownloadTask task = rawGps ? new DownloadGpsTask() : new DownloadOsmTask();
1128 // asynchronously launch the download task ...
1129 Future<?> future = task.download(true, b, null);
1130 // ... and the continuation when the download is finished (this will wait for the download to finish)
1131 Main.worker.execute(new PostDownloadHandler(task, future));
1132 }
1133
1134 /**
1135 * Identifies the current operating system family and initializes the platform hook accordingly.
1136 * @since 1849
1137 */
1138 public static void determinePlatformHook() {
1139 String os = System.getProperty("os.name");
1140 if (os == null) {
1141 warn("Your operating system has no name, so I'm guessing its some kind of *nix.");
1142 platform = new PlatformHookUnixoid();
1143 } else if (os.toLowerCase(Locale.ENGLISH).startsWith("windows")) {
1144 platform = new PlatformHookWindows();
1145 } else if ("Linux".equals(os) || "Solaris".equals(os) ||
1146 "SunOS".equals(os) || "AIX".equals(os) ||
1147 "FreeBSD".equals(os) || "NetBSD".equals(os) || "OpenBSD".equals(os)) {
1148 platform = new PlatformHookUnixoid();
1149 } else if (os.toLowerCase(Locale.ENGLISH).startsWith("mac os x")) {
1150 platform = new PlatformHookOsx();
1151 } else {
1152 warn("I don't know your operating system '"+os+"', so I'm guessing its some kind of *nix.");
1153 platform = new PlatformHookUnixoid();
1154 }
1155 }
1156
1157 /* ----------------------------------------------------------------------------------------- */
1158 /* projection handling - Main is a registry for a single, global projection instance */
1159 /* */
1160 /* TODO: For historical reasons the registry is implemented by Main. An alternative approach */
1161 /* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class. */
1162 /* ----------------------------------------------------------------------------------------- */
1163 /**
1164 * The projection method used.
1165 * use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
1166 * Use {@link #setProjection(Projection)} in order to trigger a projection change event.
1167 */
1168 private static volatile Projection proj;
1169
1170 /**
1171 * Replies the current projection.
1172 *
1173 * @return the currently active projection
1174 */
1175 public static Projection getProjection() {
1176 return proj;
1177 }
1178
1179 /**
1180 * Sets the current projection
1181 *
1182 * @param p the projection
1183 */
1184 public static void setProjection(Projection p) {
1185 CheckParameterUtil.ensureParameterNotNull(p);
1186 Projection oldValue = proj;
1187 Bounds b = isDisplayingMapView() ? map.mapView.getRealBounds() : null;
1188 proj = p;
1189 fireProjectionChanged(oldValue, proj, b);
1190 }
1191
1192 /*
1193 * Keep WeakReferences to the listeners. This relieves clients from the burden of
1194 * explicitly removing the listeners and allows us to transparently register every
1195 * created dataset as projection change listener.
1196 */
1197 private static final List<WeakReference<ProjectionChangeListener>> listeners = new ArrayList<>();
1198
1199 private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) {
1200 if ((newValue == null ^ oldValue == null)
1201 || (newValue != null && oldValue != null && !Objects.equals(newValue.toCode(), oldValue.toCode()))) {
1202 if (Main.map != null) {
1203 // This needs to be called first
1204 Main.map.mapView.fixProjection();
1205 }
1206 synchronized (Main.class) {
1207 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
1208 while (it.hasNext()) {
1209 WeakReference<ProjectionChangeListener> wr = it.next();
1210 ProjectionChangeListener listener = wr.get();
1211 if (listener == null) {
1212 it.remove();
1213 continue;
1214 }
1215 listener.projectionChanged(oldValue, newValue);
1216 }
1217 }
1218 if (newValue != null && oldBounds != null) {
1219 Main.map.mapView.zoomTo(oldBounds);
1220 }
1221 /* TODO - remove layers with fixed projection */
1222 }
1223 }
1224
1225 /**
1226 * Register a projection change listener.
1227 *
1228 * @param listener the listener. Ignored if <code>null</code>.
1229 */
1230 public static void addProjectionChangeListener(ProjectionChangeListener listener) {
1231 if (listener == null) return;
1232 synchronized (Main.class) {
1233 for (WeakReference<ProjectionChangeListener> wr : listeners) {
1234 // already registered ? => abort
1235 if (wr.get() == listener) return;
1236 }
1237 listeners.add(new WeakReference<>(listener));
1238 }
1239 }
1240
1241 /**
1242 * Removes a projection change listener.
1243 *
1244 * @param listener the listener. Ignored if <code>null</code>.
1245 */
1246 public static void removeProjectionChangeListener(ProjectionChangeListener listener) {
1247 if (listener == null) return;
1248 synchronized (Main.class) {
1249 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
1250 while (it.hasNext()) {
1251 WeakReference<ProjectionChangeListener> wr = it.next();
1252 // remove the listener - and any other listener which got garbage
1253 // collected in the meantime
1254 if (wr.get() == null || wr.get() == listener) {
1255 it.remove();
1256 }
1257 }
1258 }
1259 }
1260
1261 /**
1262 * Listener for window switch events.
1263 *
1264 * These are events, when the user activates a window of another application
1265 * or comes back to JOSM. Window switches from one JOSM window to another
1266 * are not reported.
1267 */
1268 public interface WindowSwitchListener {
1269 /**
1270 * Called when the user activates a window of another application.
1271 */
1272 void toOtherApplication();
1273
1274 /**
1275 * Called when the user comes from a window of another application back to JOSM.
1276 */
1277 void fromOtherApplication();
1278 }
1279
1280 private static final List<WeakReference<WindowSwitchListener>> windowSwitchListeners = new ArrayList<>();
1281
1282 /**
1283 * Register a window switch listener.
1284 *
1285 * @param listener the listener. Ignored if <code>null</code>.
1286 */
1287 public static void addWindowSwitchListener(WindowSwitchListener listener) {
1288 if (listener == null) return;
1289 synchronized (Main.class) {
1290 for (WeakReference<WindowSwitchListener> wr : windowSwitchListeners) {
1291 // already registered ? => abort
1292 if (wr.get() == listener) return;
1293 }
1294 boolean wasEmpty = windowSwitchListeners.isEmpty();
1295 windowSwitchListeners.add(new WeakReference<>(listener));
1296 if (wasEmpty) {
1297 // The following call will have no effect, when there is no window
1298 // at the time. Therefore, MasterWindowListener.setup() will also be
1299 // called, as soon as the main window is shown.
1300 MasterWindowListener.setup();
1301 }
1302 }
1303 }
1304
1305 /**
1306 * Removes a window switch listener.
1307 *
1308 * @param listener the listener. Ignored if <code>null</code>.
1309 */
1310 public static void removeWindowSwitchListener(WindowSwitchListener listener) {
1311 if (listener == null) return;
1312 synchronized (Main.class) {
1313 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1314 while (it.hasNext()) {
1315 WeakReference<WindowSwitchListener> wr = it.next();
1316 // remove the listener - and any other listener which got garbage
1317 // collected in the meantime
1318 if (wr.get() == null || wr.get() == listener) {
1319 it.remove();
1320 }
1321 }
1322 if (windowSwitchListeners.isEmpty()) {
1323 MasterWindowListener.teardown();
1324 }
1325 }
1326 }
1327
1328 /**
1329 * WindowListener, that is registered on all Windows of the application.
1330 *
1331 * Its purpose is to notify WindowSwitchListeners, that the user switches to
1332 * another application, e.g. a browser, or back to JOSM.
1333 *
1334 * When changing from JOSM to another application and back (e.g. two times
1335 * alt+tab), the active Window within JOSM may be different.
1336 * Therefore, we need to register listeners to <strong>all</strong> (visible)
1337 * Windows in JOSM, and it does not suffice to monitor the one that was
1338 * deactivated last.
1339 *
1340 * This class is only "active" on demand, i.e. when there is at least one
1341 * WindowSwitchListener registered.
1342 */
1343 protected static class MasterWindowListener extends WindowAdapter {
1344
1345 private static MasterWindowListener INSTANCE;
1346
1347 public static synchronized MasterWindowListener getInstance() {
1348 if (INSTANCE == null) {
1349 INSTANCE = new MasterWindowListener();
1350 }
1351 return INSTANCE;
1352 }
1353
1354 /**
1355 * Register listeners to all non-hidden windows.
1356 *
1357 * Windows that are created later, will be cared for in {@link #windowDeactivated(WindowEvent)}.
1358 */
1359 public static void setup() {
1360 if (!windowSwitchListeners.isEmpty()) {
1361 for (Window w : Window.getWindows()) {
1362 if (w.isShowing() && !Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
1363 w.addWindowListener(getInstance());
1364 }
1365 }
1366 }
1367 }
1368
1369 /**
1370 * Unregister all listeners.
1371 */
1372 public static void teardown() {
1373 for (Window w : Window.getWindows()) {
1374 w.removeWindowListener(getInstance());
1375 }
1376 }
1377
1378 @Override
1379 public void windowActivated(WindowEvent e) {
1380 if (e.getOppositeWindow() == null) { // we come from a window of a different application
1381 // fire WindowSwitchListeners
1382 synchronized (Main.class) {
1383 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1384 while (it.hasNext()) {
1385 WeakReference<WindowSwitchListener> wr = it.next();
1386 WindowSwitchListener listener = wr.get();
1387 if (listener == null) {
1388 it.remove();
1389 continue;
1390 }
1391 listener.fromOtherApplication();
1392 }
1393 }
1394 }
1395 }
1396
1397 @Override
1398 public void windowDeactivated(WindowEvent e) {
1399 // set up windows that have been created in the meantime
1400 for (Window w : Window.getWindows()) {
1401 if (!w.isShowing()) {
1402 w.removeWindowListener(getInstance());
1403 } else {
1404 if (!Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
1405 w.addWindowListener(getInstance());
1406 }
1407 }
1408 }
1409 if (e.getOppositeWindow() == null) { // we go to a window of a different application
1410 // fire WindowSwitchListeners
1411 synchronized (Main.class) {
1412 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1413 while (it.hasNext()) {
1414 WeakReference<WindowSwitchListener> wr = it.next();
1415 WindowSwitchListener listener = wr.get();
1416 if (listener == null) {
1417 it.remove();
1418 continue;
1419 }
1420 listener.toOtherApplication();
1421 }
1422 }
1423 }
1424 }
1425 }
1426
1427 /**
1428 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
1429 * @param listener The MapFrameListener
1430 * @param fireWhenMapViewPresent If true, will fire an initial mapFrameInitialized event
1431 * when the MapFrame is present. Otherwise will only fire when the MapFrame is created
1432 * or destroyed.
1433 * @return {@code true} if the listeners collection changed as a result of the call
1434 */
1435 public static boolean addMapFrameListener(MapFrameListener listener, boolean fireWhenMapViewPresent) {
1436 if (fireWhenMapViewPresent) {
1437 return mainPanel.addAndFireMapFrameListener(listener);
1438 } else {
1439 return mainPanel.addMapFrameListener(listener);
1440 }
1441 }
1442
1443 /**
1444 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
1445 * @param listener The MapFrameListener
1446 * @return {@code true} if the listeners collection changed as a result of the call
1447 * @since 5957
1448 */
1449 public static boolean addMapFrameListener(MapFrameListener listener) {
1450 return mainPanel.addMapFrameListener(listener);
1451 }
1452
1453 /**
1454 * Unregisters the given {@code MapFrameListener} from MapFrame changes
1455 * @param listener The MapFrameListener
1456 * @return {@code true} if the listeners collection changed as a result of the call
1457 * @since 5957
1458 */
1459 public static boolean removeMapFrameListener(MapFrameListener listener) {
1460 return mainPanel.removeMapFrameListener(listener);
1461 }
1462
1463 /**
1464 * Adds a new network error that occur to give a hint about broken Internet connection.
1465 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
1466 *
1467 * @param url The accessed URL that caused the error
1468 * @param t The network error
1469 * @return The previous error associated to the given resource, if any. Can be {@code null}
1470 * @since 6642
1471 */
1472 public static Throwable addNetworkError(URL url, Throwable t) {
1473 if (url != null && t != null) {
1474 Throwable old = addNetworkError(url.toExternalForm(), t);
1475 if (old != null) {
1476 Main.warn("Already here "+old);
1477 }
1478 return old;
1479 }
1480 return null;
1481 }
1482
1483 /**
1484 * Adds a new network error that occur to give a hint about broken Internet connection.
1485 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
1486 *
1487 * @param url The accessed URL that caused the error
1488 * @param t The network error
1489 * @return The previous error associated to the given resource, if any. Can be {@code null}
1490 * @since 6642
1491 */
1492 public static Throwable addNetworkError(String url, Throwable t) {
1493 if (url != null && t != null) {
1494 return NETWORK_ERRORS.put(url, t);
1495 }
1496 return null;
1497 }
1498
1499 /**
1500 * Returns the network errors that occured until now.
1501 * @return the network errors that occured until now, indexed by URL
1502 * @since 6639
1503 */
1504 public static Map<String, Throwable> getNetworkErrors() {
1505 return new HashMap<>(NETWORK_ERRORS);
1506 }
1507
1508 /**
1509 * Returns the command-line arguments used to run the application.
1510 * @return the command-line arguments used to run the application
1511 * @since 8356
1512 */
1513 public static List<String> getCommandLineArgs() {
1514 return Collections.unmodifiableList(COMMAND_LINE_ARGS);
1515 }
1516
1517 /**
1518 * Returns the JOSM website URL.
1519 * @return the josm website URL
1520 * @since 6897
1521 */
1522 public static String getJOSMWebsite() {
1523 if (Main.pref != null)
1524 return Main.pref.get("josm.url", JOSM_WEBSITE);
1525 return JOSM_WEBSITE;
1526 }
1527
1528 /**
1529 * Returns the JOSM XML URL.
1530 * @return the josm XML URL
1531 * @since 6897
1532 */
1533 public static String getXMLBase() {
1534 // Always return HTTP (issues reported with HTTPS)
1535 return "http://josm.openstreetmap.de";
1536 }
1537
1538 /**
1539 * Returns the OSM website URL.
1540 * @return the OSM website URL
1541 * @since 6897
1542 */
1543 public static String getOSMWebsite() {
1544 if (Main.pref != null)
1545 return Main.pref.get("osm.url", OSM_WEBSITE);
1546 return OSM_WEBSITE;
1547 }
1548
1549 /**
1550 * Replies the base URL for browsing information about a primitive.
1551 * @return the base URL, i.e. https://www.openstreetmap.org
1552 * @since 7678
1553 */
1554 public static String getBaseBrowseUrl() {
1555 if (Main.pref != null)
1556 return Main.pref.get("osm-browse.url", getOSMWebsite());
1557 return getOSMWebsite();
1558 }
1559
1560 /**
1561 * Replies the base URL for browsing information about a user.
1562 * @return the base URL, i.e. https://www.openstreetmap.org/user
1563 * @since 7678
1564 */
1565 public static String getBaseUserUrl() {
1566 if (Main.pref != null)
1567 return Main.pref.get("osm-user.url", getOSMWebsite() + "/user");
1568 return getOSMWebsite() + "/user";
1569 }
1570
1571 /**
1572 * Determines if we are currently running on OSX.
1573 * @return {@code true} if we are currently running on OSX
1574 * @since 6957
1575 */
1576 public static boolean isPlatformOsx() {
1577 return Main.platform instanceof PlatformHookOsx;
1578 }
1579
1580 /**
1581 * Determines if we are currently running on Windows.
1582 * @return {@code true} if we are currently running on Windows
1583 * @since 7335
1584 */
1585 public static boolean isPlatformWindows() {
1586 return Main.platform instanceof PlatformHookWindows;
1587 }
1588
1589 /**
1590 * Determines if the given online resource is currently offline.
1591 * @param r the online resource
1592 * @return {@code true} if {@code r} is offline and should not be accessed
1593 * @since 7434
1594 */
1595 public static boolean isOffline(OnlineResource r) {
1596 return OFFLINE_RESOURCES.contains(r) || OFFLINE_RESOURCES.contains(OnlineResource.ALL);
1597 }
1598
1599 /**
1600 * Sets the given online resource to offline state.
1601 * @param r the online resource
1602 * @return {@code true} if {@code r} was not already offline
1603 * @since 7434
1604 */
1605 public static boolean setOffline(OnlineResource r) {
1606 return OFFLINE_RESOURCES.add(r);
1607 }
1608
1609 /**
1610 * Sets the given online resource to online state.
1611 * @param r the online resource
1612 * @return {@code true} if {@code r} was offline
1613 * @since 8506
1614 */
1615 public static boolean setOnline(OnlineResource r) {
1616 return OFFLINE_RESOURCES.remove(r);
1617 }
1618
1619 /**
1620 * Replies the set of online resources currently offline.
1621 * @return the set of online resources currently offline
1622 * @since 7434
1623 */
1624 public static Set<OnlineResource> getOfflineResources() {
1625 return EnumSet.copyOf(OFFLINE_RESOURCES);
1626 }
1627}
Note: See TracBrowser for help on using the repository browser.