[6380] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[626] | 2 | package org.openstreetmap.josm;
|
---|
[5670] | 3 |
|
---|
[301] | 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
| 6 | import java.awt.Component;
|
---|
[11106] | 7 | import java.awt.GraphicsEnvironment;
|
---|
[9821] | 8 | import java.io.IOException;
|
---|
[4126] | 9 | import java.lang.ref.WeakReference;
|
---|
[6471] | 10 | import java.net.URL;
|
---|
[13838] | 11 | import java.nio.file.InvalidPathException;
|
---|
[301] | 12 | import java.util.Collection;
|
---|
[11986] | 13 | import java.util.Collections;
|
---|
[10044] | 14 | import java.util.EnumSet;
|
---|
[6642] | 15 | import java.util.HashMap;
|
---|
[2025] | 16 | import java.util.List;
|
---|
[301] | 17 | import java.util.Map;
|
---|
[7083] | 18 | import java.util.Objects;
|
---|
[7434] | 19 | import java.util.Set;
|
---|
[5134] | 20 | import java.util.concurrent.Callable;
|
---|
[13316] | 21 | import java.util.concurrent.CopyOnWriteArrayList;
|
---|
[10212] | 22 | import java.util.concurrent.ExecutionException;
|
---|
[1465] | 23 | import java.util.concurrent.ExecutorService;
|
---|
[5134] | 24 | import java.util.concurrent.Executors;
|
---|
[2322] | 25 | import java.util.concurrent.Future;
|
---|
[301] | 26 |
|
---|
| 27 | import org.openstreetmap.josm.data.Bounds;
|
---|
| 28 | import org.openstreetmap.josm.data.Preferences;
|
---|
| 29 | import org.openstreetmap.josm.data.UndoRedoHandler;
|
---|
[12735] | 30 | import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager;
|
---|
| 31 | import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat;
|
---|
| 32 | import org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat;
|
---|
[301] | 33 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
[6546] | 34 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[13023] | 35 | import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
|
---|
[301] | 36 | import org.openstreetmap.josm.data.projection.Projection;
|
---|
[4126] | 37 | import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
|
---|
[7185] | 38 | import org.openstreetmap.josm.io.FileWatcher;
|
---|
[7434] | 39 | import org.openstreetmap.josm.io.OnlineResource;
|
---|
[3934] | 40 | import org.openstreetmap.josm.io.OsmApi;
|
---|
[12846] | 41 | import org.openstreetmap.josm.spi.preferences.Config;
|
---|
[4126] | 42 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
---|
[301] | 43 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
[11374] | 44 | import org.openstreetmap.josm.tools.JosmRuntimeException;
|
---|
[10899] | 45 | import org.openstreetmap.josm.tools.Logging;
|
---|
[12776] | 46 | import org.openstreetmap.josm.tools.Platform;
|
---|
[1023] | 47 | import org.openstreetmap.josm.tools.PlatformHook;
|
---|
[1465] | 48 | import org.openstreetmap.josm.tools.PlatformHookOsx;
|
---|
[1023] | 49 | import org.openstreetmap.josm.tools.PlatformHookWindows;
|
---|
[4288] | 50 | import org.openstreetmap.josm.tools.Utils;
|
---|
[11642] | 51 | import org.openstreetmap.josm.tools.bugreport.BugReport;
|
---|
[626] | 52 |
|
---|
[5829] | 53 | /**
|
---|
| 54 | * Abstract class holding various static global variables and methods used in large parts of JOSM application.
|
---|
| 55 | * @since 98
|
---|
| 56 | */
|
---|
[6883] | 57 | public abstract class Main {
|
---|
[2485] | 58 |
|
---|
[1104] | 59 | /**
|
---|
[6143] | 60 | * The JOSM website URL.
|
---|
[6897] | 61 | * @since 6897 (was public from 6143 to 6896)
|
---|
[6143] | 62 | */
|
---|
[6920] | 63 | private static final String JOSM_WEBSITE = "https://josm.openstreetmap.de";
|
---|
[6453] | 64 |
|
---|
[6143] | 65 | /**
|
---|
[6453] | 66 | * The OSM website URL.
|
---|
[6897] | 67 | * @since 6897 (was public from 6453 to 6896)
|
---|
[6453] | 68 | */
|
---|
[6920] | 69 | private static final String OSM_WEBSITE = "https://www.openstreetmap.org";
|
---|
[6453] | 70 |
|
---|
| 71 | /**
|
---|
[1104] | 72 | * Global parent component for all dialogs and message boxes
|
---|
| 73 | */
|
---|
| 74 | public static Component parent;
|
---|
[6070] | 75 |
|
---|
[1104] | 76 | /**
|
---|
| 77 | * Global application.
|
---|
| 78 | */
|
---|
[8126] | 79 | public static volatile Main main;
|
---|
[6070] | 80 |
|
---|
[1104] | 81 | /**
|
---|
| 82 | * Global application preferences
|
---|
| 83 | */
|
---|
[13023] | 84 | public static final Preferences pref = new Preferences(JosmBaseDirectories.getInstance());
|
---|
[1814] | 85 |
|
---|
[1104] | 86 | /**
|
---|
[5829] | 87 | * The commands undo/redo handler.
|
---|
| 88 | */
|
---|
[12718] | 89 | public final UndoRedoHandler undoRedo = new UndoRedoHandler();
|
---|
[626] | 90 |
|
---|
[5831] | 91 | /**
|
---|
[7185] | 92 | * The file watcher service.
|
---|
| 93 | */
|
---|
| 94 | public static final FileWatcher fileWatcher = new FileWatcher();
|
---|
| 95 |
|
---|
[11650] | 96 | private static final Map<String, Throwable> NETWORK_ERRORS = new HashMap<>();
|
---|
[6642] | 97 |
|
---|
[10044] | 98 | private static final Set<OnlineResource> OFFLINE_RESOURCES = EnumSet.noneOf(OnlineResource.class);
|
---|
[7434] | 99 |
|
---|
[1356] | 100 | /**
|
---|
[1104] | 101 | * Platform specific code goes in here.
|
---|
[12641] | 102 | * Plugins may replace it, however, some hooks will be called before any plugins have been loaded.
|
---|
[1104] | 103 | * So if you need to hook into those early ones, split your class and send the one with the early hooks
|
---|
| 104 | * to the JOSM team for inclusion.
|
---|
| 105 | */
|
---|
[8126] | 106 | public static volatile PlatformHook platform;
|
---|
[1023] | 107 |
|
---|
[8840] | 108 | private static volatile InitStatusListener initListener;
|
---|
[4681] | 109 |
|
---|
[11352] | 110 | /**
|
---|
| 111 | * Initialization task listener.
|
---|
| 112 | */
|
---|
[8512] | 113 | public interface InitStatusListener {
|
---|
[4681] | 114 |
|
---|
[11352] | 115 | /**
|
---|
| 116 | * Called when an initialization task updates its status.
|
---|
| 117 | * @param event task name
|
---|
| 118 | * @return new status
|
---|
| 119 | */
|
---|
[8497] | 120 | Object updateStatus(String event);
|
---|
[8510] | 121 |
|
---|
[11352] | 122 | /**
|
---|
| 123 | * Called when an initialization task completes.
|
---|
| 124 | * @param status final status
|
---|
| 125 | */
|
---|
[8497] | 126 | void finish(Object status);
|
---|
[4681] | 127 | }
|
---|
| 128 |
|
---|
[11352] | 129 | /**
|
---|
| 130 | * Sets initialization task listener.
|
---|
| 131 | * @param listener initialization task listener
|
---|
| 132 | */
|
---|
[4681] | 133 | public static void setInitStatusListener(InitStatusListener listener) {
|
---|
[8497] | 134 | CheckParameterUtil.ensureParameterNotNull(listener);
|
---|
[4681] | 135 | initListener = listener;
|
---|
| 136 | }
|
---|
| 137 |
|
---|
[6523] | 138 | /**
|
---|
[10340] | 139 | * Constructs new {@code Main} object.
|
---|
| 140 | * @see #initialize()
|
---|
[6523] | 141 | */
|
---|
[11656] | 142 | protected Main() {
|
---|
| 143 | setInstance(this);
|
---|
[10340] | 144 | }
|
---|
| 145 |
|
---|
[11656] | 146 | private static void setInstance(Main instance) {
|
---|
| 147 | main = instance;
|
---|
| 148 | }
|
---|
| 149 |
|
---|
[10340] | 150 | /**
|
---|
| 151 | * Initializes the main object. A lot of global variables are initialized here.
|
---|
| 152 | * @since 10340
|
---|
| 153 | */
|
---|
| 154 | public void initialize() {
|
---|
[12629] | 155 | // Initializes tasks that must be run before parallel tasks
|
---|
| 156 | runInitializationTasks(beforeInitializationTasks());
|
---|
[4681] | 157 |
|
---|
[12629] | 158 | // Initializes tasks to be executed (in parallel) by a ExecutorService
|
---|
[11642] | 159 | try {
|
---|
[10899] | 160 | ExecutorService service = Executors.newFixedThreadPool(
|
---|
[8734] | 161 | Runtime.getRuntime().availableProcessors(), Utils.newThreadFactory("main-init-%d", Thread.NORM_PRIORITY));
|
---|
[12629] | 162 | for (Future<Void> i : service.invokeAll(parallelInitializationTasks())) {
|
---|
[5195] | 163 | i.get();
|
---|
| 164 | }
|
---|
[11092] | 165 | // asynchronous initializations to be completed eventually
|
---|
[12629] | 166 | asynchronousRunnableTasks().forEach(service::submit);
|
---|
| 167 | asynchronousCallableTasks().forEach(service::submit);
|
---|
[13647] | 168 | try {
|
---|
| 169 | service.shutdown();
|
---|
| 170 | } catch (SecurityException e) {
|
---|
| 171 | Logging.log(Logging.LEVEL_ERROR, "Unable to shutdown executor service", e);
|
---|
| 172 | }
|
---|
[10212] | 173 | } catch (InterruptedException | ExecutionException ex) {
|
---|
[11374] | 174 | throw new JosmRuntimeException(ex);
|
---|
[4718] | 175 | }
|
---|
[3669] | 176 |
|
---|
[12629] | 177 | // Initializes tasks that must be run after parallel tasks
|
---|
| 178 | runInitializationTasks(afterInitializationTasks());
|
---|
| 179 | }
|
---|
[4489] | 180 |
|
---|
[12629] | 181 | private static void runInitializationTasks(List<InitializationTask> tasks) {
|
---|
| 182 | for (InitializationTask task : tasks) {
|
---|
| 183 | try {
|
---|
| 184 | task.call();
|
---|
| 185 | } catch (JosmRuntimeException e) {
|
---|
| 186 | // Can happen if the current projection needs NTV2 grid which is not available
|
---|
| 187 | // In this case we want the user be able to change his projection
|
---|
| 188 | BugReport.intercept(e).warn();
|
---|
| 189 | }
|
---|
| 190 | }
|
---|
[1104] | 191 | }
|
---|
[626] | 192 |
|
---|
[10340] | 193 | /**
|
---|
[12629] | 194 | * Returns tasks that must be run before parallel tasks.
|
---|
| 195 | * @return tasks that must be run before parallel tasks
|
---|
| 196 | * @see #afterInitializationTasks
|
---|
| 197 | * @see #parallelInitializationTasks
|
---|
| 198 | */
|
---|
| 199 | protected List<InitializationTask> beforeInitializationTasks() {
|
---|
| 200 | return Collections.emptyList();
|
---|
| 201 | }
|
---|
| 202 |
|
---|
| 203 | /**
|
---|
| 204 | * Returns tasks to be executed (in parallel) by a ExecutorService.
|
---|
| 205 | * @return tasks to be executed (in parallel) by a ExecutorService
|
---|
| 206 | */
|
---|
| 207 | protected Collection<InitializationTask> parallelInitializationTasks() {
|
---|
| 208 | return Collections.emptyList();
|
---|
| 209 | }
|
---|
| 210 |
|
---|
| 211 | /**
|
---|
| 212 | * Returns asynchronous callable initializations to be completed eventually
|
---|
| 213 | * @return asynchronous callable initializations to be completed eventually
|
---|
| 214 | */
|
---|
| 215 | protected List<Callable<?>> asynchronousCallableTasks() {
|
---|
| 216 | return Collections.emptyList();
|
---|
| 217 | }
|
---|
| 218 |
|
---|
| 219 | /**
|
---|
| 220 | * Returns asynchronous runnable initializations to be completed eventually
|
---|
| 221 | * @return asynchronous runnable initializations to be completed eventually
|
---|
| 222 | */
|
---|
| 223 | protected List<Runnable> asynchronousRunnableTasks() {
|
---|
| 224 | return Collections.emptyList();
|
---|
| 225 | }
|
---|
| 226 |
|
---|
| 227 | /**
|
---|
| 228 | * Returns tasks that must be run after parallel tasks.
|
---|
| 229 | * @return tasks that must be run after parallel tasks
|
---|
| 230 | * @see #beforeInitializationTasks
|
---|
| 231 | * @see #parallelInitializationTasks
|
---|
| 232 | */
|
---|
| 233 | protected List<InitializationTask> afterInitializationTasks() {
|
---|
| 234 | return Collections.emptyList();
|
---|
| 235 | }
|
---|
| 236 |
|
---|
| 237 | protected static final class InitializationTask implements Callable<Void> {
|
---|
[6779] | 238 |
|
---|
| 239 | private final String name;
|
---|
[10936] | 240 | private final Runnable task;
|
---|
[6779] | 241 |
|
---|
[12629] | 242 | /**
|
---|
| 243 | * Constructs a new {@code InitializationTask}.
|
---|
| 244 | * @param name translated name to be displayed to user
|
---|
| 245 | * @param task runnable initialization task
|
---|
| 246 | */
|
---|
| 247 | public InitializationTask(String name, Runnable task) {
|
---|
[6779] | 248 | this.name = name;
|
---|
[10899] | 249 | this.task = task;
|
---|
[6779] | 250 | }
|
---|
| 251 |
|
---|
| 252 | @Override
|
---|
[8497] | 253 | public Void call() {
|
---|
| 254 | Object status = null;
|
---|
[6779] | 255 | if (initListener != null) {
|
---|
[8497] | 256 | status = initListener.updateStatus(name);
|
---|
[6779] | 257 | }
|
---|
[10899] | 258 | task.run();
|
---|
[8497] | 259 | if (initListener != null) {
|
---|
| 260 | initListener.finish(status);
|
---|
[6852] | 261 | }
|
---|
[6779] | 262 | return null;
|
---|
| 263 | }
|
---|
| 264 | }
|
---|
| 265 |
|
---|
[1104] | 266 | /**
|
---|
[6546] | 267 | * Replies the current selected primitives, from a end-user point of view.
|
---|
| 268 | * It is not always technically the same collection of primitives than {@link DataSet#getSelected()}.
|
---|
| 269 | * @return The current selected primitives, from a end-user point of view. Can be {@code null}.
|
---|
| 270 | * @since 6546
|
---|
| 271 | */
|
---|
| 272 | public Collection<OsmPrimitive> getInProgressSelection() {
|
---|
[12636] | 273 | return Collections.emptyList();
|
---|
[6546] | 274 | }
|
---|
[1750] | 275 |
|
---|
[11352] | 276 | /**
|
---|
[13434] | 277 | * Gets the active edit data set (not read-only).
|
---|
[12718] | 278 | * @return That data set, <code>null</code>.
|
---|
[13434] | 279 | * @see #getActiveDataSet
|
---|
[12691] | 280 | * @since 12691
|
---|
| 281 | */
|
---|
[12718] | 282 | public abstract DataSet getEditDataSet();
|
---|
[12691] | 283 |
|
---|
| 284 | /**
|
---|
[13434] | 285 | * Gets the active data set (can be read-only).
|
---|
| 286 | * @return That data set, <code>null</code>.
|
---|
| 287 | * @see #getEditDataSet
|
---|
| 288 | * @since 13434
|
---|
[12718] | 289 | */
|
---|
[13434] | 290 | public abstract DataSet getActiveDataSet();
|
---|
[12718] | 291 |
|
---|
| 292 | /**
|
---|
[13434] | 293 | * Sets the active data set (and also edit data set if not read-only).
|
---|
| 294 | * @param ds New data set, or <code>null</code>
|
---|
| 295 | * @since 13434
|
---|
| 296 | */
|
---|
| 297 | public abstract void setActiveDataSet(DataSet ds);
|
---|
| 298 |
|
---|
| 299 | /**
|
---|
[12718] | 300 | * Determines if the list of data sets managed by JOSM contains {@code ds}.
|
---|
| 301 | * @param ds the data set to look for
|
---|
| 302 | * @return {@code true} if the list of data sets managed by JOSM contains {@code ds}
|
---|
| 303 | * @since 12718
|
---|
| 304 | */
|
---|
| 305 | public abstract boolean containsDataSet(DataSet ds);
|
---|
| 306 |
|
---|
[1104] | 307 | ///////////////////////////////////////////////////////////////////////////
|
---|
| 308 | // Implementation part
|
---|
| 309 | ///////////////////////////////////////////////////////////////////////////
|
---|
[626] | 310 |
|
---|
[5829] | 311 | /**
|
---|
[1104] | 312 | * Should be called before the main constructor to setup some parameter stuff
|
---|
| 313 | */
|
---|
[10962] | 314 | public static void preConstructorInit() {
|
---|
[1990] | 315 | // init default coordinate format
|
---|
[12846] | 316 | ICoordinateFormat fmt = CoordinateFormatManager.getCoordinateFormat(Config.getPref().get("coordinates"));
|
---|
[12735] | 317 | if (fmt == null) {
|
---|
| 318 | fmt = DecimalDegreesCoordinateFormat.INSTANCE;
|
---|
[1990] | 319 | }
|
---|
[12735] | 320 | CoordinateFormatManager.setCoordinateFormat(fmt);
|
---|
[1104] | 321 | }
|
---|
[626] | 322 |
|
---|
[11986] | 323 | /**
|
---|
[8412] | 324 | * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
|
---|
[6342] | 325 | * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
|
---|
| 326 | * @param exitCode The return code
|
---|
[12636] | 327 | * @return {@code true}
|
---|
| 328 | * @since 12636
|
---|
[5829] | 329 | */
|
---|
[12636] | 330 | public static boolean exitJosm(boolean exit, int exitCode) {
|
---|
| 331 | if (Main.main != null) {
|
---|
| 332 | Main.main.shutdown();
|
---|
| 333 | }
|
---|
[8412] | 334 |
|
---|
[12636] | 335 | if (exit) {
|
---|
| 336 | System.exit(exitCode);
|
---|
[5829] | 337 | }
|
---|
[12636] | 338 | return true;
|
---|
[3378] | 339 | }
|
---|
| 340 |
|
---|
[11986] | 341 | /**
|
---|
| 342 | * Shutdown JOSM.
|
---|
| 343 | */
|
---|
[10340] | 344 | protected void shutdown() {
|
---|
[11106] | 345 | if (!GraphicsEnvironment.isHeadless()) {
|
---|
| 346 | ImageProvider.shutdown(false);
|
---|
| 347 | }
|
---|
[10340] | 348 | try {
|
---|
| 349 | pref.saveDefaults();
|
---|
[13838] | 350 | } catch (IOException | InvalidPathException ex) {
|
---|
[12620] | 351 | Logging.log(Logging.LEVEL_WARN, tr("Failed to save default preferences."), ex);
|
---|
[10340] | 352 | }
|
---|
[11106] | 353 | if (!GraphicsEnvironment.isHeadless()) {
|
---|
| 354 | ImageProvider.shutdown(true);
|
---|
| 355 | }
|
---|
[10340] | 356 | }
|
---|
| 357 |
|
---|
[2798] | 358 | /**
|
---|
[5829] | 359 | * Identifies the current operating system family and initializes the platform hook accordingly.
|
---|
| 360 | * @since 1849
|
---|
| 361 | */
|
---|
[1849] | 362 | public static void determinePlatformHook() {
|
---|
[12776] | 363 | platform = Platform.determinePlatform().accept(PlatformHook.CONSTRUCT_FROM_PLATFORM);
|
---|
[1104] | 364 | }
|
---|
[1023] | 365 |
|
---|
[4126] | 366 | /* ----------------------------------------------------------------------------------------- */
|
---|
| 367 | /* projection handling - Main is a registry for a single, global projection instance */
|
---|
| 368 | /* */
|
---|
| 369 | /* TODO: For historical reasons the registry is implemented by Main. An alternative approach */
|
---|
| 370 | /* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class. */
|
---|
| 371 | /* ----------------------------------------------------------------------------------------- */
|
---|
| 372 | /**
|
---|
| 373 | * The projection method used.
|
---|
[12631] | 374 | * Use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
|
---|
[4897] | 375 | * Use {@link #setProjection(Projection)} in order to trigger a projection change event.
|
---|
[4126] | 376 | */
|
---|
[8126] | 377 | private static volatile Projection proj;
|
---|
[4126] | 378 |
|
---|
| 379 | /**
|
---|
| 380 | * Replies the current projection.
|
---|
[4489] | 381 | *
|
---|
[5357] | 382 | * @return the currently active projection
|
---|
[4126] | 383 | */
|
---|
| 384 | public static Projection getProjection() {
|
---|
| 385 | return proj;
|
---|
| 386 | }
|
---|
| 387 |
|
---|
| 388 | /**
|
---|
| 389 | * Sets the current projection
|
---|
[4489] | 390 | *
|
---|
[4126] | 391 | * @param p the projection
|
---|
| 392 | */
|
---|
| 393 | public static void setProjection(Projection p) {
|
---|
| 394 | CheckParameterUtil.ensureParameterNotNull(p);
|
---|
| 395 | Projection oldValue = proj;
|
---|
[12631] | 396 | Bounds b = main != null ? main.getRealBounds() : null;
|
---|
[4126] | 397 | proj = p;
|
---|
[5234] | 398 | fireProjectionChanged(oldValue, proj, b);
|
---|
[4126] | 399 | }
|
---|
| 400 |
|
---|
[12631] | 401 | /**
|
---|
| 402 | * Returns the bounds for the current projection. Used for projection events.
|
---|
| 403 | * @return the bounds for the current projection
|
---|
| 404 | * @see #restoreOldBounds
|
---|
| 405 | */
|
---|
| 406 | protected Bounds getRealBounds() {
|
---|
| 407 | // To be overriden
|
---|
| 408 | return null;
|
---|
| 409 | }
|
---|
| 410 |
|
---|
| 411 | /**
|
---|
| 412 | * Restore clean state corresponding to old bounds after a projection change event.
|
---|
| 413 | * @param oldBounds bounds previously returned by {@link #getRealBounds}, before the change of projection
|
---|
| 414 | * @see #getRealBounds
|
---|
| 415 | */
|
---|
| 416 | protected void restoreOldBounds(Bounds oldBounds) {
|
---|
| 417 | // To be overriden
|
---|
| 418 | }
|
---|
| 419 |
|
---|
[4126] | 420 | /*
|
---|
| 421 | * Keep WeakReferences to the listeners. This relieves clients from the burden of
|
---|
| 422 | * explicitly removing the listeners and allows us to transparently register every
|
---|
| 423 | * created dataset as projection change listener.
|
---|
| 424 | */
|
---|
[13316] | 425 | private static final List<WeakReference<ProjectionChangeListener>> listeners = new CopyOnWriteArrayList<>();
|
---|
[4126] | 426 |
|
---|
[5234] | 427 | private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) {
|
---|
[10655] | 428 | if ((newValue == null ^ oldValue == null)
|
---|
[7083] | 429 | || (newValue != null && oldValue != null && !Objects.equals(newValue.toCode(), oldValue.toCode()))) {
|
---|
[13319] | 430 | listeners.removeIf(x -> x.get() == null);
|
---|
[13326] | 431 | listeners.stream().map(WeakReference::get).filter(Objects::nonNull).forEach(x -> x.projectionChanged(oldValue, newValue));
|
---|
[12631] | 432 | if (newValue != null && oldBounds != null && main != null) {
|
---|
| 433 | main.restoreOldBounds(oldBounds);
|
---|
[4126] | 434 | }
|
---|
| 435 | /* TODO - remove layers with fixed projection */
|
---|
| 436 | }
|
---|
| 437 | }
|
---|
| 438 |
|
---|
| 439 | /**
|
---|
[5538] | 440 | * Register a projection change listener.
|
---|
[12119] | 441 | * The listener is registered to be weak, so keep a reference of it if you want it to be preserved.
|
---|
[4489] | 442 | *
|
---|
[5357] | 443 | * @param listener the listener. Ignored if <code>null</code>.
|
---|
[4126] | 444 | */
|
---|
| 445 | public static void addProjectionChangeListener(ProjectionChangeListener listener) {
|
---|
| 446 | if (listener == null) return;
|
---|
[13322] | 447 | for (WeakReference<ProjectionChangeListener> wr : listeners) {
|
---|
| 448 | // already registered ? => abort
|
---|
| 449 | if (wr.get() == listener) return;
|
---|
[4126] | 450 | }
|
---|
[13322] | 451 | listeners.add(new WeakReference<>(listener));
|
---|
[4126] | 452 | }
|
---|
| 453 |
|
---|
| 454 | /**
|
---|
[5538] | 455 | * Removes a projection change listener.
|
---|
[4489] | 456 | *
|
---|
[5357] | 457 | * @param listener the listener. Ignored if <code>null</code>.
|
---|
[4126] | 458 | */
|
---|
| 459 | public static void removeProjectionChangeListener(ProjectionChangeListener listener) {
|
---|
| 460 | if (listener == null) return;
|
---|
[13322] | 461 | // remove the listener - and any other listener which got garbage collected in the meantime
|
---|
| 462 | listeners.removeIf(wr -> wr.get() == null || wr.get() == listener);
|
---|
[4126] | 463 | }
|
---|
[5538] | 464 |
|
---|
| 465 | /**
|
---|
[13322] | 466 | * Remove all projection change listeners. For testing purposes only.
|
---|
| 467 | * @since 13322
|
---|
| 468 | */
|
---|
| 469 | public static void clearProjectionChangeListeners() {
|
---|
| 470 | listeners.clear();
|
---|
| 471 | }
|
---|
| 472 |
|
---|
| 473 | /**
|
---|
[6642] | 474 | * Adds a new network error that occur to give a hint about broken Internet connection.
|
---|
| 475 | * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
|
---|
[6852] | 476 | *
|
---|
[6642] | 477 | * @param url The accessed URL that caused the error
|
---|
| 478 | * @param t The network error
|
---|
| 479 | * @return The previous error associated to the given resource, if any. Can be {@code null}
|
---|
[6643] | 480 | * @since 6642
|
---|
[6642] | 481 | */
|
---|
| 482 | public static Throwable addNetworkError(URL url, Throwable t) {
|
---|
| 483 | if (url != null && t != null) {
|
---|
| 484 | Throwable old = addNetworkError(url.toExternalForm(), t);
|
---|
| 485 | if (old != null) {
|
---|
[12620] | 486 | Logging.warn("Already here "+old);
|
---|
[6642] | 487 | }
|
---|
| 488 | return old;
|
---|
| 489 | }
|
---|
| 490 | return null;
|
---|
| 491 | }
|
---|
| 492 |
|
---|
| 493 | /**
|
---|
| 494 | * Adds a new network error that occur to give a hint about broken Internet connection.
|
---|
| 495 | * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
|
---|
[6852] | 496 | *
|
---|
[6642] | 497 | * @param url The accessed URL that caused the error
|
---|
| 498 | * @param t The network error
|
---|
| 499 | * @return The previous error associated to the given resource, if any. Can be {@code null}
|
---|
[6643] | 500 | * @since 6642
|
---|
[6642] | 501 | */
|
---|
| 502 | public static Throwable addNetworkError(String url, Throwable t) {
|
---|
| 503 | if (url != null && t != null) {
|
---|
[6806] | 504 | return NETWORK_ERRORS.put(url, t);
|
---|
[6642] | 505 | }
|
---|
| 506 | return null;
|
---|
| 507 | }
|
---|
| 508 |
|
---|
| 509 | /**
|
---|
| 510 | * Returns the network errors that occured until now.
|
---|
| 511 | * @return the network errors that occured until now, indexed by URL
|
---|
| 512 | * @since 6639
|
---|
| 513 | */
|
---|
| 514 | public static Map<String, Throwable> getNetworkErrors() {
|
---|
[7005] | 515 | return new HashMap<>(NETWORK_ERRORS);
|
---|
[6642] | 516 | }
|
---|
[6897] | 517 |
|
---|
| 518 | /**
|
---|
[12011] | 519 | * Clears the network errors cache.
|
---|
| 520 | * @since 12011
|
---|
| 521 | */
|
---|
| 522 | public static void clearNetworkErrors() {
|
---|
| 523 | NETWORK_ERRORS.clear();
|
---|
| 524 | }
|
---|
| 525 |
|
---|
| 526 | /**
|
---|
[6897] | 527 | * Returns the JOSM website URL.
|
---|
| 528 | * @return the josm website URL
|
---|
| 529 | * @since 6897
|
---|
| 530 | */
|
---|
| 531 | public static String getJOSMWebsite() {
|
---|
[12846] | 532 | if (Config.getPref() != null)
|
---|
| 533 | return Config.getPref().get("josm.url", JOSM_WEBSITE);
|
---|
[6897] | 534 | return JOSM_WEBSITE;
|
---|
| 535 | }
|
---|
| 536 |
|
---|
| 537 | /**
|
---|
| 538 | * Returns the JOSM XML URL.
|
---|
| 539 | * @return the josm XML URL
|
---|
| 540 | * @since 6897
|
---|
| 541 | */
|
---|
| 542 | public static String getXMLBase() {
|
---|
[6920] | 543 | // Always return HTTP (issues reported with HTTPS)
|
---|
[6900] | 544 | return "http://josm.openstreetmap.de";
|
---|
[6897] | 545 | }
|
---|
| 546 |
|
---|
| 547 | /**
|
---|
| 548 | * Returns the OSM website URL.
|
---|
| 549 | * @return the OSM website URL
|
---|
| 550 | * @since 6897
|
---|
| 551 | */
|
---|
| 552 | public static String getOSMWebsite() {
|
---|
[12846] | 553 | if (Config.getPref() != null)
|
---|
| 554 | return Config.getPref().get("osm.url", OSM_WEBSITE);
|
---|
[6897] | 555 | return OSM_WEBSITE;
|
---|
| 556 | }
|
---|
[6957] | 557 |
|
---|
| 558 | /**
|
---|
[11262] | 559 | * Returns the OSM website URL depending on the selected {@link OsmApi}.
|
---|
| 560 | * @return the OSM website URL depending on the selected {@link OsmApi}
|
---|
| 561 | */
|
---|
| 562 | private static String getOSMWebsiteDependingOnSelectedApi() {
|
---|
| 563 | final String api = OsmApi.getOsmApi().getServerUrl();
|
---|
| 564 | if (OsmApi.DEFAULT_API_URL.equals(api)) {
|
---|
| 565 | return getOSMWebsite();
|
---|
| 566 | } else {
|
---|
| 567 | return api.replaceAll("/api$", "");
|
---|
| 568 | }
|
---|
| 569 | }
|
---|
| 570 |
|
---|
| 571 | /**
|
---|
[7678] | 572 | * Replies the base URL for browsing information about a primitive.
|
---|
| 573 | * @return the base URL, i.e. https://www.openstreetmap.org
|
---|
| 574 | * @since 7678
|
---|
| 575 | */
|
---|
| 576 | public static String getBaseBrowseUrl() {
|
---|
[12846] | 577 | if (Config.getPref() != null)
|
---|
| 578 | return Config.getPref().get("osm-browse.url", getOSMWebsiteDependingOnSelectedApi());
|
---|
[11262] | 579 | return getOSMWebsiteDependingOnSelectedApi();
|
---|
[7678] | 580 | }
|
---|
| 581 |
|
---|
| 582 | /**
|
---|
| 583 | * Replies the base URL for browsing information about a user.
|
---|
| 584 | * @return the base URL, i.e. https://www.openstreetmap.org/user
|
---|
| 585 | * @since 7678
|
---|
| 586 | */
|
---|
| 587 | public static String getBaseUserUrl() {
|
---|
[12846] | 588 | if (Config.getPref() != null)
|
---|
| 589 | return Config.getPref().get("osm-user.url", getOSMWebsiteDependingOnSelectedApi() + "/user");
|
---|
[11262] | 590 | return getOSMWebsiteDependingOnSelectedApi() + "/user";
|
---|
[7678] | 591 | }
|
---|
| 592 |
|
---|
| 593 | /**
|
---|
[6957] | 594 | * Determines if we are currently running on OSX.
|
---|
| 595 | * @return {@code true} if we are currently running on OSX
|
---|
| 596 | * @since 6957
|
---|
| 597 | */
|
---|
| 598 | public static boolean isPlatformOsx() {
|
---|
| 599 | return Main.platform instanceof PlatformHookOsx;
|
---|
| 600 | }
|
---|
[7335] | 601 |
|
---|
| 602 | /**
|
---|
| 603 | * Determines if we are currently running on Windows.
|
---|
| 604 | * @return {@code true} if we are currently running on Windows
|
---|
| 605 | * @since 7335
|
---|
| 606 | */
|
---|
| 607 | public static boolean isPlatformWindows() {
|
---|
| 608 | return Main.platform instanceof PlatformHookWindows;
|
---|
| 609 | }
|
---|
[7434] | 610 |
|
---|
| 611 | /**
|
---|
| 612 | * Determines if the given online resource is currently offline.
|
---|
| 613 | * @param r the online resource
|
---|
| 614 | * @return {@code true} if {@code r} is offline and should not be accessed
|
---|
| 615 | * @since 7434
|
---|
| 616 | */
|
---|
| 617 | public static boolean isOffline(OnlineResource r) {
|
---|
| 618 | return OFFLINE_RESOURCES.contains(r) || OFFLINE_RESOURCES.contains(OnlineResource.ALL);
|
---|
| 619 | }
|
---|
| 620 |
|
---|
| 621 | /**
|
---|
| 622 | * Sets the given online resource to offline state.
|
---|
| 623 | * @param r the online resource
|
---|
| 624 | * @return {@code true} if {@code r} was not already offline
|
---|
| 625 | * @since 7434
|
---|
| 626 | */
|
---|
| 627 | public static boolean setOffline(OnlineResource r) {
|
---|
| 628 | return OFFLINE_RESOURCES.add(r);
|
---|
| 629 | }
|
---|
| 630 |
|
---|
| 631 | /**
|
---|
[8506] | 632 | * Sets the given online resource to online state.
|
---|
| 633 | * @param r the online resource
|
---|
| 634 | * @return {@code true} if {@code r} was offline
|
---|
| 635 | * @since 8506
|
---|
| 636 | */
|
---|
| 637 | public static boolean setOnline(OnlineResource r) {
|
---|
| 638 | return OFFLINE_RESOURCES.remove(r);
|
---|
| 639 | }
|
---|
| 640 |
|
---|
| 641 | /**
|
---|
[7434] | 642 | * Replies the set of online resources currently offline.
|
---|
| 643 | * @return the set of online resources currently offline
|
---|
| 644 | * @since 7434
|
---|
| 645 | */
|
---|
| 646 | public static Set<OnlineResource> getOfflineResources() {
|
---|
[10044] | 647 | return EnumSet.copyOf(OFFLINE_RESOURCES);
|
---|
[7434] | 648 | }
|
---|
[626] | 649 | }
|
---|