PageRenderTime 29ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/nextreactionmodel/src/main/java/org/openstreetmap/josm/Main.java

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