001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm; 003 import static org.openstreetmap.josm.tools.I18n.tr; 004 005 import java.awt.BorderLayout; 006 import java.awt.Component; 007 import java.awt.GridBagConstraints; 008 import java.awt.GridBagLayout; 009 import java.awt.Window; 010 import java.awt.event.ComponentEvent; 011 import java.awt.event.ComponentListener; 012 import java.awt.event.KeyEvent; 013 import java.awt.event.WindowAdapter; 014 import java.awt.event.WindowEvent; 015 import java.io.File; 016 import java.lang.ref.WeakReference; 017 import java.net.URI; 018 import java.net.URISyntaxException; 019 import java.text.MessageFormat; 020 import java.util.ArrayList; 021 import java.util.Arrays; 022 import java.util.Collection; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Map; 026 import java.util.StringTokenizer; 027 import java.util.concurrent.Callable; 028 import java.util.concurrent.ExecutorService; 029 import java.util.concurrent.Executors; 030 import java.util.concurrent.Future; 031 032 import javax.swing.Action; 033 import javax.swing.InputMap; 034 import javax.swing.JComponent; 035 import javax.swing.JFrame; 036 import javax.swing.JLabel; 037 import javax.swing.JOptionPane; 038 import javax.swing.JPanel; 039 import javax.swing.JTextArea; 040 import javax.swing.KeyStroke; 041 import javax.swing.UIManager; 042 043 import org.openstreetmap.gui.jmapviewer.FeatureAdapter; 044 import org.openstreetmap.josm.actions.JosmAction; 045 import org.openstreetmap.josm.actions.OpenFileAction; 046 import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask; 047 import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask; 048 import org.openstreetmap.josm.actions.downloadtasks.DownloadTask; 049 import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler; 050 import org.openstreetmap.josm.actions.mapmode.MapMode; 051 import org.openstreetmap.josm.actions.search.SearchAction; 052 import org.openstreetmap.josm.data.Bounds; 053 import org.openstreetmap.josm.data.Preferences; 054 import org.openstreetmap.josm.data.UndoRedoHandler; 055 import org.openstreetmap.josm.data.coor.CoordinateFormat; 056 import org.openstreetmap.josm.data.coor.LatLon; 057 import org.openstreetmap.josm.data.osm.DataSet; 058 import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy; 059 import org.openstreetmap.josm.data.projection.Projection; 060 import org.openstreetmap.josm.data.projection.ProjectionChangeListener; 061 import org.openstreetmap.josm.data.validation.OsmValidator; 062 import org.openstreetmap.josm.gui.GettingStarted; 063 import org.openstreetmap.josm.gui.MainApplication.Option; 064 import org.openstreetmap.josm.gui.MainMenu; 065 import org.openstreetmap.josm.gui.MapFrame; 066 import org.openstreetmap.josm.gui.MapView; 067 import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 068 import org.openstreetmap.josm.gui.io.SaveLayersDialog; 069 import org.openstreetmap.josm.gui.layer.Layer; 070 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 071 import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener; 072 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; 073 import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference; 074 import org.openstreetmap.josm.gui.preferences.map.MapPaintPreference; 075 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference; 076 import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference; 077 import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor; 078 import org.openstreetmap.josm.gui.progress.ProgressMonitorExecutor; 079 import org.openstreetmap.josm.gui.util.RedirectInputMap; 080 import org.openstreetmap.josm.io.OsmApi; 081 import org.openstreetmap.josm.plugins.PluginHandler; 082 import org.openstreetmap.josm.tools.CheckParameterUtil; 083 import org.openstreetmap.josm.tools.I18n; 084 import org.openstreetmap.josm.tools.ImageProvider; 085 import org.openstreetmap.josm.tools.OpenBrowser; 086 import org.openstreetmap.josm.tools.OsmUrlToBounds; 087 import org.openstreetmap.josm.tools.PlatformHook; 088 import org.openstreetmap.josm.tools.PlatformHookOsx; 089 import org.openstreetmap.josm.tools.PlatformHookUnixoid; 090 import org.openstreetmap.josm.tools.PlatformHookWindows; 091 import org.openstreetmap.josm.tools.Shortcut; 092 import org.openstreetmap.josm.tools.Utils; 093 import org.openstreetmap.josm.tools.WindowGeometry; 094 095 abstract public class Main { 096 097 /** 098 * Replies true if JOSM currently displays a map view. False, if it doesn't, i.e. if 099 * it only shows the MOTD panel. 100 * 101 * @return <code>true</code> if JOSM currently displays a map view 102 */ 103 static public boolean isDisplayingMapView() { 104 if (map == null) return false; 105 if (map.mapView == null) return false; 106 return true; 107 } 108 /** 109 * Global parent component for all dialogs and message boxes 110 */ 111 public static Component parent; 112 /** 113 * Global application. 114 */ 115 public static Main main; 116 /** 117 * The worker thread slave. This is for executing all long and intensive 118 * calculations. The executed runnables are guaranteed to be executed separately 119 * and sequential. 120 */ 121 public final static ExecutorService worker = new ProgressMonitorExecutor(); 122 /** 123 * Global application preferences 124 */ 125 public static Preferences pref; 126 127 /** 128 * The global paste buffer. 129 */ 130 public static final PrimitiveDeepCopy pasteBuffer = new PrimitiveDeepCopy(); 131 public static Layer pasteSource; 132 133 /** 134 * The MapFrame. Use setMapFrame to set or clear it. 135 */ 136 public static MapFrame map; 137 /** 138 * Set to <code>true</code>, when in applet mode 139 */ 140 public static boolean applet = false; 141 142 /** 143 * The toolbar preference control to register new actions. 144 */ 145 public static ToolbarPreferences toolbar; 146 147 public UndoRedoHandler undoRedo = new UndoRedoHandler(); 148 149 public static PleaseWaitProgressMonitor currentProgressMonitor; 150 151 /** 152 * The main menu bar at top of screen. 153 */ 154 public MainMenu menu; 155 156 /** 157 * The data validation handler. 158 */ 159 public OsmValidator validator; 160 /** 161 * The MOTD Layer. 162 */ 163 private GettingStarted gettingStarted = new GettingStarted(); 164 165 /** 166 * Logging level (3 = debug, 2 = info, 1 = warn, 0 = none). 167 */ 168 static public int log_level = 2; 169 /** 170 * Print a warning message if logging is on. 171 * @param msg The message to print. 172 */ 173 static public void warn(String msg) { 174 if (log_level < 1) 175 return; 176 System.out.println(msg); 177 } 178 /** 179 * Print an informational message if logging is on. 180 * @param msg The message to print. 181 */ 182 static public void info(String msg) { 183 if (log_level < 2) 184 return; 185 System.out.println(msg); 186 } 187 /** 188 * Print an debug message if logging is on. 189 * @param msg The message to print. 190 */ 191 static public void debug(String msg) { 192 if (log_level < 3) 193 return; 194 System.out.println(msg); 195 } 196 /** 197 * Print a formated warning message if logging is on. Calls {@link MessageFormat#format} 198 * function to format text. 199 * @param msg The formated message to print. 200 * @param objects The objects to insert into format string. 201 */ 202 static public void warn(String msg, Object... objects) { 203 warn(MessageFormat.format(msg, objects)); 204 } 205 /** 206 * Print a formated informational message if logging is on. Calls {@link MessageFormat#format} 207 * function to format text. 208 * @param msg The formated message to print. 209 * @param objects The objects to insert into format string. 210 */ 211 static public void info(String msg, Object... objects) { 212 info(MessageFormat.format(msg, objects)); 213 } 214 /** 215 * Print a formated debug message if logging is on. Calls {@link MessageFormat#format} 216 * function to format text. 217 * @param msg The formated message to print. 218 * @param objects The objects to insert into format string. 219 */ 220 static public void debug(String msg, Object... objects) { 221 debug(MessageFormat.format(msg, objects)); 222 } 223 224 /** 225 * Platform specific code goes in here. 226 * Plugins may replace it, however, some hooks will be called before any plugins have been loeaded. 227 * So if you need to hook into those early ones, split your class and send the one with the early hooks 228 * to the JOSM team for inclusion. 229 */ 230 public static PlatformHook platform; 231 232 /** 233 * Whether or not the java vm is openjdk 234 * We use this to work around openjdk bugs 235 */ 236 public static boolean isOpenjdk; 237 238 /** 239 * Set or clear (if passed <code>null</code>) the map. 240 */ 241 public final void setMapFrame(final MapFrame map) { 242 MapFrame old = Main.map; 243 panel.setVisible(false); 244 panel.removeAll(); 245 if (map != null) { 246 map.fillPanel(panel); 247 } else { 248 old.destroy(); 249 panel.add(gettingStarted, BorderLayout.CENTER); 250 } 251 panel.setVisible(true); 252 redoUndoListener.commandChanged(0,0); 253 254 Main.map = map; 255 256 PluginHandler.notifyMapFrameChanged(old, map); 257 if (map == null && currentProgressMonitor != null) { 258 currentProgressMonitor.showForegroundDialog(); 259 } 260 } 261 262 /** 263 * Remove the specified layer from the map. If it is the last layer, 264 * remove the map as well. 265 */ 266 public final void removeLayer(final Layer layer) { 267 if (map != null) { 268 map.mapView.removeLayer(layer); 269 if (map != null && map.mapView.getAllLayers().isEmpty()) { 270 setMapFrame(null); 271 } 272 } 273 } 274 275 private static InitStatusListener initListener = null; 276 277 public static interface InitStatusListener { 278 279 void updateStatus(String event); 280 } 281 282 public static void setInitStatusListener(InitStatusListener listener) { 283 initListener = listener; 284 } 285 286 public Main() { 287 main = this; 288 isOpenjdk = System.getProperty("java.vm.name").toUpperCase().indexOf("OPENJDK") != -1; 289 290 if (initListener != null) { 291 initListener.updateStatus(tr("Executing platform startup hook")); 292 } 293 platform.startupHook(); 294 295 if (initListener != null) { 296 initListener.updateStatus(tr("Building main menu")); 297 } 298 contentPanePrivate.add(panel, BorderLayout.CENTER); 299 panel.add(gettingStarted, BorderLayout.CENTER); 300 menu = new MainMenu(); 301 302 undoRedo.addCommandQueueListener(redoUndoListener); 303 304 // creating toolbar 305 contentPanePrivate.add(toolbar.control, BorderLayout.NORTH); 306 307 registerActionShortcut(menu.help, Shortcut.registerShortcut("system:help", tr("Help"), 308 KeyEvent.VK_F1, Shortcut.DIRECT)); 309 310 // contains several initialization tasks to be executed (in parallel) by a ExecutorService 311 List<Callable<Void>> tasks = new ArrayList<Callable<Void>>(); 312 313 tasks.add(new Callable<Void>() { 314 315 @Override 316 public Void call() throws Exception { 317 // We try to establish an API connection early, so that any API 318 // capabilities are already known to the editor instance. However 319 // if it goes wrong that's not critical at this stage. 320 if (initListener != null) { 321 initListener.updateStatus(tr("Initializing OSM API")); 322 } 323 try { 324 OsmApi.getOsmApi().initialize(null, true); 325 } catch (Exception x) { 326 // ignore any exception here. 327 } 328 return null; 329 } 330 }); 331 332 tasks.add(new Callable<Void>() { 333 334 @Override 335 public Void call() throws Exception { 336 if (initListener != null) { 337 initListener.updateStatus(tr("Initializing presets")); 338 } 339 TaggingPresetPreference.initialize(); 340 // some validator tests require the presets to be initialized 341 // TODO remove this dependency for parallel initialization 342 if (initListener != null) { 343 initListener.updateStatus(tr("Initializing validator")); 344 } 345 validator = new OsmValidator(); 346 MapView.addLayerChangeListener(validator); 347 return null; 348 } 349 }); 350 351 tasks.add(new Callable<Void>() { 352 353 @Override 354 public Void call() throws Exception { 355 if (initListener != null) { 356 initListener.updateStatus(tr("Initializing map styles")); 357 } 358 MapPaintPreference.initialize(); 359 return null; 360 } 361 }); 362 363 tasks.add(new Callable<Void>() { 364 365 @Override 366 public Void call() throws Exception { 367 if (initListener != null) { 368 initListener.updateStatus(tr("Loading imagery preferences")); 369 } 370 ImageryPreference.initialize(); 371 return null; 372 } 373 }); 374 375 try { 376 for (Future<Void> i : Executors.newFixedThreadPool( 377 Runtime.getRuntime().availableProcessors()).invokeAll(tasks)) { 378 i.get(); 379 } 380 } catch (Exception ex) { 381 throw new RuntimeException(ex); 382 } 383 384 // hooks for the jmapviewer component 385 FeatureAdapter.registerBrowserAdapter(new FeatureAdapter.BrowserAdapter() { 386 @Override 387 public void openLink(String url) { 388 OpenBrowser.displayUrl(url); 389 } 390 }); 391 FeatureAdapter.registerTranslationAdapter(I18n.getTranslationAdapter()); 392 393 if (initListener != null) { 394 initListener.updateStatus(tr("Updating user interface")); 395 } 396 397 toolbar.refreshToolbarControl(); 398 399 toolbar.control.updateUI(); 400 contentPanePrivate.updateUI(); 401 402 } 403 404 /** 405 * Add a new layer to the map. If no map exists, create one. 406 */ 407 public final synchronized void addLayer(final Layer layer) { 408 if (map == null) { 409 final MapFrame mapFrame = new MapFrame(contentPanePrivate); 410 setMapFrame(mapFrame); 411 mapFrame.selectMapMode((MapMode)mapFrame.getDefaultButtonAction(), layer); 412 mapFrame.setVisible(true); 413 mapFrame.initializeDialogsPane(); 414 // bootstrapping problem: make sure the layer list dialog is going to 415 // listen to change events of the very first layer 416 // 417 layer.addPropertyChangeListener(LayerListDialog.getInstance().getModel()); 418 } 419 layer.hookUpMapView(); 420 map.mapView.addLayer(layer); 421 } 422 423 /** 424 * Replies <code>true</code> if there is an edit layer 425 * 426 * @return <code>true</code> if there is an edit layer 427 */ 428 public boolean hasEditLayer() { 429 if (getEditLayer() == null) return false; 430 return true; 431 } 432 433 /** 434 * Replies the current edit layer 435 * 436 * @return the current edit layer. <code>null</code>, if no current edit layer exists 437 */ 438 public OsmDataLayer getEditLayer() { 439 if (map == null) return null; 440 if (map.mapView == null) return null; 441 return map.mapView.getEditLayer(); 442 } 443 444 /** 445 * Replies the current data set. 446 * 447 * @return the current data set. <code>null</code>, if no current data set exists 448 */ 449 public DataSet getCurrentDataSet() { 450 if (!hasEditLayer()) return null; 451 return getEditLayer().data; 452 } 453 454 /** 455 * Returns the currently active layer 456 * 457 * @return the currently active layer. <code>null</code>, if currently no active layer exists 458 */ 459 public Layer getActiveLayer() { 460 if (map == null) return null; 461 if (map.mapView == null) return null; 462 return map.mapView.getActiveLayer(); 463 } 464 465 protected static final JPanel contentPanePrivate = new JPanel(new BorderLayout()); 466 467 public static void redirectToMainContentPane(JComponent source) { 468 RedirectInputMap.redirect(source, contentPanePrivate); 469 } 470 471 public static void registerActionShortcut(JosmAction action) { 472 registerActionShortcut(action, action.getShortcut()); 473 } 474 475 public static void registerActionShortcut(Action action, Shortcut shortcut) { 476 KeyStroke keyStroke = shortcut.getKeyStroke(); 477 if (keyStroke == null) 478 return; 479 480 InputMap inputMap = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 481 Object existing = inputMap.get(keyStroke); 482 if (existing != null && !existing.equals(action)) { 483 System.out.println(String.format("Keystroke %s is already assigned to %s, will be overridden by %s", keyStroke, existing, action)); 484 } 485 inputMap.put(keyStroke, action); 486 487 contentPanePrivate.getActionMap().put(action, action); 488 } 489 490 public static void unregisterShortcut(Shortcut shortcut) { 491 contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(shortcut.getKeyStroke()); 492 } 493 494 public static void unregisterActionShortcut(JosmAction action) { 495 unregisterActionShortcut(action, action.getShortcut()); 496 } 497 498 public static void unregisterActionShortcut(Action action, Shortcut shortcut) { 499 unregisterShortcut(shortcut); 500 contentPanePrivate.getActionMap().remove(action); 501 } 502 503 /////////////////////////////////////////////////////////////////////////// 504 // Implementation part 505 /////////////////////////////////////////////////////////////////////////// 506 507 public static final JPanel panel = new JPanel(new BorderLayout()); 508 509 protected static WindowGeometry geometry; 510 protected static int windowState = JFrame.NORMAL; 511 512 private final CommandQueueListener redoUndoListener = new CommandQueueListener(){ 513 public void commandChanged(final int queueSize, final int redoSize) { 514 menu.undo.setEnabled(queueSize > 0); 515 menu.redo.setEnabled(redoSize > 0); 516 } 517 }; 518 519 /** 520 * Should be called before the main constructor to setup some parameter stuff 521 * @param args The parsed argument list. 522 */ 523 public static void preConstructorInit(Map<Option, Collection<String>> args) { 524 ProjectionPreference.setProjection(); 525 526 try { 527 String defaultlaf = platform.getDefaultStyle(); 528 String laf = Main.pref.get("laf", defaultlaf); 529 try { 530 UIManager.setLookAndFeel(laf); 531 } 532 catch (final java.lang.ClassNotFoundException e) { 533 System.out.println("Look and Feel not found: " + laf); 534 Main.pref.put("laf", defaultlaf); 535 } 536 catch (final javax.swing.UnsupportedLookAndFeelException e) { 537 System.out.println("Look and Feel not supported: " + laf); 538 Main.pref.put("laf", defaultlaf); 539 } 540 toolbar = new ToolbarPreferences(); 541 contentPanePrivate.updateUI(); 542 panel.updateUI(); 543 } catch (final Exception e) { 544 e.printStackTrace(); 545 } 546 UIManager.put("OptionPane.okIcon", ImageProvider.get("ok")); 547 UIManager.put("OptionPane.yesIcon", UIManager.get("OptionPane.okIcon")); 548 UIManager.put("OptionPane.cancelIcon", ImageProvider.get("cancel")); 549 UIManager.put("OptionPane.noIcon", UIManager.get("OptionPane.cancelIcon")); 550 551 I18n.translateJavaInternalMessages(); 552 553 // init default coordinate format 554 // 555 try { 556 //CoordinateFormat format = CoordinateFormat.valueOf(Main.pref.get("coordinates")); 557 CoordinateFormat.setCoordinateFormat(CoordinateFormat.valueOf(Main.pref.get("coordinates"))); 558 } catch (IllegalArgumentException iae) { 559 CoordinateFormat.setCoordinateFormat(CoordinateFormat.DECIMAL_DEGREES); 560 } 561 562 geometry = WindowGeometry.mainWindow("gui.geometry", 563 (args.containsKey(Option.GEOMETRY) ? args.get(Option.GEOMETRY).iterator().next() : null), 564 !args.containsKey(Option.NO_MAXIMIZE) && Main.pref.getBoolean("gui.maximized", false)); 565 } 566 567 public void postConstructorProcessCmdLine(Map<Option, Collection<String>> args) { 568 if (args.containsKey(Option.DOWNLOAD)) { 569 List<File> fileList = new ArrayList<File>(); 570 for (String s : args.get(Option.DOWNLOAD)) { 571 File f = null; 572 switch(paramType(s)) { 573 case httpUrl: 574 downloadFromParamHttp(false, s); 575 break; 576 case bounds: 577 downloadFromParamBounds(false, s); 578 break; 579 case fileUrl: 580 try { 581 f = new File(new URI(s)); 582 } catch (URISyntaxException e) { 583 JOptionPane.showMessageDialog( 584 Main.parent, 585 tr("Ignoring malformed file URL: \"{0}\"", s), 586 tr("Warning"), 587 JOptionPane.WARNING_MESSAGE 588 ); 589 } 590 if (f!=null) { 591 fileList.add(f); 592 } 593 break; 594 case fileName: 595 f = new File(s); 596 fileList.add(f); 597 break; 598 } 599 } 600 if(!fileList.isEmpty()) 601 { 602 OpenFileAction.openFiles(fileList, true); 603 } 604 } 605 if (args.containsKey(Option.DOWNLOADGPS)) { 606 for (String s : args.get(Option.DOWNLOADGPS)) { 607 switch(paramType(s)) { 608 case httpUrl: 609 downloadFromParamHttp(true, s); 610 break; 611 case bounds: 612 downloadFromParamBounds(true, s); 613 break; 614 case fileUrl: 615 case fileName: 616 JOptionPane.showMessageDialog( 617 Main.parent, 618 tr("Parameter \"downloadgps\" does not accept file names or file URLs"), 619 tr("Warning"), 620 JOptionPane.WARNING_MESSAGE 621 ); 622 } 623 } 624 } 625 if (args.containsKey(Option.SELECTION)) { 626 for (String s : args.get(Option.SELECTION)) { 627 SearchAction.search(s, SearchAction.SearchMode.add); 628 } 629 } 630 } 631 632 /** 633 * Asks user to perform "save layer" operations (save .osm on disk and/or upload osm data to server) for all {@link OsmDataLayer} before JOSM exits. 634 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations. {@code false} if the user cancels. 635 * @since 2025 636 */ 637 public static boolean saveUnsavedModifications() { 638 if (map == null || map.mapView == null) return true; 639 return saveUnsavedModifications(map.mapView.getLayersOfType(OsmDataLayer.class), true); 640 } 641 642 /** 643 * Asks user to perform "save layer" operations (save .osm on disk and/or upload osm data to server) before osm layers deletion. 644 * 645 * @param selectedLayers The layers to check. Only instances of {@link OsmDataLayer} are considered. 646 * @param exit {@code true} if JOSM is exiting, {@code false} otherwise. 647 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations. {@code false} if the user cancels. 648 * @since 5519 649 */ 650 public static boolean saveUnsavedModifications(List<? extends Layer> selectedLayers, boolean exit) { 651 SaveLayersDialog dialog = new SaveLayersDialog(parent); 652 List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>(); 653 for (Layer l: selectedLayers) { 654 if (!(l instanceof OsmDataLayer)) { 655 continue; 656 } 657 OsmDataLayer odl = (OsmDataLayer)l; 658 if ((odl.requiresSaveToFile() || (odl.requiresUploadToServer() && !odl.isUploadDiscouraged())) && odl.data.isModified()) { 659 layersWithUnmodifiedChanges.add(odl); 660 } 661 } 662 if (exit) { 663 dialog.prepareForSavingAndUpdatingLayersBeforeExit(); 664 } else { 665 dialog.prepareForSavingAndUpdatingLayersBeforeDelete(); 666 } 667 if (!layersWithUnmodifiedChanges.isEmpty()) { 668 dialog.getModel().populate(layersWithUnmodifiedChanges); 669 dialog.setVisible(true); 670 switch(dialog.getUserAction()) { 671 case CANCEL: return false; 672 case PROCEED: return true; 673 default: return false; 674 } 675 } 676 677 return true; 678 } 679 680 public static boolean exitJosm(boolean exit) { 681 if (Main.saveUnsavedModifications()) { 682 geometry.remember("gui.geometry"); 683 if (map != null) { 684 map.rememberToggleDialogWidth(); 685 } 686 pref.put("gui.maximized", (windowState & JFrame.MAXIMIZED_BOTH) != 0); 687 // Remove all layers because somebody may rely on layerRemoved events (like AutosaveTask) 688 if (Main.isDisplayingMapView()) { 689 Collection<Layer> layers = new ArrayList<Layer>(Main.map.mapView.getAllLayers()); 690 for (Layer l: layers) { 691 Main.map.mapView.removeLayer(l); 692 } 693 } 694 if (exit) { 695 System.exit(0); 696 return true; 697 } else 698 return true; 699 } else 700 return false; 701 } 702 703 /** 704 * The type of a command line parameter, to be used in switch statements. 705 * @see #paramType 706 */ 707 private enum DownloadParamType { httpUrl, fileUrl, bounds, fileName } 708 709 /** 710 * Guess the type of a parameter string specified on the command line with --download= or --downloadgps. 711 * @param s A parameter string 712 * @return The guessed parameter type 713 */ 714 private DownloadParamType paramType(String s) { 715 if(s.startsWith("http:")) return DownloadParamType.httpUrl; 716 if(s.startsWith("file:")) return DownloadParamType.fileUrl; 717 String coorPattern = "\\s*[+-]?[0-9]+(\\.[0-9]+)?\\s*"; 718 if(s.matches(coorPattern+"(,"+coorPattern+"){3}")) return DownloadParamType.bounds; 719 // everything else must be a file name 720 return DownloadParamType.fileName; 721 } 722 723 /** 724 * Download area specified on the command line as OSM URL. 725 * @param rawGps Flag to download raw GPS tracks 726 * @param s The URL parameter 727 */ 728 private static void downloadFromParamHttp(final boolean rawGps, String s) { 729 final Bounds b = OsmUrlToBounds.parse(s); 730 if (b == null) { 731 JOptionPane.showMessageDialog( 732 Main.parent, 733 tr("Ignoring malformed URL: \"{0}\"", s), 734 tr("Warning"), 735 JOptionPane.WARNING_MESSAGE 736 ); 737 } else { 738 downloadFromParamBounds(rawGps, b); 739 } 740 } 741 742 /** 743 * Download area specified on the command line as bounds string. 744 * @param rawGps Flag to download raw GPS tracks 745 * @param s The bounds parameter 746 */ 747 private static void downloadFromParamBounds(final boolean rawGps, String s) { 748 final StringTokenizer st = new StringTokenizer(s, ","); 749 if (st.countTokens() == 4) { 750 Bounds b = new Bounds( 751 new LatLon(Double.parseDouble(st.nextToken()),Double.parseDouble(st.nextToken())), 752 new LatLon(Double.parseDouble(st.nextToken()),Double.parseDouble(st.nextToken())) 753 ); 754 downloadFromParamBounds(rawGps, b); 755 } 756 } 757 758 /** 759 * Download area specified as Bounds value. 760 * @param rawGps Flag to download raw GPS tracks 761 * @param b The bounds value 762 * @see #downloadFromParamBounds(boolean, String) 763 * @see #downloadFromParamHttp 764 */ 765 private static void downloadFromParamBounds(final boolean rawGps, Bounds b) { 766 DownloadTask task = rawGps ? new DownloadGpsTask() : new DownloadOsmTask(); 767 // asynchronously launch the download task ... 768 Future<?> future = task.download(true, b, null); 769 // ... and the continuation when the download is finished (this will wait for the download to finish) 770 Main.worker.execute(new PostDownloadHandler(task, future)); 771 } 772 773 public static void determinePlatformHook() { 774 String os = System.getProperty("os.name"); 775 if (os == null) { 776 System.err.println("Your operating system has no name, so I'm guessing its some kind of *nix."); 777 platform = new PlatformHookUnixoid(); 778 } else if (os.toLowerCase().startsWith("windows")) { 779 platform = new PlatformHookWindows(); 780 } else if (os.equals("Linux") || os.equals("Solaris") || 781 os.equals("SunOS") || os.equals("AIX") || 782 os.equals("FreeBSD") || os.equals("NetBSD") || os.equals("OpenBSD")) { 783 platform = new PlatformHookUnixoid(); 784 } else if (os.toLowerCase().startsWith("mac os x")) { 785 platform = new PlatformHookOsx(); 786 } else { 787 System.err.println("I don't know your operating system '"+os+"', so I'm guessing its some kind of *nix."); 788 platform = new PlatformHookUnixoid(); 789 } 790 } 791 792 private static class WindowPositionSizeListener extends WindowAdapter implements 793 ComponentListener { 794 @Override 795 public void windowStateChanged(WindowEvent e) { 796 Main.windowState = e.getNewState(); 797 } 798 799 @Override 800 public void componentHidden(ComponentEvent e) { 801 } 802 803 @Override 804 public void componentMoved(ComponentEvent e) { 805 handleComponentEvent(e); 806 } 807 808 @Override 809 public void componentResized(ComponentEvent e) { 810 handleComponentEvent(e); 811 } 812 813 @Override 814 public void componentShown(ComponentEvent e) { 815 } 816 817 private void handleComponentEvent(ComponentEvent e) { 818 Component c = e.getComponent(); 819 if (c instanceof JFrame && c.isVisible()) { 820 if(Main.windowState == JFrame.NORMAL) { 821 Main.geometry = new WindowGeometry((JFrame) c); 822 } else { 823 Main.geometry.fixScreen((JFrame) c); 824 } 825 } 826 } 827 } 828 public static void addListener() { 829 parent.addComponentListener(new WindowPositionSizeListener()); 830 ((JFrame)parent).addWindowStateListener(new WindowPositionSizeListener()); 831 } 832 833 public static void checkJava6() { 834 String version = System.getProperty("java.version"); 835 if (version != null) { 836 if (version.startsWith("1.6") || version.startsWith("6") || 837 version.startsWith("1.7") || version.startsWith("7")) 838 return; 839 if (version.startsWith("1.5") || version.startsWith("5")) { 840 JLabel ho = new JLabel("<html>"+ 841 tr("<h2>JOSM requires Java version 6.</h2>"+ 842 "Detected Java version: {0}.<br>"+ 843 "You can <ul><li>update your Java (JRE) or</li>"+ 844 "<li>use an earlier (Java 5 compatible) version of JOSM.</li></ul>"+ 845 "More Info:", version)+"</html>"); 846 JTextArea link = new JTextArea("http://josm.openstreetmap.de/wiki/Help/SystemRequirements"); 847 link.setEditable(false); 848 link.setBackground(panel.getBackground()); 849 JPanel panel = new JPanel(new GridBagLayout()); 850 GridBagConstraints gbc = new GridBagConstraints(); 851 gbc.gridwidth = GridBagConstraints.REMAINDER; 852 gbc.anchor = GridBagConstraints.WEST; 853 gbc.weightx = 1.0; 854 panel.add(ho, gbc); 855 panel.add(link, gbc); 856 final String EXIT = tr("Exit JOSM"); 857 final String CONTINUE = tr("Continue, try anyway"); 858 int ret = JOptionPane.showOptionDialog(null, panel, tr("Error"), JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, new String[] {EXIT, CONTINUE}, EXIT); 859 if (ret == 0) { 860 System.exit(0); 861 } 862 return; 863 } 864 } 865 System.err.println("Error: Could not recognize Java Version: "+version); 866 } 867 868 /* ----------------------------------------------------------------------------------------- */ 869 /* projection handling - Main is a registry for a single, global projection instance */ 870 /* */ 871 /* TODO: For historical reasons the registry is implemented by Main. An alternative approach */ 872 /* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class. */ 873 /* ----------------------------------------------------------------------------------------- */ 874 /** 875 * The projection method used. 876 * use {@link #getProjection()} and {@link #setProjection(Projection)} for access. 877 * Use {@link #setProjection(Projection)} in order to trigger a projection change event. 878 */ 879 private static Projection proj; 880 881 /** 882 * Replies the current projection. 883 * 884 * @return the currently active projection 885 */ 886 public static Projection getProjection() { 887 return proj; 888 } 889 890 /** 891 * Sets the current projection 892 * 893 * @param p the projection 894 */ 895 public static void setProjection(Projection p) { 896 CheckParameterUtil.ensureParameterNotNull(p); 897 Projection oldValue = proj; 898 Bounds b = isDisplayingMapView() ? map.mapView.getRealBounds() : null; 899 proj = p; 900 fireProjectionChanged(oldValue, proj, b); 901 } 902 903 /* 904 * Keep WeakReferences to the listeners. This relieves clients from the burden of 905 * explicitly removing the listeners and allows us to transparently register every 906 * created dataset as projection change listener. 907 */ 908 private static final ArrayList<WeakReference<ProjectionChangeListener>> listeners = new ArrayList<WeakReference<ProjectionChangeListener>>(); 909 910 private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) { 911 if (newValue == null ^ oldValue == null 912 || (newValue != null && oldValue != null && !Utils.equal(newValue.toCode(), oldValue.toCode()))) { 913 914 synchronized(Main.class) { 915 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator(); 916 while (it.hasNext()){ 917 WeakReference<ProjectionChangeListener> wr = it.next(); 918 ProjectionChangeListener listener = wr.get(); 919 if (listener == null) { 920 it.remove(); 921 continue; 922 } 923 listener.projectionChanged(oldValue, newValue); 924 } 925 } 926 if (newValue != null && oldBounds != null) { 927 Main.map.mapView.zoomTo(oldBounds); 928 } 929 /* TODO - remove layers with fixed projection */ 930 } 931 } 932 933 /** 934 * Register a projection change listener. 935 * 936 * @param listener the listener. Ignored if <code>null</code>. 937 */ 938 public static void addProjectionChangeListener(ProjectionChangeListener listener) { 939 if (listener == null) return; 940 synchronized (Main.class) { 941 for (WeakReference<ProjectionChangeListener> wr : listeners) { 942 // already registered ? => abort 943 if (wr.get() == listener) return; 944 } 945 listeners.add(new WeakReference<ProjectionChangeListener>(listener)); 946 } 947 } 948 949 /** 950 * Removes a projection change listener. 951 * 952 * @param listener the listener. Ignored if <code>null</code>. 953 */ 954 public static void removeProjectionChangeListener(ProjectionChangeListener listener) { 955 if (listener == null) return; 956 synchronized(Main.class){ 957 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator(); 958 while (it.hasNext()){ 959 WeakReference<ProjectionChangeListener> wr = it.next(); 960 // remove the listener - and any other listener which got garbage 961 // collected in the meantime 962 if (wr.get() == null || wr.get() == listener) { 963 it.remove(); 964 } 965 } 966 } 967 } 968 969 /** 970 * Listener for window switch events. 971 * 972 * These are events, when the user activates a window of another application 973 * or comes back to JOSM. Window switches from one JOSM window to another 974 * are not reported. 975 */ 976 public static interface WindowSwitchListener { 977 /** 978 * Called when the user activates a window of another application. 979 */ 980 void toOtherApplication(); 981 /** 982 * Called when the user comes from a window of another application 983 * back to JOSM. 984 */ 985 void fromOtherApplication(); 986 } 987 988 private static final ArrayList<WeakReference<WindowSwitchListener>> windowSwitchListeners = new ArrayList<WeakReference<WindowSwitchListener>>(); 989 990 /** 991 * Register a window switch listener. 992 * 993 * @param listener the listener. Ignored if <code>null</code>. 994 */ 995 public static void addWindowSwitchListener(WindowSwitchListener listener) { 996 if (listener == null) return; 997 synchronized (Main.class) { 998 for (WeakReference<WindowSwitchListener> wr : windowSwitchListeners) { 999 // already registered ? => abort 1000 if (wr.get() == listener) return; 1001 } 1002 boolean wasEmpty = windowSwitchListeners.isEmpty(); 1003 windowSwitchListeners.add(new WeakReference<WindowSwitchListener>(listener)); 1004 if (wasEmpty) { 1005 // The following call will have no effect, when there is no window 1006 // at the time. Therefore, MasterWindowListener.setup() will also be 1007 // called, as soon as the main window is shown. 1008 MasterWindowListener.setup(); 1009 } 1010 } 1011 } 1012 1013 /** 1014 * Removes a window switch listener. 1015 * 1016 * @param listener the listener. Ignored if <code>null</code>. 1017 */ 1018 public static void removeWindowSwitchListener(WindowSwitchListener listener) { 1019 if (listener == null) return; 1020 synchronized (Main.class){ 1021 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator(); 1022 while (it.hasNext()){ 1023 WeakReference<WindowSwitchListener> wr = it.next(); 1024 // remove the listener - and any other listener which got garbage 1025 // collected in the meantime 1026 if (wr.get() == null || wr.get() == listener) { 1027 it.remove(); 1028 } 1029 } 1030 if (windowSwitchListeners.isEmpty()) { 1031 MasterWindowListener.teardown(); 1032 } 1033 } 1034 } 1035 1036 /** 1037 * WindowListener, that is registered on all Windows of the application. 1038 * 1039 * Its purpose is to notify WindowSwitchListeners, that the user switches to 1040 * another application, e.g. a browser, or back to JOSM. 1041 * 1042 * When changing from JOSM to another application and back (e.g. two times 1043 * alt+tab), the active Window within JOSM may be different. 1044 * Therefore, we need to register listeners to <strong>all</strong> (visible) 1045 * Windows in JOSM, and it does not suffice to monitor the one that was 1046 * deactivated last. 1047 * 1048 * This class is only "active" on demand, i.e. when there is at least one 1049 * WindowSwitchListener registered. 1050 */ 1051 protected static class MasterWindowListener extends WindowAdapter { 1052 1053 private static MasterWindowListener INSTANCE; 1054 1055 public static MasterWindowListener getInstance() { 1056 if (INSTANCE == null) { 1057 INSTANCE = new MasterWindowListener(); 1058 } 1059 return INSTANCE; 1060 } 1061 1062 /** 1063 * Register listeners to all non-hidden windows. 1064 * 1065 * Windows that are created later, will be cared for in {@link #windowDeactivated(WindowEvent)}. 1066 */ 1067 public static void setup() { 1068 if (!windowSwitchListeners.isEmpty()) { 1069 for (Window w : Window.getWindows()) { 1070 if (w.isShowing()) { 1071 if (!Arrays.asList(w.getWindowListeners()).contains(getInstance())) { 1072 w.addWindowListener(getInstance()); 1073 } 1074 } 1075 } 1076 } 1077 } 1078 1079 /** 1080 * Unregister all listeners. 1081 */ 1082 public static void teardown() { 1083 for (Window w : Window.getWindows()) { 1084 w.removeWindowListener(getInstance()); 1085 } 1086 } 1087 1088 @Override 1089 public void windowActivated(WindowEvent e) { 1090 if (e.getOppositeWindow() == null) { // we come from a window of a different application 1091 // fire WindowSwitchListeners 1092 synchronized (Main.class) { 1093 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator(); 1094 while (it.hasNext()){ 1095 WeakReference<WindowSwitchListener> wr = it.next(); 1096 WindowSwitchListener listener = wr.get(); 1097 if (listener == null) { 1098 it.remove(); 1099 continue; 1100 } 1101 listener.fromOtherApplication(); 1102 } 1103 } 1104 } 1105 } 1106 1107 @Override 1108 public void windowDeactivated(WindowEvent e) { 1109 // set up windows that have been created in the meantime 1110 for (Window w : Window.getWindows()) { 1111 if (!w.isShowing()) { 1112 w.removeWindowListener(getInstance()); 1113 } else { 1114 if (!Arrays.asList(w.getWindowListeners()).contains(getInstance())) { 1115 w.addWindowListener(getInstance()); 1116 } 1117 } 1118 } 1119 if (e.getOppositeWindow() == null) { // we go to a window of a different application 1120 // fire WindowSwitchListeners 1121 synchronized (Main.class) { 1122 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator(); 1123 while (it.hasNext()){ 1124 WeakReference<WindowSwitchListener> wr = it.next(); 1125 WindowSwitchListener listener = wr.get(); 1126 if (listener == null) { 1127 it.remove(); 1128 continue; 1129 } 1130 listener.toOtherApplication(); 1131 } 1132 } 1133 } 1134 } 1135 } 1136 1137 }