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

Last change on this file since 11243 was 11213, checked in by Don-vip, 7 years ago

checkstyle

  • 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(),
809 reason != null ? reason : SaveLayersDialog.Reason.EXIT)));
810 if (proceed) {
811 if (Main.main != null) {
812 Main.main.shutdown();
813 }
814
815 if (exit) {
816 System.exit(exitCode);
817 }
818 return true;
819 }
820 return false;
821 }
822
823 protected void shutdown() {
824 if (!GraphicsEnvironment.isHeadless()) {
825 worker.shutdown();
826 ImageProvider.shutdown(false);
827 JCSCacheManager.shutdown();
828 }
829 if (map != null) {
830 map.rememberToggleDialogWidth();
831 }
832 // Remove all layers because somebody may rely on layerRemoved events (like AutosaveTask)
833 getLayerManager().resetState();
834 try {
835 pref.saveDefaults();
836 } catch (IOException ex) {
837 Main.warn(ex, tr("Failed to save default preferences."));
838 }
839 if (!GraphicsEnvironment.isHeadless()) {
840 worker.shutdownNow();
841 ImageProvider.shutdown(true);
842 }
843 }
844
845 /**
846 * The type of a command line parameter, to be used in switch statements.
847 * @see #paramType
848 */
849 enum DownloadParamType {
850 httpUrl {
851 @Override
852 void download(String s, Collection<File> fileList) {
853 new OpenLocationAction().openUrl(false, s);
854 }
855
856 @Override
857 void downloadGps(String s) {
858 final Bounds b = OsmUrlToBounds.parse(s);
859 if (b == null) {
860 JOptionPane.showMessageDialog(
861 Main.parent,
862 tr("Ignoring malformed URL: \"{0}\"", s),
863 tr("Warning"),
864 JOptionPane.WARNING_MESSAGE
865 );
866 return;
867 }
868 downloadFromParamBounds(true, b);
869 }
870 }, fileUrl {
871 @Override
872 void download(String s, Collection<File> fileList) {
873 File f = null;
874 try {
875 f = new File(new URI(s));
876 } catch (URISyntaxException e) {
877 Main.warn(e);
878 JOptionPane.showMessageDialog(
879 Main.parent,
880 tr("Ignoring malformed file URL: \"{0}\"", s),
881 tr("Warning"),
882 JOptionPane.WARNING_MESSAGE
883 );
884 }
885 if (f != null) {
886 fileList.add(f);
887 }
888 }
889 }, bounds {
890
891 /**
892 * Download area specified on the command line as bounds string.
893 * @param rawGps Flag to download raw GPS tracks
894 * @param s The bounds parameter
895 */
896 private void downloadFromParamBounds(final boolean rawGps, String s) {
897 final StringTokenizer st = new StringTokenizer(s, ",");
898 if (st.countTokens() == 4) {
899 Bounds b = new Bounds(
900 new LatLon(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken())),
901 new LatLon(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken()))
902 );
903 Main.downloadFromParamBounds(rawGps, b);
904 }
905 }
906
907 @Override
908 void download(String param, Collection<File> fileList) {
909 downloadFromParamBounds(false, param);
910 }
911
912 @Override
913 void downloadGps(String param) {
914 downloadFromParamBounds(true, param);
915 }
916 }, fileName {
917 @Override
918 void download(String s, Collection<File> fileList) {
919 fileList.add(new File(s));
920 }
921 };
922
923 /**
924 * Performs the download
925 * @param param represents the object to be downloaded
926 * @param fileList files which shall be opened, should be added to this collection
927 */
928 abstract void download(String param, Collection<File> fileList);
929
930 /**
931 * Performs the GPS download
932 * @param param represents the object to be downloaded
933 */
934 void downloadGps(String param) {
935 JOptionPane.showMessageDialog(
936 Main.parent,
937 tr("Parameter \"downloadgps\" does not accept file names or file URLs"),
938 tr("Warning"),
939 JOptionPane.WARNING_MESSAGE
940 );
941 }
942
943 /**
944 * Guess the type of a parameter string specified on the command line with --download= or --downloadgps.
945 *
946 * @param s A parameter string
947 * @return The guessed parameter type
948 */
949 static DownloadParamType paramType(String s) {
950 if (s.startsWith("http:") || s.startsWith("https:")) return DownloadParamType.httpUrl;
951 if (s.startsWith("file:")) return DownloadParamType.fileUrl;
952 String coorPattern = "\\s*[+-]?[0-9]+(\\.[0-9]+)?\\s*";
953 if (s.matches(coorPattern + "(," + coorPattern + "){3}")) return DownloadParamType.bounds;
954 // everything else must be a file name
955 return DownloadParamType.fileName;
956 }
957 }
958
959 /**
960 * Download area specified as Bounds value.
961 * @param rawGps Flag to download raw GPS tracks
962 * @param b The bounds value
963 */
964 private static void downloadFromParamBounds(final boolean rawGps, Bounds b) {
965 DownloadTask task = rawGps ? new DownloadGpsTask() : new DownloadOsmTask();
966 // asynchronously launch the download task ...
967 Future<?> future = task.download(true, b, null);
968 // ... and the continuation when the download is finished (this will wait for the download to finish)
969 Main.worker.execute(new PostDownloadHandler(task, future));
970 }
971
972 /**
973 * Identifies the current operating system family and initializes the platform hook accordingly.
974 * @since 1849
975 */
976 public static void determinePlatformHook() {
977 String os = System.getProperty("os.name");
978 if (os == null) {
979 warn("Your operating system has no name, so I'm guessing its some kind of *nix.");
980 platform = new PlatformHookUnixoid();
981 } else if (os.toLowerCase(Locale.ENGLISH).startsWith("windows")) {
982 platform = new PlatformHookWindows();
983 } else if ("Linux".equals(os) || "Solaris".equals(os) ||
984 "SunOS".equals(os) || "AIX".equals(os) ||
985 "FreeBSD".equals(os) || "NetBSD".equals(os) || "OpenBSD".equals(os)) {
986 platform = new PlatformHookUnixoid();
987 } else if (os.toLowerCase(Locale.ENGLISH).startsWith("mac os x")) {
988 platform = new PlatformHookOsx();
989 } else {
990 warn("I don't know your operating system '"+os+"', so I'm guessing its some kind of *nix.");
991 platform = new PlatformHookUnixoid();
992 }
993 }
994
995 /* ----------------------------------------------------------------------------------------- */
996 /* projection handling - Main is a registry for a single, global projection instance */
997 /* */
998 /* TODO: For historical reasons the registry is implemented by Main. An alternative approach */
999 /* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class. */
1000 /* ----------------------------------------------------------------------------------------- */
1001 /**
1002 * The projection method used.
1003 * use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
1004 * Use {@link #setProjection(Projection)} in order to trigger a projection change event.
1005 */
1006 private static volatile Projection proj;
1007
1008 /**
1009 * Replies the current projection.
1010 *
1011 * @return the currently active projection
1012 */
1013 public static Projection getProjection() {
1014 return proj;
1015 }
1016
1017 /**
1018 * Sets the current projection
1019 *
1020 * @param p the projection
1021 */
1022 public static void setProjection(Projection p) {
1023 CheckParameterUtil.ensureParameterNotNull(p);
1024 Projection oldValue = proj;
1025 Bounds b = isDisplayingMapView() ? map.mapView.getRealBounds() : null;
1026 proj = p;
1027 fireProjectionChanged(oldValue, proj, b);
1028 }
1029
1030 /*
1031 * Keep WeakReferences to the listeners. This relieves clients from the burden of
1032 * explicitly removing the listeners and allows us to transparently register every
1033 * created dataset as projection change listener.
1034 */
1035 private static final List<WeakReference<ProjectionChangeListener>> listeners = new ArrayList<>();
1036
1037 private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) {
1038 if ((newValue == null ^ oldValue == null)
1039 || (newValue != null && oldValue != null && !Objects.equals(newValue.toCode(), oldValue.toCode()))) {
1040 if (Main.map != null) {
1041 // This needs to be called first
1042 Main.map.mapView.fixProjection();
1043 }
1044 synchronized (Main.class) {
1045 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
1046 while (it.hasNext()) {
1047 WeakReference<ProjectionChangeListener> wr = it.next();
1048 ProjectionChangeListener listener = wr.get();
1049 if (listener == null) {
1050 it.remove();
1051 continue;
1052 }
1053 listener.projectionChanged(oldValue, newValue);
1054 }
1055 }
1056 if (newValue != null && oldBounds != null) {
1057 Main.map.mapView.zoomTo(oldBounds);
1058 }
1059 /* TODO - remove layers with fixed projection */
1060 }
1061 }
1062
1063 /**
1064 * Register a projection change listener.
1065 *
1066 * @param listener the listener. Ignored if <code>null</code>.
1067 */
1068 public static void addProjectionChangeListener(ProjectionChangeListener listener) {
1069 if (listener == null) return;
1070 synchronized (Main.class) {
1071 for (WeakReference<ProjectionChangeListener> wr : listeners) {
1072 // already registered ? => abort
1073 if (wr.get() == listener) return;
1074 }
1075 listeners.add(new WeakReference<>(listener));
1076 }
1077 }
1078
1079 /**
1080 * Removes a projection change listener.
1081 *
1082 * @param listener the listener. Ignored if <code>null</code>.
1083 */
1084 public static void removeProjectionChangeListener(ProjectionChangeListener listener) {
1085 if (listener == null) return;
1086 synchronized (Main.class) {
1087 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
1088 while (it.hasNext()) {
1089 WeakReference<ProjectionChangeListener> wr = it.next();
1090 // remove the listener - and any other listener which got garbage
1091 // collected in the meantime
1092 if (wr.get() == null || wr.get() == listener) {
1093 it.remove();
1094 }
1095 }
1096 }
1097 }
1098
1099 /**
1100 * Listener for window switch events.
1101 *
1102 * These are events, when the user activates a window of another application
1103 * or comes back to JOSM. Window switches from one JOSM window to another
1104 * are not reported.
1105 */
1106 public interface WindowSwitchListener {
1107 /**
1108 * Called when the user activates a window of another application.
1109 */
1110 void toOtherApplication();
1111
1112 /**
1113 * Called when the user comes from a window of another application back to JOSM.
1114 */
1115 void fromOtherApplication();
1116 }
1117
1118 private static final List<WeakReference<WindowSwitchListener>> windowSwitchListeners = new ArrayList<>();
1119
1120 /**
1121 * Register a window switch listener.
1122 *
1123 * @param listener the listener. Ignored if <code>null</code>.
1124 */
1125 public static void addWindowSwitchListener(WindowSwitchListener listener) {
1126 if (listener == null) return;
1127 synchronized (Main.class) {
1128 for (WeakReference<WindowSwitchListener> wr : windowSwitchListeners) {
1129 // already registered ? => abort
1130 if (wr.get() == listener) return;
1131 }
1132 boolean wasEmpty = windowSwitchListeners.isEmpty();
1133 windowSwitchListeners.add(new WeakReference<>(listener));
1134 if (wasEmpty) {
1135 // The following call will have no effect, when there is no window
1136 // at the time. Therefore, MasterWindowListener.setup() will also be
1137 // called, as soon as the main window is shown.
1138 MasterWindowListener.setup();
1139 }
1140 }
1141 }
1142
1143 /**
1144 * Removes a window switch listener.
1145 *
1146 * @param listener the listener. Ignored if <code>null</code>.
1147 */
1148 public static void removeWindowSwitchListener(WindowSwitchListener listener) {
1149 if (listener == null) return;
1150 synchronized (Main.class) {
1151 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1152 while (it.hasNext()) {
1153 WeakReference<WindowSwitchListener> wr = it.next();
1154 // remove the listener - and any other listener which got garbage
1155 // collected in the meantime
1156 if (wr.get() == null || wr.get() == listener) {
1157 it.remove();
1158 }
1159 }
1160 if (windowSwitchListeners.isEmpty()) {
1161 MasterWindowListener.teardown();
1162 }
1163 }
1164 }
1165
1166 /**
1167 * WindowListener, that is registered on all Windows of the application.
1168 *
1169 * Its purpose is to notify WindowSwitchListeners, that the user switches to
1170 * another application, e.g. a browser, or back to JOSM.
1171 *
1172 * When changing from JOSM to another application and back (e.g. two times
1173 * alt+tab), the active Window within JOSM may be different.
1174 * Therefore, we need to register listeners to <strong>all</strong> (visible)
1175 * Windows in JOSM, and it does not suffice to monitor the one that was
1176 * deactivated last.
1177 *
1178 * This class is only "active" on demand, i.e. when there is at least one
1179 * WindowSwitchListener registered.
1180 */
1181 protected static class MasterWindowListener extends WindowAdapter {
1182
1183 private static MasterWindowListener INSTANCE;
1184
1185 public static synchronized MasterWindowListener getInstance() {
1186 if (INSTANCE == null) {
1187 INSTANCE = new MasterWindowListener();
1188 }
1189 return INSTANCE;
1190 }
1191
1192 /**
1193 * Register listeners to all non-hidden windows.
1194 *
1195 * Windows that are created later, will be cared for in {@link #windowDeactivated(WindowEvent)}.
1196 */
1197 public static void setup() {
1198 if (!windowSwitchListeners.isEmpty()) {
1199 for (Window w : Window.getWindows()) {
1200 if (w.isShowing() && !Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
1201 w.addWindowListener(getInstance());
1202 }
1203 }
1204 }
1205 }
1206
1207 /**
1208 * Unregister all listeners.
1209 */
1210 public static void teardown() {
1211 for (Window w : Window.getWindows()) {
1212 w.removeWindowListener(getInstance());
1213 }
1214 }
1215
1216 @Override
1217 public void windowActivated(WindowEvent e) {
1218 if (e.getOppositeWindow() == null) { // we come from a window of a different application
1219 // fire WindowSwitchListeners
1220 synchronized (Main.class) {
1221 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1222 while (it.hasNext()) {
1223 WeakReference<WindowSwitchListener> wr = it.next();
1224 WindowSwitchListener listener = wr.get();
1225 if (listener == null) {
1226 it.remove();
1227 continue;
1228 }
1229 listener.fromOtherApplication();
1230 }
1231 }
1232 }
1233 }
1234
1235 @Override
1236 public void windowDeactivated(WindowEvent e) {
1237 // set up windows that have been created in the meantime
1238 for (Window w : Window.getWindows()) {
1239 if (!w.isShowing()) {
1240 w.removeWindowListener(getInstance());
1241 } else {
1242 if (!Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
1243 w.addWindowListener(getInstance());
1244 }
1245 }
1246 }
1247 if (e.getOppositeWindow() == null) { // we go to a window of a different application
1248 // fire WindowSwitchListeners
1249 synchronized (Main.class) {
1250 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1251 while (it.hasNext()) {
1252 WeakReference<WindowSwitchListener> wr = it.next();
1253 WindowSwitchListener listener = wr.get();
1254 if (listener == null) {
1255 it.remove();
1256 continue;
1257 }
1258 listener.toOtherApplication();
1259 }
1260 }
1261 }
1262 }
1263 }
1264
1265 /**
1266 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
1267 * @param listener The MapFrameListener
1268 * @param fireWhenMapViewPresent If true, will fire an initial mapFrameInitialized event
1269 * when the MapFrame is present. Otherwise will only fire when the MapFrame is created
1270 * or destroyed.
1271 * @return {@code true} if the listeners collection changed as a result of the call
1272 */
1273 public static boolean addMapFrameListener(MapFrameListener listener, boolean fireWhenMapViewPresent) {
1274 if (fireWhenMapViewPresent) {
1275 return mainPanel.addAndFireMapFrameListener(listener);
1276 } else {
1277 return mainPanel.addMapFrameListener(listener);
1278 }
1279 }
1280
1281 /**
1282 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
1283 * @param listener The MapFrameListener
1284 * @return {@code true} if the listeners collection changed as a result of the call
1285 * @since 5957
1286 */
1287 public static boolean addMapFrameListener(MapFrameListener listener) {
1288 return mainPanel.addMapFrameListener(listener);
1289 }
1290
1291 /**
1292 * Unregisters the given {@code MapFrameListener} from MapFrame changes
1293 * @param listener The MapFrameListener
1294 * @return {@code true} if the listeners collection changed as a result of the call
1295 * @since 5957
1296 */
1297 public static boolean removeMapFrameListener(MapFrameListener listener) {
1298 return mainPanel.removeMapFrameListener(listener);
1299 }
1300
1301 /**
1302 * Adds a new network error that occur to give a hint about broken Internet connection.
1303 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
1304 *
1305 * @param url The accessed URL that caused the error
1306 * @param t The network error
1307 * @return The previous error associated to the given resource, if any. Can be {@code null}
1308 * @since 6642
1309 */
1310 public static Throwable addNetworkError(URL url, Throwable t) {
1311 if (url != null && t != null) {
1312 Throwable old = addNetworkError(url.toExternalForm(), t);
1313 if (old != null) {
1314 Main.warn("Already here "+old);
1315 }
1316 return old;
1317 }
1318 return null;
1319 }
1320
1321 /**
1322 * Adds a new network error that occur to give a hint about broken Internet connection.
1323 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
1324 *
1325 * @param url The accessed URL that caused the error
1326 * @param t The network error
1327 * @return The previous error associated to the given resource, if any. Can be {@code null}
1328 * @since 6642
1329 */
1330 public static Throwable addNetworkError(String url, Throwable t) {
1331 if (url != null && t != null) {
1332 return NETWORK_ERRORS.put(url, t);
1333 }
1334 return null;
1335 }
1336
1337 /**
1338 * Returns the network errors that occured until now.
1339 * @return the network errors that occured until now, indexed by URL
1340 * @since 6639
1341 */
1342 public static Map<String, Throwable> getNetworkErrors() {
1343 return new HashMap<>(NETWORK_ERRORS);
1344 }
1345
1346 /**
1347 * Returns the command-line arguments used to run the application.
1348 * @return the command-line arguments used to run the application
1349 * @since 8356
1350 */
1351 public static List<String> getCommandLineArgs() {
1352 return Collections.unmodifiableList(COMMAND_LINE_ARGS);
1353 }
1354
1355 /**
1356 * Returns the JOSM website URL.
1357 * @return the josm website URL
1358 * @since 6897
1359 */
1360 public static String getJOSMWebsite() {
1361 if (Main.pref != null)
1362 return Main.pref.get("josm.url", JOSM_WEBSITE);
1363 return JOSM_WEBSITE;
1364 }
1365
1366 /**
1367 * Returns the JOSM XML URL.
1368 * @return the josm XML URL
1369 * @since 6897
1370 */
1371 public static String getXMLBase() {
1372 // Always return HTTP (issues reported with HTTPS)
1373 return "http://josm.openstreetmap.de";
1374 }
1375
1376 /**
1377 * Returns the OSM website URL.
1378 * @return the OSM website URL
1379 * @since 6897
1380 */
1381 public static String getOSMWebsite() {
1382 if (Main.pref != null)
1383 return Main.pref.get("osm.url", OSM_WEBSITE);
1384 return OSM_WEBSITE;
1385 }
1386
1387 /**
1388 * Replies the base URL for browsing information about a primitive.
1389 * @return the base URL, i.e. https://www.openstreetmap.org
1390 * @since 7678
1391 */
1392 public static String getBaseBrowseUrl() {
1393 if (Main.pref != null)
1394 return Main.pref.get("osm-browse.url", getOSMWebsite());
1395 return getOSMWebsite();
1396 }
1397
1398 /**
1399 * Replies the base URL for browsing information about a user.
1400 * @return the base URL, i.e. https://www.openstreetmap.org/user
1401 * @since 7678
1402 */
1403 public static String getBaseUserUrl() {
1404 if (Main.pref != null)
1405 return Main.pref.get("osm-user.url", getOSMWebsite() + "/user");
1406 return getOSMWebsite() + "/user";
1407 }
1408
1409 /**
1410 * Determines if we are currently running on OSX.
1411 * @return {@code true} if we are currently running on OSX
1412 * @since 6957
1413 */
1414 public static boolean isPlatformOsx() {
1415 return Main.platform instanceof PlatformHookOsx;
1416 }
1417
1418 /**
1419 * Determines if we are currently running on Windows.
1420 * @return {@code true} if we are currently running on Windows
1421 * @since 7335
1422 */
1423 public static boolean isPlatformWindows() {
1424 return Main.platform instanceof PlatformHookWindows;
1425 }
1426
1427 /**
1428 * Determines if the given online resource is currently offline.
1429 * @param r the online resource
1430 * @return {@code true} if {@code r} is offline and should not be accessed
1431 * @since 7434
1432 */
1433 public static boolean isOffline(OnlineResource r) {
1434 return OFFLINE_RESOURCES.contains(r) || OFFLINE_RESOURCES.contains(OnlineResource.ALL);
1435 }
1436
1437 /**
1438 * Sets the given online resource to offline state.
1439 * @param r the online resource
1440 * @return {@code true} if {@code r} was not already offline
1441 * @since 7434
1442 */
1443 public static boolean setOffline(OnlineResource r) {
1444 return OFFLINE_RESOURCES.add(r);
1445 }
1446
1447 /**
1448 * Sets the given online resource to online state.
1449 * @param r the online resource
1450 * @return {@code true} if {@code r} was offline
1451 * @since 8506
1452 */
1453 public static boolean setOnline(OnlineResource r) {
1454 return OFFLINE_RESOURCES.remove(r);
1455 }
1456
1457 /**
1458 * Replies the set of online resources currently offline.
1459 * @return the set of online resources currently offline
1460 * @since 7434
1461 */
1462 public static Set<OnlineResource> getOfflineResources() {
1463 return EnumSet.copyOf(OFFLINE_RESOURCES);
1464 }
1465}
Note: See TracBrowser for help on using the repository browser.