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

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

fix #10242 - catch NoClassDefFoundError when ClassNotFoundException is catched

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