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

Last change on this file since 11211 was 11208, checked in by simon04, 7 years ago

fix #13899 - EDT violation in SaveLayersDialog via PluginHandler

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