001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm;
003    import static org.openstreetmap.josm.tools.I18n.tr;
004    
005    import java.awt.BorderLayout;
006    import java.awt.Component;
007    import java.awt.GridBagConstraints;
008    import java.awt.GridBagLayout;
009    import java.awt.event.ComponentEvent;
010    import java.awt.event.ComponentListener;
011    import java.awt.event.KeyEvent;
012    import java.awt.event.WindowAdapter;
013    import java.awt.event.WindowEvent;
014    import java.io.File;
015    import java.lang.ref.WeakReference;
016    import java.net.URI;
017    import java.net.URISyntaxException;
018    import java.text.MessageFormat;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.StringTokenizer;
025    import java.util.concurrent.Callable;
026    import java.util.concurrent.ExecutorService;
027    import java.util.concurrent.Executors;
028    import java.util.concurrent.Future;
029    
030    import javax.swing.Action;
031    import javax.swing.InputMap;
032    import javax.swing.JComponent;
033    import javax.swing.JFrame;
034    import javax.swing.JLabel;
035    import javax.swing.JOptionPane;
036    import javax.swing.JPanel;
037    import javax.swing.JTextArea;
038    import javax.swing.KeyStroke;
039    import javax.swing.UIManager;
040    
041    import org.openstreetmap.gui.jmapviewer.FeatureAdapter;
042    import org.openstreetmap.josm.actions.JosmAction;
043    import org.openstreetmap.josm.actions.OpenFileAction;
044    import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask;
045    import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
046    import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
047    import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
048    import org.openstreetmap.josm.actions.mapmode.MapMode;
049    import org.openstreetmap.josm.actions.search.SearchAction;
050    import org.openstreetmap.josm.data.Bounds;
051    import org.openstreetmap.josm.data.Preferences;
052    import org.openstreetmap.josm.data.UndoRedoHandler;
053    import org.openstreetmap.josm.data.coor.CoordinateFormat;
054    import org.openstreetmap.josm.data.coor.LatLon;
055    import org.openstreetmap.josm.data.osm.DataSet;
056    import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
057    import org.openstreetmap.josm.data.projection.Projection;
058    import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
059    import org.openstreetmap.josm.data.validation.OsmValidator;
060    import org.openstreetmap.josm.gui.GettingStarted;
061    import org.openstreetmap.josm.gui.MainApplication.Option;
062    import org.openstreetmap.josm.gui.MainMenu;
063    import org.openstreetmap.josm.gui.MapFrame;
064    import org.openstreetmap.josm.gui.MapView;
065    import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
066    import org.openstreetmap.josm.gui.io.SaveLayersDialog;
067    import org.openstreetmap.josm.gui.layer.Layer;
068    import org.openstreetmap.josm.gui.layer.OsmDataLayer;
069    import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener;
070    import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
071    import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference;
072    import org.openstreetmap.josm.gui.preferences.map.MapPaintPreference;
073    import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference;
074    import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
075    import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
076    import org.openstreetmap.josm.gui.progress.ProgressMonitorExecutor;
077    import org.openstreetmap.josm.gui.util.RedirectInputMap;
078    import org.openstreetmap.josm.io.OsmApi;
079    import org.openstreetmap.josm.plugins.PluginHandler;
080    import org.openstreetmap.josm.tools.CheckParameterUtil;
081    import org.openstreetmap.josm.tools.I18n;
082    import org.openstreetmap.josm.tools.ImageProvider;
083    import org.openstreetmap.josm.tools.OpenBrowser;
084    import org.openstreetmap.josm.tools.OsmUrlToBounds;
085    import org.openstreetmap.josm.tools.PlatformHook;
086    import org.openstreetmap.josm.tools.PlatformHookOsx;
087    import org.openstreetmap.josm.tools.PlatformHookUnixoid;
088    import org.openstreetmap.josm.tools.PlatformHookWindows;
089    import org.openstreetmap.josm.tools.Shortcut;
090    import org.openstreetmap.josm.tools.Utils;
091    import org.openstreetmap.josm.tools.WindowGeometry;
092    
093    abstract public class Main {
094    
095        /**
096         * Replies true if JOSM currently displays a map view. False, if it doesn't, i.e. if
097         * it only shows the MOTD panel.
098         *
099         * @return <code>true</code> if JOSM currently displays a map view
100         */
101        static public boolean isDisplayingMapView() {
102            if (map == null) return false;
103            if (map.mapView == null) return false;
104            return true;
105        }
106        /**
107         * Global parent component for all dialogs and message boxes
108         */
109        public static Component parent;
110        /**
111         * Global application.
112         */
113        public static Main main;
114        /**
115         * The worker thread slave. This is for executing all long and intensive
116         * calculations. The executed runnables are guaranteed to be executed separately
117         * and sequential.
118         */
119        public final static ExecutorService worker = new ProgressMonitorExecutor();
120        /**
121         * Global application preferences
122         */
123        public static Preferences pref;
124    
125        /**
126         * The global paste buffer.
127         */
128        public static final PrimitiveDeepCopy pasteBuffer = new PrimitiveDeepCopy();
129        public static Layer pasteSource;
130    
131        /**
132         * The MapFrame. Use setMapFrame to set or clear it.
133         */
134        public static MapFrame map;
135        /**
136         * Set to <code>true</code>, when in applet mode
137         */
138        public static boolean applet = false;
139    
140        /**
141         * The toolbar preference control to register new actions.
142         */
143        public static ToolbarPreferences toolbar;
144    
145        public UndoRedoHandler undoRedo = new UndoRedoHandler();
146    
147        public static PleaseWaitProgressMonitor currentProgressMonitor;
148    
149        /**
150         * The main menu bar at top of screen.
151         */
152        public MainMenu menu;
153    
154        /**
155         * The data validation handler.
156         */
157        public OsmValidator validator;
158        /**
159         * The MOTD Layer.
160         */
161        private GettingStarted gettingStarted = new GettingStarted();
162    
163        /**
164         * Logging level (3 = debug, 2 = info, 1 = warn, 0 = none).
165         */
166        static public int log_level = 2;
167        /**
168         * Print a warning message if logging is on.
169         * @param msg The message to print.
170         */
171        static public void warn(String msg) {
172            if (log_level < 1)
173                return;
174            System.out.println(msg);
175        }
176        /**
177         * Print an informational message if logging is on.
178         * @param msg The message to print.
179         */
180        static public void info(String msg) {
181            if (log_level < 2)
182                return;
183            System.out.println(msg);
184        }
185        /**
186         * Print an debug message if logging is on.
187         * @param msg The message to print.
188         */
189        static public void debug(String msg) {
190            if (log_level < 3)
191                return;
192            System.out.println(msg);
193        }
194        /**
195         * Print a formated warning message if logging is on. Calls {@link MessageFormat#format}
196         * function to format text.
197         * @param msg The formated message to print.
198         * @param objects The objects to insert into format string.
199         */
200        static public void warn(String msg, Object... objects) {
201            warn(MessageFormat.format(msg, objects));
202        }
203        /**
204         * Print a formated informational message if logging is on. Calls {@link MessageFormat#format}
205         * function to format text.
206         * @param msg The formated message to print.
207         * @param objects The objects to insert into format string.
208         */
209        static public void info(String msg, Object... objects) {
210            info(MessageFormat.format(msg, objects));
211        }
212        /**
213         * Print a formated debug message if logging is on. Calls {@link MessageFormat#format}
214         * function to format text.
215         * @param msg The formated message to print.
216         * @param objects The objects to insert into format string.
217         */
218        static public void debug(String msg, Object... objects) {
219            debug(MessageFormat.format(msg, objects));
220        }
221    
222        /**
223         * Platform specific code goes in here.
224         * Plugins may replace it, however, some hooks will be called before any plugins have been loeaded.
225         * So if you need to hook into those early ones, split your class and send the one with the early hooks
226         * to the JOSM team for inclusion.
227         */
228        public static PlatformHook platform;
229    
230        /**
231         * Whether or not the java vm is openjdk
232         * We use this to work around openjdk bugs
233         */
234        public static boolean isOpenjdk;
235    
236        /**
237         * Set or clear (if passed <code>null</code>) the map.
238         */
239        public final void setMapFrame(final MapFrame map) {
240            MapFrame old = Main.map;
241            panel.setVisible(false);
242            panel.removeAll();
243            if (map != null) {
244                map.fillPanel(panel);
245            } else {
246                old.destroy();
247                panel.add(gettingStarted, BorderLayout.CENTER);
248            }
249            panel.setVisible(true);
250            redoUndoListener.commandChanged(0,0);
251    
252            Main.map = map;
253    
254            PluginHandler.notifyMapFrameChanged(old, map);
255            if (map == null && currentProgressMonitor != null) {
256                currentProgressMonitor.showForegroundDialog();
257            }
258        }
259    
260        /**
261         * Remove the specified layer from the map. If it is the last layer,
262         * remove the map as well.
263         */
264        public final void removeLayer(final Layer layer) {
265            if (map != null) {
266                map.mapView.removeLayer(layer);
267                if (map != null && map.mapView.getAllLayers().isEmpty()) {
268                    setMapFrame(null);
269                }
270            }
271        }
272    
273        private static InitStatusListener initListener = null;
274    
275        public static interface InitStatusListener {
276    
277            void updateStatus(String event);
278        }
279    
280        public static void setInitStatusListener(InitStatusListener listener) {
281            initListener = listener;
282        }
283    
284        public Main() {
285            main = this;
286            isOpenjdk = System.getProperty("java.vm.name").toUpperCase().indexOf("OPENJDK") != -1;
287    
288            if (initListener != null) {
289                initListener.updateStatus(tr("Executing platform startup hook"));
290            }
291            platform.startupHook();
292    
293            if (initListener != null) {
294                initListener.updateStatus(tr("Building main menu"));
295            }
296            contentPanePrivate.add(panel, BorderLayout.CENTER);
297            panel.add(gettingStarted, BorderLayout.CENTER);
298            menu = new MainMenu();
299    
300            undoRedo.addCommandQueueListener(redoUndoListener);
301    
302            // creating toolbar
303            contentPanePrivate.add(toolbar.control, BorderLayout.NORTH);
304    
305            registerActionShortcut(menu.help, Shortcut.registerShortcut("system:help", tr("Help"),
306                    KeyEvent.VK_F1, Shortcut.DIRECT));
307    
308            // contains several initialization tasks to be executed (in parallel) by a ExecutorService
309            List<Callable<Void>> tasks = new ArrayList<Callable<Void>>();
310    
311            tasks.add(new Callable<Void>() {
312    
313                @Override
314                public Void call() throws Exception {
315                    // We try to establish an API connection early, so that any API
316                    // capabilities are already known to the editor instance. However
317                    // if it goes wrong that's not critical at this stage.
318                    if (initListener != null) {
319                        initListener.updateStatus(tr("Initializing OSM API"));
320                    }
321                    try {
322                        OsmApi.getOsmApi().initialize(null, true);
323                    } catch (Exception x) {
324                        // ignore any exception here.
325                    }
326                    return null;
327                }
328            });
329    
330            tasks.add(new Callable<Void>() {
331    
332                @Override
333                public Void call() throws Exception {
334                    if (initListener != null) {
335                        initListener.updateStatus(tr("Initializing presets"));
336                    }
337                    TaggingPresetPreference.initialize();
338                    // some validator tests require the presets to be initialized
339                    // TODO remove this dependency for parallel initialization
340                    if (initListener != null) {
341                        initListener.updateStatus(tr("Initializing validator"));
342                    }
343                    validator = new OsmValidator();
344                    MapView.addLayerChangeListener(validator);
345                    return null;
346                }
347            });
348    
349            tasks.add(new Callable<Void>() {
350    
351                @Override
352                public Void call() throws Exception {
353                    if (initListener != null) {
354                        initListener.updateStatus(tr("Initializing map styles"));
355                    }
356                    MapPaintPreference.initialize();
357                    return null;
358                }
359            });
360    
361            tasks.add(new Callable<Void>() {
362    
363                @Override
364                public Void call() throws Exception {
365                    if (initListener != null) {
366                        initListener.updateStatus(tr("Loading imagery preferences"));
367                    }
368                    ImageryPreference.initialize();
369                    return null;
370                }
371            });
372    
373            try {
374                for (Future<Void> i : Executors.newFixedThreadPool(
375                        Runtime.getRuntime().availableProcessors()).invokeAll(tasks)) {
376                    i.get();
377                }
378            } catch (Exception ex) {
379                throw new RuntimeException(ex);
380            }
381    
382            // hooks for the jmapviewer component
383            FeatureAdapter.registerBrowserAdapter(new FeatureAdapter.BrowserAdapter() {
384                @Override
385                public void openLink(String url) {
386                    OpenBrowser.displayUrl(url);
387                }
388            });
389            FeatureAdapter.registerTranslationAdapter(I18n.getTranslationAdapter());
390    
391            if (initListener != null) {
392                initListener.updateStatus(tr("Updating user interface"));
393            }
394    
395            toolbar.refreshToolbarControl();
396    
397            toolbar.control.updateUI();
398            contentPanePrivate.updateUI();
399    
400        }
401    
402        /**
403         * Add a new layer to the map. If no map exists, create one.
404         */
405        public final synchronized void addLayer(final Layer layer) {
406            if (map == null) {
407                final MapFrame mapFrame = new MapFrame(contentPanePrivate);
408                setMapFrame(mapFrame);
409                mapFrame.selectMapMode((MapMode)mapFrame.getDefaultButtonAction(), layer);
410                mapFrame.setVisible(true);
411                mapFrame.initializeDialogsPane();
412                // bootstrapping problem: make sure the layer list dialog is going to
413                // listen to change events of the very first layer
414                //
415                layer.addPropertyChangeListener(LayerListDialog.getInstance().getModel());
416            }
417            layer.hookUpMapView();
418            map.mapView.addLayer(layer);
419        }
420    
421        /**
422         * Replies <code>true</code> if there is an edit layer
423         *
424         * @return <code>true</code> if there is an edit layer
425         */
426        public boolean hasEditLayer() {
427            if (getEditLayer() == null) return false;
428            return true;
429        }
430    
431        /**
432         * Replies the current edit layer
433         *
434         * @return the current edit layer. <code>null</code>, if no current edit layer exists
435         */
436        public OsmDataLayer getEditLayer() {
437            if (map == null) return null;
438            if (map.mapView == null) return null;
439            return map.mapView.getEditLayer();
440        }
441    
442        /**
443         * Replies the current data set.
444         *
445         * @return the current data set. <code>null</code>, if no current data set exists
446         */
447        public DataSet getCurrentDataSet() {
448            if (!hasEditLayer()) return null;
449            return getEditLayer().data;
450        }
451    
452        /**
453         * Returns the currently active  layer
454         *
455         * @return the currently active layer. <code>null</code>, if currently no active layer exists
456         */
457        public Layer getActiveLayer() {
458            if (map == null) return null;
459            if (map.mapView == null) return null;
460            return map.mapView.getActiveLayer();
461        }
462    
463        protected static final JPanel contentPanePrivate = new JPanel(new BorderLayout());
464    
465        public static void redirectToMainContentPane(JComponent source) {
466            RedirectInputMap.redirect(source, contentPanePrivate);
467        }
468    
469        public static void registerActionShortcut(JosmAction action) {
470            registerActionShortcut(action, action.getShortcut());
471        }
472    
473        public static void registerActionShortcut(Action action, Shortcut shortcut) {
474            KeyStroke keyStroke = shortcut.getKeyStroke();
475            if (keyStroke == null)
476                return;
477    
478            InputMap inputMap = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
479            Object existing = inputMap.get(keyStroke);
480            if (existing != null && !existing.equals(action)) {
481                System.out.println(String.format("Keystroke %s is already assigned to %s, will be overridden by %s", keyStroke, existing, action));
482            }
483            inputMap.put(keyStroke, action);
484    
485            contentPanePrivate.getActionMap().put(action, action);
486        }
487    
488        public static void unregisterShortcut(Shortcut shortcut) {
489            contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(shortcut.getKeyStroke());
490        }
491    
492        public static void unregisterActionShortcut(JosmAction action) {
493            unregisterActionShortcut(action, action.getShortcut());
494        }
495    
496        public static void unregisterActionShortcut(Action action, Shortcut shortcut) {
497            unregisterShortcut(shortcut);
498            contentPanePrivate.getActionMap().remove(action);
499        }
500    
501        ///////////////////////////////////////////////////////////////////////////
502        //  Implementation part
503        ///////////////////////////////////////////////////////////////////////////
504    
505        public static final JPanel panel = new JPanel(new BorderLayout());
506    
507        protected static WindowGeometry geometry;
508        protected static int windowState = JFrame.NORMAL;
509    
510        private final CommandQueueListener redoUndoListener = new CommandQueueListener(){
511            public void commandChanged(final int queueSize, final int redoSize) {
512                menu.undo.setEnabled(queueSize > 0);
513                menu.redo.setEnabled(redoSize > 0);
514            }
515        };
516    
517        /**
518         * Should be called before the main constructor to setup some parameter stuff
519         * @param args The parsed argument list.
520         */
521        public static void preConstructorInit(Map<Option, Collection<String>> args) {
522            ProjectionPreference.setProjection();
523    
524            try {
525                String defaultlaf = platform.getDefaultStyle();
526                String laf = Main.pref.get("laf", defaultlaf);
527                try {
528                    UIManager.setLookAndFeel(laf);
529                }
530                catch (final java.lang.ClassNotFoundException e) {
531                    System.out.println("Look and Feel not found: " + laf);
532                    Main.pref.put("laf", defaultlaf);
533                }
534                catch (final javax.swing.UnsupportedLookAndFeelException e) {
535                    System.out.println("Look and Feel not supported: " + laf);
536                    Main.pref.put("laf", defaultlaf);
537                }
538                toolbar = new ToolbarPreferences();
539                contentPanePrivate.updateUI();
540                panel.updateUI();
541            } catch (final Exception e) {
542                e.printStackTrace();
543            }
544            UIManager.put("OptionPane.okIcon", ImageProvider.get("ok"));
545            UIManager.put("OptionPane.yesIcon", UIManager.get("OptionPane.okIcon"));
546            UIManager.put("OptionPane.cancelIcon", ImageProvider.get("cancel"));
547            UIManager.put("OptionPane.noIcon", UIManager.get("OptionPane.cancelIcon"));
548    
549            I18n.translateJavaInternalMessages();
550    
551            // init default coordinate format
552            //
553            try {
554                //CoordinateFormat format = CoordinateFormat.valueOf(Main.pref.get("coordinates"));
555                CoordinateFormat.setCoordinateFormat(CoordinateFormat.valueOf(Main.pref.get("coordinates")));
556            } catch (IllegalArgumentException iae) {
557                CoordinateFormat.setCoordinateFormat(CoordinateFormat.DECIMAL_DEGREES);
558            }
559    
560            geometry = WindowGeometry.mainWindow("gui.geometry",
561                (args.containsKey(Option.GEOMETRY) ? args.get(Option.GEOMETRY).iterator().next() : null),
562                !args.containsKey(Option.NO_MAXIMIZE) && Main.pref.getBoolean("gui.maximized", false));
563        }
564    
565        public void postConstructorProcessCmdLine(Map<Option, Collection<String>> args) {
566            if (args.containsKey(Option.DOWNLOAD)) {
567                List<File> fileList = new ArrayList<File>();
568                for (String s : args.get(Option.DOWNLOAD)) {
569                    File f = null;
570                    switch(paramType(s)) {
571                    case httpUrl:
572                        downloadFromParamHttp(false, s);
573                        break;
574                    case bounds:
575                        downloadFromParamBounds(false, s);
576                        break;
577                    case fileUrl:
578                        try {
579                            f = new File(new URI(s));
580                        } catch (URISyntaxException e) {
581                            JOptionPane.showMessageDialog(
582                                    Main.parent,
583                                    tr("Ignoring malformed file URL: \"{0}\"", s),
584                                    tr("Warning"),
585                                    JOptionPane.WARNING_MESSAGE
586                                    );
587                        }
588                        if (f!=null) {
589                            fileList.add(f);
590                        }
591                        break;
592                    case fileName:
593                        f = new File(s);
594                        fileList.add(f);
595                        break;
596                    }
597                }
598                if(!fileList.isEmpty())
599                {
600                    OpenFileAction.openFiles(fileList, true);
601                }
602            }
603            if (args.containsKey(Option.DOWNLOADGPS)) {
604                for (String s : args.get(Option.DOWNLOADGPS)) {
605                    switch(paramType(s)) {
606                    case httpUrl:
607                        downloadFromParamHttp(true, s);
608                        break;
609                    case bounds:
610                        downloadFromParamBounds(true, s);
611                        break;
612                    case fileUrl:
613                    case fileName:
614                        JOptionPane.showMessageDialog(
615                                Main.parent,
616                                tr("Parameter \"downloadgps\" does not accept file names or file URLs"),
617                                tr("Warning"),
618                                JOptionPane.WARNING_MESSAGE
619                                );
620                    }
621                }
622            }
623            if (args.containsKey(Option.SELECTION)) {
624                for (String s : args.get(Option.SELECTION)) {
625                    SearchAction.search(s, SearchAction.SearchMode.add);
626                }
627            }
628        }
629    
630        public static boolean saveUnsavedModifications() {
631            if (map == null) return true;
632            SaveLayersDialog dialog = new SaveLayersDialog(Main.parent);
633            List<OsmDataLayer> layersWithUnmodifiedChanges = new ArrayList<OsmDataLayer>();
634            for (OsmDataLayer l: Main.map.mapView.getLayersOfType(OsmDataLayer.class)) {
635                if ((l.requiresSaveToFile() || l.requiresUploadToServer()) && l.data.isModified()) {
636                    layersWithUnmodifiedChanges.add(l);
637                }
638            }
639            dialog.prepareForSavingAndUpdatingLayersBeforeExit();
640            if (!layersWithUnmodifiedChanges.isEmpty()) {
641                dialog.getModel().populate(layersWithUnmodifiedChanges);
642                dialog.setVisible(true);
643                switch(dialog.getUserAction()) {
644                case CANCEL: return false;
645                case PROCEED: return true;
646                default: return false;
647                }
648            }
649    
650            return true;
651        }
652    
653        public static boolean exitJosm(boolean exit) {
654            if (Main.saveUnsavedModifications()) {
655                geometry.remember("gui.geometry");
656                if (map  != null) {
657                    map.rememberToggleDialogWidth();
658                }
659                pref.put("gui.maximized", (windowState & JFrame.MAXIMIZED_BOTH) != 0);
660                // Remove all layers because somebody may rely on layerRemoved events (like AutosaveTask)
661                if (Main.isDisplayingMapView()) {
662                    Collection<Layer> layers = new ArrayList<Layer>(Main.map.mapView.getAllLayers());
663                    for (Layer l: layers) {
664                        Main.map.mapView.removeLayer(l);
665                    }
666                }
667                if (exit) {
668                    System.exit(0);
669                    return true;
670                } else
671                    return true;
672            } else
673                return false;
674        }
675    
676        /**
677         * The type of a command line parameter, to be used in switch statements.
678         * @see #paramType
679         */
680        private enum DownloadParamType { httpUrl, fileUrl, bounds, fileName }
681    
682        /**
683         * Guess the type of a parameter string specified on the command line with --download= or --downloadgps.
684         * @param s A parameter string
685         * @return The guessed parameter type
686         */
687        private DownloadParamType paramType(String s) {
688            if(s.startsWith("http:")) return DownloadParamType.httpUrl;
689            if(s.startsWith("file:")) return DownloadParamType.fileUrl;
690            String coorPattern = "\\s*[+-]?[0-9]+(\\.[0-9]+)?\\s*";
691            if(s.matches(coorPattern+"(,"+coorPattern+"){3}")) return DownloadParamType.bounds;
692            // everything else must be a file name
693            return DownloadParamType.fileName;
694        }
695    
696        /**
697         * Download area specified on the command line as OSM URL.
698         * @param rawGps Flag to download raw GPS tracks
699         * @param s The URL parameter
700         */
701        private static void downloadFromParamHttp(final boolean rawGps, String s) {
702            final Bounds b = OsmUrlToBounds.parse(s);
703            if (b == null) {
704                JOptionPane.showMessageDialog(
705                        Main.parent,
706                        tr("Ignoring malformed URL: \"{0}\"", s),
707                        tr("Warning"),
708                        JOptionPane.WARNING_MESSAGE
709                        );
710            } else {
711                downloadFromParamBounds(rawGps, b);
712            }
713        }
714    
715        /**
716         * Download area specified on the command line as bounds string.
717         * @param rawGps Flag to download raw GPS tracks
718         * @param s The bounds parameter
719         */
720        private static void downloadFromParamBounds(final boolean rawGps, String s) {
721            final StringTokenizer st = new StringTokenizer(s, ",");
722            if (st.countTokens() == 4) {
723                Bounds b = new Bounds(
724                        new LatLon(Double.parseDouble(st.nextToken()),Double.parseDouble(st.nextToken())),
725                        new LatLon(Double.parseDouble(st.nextToken()),Double.parseDouble(st.nextToken()))
726                        );
727                downloadFromParamBounds(rawGps, b);
728            }
729        }
730    
731        /**
732         * Download area specified as Bounds value.
733         * @param rawGps Flag to download raw GPS tracks
734         * @param b The bounds value
735         * @see #downloadFromParamBounds(boolean, String)
736         * @see #downloadFromParamHttp
737         */
738        private static void downloadFromParamBounds(final boolean rawGps, Bounds b) {
739            DownloadTask task = rawGps ? new DownloadGpsTask() : new DownloadOsmTask();
740            // asynchronously launch the download task ...
741            Future<?> future = task.download(true, b, null);
742            // ... and the continuation when the download is finished (this will wait for the download to finish)
743            Main.worker.execute(new PostDownloadHandler(task, future));
744        }
745    
746        public static void determinePlatformHook() {
747            String os = System.getProperty("os.name");
748            if (os == null) {
749                System.err.println("Your operating system has no name, so I'm guessing its some kind of *nix.");
750                platform = new PlatformHookUnixoid();
751            } else if (os.toLowerCase().startsWith("windows")) {
752                platform = new PlatformHookWindows();
753            } else if (os.equals("Linux") || os.equals("Solaris") ||
754                    os.equals("SunOS") || os.equals("AIX") ||
755                    os.equals("FreeBSD") || os.equals("NetBSD") || os.equals("OpenBSD")) {
756                platform = new PlatformHookUnixoid();
757            } else if (os.toLowerCase().startsWith("mac os x")) {
758                platform = new PlatformHookOsx();
759            } else {
760                System.err.println("I don't know your operating system '"+os+"', so I'm guessing its some kind of *nix.");
761                platform = new PlatformHookUnixoid();
762            }
763        }
764    
765        private static class WindowPositionSizeListener extends WindowAdapter implements
766        ComponentListener {
767            @Override
768            public void windowStateChanged(WindowEvent e) {
769                Main.windowState = e.getNewState();
770            }
771    
772            @Override
773            public void componentHidden(ComponentEvent e) {
774            }
775    
776            @Override
777            public void componentMoved(ComponentEvent e) {
778                handleComponentEvent(e);
779            }
780    
781            @Override
782            public void componentResized(ComponentEvent e) {
783                handleComponentEvent(e);
784            }
785    
786            @Override
787            public void componentShown(ComponentEvent e) {
788            }
789    
790            private void handleComponentEvent(ComponentEvent e) {
791                Component c = e.getComponent();
792                if (c instanceof JFrame && c.isVisible() && Main.windowState == JFrame.NORMAL) {
793                    Main.geometry = new WindowGeometry((JFrame) c);
794                }
795            }
796        }
797        public static void addListener() {
798            parent.addComponentListener(new WindowPositionSizeListener());
799            ((JFrame)parent).addWindowStateListener(new WindowPositionSizeListener());
800        }
801    
802        public static void checkJava6() {
803            String version = System.getProperty("java.version");
804            if (version != null) {
805                if (version.startsWith("1.6") || version.startsWith("6") ||
806                        version.startsWith("1.7") || version.startsWith("7"))
807                    return;
808                if (version.startsWith("1.5") || version.startsWith("5")) {
809                    JLabel ho = new JLabel("<html>"+
810                            tr("<h2>JOSM requires Java version 6.</h2>"+
811                                    "Detected Java version: {0}.<br>"+
812                                    "You can <ul><li>update your Java (JRE) or</li>"+
813                                    "<li>use an earlier (Java 5 compatible) version of JOSM.</li></ul>"+
814                                    "More Info:", version)+"</html>");
815                    JTextArea link = new JTextArea("http://josm.openstreetmap.de/wiki/Help/SystemRequirements");
816                    link.setEditable(false);
817                    link.setBackground(panel.getBackground());
818                    JPanel panel = new JPanel(new GridBagLayout());
819                    GridBagConstraints gbc = new GridBagConstraints();
820                    gbc.gridwidth = GridBagConstraints.REMAINDER;
821                    gbc.anchor = GridBagConstraints.WEST;
822                    gbc.weightx = 1.0;
823                    panel.add(ho, gbc);
824                    panel.add(link, gbc);
825                    final String EXIT = tr("Exit JOSM");
826                    final String CONTINUE = tr("Continue, try anyway");
827                    int ret = JOptionPane.showOptionDialog(null, panel, tr("Error"), JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE, null, new String[] {EXIT, CONTINUE}, EXIT);
828                    if (ret == 0) {
829                        System.exit(0);
830                    }
831                    return;
832                }
833            }
834            System.err.println("Error: Could not recognize Java Version: "+version);
835        }
836    
837        /* ----------------------------------------------------------------------------------------- */
838        /* projection handling  - Main is a registry for a single, global projection instance        */
839        /*                                                                                           */
840        /* TODO: For historical reasons the registry is implemented by Main. An alternative approach */
841        /* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class.     */
842        /* ----------------------------------------------------------------------------------------- */
843        /**
844         * The projection method used.
845         * use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
846         * Use {@link #setProjection(Projection)} in order to trigger a projection change event.
847         */
848        private static Projection proj;
849    
850        /**
851         * Replies the current projection.
852         *
853         * @return the currently active projection
854         */
855        public static Projection getProjection() {
856            return proj;
857        }
858    
859        /**
860         * Sets the current projection
861         *
862         * @param p the projection
863         */
864        public static void setProjection(Projection p) {
865            CheckParameterUtil.ensureParameterNotNull(p);
866            Projection oldValue = proj;
867            Bounds b = isDisplayingMapView() ? map.mapView.getRealBounds() : null;
868            proj = p;
869            fireProjectionChanged(oldValue, proj, b);
870        }
871    
872        /*
873         * Keep WeakReferences to the listeners. This relieves clients from the burden of
874         * explicitly removing the listeners and allows us to transparently register every
875         * created dataset as projection change listener.
876         */
877        private static final ArrayList<WeakReference<ProjectionChangeListener>> listeners = new ArrayList<WeakReference<ProjectionChangeListener>>();
878    
879        private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) {
880            if (newValue == null ^ oldValue == null
881                    || (newValue != null && oldValue != null && !Utils.equal(newValue.toCode(), oldValue.toCode()))) {
882    
883                synchronized(Main.class) {
884                    Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
885                    while(it.hasNext()){
886                        WeakReference<ProjectionChangeListener> wr = it.next();
887                        ProjectionChangeListener listener = wr.get();
888                        if (listener == null) {
889                            it.remove();
890                            continue;
891                        }
892                        listener.projectionChanged(oldValue, newValue);
893                    }
894                }
895                if (newValue != null && oldBounds != null) {
896                    Main.map.mapView.zoomTo(oldBounds);
897                }
898                /* TODO - remove layers with fixed projection */
899            }
900        }
901    
902        /**
903         * Register a projection change listener
904         *
905         * @param listener the listener. Ignored if <code>null</code>.
906         */
907        public static void addProjectionChangeListener(ProjectionChangeListener listener) {
908            if (listener == null) return;
909            synchronized (Main.class) {
910                for (WeakReference<ProjectionChangeListener> wr : listeners) {
911                    // already registered ? => abort
912                    if (wr.get() == listener) return;
913                }
914                listeners.add(new WeakReference<ProjectionChangeListener>(listener));
915            }
916        }
917    
918        /**
919         * Removes a projection change listener
920         *
921         * @param listener the listener. Ignored if <code>null</code>.
922         */
923        public static void removeProjectionChangeListener(ProjectionChangeListener listener) {
924            if (listener == null) return;
925            synchronized(Main.class){
926                Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
927                while(it.hasNext()){
928                    WeakReference<ProjectionChangeListener> wr = it.next();
929                    // remove the listener - and any other listener which god garbage
930                    // collected in the meantime
931                    if (wr.get() == null || wr.get() == listener) {
932                        it.remove();
933                    }
934                }
935            }
936        }
937    }