source: josm/trunk/src/org/openstreetmap/josm/gui/MainApplication.java@ 13647

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

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

  • Property svn:eol-style set to native
File size: 68.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
7
8import java.awt.BorderLayout;
9import java.awt.Container;
10import java.awt.Dimension;
11import java.awt.Font;
12import java.awt.GraphicsEnvironment;
13import java.awt.GridBagLayout;
14import java.awt.Toolkit;
15import java.awt.event.KeyEvent;
16import java.io.File;
17import java.io.IOException;
18import java.io.InputStream;
19import java.lang.reflect.Field;
20import java.net.Authenticator;
21import java.net.Inet6Address;
22import java.net.InetAddress;
23import java.net.ProxySelector;
24import java.net.URL;
25import java.nio.file.InvalidPathException;
26import java.nio.file.Paths;
27import java.security.AllPermission;
28import java.security.CodeSource;
29import java.security.GeneralSecurityException;
30import java.security.KeyStoreException;
31import java.security.NoSuchAlgorithmException;
32import java.security.PermissionCollection;
33import java.security.Permissions;
34import java.security.Policy;
35import java.security.cert.CertificateException;
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.Collection;
39import java.util.Collections;
40import java.util.List;
41import java.util.Locale;
42import java.util.Map;
43import java.util.Objects;
44import java.util.Optional;
45import java.util.ResourceBundle;
46import java.util.Set;
47import java.util.TreeSet;
48import java.util.concurrent.Callable;
49import java.util.concurrent.ExecutorService;
50import java.util.concurrent.Executors;
51import java.util.concurrent.Future;
52import java.util.logging.Level;
53import java.util.stream.Collectors;
54import java.util.stream.Stream;
55
56import javax.net.ssl.SSLSocketFactory;
57import javax.swing.Action;
58import javax.swing.InputMap;
59import javax.swing.JComponent;
60import javax.swing.JLabel;
61import javax.swing.JOptionPane;
62import javax.swing.JPanel;
63import javax.swing.KeyStroke;
64import javax.swing.LookAndFeel;
65import javax.swing.RepaintManager;
66import javax.swing.SwingUtilities;
67import javax.swing.UIManager;
68import javax.swing.UnsupportedLookAndFeelException;
69
70import org.jdesktop.swinghelper.debug.CheckThreadViolationRepaintManager;
71import org.openstreetmap.gui.jmapviewer.FeatureAdapter;
72import org.openstreetmap.josm.CLIModule;
73import org.openstreetmap.josm.Main;
74import org.openstreetmap.josm.actions.DeleteAction;
75import org.openstreetmap.josm.actions.JosmAction;
76import org.openstreetmap.josm.actions.OpenFileAction;
77import org.openstreetmap.josm.actions.OpenFileAction.OpenFileTask;
78import org.openstreetmap.josm.actions.PreferencesAction;
79import org.openstreetmap.josm.actions.RestartAction;
80import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask;
81import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
82import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
83import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
84import org.openstreetmap.josm.actions.mapmode.DrawAction;
85import org.openstreetmap.josm.actions.search.SearchAction;
86import org.openstreetmap.josm.command.DeleteCommand;
87import org.openstreetmap.josm.command.SplitWayCommand;
88import org.openstreetmap.josm.data.Bounds;
89import org.openstreetmap.josm.data.UndoRedoHandler;
90import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener;
91import org.openstreetmap.josm.data.Version;
92import org.openstreetmap.josm.data.cache.JCSCacheManager;
93import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
94import org.openstreetmap.josm.data.osm.DataSet;
95import org.openstreetmap.josm.data.osm.OsmPrimitive;
96import org.openstreetmap.josm.data.osm.UserInfo;
97import org.openstreetmap.josm.data.osm.search.SearchMode;
98import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
99import org.openstreetmap.josm.data.preferences.sources.SourceType;
100import org.openstreetmap.josm.data.projection.ProjectionCLI;
101import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileSource;
102import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
103import org.openstreetmap.josm.data.projection.datum.NTV2Proj4DirGridShiftFileSource;
104import org.openstreetmap.josm.data.validation.OsmValidator;
105import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
106import org.openstreetmap.josm.gui.ProgramArguments.Option;
107import org.openstreetmap.josm.gui.SplashScreen.SplashProgressMonitor;
108import org.openstreetmap.josm.gui.bugreport.BugReportDialog;
109import org.openstreetmap.josm.gui.download.DownloadDialog;
110import org.openstreetmap.josm.gui.io.CredentialDialog;
111import org.openstreetmap.josm.gui.io.CustomConfigurator.XMLCommandProcessor;
112import org.openstreetmap.josm.gui.io.SaveLayersDialog;
113import org.openstreetmap.josm.gui.layer.AutosaveTask;
114import org.openstreetmap.josm.gui.layer.ImageryLayer;
115import org.openstreetmap.josm.gui.layer.Layer;
116import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
117import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
118import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
119import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
120import org.openstreetmap.josm.gui.layer.MainLayerManager;
121import org.openstreetmap.josm.gui.layer.OsmDataLayer;
122import org.openstreetmap.josm.gui.layer.TMSLayer;
123import org.openstreetmap.josm.gui.mappaint.RenderingCLI;
124import org.openstreetmap.josm.gui.mappaint.loader.MapPaintStyleLoader;
125import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
126import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
127import org.openstreetmap.josm.gui.preferences.display.LafPreference;
128import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;
129import org.openstreetmap.josm.gui.preferences.map.MapPaintPreference;
130import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
131import org.openstreetmap.josm.gui.preferences.server.ProxyPreference;
132import org.openstreetmap.josm.gui.progress.swing.ProgressMonitorExecutor;
133import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
134import org.openstreetmap.josm.gui.util.GuiHelper;
135import org.openstreetmap.josm.gui.util.RedirectInputMap;
136import org.openstreetmap.josm.gui.util.WindowGeometry;
137import org.openstreetmap.josm.gui.widgets.UrlLabel;
138import org.openstreetmap.josm.io.CachedFile;
139import org.openstreetmap.josm.io.CertificateAmendment;
140import org.openstreetmap.josm.io.DefaultProxySelector;
141import org.openstreetmap.josm.io.FileWatcher;
142import org.openstreetmap.josm.io.MessageNotifier;
143import org.openstreetmap.josm.io.OnlineResource;
144import org.openstreetmap.josm.io.OsmApi;
145import org.openstreetmap.josm.io.OsmApiInitializationException;
146import org.openstreetmap.josm.io.OsmConnection;
147import org.openstreetmap.josm.io.OsmTransferCanceledException;
148import org.openstreetmap.josm.io.OsmTransferException;
149import org.openstreetmap.josm.io.auth.AbstractCredentialsAgent;
150import org.openstreetmap.josm.io.auth.CredentialsManager;
151import org.openstreetmap.josm.io.auth.DefaultAuthenticator;
152import org.openstreetmap.josm.io.protocols.data.Handler;
153import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
154import org.openstreetmap.josm.plugins.PluginHandler;
155import org.openstreetmap.josm.plugins.PluginInformation;
156import org.openstreetmap.josm.spi.preferences.Config;
157import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
158import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
159import org.openstreetmap.josm.tools.FontsManager;
160import org.openstreetmap.josm.tools.GBC;
161import org.openstreetmap.josm.tools.I18n;
162import org.openstreetmap.josm.tools.ImageProvider;
163import org.openstreetmap.josm.tools.JosmRuntimeException;
164import org.openstreetmap.josm.tools.Logging;
165import org.openstreetmap.josm.tools.OpenBrowser;
166import org.openstreetmap.josm.tools.OsmUrlToBounds;
167import org.openstreetmap.josm.tools.OverpassTurboQueryWizard;
168import org.openstreetmap.josm.tools.PlatformHook.NativeOsCallback;
169import org.openstreetmap.josm.tools.PlatformHookWindows;
170import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
171import org.openstreetmap.josm.tools.Shortcut;
172import org.openstreetmap.josm.tools.Territories;
173import org.openstreetmap.josm.tools.Utils;
174import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
175import org.openstreetmap.josm.tools.bugreport.BugReportQueue;
176import org.openstreetmap.josm.tools.bugreport.BugReportSender;
177import org.xml.sax.SAXException;
178
179/**
180 * Main window class application.
181 *
182 * @author imi
183 */
184public class MainApplication extends Main {
185
186 /**
187 * Command-line arguments used to run the application.
188 */
189 private static volatile List<String> commandLineArgs;
190
191 /**
192 * The main menu bar at top of screen.
193 */
194 static MainMenu menu;
195
196 /**
197 * The main panel, required to be static for {@link MapFrameListener} handling.
198 */
199 static MainPanel mainPanel;
200
201 /**
202 * The private content pane of {@link MainFrame}, required to be static for shortcut handling.
203 */
204 static JComponent contentPanePrivate;
205
206 /**
207 * The MapFrame.
208 */
209 static MapFrame map;
210
211 /**
212 * The toolbar preference control to register new actions.
213 */
214 static volatile ToolbarPreferences toolbar;
215
216 private final MainFrame mainFrame;
217
218 /**
219 * The worker thread slave. This is for executing all long and intensive
220 * calculations. The executed runnables are guaranteed to be executed separately and sequential.
221 * @since 12634 (as a replacement to {@code Main.worker})
222 */
223 public static final ExecutorService worker = new ProgressMonitorExecutor("main-worker-%d", Thread.NORM_PRIORITY);
224
225 /**
226 * Provides access to the layers displayed in the main view.
227 */
228 private static final MainLayerManager layerManager = new MainLayerManager();
229
230 /**
231 * The commands undo/redo handler.
232 * @since 12641
233 */
234 public static volatile UndoRedoHandler undoRedo;
235
236 private static final LayerChangeListener undoRedoCleaner = new LayerChangeListener() {
237 @Override
238 public void layerRemoving(LayerRemoveEvent e) {
239 Layer layer = e.getRemovedLayer();
240 if (layer instanceof OsmDataLayer) {
241 undoRedo.clean(((OsmDataLayer) layer).data);
242 }
243 }
244
245 @Override
246 public void layerOrderChanged(LayerOrderChangeEvent e) {
247 // Do nothing
248 }
249
250 @Override
251 public void layerAdded(LayerAddEvent e) {
252 // Do nothing
253 }
254 };
255
256 private static final List<CLIModule> cliModules = new ArrayList<>();
257
258 /**
259 * Default JOSM command line interface.
260 * <p>
261 * Runs JOSM and performs some action, depending on the options and positional
262 * arguments.
263 */
264 public static final CLIModule JOSM_CLI_MODULE = new CLIModule() {
265 @Override
266 public String getActionKeyword() {
267 return "runjosm";
268 }
269
270 @Override
271 public void processArguments(String[] argArray) {
272 ProgramArguments args = null;
273 // construct argument table
274 try {
275 args = new ProgramArguments(argArray);
276 } catch (IllegalArgumentException e) {
277 System.err.println(e.getMessage());
278 System.exit(1);
279 }
280 mainJOSM(args);
281 }
282 };
283
284 /**
285 * Listener that sets the enabled state of undo/redo menu entries.
286 */
287 private final CommandQueueListener redoUndoListener = (queueSize, redoSize) -> {
288 menu.undo.setEnabled(queueSize > 0);
289 menu.redo.setEnabled(redoSize > 0);
290 };
291
292 /**
293 * Source of NTV2 shift files: Download from JOSM website.
294 * @since 12777
295 */
296 public static final NTV2GridShiftFileSource JOSM_WEBSITE_NTV2_SOURCE = gridFileName -> {
297 String location = Main.getJOSMWebsite() + "/proj/" + gridFileName;
298 // Try to load grid file
299 CachedFile cf = new CachedFile(location);
300 try {
301 return cf.getInputStream();
302 } catch (IOException ex) {
303 Logging.warn(ex);
304 return null;
305 }
306 };
307
308 static {
309 registerCLIModule(JOSM_CLI_MODULE);
310 registerCLIModule(ProjectionCLI.INSTANCE);
311 registerCLIModule(RenderingCLI.INSTANCE);
312 }
313
314 /**
315 * Register a command line interface module.
316 * @param module the module
317 * @since 12886
318 */
319 public static void registerCLIModule(CLIModule module) {
320 cliModules.add(module);
321 }
322
323 /**
324 * Constructs a new {@code MainApplication} without a window.
325 */
326 public MainApplication() {
327 this(null);
328 }
329
330 /**
331 * Constructs a main frame, ready sized and operating. Does not display the frame.
332 * @param mainFrame The main JFrame of the application
333 * @since 10340
334 */
335 public MainApplication(MainFrame mainFrame) {
336 this.mainFrame = mainFrame;
337 undoRedo = super.undoRedo;
338 getLayerManager().addLayerChangeListener(undoRedoCleaner);
339 }
340
341 /**
342 * Asks user to update its version of Java.
343 * @param updVersion target update version
344 * @param url download URL
345 * @param major true for a migration towards a major version of Java (8:9), false otherwise
346 * @param eolDate the EOL/expiration date
347 * @since 12270
348 */
349 public static void askUpdateJava(String updVersion, String url, String eolDate, boolean major) {
350 ExtendedDialog ed = new ExtendedDialog(
351 Main.parent,
352 tr("Outdated Java version"),
353 tr("OK"), tr("Update Java"), tr("Cancel"));
354 // Check if the dialog has not already been permanently hidden by user
355 if (!ed.toggleEnable("askUpdateJava"+updVersion).toggleCheckState()) {
356 ed.setButtonIcons("ok", "java", "cancel").setCancelButton(3);
357 ed.setMinimumSize(new Dimension(480, 300));
358 ed.setIcon(JOptionPane.WARNING_MESSAGE);
359 StringBuilder content = new StringBuilder(tr("You are running version {0} of Java.",
360 "<b>"+getSystemProperty("java.version")+"</b>")).append("<br><br>");
361 if ("Sun Microsystems Inc.".equals(getSystemProperty("java.vendor")) && !platform.isOpenJDK()) {
362 content.append("<b>").append(tr("This version is no longer supported by {0} since {1} and is not recommended for use.",
363 "Oracle", eolDate)).append("</b><br><br>");
364 }
365 content.append("<b>")
366 .append(major ?
367 tr("JOSM will soon stop working with this version; we highly recommend you to update to Java {0}.", updVersion) :
368 tr("You may face critical Java bugs; we highly recommend you to update to Java {0}.", updVersion))
369 .append("</b><br><br>")
370 .append(tr("Would you like to update now ?"));
371 ed.setContent(content.toString());
372
373 if (ed.showDialog().getValue() == 2) {
374 try {
375 platform.openUrl(url);
376 } catch (IOException e) {
377 Logging.warn(e);
378 }
379 }
380 }
381 }
382
383 @Override
384 protected List<InitializationTask> beforeInitializationTasks() {
385 return Arrays.asList(
386 new InitializationTask(tr("Starting file watcher"), fileWatcher::start),
387 new InitializationTask(tr("Executing platform startup hook"), () -> platform.startupHook(MainApplication::askUpdateJava)),
388 new InitializationTask(tr("Building main menu"), this::initializeMainWindow),
389 new InitializationTask(tr("Updating user interface"), () -> {
390 undoRedo.addCommandQueueListener(redoUndoListener);
391 // creating toolbar
392 GuiHelper.runInEDTAndWait(() -> contentPanePrivate.add(toolbar.control, BorderLayout.NORTH));
393 // help shortcut
394 registerActionShortcut(menu.help, Shortcut.registerShortcut("system:help", tr("Help"),
395 KeyEvent.VK_F1, Shortcut.DIRECT));
396 }),
397 // This needs to be done before RightAndLefthandTraffic::initialize is called
398 new InitializationTask(tr("Initializing internal boundaries data"), Territories::initialize)
399 );
400 }
401
402 @Override
403 protected Collection<InitializationTask> parallelInitializationTasks() {
404 return Arrays.asList(
405 new InitializationTask(tr("Initializing OSM API"), () -> {
406 OsmApi.addOsmApiInitializationListener(api -> {
407 // This checks if there are any layers currently displayed that are now on the blacklist, and removes them.
408 // This is a rare situation - probably only occurs if the user changes the API URL in the preferences menu.
409 // Otherwise they would not have been able to load the layers in the first place because they would have been disabled
410 if (isDisplayingMapView()) {
411 for (Layer l : getLayerManager().getLayersOfType(ImageryLayer.class)) {
412 if (((ImageryLayer) l).getInfo().isBlacklisted()) {
413 Logging.info(tr("Removed layer {0} because it is not allowed by the configured API.", l.getName()));
414 getLayerManager().removeLayer(l);
415 }
416 }
417 }
418 });
419 // We try to establish an API connection early, so that any API
420 // capabilities are already known to the editor instance. However
421 // if it goes wrong that's not critical at this stage.
422 try {
423 OsmApi.getOsmApi().initialize(null, true);
424 } catch (OsmTransferCanceledException | OsmApiInitializationException | SecurityException e) {
425 Logging.warn(Logging.getErrorMessage(Utils.getRootCause(e)));
426 }
427 }),
428 new InitializationTask(tr("Initializing internal traffic data"), RightAndLefthandTraffic::initialize),
429 new InitializationTask(tr("Initializing validator"), OsmValidator::initialize),
430 new InitializationTask(tr("Initializing presets"), TaggingPresets::initialize),
431 new InitializationTask(tr("Initializing map styles"), MapPaintPreference::initialize),
432 new InitializationTask(tr("Loading imagery preferences"), ImageryPreference::initialize)
433 );
434 }
435
436 @Override
437 protected List<Callable<?>> asynchronousCallableTasks() {
438 return Arrays.asList(
439 OverpassTurboQueryWizard::getInstance
440 );
441 }
442
443 @Override
444 protected List<Runnable> asynchronousRunnableTasks() {
445 return Arrays.asList(
446 TMSLayer::getCache,
447 OsmValidator::initializeTests
448 );
449 }
450
451 @Override
452 protected List<InitializationTask> afterInitializationTasks() {
453 return Arrays.asList(
454 new InitializationTask(tr("Updating user interface"), () -> GuiHelper.runInEDTAndWait(() -> {
455 // hooks for the jmapviewer component
456 FeatureAdapter.registerBrowserAdapter(OpenBrowser::displayUrl);
457 FeatureAdapter.registerTranslationAdapter(I18n::tr);
458 FeatureAdapter.registerLoggingAdapter(name -> Logging.getLogger());
459 // UI update
460 toolbar.refreshToolbarControl();
461 toolbar.control.updateUI();
462 contentPanePrivate.updateUI();
463 }))
464 );
465 }
466
467 /**
468 * Called once at startup to initialize the main window content.
469 * Should set {@link #menu} and {@link #mainPanel}
470 */
471 protected void initializeMainWindow() {
472 if (mainFrame != null) {
473 mainPanel = mainFrame.getPanel();
474 mainFrame.initialize();
475 menu = mainFrame.getMenu();
476 } else {
477 // required for running some tests.
478 mainPanel = new MainPanel(layerManager);
479 menu = new MainMenu();
480 }
481 mainPanel.addMapFrameListener((o, n) -> redoUndoListener.commandChanged(0, 0));
482 mainPanel.reAddListeners();
483 }
484
485 @Override
486 protected void shutdown() {
487 if (!GraphicsEnvironment.isHeadless()) {
488 try {
489 worker.shutdown();
490 } catch (SecurityException e) {
491 Logging.log(Logging.LEVEL_ERROR, "Unable to shutdown worker", e);
492 }
493 JCSCacheManager.shutdown();
494 }
495 if (mainFrame != null) {
496 mainFrame.storeState();
497 }
498 if (map != null) {
499 map.rememberToggleDialogWidth();
500 }
501 // Remove all layers because somebody may rely on layerRemoved events (like AutosaveTask)
502 layerManager.resetState();
503 super.shutdown();
504 if (!GraphicsEnvironment.isHeadless()) {
505 try {
506 worker.shutdownNow();
507 } catch (SecurityException e) {
508 Logging.log(Logging.LEVEL_ERROR, "Unable to shutdown worker", e);
509 }
510 }
511 }
512
513 @Override
514 protected Bounds getRealBounds() {
515 return isDisplayingMapView() ? map.mapView.getRealBounds() : null;
516 }
517
518 @Override
519 protected void restoreOldBounds(Bounds oldBounds) {
520 if (isDisplayingMapView()) {
521 map.mapView.zoomTo(oldBounds);
522 }
523 }
524
525 /**
526 * Replies the current selected primitives, from a end-user point of view.
527 * It is not always technically the same collection of primitives than {@link DataSet#getSelected()}.
528 * Indeed, if the user is currently in drawing mode, only the way currently being drawn is returned,
529 * see {@link DrawAction#getInProgressSelection()}.
530 *
531 * @return The current selected primitives, from a end-user point of view. Can be {@code null}.
532 * @since 6546
533 */
534 @Override
535 public Collection<OsmPrimitive> getInProgressSelection() {
536 if (map != null && map.mapMode instanceof DrawAction) {
537 return ((DrawAction) map.mapMode).getInProgressSelection();
538 } else {
539 DataSet ds = layerManager.getActiveDataSet();
540 if (ds == null) return null;
541 return ds.getSelected();
542 }
543 }
544
545 @Override
546 public DataSet getEditDataSet() {
547 return getLayerManager().getEditDataSet();
548 }
549
550 @Override
551 public DataSet getActiveDataSet() {
552 return getLayerManager().getActiveDataSet();
553 }
554
555 @Override
556 public void setActiveDataSet(DataSet ds) {
557 Optional<OsmDataLayer> layer = getLayerManager().getLayersOfType(OsmDataLayer.class).stream()
558 .filter(l -> l.data.equals(ds)).findFirst();
559 if (layer.isPresent()) {
560 getLayerManager().setActiveLayer(layer.get());
561 }
562 }
563
564 @Override
565 public boolean containsDataSet(DataSet ds) {
566 return getLayerManager().getLayersOfType(OsmDataLayer.class).stream().anyMatch(l -> l.data.equals(ds));
567 }
568
569 /**
570 * Returns the command-line arguments used to run the application.
571 * @return the command-line arguments used to run the application
572 * @since 11650
573 */
574 public static List<String> getCommandLineArgs() {
575 return Collections.unmodifiableList(commandLineArgs);
576 }
577
578 /**
579 * Returns the main layer manager that is used by the map view.
580 * @return The layer manager. The value returned will never change.
581 * @since 12636 (as a replacement to {@code Main.getLayerManager()})
582 */
583 public static MainLayerManager getLayerManager() {
584 return layerManager;
585 }
586
587 /**
588 * Returns the MapFrame.
589 * <p>
590 * There should be no need to access this to access any map data. Use {@link #layerManager} instead.
591 * @return the MapFrame
592 * @see MainPanel
593 * @since 12630 (as a replacement to {@code Main.map})
594 */
595 public static MapFrame getMap() {
596 return map;
597 }
598
599 /**
600 * Returns the main panel.
601 * @return the main panel
602 * @since 12642 (as a replacement to {@code Main.main.panel})
603 */
604 public static MainPanel getMainPanel() {
605 return mainPanel;
606 }
607
608 /**
609 * Returns the main menu, at top of screen.
610 * @return the main menu
611 * @since 12643 (as a replacement to {@code MainApplication.getMenu()})
612 */
613 public static MainMenu getMenu() {
614 return menu;
615 }
616
617 /**
618 * Returns the toolbar preference control to register new actions.
619 * @return the toolbar preference control
620 * @since 12637 (as a replacement to {@code Main.toolbar})
621 */
622 public static ToolbarPreferences getToolbar() {
623 return toolbar;
624 }
625
626 /**
627 * Replies true if JOSM currently displays a map view. False, if it doesn't, i.e. if
628 * it only shows the MOTD panel.
629 * <p>
630 * You do not need this when accessing the layer manager. The layer manager will be empty if no map view is shown.
631 *
632 * @return <code>true</code> if JOSM currently displays a map view
633 * @since 12630 (as a replacement to {@code Main.isDisplayingMapView()})
634 */
635 public static boolean isDisplayingMapView() {
636 return map != null && map.mapView != null;
637 }
638
639 /**
640 * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
641 * If there are some unsaved data layers, asks first for user confirmation.
642 * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
643 * @param exitCode The return code
644 * @param reason the reason for exiting
645 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation.
646 * @since 12636 (specialized version of {@link Main#exitJosm})
647 */
648 public static boolean exitJosm(boolean exit, int exitCode, SaveLayersDialog.Reason reason) {
649 final boolean proceed = Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() ->
650 SaveLayersDialog.saveUnsavedModifications(layerManager.getLayers(),
651 reason != null ? reason : SaveLayersDialog.Reason.EXIT)));
652 if (proceed) {
653 return Main.exitJosm(exit, exitCode);
654 }
655 return false;
656 }
657
658 public static void redirectToMainContentPane(JComponent source) {
659 RedirectInputMap.redirect(source, contentPanePrivate);
660 }
661
662 /**
663 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes.
664 * <p>
665 * It will fire an initial mapFrameInitialized event when the MapFrame is present.
666 * Otherwise will only fire when the MapFrame is created or destroyed.
667 * @param listener The MapFrameListener
668 * @return {@code true} if the listeners collection changed as a result of the call
669 * @see #addMapFrameListener
670 * @since 12639 (as a replacement to {@code Main.addAndFireMapFrameListener})
671 */
672 public static boolean addAndFireMapFrameListener(MapFrameListener listener) {
673 return mainPanel != null && mainPanel.addAndFireMapFrameListener(listener);
674 }
675
676 /**
677 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
678 * @param listener The MapFrameListener
679 * @return {@code true} if the listeners collection changed as a result of the call
680 * @see #addAndFireMapFrameListener
681 * @since 12639 (as a replacement to {@code Main.addMapFrameListener})
682 */
683 public static boolean addMapFrameListener(MapFrameListener listener) {
684 return mainPanel != null && mainPanel.addMapFrameListener(listener);
685 }
686
687 /**
688 * Unregisters the given {@code MapFrameListener} from MapFrame changes
689 * @param listener The MapFrameListener
690 * @return {@code true} if the listeners collection changed as a result of the call
691 * @since 12639 (as a replacement to {@code Main.removeMapFrameListener})
692 */
693 public static boolean removeMapFrameListener(MapFrameListener listener) {
694 return mainPanel != null && mainPanel.removeMapFrameListener(listener);
695 }
696
697 /**
698 * Registers a {@code JosmAction} and its shortcut.
699 * @param action action defining its own shortcut
700 * @since 12639 (as a replacement to {@code Main.registerActionShortcut})
701 */
702 public static void registerActionShortcut(JosmAction action) {
703 registerActionShortcut(action, action.getShortcut());
704 }
705
706 /**
707 * Registers an action and its shortcut.
708 * @param action action to register
709 * @param shortcut shortcut to associate to {@code action}
710 * @since 12639 (as a replacement to {@code Main.registerActionShortcut})
711 */
712 public static void registerActionShortcut(Action action, Shortcut shortcut) {
713 KeyStroke keyStroke = shortcut.getKeyStroke();
714 if (keyStroke == null)
715 return;
716
717 InputMap inputMap = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
718 Object existing = inputMap.get(keyStroke);
719 if (existing != null && !existing.equals(action)) {
720 Logging.info(String.format("Keystroke %s is already assigned to %s, will be overridden by %s", keyStroke, existing, action));
721 }
722 inputMap.put(keyStroke, action);
723
724 contentPanePrivate.getActionMap().put(action, action);
725 }
726
727 /**
728 * Unregisters a shortcut.
729 * @param shortcut shortcut to unregister
730 * @since 12639 (as a replacement to {@code Main.unregisterShortcut})
731 */
732 public static void unregisterShortcut(Shortcut shortcut) {
733 contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(shortcut.getKeyStroke());
734 }
735
736 /**
737 * Unregisters a {@code JosmAction} and its shortcut.
738 * @param action action to unregister
739 * @since 12639 (as a replacement to {@code Main.unregisterActionShortcut})
740 */
741 public static void unregisterActionShortcut(JosmAction action) {
742 unregisterActionShortcut(action, action.getShortcut());
743 }
744
745 /**
746 * Unregisters an action and its shortcut.
747 * @param action action to unregister
748 * @param shortcut shortcut to unregister
749 * @since 12639 (as a replacement to {@code Main.unregisterActionShortcut})
750 */
751 public static void unregisterActionShortcut(Action action, Shortcut shortcut) {
752 unregisterShortcut(shortcut);
753 contentPanePrivate.getActionMap().remove(action);
754 }
755
756 /**
757 * Replies the registered action for the given shortcut
758 * @param shortcut The shortcut to look for
759 * @return the registered action for the given shortcut
760 * @since 12639 (as a replacement to {@code Main.getRegisteredActionShortcut})
761 */
762 public static Action getRegisteredActionShortcut(Shortcut shortcut) {
763 KeyStroke keyStroke = shortcut.getKeyStroke();
764 if (keyStroke == null)
765 return null;
766 Object action = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).get(keyStroke);
767 if (action instanceof Action)
768 return (Action) action;
769 return null;
770 }
771
772 /**
773 * Displays help on the console
774 * @since 2748
775 */
776 public static void showHelp() {
777 // TODO: put in a platformHook for system that have no console by default
778 System.out.println(getHelp());
779 }
780
781 static String getHelp() {
782 return tr("Java OpenStreetMap Editor")+" ["
783 +Version.getInstance().getAgentString()+"]\n\n"+
784 tr("usage")+":\n"+
785 "\tjava -jar josm.jar [<command>] <options>...\n\n"+
786 tr("commands")+":\n"+
787 "\trunjosm "+tr("launch JOSM (default, performed when no command is specified)")+'\n'+
788 "\trender "+tr("render data and save the result to an image file")+'\n'+
789 "\tproject "+tr("convert coordinates from one coordinate reference system to another")+"\n\n"+
790 tr("For details on the {0} and {1} commands, run them with the {2} option.", "render", "project", "--help")+'\n'+
791 tr("The remainder of this help page documents the {0} command.", "runjosm")+"\n\n"+
792 tr("options")+":\n"+
793 "\t--help|-h "+tr("Show this help")+'\n'+
794 "\t--geometry=widthxheight(+|-)x(+|-)y "+tr("Standard unix geometry argument")+'\n'+
795 "\t[--download=]minlat,minlon,maxlat,maxlon "+tr("Download the bounding box")+'\n'+
796 "\t[--download=]<URL> "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z)")+'\n'+
797 "\t[--download=]<filename> "+tr("Open a file (any file type that can be opened with File/Open)")+'\n'+
798 "\t--downloadgps=minlat,minlon,maxlat,maxlon "+tr("Download the bounding box as raw GPS")+'\n'+
799 "\t--downloadgps=<URL> "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z) as raw GPS")+'\n'+
800 "\t--selection=<searchstring> "+tr("Select with the given search")+'\n'+
801 "\t--[no-]maximize "+tr("Launch in maximized mode")+'\n'+
802 "\t--reset-preferences "+tr("Reset the preferences to default")+"\n\n"+
803 "\t--load-preferences=<url-to-xml> "+tr("Changes preferences according to the XML file")+"\n\n"+
804 "\t--set=<key>=<value> "+tr("Set preference key to value")+"\n\n"+
805 "\t--language=<language> "+tr("Set the language")+"\n\n"+
806 "\t--version "+tr("Displays the JOSM version and exits")+"\n\n"+
807 "\t--debug "+tr("Print debugging messages to console")+"\n\n"+
808 "\t--skip-plugins "+tr("Skip loading plugins")+"\n\n"+
809 "\t--offline=<osm_api|josm_website|all> "+tr("Disable access to the given resource(s), separated by comma")+"\n\n"+
810 tr("options provided as Java system properties")+":\n"+
811 align("\t-Djosm.dir.name=JOSM") + tr("Change the JOSM directory name") + "\n\n" +
812 align("\t-Djosm.pref=" + tr("/PATH/TO/JOSM/PREF ")) + tr("Set the preferences directory") + "\n" +
813 align("\t") + tr("Default: {0}", platform.getDefaultPrefDirectory()) + "\n\n" +
814 align("\t-Djosm.userdata=" + tr("/PATH/TO/JOSM/USERDATA")) + tr("Set the user data directory") + "\n" +
815 align("\t") + tr("Default: {0}", platform.getDefaultUserDataDirectory()) + "\n\n" +
816 align("\t-Djosm.cache=" + tr("/PATH/TO/JOSM/CACHE ")) + tr("Set the cache directory") + "\n" +
817 align("\t") + tr("Default: {0}", platform.getDefaultCacheDirectory()) + "\n\n" +
818 align("\t-Djosm.home=" + tr("/PATH/TO/JOSM/HOMEDIR ")) +
819 tr("Set the preferences+data+cache directory (cache directory will be josm.home/cache)")+"\n\n"+
820 tr("-Djosm.home has lower precedence, i.e. the specific setting overrides the general one")+"\n\n"+
821 tr("note: For some tasks, JOSM needs a lot of memory. It can be necessary to add the following\n" +
822 " Java option to specify the maximum size of allocated memory in megabytes")+":\n"+
823 "\t-Xmx...m\n\n"+
824 tr("examples")+":\n"+
825 "\tjava -jar josm.jar track1.gpx track2.gpx london.osm\n"+
826 "\tjava -jar josm.jar "+OsmUrlToBounds.getURL(43.2, 11.1, 13)+'\n'+
827 "\tjava -jar josm.jar london.osm --selection=http://www.ostertag.name/osm/OSM_errors_node-duplicate.xml\n"+
828 "\tjava -jar josm.jar 43.2,11.1,43.4,11.4\n"+
829 "\tjava -Djosm.pref=$XDG_CONFIG_HOME -Djosm.userdata=$XDG_DATA_HOME -Djosm.cache=$XDG_CACHE_HOME -jar josm.jar\n"+
830 "\tjava -Djosm.dir.name=josm_dev -jar josm.jar\n"+
831 "\tjava -Djosm.home=/home/user/.josm_dev -jar josm.jar\n"+
832 "\tjava -Xmx1024m -jar josm.jar\n\n"+
833 tr("Parameters --download, --downloadgps, and --selection are processed in this order.")+'\n'+
834 tr("Make sure you load some data if you use --selection.")+'\n';
835 }
836
837 private static String align(String str) {
838 return str + Stream.generate(() -> " ").limit(Math.max(0, 43 - str.length())).collect(Collectors.joining(""));
839 }
840
841 /**
842 * Main application Startup
843 * @param argArray Command-line arguments
844 */
845 public static void main(final String[] argArray) {
846 I18n.init();
847 commandLineArgs = Arrays.asList(Arrays.copyOf(argArray, argArray.length));
848
849 if (argArray.length > 0) {
850 String moduleStr = argArray[0];
851 for (CLIModule module : cliModules) {
852 if (Objects.equals(moduleStr, module.getActionKeyword())) {
853 String[] argArrayCdr = Arrays.copyOfRange(argArray, 1, argArray.length);
854 module.processArguments(argArrayCdr);
855 return;
856 }
857 }
858 }
859 // no module specified, use default (josm)
860 JOSM_CLI_MODULE.processArguments(argArray);
861 }
862
863 /**
864 * Main method to run the JOSM GUI.
865 * @param args program arguments
866 */
867 public static void mainJOSM(ProgramArguments args) {
868
869 if (!GraphicsEnvironment.isHeadless()) {
870 BugReportQueue.getInstance().setBugReportHandler(BugReportDialog::showFor);
871 BugReportSender.setBugReportSendingHandler(BugReportDialog.bugReportSendingHandler);
872 }
873
874 Level logLevel = args.getLogLevel();
875 Logging.setLogLevel(logLevel);
876 if (!args.showVersion() && !args.showHelp()) {
877 Logging.info(tr("Log level is at {0} ({1}, {2})", logLevel.getLocalizedName(), logLevel.getName(), logLevel.intValue()));
878 }
879
880 Optional<String> language = args.getSingle(Option.LANGUAGE);
881 I18n.set(language.orElse(null));
882
883 try {
884 Policy.setPolicy(new Policy() {
885 // Permissions for plug-ins loaded when josm is started via webstart
886 private PermissionCollection pc;
887
888 {
889 pc = new Permissions();
890 pc.add(new AllPermission());
891 }
892
893 @Override
894 public PermissionCollection getPermissions(CodeSource codesource) {
895 return pc;
896 }
897 });
898 } catch (SecurityException e) {
899 Logging.log(Logging.LEVEL_ERROR, "Unable to set permissions", e);
900 }
901
902 try {
903 Thread.setDefaultUncaughtExceptionHandler(new BugReportExceptionHandler());
904 } catch (SecurityException e) {
905 Logging.log(Logging.LEVEL_ERROR, "Unable to set uncaught exception handler", e);
906 }
907
908 // initialize the platform hook, and
909 Main.determinePlatformHook();
910 Main.platform.setNativeOsCallback(new DefaultNativeOsCallback());
911 // call the really early hook before we do anything else
912 Main.platform.preStartupHook();
913
914 Config.setPreferencesInstance(Main.pref);
915 Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance());
916
917 if (args.showVersion()) {
918 System.out.println(Version.getInstance().getAgentString());
919 return;
920 } else if (args.showHelp()) {
921 showHelp();
922 return;
923 }
924
925 boolean skipLoadingPlugins = args.hasOption(Option.SKIP_PLUGINS);
926 if (skipLoadingPlugins) {
927 Logging.info(tr("Plugin loading skipped"));
928 }
929
930 if (Logging.isLoggingEnabled(Logging.LEVEL_TRACE)) {
931 // Enable debug in OAuth signpost via system preference, but only at trace level
932 Utils.updateSystemProperty("debug", "true");
933 Logging.info(tr("Enabled detailed debug level (trace)"));
934 }
935
936 try {
937 Main.pref.init(args.hasOption(Option.RESET_PREFERENCES));
938 } catch (SecurityException e) {
939 Logging.log(Logging.LEVEL_ERROR, "Unable to initialize preferences", e);
940 }
941
942 args.getPreferencesToSet().forEach(Main.pref::put);
943
944 if (!language.isPresent()) {
945 I18n.set(Config.getPref().get("language", null));
946 }
947 updateSystemProperties();
948 Main.pref.addPreferenceChangeListener(new PreferenceChangedListener() {
949 @Override
950 public void preferenceChanged(PreferenceChangeEvent e) {
951 updateSystemProperties();
952 }
953 });
954
955 checkIPv6();
956
957 processOffline(args);
958
959 Main.platform.afterPrefStartupHook();
960
961 applyWorkarounds();
962
963 FontsManager.initialize();
964
965 GuiHelper.setupLanguageFonts();
966
967 Handler.install();
968
969 WindowGeometry geometry = WindowGeometry.mainWindow("gui.geometry",
970 args.getSingle(Option.GEOMETRY).orElse(null),
971 !args.hasOption(Option.NO_MAXIMIZE) && Config.getPref().getBoolean("gui.maximized", false));
972 final MainFrame mainFrame = new MainFrame(geometry);
973 final Container contentPane = mainFrame.getContentPane();
974 if (contentPane instanceof JComponent) {
975 contentPanePrivate = (JComponent) contentPane;
976 }
977 mainPanel = mainFrame.getPanel();
978 Main.parent = mainFrame;
979
980 if (args.hasOption(Option.LOAD_PREFERENCES)) {
981 XMLCommandProcessor config = new XMLCommandProcessor(Main.pref);
982 for (String i : args.get(Option.LOAD_PREFERENCES)) {
983 try {
984 URL url = i.contains(":/") ? new URL(i) : Paths.get(i).toUri().toURL();
985 Logging.info("Reading preferences from " + url);
986 try (InputStream is = Utils.openStream(url)) {
987 config.openAndReadXML(is);
988 }
989 } catch (IOException | InvalidPathException ex) {
990 Logging.error(ex);
991 return;
992 }
993 }
994 }
995
996 try {
997 CertificateAmendment.addMissingCertificates();
998 } catch (IOException | GeneralSecurityException ex) {
999 Logging.warn(ex);
1000 Logging.warn(Logging.getErrorMessage(Utils.getRootCause(ex)));
1001 }
1002 try {
1003 Authenticator.setDefault(DefaultAuthenticator.getInstance());
1004 } catch (SecurityException e) {
1005 Logging.log(Logging.LEVEL_ERROR, "Unable to set default authenticator", e);
1006 }
1007 DefaultProxySelector proxySelector = null;
1008 try {
1009 proxySelector = new DefaultProxySelector(ProxySelector.getDefault());
1010 } catch (SecurityException e) {
1011 Logging.log(Logging.LEVEL_ERROR, "Unable to get default proxy selector", e);
1012 }
1013 try {
1014 if (proxySelector != null) {
1015 ProxySelector.setDefault(proxySelector);
1016 }
1017 } catch (SecurityException e) {
1018 Logging.log(Logging.LEVEL_ERROR, "Unable to set default proxy selector", e);
1019 }
1020 OAuthAccessTokenHolder.getInstance().init(CredentialsManager.getInstance());
1021
1022 setupCallbacks();
1023
1024 final SplashScreen splash = GuiHelper.runInEDTAndWaitAndReturn(SplashScreen::new);
1025 final SplashScreen.SplashProgressMonitor monitor = splash.getProgressMonitor();
1026 monitor.beginTask(tr("Initializing"));
1027 GuiHelper.runInEDT(() -> splash.setVisible(Config.getPref().getBoolean("draw.splashscreen", true)));
1028 Main.setInitStatusListener(new InitStatusListener() {
1029
1030 @Override
1031 public Object updateStatus(String event) {
1032 monitor.beginTask(event);
1033 return event;
1034 }
1035
1036 @Override
1037 public void finish(Object status) {
1038 if (status instanceof String) {
1039 monitor.finishTask((String) status);
1040 }
1041 }
1042 });
1043
1044 Collection<PluginInformation> pluginsToLoad = null;
1045
1046 if (!skipLoadingPlugins) {
1047 pluginsToLoad = updateAndLoadEarlyPlugins(splash, monitor);
1048 }
1049
1050 monitor.indeterminateSubTask(tr("Setting defaults"));
1051 setupUIManager();
1052 toolbar = new ToolbarPreferences();
1053 ProjectionPreference.setProjection();
1054 setupNadGridSources();
1055 GuiHelper.translateJavaInternalMessages();
1056 preConstructorInit();
1057
1058 monitor.indeterminateSubTask(tr("Creating main GUI"));
1059 final Main main = new MainApplication(mainFrame);
1060 main.initialize();
1061
1062 if (!skipLoadingPlugins) {
1063 loadLatePlugins(splash, monitor, pluginsToLoad);
1064 }
1065
1066 // Wait for splash disappearance (fix #9714)
1067 GuiHelper.runInEDTAndWait(() -> {
1068 splash.setVisible(false);
1069 splash.dispose();
1070 mainFrame.setVisible(true);
1071 });
1072
1073 boolean maximized = Config.getPref().getBoolean("gui.maximized", false);
1074 if ((!args.hasOption(Option.NO_MAXIMIZE) && maximized) || args.hasOption(Option.MAXIMIZE)) {
1075 mainFrame.setMaximized(true);
1076 }
1077 if (menu.fullscreenToggleAction != null) {
1078 menu.fullscreenToggleAction.initial();
1079 }
1080
1081 SwingUtilities.invokeLater(new GuiFinalizationWorker(args, proxySelector));
1082
1083 if (Main.isPlatformWindows()) {
1084 try {
1085 // Check for insecure certificates to remove.
1086 // This is Windows-dependant code but it can't go to preStartupHook (need i18n)
1087 // neither startupHook (need to be called before remote control)
1088 PlatformHookWindows.removeInsecureCertificates();
1089 } catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | IOException e) {
1090 Logging.error(e);
1091 }
1092 }
1093
1094 if (RemoteControl.PROP_REMOTECONTROL_ENABLED.get()) {
1095 RemoteControl.start();
1096 }
1097
1098 if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) {
1099 MessageNotifier.start();
1100 }
1101
1102 if (Config.getPref().getBoolean("debug.edt-checker.enable", Version.getInstance().isLocalBuild())) {
1103 // Repaint manager is registered so late for a reason - there is lots of violation during startup process
1104 // but they don't seem to break anything and are difficult to fix
1105 Logging.info("Enabled EDT checker, wrongful access to gui from non EDT thread will be printed to console");
1106 RepaintManager.setCurrentManager(new CheckThreadViolationRepaintManager());
1107 }
1108 }
1109
1110 /**
1111 * Updates system properties with the current values in the preferences.
1112 */
1113 private static void updateSystemProperties() {
1114 if ("true".equals(Config.getPref().get("prefer.ipv6", "auto"))
1115 && !"true".equals(Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true"))) {
1116 // never set this to false, only true!
1117 Logging.info(tr("Try enabling IPv6 network, prefering IPv6 over IPv4 (only works on early startup)."));
1118 }
1119 Utils.updateSystemProperty("http.agent", Version.getInstance().getAgentString());
1120 Utils.updateSystemProperty("user.language", Config.getPref().get("language"));
1121 // Workaround to fix a Java bug. This ugly hack comes from Sun bug database: https://bugs.openjdk.java.net/browse/JDK-6292739
1122 // Force AWT toolkit to update its internal preferences (fix #6345).
1123 // Does not work anymore with Java 9, to remove with Java 9 migration
1124 if (Utils.getJavaVersion() < 9 && !GraphicsEnvironment.isHeadless()) {
1125 try {
1126 Field field = Toolkit.class.getDeclaredField("resources");
1127 Utils.setObjectsAccessible(field);
1128 field.set(null, ResourceBundle.getBundle("sun.awt.resources.awt"));
1129 } catch (ReflectiveOperationException | RuntimeException e) { // NOPMD
1130 // Catch RuntimeException in order to catch InaccessibleObjectException, new in Java 9
1131 Logging.log(Logging.LEVEL_WARN, null, e);
1132 }
1133 }
1134 // Possibility to disable SNI (not by default) in case of misconfigured https servers
1135 // See #9875 + http://stackoverflow.com/a/14884941/2257172
1136 // then https://josm.openstreetmap.de/ticket/12152#comment:5 for details
1137 if (Config.getPref().getBoolean("jdk.tls.disableSNIExtension", false)) {
1138 Utils.updateSystemProperty("jsse.enableSNIExtension", "false");
1139 }
1140 }
1141
1142 /**
1143 * Setup the sources for NTV2 grid shift files for projection support.
1144 * @since 12795
1145 */
1146 public static void setupNadGridSources() {
1147 NTV2GridShiftFileWrapper.registerNTV2GridShiftFileSource(
1148 NTV2GridShiftFileWrapper.NTV2_SOURCE_PRIORITY_LOCAL,
1149 NTV2Proj4DirGridShiftFileSource.getInstance());
1150 NTV2GridShiftFileWrapper.registerNTV2GridShiftFileSource(
1151 NTV2GridShiftFileWrapper.NTV2_SOURCE_PRIORITY_DOWNLOAD,
1152 JOSM_WEBSITE_NTV2_SOURCE);
1153 }
1154
1155 static void applyWorkarounds() {
1156 // Workaround for JDK-8180379: crash on Windows 10 1703 with Windows L&F and java < 8u141 / 9+172
1157 // To remove during Java 9 migration
1158 if (getSystemProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows 10") &&
1159 platform.getDefaultStyle().equals(LafPreference.LAF.get())) {
1160 try {
1161 final int currentBuild = Integer.parseInt(PlatformHookWindows.getCurrentBuild());
1162 final int javaVersion = Utils.getJavaVersion();
1163 final int javaUpdate = Utils.getJavaUpdate();
1164 final int javaBuild = Utils.getJavaBuild();
1165 // See https://technet.microsoft.com/en-us/windows/release-info.aspx
1166 if (currentBuild >= 15_063 && ((javaVersion == 8 && javaUpdate < 141)
1167 || (javaVersion == 9 && javaUpdate == 0 && javaBuild < 173))) {
1168 // Workaround from https://bugs.openjdk.java.net/browse/JDK-8179014
1169 UIManager.put("FileChooser.useSystemExtensionHiding", Boolean.FALSE);
1170 }
1171 } catch (NumberFormatException | ReflectiveOperationException | JosmRuntimeException e) {
1172 Logging.error(e);
1173 } catch (ExceptionInInitializerError e) {
1174 Logging.log(Logging.LEVEL_ERROR, null, e);
1175 }
1176 }
1177 }
1178
1179 static void setupCallbacks() {
1180 OsmConnection.setOAuthAccessTokenFetcher(OAuthAuthorizationWizard::obtainAccessToken);
1181 AbstractCredentialsAgent.setCredentialsProvider(CredentialDialog::promptCredentials);
1182 MessageNotifier.setNotifierCallback(MainApplication::notifyNewMessages);
1183 DeleteCommand.setDeletionCallback(DeleteAction.defaultDeletionCallback);
1184 SplitWayCommand.setWarningNotifier(msg -> new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show());
1185 FileWatcher.registerLoader(SourceType.MAP_PAINT_STYLE, MapPaintStyleLoader::reloadStyle);
1186 FileWatcher.registerLoader(SourceType.TAGCHECKER_RULE, MapCSSTagChecker::reloadRule);
1187 OsmUrlToBounds.setMapSizeSupplier(() -> {
1188 if (isDisplayingMapView()) {
1189 MapView mapView = getMap().mapView;
1190 return new Dimension(mapView.getWidth(), mapView.getHeight());
1191 } else {
1192 return GuiHelper.getScreenSize();
1193 }
1194 });
1195 }
1196
1197 static void setupUIManager() {
1198 String defaultlaf = platform.getDefaultStyle();
1199 String laf = LafPreference.LAF.get();
1200 try {
1201 UIManager.setLookAndFeel(laf);
1202 } catch (final NoClassDefFoundError | ClassNotFoundException e) {
1203 // Try to find look and feel in plugin classloaders
1204 Logging.trace(e);
1205 Class<?> klass = null;
1206 for (ClassLoader cl : PluginHandler.getResourceClassLoaders()) {
1207 try {
1208 klass = cl.loadClass(laf);
1209 break;
1210 } catch (ClassNotFoundException ex) {
1211 Logging.trace(ex);
1212 }
1213 }
1214 if (klass != null && LookAndFeel.class.isAssignableFrom(klass)) {
1215 try {
1216 UIManager.setLookAndFeel((LookAndFeel) klass.getConstructor().newInstance());
1217 } catch (ReflectiveOperationException ex) {
1218 Logging.log(Logging.LEVEL_WARN, "Cannot set Look and Feel: " + laf + ": "+ex.getMessage(), ex);
1219 } catch (UnsupportedLookAndFeelException ex) {
1220 Logging.info("Look and Feel not supported: " + laf);
1221 LafPreference.LAF.put(defaultlaf);
1222 Logging.trace(ex);
1223 }
1224 } else {
1225 Logging.info("Look and Feel not found: " + laf);
1226 LafPreference.LAF.put(defaultlaf);
1227 }
1228 } catch (UnsupportedLookAndFeelException e) {
1229 Logging.info("Look and Feel not supported: " + laf);
1230 LafPreference.LAF.put(defaultlaf);
1231 Logging.trace(e);
1232 } catch (InstantiationException | IllegalAccessException e) {
1233 Logging.error(e);
1234 }
1235
1236 UIManager.put("OptionPane.okIcon", ImageProvider.getIfAvailable("ok"));
1237 UIManager.put("OptionPane.yesIcon", UIManager.get("OptionPane.okIcon"));
1238 UIManager.put("OptionPane.cancelIcon", ImageProvider.getIfAvailable("cancel"));
1239 UIManager.put("OptionPane.noIcon", UIManager.get("OptionPane.cancelIcon"));
1240 // Ensures caret color is the same than text foreground color, see #12257
1241 // See http://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/synth/doc-files/componentProperties.html
1242 for (String p : Arrays.asList(
1243 "EditorPane", "FormattedTextField", "PasswordField", "TextArea", "TextField", "TextPane")) {
1244 UIManager.put(p+".caretForeground", UIManager.getColor(p+".foreground"));
1245 }
1246
1247 double menuFontFactor = Config.getPref().getDouble("gui.scale.menu.font", 1.0);
1248 if (menuFontFactor != 1.0) {
1249 for (String key : Arrays.asList(
1250 "Menu.font", "MenuItem.font", "CheckBoxMenuItem.font", "RadioButtonMenuItem.font", "MenuItem.acceleratorFont")) {
1251 Font font = UIManager.getFont(key);
1252 if (font != null) {
1253 UIManager.put(key, font.deriveFont(font.getSize2D() * (float) menuFontFactor));
1254 }
1255 }
1256 }
1257 }
1258
1259 static Collection<PluginInformation> updateAndLoadEarlyPlugins(SplashScreen splash, SplashProgressMonitor monitor) {
1260 Collection<PluginInformation> pluginsToLoad;
1261 pluginsToLoad = PluginHandler.buildListOfPluginsToLoad(splash, monitor.createSubTaskMonitor(1, false));
1262 if (!pluginsToLoad.isEmpty() && PluginHandler.checkAndConfirmPluginUpdate(splash)) {
1263 monitor.subTask(tr("Updating plugins"));
1264 pluginsToLoad = PluginHandler.updatePlugins(splash, null, monitor.createSubTaskMonitor(1, false), false);
1265 }
1266
1267 monitor.indeterminateSubTask(tr("Installing updated plugins"));
1268 try {
1269 PluginHandler.installDownloadedPlugins(pluginsToLoad, true);
1270 } catch (SecurityException e) {
1271 Logging.log(Logging.LEVEL_ERROR, "Unable to install plugins", e);
1272 }
1273
1274 monitor.indeterminateSubTask(tr("Loading early plugins"));
1275 PluginHandler.loadEarlyPlugins(splash, pluginsToLoad, monitor.createSubTaskMonitor(1, false));
1276 return pluginsToLoad;
1277 }
1278
1279 static void loadLatePlugins(SplashScreen splash, SplashProgressMonitor monitor, Collection<PluginInformation> pluginsToLoad) {
1280 monitor.indeterminateSubTask(tr("Loading plugins"));
1281 PluginHandler.loadLatePlugins(splash, pluginsToLoad, monitor.createSubTaskMonitor(1, false));
1282 GuiHelper.runInEDTAndWait(() -> toolbar.refreshToolbarControl());
1283 }
1284
1285 private static void processOffline(ProgramArguments args) {
1286 for (String offlineNames : args.get(Option.OFFLINE)) {
1287 for (String s : offlineNames.split(",")) {
1288 try {
1289 Main.setOffline(OnlineResource.valueOf(s.toUpperCase(Locale.ENGLISH)));
1290 } catch (IllegalArgumentException e) {
1291 Logging.log(Logging.LEVEL_ERROR,
1292 tr("''{0}'' is not a valid value for argument ''{1}''. Possible values are {2}, possibly delimited by commas.",
1293 s.toUpperCase(Locale.ENGLISH), Option.OFFLINE.getName(), Arrays.toString(OnlineResource.values())), e);
1294 System.exit(1);
1295 return;
1296 }
1297 }
1298 }
1299 Set<OnlineResource> offline = Main.getOfflineResources();
1300 if (!offline.isEmpty()) {
1301 Logging.warn(trn("JOSM is running in offline mode. This resource will not be available: {0}",
1302 "JOSM is running in offline mode. These resources will not be available: {0}",
1303 offline.size(), offline.size() == 1 ? offline.iterator().next() : Arrays.toString(offline.toArray())));
1304 }
1305 }
1306
1307 /**
1308 * Check if IPv6 can be safely enabled and do so. Because this cannot be done after network activation,
1309 * disabling or enabling IPV6 may only be done with next start.
1310 */
1311 private static void checkIPv6() {
1312 if ("auto".equals(Config.getPref().get("prefer.ipv6", "auto"))) {
1313 new Thread((Runnable) () -> { /* this may take some time (DNS, Connect) */
1314 boolean hasv6 = false;
1315 boolean wasv6 = Config.getPref().getBoolean("validated.ipv6", false);
1316 try {
1317 /* Use the check result from last run of the software, as after the test, value
1318 changes have no effect anymore */
1319 if (wasv6) {
1320 Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true");
1321 }
1322 for (InetAddress a : InetAddress.getAllByName("josm.openstreetmap.de")) {
1323 if (a instanceof Inet6Address) {
1324 if (a.isReachable(1000)) {
1325 /* be sure it REALLY works */
1326 SSLSocketFactory.getDefault().createSocket(a, 443).close();
1327 Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true");
1328 if (!wasv6) {
1329 Logging.info(tr("Detected useable IPv6 network, prefering IPv6 over IPv4 after next restart."));
1330 } else {
1331 Logging.info(tr("Detected useable IPv6 network, prefering IPv6 over IPv4."));
1332 }
1333 hasv6 = true;
1334 }
1335 break; /* we're done */
1336 }
1337 }
1338 } catch (IOException | SecurityException e) {
1339 Logging.debug("Exception while checking IPv6 connectivity: {0}", e);
1340 Logging.trace(e);
1341 }
1342 if (wasv6 && !hasv6) {
1343 Logging.info(tr("Detected no useable IPv6 network, prefering IPv4 over IPv6 after next restart."));
1344 Config.getPref().putBoolean("validated.ipv6", hasv6); // be sure it is stored before the restart!
1345 try {
1346 RestartAction.restartJOSM();
1347 } catch (IOException e) {
1348 Logging.error(e);
1349 }
1350 }
1351 Config.getPref().putBoolean("validated.ipv6", hasv6);
1352 }, "IPv6-checker").start();
1353 }
1354 }
1355
1356 /**
1357 * Download area specified as Bounds value.
1358 * @param rawGps Flag to download raw GPS tracks
1359 * @param b The bounds value
1360 * @return the complete download task (including post-download handler)
1361 */
1362 static List<Future<?>> downloadFromParamBounds(final boolean rawGps, Bounds b) {
1363 DownloadTask task = rawGps ? new DownloadGpsTask() : new DownloadOsmTask();
1364 // asynchronously launch the download task ...
1365 Future<?> future = task.download(true, b, null);
1366 // ... and the continuation when the download is finished (this will wait for the download to finish)
1367 return Collections.singletonList(MainApplication.worker.submit(new PostDownloadHandler(task, future)));
1368 }
1369
1370 /**
1371 * Handle command line instructions after GUI has been initialized.
1372 * @param args program arguments
1373 * @return the list of submitted tasks
1374 */
1375 static List<Future<?>> postConstructorProcessCmdLine(ProgramArguments args) {
1376 List<Future<?>> tasks = new ArrayList<>();
1377 List<File> fileList = new ArrayList<>();
1378 for (String s : args.get(Option.DOWNLOAD)) {
1379 tasks.addAll(DownloadParamType.paramType(s).download(s, fileList));
1380 }
1381 if (!fileList.isEmpty()) {
1382 tasks.add(OpenFileAction.openFiles(fileList, true));
1383 }
1384 for (String s : args.get(Option.DOWNLOADGPS)) {
1385 tasks.addAll(DownloadParamType.paramType(s).downloadGps(s));
1386 }
1387 final Collection<String> selectionArguments = args.get(Option.SELECTION);
1388 if (!selectionArguments.isEmpty()) {
1389 tasks.add(MainApplication.worker.submit(() -> {
1390 for (String s : selectionArguments) {
1391 SearchAction.search(s, SearchMode.add);
1392 }
1393 }));
1394 }
1395 return tasks;
1396 }
1397
1398 private static class GuiFinalizationWorker implements Runnable {
1399
1400 private final ProgramArguments args;
1401 private final DefaultProxySelector proxySelector;
1402
1403 GuiFinalizationWorker(ProgramArguments args, DefaultProxySelector proxySelector) {
1404 this.args = args;
1405 this.proxySelector = proxySelector;
1406 }
1407
1408 @Override
1409 public void run() {
1410
1411 // Handle proxy/network errors early to inform user he should change settings to be able to use JOSM correctly
1412 if (!handleProxyErrors()) {
1413 handleNetworkErrors();
1414 }
1415
1416 // Restore autosave layers after crash and start autosave thread
1417 handleAutosave();
1418
1419 // Handle command line instructions
1420 postConstructorProcessCmdLine(args);
1421
1422 // Show download dialog if autostart is enabled
1423 DownloadDialog.autostartIfNeeded();
1424 }
1425
1426 private static void handleAutosave() {
1427 if (AutosaveTask.PROP_AUTOSAVE_ENABLED.get()) {
1428 AutosaveTask autosaveTask = new AutosaveTask();
1429 List<File> unsavedLayerFiles = autosaveTask.getUnsavedLayersFiles();
1430 if (!unsavedLayerFiles.isEmpty()) {
1431 ExtendedDialog dialog = new ExtendedDialog(
1432 Main.parent,
1433 tr("Unsaved osm data"),
1434 tr("Restore"), tr("Cancel"), tr("Discard")
1435 );
1436 dialog.setContent(
1437 trn("JOSM found {0} unsaved osm data layer. ",
1438 "JOSM found {0} unsaved osm data layers. ", unsavedLayerFiles.size(), unsavedLayerFiles.size()) +
1439 tr("It looks like JOSM crashed last time. Would you like to restore the data?"));
1440 dialog.setButtonIcons("ok", "cancel", "dialogs/delete");
1441 int selection = dialog.showDialog().getValue();
1442 if (selection == 1) {
1443 autosaveTask.recoverUnsavedLayers();
1444 } else if (selection == 3) {
1445 autosaveTask.discardUnsavedLayers();
1446 }
1447 }
1448 try {
1449 autosaveTask.schedule();
1450 } catch (SecurityException e) {
1451 Logging.log(Logging.LEVEL_ERROR, "Unable to schedule autosave!", e);
1452 }
1453 }
1454 }
1455
1456 private static boolean handleNetworkOrProxyErrors(boolean hasErrors, String title, String message) {
1457 if (hasErrors) {
1458 ExtendedDialog ed = new ExtendedDialog(
1459 Main.parent, title,
1460 tr("Change proxy settings"), tr("Cancel"));
1461 ed.setButtonIcons("dialogs/settings", "cancel").setCancelButton(2);
1462 ed.setMinimumSize(new Dimension(460, 260));
1463 ed.setIcon(JOptionPane.WARNING_MESSAGE);
1464 ed.setContent(message);
1465
1466 if (ed.showDialog().getValue() == 1) {
1467 PreferencesAction.forPreferenceSubTab(null, null, ProxyPreference.class).run();
1468 }
1469 }
1470 return hasErrors;
1471 }
1472
1473 private boolean handleProxyErrors() {
1474 return proxySelector != null &&
1475 handleNetworkOrProxyErrors(proxySelector.hasErrors(), tr("Proxy errors occurred"),
1476 tr("JOSM tried to access the following resources:<br>" +
1477 "{0}" +
1478 "but <b>failed</b> to do so, because of the following proxy errors:<br>" +
1479 "{1}" +
1480 "Would you like to change your proxy settings now?",
1481 Utils.joinAsHtmlUnorderedList(proxySelector.getErrorResources()),
1482 Utils.joinAsHtmlUnorderedList(proxySelector.getErrorMessages())
1483 ));
1484 }
1485
1486 private static boolean handleNetworkErrors() {
1487 Map<String, Throwable> networkErrors = Main.getNetworkErrors();
1488 boolean condition = !networkErrors.isEmpty();
1489 if (condition) {
1490 Set<String> errors = new TreeSet<>();
1491 for (Throwable t : networkErrors.values()) {
1492 errors.add(t.toString());
1493 }
1494 return handleNetworkOrProxyErrors(condition, tr("Network errors occurred"),
1495 tr("JOSM tried to access the following resources:<br>" +
1496 "{0}" +
1497 "but <b>failed</b> to do so, because of the following network errors:<br>" +
1498 "{1}" +
1499 "It may be due to a missing proxy configuration.<br>" +
1500 "Would you like to change your proxy settings now?",
1501 Utils.joinAsHtmlUnorderedList(networkErrors.keySet()),
1502 Utils.joinAsHtmlUnorderedList(errors)
1503 ));
1504 }
1505 return false;
1506 }
1507 }
1508
1509 private static class DefaultNativeOsCallback implements NativeOsCallback {
1510 @Override
1511 public void openFiles(List<File> files) {
1512 Executors.newSingleThreadExecutor(Utils.newThreadFactory("openFiles-%d", Thread.NORM_PRIORITY)).submit(
1513 new OpenFileTask(files, null) {
1514 @Override
1515 protected void realRun() throws SAXException, IOException, OsmTransferException {
1516 // Wait for JOSM startup is advanced enough to load a file
1517 while (Main.parent == null || !Main.parent.isVisible()) {
1518 try {
1519 Thread.sleep(25);
1520 } catch (InterruptedException e) {
1521 Logging.warn(e);
1522 Thread.currentThread().interrupt();
1523 }
1524 }
1525 super.realRun();
1526 }
1527 });
1528 }
1529
1530 @Override
1531 public boolean handleQuitRequest() {
1532 return MainApplication.exitJosm(false, 0, null);
1533 }
1534
1535 @Override
1536 public void handleAbout() {
1537 MainApplication.getMenu().about.actionPerformed(null);
1538 }
1539
1540 @Override
1541 public void handlePreferences() {
1542 MainApplication.getMenu().preferences.actionPerformed(null);
1543 }
1544 }
1545
1546 static void notifyNewMessages(UserInfo userInfo) {
1547 GuiHelper.runInEDT(() -> {
1548 JPanel panel = new JPanel(new GridBagLayout());
1549 panel.add(new JLabel(trn("You have {0} unread message.", "You have {0} unread messages.",
1550 userInfo.getUnreadMessages(), userInfo.getUnreadMessages())),
1551 GBC.eol());
1552 panel.add(new UrlLabel(Main.getBaseUserUrl() + '/' + userInfo.getDisplayName() + "/inbox",
1553 tr("Click here to see your inbox.")), GBC.eol());
1554 panel.setOpaque(false);
1555 new Notification().setContent(panel)
1556 .setIcon(JOptionPane.INFORMATION_MESSAGE)
1557 .setDuration(Notification.TIME_LONG)
1558 .show();
1559 });
1560 }
1561}
Note: See TracBrowser for help on using the repository browser.