001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.gui.download;
003    
004    import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005    import static org.openstreetmap.josm.tools.I18n.tr;
006    
007    import java.awt.BorderLayout;
008    import java.awt.Color;
009    import java.awt.Component;
010    import java.awt.Dimension;
011    import java.awt.FlowLayout;
012    import java.awt.Font;
013    import java.awt.Graphics;
014    import java.awt.GridBagLayout;
015    import java.awt.event.ActionEvent;
016    import java.awt.event.ActionListener;
017    import java.awt.event.InputEvent;
018    import java.awt.event.KeyEvent;
019    import java.awt.event.WindowAdapter;
020    import java.awt.event.WindowEvent;
021    import java.util.ArrayList;
022    import java.util.List;
023    
024    import javax.swing.AbstractAction;
025    import javax.swing.JCheckBox;
026    import javax.swing.JComponent;
027    import javax.swing.JDialog;
028    import javax.swing.JLabel;
029    import javax.swing.JOptionPane;
030    import javax.swing.JPanel;
031    import javax.swing.JTabbedPane;
032    import javax.swing.KeyStroke;
033    
034    import org.openstreetmap.josm.Main;
035    import org.openstreetmap.josm.actions.ExpertToggleAction;
036    import org.openstreetmap.josm.data.Bounds;
037    import org.openstreetmap.josm.gui.MapView;
038    import org.openstreetmap.josm.gui.SideButton;
039    import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
040    import org.openstreetmap.josm.gui.help.HelpUtil;
041    import org.openstreetmap.josm.plugins.PluginHandler;
042    import org.openstreetmap.josm.tools.GBC;
043    import org.openstreetmap.josm.tools.ImageProvider;
044    import org.openstreetmap.josm.tools.InputMapUtils;
045    import org.openstreetmap.josm.tools.OsmUrlToBounds;
046    import org.openstreetmap.josm.tools.Utils;
047    import org.openstreetmap.josm.tools.WindowGeometry;
048    
049    /**
050     *
051     */
052    public class DownloadDialog extends JDialog  {
053        /** the unique instance of the download dialog */
054        static private DownloadDialog instance;
055    
056        /**
057         * Replies the unique instance of the download dialog
058         *
059         * @return the unique instance of the download dialog
060         */
061        static public DownloadDialog getInstance() {
062            if (instance == null) {
063                instance = new DownloadDialog(Main.parent);
064            }
065            return instance;
066        }
067    
068        protected final List<DownloadSelection> downloadSelections = new ArrayList<DownloadSelection>();
069        protected final JTabbedPane tpDownloadAreaSelectors = new JTabbedPane();
070        protected JCheckBox cbNewLayer;
071        protected JCheckBox cbStartup;
072        protected final JLabel sizeCheck = new JLabel();
073        protected Bounds currentBounds = null;
074        protected boolean canceled;
075    
076        protected JCheckBox cbDownloadOsmData;
077        protected JCheckBox cbDownloadGpxData;
078        /** the download action and button */
079        private DownloadAction actDownload;
080        protected SideButton btnDownload;
081    
082        private void makeCheckBoxRespondToEnter(JCheckBox cb) {
083            cb.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "doDownload");
084            cb.getActionMap().put("doDownload", actDownload);
085        }
086    
087        protected JPanel buildMainPanel() {
088            JPanel pnl = new JPanel();
089            pnl.setLayout(new GridBagLayout());
090    
091            // adding the download tasks
092            pnl.add(new JLabel(tr("Data Sources and Types:")), GBC.std().insets(5,5,1,5));
093            cbDownloadOsmData = new JCheckBox(tr("OpenStreetMap data"), true);
094            cbDownloadOsmData.setToolTipText(tr("Select to download OSM data in the selected download area."));
095            pnl.add(cbDownloadOsmData,  GBC.std().insets(1,5,1,5));
096            cbDownloadGpxData = new JCheckBox(tr("Raw GPS data"));
097            cbDownloadGpxData.setToolTipText(tr("Select to download GPS traces in the selected download area."));
098            pnl.add(cbDownloadGpxData,  GBC.eol().insets(5,5,1,5));
099    
100            // hook for subclasses
101            buildMainPanelAboveDownloadSelections(pnl);
102    
103            // predefined download selections
104            downloadSelections.add(new SlippyMapChooser());
105            downloadSelections.add(new BookmarkSelection());
106            downloadSelections.add(new BoundingBoxSelection());
107            downloadSelections.add(new PlaceSelection());
108            downloadSelections.add(new TileSelection());
109    
110            // add selections from plugins
111            PluginHandler.addDownloadSelection(downloadSelections);
112    
113            // now everybody may add their tab to the tabbed pane
114            // (not done right away to allow plugins to remove one of
115            // the default selectors!)
116            for (DownloadSelection s : downloadSelections) {
117                s.addGui(this);
118            }
119    
120            pnl.add(tpDownloadAreaSelectors, GBC.eol().fill());
121    
122            try {
123                tpDownloadAreaSelectors.setSelectedIndex(Main.pref.getInteger("download.tab", 0));
124            } catch (Exception ex) {
125                Main.pref.putInteger("download.tab", 0);
126            }
127    
128            Font labelFont = sizeCheck.getFont();
129            sizeCheck.setFont(labelFont.deriveFont(Font.PLAIN, labelFont.getSize()));
130    
131            cbNewLayer = new JCheckBox(tr("Download as new layer"));
132            cbNewLayer.setToolTipText(tr("<html>Select to download data into a new data layer.<br>"
133                    +"Unselect to download into the currently active data layer.</html>"));
134    
135            cbStartup = new JCheckBox(tr("Open this dialog on startup"));
136            cbStartup.setToolTipText(tr("<html>Autostart ''Download from OSM'' dialog every time JOSM is started.<br>You can open it manually from File menu or toolbar.</html>"));
137            cbStartup.addActionListener(new ActionListener() {
138                public void actionPerformed(ActionEvent e) {
139                     Main.pref.put("download.autorun", cbStartup.isSelected());
140                }});
141    
142            pnl.add(cbNewLayer, GBC.std().anchor(GBC.WEST).insets(5,5,5,5));
143            pnl.add(cbStartup, GBC.std().anchor(GBC.WEST).insets(15,5,5,5));
144    
145            pnl.add(sizeCheck,  GBC.eol().anchor(GBC.EAST).insets(5,5,5,2));
146            
147            if (!ExpertToggleAction.isExpert()) {
148                JLabel infoLabel  = new JLabel(tr("Use left click&drag to select area, arrows or right mouse button to scroll map, wheel or +/- to zoom."));
149                pnl.add(infoLabel,GBC.eol().anchor(GBC.SOUTH).insets(0,0,0,0));
150            }
151            return pnl;
152        }
153    
154        /* This should not be necessary, but if not here, repaint is not always correct in SlippyMap! */
155        public void paint(Graphics g) {
156            tpDownloadAreaSelectors.getSelectedComponent().paint(g);
157            super.paint(g);
158        }
159    
160        protected JPanel buildButtonPanel() {
161            JPanel pnl = new JPanel();
162            pnl.setLayout(new FlowLayout());
163    
164            // -- download button
165            pnl.add(btnDownload = new SideButton(actDownload = new DownloadAction()));
166            InputMapUtils.enableEnter(btnDownload);
167            
168            makeCheckBoxRespondToEnter(cbDownloadGpxData);
169            makeCheckBoxRespondToEnter(cbDownloadOsmData);
170            makeCheckBoxRespondToEnter(cbNewLayer);
171    
172            // -- cancel button
173            SideButton btnCancel;
174            CancelAction actCancel = new CancelAction();
175            pnl.add(btnCancel = new SideButton(actCancel));
176            InputMapUtils.enableEnter(btnCancel);
177    
178            // -- cancel on ESC
179            getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "cancel");
180            getRootPane().getActionMap().put("cancel", actCancel);
181    
182            // -- help button
183            SideButton btnHelp;
184            pnl.add(btnHelp = new SideButton(new ContextSensitiveHelpAction(ht("/Action/Download"))));
185            InputMapUtils.enableEnter(btnHelp);
186    
187            return pnl;
188        }
189    
190        public DownloadDialog(Component parent) {
191            super(JOptionPane.getFrameForComponent(parent),tr("Download"), ModalityType.DOCUMENT_MODAL);
192            getContentPane().setLayout(new BorderLayout());
193            getContentPane().add(buildMainPanel(), BorderLayout.CENTER);
194            getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH);
195    
196            getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
197                    KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), "checkClipboardContents");
198    
199            getRootPane().getActionMap().put("checkClipboardContents", new AbstractAction() {
200                public void actionPerformed(ActionEvent e) {
201                    String clip = Utils.getClipboardContent();
202                    if (clip == null) {
203                        return;
204                    }
205                    Bounds b = OsmUrlToBounds.parse(clip);
206                    if (b != null) {
207                        boundingBoxChanged(new Bounds(b), null);
208                    }
209                }
210            });
211            HelpUtil.setHelpContext(getRootPane(), ht("/Action/Download"));
212            addWindowListener(new WindowEventHandler());
213            restoreSettings();
214        }
215    
216        private void updateSizeCheck() {
217            if (currentBounds == null) {
218                sizeCheck.setText(tr("No area selected yet"));
219                sizeCheck.setForeground(Color.darkGray);
220            } else if (currentBounds.getArea() > Main.pref.getDouble("osm-server.max-request-area", 0.25)) {
221                sizeCheck.setText(tr("Download area too large; will probably be rejected by server"));
222                sizeCheck.setForeground(Color.red);
223            } else {
224                sizeCheck.setText(tr("Download area ok, size probably acceptable to server"));
225                sizeCheck.setForeground(Color.darkGray);
226            }
227        }
228    
229        /**
230         * Distributes a "bounding box changed" from one DownloadSelection
231         * object to the others, so they may update or clear their input
232         * fields.
233         *
234         * @param eventSource - the DownloadSelection object that fired this notification.
235         */
236        public void boundingBoxChanged(Bounds b, DownloadSelection eventSource) {
237            this.currentBounds = b;
238            for (DownloadSelection s : downloadSelections) {
239                if (s != eventSource) {
240                    s.setDownloadArea(currentBounds);
241                }
242            }
243            updateSizeCheck();
244        }
245    
246        /**
247         * Invoked by
248         * @param b
249         */
250        public void startDownload(Bounds b) {
251            this.currentBounds = b;
252            actDownload.run();
253        }
254    
255        /**
256         * Replies true if the user selected to download OSM data
257         *
258         * @return true if the user selected to download OSM data
259         */
260        public boolean isDownloadOsmData() {
261            return cbDownloadOsmData.isSelected();
262        }
263    
264        /**
265         * Replies true if the user selected to download GPX data
266         *
267         * @return true if the user selected to download GPX data
268         */
269        public boolean isDownloadGpxData() {
270            return cbDownloadGpxData.isSelected();
271        }
272    
273        /**
274         * Replies true if the user requires to download into a new layer
275         *
276         * @return true if the user requires to download into a new layer
277         */
278        public boolean isNewLayerRequired() {
279            return cbNewLayer.isSelected();
280        }
281    
282        /**
283         * Adds a new download area selector to the download dialog
284         *
285         * @param selector the download are selector
286         * @param displayName the display name of the selector
287         */
288        public void addDownloadAreaSelector(JPanel selector, String displayName) {
289            tpDownloadAreaSelectors.add(displayName, selector);
290        }
291    
292        /**
293         * Remembers the current settings in the download dialog
294         *
295         */
296        public void rememberSettings() {
297            Main.pref.put("download.tab", Integer.toString(tpDownloadAreaSelectors.getSelectedIndex()));
298            Main.pref.put("download.osm", cbDownloadOsmData.isSelected());
299            Main.pref.put("download.gps", cbDownloadGpxData.isSelected());
300            Main.pref.put("download.newlayer", cbNewLayer.isSelected());
301            if (currentBounds != null) {
302                Main.pref.put("osm-download.bounds", currentBounds.encodeAsString(";"));
303            }
304        }
305    
306        public void restoreSettings() {
307            cbDownloadOsmData.setSelected(Main.pref.getBoolean("download.osm", true));
308            cbDownloadGpxData.setSelected(Main.pref.getBoolean("download.gps", false));
309            cbNewLayer.setSelected(Main.pref.getBoolean("download.newlayer", false));
310            cbStartup.setSelected( isAutorunEnabled() ); 
311            int idx = Main.pref.getInteger("download.tab", 0);
312            if (idx < 0 || idx > tpDownloadAreaSelectors.getTabCount()) {
313                idx = 0;
314            }
315            tpDownloadAreaSelectors.setSelectedIndex(idx);
316    
317            if (Main.isDisplayingMapView()) {
318                MapView mv = Main.map.mapView;
319                currentBounds = new Bounds(
320                        mv.getLatLon(0, mv.getHeight()),
321                        mv.getLatLon(mv.getWidth(), 0)
322                );
323                boundingBoxChanged(currentBounds,null);
324            }
325            else if (!Main.pref.get("osm-download.bounds").isEmpty()) {
326                // read the bounding box from the preferences
327                try {
328                    currentBounds = new Bounds(Main.pref.get("osm-download.bounds"), ";");
329                    boundingBoxChanged(currentBounds,null);
330                }
331                catch (Exception e) {
332                    e.printStackTrace();
333                }
334            }
335        }
336    
337        public static boolean isAutorunEnabled() {
338            //String autorun=Main.pref.get("download.autorun",null);
339            //boolean expert=ExpertToggleAction.isExpert();
340            //return (autorun==null && !expert) || "true".equals(autorun) ;
341            return Main.pref.getBoolean("download.autorun",false);
342        }
343    
344        public static void autostartIfNeeded() {
345            if (isAutorunEnabled()) {
346                Main.main.menu.download.actionPerformed(null);
347            }
348        }
349    
350        /**
351         * Replies the currently selected download area. May be null, if no download area is selected
352         * yet.
353         */
354        public Bounds getSelectedDownloadArea() {
355            return currentBounds;
356        }
357    
358        @Override
359        public void setVisible(boolean visible) {
360            if (visible) {
361                new WindowGeometry(
362                        getClass().getName() + ".geometry",
363                        WindowGeometry.centerInWindow(
364                                getParent(),
365                                new Dimension(1000,600)
366                        )
367                ).applySafe(this);
368            } else if (!visible && isShowing()){
369                new WindowGeometry(this).remember(getClass().getName() + ".geometry");
370            }
371            super.setVisible(visible);
372        }
373    
374        /**
375         * Replies true if the dialog was canceled
376         *
377         * @return true if the dialog was canceled
378         */
379        public boolean isCanceled() {
380            return canceled;
381        }
382    
383        protected void setCanceled(boolean canceled) {
384            this.canceled = canceled;
385        }
386    
387        protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
388        }
389    
390        class CancelAction extends AbstractAction {
391            public CancelAction() {
392                putValue(NAME, tr("Cancel"));
393                putValue(SMALL_ICON, ImageProvider.get("cancel"));
394                putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort downloading"));
395            }
396    
397            public void run() {
398                setCanceled(true);
399                setVisible(false);
400            }
401    
402            public void actionPerformed(ActionEvent e) {
403                run();
404            }
405        }
406    
407        class DownloadAction extends AbstractAction {
408            public DownloadAction() {
409                putValue(NAME, tr("Download"));
410                putValue(SMALL_ICON, ImageProvider.get("download"));
411                putValue(SHORT_DESCRIPTION, tr("Click to download the currently selected area"));
412            }
413    
414            public void run() {
415                if (currentBounds == null) {
416                    JOptionPane.showMessageDialog(
417                            DownloadDialog.this,
418                            tr("Please select a download area first."),
419                            tr("Error"),
420                            JOptionPane.ERROR_MESSAGE
421                    );
422                    return;
423                }
424                if (!isDownloadOsmData() && !isDownloadGpxData()) {
425                    JOptionPane.showMessageDialog(
426                            DownloadDialog.this,
427                            tr("<html>Neither <strong>{0}</strong> nor <strong>{1}</strong> is enabled.<br>"
428                                    + "Please choose to either download OSM data, or GPX data, or both.</html>",
429                                    cbDownloadOsmData.getText(),
430                                    cbDownloadGpxData.getText()
431                            ),
432                            tr("Error"),
433                            JOptionPane.ERROR_MESSAGE
434                    );
435                    return;
436                }
437                setCanceled(false);
438                setVisible(false);
439            }
440    
441            public void actionPerformed(ActionEvent e) {
442                run();
443            }
444        }
445    
446        class WindowEventHandler extends WindowAdapter {
447            @Override
448            public void windowClosing(WindowEvent e) {
449                new CancelAction().run();
450            }
451    
452            @Override
453            public void windowActivated(WindowEvent e) {
454                btnDownload.requestFocusInWindow();
455            }
456        }
457    }