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; 006import gnu.getopt.Getopt; 007import gnu.getopt.LongOpt; 008 009import java.awt.Dimension; 010import java.awt.Image; 011import java.awt.Toolkit; 012import java.awt.event.WindowAdapter; 013import java.awt.event.WindowEvent; 014import java.io.File; 015import java.io.IOException; 016import java.io.InputStream; 017import java.net.Authenticator; 018import java.net.ProxySelector; 019import java.net.URL; 020import java.security.AllPermission; 021import java.security.CodeSource; 022import java.security.KeyStoreException; 023import java.security.NoSuchAlgorithmException; 024import java.security.PermissionCollection; 025import java.security.Permissions; 026import java.security.Policy; 027import java.security.cert.CertificateException; 028import java.util.ArrayList; 029import java.util.Collection; 030import java.util.HashMap; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035import java.util.TreeSet; 036 037import javax.swing.JFrame; 038import javax.swing.JOptionPane; 039import javax.swing.RepaintManager; 040import javax.swing.SwingUtilities; 041 042import org.jdesktop.swinghelper.debug.CheckThreadViolationRepaintManager; 043import org.openstreetmap.josm.Main; 044import org.openstreetmap.josm.actions.PreferencesAction; 045import org.openstreetmap.josm.data.AutosaveTask; 046import org.openstreetmap.josm.data.CustomConfigurator; 047import org.openstreetmap.josm.data.Preferences; 048import org.openstreetmap.josm.data.Version; 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.progress.ProgressMonitor; 053import org.openstreetmap.josm.gui.util.GuiHelper; 054import org.openstreetmap.josm.io.DefaultProxySelector; 055import org.openstreetmap.josm.io.MessageNotifier; 056import org.openstreetmap.josm.io.auth.CredentialsManager; 057import org.openstreetmap.josm.io.auth.DefaultAuthenticator; 058import org.openstreetmap.josm.io.remotecontrol.RemoteControl; 059import org.openstreetmap.josm.plugins.PluginHandler; 060import org.openstreetmap.josm.plugins.PluginInformation; 061import org.openstreetmap.josm.tools.BugReportExceptionHandler; 062import org.openstreetmap.josm.tools.I18n; 063import org.openstreetmap.josm.tools.ImageProvider; 064import org.openstreetmap.josm.tools.OsmUrlToBounds; 065import org.openstreetmap.josm.tools.PlatformHookWindows; 066import org.openstreetmap.josm.tools.Utils; 067 068/** 069 * Main window class application. 070 * 071 * @author imi 072 */ 073public class MainApplication extends Main { 074 /** 075 * Allow subclassing (see JOSM.java) 076 */ 077 public MainApplication() {} 078 079 /** 080 * Constructs a main frame, ready sized and operating. Does not display the frame. 081 * @param mainFrame The main JFrame of the application 082 */ 083 public MainApplication(JFrame mainFrame) { 084 addListener(); 085 mainFrame.setContentPane(contentPanePrivate); 086 mainFrame.setJMenuBar(menu); 087 geometry.applySafe(mainFrame); 088 LinkedList<Image> l = new LinkedList<>(); 089 l.add(ImageProvider.get("logo_16x16x32").getImage()); 090 l.add(ImageProvider.get("logo_16x16x8").getImage()); 091 l.add(ImageProvider.get("logo_32x32x32").getImage()); 092 l.add(ImageProvider.get("logo_32x32x8").getImage()); 093 l.add(ImageProvider.get("logo_48x48x32").getImage()); 094 l.add(ImageProvider.get("logo_48x48x8").getImage()); 095 l.add(ImageProvider.get("logo").getImage()); 096 mainFrame.setIconImages(l); 097 mainFrame.addWindowListener(new WindowAdapter(){ 098 @Override 099 public void windowClosing(final WindowEvent arg0) { 100 Main.exitJosm(true, 0); 101 } 102 }); 103 mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); 104 } 105 106 /** 107 * Displays help on the console 108 * @since 2748 109 */ 110 public static void showHelp() { 111 // TODO: put in a platformHook for system that have no console by default 112 System.out.println(tr("Java OpenStreetMap Editor")+" [" 113 +Version.getInstance().getAgentString()+"]\n\n"+ 114 tr("usage")+":\n"+ 115 "\tjava -jar josm.jar <options>...\n\n"+ 116 tr("options")+":\n"+ 117 "\t--help|-h "+tr("Show this help")+"\n"+ 118 "\t--geometry=widthxheight(+|-)x(+|-)y "+tr("Standard unix geometry argument")+"\n"+ 119 "\t[--download=]minlat,minlon,maxlat,maxlon "+tr("Download the bounding box")+"\n"+ 120 "\t[--download=]<URL> "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z)")+"\n"+ 121 "\t[--download=]<filename> "+tr("Open a file (any file type that can be opened with File/Open)")+"\n"+ 122 "\t--downloadgps=minlat,minlon,maxlat,maxlon "+tr("Download the bounding box as raw GPS")+"\n"+ 123 "\t--downloadgps=<URL> "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z) as raw GPS")+"\n"+ 124 "\t--selection=<searchstring> "+tr("Select with the given search")+"\n"+ 125 "\t--[no-]maximize "+tr("Launch in maximized mode")+"\n"+ 126 "\t--reset-preferences "+tr("Reset the preferences to default")+"\n\n"+ 127 "\t--load-preferences=<url-to-xml> "+tr("Changes preferences according to the XML file")+"\n\n"+ 128 "\t--set=<key>=<value> "+tr("Set preference key to value")+"\n\n"+ 129 "\t--language=<language> "+tr("Set the language")+"\n\n"+ 130 "\t--version "+tr("Displays the JOSM version and exits")+"\n\n"+ 131 "\t--debug "+tr("Print debugging messages to console")+"\n\n"+ 132 tr("options provided as Java system properties")+":\n"+ 133 "\t-Djosm.home="+tr("/PATH/TO/JOSM/FOLDER/ ")+tr("Change the folder for all user settings")+"\n\n"+ 134 tr("note: For some tasks, JOSM needs a lot of memory. It can be necessary to add the following\n" + 135 " Java option to specify the maximum size of allocated memory in megabytes")+":\n"+ 136 "\t-Xmx...m\n\n"+ 137 tr("examples")+":\n"+ 138 "\tjava -jar josm.jar track1.gpx track2.gpx london.osm\n"+ 139 "\tjava -jar josm.jar "+OsmUrlToBounds.getURL(43.2, 11.1, 13)+"\n"+ 140 "\tjava -jar josm.jar london.osm --selection=http://www.ostertag.name/osm/OSM_errors_node-duplicate.xml\n"+ 141 "\tjava -jar josm.jar 43.2,11.1,43.4,11.4\n"+ 142 "\tjava -Djosm.home=/home/user/.josm_dev -jar josm.jar\n"+ 143 "\tjava -Xmx1024m -jar josm.jar\n\n"+ 144 tr("Parameters --download, --downloadgps, and --selection are processed in this order.")+"\n"+ 145 tr("Make sure you load some data if you use --selection.")+"\n" 146 ); 147 } 148 149 /** 150 * JOSM command line options. 151 * @see <a href="https://josm.openstreetmap.de/wiki/Help/CommandLineOptions">Help/CommandLineOptions</a> 152 * @since 5279 153 */ 154 public enum Option { 155 /** --help|-h Show this help */ 156 HELP(false), 157 /** --version Displays the JOSM version and exits */ 158 VERSION(false), 159 /** --debug Print debugging messages to console */ 160 DEBUG(false), 161 /** --trace Print detailed debugging messages to console */ 162 TRACE(false), 163 /** --language=<language> Set the language */ 164 LANGUAGE(true), 165 /** --reset-preferences Reset the preferences to default */ 166 RESET_PREFERENCES(false), 167 /** --load-preferences=<url-to-xml> Changes preferences according to the XML file */ 168 LOAD_PREFERENCES(true), 169 /** --set=<key>=<value> Set preference key to value */ 170 SET(true), 171 /** --geometry=widthxheight(+|-)x(+|-)y Standard unix geometry argument */ 172 GEOMETRY(true), 173 /** --no-maximize Do not launch in maximized mode */ 174 NO_MAXIMIZE(false), 175 /** --maximize Launch in maximized mode */ 176 MAXIMIZE(false), 177 /** --download=minlat,minlon,maxlat,maxlon Download the bounding box <br> 178 * --download=<URL> Download the location at the URL (with lat=x&lon=y&zoom=z) <br> 179 * --download=<filename> Open a file (any file type that can be opened with File/Open) */ 180 DOWNLOAD(true), 181 /** --downloadgps=minlat,minlon,maxlat,maxlon Download the bounding box as raw GPS <br> 182 * --downloadgps=<URL> Download the location at the URL (with lat=x&lon=y&zoom=z) as raw GPS */ 183 DOWNLOADGPS(true), 184 /** --selection=<searchstring> Select with the given search */ 185 SELECTION(true); 186 187 private String name; 188 private boolean requiresArgument; 189 190 private Option(boolean requiresArgument) { 191 this.name = name().toLowerCase().replace("_", "-"); 192 this.requiresArgument = requiresArgument; 193 } 194 195 /** 196 * Replies the option name 197 * @return The option name, in lowercase 198 */ 199 public String getName() { 200 return name; 201 } 202 203 /** 204 * Determines if this option requires an argument. 205 * @return {@code true} if this option requires an argument, {@code false} otherwise 206 */ 207 public boolean requiresArgument() { 208 return requiresArgument; 209 } 210 211 public static Map<Option, Collection<String>> fromStringMap(Map<String, Collection<String>> opts) { 212 Map<Option, Collection<String>> res = new HashMap<>(); 213 for (Map.Entry<String, Collection<String>> e : opts.entrySet()) { 214 Option o = Option.valueOf(e.getKey().toUpperCase().replace("-", "_")); 215 if (o != null) { 216 res.put(o, e.getValue()); 217 } 218 } 219 return res; 220 } 221 } 222 223 private static Map<Option, Collection<String>> buildCommandLineArgumentMap(String[] args) { 224 225 List<LongOpt> los = new ArrayList<>(); 226 for (Option o : Option.values()) { 227 los.add(new LongOpt(o.getName(), o.requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, 0)); 228 } 229 230 Getopt g = new Getopt("JOSM", args, "hv", los.toArray(new LongOpt[los.size()])); 231 232 Map<Option, Collection<String>> argMap = new HashMap<>(); 233 234 int c; 235 while ((c = g.getopt()) != -1 ) { 236 Option opt = null; 237 switch (c) { 238 case 'h': 239 opt = Option.HELP; 240 break; 241 case 'v': 242 opt = Option.VERSION; 243 break; 244 case 0: 245 opt = Option.values()[g.getLongind()]; 246 break; 247 } 248 if (opt != null) { 249 Collection<String> values = argMap.get(opt); 250 if (values == null) { 251 values = new ArrayList<>(); 252 argMap.put(opt, values); 253 } 254 values.add(g.getOptarg()); 255 } else 256 throw new IllegalArgumentException(); 257 } 258 // positional arguments are a shortcut for the --download ... option 259 for (int i = g.getOptind(); i < args.length; ++i) { 260 Collection<String> values = argMap.get(Option.DOWNLOAD); 261 if (values == null) { 262 values = new ArrayList<>(); 263 argMap.put(Option.DOWNLOAD, values); 264 } 265 values.add(args[i]); 266 } 267 268 return argMap; 269 } 270 271 /** 272 * Main application Startup 273 * @param argArray Command-line arguments 274 */ 275 public static void main(final String[] argArray) { 276 I18n.init(); 277 Main.checkJavaVersion(); 278 279 // construct argument table 280 Map<Option, Collection<String>> args = null; 281 try { 282 args = buildCommandLineArgumentMap(argArray); 283 } catch (IllegalArgumentException e) { 284 System.exit(1); 285 } 286 287 final boolean languageGiven = args.containsKey(Option.LANGUAGE); 288 289 if (languageGiven) { 290 I18n.set(args.get(Option.LANGUAGE).iterator().next()); 291 } 292 293 initApplicationPreferences(); 294 295 Policy.setPolicy(new Policy() { 296 // Permissions for plug-ins loaded when josm is started via webstart 297 private PermissionCollection pc; 298 299 { 300 pc = new Permissions(); 301 pc.add(new AllPermission()); 302 } 303 304 @Override 305 public void refresh() { } 306 307 @Override 308 public PermissionCollection getPermissions(CodeSource codesource) { 309 return pc; 310 } 311 }); 312 313 Thread.setDefaultUncaughtExceptionHandler(new BugReportExceptionHandler()); 314 315 // initialize the platform hook, and 316 Main.determinePlatformHook(); 317 // call the really early hook before we do anything else 318 Main.platform.preStartupHook(); 319 320 Main.commandLineArgs = Utils.copyArray(argArray); 321 322 if (args.containsKey(Option.VERSION)) { 323 System.out.println(Version.getInstance().getAgentString()); 324 System.exit(0); 325 } 326 327 if (args.containsKey(Option.DEBUG) || args.containsKey(Option.TRACE)) { 328 // Enable JOSM debug level 329 logLevel = 4; 330 Main.info(tr("Printing debugging messages to console")); 331 } 332 333 if (args.containsKey(Option.TRACE)) { 334 // Enable JOSM debug level 335 logLevel = 5; 336 // Enable debug in OAuth signpost via system preference, but only at trace level 337 Preferences.updateSystemProperty("debug", "true"); 338 Main.info(tr("Enabled detailed debug level (trace)")); 339 } 340 341 Main.pref.init(args.containsKey(Option.RESET_PREFERENCES)); 342 343 if (!languageGiven) { 344 I18n.set(Main.pref.get("language", null)); 345 } 346 Main.pref.updateSystemProperties(); 347 348 final JFrame mainFrame = new JFrame(tr("Java OpenStreetMap Editor")); 349 Main.parent = mainFrame; 350 351 if (args.containsKey(Option.LOAD_PREFERENCES)) { 352 CustomConfigurator.XMLCommandProcessor config = new CustomConfigurator.XMLCommandProcessor(Main.pref); 353 for (String i : args.get(Option.LOAD_PREFERENCES)) { 354 info("Reading preferences from " + i); 355 try (InputStream is = Utils.openURL(new URL(i))) { 356 config.openAndReadXML(is); 357 } catch (Exception ex) { 358 throw new RuntimeException(ex); 359 } 360 } 361 } 362 363 if (args.containsKey(Option.SET)) { 364 for (String i : args.get(Option.SET)) { 365 String[] kv = i.split("=", 2); 366 Main.pref.put(kv[0], "null".equals(kv[1]) ? null : kv[1]); 367 } 368 } 369 370 DefaultAuthenticator.createInstance(); 371 Authenticator.setDefault(DefaultAuthenticator.getInstance()); 372 DefaultProxySelector proxySelector = new DefaultProxySelector(ProxySelector.getDefault()); 373 ProxySelector.setDefault(proxySelector); 374 OAuthAccessTokenHolder.getInstance().init(Main.pref, CredentialsManager.getInstance()); 375 376 // asking for help? show help and exit 377 if (args.containsKey(Option.HELP)) { 378 showHelp(); 379 System.exit(0); 380 } 381 382 final SplashScreen splash = new SplashScreen(); 383 final ProgressMonitor monitor = splash.getProgressMonitor(); 384 monitor.beginTask(tr("Initializing")); 385 splash.setVisible(Main.pref.getBoolean("draw.splashscreen", true)); 386 Main.setInitStatusListener(new InitStatusListener() { 387 388 @Override 389 public void updateStatus(String event) { 390 monitor.indeterminateSubTask(event); 391 } 392 }); 393 394 Collection<PluginInformation> pluginsToLoad = PluginHandler.buildListOfPluginsToLoad(splash,monitor.createSubTaskMonitor(1, false)); 395 if (!pluginsToLoad.isEmpty() && PluginHandler.checkAndConfirmPluginUpdate(splash)) { 396 monitor.subTask(tr("Updating plugins")); 397 pluginsToLoad = PluginHandler.updatePlugins(splash, null, monitor.createSubTaskMonitor(1, false), false); 398 } 399 400 monitor.indeterminateSubTask(tr("Installing updated plugins")); 401 PluginHandler.installDownloadedPlugins(true); 402 403 monitor.indeterminateSubTask(tr("Loading early plugins")); 404 PluginHandler.loadEarlyPlugins(splash,pluginsToLoad, monitor.createSubTaskMonitor(1, false)); 405 406 monitor.indeterminateSubTask(tr("Setting defaults")); 407 preConstructorInit(args); 408 409 monitor.indeterminateSubTask(tr("Creating main GUI")); 410 final Main main = new MainApplication(mainFrame); 411 412 monitor.indeterminateSubTask(tr("Loading plugins")); 413 PluginHandler.loadLatePlugins(splash,pluginsToLoad, monitor.createSubTaskMonitor(1, false)); 414 toolbar.refreshToolbarControl(); 415 416 // Wait for splash disappearance (fix #9714) 417 GuiHelper.runInEDTAndWait(new Runnable() { 418 @Override 419 public void run() { 420 splash.setVisible(false); 421 splash.dispose(); 422 mainFrame.setVisible(true); 423 } 424 }); 425 426 Main.MasterWindowListener.setup(); 427 428 boolean maximized = Main.pref.getBoolean("gui.maximized", false); 429 if ((!args.containsKey(Option.NO_MAXIMIZE) && maximized) || args.containsKey(Option.MAXIMIZE)) { 430 if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH)) { 431 Main.windowState = JFrame.MAXIMIZED_BOTH; 432 mainFrame.setExtendedState(Main.windowState); 433 } else { 434 Main.debug("Main window: maximizing not supported"); 435 } 436 } 437 if (main.menu.fullscreenToggleAction != null) { 438 main.menu.fullscreenToggleAction.initial(); 439 } 440 441 SwingUtilities.invokeLater(new GuiFinalizationWorker(args, proxySelector)); 442 443 if (Main.isPlatformWindows()) { 444 try { 445 // Check for insecure certificates to remove. 446 // This is Windows-dependant code but it can't go to preStartupHook (need i18n) neither startupHook (need to be called before remote control) 447 PlatformHookWindows.removeInsecureCertificates(); 448 } catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | IOException e) { 449 error(e); 450 } 451 } 452 453 if (RemoteControl.PROP_REMOTECONTROL_ENABLED.get()) { 454 RemoteControl.start(); 455 } 456 457 if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) { 458 MessageNotifier.start(); 459 } 460 461 if (Main.pref.getBoolean("debug.edt-checker.enable", Version.getInstance().isLocalBuild())) { 462 // Repaint manager is registered so late for a reason - there is lots of violation during startup process but they don't seem to break anything and are difficult to fix 463 info("Enabled EDT checker, wrongful access to gui from non EDT thread will be printed to console"); 464 RepaintManager.setCurrentManager(new CheckThreadViolationRepaintManager()); 465 } 466 } 467 468 private static class GuiFinalizationWorker implements Runnable { 469 470 private final Map<Option, Collection<String>> args; 471 private final DefaultProxySelector proxySelector; 472 473 public GuiFinalizationWorker(Map<Option, Collection<String>> args, DefaultProxySelector proxySelector) { 474 this.args = args; 475 this.proxySelector = proxySelector; 476 } 477 478 @Override 479 public void run() { 480 481 // Handle proxy/network errors early to inform user he should change settings to be able to use JOSM correctly 482 if (!handleProxyErrors()) { 483 handleNetworkErrors(); 484 } 485 486 // Restore autosave layers after crash and start autosave thread 487 handleAutosave(); 488 489 // Handle command line instructions 490 postConstructorProcessCmdLine(args); 491 492 // Show download dialog if autostart is enabled 493 DownloadDialog.autostartIfNeeded(); 494 } 495 496 private void handleAutosave() { 497 if (AutosaveTask.PROP_AUTOSAVE_ENABLED.get()) { 498 AutosaveTask autosaveTask = new AutosaveTask(); 499 List<File> unsavedLayerFiles = autosaveTask.getUnsavedLayersFiles(); 500 if (!unsavedLayerFiles.isEmpty()) { 501 ExtendedDialog dialog = new ExtendedDialog( 502 Main.parent, 503 tr("Unsaved osm data"), 504 new String[] {tr("Restore"), tr("Cancel"), tr("Discard")} 505 ); 506 dialog.setContent( 507 trn("JOSM found {0} unsaved osm data layer. ", 508 "JOSM found {0} unsaved osm data layers. ", unsavedLayerFiles.size(), unsavedLayerFiles.size()) + 509 tr("It looks like JOSM crashed last time. Would you like to restore the data?")); 510 dialog.setButtonIcons(new String[] {"ok", "cancel", "dialogs/delete"}); 511 int selection = dialog.showDialog().getValue(); 512 if (selection == 1) { 513 autosaveTask.recoverUnsavedLayers(); 514 } else if (selection == 3) { 515 autosaveTask.discardUnsavedLayers(); 516 } 517 } 518 autosaveTask.schedule(); 519 } 520 } 521 522 private boolean handleNetworkOrProxyErrors(boolean hasErrors, String title, String message) { 523 if (hasErrors) { 524 ExtendedDialog ed = new ExtendedDialog( 525 Main.parent, title, 526 new String[]{tr("Change proxy settings"), tr("Cancel")}); 527 ed.setButtonIcons(new String[]{"dialogs/settings.png", "cancel.png"}).setCancelButton(2); 528 ed.setMinimumSize(new Dimension(460, 260)); 529 ed.setIcon(JOptionPane.WARNING_MESSAGE); 530 ed.setContent(message); 531 532 if (ed.showDialog().getValue() == 1) { 533 PreferencesAction.forPreferenceSubTab(null, null, ProxyPreference.class).run(); 534 } 535 } 536 return hasErrors; 537 } 538 539 private boolean handleProxyErrors() { 540 return handleNetworkOrProxyErrors(proxySelector.hasErrors(), tr("Proxy errors occurred"), 541 tr("JOSM tried to access the following resources:<br>" + 542 "{0}" + 543 "but <b>failed</b> to do so, because of the following proxy errors:<br>" + 544 "{1}" + 545 "Would you like to change your proxy settings now?", 546 Utils.joinAsHtmlUnorderedList(proxySelector.getErrorResources()), 547 Utils.joinAsHtmlUnorderedList(proxySelector.getErrorMessages()) 548 )); 549 } 550 551 private boolean handleNetworkErrors() { 552 boolean condition = !NETWORK_ERRORS.isEmpty(); 553 if (condition) { 554 Set<String> errors = new TreeSet<>(); 555 for (Throwable t : NETWORK_ERRORS.values()) { 556 errors.add(t.toString()); 557 } 558 return handleNetworkOrProxyErrors(condition, tr("Network errors occurred"), 559 tr("JOSM tried to access the following resources:<br>" + 560 "{0}" + 561 "but <b>failed</b> to do so, because of the following network errors:<br>" + 562 "{1}" + 563 "It may be due to a missing proxy configuration.<br>" + 564 "Would you like to change your proxy settings now?", 565 Utils.joinAsHtmlUnorderedList(NETWORK_ERRORS.keySet()), 566 Utils.joinAsHtmlUnorderedList(errors) 567 )); 568 } 569 return false; 570 } 571 } 572}