001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.awt.Dimension; 008import java.io.File; 009import java.io.IOException; 010import java.io.InputStream; 011import java.net.Authenticator; 012import java.net.Inet6Address; 013import java.net.InetAddress; 014import java.net.InetSocketAddress; 015import java.net.ProxySelector; 016import java.net.Socket; 017import java.net.URL; 018import java.security.AllPermission; 019import java.security.CodeSource; 020import java.security.GeneralSecurityException; 021import java.security.KeyStoreException; 022import java.security.NoSuchAlgorithmException; 023import java.security.PermissionCollection; 024import java.security.Permissions; 025import java.security.Policy; 026import java.security.cert.CertificateException; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.List; 030import java.util.Locale; 031import java.util.Optional; 032import java.util.Set; 033import java.util.TreeSet; 034import java.util.logging.Level; 035 036import javax.swing.JOptionPane; 037import javax.swing.RepaintManager; 038import javax.swing.SwingUtilities; 039 040import org.jdesktop.swinghelper.debug.CheckThreadViolationRepaintManager; 041import org.openstreetmap.josm.Main; 042import org.openstreetmap.josm.actions.PreferencesAction; 043import org.openstreetmap.josm.actions.RestartAction; 044import org.openstreetmap.josm.data.AutosaveTask; 045import org.openstreetmap.josm.data.CustomConfigurator; 046import org.openstreetmap.josm.data.Version; 047import org.openstreetmap.josm.gui.ProgramArguments.Option; 048import org.openstreetmap.josm.gui.SplashScreen.SplashProgressMonitor; 049import org.openstreetmap.josm.gui.download.DownloadDialog; 050import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder; 051import org.openstreetmap.josm.gui.preferences.server.ProxyPreference; 052import org.openstreetmap.josm.gui.util.GuiHelper; 053import org.openstreetmap.josm.io.CertificateAmendment; 054import org.openstreetmap.josm.io.DefaultProxySelector; 055import org.openstreetmap.josm.io.MessageNotifier; 056import org.openstreetmap.josm.io.OnlineResource; 057import org.openstreetmap.josm.io.auth.CredentialsManager; 058import org.openstreetmap.josm.io.auth.DefaultAuthenticator; 059import org.openstreetmap.josm.io.protocols.data.Handler; 060import org.openstreetmap.josm.io.remotecontrol.RemoteControl; 061import org.openstreetmap.josm.plugins.PluginHandler; 062import org.openstreetmap.josm.plugins.PluginInformation; 063import org.openstreetmap.josm.tools.FontsManager; 064import org.openstreetmap.josm.tools.HttpClient; 065import org.openstreetmap.josm.tools.I18n; 066import org.openstreetmap.josm.tools.Logging; 067import org.openstreetmap.josm.tools.OsmUrlToBounds; 068import org.openstreetmap.josm.tools.PlatformHookWindows; 069import org.openstreetmap.josm.tools.Utils; 070import org.openstreetmap.josm.tools.WindowGeometry; 071import org.openstreetmap.josm.tools.bugreport.BugReport; 072import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler; 073 074/** 075 * Main window class application. 076 * 077 * @author imi 078 */ 079public class MainApplication extends Main { 080 081 private MainFrame mainFrame; 082 083 /** 084 * Constructs a new {@code MainApplication} without a window. 085 */ 086 public MainApplication() { 087 // Allow subclassing (see JOSM.java) 088 this(null); 089 } 090 091 /** 092 * Constructs a main frame, ready sized and operating. Does not display the frame. 093 * @param mainFrame The main JFrame of the application 094 * @since 10340 095 */ 096 public MainApplication(MainFrame mainFrame) { 097 this.mainFrame = mainFrame; 098 } 099 100 @Override 101 protected void initializeMainWindow() { 102 mainPanel.reAddListeners(); 103 if (mainFrame != null) { 104 mainFrame.initialize(); 105 106 menu = mainFrame.getMenu(); 107 } else { 108 // required for running some tests. 109 menu = new MainMenu(); 110 } 111 } 112 113 @Override 114 protected void shutdown() { 115 if (mainFrame != null) { 116 mainFrame.storeState(); 117 } 118 super.shutdown(); 119 } 120 121 /** 122 * Displays help on the console 123 * @since 2748 124 */ 125 public static void showHelp() { 126 // TODO: put in a platformHook for system that have no console by default 127 System.out.println(getHelp()); 128 } 129 130 static String getHelp() { 131 return tr("Java OpenStreetMap Editor")+" [" 132 +Version.getInstance().getAgentString()+"]\n\n"+ 133 tr("usage")+":\n"+ 134 "\tjava -jar josm.jar <options>...\n\n"+ 135 tr("options")+":\n"+ 136 "\t--help|-h "+tr("Show this help")+'\n'+ 137 "\t--geometry=widthxheight(+|-)x(+|-)y "+tr("Standard unix geometry argument")+'\n'+ 138 "\t[--download=]minlat,minlon,maxlat,maxlon "+tr("Download the bounding box")+'\n'+ 139 "\t[--download=]<URL> "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z)")+'\n'+ 140 "\t[--download=]<filename> "+tr("Open a file (any file type that can be opened with File/Open)")+'\n'+ 141 "\t--downloadgps=minlat,minlon,maxlat,maxlon "+tr("Download the bounding box as raw GPS")+'\n'+ 142 "\t--downloadgps=<URL> "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z) as raw GPS")+'\n'+ 143 "\t--selection=<searchstring> "+tr("Select with the given search")+'\n'+ 144 "\t--[no-]maximize "+tr("Launch in maximized mode")+'\n'+ 145 "\t--reset-preferences "+tr("Reset the preferences to default")+"\n\n"+ 146 "\t--load-preferences=<url-to-xml> "+tr("Changes preferences according to the XML file")+"\n\n"+ 147 "\t--set=<key>=<value> "+tr("Set preference key to value")+"\n\n"+ 148 "\t--language=<language> "+tr("Set the language")+"\n\n"+ 149 "\t--version "+tr("Displays the JOSM version and exits")+"\n\n"+ 150 "\t--debug "+tr("Print debugging messages to console")+"\n\n"+ 151 "\t--skip-plugins "+tr("Skip loading plugins")+"\n\n"+ 152 "\t--offline=<osm_api|josm_website|all> "+tr("Disable access to the given resource(s), separated by comma")+"\n\n"+ 153 tr("options provided as Java system properties")+":\n"+ 154 // CHECKSTYLE.OFF: SingleSpaceSeparator 155 "\t-Djosm.pref=" +tr("/PATH/TO/JOSM/PREF ")+tr("Set the preferences directory")+"\n\n"+ 156 "\t-Djosm.userdata="+tr("/PATH/TO/JOSM/USERDATA")+tr("Set the user data directory")+"\n\n"+ 157 "\t-Djosm.cache=" +tr("/PATH/TO/JOSM/CACHE ")+tr("Set the cache directory")+"\n\n"+ 158 "\t-Djosm.home=" +tr("/PATH/TO/JOSM/HOMEDIR ")+ 159 // CHECKSTYLE.ON: SingleSpaceSeparator 160 tr("Relocate all 3 directories to homedir. Cache directory will be in homedir/cache")+"\n\n"+ 161 tr("-Djosm.home has lower precedence, i.e. the specific setting overrides the general one")+"\n\n"+ 162 tr("note: For some tasks, JOSM needs a lot of memory. It can be necessary to add the following\n" + 163 " Java option to specify the maximum size of allocated memory in megabytes")+":\n"+ 164 "\t-Xmx...m\n\n"+ 165 tr("examples")+":\n"+ 166 "\tjava -jar josm.jar track1.gpx track2.gpx london.osm\n"+ 167 "\tjava -jar josm.jar "+OsmUrlToBounds.getURL(43.2, 11.1, 13)+'\n'+ 168 "\tjava -jar josm.jar london.osm --selection=http://www.ostertag.name/osm/OSM_errors_node-duplicate.xml\n"+ 169 "\tjava -jar josm.jar 43.2,11.1,43.4,11.4\n"+ 170 "\tjava -Djosm.pref=$XDG_CONFIG_HOME -Djosm.userdata=$XDG_DATA_HOME -Djosm.cache=$XDG_CACHE_HOME -jar josm.jar\n"+ 171 "\tjava -Djosm.home=/home/user/.josm_dev -jar josm.jar\n"+ 172 "\tjava -Xmx1024m -jar josm.jar\n\n"+ 173 tr("Parameters --download, --downloadgps, and --selection are processed in this order.")+'\n'+ 174 tr("Make sure you load some data if you use --selection.")+'\n'; 175 } 176 177 /** 178 * Main application Startup 179 * @param argArray Command-line arguments 180 */ 181 public static void main(final String[] argArray) { 182 I18n.init(); 183 184 // construct argument table 185 ProgramArguments args = null; 186 try { 187 args = new ProgramArguments(argArray); 188 } catch (IllegalArgumentException e) { 189 System.err.println(e.getMessage()); 190 System.exit(1); 191 return; 192 } 193 194 Level logLevel = args.getLogLevel(); 195 Logging.setLogLevel(logLevel); 196 if (!args.showVersion() && !args.showHelp()) { 197 Main.info(tr("Log level is at {0} ({1}, {2})", logLevel.getLocalizedName(), logLevel.getName(), logLevel.intValue())); 198 } 199 200 Optional<String> language = args.getSingle(Option.LANGUAGE); 201 I18n.set(language.orElse(null)); 202 203 Policy.setPolicy(new Policy() { 204 // Permissions for plug-ins loaded when josm is started via webstart 205 private PermissionCollection pc; 206 207 { 208 pc = new Permissions(); 209 pc.add(new AllPermission()); 210 } 211 212 @Override 213 public PermissionCollection getPermissions(CodeSource codesource) { 214 return pc; 215 } 216 }); 217 218 Thread.setDefaultUncaughtExceptionHandler(new BugReportExceptionHandler()); 219 220 // initialize the platform hook, and 221 Main.determinePlatformHook(); 222 // call the really early hook before we do anything else 223 Main.platform.preStartupHook(); 224 225 if (args.showVersion()) { 226 System.out.println(Version.getInstance().getAgentString()); 227 return; 228 } else if (args.showHelp()) { 229 showHelp(); 230 return; 231 } 232 233 Main.COMMAND_LINE_ARGS.addAll(Arrays.asList(argArray)); 234 235 boolean skipLoadingPlugins = args.hasOption(Option.SKIP_PLUGINS); 236 if (skipLoadingPlugins) { 237 Main.info(tr("Plugin loading skipped")); 238 } 239 240 if (Logging.isLoggingEnabled(Logging.LEVEL_TRACE)) { 241 // Enable debug in OAuth signpost via system preference, but only at trace level 242 Utils.updateSystemProperty("debug", "true"); 243 Main.info(tr("Enabled detailed debug level (trace)")); 244 } 245 246 Main.pref.init(args.hasOption(Option.RESET_PREFERENCES)); 247 248 args.getPreferencesToSet().forEach(Main.pref::put); 249 250 if (!language.isPresent()) { 251 I18n.set(Main.pref.get("language", null)); 252 } 253 Main.pref.updateSystemProperties(); 254 255 checkIPv6(); 256 257 processOffline(args); 258 259 Main.platform.afterPrefStartupHook(); 260 261 FontsManager.initialize(); 262 263 I18n.setupLanguageFonts(); 264 265 Handler.install(); 266 267 WindowGeometry geometry = WindowGeometry.mainWindow("gui.geometry", 268 args.getSingle(Option.GEOMETRY).orElse(null), 269 !args.hasOption(Option.NO_MAXIMIZE) && Main.pref.getBoolean("gui.maximized", false)); 270 final MainFrame mainFrame = new MainFrame(contentPanePrivate, mainPanel, geometry); 271 Main.parent = mainFrame; 272 273 if (args.hasOption(Option.LOAD_PREFERENCES)) { 274 CustomConfigurator.XMLCommandProcessor config = new CustomConfigurator.XMLCommandProcessor(Main.pref); 275 for (String i : args.get(Option.LOAD_PREFERENCES)) { 276 info("Reading preferences from " + i); 277 try (InputStream is = HttpClient.create(new URL(i)).connect().getContent()) { 278 config.openAndReadXML(is); 279 } catch (IOException ex) { 280 throw BugReport.intercept(ex).put("file", i); 281 } 282 } 283 } 284 285 try { 286 CertificateAmendment.addMissingCertificates(); 287 } catch (IOException | GeneralSecurityException ex) { 288 Main.warn(ex); 289 Main.warn(getErrorMessage(Utils.getRootCause(ex))); 290 } 291 Authenticator.setDefault(DefaultAuthenticator.getInstance()); 292 DefaultProxySelector proxySelector = new DefaultProxySelector(ProxySelector.getDefault()); 293 ProxySelector.setDefault(proxySelector); 294 OAuthAccessTokenHolder.getInstance().init(Main.pref, CredentialsManager.getInstance()); 295 296 final SplashScreen splash = GuiHelper.runInEDTAndWaitAndReturn(SplashScreen::new); 297 final SplashScreen.SplashProgressMonitor monitor = splash.getProgressMonitor(); 298 monitor.beginTask(tr("Initializing")); 299 GuiHelper.runInEDT(() -> splash.setVisible(Main.pref.getBoolean("draw.splashscreen", true))); 300 Main.setInitStatusListener(new InitStatusListener() { 301 302 @Override 303 public Object updateStatus(String event) { 304 monitor.beginTask(event); 305 return event; 306 } 307 308 @Override 309 public void finish(Object status) { 310 if (status instanceof String) { 311 monitor.finishTask((String) status); 312 } 313 } 314 }); 315 316 Collection<PluginInformation> pluginsToLoad = null; 317 318 if (!skipLoadingPlugins) { 319 pluginsToLoad = updateAndLoadEarlyPlugins(splash, monitor); 320 } 321 322 monitor.indeterminateSubTask(tr("Setting defaults")); 323 preConstructorInit(); 324 325 monitor.indeterminateSubTask(tr("Creating main GUI")); 326 final Main main = new MainApplication(mainFrame); 327 main.initialize(); 328 329 if (!skipLoadingPlugins) { 330 loadLatePlugins(splash, monitor, pluginsToLoad); 331 } 332 333 // Wait for splash disappearance (fix #9714) 334 GuiHelper.runInEDTAndWait(() -> { 335 splash.setVisible(false); 336 splash.dispose(); 337 mainFrame.setVisible(true); 338 }); 339 340 Main.MasterWindowListener.setup(); 341 342 boolean maximized = Main.pref.getBoolean("gui.maximized", false); 343 if ((!args.hasOption(Option.NO_MAXIMIZE) && maximized) || args.hasOption(Option.MAXIMIZE)) { 344 mainFrame.setMaximized(true); 345 } 346 if (main.menu.fullscreenToggleAction != null) { 347 main.menu.fullscreenToggleAction.initial(); 348 } 349 350 SwingUtilities.invokeLater(new GuiFinalizationWorker(args, proxySelector)); 351 352 if (Main.isPlatformWindows()) { 353 try { 354 // Check for insecure certificates to remove. 355 // This is Windows-dependant code but it can't go to preStartupHook (need i18n) 356 // neither startupHook (need to be called before remote control) 357 PlatformHookWindows.removeInsecureCertificates(); 358 } catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | IOException e) { 359 error(e); 360 } 361 } 362 363 if (RemoteControl.PROP_REMOTECONTROL_ENABLED.get()) { 364 RemoteControl.start(); 365 } 366 367 if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) { 368 MessageNotifier.start(); 369 } 370 371 if (Main.pref.getBoolean("debug.edt-checker.enable", Version.getInstance().isLocalBuild())) { 372 // Repaint manager is registered so late for a reason - there is lots of violation during startup process 373 // but they don't seem to break anything and are difficult to fix 374 info("Enabled EDT checker, wrongful access to gui from non EDT thread will be printed to console"); 375 RepaintManager.setCurrentManager(new CheckThreadViolationRepaintManager()); 376 } 377 } 378 379 static Collection<PluginInformation> updateAndLoadEarlyPlugins(SplashScreen splash, SplashProgressMonitor monitor) { 380 Collection<PluginInformation> pluginsToLoad; 381 pluginsToLoad = PluginHandler.buildListOfPluginsToLoad(splash, monitor.createSubTaskMonitor(1, false)); 382 if (!pluginsToLoad.isEmpty() && PluginHandler.checkAndConfirmPluginUpdate(splash)) { 383 monitor.subTask(tr("Updating plugins")); 384 pluginsToLoad = PluginHandler.updatePlugins(splash, null, monitor.createSubTaskMonitor(1, false), false); 385 } 386 387 monitor.indeterminateSubTask(tr("Installing updated plugins")); 388 PluginHandler.installDownloadedPlugins(true); 389 390 monitor.indeterminateSubTask(tr("Loading early plugins")); 391 PluginHandler.loadEarlyPlugins(splash, pluginsToLoad, monitor.createSubTaskMonitor(1, false)); 392 return pluginsToLoad; 393 } 394 395 static void loadLatePlugins(SplashScreen splash, SplashProgressMonitor monitor, Collection<PluginInformation> pluginsToLoad) { 396 monitor.indeterminateSubTask(tr("Loading plugins")); 397 PluginHandler.loadLatePlugins(splash, pluginsToLoad, monitor.createSubTaskMonitor(1, false)); 398 toolbar.refreshToolbarControl(); 399 } 400 401 private static void processOffline(ProgramArguments args) { 402 for (String offlineNames : args.get(Option.OFFLINE)) { 403 for (String s : offlineNames.split(",")) { 404 try { 405 Main.setOffline(OnlineResource.valueOf(s.toUpperCase(Locale.ENGLISH))); 406 } catch (IllegalArgumentException e) { 407 Main.error(e, tr("''{0}'' is not a valid value for argument ''{1}''. Possible values are {2}, possibly delimited by commas.", 408 s.toUpperCase(Locale.ENGLISH), Option.OFFLINE.getName(), Arrays.toString(OnlineResource.values()))); 409 System.exit(1); 410 return; 411 } 412 } 413 } 414 Set<OnlineResource> offline = Main.getOfflineResources(); 415 if (!offline.isEmpty()) { 416 Main.warn(trn("JOSM is running in offline mode. This resource will not be available: {0}", 417 "JOSM is running in offline mode. These resources will not be available: {0}", 418 offline.size(), offline.size() == 1 ? offline.iterator().next() : Arrays.toString(offline.toArray()))); 419 } 420 } 421 422 /** 423 * Check if IPv6 can be safely enabled and do so. Because this cannot be done after network activation, 424 * disabling or enabling IPV6 may only be done with next start. 425 */ 426 private static void checkIPv6() { 427 if ("auto".equals(Main.pref.get("prefer.ipv6", "auto"))) { 428 new Thread((Runnable) () -> { /* this may take some time (DNS, Connect) */ 429 boolean hasv6 = false; 430 boolean wasv6 = Main.pref.getBoolean("validated.ipv6", false); 431 try { 432 /* Use the check result from last run of the software, as after the test, value 433 changes have no effect anymore */ 434 if (wasv6) { 435 Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true"); 436 } 437 for (InetAddress a : InetAddress.getAllByName("josm.openstreetmap.de")) { 438 if (a instanceof Inet6Address) { 439 if (a.isReachable(1000)) { 440 /* be sure it REALLY works */ 441 Socket s = new Socket(); 442 s.connect(new InetSocketAddress(a, 80), 1000); 443 s.close(); 444 Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true"); 445 if (!wasv6) { 446 Main.info(tr("Detected useable IPv6 network, prefering IPv6 over IPv4 after next restart.")); 447 } else { 448 Main.info(tr("Detected useable IPv6 network, prefering IPv6 over IPv4.")); 449 } 450 hasv6 = true; 451 } 452 break; /* we're done */ 453 } 454 } 455 } catch (IOException | SecurityException e) { 456 if (Main.isDebugEnabled()) { 457 Main.debug("Exception while checking IPv6 connectivity: "+e); 458 } 459 Main.trace(e); 460 } 461 if (wasv6 && !hasv6) { 462 Main.info(tr("Detected no useable IPv6 network, prefering IPv4 over IPv6 after next restart.")); 463 Main.pref.put("validated.ipv6", hasv6); // be sure it is stored before the restart! 464 new RestartAction().actionPerformed(null); 465 } 466 Main.pref.put("validated.ipv6", hasv6); 467 }, "IPv6-checker").start(); 468 } 469 } 470 471 private static class GuiFinalizationWorker implements Runnable { 472 473 private final ProgramArguments args; 474 private final DefaultProxySelector proxySelector; 475 476 GuiFinalizationWorker(ProgramArguments args, DefaultProxySelector proxySelector) { 477 this.args = args; 478 this.proxySelector = proxySelector; 479 } 480 481 @Override 482 public void run() { 483 484 // Handle proxy/network errors early to inform user he should change settings to be able to use JOSM correctly 485 if (!handleProxyErrors()) { 486 handleNetworkErrors(); 487 } 488 489 // Restore autosave layers after crash and start autosave thread 490 handleAutosave(); 491 492 // Handle command line instructions 493 postConstructorProcessCmdLine(args); 494 495 // Show download dialog if autostart is enabled 496 DownloadDialog.autostartIfNeeded(); 497 } 498 499 private static void handleAutosave() { 500 if (AutosaveTask.PROP_AUTOSAVE_ENABLED.get()) { 501 AutosaveTask autosaveTask = new AutosaveTask(); 502 List<File> unsavedLayerFiles = autosaveTask.getUnsavedLayersFiles(); 503 if (!unsavedLayerFiles.isEmpty()) { 504 ExtendedDialog dialog = new ExtendedDialog( 505 Main.parent, 506 tr("Unsaved osm data"), 507 new String[] {tr("Restore"), tr("Cancel"), tr("Discard")} 508 ); 509 dialog.setContent( 510 trn("JOSM found {0} unsaved osm data layer. ", 511 "JOSM found {0} unsaved osm data layers. ", unsavedLayerFiles.size(), unsavedLayerFiles.size()) + 512 tr("It looks like JOSM crashed last time. Would you like to restore the data?")); 513 dialog.setButtonIcons(new String[] {"ok", "cancel", "dialogs/delete"}); 514 int selection = dialog.showDialog().getValue(); 515 if (selection == 1) { 516 autosaveTask.recoverUnsavedLayers(); 517 } else if (selection == 3) { 518 autosaveTask.discardUnsavedLayers(); 519 } 520 } 521 autosaveTask.schedule(); 522 } 523 } 524 525 private static boolean handleNetworkOrProxyErrors(boolean hasErrors, String title, String message) { 526 if (hasErrors) { 527 ExtendedDialog ed = new ExtendedDialog( 528 Main.parent, title, 529 new String[]{tr("Change proxy settings"), tr("Cancel")}); 530 ed.setButtonIcons(new String[]{"dialogs/settings", "cancel"}).setCancelButton(2); 531 ed.setMinimumSize(new Dimension(460, 260)); 532 ed.setIcon(JOptionPane.WARNING_MESSAGE); 533 ed.setContent(message); 534 535 if (ed.showDialog().getValue() == 1) { 536 PreferencesAction.forPreferenceSubTab(null, null, ProxyPreference.class).run(); 537 } 538 } 539 return hasErrors; 540 } 541 542 private boolean handleProxyErrors() { 543 return handleNetworkOrProxyErrors(proxySelector.hasErrors(), tr("Proxy errors occurred"), 544 tr("JOSM tried to access the following resources:<br>" + 545 "{0}" + 546 "but <b>failed</b> to do so, because of the following proxy errors:<br>" + 547 "{1}" + 548 "Would you like to change your proxy settings now?", 549 Utils.joinAsHtmlUnorderedList(proxySelector.getErrorResources()), 550 Utils.joinAsHtmlUnorderedList(proxySelector.getErrorMessages()) 551 )); 552 } 553 554 private static boolean handleNetworkErrors() { 555 boolean condition = !NETWORK_ERRORS.isEmpty(); 556 if (condition) { 557 Set<String> errors = new TreeSet<>(); 558 for (Throwable t : NETWORK_ERRORS.values()) { 559 errors.add(t.toString()); 560 } 561 return handleNetworkOrProxyErrors(condition, tr("Network errors occurred"), 562 tr("JOSM tried to access the following resources:<br>" + 563 "{0}" + 564 "but <b>failed</b> to do so, because of the following network errors:<br>" + 565 "{1}" + 566 "It may be due to a missing proxy configuration.<br>" + 567 "Would you like to change your proxy settings now?", 568 Utils.joinAsHtmlUnorderedList(NETWORK_ERRORS.keySet()), 569 Utils.joinAsHtmlUnorderedList(errors) 570 )); 571 } 572 return false; 573 } 574 } 575}