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

Last change on this file since 10507 was 10507, checked in by Don-vip, 8 years ago

fix #13095 - Exception on closing layers (patch by michael2402) - gsoc-core

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