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

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

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

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