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

Last change on this file since 12841 was 12841, checked in by bastiK, 5 weeks ago

see #15229 - fix deprecations caused by [12840]

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