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

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

see #15229 - see #15182 - remove GUI stuff from MessageNotifier

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