source: josm/trunk/src/org/openstreetmap/josm/Main.java@ 6524

Last change on this file since 6524 was 6523, checked in by Don-vip, 10 years ago

Ask user to change proxy settings when proxy errors occur at startup (useful when a laptop is often used between two locations with different proxy settings)

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