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

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

see #11000 - Remote control: allow to specify layer_name for import

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