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

Last change on this file since 10421 was 10421, checked in by Klumbumbus, 8 years ago

fix @since 10420

  • Property svn:eol-style set to native
File size: 67.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.KeyEvent;
12import java.awt.event.WindowAdapter;
13import java.awt.event.WindowEvent;
14import java.io.File;
15import java.io.IOException;
16import java.lang.ref.WeakReference;
17import java.net.URI;
18import java.net.URISyntaxException;
19import java.net.URL;
20import java.text.MessageFormat;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.Collections;
25import java.util.EnumSet;
26import java.util.HashMap;
27import java.util.Iterator;
28import java.util.List;
29import java.util.Locale;
30import java.util.Map;
31import java.util.Objects;
32import java.util.Set;
33import java.util.StringTokenizer;
34import java.util.concurrent.Callable;
35import java.util.concurrent.ExecutionException;
36import java.util.concurrent.ExecutorService;
37import java.util.concurrent.Executors;
38import java.util.concurrent.Future;
39import java.util.logging.Handler;
40import java.util.logging.Level;
41import java.util.logging.LogRecord;
42import java.util.logging.Logger;
43
44import javax.swing.Action;
45import javax.swing.InputMap;
46import javax.swing.JComponent;
47import javax.swing.JOptionPane;
48import javax.swing.JPanel;
49import javax.swing.JTextArea;
50import javax.swing.KeyStroke;
51import javax.swing.LookAndFeel;
52import javax.swing.UIManager;
53import javax.swing.UnsupportedLookAndFeelException;
54
55import org.openstreetmap.gui.jmapviewer.FeatureAdapter;
56import org.openstreetmap.josm.actions.JosmAction;
57import org.openstreetmap.josm.actions.OpenFileAction;
58import org.openstreetmap.josm.actions.OpenLocationAction;
59import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask;
60import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
61import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
62import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
63import org.openstreetmap.josm.actions.mapmode.DrawAction;
64import org.openstreetmap.josm.actions.mapmode.MapMode;
65import org.openstreetmap.josm.actions.search.SearchAction;
66import org.openstreetmap.josm.data.Bounds;
67import org.openstreetmap.josm.data.Preferences;
68import org.openstreetmap.josm.data.ProjectionBounds;
69import org.openstreetmap.josm.data.UndoRedoHandler;
70import org.openstreetmap.josm.data.ViewportData;
71import org.openstreetmap.josm.data.cache.JCSCacheManager;
72import org.openstreetmap.josm.data.coor.CoordinateFormat;
73import org.openstreetmap.josm.data.coor.LatLon;
74import org.openstreetmap.josm.data.osm.DataSet;
75import org.openstreetmap.josm.data.osm.OsmPrimitive;
76import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
77import org.openstreetmap.josm.data.projection.Projection;
78import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
79import org.openstreetmap.josm.data.validation.OsmValidator;
80import org.openstreetmap.josm.gui.GettingStarted;
81import org.openstreetmap.josm.gui.MainApplication.Option;
82import org.openstreetmap.josm.gui.MainMenu;
83import org.openstreetmap.josm.gui.MapFrame;
84import org.openstreetmap.josm.gui.MapFrameListener;
85import org.openstreetmap.josm.gui.MapView;
86import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
87import org.openstreetmap.josm.gui.help.HelpUtil;
88import org.openstreetmap.josm.gui.io.SaveLayersDialog;
89import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
90import org.openstreetmap.josm.gui.layer.Layer;
91import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
92import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
93import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
94import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
95import org.openstreetmap.josm.gui.layer.MainLayerManager;
96import org.openstreetmap.josm.gui.layer.OsmDataLayer;
97import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener;
98import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
99import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;
100import org.openstreetmap.josm.gui.preferences.map.MapPaintPreference;
101import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
102import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
103import org.openstreetmap.josm.gui.progress.ProgressMonitorExecutor;
104import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
105import org.openstreetmap.josm.gui.util.GuiHelper;
106import org.openstreetmap.josm.gui.util.RedirectInputMap;
107import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
108import org.openstreetmap.josm.io.FileWatcher;
109import org.openstreetmap.josm.io.OnlineResource;
110import org.openstreetmap.josm.io.OsmApi;
111import org.openstreetmap.josm.io.OsmApiInitializationException;
112import org.openstreetmap.josm.io.OsmTransferCanceledException;
113import org.openstreetmap.josm.plugins.PluginHandler;
114import org.openstreetmap.josm.tools.CheckParameterUtil;
115import org.openstreetmap.josm.tools.I18n;
116import org.openstreetmap.josm.tools.ImageProvider;
117import org.openstreetmap.josm.tools.OpenBrowser;
118import org.openstreetmap.josm.tools.OsmUrlToBounds;
119import org.openstreetmap.josm.tools.PlatformHook;
120import org.openstreetmap.josm.tools.PlatformHookOsx;
121import org.openstreetmap.josm.tools.PlatformHookUnixoid;
122import org.openstreetmap.josm.tools.PlatformHookWindows;
123import org.openstreetmap.josm.tools.Shortcut;
124import org.openstreetmap.josm.tools.Utils;
125
126/**
127 * Abstract class holding various static global variables and methods used in large parts of JOSM application.
128 * @since 98
129 */
130public abstract class Main {
131
132 /**
133 * The JOSM website URL.
134 * @since 6897 (was public from 6143 to 6896)
135 */
136 private static final String JOSM_WEBSITE = "https://josm.openstreetmap.de";
137
138 /**
139 * The OSM website URL.
140 * @since 6897 (was public from 6453 to 6896)
141 */
142 private static final String OSM_WEBSITE = "https://www.openstreetmap.org";
143
144 /**
145 * Replies true if JOSM currently displays a map view. False, if it doesn't, i.e. if
146 * it only shows the MOTD panel.
147 * <p>
148 * You do not need this when accessing the layer manager. The layer manager will be empty if no map view is shown.
149 *
150 * @return <code>true</code> if JOSM currently displays a map view
151 */
152 public static boolean isDisplayingMapView() {
153 return map != null && map.mapView != null;
154 }
155
156 /**
157 * Global parent component for all dialogs and message boxes
158 */
159 public static Component parent;
160
161 /**
162 * Global application.
163 */
164 public static volatile Main main;
165
166 /**
167 * Command-line arguments used to run the application.
168 */
169 protected static final List<String> COMMAND_LINE_ARGS = new ArrayList<>();
170
171 /**
172 * The worker thread slave. This is for executing all long and intensive
173 * calculations. The executed runnables are guaranteed to be executed separately
174 * and sequential.
175 */
176 public static final ExecutorService worker = new ProgressMonitorExecutor("main-worker-%d", Thread.NORM_PRIORITY);
177
178 /**
179 * Global application preferences
180 */
181 public static Preferences pref;
182
183 /**
184 * The global paste buffer.
185 */
186 public static final PrimitiveDeepCopy pasteBuffer = new PrimitiveDeepCopy();
187
188 /**
189 * The layer source from which {@link Main#pasteBuffer} data comes from.
190 */
191 public static Layer pasteSource;
192
193 /**
194 * The MapFrame. Use {@link Main#setMapFrame} to set or clear it.
195 * <p>
196 * There should be no need to access this to access any map data. Use {@link #layerManager} instead.
197 */
198 public static MapFrame map;
199
200 /**
201 * Provides access to the layers displayed in the main view.
202 * @since 10271
203 */
204 private static final MainLayerManager layerManager = new MainLayerManager();
205
206 /**
207 * The toolbar preference control to register new actions.
208 */
209 public static volatile ToolbarPreferences toolbar;
210
211 /**
212 * The commands undo/redo handler.
213 */
214 public final UndoRedoHandler undoRedo = new UndoRedoHandler();
215
216 /**
217 * The progress monitor being currently displayed.
218 */
219 public static PleaseWaitProgressMonitor currentProgressMonitor;
220
221 /**
222 * The main menu bar at top of screen.
223 */
224 public MainMenu menu;
225
226 /**
227 * The data validation handler.
228 */
229 public OsmValidator validator;
230
231 /**
232 * The file watcher service.
233 */
234 public static final FileWatcher fileWatcher = new FileWatcher();
235
236 /**
237 * The MOTD Layer.
238 */
239 public final GettingStarted gettingStarted = new GettingStarted();
240
241 private static final Collection<MapFrameListener> mapFrameListeners = new ArrayList<>();
242
243 protected static final Map<String, Throwable> NETWORK_ERRORS = new HashMap<>();
244
245 // First lines of last 5 error and warning messages, used for bug reports
246 private static final List<String> ERRORS_AND_WARNINGS = Collections.<String>synchronizedList(new ArrayList<String>());
247
248 private static final Set<OnlineResource> OFFLINE_RESOURCES = EnumSet.noneOf(OnlineResource.class);
249
250 /**
251 * Logging level (5 = trace, 4 = debug, 3 = info, 2 = warn, 1 = error, 0 = none).
252 * @since 6248
253 */
254 public static int logLevel = 3;
255
256 private static void rememberWarnErrorMsg(String msg) {
257 // Only remember first line of message
258 int idx = msg.indexOf('\n');
259 if (idx > 0) {
260 ERRORS_AND_WARNINGS.add(msg.substring(0, idx));
261 } else {
262 ERRORS_AND_WARNINGS.add(msg);
263 }
264 // Only keep 5 lines to avoid memory leak and incomplete stacktraces in bug reports
265 while (ERRORS_AND_WARNINGS.size() > 5) {
266 ERRORS_AND_WARNINGS.remove(0);
267 }
268 }
269
270 /**
271 * Replies the first lines of last 5 error and warning messages, used for bug reports
272 * @return the first lines of last 5 error and warning messages
273 * @since 7420
274 */
275 public static final Collection<String> getLastErrorAndWarnings() {
276 return Collections.unmodifiableList(ERRORS_AND_WARNINGS);
277 }
278
279 /**
280 * Clears the list of last error and warning messages.
281 * @since 8959
282 */
283 public static void clearLastErrorAndWarnings() {
284 ERRORS_AND_WARNINGS.clear();
285 }
286
287 /**
288 * Prints an error message if logging is on.
289 * @param msg The message to print.
290 * @since 6248
291 */
292 public static void error(String msg) {
293 if (logLevel < 1)
294 return;
295 if (msg != null && !msg.isEmpty()) {
296 System.err.println(tr("ERROR: {0}", msg));
297 rememberWarnErrorMsg("E: "+msg);
298 }
299 }
300
301 /**
302 * Prints a warning message if logging is on.
303 * @param msg The message to print.
304 */
305 public static void warn(String msg) {
306 if (logLevel < 2)
307 return;
308 if (msg != null && !msg.isEmpty()) {
309 System.err.println(tr("WARNING: {0}", msg));
310 rememberWarnErrorMsg("W: "+msg);
311 }
312 }
313
314 /**
315 * Prints an informational message if logging is on.
316 * @param msg The message to print.
317 */
318 public static void info(String msg) {
319 if (logLevel < 3)
320 return;
321 if (msg != null && !msg.isEmpty()) {
322 System.out.println(tr("INFO: {0}", msg));
323 }
324 }
325
326 /**
327 * Prints a debug message if logging is on.
328 * @param msg The message to print.
329 */
330 public static void debug(String msg) {
331 if (logLevel < 4)
332 return;
333 if (msg != null && !msg.isEmpty()) {
334 System.out.println(tr("DEBUG: {0}", msg));
335 }
336 }
337
338 /**
339 * Prints a trace message if logging is on.
340 * @param msg The message to print.
341 */
342 public static void trace(String msg) {
343 if (logLevel < 5)
344 return;
345 if (msg != null && !msg.isEmpty()) {
346 System.out.print("TRACE: ");
347 System.out.println(msg);
348 }
349 }
350
351 /**
352 * Determines if debug log level is enabled.
353 * Useful to avoid costly construction of debug messages when not enabled.
354 * @return {@code true} if log level is at least debug, {@code false} otherwise
355 * @since 6852
356 */
357 public static boolean isDebugEnabled() {
358 return logLevel >= 4;
359 }
360
361 /**
362 * Determines if trace log level is enabled.
363 * Useful to avoid costly construction of trace messages when not enabled.
364 * @return {@code true} if log level is at least trace, {@code false} otherwise
365 * @since 6852
366 */
367 public static boolean isTraceEnabled() {
368 return logLevel >= 5;
369 }
370
371 /**
372 * Prints a formatted error message if logging is on. Calls {@link MessageFormat#format}
373 * function to format text.
374 * @param msg The formatted message to print.
375 * @param objects The objects to insert into format string.
376 * @since 6248
377 */
378 public static void error(String msg, Object... objects) {
379 error(MessageFormat.format(msg, objects));
380 }
381
382 /**
383 * Prints a formatted warning message if logging is on. Calls {@link MessageFormat#format}
384 * function to format text.
385 * @param msg The formatted message to print.
386 * @param objects The objects to insert into format string.
387 */
388 public static void warn(String msg, Object... objects) {
389 warn(MessageFormat.format(msg, objects));
390 }
391
392 /**
393 * Prints a formatted informational message if logging is on. Calls {@link MessageFormat#format}
394 * function to format text.
395 * @param msg The formatted message to print.
396 * @param objects The objects to insert into format string.
397 */
398 public static void info(String msg, Object... objects) {
399 info(MessageFormat.format(msg, objects));
400 }
401
402 /**
403 * Prints a formatted debug message if logging is on. Calls {@link MessageFormat#format}
404 * function to format text.
405 * @param msg The formatted message to print.
406 * @param objects The objects to insert into format string.
407 */
408 public static void debug(String msg, Object... objects) {
409 debug(MessageFormat.format(msg, objects));
410 }
411
412 /**
413 * Prints a formatted trace message if logging is on. Calls {@link MessageFormat#format}
414 * function to format text.
415 * @param msg The formatted message to print.
416 * @param objects The objects to insert into format string.
417 */
418 public static void trace(String msg, Object... objects) {
419 trace(MessageFormat.format(msg, objects));
420 }
421
422 /**
423 * Prints an error message for the given Throwable.
424 * @param t The throwable object causing the error
425 * @since 6248
426 */
427 public static void error(Throwable t) {
428 error(t, true);
429 }
430
431 /**
432 * Prints a warning message for the given Throwable.
433 * @param t The throwable object causing the error
434 * @since 6248
435 */
436 public static void warn(Throwable t) {
437 warn(t, true);
438 }
439
440 /**
441 * Prints a debug message for the given Throwable. Useful for exceptions usually ignored
442 * @param t The throwable object causing the error
443 * @since 10420
444 */
445 public static void debug(Throwable t) {
446 debug(getErrorMessage(t));
447 }
448
449 /**
450 * Prints a trace message for the given Throwable. Useful for exceptions usually ignored
451 * @param t The throwable object causing the error
452 * @since 10420
453 */
454 public static void trace(Throwable t) {
455 trace(getErrorMessage(t));
456 }
457
458 /**
459 * Prints an error message for the given Throwable.
460 * @param t The throwable object causing the error
461 * @param stackTrace {@code true}, if the stacktrace should be displayed
462 * @since 6642
463 */
464 public static void error(Throwable t, boolean stackTrace) {
465 error(getErrorMessage(t));
466 if (stackTrace) {
467 t.printStackTrace();
468 }
469 }
470
471 /**
472 * Prints an error message for the given Throwable.
473 * @param t The throwable object causing the error
474 * @param message additional error message
475 * @since 10420
476 */
477 public static void error(Throwable t, String message) {
478 warn(message + ' ' + getErrorMessage(t));
479 }
480
481 /**
482 * Prints a warning message for the given Throwable.
483 * @param t The throwable object causing the error
484 * @param stackTrace {@code true}, if the stacktrace should be displayed
485 * @since 6642
486 */
487 public static void warn(Throwable t, boolean stackTrace) {
488 warn(getErrorMessage(t));
489 if (stackTrace) {
490 t.printStackTrace();
491 }
492 }
493
494 /**
495 * Prints a warning message for the given Throwable.
496 * @param t The throwable object causing the error
497 * @param message additional error message
498 * @since 10420
499 */
500 public static void warn(Throwable t, String message) {
501 warn(message + ' ' + getErrorMessage(t));
502 }
503
504 /**
505 * Returns a human-readable message of error, also usable for developers.
506 * @param t The error
507 * @return The human-readable error message
508 * @since 6642
509 */
510 public static String getErrorMessage(Throwable t) {
511 if (t == null) {
512 return null;
513 }
514 StringBuilder sb = new StringBuilder(t.getClass().getName());
515 String msg = t.getMessage();
516 if (msg != null) {
517 sb.append(": ").append(msg.trim());
518 }
519 Throwable cause = t.getCause();
520 if (cause != null && !cause.equals(t)) {
521 sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause));
522 }
523 return sb.toString();
524 }
525
526 /**
527 * Platform specific code goes in here.
528 * Plugins may replace it, however, some hooks will be called before any plugins have been loeaded.
529 * So if you need to hook into those early ones, split your class and send the one with the early hooks
530 * to the JOSM team for inclusion.
531 */
532 public static volatile PlatformHook platform;
533
534 /**
535 * Whether or not the java vm is openjdk
536 * We use this to work around openjdk bugs
537 */
538 public static boolean isOpenjdk;
539
540 /**
541 * Initializes {@code Main.pref} in normal application context.
542 * @since 6471
543 */
544 public static void initApplicationPreferences() {
545 Main.pref = new Preferences();
546 }
547
548 /**
549 * Set or clear (if passed <code>null</code>) the map.
550 * @param map The map to set {@link Main#map} to. Can be null.
551 */
552 public final void setMapFrame(final MapFrame map) {
553 MapFrame old = Main.map;
554 panel.setVisible(false);
555 panel.removeAll();
556 if (map != null) {
557 map.fillPanel(panel);
558 } else {
559 old.destroy();
560 panel.add(gettingStarted, BorderLayout.CENTER);
561 }
562 panel.setVisible(true);
563 redoUndoListener.commandChanged(0, 0);
564
565 Main.map = map;
566
567 // Notify map frame listeners, mostly plugins.
568 if ((map == null) == (old == null)) {
569 Main.warn("Replacing the map frame. This is not expected by some plugins and should not happen.");
570 }
571 for (MapFrameListener listener : mapFrameListeners) {
572 MapView.fireDeprecatedListenerOnAdd = true;
573 listener.mapFrameInitialized(old, map);
574 MapView.fireDeprecatedListenerOnAdd = false;
575 }
576 if (map == null && currentProgressMonitor != null) {
577 currentProgressMonitor.showForegroundDialog();
578 }
579 }
580
581 /**
582 * Remove the specified layer from the map. If it is the last layer,
583 * remove the map as well.
584 * @param layer The layer to remove
585 */
586 public final synchronized void removeLayer(final Layer layer) {
587 if (map != null) {
588 getLayerManager().removeLayer(layer);
589 if (isDisplayingMapView() && getLayerManager().getLayers().isEmpty()) {
590 setMapFrame(null);
591 }
592 }
593 }
594
595 private static volatile InitStatusListener initListener;
596
597 public interface InitStatusListener {
598
599 Object updateStatus(String event);
600
601 void finish(Object status);
602 }
603
604 public static void setInitStatusListener(InitStatusListener listener) {
605 CheckParameterUtil.ensureParameterNotNull(listener);
606 initListener = listener;
607 }
608
609 /**
610 * Constructs new {@code Main} object.
611 * @see #initialize()
612 */
613 public Main() {
614 main = this;
615 getLayerManager().addLayerChangeListener(new LayerChangeListener() {
616 @Override
617 public void layerAdded(LayerAddEvent e) {
618 Layer layer = e.getAddedLayer();
619 if (map == null) {
620 Main.main.createMapFrame(layer, null);
621 Main.map.setVisible(true);
622 }
623 ProjectionBounds viewProjectionBounds = layer.getViewProjectionBounds();
624 if (viewProjectionBounds != null) {
625 Main.map.mapView.scheduleZoomTo(new ViewportData(viewProjectionBounds));
626 }
627 }
628
629 @Override
630 public void layerRemoving(LayerRemoveEvent e) {
631 // empty
632 }
633
634 @Override
635 public void layerOrderChanged(LayerOrderChangeEvent e) {
636 //empty
637 }
638
639 });
640 }
641
642 /**
643 * Initializes the main object. A lot of global variables are initialized here.
644 * @since 10340
645 */
646 public void initialize() {
647 isOpenjdk = System.getProperty("java.vm.name").toUpperCase(Locale.ENGLISH).indexOf("OPENJDK") != -1;
648 fileWatcher.start();
649
650 new InitializationTask(tr("Executing platform startup hook")) {
651 @Override
652 public void initialize() {
653 platform.startupHook();
654 }
655 }.call();
656
657 new InitializationTask(tr("Building main menu")) {
658
659 @Override
660 public void initialize() {
661 initializeMainWindow();
662 }
663 }.call();
664
665 undoRedo.addCommandQueueListener(redoUndoListener);
666
667 // creating toolbar
668 contentPanePrivate.add(toolbar.control, BorderLayout.NORTH);
669
670 registerActionShortcut(menu.help, Shortcut.registerShortcut("system:help", tr("Help"),
671 KeyEvent.VK_F1, Shortcut.DIRECT));
672
673 // contains several initialization tasks to be executed (in parallel) by a ExecutorService
674 List<Callable<Void>> tasks = new ArrayList<>();
675
676 tasks.add(new InitializationTask(tr("Initializing OSM API")) {
677
678 @Override
679 public void initialize() {
680 // We try to establish an API connection early, so that any API
681 // capabilities are already known to the editor instance. However
682 // if it goes wrong that's not critical at this stage.
683 try {
684 OsmApi.getOsmApi().initialize(null, true);
685 } catch (OsmTransferCanceledException | OsmApiInitializationException e) {
686 Main.warn(getErrorMessage(Utils.getRootCause(e)));
687 }
688 }
689 });
690
691 tasks.add(new InitializationTask(tr("Initializing validator")) {
692
693 @Override
694 public void initialize() {
695 validator = new OsmValidator();
696 getLayerManager().addLayerChangeListener(validator);
697 }
698 });
699
700 tasks.add(new InitializationTask(tr("Initializing presets")) {
701
702 @Override
703 public void initialize() {
704 TaggingPresets.initialize();
705 }
706 });
707
708 tasks.add(new InitializationTask(tr("Initializing map styles")) {
709
710 @Override
711 public void initialize() {
712 MapPaintPreference.initialize();
713 }
714 });
715
716 tasks.add(new InitializationTask(tr("Loading imagery preferences")) {
717
718 @Override
719 public void initialize() {
720 ImageryPreference.initialize();
721 }
722 });
723
724 try {
725 final ExecutorService service = Executors.newFixedThreadPool(
726 Runtime.getRuntime().availableProcessors(), Utils.newThreadFactory("main-init-%d", Thread.NORM_PRIORITY));
727 for (Future<Void> i : service.invokeAll(tasks)) {
728 i.get();
729 }
730 service.shutdown();
731 } catch (InterruptedException | ExecutionException ex) {
732 throw new RuntimeException(ex);
733 }
734
735 // hooks for the jmapviewer component
736 FeatureAdapter.registerBrowserAdapter(new FeatureAdapter.BrowserAdapter() {
737 @Override
738 public void openLink(String url) {
739 OpenBrowser.displayUrl(url);
740 }
741 });
742 FeatureAdapter.registerTranslationAdapter(I18n.getTranslationAdapter());
743 FeatureAdapter.registerLoggingAdapter(new FeatureAdapter.LoggingAdapter() {
744 @Override
745 public Logger getLogger(String name) {
746 Logger logger = Logger.getAnonymousLogger();
747 logger.setUseParentHandlers(false);
748 logger.setLevel(Level.ALL);
749 if (logger.getHandlers().length == 0) {
750 logger.addHandler(new Handler() {
751 @Override
752 public void publish(LogRecord record) {
753 String msg = MessageFormat.format(record.getMessage(), record.getParameters());
754 if (record.getLevel().intValue() >= Level.SEVERE.intValue()) {
755 Main.error(msg);
756 } else if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
757 Main.warn(msg);
758 } else if (record.getLevel().intValue() >= Level.INFO.intValue()) {
759 Main.info(msg);
760 } else if (record.getLevel().intValue() >= Level.FINE.intValue()) {
761 Main.debug(msg);
762 } else {
763 Main.trace(msg);
764 }
765 }
766
767 @Override
768 public void flush() {
769 // Do nothing
770 }
771
772 @Override
773 public void close() {
774 // Do nothing
775 }
776 });
777 }
778 return logger;
779 }
780 });
781
782 new InitializationTask(tr("Updating user interface")) {
783
784 @Override
785 public void initialize() {
786 toolbar.refreshToolbarControl();
787 toolbar.control.updateUI();
788 contentPanePrivate.updateUI();
789 }
790 }.call();
791 }
792
793 /**
794 * Called once at startup to initialize the main window content.
795 * Should set {@link #menu}
796 */
797 protected void initializeMainWindow() {
798 // can be implementd by subclasses
799 }
800
801 private abstract static class InitializationTask implements Callable<Void> {
802
803 private final String name;
804
805 protected InitializationTask(String name) {
806 this.name = name;
807 }
808
809 public abstract void initialize();
810
811 @Override
812 public Void call() {
813 Object status = null;
814 if (initListener != null) {
815 status = initListener.updateStatus(name);
816 }
817 initialize();
818 if (initListener != null) {
819 initListener.finish(status);
820 }
821 return null;
822 }
823 }
824
825 /**
826 * Returns the main layer manager that is used by the map view.
827 * @return The layer manager. The value returned will never change.
828 * @since 10279
829 */
830 public static MainLayerManager getLayerManager() {
831 return layerManager;
832 }
833
834 /**
835 * Add a new layer to the map.
836 *
837 * If no map exists, create one.
838 *
839 * @param layer the layer
840 *
841 * @see #addLayer(Layer, ProjectionBounds)
842 * @see #addLayer(Layer, ViewportData)
843 */
844 public final void addLayer(final Layer layer) {
845 addLayer(layer, layer.getViewProjectionBounds());
846 }
847
848 /**
849 * Add a new layer to the map.
850 *
851 * If no map exists, create one.
852 *
853 * @param layer the layer
854 * @param bounds the bounds of the layer (target zoom area); can be null, then
855 * the viewport isn't changed
856 */
857 public final void addLayer(Layer layer, ProjectionBounds bounds) {
858 addLayer(layer, bounds == null ? null : new ViewportData(bounds));
859 }
860
861 /**
862 * Add a new layer to the map.
863 *
864 * If no map exists, create one.
865 *
866 * @param layer the layer
867 * @param viewport the viewport to zoom to; can be null, then the viewport isn't changed
868 */
869 public final void addLayer(Layer layer, ViewportData viewport) {
870 getLayerManager().addLayer(layer);
871 if (viewport != null) {
872 Main.map.mapView.scheduleZoomTo(viewport);
873 }
874 }
875
876 /**
877 * Creates the map frame. Call only in EDT Thread.
878 * @param firstLayer The first layer that was added.
879 * @param viewportData The initial viewport. Can be <code>null</code> to be automatically computed.
880 */
881 public synchronized void createMapFrame(Layer firstLayer, ViewportData viewportData) {
882 GuiHelper.assertCallFromEdt();
883 MapFrame mapFrame = new MapFrame(contentPanePrivate, viewportData);
884 setMapFrame(mapFrame);
885 if (firstLayer != null) {
886 mapFrame.selectMapMode((MapMode) mapFrame.getDefaultButtonAction(), firstLayer);
887 }
888 mapFrame.initializeDialogsPane();
889 // bootstrapping problem: make sure the layer list dialog is going to
890 // listen to change events of the very first layer
891 //
892 if (firstLayer != null) {
893 firstLayer.addPropertyChangeListener(LayerListDialog.getInstance().getModel());
894 }
895 }
896
897 /**
898 * Replies <code>true</code> if there is an edit layer
899 *
900 * @return <code>true</code> if there is an edit layer
901 */
902 public boolean hasEditLayer() {
903 if (getEditLayer() == null) return false;
904 return true;
905 }
906
907 /**
908 * Replies the current edit layer
909 *
910 * @return the current edit layer. <code>null</code>, if no current edit layer exists
911 */
912 public OsmDataLayer getEditLayer() {
913 if (!isDisplayingMapView()) return null;
914 return getLayerManager().getEditLayer();
915 }
916
917 /**
918 * Replies the current data set.
919 *
920 * @return the current data set. <code>null</code>, if no current data set exists
921 */
922 public DataSet getCurrentDataSet() {
923 if (!hasEditLayer()) return null;
924 return getEditLayer().data;
925 }
926
927 /**
928 * Replies the current selected primitives, from a end-user point of view.
929 * It is not always technically the same collection of primitives than {@link DataSet#getSelected()}.
930 * Indeed, if the user is currently in drawing mode, only the way currently being drawn is returned,
931 * see {@link DrawAction#getInProgressSelection()}.
932 *
933 * @return The current selected primitives, from a end-user point of view. Can be {@code null}.
934 * @since 6546
935 */
936 public Collection<OsmPrimitive> getInProgressSelection() {
937 if (map != null && map.mapMode instanceof DrawAction) {
938 return ((DrawAction) map.mapMode).getInProgressSelection();
939 } else {
940 DataSet ds = getCurrentDataSet();
941 if (ds == null) return null;
942 return ds.getSelected();
943 }
944 }
945
946 /**
947 * Returns the currently active layer
948 *
949 * @return the currently active layer. <code>null</code>, if currently no active layer exists
950 */
951 public Layer getActiveLayer() {
952 if (!isDisplayingMapView()) return null;
953 return getLayerManager().getActiveLayer();
954 }
955
956 protected static final JPanel contentPanePrivate = new JPanel(new BorderLayout());
957
958 public static void redirectToMainContentPane(JComponent source) {
959 RedirectInputMap.redirect(source, contentPanePrivate);
960 }
961
962 public static void registerActionShortcut(JosmAction action) {
963 registerActionShortcut(action, action.getShortcut());
964 }
965
966 public static void registerActionShortcut(Action action, Shortcut shortcut) {
967 KeyStroke keyStroke = shortcut.getKeyStroke();
968 if (keyStroke == null)
969 return;
970
971 InputMap inputMap = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
972 Object existing = inputMap.get(keyStroke);
973 if (existing != null && !existing.equals(action)) {
974 info(String.format("Keystroke %s is already assigned to %s, will be overridden by %s", keyStroke, existing, action));
975 }
976 inputMap.put(keyStroke, action);
977
978 contentPanePrivate.getActionMap().put(action, action);
979 }
980
981 public static void unregisterShortcut(Shortcut shortcut) {
982 contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(shortcut.getKeyStroke());
983 }
984
985 public static void unregisterActionShortcut(JosmAction action) {
986 unregisterActionShortcut(action, action.getShortcut());
987 }
988
989 public static void unregisterActionShortcut(Action action, Shortcut shortcut) {
990 unregisterShortcut(shortcut);
991 contentPanePrivate.getActionMap().remove(action);
992 }
993
994 /**
995 * Replies the registered action for the given shortcut
996 * @param shortcut The shortcut to look for
997 * @return the registered action for the given shortcut
998 * @since 5696
999 */
1000 public static Action getRegisteredActionShortcut(Shortcut shortcut) {
1001 KeyStroke keyStroke = shortcut.getKeyStroke();
1002 if (keyStroke == null)
1003 return null;
1004 Object action = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).get(keyStroke);
1005 if (action instanceof Action)
1006 return (Action) action;
1007 return null;
1008 }
1009
1010 ///////////////////////////////////////////////////////////////////////////
1011 // Implementation part
1012 ///////////////////////////////////////////////////////////////////////////
1013
1014 /**
1015 * Global panel.
1016 */
1017 public static final JPanel panel = new JPanel(new BorderLayout());
1018
1019 private final CommandQueueListener redoUndoListener = new CommandQueueListener() {
1020 @Override
1021 public void commandChanged(final int queueSize, final int redoSize) {
1022 menu.undo.setEnabled(queueSize > 0);
1023 menu.redo.setEnabled(redoSize > 0);
1024 }
1025 };
1026
1027 /**
1028 * Should be called before the main constructor to setup some parameter stuff
1029 * @param args The parsed argument list.
1030 */
1031 public static void preConstructorInit(Map<Option, Collection<String>> args) {
1032 ProjectionPreference.setProjection();
1033
1034 String defaultlaf = platform.getDefaultStyle();
1035 String laf = Main.pref.get("laf", defaultlaf);
1036 try {
1037 UIManager.setLookAndFeel(laf);
1038 } catch (final NoClassDefFoundError | ClassNotFoundException e) {
1039 // Try to find look and feel in plugin classloaders
1040 Class<?> klass = null;
1041 for (ClassLoader cl : PluginHandler.getResourceClassLoaders()) {
1042 try {
1043 klass = cl.loadClass(laf);
1044 break;
1045 } catch (ClassNotFoundException ex) {
1046 if (Main.isTraceEnabled()) {
1047 Main.trace(ex.getMessage());
1048 }
1049 }
1050 }
1051 if (klass != null && LookAndFeel.class.isAssignableFrom(klass)) {
1052 try {
1053 UIManager.setLookAndFeel((LookAndFeel) klass.getConstructor().newInstance());
1054 } catch (ReflectiveOperationException ex) {
1055 warn("Cannot set Look and Feel: " + laf + ": "+ex.getMessage());
1056 } catch (UnsupportedLookAndFeelException ex) {
1057 info("Look and Feel not supported: " + laf);
1058 Main.pref.put("laf", defaultlaf);
1059 }
1060 } else {
1061 info("Look and Feel not found: " + laf);
1062 Main.pref.put("laf", defaultlaf);
1063 }
1064 } catch (UnsupportedLookAndFeelException e) {
1065 info("Look and Feel not supported: " + laf);
1066 Main.pref.put("laf", defaultlaf);
1067 } catch (InstantiationException | IllegalAccessException e) {
1068 error(e);
1069 }
1070 toolbar = new ToolbarPreferences();
1071 contentPanePrivate.updateUI();
1072 panel.updateUI();
1073
1074 UIManager.put("OptionPane.okIcon", ImageProvider.get("ok"));
1075 UIManager.put("OptionPane.yesIcon", UIManager.get("OptionPane.okIcon"));
1076 UIManager.put("OptionPane.cancelIcon", ImageProvider.get("cancel"));
1077 UIManager.put("OptionPane.noIcon", UIManager.get("OptionPane.cancelIcon"));
1078 // Ensures caret color is the same than text foreground color, see #12257
1079 // See http://docs.oracle.com/javase/7/docs/api/javax/swing/plaf/synth/doc-files/componentProperties.html
1080 for (String p : Arrays.asList(
1081 "EditorPane", "FormattedTextField", "PasswordField", "TextArea", "TextField", "TextPane")) {
1082 UIManager.put(p+".caretForeground", UIManager.getColor(p+".foreground"));
1083 }
1084
1085 I18n.translateJavaInternalMessages();
1086
1087 // init default coordinate format
1088 //
1089 try {
1090 CoordinateFormat.setCoordinateFormat(CoordinateFormat.valueOf(Main.pref.get("coordinates")));
1091 } catch (IllegalArgumentException iae) {
1092 CoordinateFormat.setCoordinateFormat(CoordinateFormat.DECIMAL_DEGREES);
1093 }
1094
1095 }
1096
1097 protected static void postConstructorProcessCmdLine(Map<Option, Collection<String>> args) {
1098 if (args.containsKey(Option.DOWNLOAD)) {
1099 List<File> fileList = new ArrayList<>();
1100 for (String s : args.get(Option.DOWNLOAD)) {
1101 DownloadParamType.paramType(s).download(s, fileList);
1102 }
1103 if (!fileList.isEmpty()) {
1104 OpenFileAction.openFiles(fileList, true);
1105 }
1106 }
1107 if (args.containsKey(Option.DOWNLOADGPS)) {
1108 for (String s : args.get(Option.DOWNLOADGPS)) {
1109 DownloadParamType.paramType(s).downloadGps(s);
1110 }
1111 }
1112 if (args.containsKey(Option.SELECTION)) {
1113 for (String s : args.get(Option.SELECTION)) {
1114 SearchAction.search(s, SearchAction.SearchMode.add);
1115 }
1116 }
1117 }
1118
1119 /**
1120 * Asks user to perform "save layer" operations (save on disk and/or upload data to server) for all
1121 * {@link AbstractModifiableLayer} before JOSM exits.
1122 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations.
1123 * {@code false} if the user cancels.
1124 * @since 2025
1125 */
1126 public static boolean saveUnsavedModifications() {
1127 if (!isDisplayingMapView()) return true;
1128 return saveUnsavedModifications(getLayerManager().getLayersOfType(AbstractModifiableLayer.class), true);
1129 }
1130
1131 /**
1132 * Asks user to perform "save layer" operations (save on disk and/or upload data to server) before data layers deletion.
1133 *
1134 * @param selectedLayers The layers to check. Only instances of {@link AbstractModifiableLayer} are considered.
1135 * @param exit {@code true} if JOSM is exiting, {@code false} otherwise.
1136 * @return {@code true} if there was nothing to save, or if the user wants to proceed to save operations.
1137 * {@code false} if the user cancels.
1138 * @since 5519
1139 */
1140 public static boolean saveUnsavedModifications(Iterable<? extends Layer> selectedLayers, boolean exit) {
1141 SaveLayersDialog dialog = new SaveLayersDialog(parent);
1142 List<AbstractModifiableLayer> layersWithUnmodifiedChanges = new ArrayList<>();
1143 for (Layer l: selectedLayers) {
1144 if (!(l instanceof AbstractModifiableLayer)) {
1145 continue;
1146 }
1147 AbstractModifiableLayer odl = (AbstractModifiableLayer) l;
1148 if (odl.isModified() &&
1149 ((!odl.isSavable() && !odl.isUploadable()) ||
1150 odl.requiresSaveToFile() ||
1151 (odl.requiresUploadToServer() && !odl.isUploadDiscouraged()))) {
1152 layersWithUnmodifiedChanges.add(odl);
1153 }
1154 }
1155 if (exit) {
1156 dialog.prepareForSavingAndUpdatingLayersBeforeExit();
1157 } else {
1158 dialog.prepareForSavingAndUpdatingLayersBeforeDelete();
1159 }
1160 if (!layersWithUnmodifiedChanges.isEmpty()) {
1161 dialog.getModel().populate(layersWithUnmodifiedChanges);
1162 dialog.setVisible(true);
1163 switch(dialog.getUserAction()) {
1164 case PROCEED: return true;
1165 case CANCEL:
1166 default: return false;
1167 }
1168 }
1169
1170 return true;
1171 }
1172
1173 /**
1174 * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
1175 * If there are some unsaved data layers, asks first for user confirmation.
1176 * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
1177 * @param exitCode The return code
1178 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation.
1179 * @since 3378
1180 */
1181 public static boolean exitJosm(boolean exit, int exitCode) {
1182 if (Main.saveUnsavedModifications()) {
1183 if (Main.main != null) {
1184 Main.main.shutdown();
1185 }
1186
1187 if (exit) {
1188 System.exit(exitCode);
1189 }
1190 return true;
1191 }
1192 return false;
1193 }
1194
1195 protected void shutdown() {
1196 worker.shutdown();
1197 ImageProvider.shutdown(false);
1198 JCSCacheManager.shutdown();
1199 if (map != null) {
1200 map.rememberToggleDialogWidth();
1201 }
1202 // Remove all layers because somebody may rely on layerRemoved events (like AutosaveTask)
1203 if (Main.isDisplayingMapView()) {
1204 Collection<Layer> layers = new ArrayList<>(getLayerManager().getLayers());
1205 for (Layer l: layers) {
1206 Main.main.removeLayer(l);
1207 }
1208 }
1209 try {
1210 pref.saveDefaults();
1211 } catch (IOException ex) {
1212 Main.warn(ex, tr("Failed to save default preferences."));
1213 }
1214 worker.shutdownNow();
1215 ImageProvider.shutdown(true);
1216 }
1217
1218 /**
1219 * The type of a command line parameter, to be used in switch statements.
1220 * @see #paramType
1221 */
1222 enum DownloadParamType {
1223 httpUrl {
1224 @Override
1225 void download(String s, Collection<File> fileList) {
1226 new OpenLocationAction().openUrl(false, s);
1227 }
1228
1229 @Override
1230 void downloadGps(String s) {
1231 final Bounds b = OsmUrlToBounds.parse(s);
1232 if (b == null) {
1233 JOptionPane.showMessageDialog(
1234 Main.parent,
1235 tr("Ignoring malformed URL: \"{0}\"", s),
1236 tr("Warning"),
1237 JOptionPane.WARNING_MESSAGE
1238 );
1239 return;
1240 }
1241 downloadFromParamBounds(true, b);
1242 }
1243 }, fileUrl {
1244 @Override
1245 void download(String s, Collection<File> fileList) {
1246 File f = null;
1247 try {
1248 f = new File(new URI(s));
1249 } catch (URISyntaxException e) {
1250 JOptionPane.showMessageDialog(
1251 Main.parent,
1252 tr("Ignoring malformed file URL: \"{0}\"", s),
1253 tr("Warning"),
1254 JOptionPane.WARNING_MESSAGE
1255 );
1256 }
1257 if (f != null) {
1258 fileList.add(f);
1259 }
1260 }
1261 }, bounds {
1262
1263 /**
1264 * Download area specified on the command line as bounds string.
1265 * @param rawGps Flag to download raw GPS tracks
1266 * @param s The bounds parameter
1267 */
1268 private void downloadFromParamBounds(final boolean rawGps, String s) {
1269 final StringTokenizer st = new StringTokenizer(s, ",");
1270 if (st.countTokens() == 4) {
1271 Bounds b = new Bounds(
1272 new LatLon(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken())),
1273 new LatLon(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken()))
1274 );
1275 Main.downloadFromParamBounds(rawGps, b);
1276 }
1277 }
1278
1279 @Override
1280 void download(String param, Collection<File> fileList) {
1281 downloadFromParamBounds(false, param);
1282 }
1283
1284 @Override
1285 void downloadGps(String param) {
1286 downloadFromParamBounds(true, param);
1287 }
1288 }, fileName {
1289 @Override
1290 void download(String s, Collection<File> fileList) {
1291 fileList.add(new File(s));
1292 }
1293 };
1294
1295 /**
1296 * Performs the download
1297 * @param param represents the object to be downloaded
1298 * @param fileList files which shall be opened, should be added to this collection
1299 */
1300 abstract void download(String param, Collection<File> fileList);
1301
1302 /**
1303 * Performs the GPS download
1304 * @param param represents the object to be downloaded
1305 */
1306 void downloadGps(String param) {
1307 JOptionPane.showMessageDialog(
1308 Main.parent,
1309 tr("Parameter \"downloadgps\" does not accept file names or file URLs"),
1310 tr("Warning"),
1311 JOptionPane.WARNING_MESSAGE
1312 );
1313 }
1314
1315 /**
1316 * Guess the type of a parameter string specified on the command line with --download= or --downloadgps.
1317 *
1318 * @param s A parameter string
1319 * @return The guessed parameter type
1320 */
1321 static DownloadParamType paramType(String s) {
1322 if (s.startsWith("http:") || s.startsWith("https:")) return DownloadParamType.httpUrl;
1323 if (s.startsWith("file:")) return DownloadParamType.fileUrl;
1324 String coorPattern = "\\s*[+-]?[0-9]+(\\.[0-9]+)?\\s*";
1325 if (s.matches(coorPattern + "(," + coorPattern + "){3}")) return DownloadParamType.bounds;
1326 // everything else must be a file name
1327 return DownloadParamType.fileName;
1328 }
1329 }
1330
1331 /**
1332 * Download area specified as Bounds value.
1333 * @param rawGps Flag to download raw GPS tracks
1334 * @param b The bounds value
1335 */
1336 private static void downloadFromParamBounds(final boolean rawGps, Bounds b) {
1337 DownloadTask task = rawGps ? new DownloadGpsTask() : new DownloadOsmTask();
1338 // asynchronously launch the download task ...
1339 Future<?> future = task.download(true, b, null);
1340 // ... and the continuation when the download is finished (this will wait for the download to finish)
1341 Main.worker.execute(new PostDownloadHandler(task, future));
1342 }
1343
1344 /**
1345 * Identifies the current operating system family and initializes the platform hook accordingly.
1346 * @since 1849
1347 */
1348 public static void determinePlatformHook() {
1349 String os = System.getProperty("os.name");
1350 if (os == null) {
1351 warn("Your operating system has no name, so I'm guessing its some kind of *nix.");
1352 platform = new PlatformHookUnixoid();
1353 } else if (os.toLowerCase(Locale.ENGLISH).startsWith("windows")) {
1354 platform = new PlatformHookWindows();
1355 } else if ("Linux".equals(os) || "Solaris".equals(os) ||
1356 "SunOS".equals(os) || "AIX".equals(os) ||
1357 "FreeBSD".equals(os) || "NetBSD".equals(os) || "OpenBSD".equals(os)) {
1358 platform = new PlatformHookUnixoid();
1359 } else if (os.toLowerCase(Locale.ENGLISH).startsWith("mac os x")) {
1360 platform = new PlatformHookOsx();
1361 } else {
1362 warn("I don't know your operating system '"+os+"', so I'm guessing its some kind of *nix.");
1363 platform = new PlatformHookUnixoid();
1364 }
1365 }
1366
1367 /**
1368 * Determines if JOSM currently runs with Java 8 or later.
1369 * @return {@code true} if the current JVM is at least Java 8, {@code false} otherwise
1370 * @since 7894
1371 */
1372 public static boolean isJava8orLater() {
1373 String version = System.getProperty("java.version");
1374 return version != null && !version.matches("^(1\\.)?[7].*");
1375 }
1376
1377 /**
1378 * Checks that JOSM is at least running with Java 7.
1379 * @since 7001
1380 */
1381 public static void checkJavaVersion() {
1382 String version = System.getProperty("java.version");
1383 if (version != null) {
1384 if (version.matches("^(1\\.)?[789].*"))
1385 return;
1386 if (version.matches("^(1\\.)?[56].*")) {
1387 JMultilineLabel ho = new JMultilineLabel("<html>"+
1388 tr("<h2>JOSM requires Java version {0}.</h2>"+
1389 "Detected Java version: {1}.<br>"+
1390 "You can <ul><li>update your Java (JRE) or</li>"+
1391 "<li>use an earlier (Java {2} compatible) version of JOSM.</li></ul>"+
1392 "More Info:", "7", version, "6")+"</html>");
1393 JTextArea link = new JTextArea(HelpUtil.getWikiBaseHelpUrl()+"/Help/SystemRequirements");
1394 link.setEditable(false);
1395 link.setBackground(panel.getBackground());
1396 JPanel panel = new JPanel(new GridBagLayout());
1397 GridBagConstraints gbc = new GridBagConstraints();
1398 gbc.gridwidth = GridBagConstraints.REMAINDER;
1399 gbc.anchor = GridBagConstraints.WEST;
1400 gbc.weightx = 1.0;
1401 panel.add(ho, gbc);
1402 panel.add(link, gbc);
1403 final String exitStr = tr("Exit JOSM");
1404 final String continueStr = tr("Continue, try anyway");
1405 int ret = JOptionPane.showOptionDialog(null, panel, tr("Error"), JOptionPane.YES_NO_OPTION,
1406 JOptionPane.ERROR_MESSAGE, null, new String[] {exitStr, continueStr}, exitStr);
1407 if (ret == 0) {
1408 System.exit(0);
1409 }
1410 return;
1411 }
1412 }
1413 error("Could not recognize Java Version: "+version);
1414 }
1415
1416 /* ----------------------------------------------------------------------------------------- */
1417 /* projection handling - Main is a registry for a single, global projection instance */
1418 /* */
1419 /* TODO: For historical reasons the registry is implemented by Main. An alternative approach */
1420 /* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class. */
1421 /* ----------------------------------------------------------------------------------------- */
1422 /**
1423 * The projection method used.
1424 * use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
1425 * Use {@link #setProjection(Projection)} in order to trigger a projection change event.
1426 */
1427 private static volatile Projection proj;
1428
1429 /**
1430 * Replies the current projection.
1431 *
1432 * @return the currently active projection
1433 */
1434 public static Projection getProjection() {
1435 return proj;
1436 }
1437
1438 /**
1439 * Sets the current projection
1440 *
1441 * @param p the projection
1442 */
1443 public static void setProjection(Projection p) {
1444 CheckParameterUtil.ensureParameterNotNull(p);
1445 Projection oldValue = proj;
1446 Bounds b = isDisplayingMapView() ? map.mapView.getRealBounds() : null;
1447 proj = p;
1448 fireProjectionChanged(oldValue, proj, b);
1449 }
1450
1451 /*
1452 * Keep WeakReferences to the listeners. This relieves clients from the burden of
1453 * explicitly removing the listeners and allows us to transparently register every
1454 * created dataset as projection change listener.
1455 */
1456 private static final List<WeakReference<ProjectionChangeListener>> listeners = new ArrayList<>();
1457
1458 private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) {
1459 if (newValue == null ^ oldValue == null
1460 || (newValue != null && oldValue != null && !Objects.equals(newValue.toCode(), oldValue.toCode()))) {
1461
1462 synchronized (Main.class) {
1463 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
1464 while (it.hasNext()) {
1465 WeakReference<ProjectionChangeListener> wr = it.next();
1466 ProjectionChangeListener listener = wr.get();
1467 if (listener == null) {
1468 it.remove();
1469 continue;
1470 }
1471 listener.projectionChanged(oldValue, newValue);
1472 }
1473 }
1474 if (newValue != null && oldBounds != null) {
1475 Main.map.mapView.zoomTo(oldBounds);
1476 }
1477 /* TODO - remove layers with fixed projection */
1478 }
1479 }
1480
1481 /**
1482 * Register a projection change listener.
1483 *
1484 * @param listener the listener. Ignored if <code>null</code>.
1485 */
1486 public static void addProjectionChangeListener(ProjectionChangeListener listener) {
1487 if (listener == null) return;
1488 synchronized (Main.class) {
1489 for (WeakReference<ProjectionChangeListener> wr : listeners) {
1490 // already registered ? => abort
1491 if (wr.get() == listener) return;
1492 }
1493 listeners.add(new WeakReference<>(listener));
1494 }
1495 }
1496
1497 /**
1498 * Removes a projection change listener.
1499 *
1500 * @param listener the listener. Ignored if <code>null</code>.
1501 */
1502 public static void removeProjectionChangeListener(ProjectionChangeListener listener) {
1503 if (listener == null) return;
1504 synchronized (Main.class) {
1505 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
1506 while (it.hasNext()) {
1507 WeakReference<ProjectionChangeListener> wr = it.next();
1508 // remove the listener - and any other listener which got garbage
1509 // collected in the meantime
1510 if (wr.get() == null || wr.get() == listener) {
1511 it.remove();
1512 }
1513 }
1514 }
1515 }
1516
1517 /**
1518 * Listener for window switch events.
1519 *
1520 * These are events, when the user activates a window of another application
1521 * or comes back to JOSM. Window switches from one JOSM window to another
1522 * are not reported.
1523 */
1524 public interface WindowSwitchListener {
1525 /**
1526 * Called when the user activates a window of another application.
1527 */
1528 void toOtherApplication();
1529
1530 /**
1531 * Called when the user comes from a window of another application back to JOSM.
1532 */
1533 void fromOtherApplication();
1534 }
1535
1536 private static final List<WeakReference<WindowSwitchListener>> windowSwitchListeners = new ArrayList<>();
1537
1538 /**
1539 * Register a window switch listener.
1540 *
1541 * @param listener the listener. Ignored if <code>null</code>.
1542 */
1543 public static void addWindowSwitchListener(WindowSwitchListener listener) {
1544 if (listener == null) return;
1545 synchronized (Main.class) {
1546 for (WeakReference<WindowSwitchListener> wr : windowSwitchListeners) {
1547 // already registered ? => abort
1548 if (wr.get() == listener) return;
1549 }
1550 boolean wasEmpty = windowSwitchListeners.isEmpty();
1551 windowSwitchListeners.add(new WeakReference<>(listener));
1552 if (wasEmpty) {
1553 // The following call will have no effect, when there is no window
1554 // at the time. Therefore, MasterWindowListener.setup() will also be
1555 // called, as soon as the main window is shown.
1556 MasterWindowListener.setup();
1557 }
1558 }
1559 }
1560
1561 /**
1562 * Removes a window switch listener.
1563 *
1564 * @param listener the listener. Ignored if <code>null</code>.
1565 */
1566 public static void removeWindowSwitchListener(WindowSwitchListener listener) {
1567 if (listener == null) return;
1568 synchronized (Main.class) {
1569 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1570 while (it.hasNext()) {
1571 WeakReference<WindowSwitchListener> wr = it.next();
1572 // remove the listener - and any other listener which got garbage
1573 // collected in the meantime
1574 if (wr.get() == null || wr.get() == listener) {
1575 it.remove();
1576 }
1577 }
1578 if (windowSwitchListeners.isEmpty()) {
1579 MasterWindowListener.teardown();
1580 }
1581 }
1582 }
1583
1584 /**
1585 * WindowListener, that is registered on all Windows of the application.
1586 *
1587 * Its purpose is to notify WindowSwitchListeners, that the user switches to
1588 * another application, e.g. a browser, or back to JOSM.
1589 *
1590 * When changing from JOSM to another application and back (e.g. two times
1591 * alt+tab), the active Window within JOSM may be different.
1592 * Therefore, we need to register listeners to <strong>all</strong> (visible)
1593 * Windows in JOSM, and it does not suffice to monitor the one that was
1594 * deactivated last.
1595 *
1596 * This class is only "active" on demand, i.e. when there is at least one
1597 * WindowSwitchListener registered.
1598 */
1599 protected static class MasterWindowListener extends WindowAdapter {
1600
1601 private static MasterWindowListener INSTANCE;
1602
1603 public static synchronized MasterWindowListener getInstance() {
1604 if (INSTANCE == null) {
1605 INSTANCE = new MasterWindowListener();
1606 }
1607 return INSTANCE;
1608 }
1609
1610 /**
1611 * Register listeners to all non-hidden windows.
1612 *
1613 * Windows that are created later, will be cared for in {@link #windowDeactivated(WindowEvent)}.
1614 */
1615 public static void setup() {
1616 if (!windowSwitchListeners.isEmpty()) {
1617 for (Window w : Window.getWindows()) {
1618 if (w.isShowing()) {
1619 if (!Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
1620 w.addWindowListener(getInstance());
1621 }
1622 }
1623 }
1624 }
1625 }
1626
1627 /**
1628 * Unregister all listeners.
1629 */
1630 public static void teardown() {
1631 for (Window w : Window.getWindows()) {
1632 w.removeWindowListener(getInstance());
1633 }
1634 }
1635
1636 @Override
1637 public void windowActivated(WindowEvent e) {
1638 if (e.getOppositeWindow() == null) { // we come from a window of a different application
1639 // fire WindowSwitchListeners
1640 synchronized (Main.class) {
1641 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1642 while (it.hasNext()) {
1643 WeakReference<WindowSwitchListener> wr = it.next();
1644 WindowSwitchListener listener = wr.get();
1645 if (listener == null) {
1646 it.remove();
1647 continue;
1648 }
1649 listener.fromOtherApplication();
1650 }
1651 }
1652 }
1653 }
1654
1655 @Override
1656 public void windowDeactivated(WindowEvent e) {
1657 // set up windows that have been created in the meantime
1658 for (Window w : Window.getWindows()) {
1659 if (!w.isShowing()) {
1660 w.removeWindowListener(getInstance());
1661 } else {
1662 if (!Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
1663 w.addWindowListener(getInstance());
1664 }
1665 }
1666 }
1667 if (e.getOppositeWindow() == null) { // we go to a window of a different application
1668 // fire WindowSwitchListeners
1669 synchronized (Main.class) {
1670 Iterator<WeakReference<WindowSwitchListener>> it = windowSwitchListeners.iterator();
1671 while (it.hasNext()) {
1672 WeakReference<WindowSwitchListener> wr = it.next();
1673 WindowSwitchListener listener = wr.get();
1674 if (listener == null) {
1675 it.remove();
1676 continue;
1677 }
1678 listener.toOtherApplication();
1679 }
1680 }
1681 }
1682 }
1683 }
1684
1685 /**
1686 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
1687 * @param listener The MapFrameListener
1688 * @param fireWhenMapViewPresent If true, will fire an initial mapFrameInitialized event
1689 * when the MapFrame is present. Otherwise will only fire when the MapFrame is created
1690 * or destroyed.
1691 * @return {@code true} if the listeners collection changed as a result of the call
1692 */
1693 public static boolean addMapFrameListener(MapFrameListener listener, boolean fireWhenMapViewPresent) {
1694 boolean changed = listener != null && mapFrameListeners.add(listener);
1695 if (fireWhenMapViewPresent && changed && map != null) {
1696 listener.mapFrameInitialized(null, map);
1697 }
1698 return changed;
1699 }
1700
1701 /**
1702 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
1703 * @param listener The MapFrameListener
1704 * @return {@code true} if the listeners collection changed as a result of the call
1705 * @since 5957
1706 */
1707 public static boolean addMapFrameListener(MapFrameListener listener) {
1708 return addMapFrameListener(listener, false);
1709 }
1710
1711 /**
1712 * Unregisters the given {@code MapFrameListener} from MapFrame changes
1713 * @param listener The MapFrameListener
1714 * @return {@code true} if the listeners collection changed as a result of the call
1715 * @since 5957
1716 */
1717 public static boolean removeMapFrameListener(MapFrameListener listener) {
1718 return listener != null && mapFrameListeners.remove(listener);
1719 }
1720
1721 /**
1722 * Adds a new network error that occur to give a hint about broken Internet connection.
1723 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
1724 *
1725 * @param url The accessed URL that caused the error
1726 * @param t The network error
1727 * @return The previous error associated to the given resource, if any. Can be {@code null}
1728 * @since 6642
1729 */
1730 public static Throwable addNetworkError(URL url, Throwable t) {
1731 if (url != null && t != null) {
1732 Throwable old = addNetworkError(url.toExternalForm(), t);
1733 if (old != null) {
1734 Main.warn("Already here "+old);
1735 }
1736 return old;
1737 }
1738 return null;
1739 }
1740
1741 /**
1742 * Adds a new network error that occur to give a hint about broken Internet connection.
1743 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
1744 *
1745 * @param url The accessed URL that caused the error
1746 * @param t The network error
1747 * @return The previous error associated to the given resource, if any. Can be {@code null}
1748 * @since 6642
1749 */
1750 public static Throwable addNetworkError(String url, Throwable t) {
1751 if (url != null && t != null) {
1752 return NETWORK_ERRORS.put(url, t);
1753 }
1754 return null;
1755 }
1756
1757 /**
1758 * Returns the network errors that occured until now.
1759 * @return the network errors that occured until now, indexed by URL
1760 * @since 6639
1761 */
1762 public static Map<String, Throwable> getNetworkErrors() {
1763 return new HashMap<>(NETWORK_ERRORS);
1764 }
1765
1766 /**
1767 * Returns the command-line arguments used to run the application.
1768 * @return the command-line arguments used to run the application
1769 * @since 8356
1770 */
1771 public static List<String> getCommandLineArgs() {
1772 return Collections.unmodifiableList(COMMAND_LINE_ARGS);
1773 }
1774
1775 /**
1776 * Returns the JOSM website URL.
1777 * @return the josm website URL
1778 * @since 6897
1779 */
1780 public static String getJOSMWebsite() {
1781 if (Main.pref != null)
1782 return Main.pref.get("josm.url", JOSM_WEBSITE);
1783 return JOSM_WEBSITE;
1784 }
1785
1786 /**
1787 * Returns the JOSM XML URL.
1788 * @return the josm XML URL
1789 * @since 6897
1790 */
1791 public static String getXMLBase() {
1792 // Always return HTTP (issues reported with HTTPS)
1793 return "http://josm.openstreetmap.de";
1794 }
1795
1796 /**
1797 * Returns the OSM website URL.
1798 * @return the OSM website URL
1799 * @since 6897
1800 */
1801 public static String getOSMWebsite() {
1802 if (Main.pref != null)
1803 return Main.pref.get("osm.url", OSM_WEBSITE);
1804 return OSM_WEBSITE;
1805 }
1806
1807 /**
1808 * Replies the base URL for browsing information about a primitive.
1809 * @return the base URL, i.e. https://www.openstreetmap.org
1810 * @since 7678
1811 */
1812 public static String getBaseBrowseUrl() {
1813 if (Main.pref != null)
1814 return Main.pref.get("osm-browse.url", getOSMWebsite());
1815 return getOSMWebsite();
1816 }
1817
1818 /**
1819 * Replies the base URL for browsing information about a user.
1820 * @return the base URL, i.e. https://www.openstreetmap.org/user
1821 * @since 7678
1822 */
1823 public static String getBaseUserUrl() {
1824 if (Main.pref != null)
1825 return Main.pref.get("osm-user.url", getOSMWebsite() + "/user");
1826 return getOSMWebsite() + "/user";
1827 }
1828
1829 /**
1830 * Determines if we are currently running on OSX.
1831 * @return {@code true} if we are currently running on OSX
1832 * @since 6957
1833 */
1834 public static boolean isPlatformOsx() {
1835 return Main.platform instanceof PlatformHookOsx;
1836 }
1837
1838 /**
1839 * Determines if we are currently running on Windows.
1840 * @return {@code true} if we are currently running on Windows
1841 * @since 7335
1842 */
1843 public static boolean isPlatformWindows() {
1844 return Main.platform instanceof PlatformHookWindows;
1845 }
1846
1847 /**
1848 * Determines if the given online resource is currently offline.
1849 * @param r the online resource
1850 * @return {@code true} if {@code r} is offline and should not be accessed
1851 * @since 7434
1852 */
1853 public static boolean isOffline(OnlineResource r) {
1854 return OFFLINE_RESOURCES.contains(r) || OFFLINE_RESOURCES.contains(OnlineResource.ALL);
1855 }
1856
1857 /**
1858 * Sets the given online resource to offline state.
1859 * @param r the online resource
1860 * @return {@code true} if {@code r} was not already offline
1861 * @since 7434
1862 */
1863 public static boolean setOffline(OnlineResource r) {
1864 return OFFLINE_RESOURCES.add(r);
1865 }
1866
1867 /**
1868 * Sets the given online resource to online state.
1869 * @param r the online resource
1870 * @return {@code true} if {@code r} was offline
1871 * @since 8506
1872 */
1873 public static boolean setOnline(OnlineResource r) {
1874 return OFFLINE_RESOURCES.remove(r);
1875 }
1876
1877 /**
1878 * Replies the set of online resources currently offline.
1879 * @return the set of online resources currently offline
1880 * @since 7434
1881 */
1882 public static Set<OnlineResource> getOfflineResources() {
1883 return EnumSet.copyOf(OFFLINE_RESOURCES);
1884 }
1885}
Note: See TracBrowser for help on using the repository browser.