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