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

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

remove deprecated stuff - gsoc-core

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