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

Last change on this file since 18598 was 18598, checked in by taylor.smock, 18 months ago

FlatLaf uses properties files which occasionally have breaking changes

In the event that a properties file is misconfigured, FlatLaf will throw
an IllegalArgumentException, which causes JOSM to exit, which is not ideal,
since recovering is not intuitive.

This will roll back the LAF to the platform default, so people can start JOSM.
Which isn't ideal, but is better than not starting indefinitely.

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