// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Component; import java.awt.GraphicsEnvironment; import java.io.IOException; import java.lang.ref.WeakReference; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import javax.swing.Action; import org.openstreetmap.josm.actions.JosmAction; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.Preferences; import org.openstreetmap.josm.data.UndoRedoHandler; import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager; import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat; import org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.projection.Projection; import org.openstreetmap.josm.data.projection.ProjectionChangeListener; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.MainMenu; import org.openstreetmap.josm.gui.MainPanel; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.gui.MapFrameListener; import org.openstreetmap.josm.gui.layer.MainLayerManager; import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; import org.openstreetmap.josm.io.FileWatcher; import org.openstreetmap.josm.io.OnlineResource; import org.openstreetmap.josm.io.OsmApi; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.JosmRuntimeException; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.Platform; import org.openstreetmap.josm.tools.PlatformHook; import org.openstreetmap.josm.tools.PlatformHookOsx; import org.openstreetmap.josm.tools.PlatformHookWindows; import org.openstreetmap.josm.tools.Shortcut; import org.openstreetmap.josm.tools.Utils; import org.openstreetmap.josm.tools.bugreport.BugReport; /** * Abstract class holding various static global variables and methods used in large parts of JOSM application. * @since 98 */ public abstract class Main { /** * The JOSM website URL. * @since 6897 (was public from 6143 to 6896) */ private static final String JOSM_WEBSITE = "https://josm.openstreetmap.de"; /** * The OSM website URL. * @since 6897 (was public from 6453 to 6896) */ private static final String OSM_WEBSITE = "https://www.openstreetmap.org"; /** * Replies true if JOSM currently displays a map view. False, if it doesn't, i.e. if * it only shows the MOTD panel. *
* You do not need this when accessing the layer manager. The layer manager will be empty if no map view is shown.
*
* @return true
if JOSM currently displays a map view
* @deprecated use {@link org.openstreetmap.josm.gui.MainApplication#isDisplayingMapView()}
*/
@Deprecated
public static boolean isDisplayingMapView() {
return MainApplication.isDisplayingMapView();
}
/**
* Global parent component for all dialogs and message boxes
*/
public static Component parent;
/**
* Global application.
*/
public static volatile Main main;
/**
* The worker thread slave. This is for executing all long and intensive
* calculations. The executed runnables are guaranteed to be executed separately and sequential.
* @deprecated use {@link MainApplication#worker} instead
*/
@Deprecated
public static ExecutorService worker;
/**
* Global application preferences
*/
public static final Preferences pref = new Preferences();
/**
* The MapFrame.
*
* There should be no need to access this to access any map data. Use {@link MainApplication#getLayerManager} instead.
*
* @deprecated Use {@link MainApplication#getMap()} instead
* @see MainPanel
*/
@Deprecated
public static MapFrame map;
/**
* The toolbar preference control to register new actions.
* @deprecated Use {@link MainApplication#getToolbar} instead
*/
@Deprecated
public static volatile ToolbarPreferences toolbar;
/**
* The commands undo/redo handler.
*/
public final UndoRedoHandler undoRedo = new UndoRedoHandler();
/**
* The main menu bar at top of screen.
* @deprecated Use {@link MainApplication#getMenu} instead
*/
@Deprecated
public MainMenu menu;
/**
* The main panel.
* @deprecated Use {@link MainApplication#getMainPanel} instead
* @since 12125
*/
@Deprecated
public MainPanel panel;
/**
* The file watcher service.
*/
public static final FileWatcher fileWatcher = new FileWatcher();
private static final Map
* It will fire an initial mapFrameInitialized event when the MapFrame is present.
* Otherwise will only fire when the MapFrame is created or destroyed.
* @param listener The MapFrameListener
* @return {@code true} if the listeners collection changed as a result of the call
* @see #addMapFrameListener
* @deprecated use {@link MainApplication#addAndFireMapFrameListener} instead
* @since 11904
*/
@Deprecated
public static boolean addAndFireMapFrameListener(MapFrameListener listener) {
return MainApplication.addAndFireMapFrameListener(listener);
}
/**
* Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
* @param listener The MapFrameListener
* @return {@code true} if the listeners collection changed as a result of the call
* @see #addAndFireMapFrameListener
* @deprecated use {@link MainApplication#addMapFrameListener} instead
* @since 5957
*/
@Deprecated
public static boolean addMapFrameListener(MapFrameListener listener) {
return MainApplication.addMapFrameListener(listener);
}
/**
* Unregisters the given {@code MapFrameListener} from MapFrame changes
* @param listener The MapFrameListener
* @return {@code true} if the listeners collection changed as a result of the call
* @deprecated use {@link MainApplication#removeMapFrameListener} instead
* @since 5957
*/
@Deprecated
public static boolean removeMapFrameListener(MapFrameListener listener) {
return MainApplication.removeMapFrameListener(listener);
}
/**
* Adds a new network error that occur to give a hint about broken Internet connection.
* Do not use this method for errors known for sure thrown because of a bad proxy configuration.
*
* @param url The accessed URL that caused the error
* @param t The network error
* @return The previous error associated to the given resource, if any. Can be {@code null}
* @since 6642
*/
public static Throwable addNetworkError(URL url, Throwable t) {
if (url != null && t != null) {
Throwable old = addNetworkError(url.toExternalForm(), t);
if (old != null) {
Logging.warn("Already here "+old);
}
return old;
}
return null;
}
/**
* Adds a new network error that occur to give a hint about broken Internet connection.
* Do not use this method for errors known for sure thrown because of a bad proxy configuration.
*
* @param url The accessed URL that caused the error
* @param t The network error
* @return The previous error associated to the given resource, if any. Can be {@code null}
* @since 6642
*/
public static Throwable addNetworkError(String url, Throwable t) {
if (url != null && t != null) {
return NETWORK_ERRORS.put(url, t);
}
return null;
}
/**
* Returns the network errors that occured until now.
* @return the network errors that occured until now, indexed by URL
* @since 6639
*/
public static Mapnull
.
* @since 12691
*/
public abstract DataSet getEditDataSet();
/**
* Sets the active data set.
* @param ds New edit data set, or null
* @since 12718
*/
public abstract void setEditDataSet(DataSet ds);
/**
* Determines if the list of data sets managed by JOSM contains {@code ds}.
* @param ds the data set to look for
* @return {@code true} if the list of data sets managed by JOSM contains {@code ds}
* @since 12718
*/
public abstract boolean containsDataSet(DataSet ds);
/**
* Registers a {@code JosmAction} and its shortcut.
* @param action action defining its own shortcut
* @deprecated use {@link MainApplication#registerActionShortcut(JosmAction)} instead
*/
@Deprecated
public static void registerActionShortcut(JosmAction action) {
MainApplication.registerActionShortcut(action);
}
/**
* Registers an action and its shortcut.
* @param action action to register
* @param shortcut shortcut to associate to {@code action}
* @deprecated use {@link MainApplication#registerActionShortcut(Action, Shortcut)} instead
*/
@Deprecated
public static void registerActionShortcut(Action action, Shortcut shortcut) {
MainApplication.registerActionShortcut(action, shortcut);
}
/**
* Unregisters a shortcut.
* @param shortcut shortcut to unregister
* @deprecated use {@link MainApplication#unregisterShortcut(Shortcut)} instead
*/
@Deprecated
public static void unregisterShortcut(Shortcut shortcut) {
MainApplication.unregisterShortcut(shortcut);
}
/**
* Unregisters a {@code JosmAction} and its shortcut.
* @param action action to unregister
* @deprecated use {@link MainApplication#unregisterActionShortcut(JosmAction)} instead
*/
@Deprecated
public static void unregisterActionShortcut(JosmAction action) {
MainApplication.unregisterActionShortcut(action);
}
/**
* Unregisters an action and its shortcut.
* @param action action to unregister
* @param shortcut shortcut to unregister
* @deprecated use {@link MainApplication#unregisterActionShortcut(Action, Shortcut)} instead
*/
@Deprecated
public static void unregisterActionShortcut(Action action, Shortcut shortcut) {
MainApplication.unregisterActionShortcut(action, shortcut);
}
/**
* Replies the registered action for the given shortcut
* @param shortcut The shortcut to look for
* @return the registered action for the given shortcut
* @deprecated use {@link MainApplication#getRegisteredActionShortcut(Shortcut)} instead
* @since 5696
*/
@Deprecated
public static Action getRegisteredActionShortcut(Shortcut shortcut) {
return MainApplication.getRegisteredActionShortcut(shortcut);
}
///////////////////////////////////////////////////////////////////////////
// Implementation part
///////////////////////////////////////////////////////////////////////////
/**
* Should be called before the main constructor to setup some parameter stuff
*/
public static void preConstructorInit() {
// init default coordinate format
ICoordinateFormat fmt = CoordinateFormatManager.getCoordinateFormat(Config.getPref().get("coordinates"));
if (fmt == null) {
fmt = DecimalDegreesCoordinateFormat.INSTANCE;
}
CoordinateFormatManager.setCoordinateFormat(fmt);
}
/**
* Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
* @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
* @param exitCode The return code
* @return {@code true}
* @since 12636
*/
public static boolean exitJosm(boolean exit, int exitCode) {
if (Main.main != null) {
Main.main.shutdown();
}
if (exit) {
System.exit(exitCode);
}
return true;
}
/**
* Shutdown JOSM.
*/
protected void shutdown() {
if (!GraphicsEnvironment.isHeadless()) {
ImageProvider.shutdown(false);
}
try {
pref.saveDefaults();
} catch (IOException ex) {
Logging.log(Logging.LEVEL_WARN, tr("Failed to save default preferences."), ex);
}
if (!GraphicsEnvironment.isHeadless()) {
ImageProvider.shutdown(true);
}
}
/**
* Identifies the current operating system family and initializes the platform hook accordingly.
* @since 1849
*/
public static void determinePlatformHook() {
platform = Platform.determinePlatform().accept(PlatformHook.CONSTRUCT_FROM_PLATFORM);
}
/* ----------------------------------------------------------------------------------------- */
/* projection handling - Main is a registry for a single, global projection instance */
/* */
/* TODO: For historical reasons the registry is implemented by Main. An alternative approach */
/* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class. */
/* ----------------------------------------------------------------------------------------- */
/**
* The projection method used.
* Use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
* Use {@link #setProjection(Projection)} in order to trigger a projection change event.
*/
private static volatile Projection proj;
/**
* Replies the current projection.
*
* @return the currently active projection
*/
public static Projection getProjection() {
return proj;
}
/**
* Sets the current projection
*
* @param p the projection
*/
public static void setProjection(Projection p) {
CheckParameterUtil.ensureParameterNotNull(p);
Projection oldValue = proj;
Bounds b = main != null ? main.getRealBounds() : null;
proj = p;
fireProjectionChanged(oldValue, proj, b);
}
/**
* Returns the bounds for the current projection. Used for projection events.
* @return the bounds for the current projection
* @see #restoreOldBounds
*/
protected Bounds getRealBounds() {
// To be overriden
return null;
}
/**
* Restore clean state corresponding to old bounds after a projection change event.
* @param oldBounds bounds previously returned by {@link #getRealBounds}, before the change of projection
* @see #getRealBounds
*/
protected void restoreOldBounds(Bounds oldBounds) {
// To be overriden
}
/*
* Keep WeakReferences to the listeners. This relieves clients from the burden of
* explicitly removing the listeners and allows us to transparently register every
* created dataset as projection change listener.
*/
private static final Listnull
.
*/
public static void addProjectionChangeListener(ProjectionChangeListener listener) {
if (listener == null) return;
synchronized (Main.class) {
for (WeakReferencenull
.
*/
public static void removeProjectionChangeListener(ProjectionChangeListener listener) {
if (listener == null) return;
synchronized (Main.class) {
// remove the listener - and any other listener which got garbage
// collected in the meantime
listeners.removeIf(wr -> wr.get() == null || wr.get() == listener);
}
}
/**
* Listener for window switch events.
*
* These are events, when the user activates a window of another application
* or comes back to JOSM. Window switches from one JOSM window to another
* are not reported.
*/
public interface WindowSwitchListener {
/**
* Called when the user activates a window of another application.
*/
void toOtherApplication();
/**
* Called when the user comes from a window of another application back to JOSM.
*/
void fromOtherApplication();
}
/**
* Registers a new {@code MapFrameListener} that will be notified of MapFrame changes.
*