source: josm/trunk/src/org/openstreetmap/josm/gui/download/DownloadDialog.java@ 17318

Last change on this file since 17318 was 16928, checked in by simon04, 4 years ago

see #7638 - Download dialog: use bbox icon in status bar (icon by Klumbumbus)

  • Property svn:eol-style set to native
File size: 30.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.download;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.BorderLayout;
8import java.awt.Color;
9import java.awt.Component;
10import java.awt.Dimension;
11import java.awt.FlowLayout;
12import java.awt.GridBagLayout;
13import java.awt.event.ActionEvent;
14import java.awt.event.ComponentAdapter;
15import java.awt.event.ComponentEvent;
16import java.awt.event.InputEvent;
17import java.awt.event.KeyEvent;
18import java.awt.event.WindowAdapter;
19import java.awt.event.WindowEvent;
20import java.util.ArrayList;
21import java.util.List;
22import java.util.Optional;
23import java.util.stream.Collectors;
24import java.util.stream.IntStream;
25
26import javax.swing.AbstractAction;
27import javax.swing.BorderFactory;
28import javax.swing.Box;
29import javax.swing.Icon;
30import javax.swing.JButton;
31import javax.swing.JCheckBox;
32import javax.swing.JComponent;
33import javax.swing.JDialog;
34import javax.swing.JLabel;
35import javax.swing.JPanel;
36import javax.swing.JSplitPane;
37import javax.swing.JTabbedPane;
38import javax.swing.KeyStroke;
39import javax.swing.event.ChangeEvent;
40import javax.swing.event.ChangeListener;
41
42import org.openstreetmap.josm.actions.ExpertToggleAction;
43import org.openstreetmap.josm.data.Bounds;
44import org.openstreetmap.josm.data.coor.ILatLon;
45import org.openstreetmap.josm.data.coor.LatLon;
46import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager;
47import org.openstreetmap.josm.data.coor.conversion.DMSCoordinateFormat;
48import org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat;
49import org.openstreetmap.josm.data.preferences.BooleanProperty;
50import org.openstreetmap.josm.data.preferences.IntegerProperty;
51import org.openstreetmap.josm.data.preferences.StringProperty;
52import org.openstreetmap.josm.gui.MainApplication;
53import org.openstreetmap.josm.gui.MapView;
54import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
55import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
56import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
57import org.openstreetmap.josm.gui.help.HelpUtil;
58import org.openstreetmap.josm.gui.layer.OsmDataLayer;
59import org.openstreetmap.josm.gui.util.GuiHelper;
60import org.openstreetmap.josm.gui.util.WindowGeometry;
61import org.openstreetmap.josm.gui.widgets.ImageLabel;
62import org.openstreetmap.josm.io.NetworkManager;
63import org.openstreetmap.josm.io.OnlineResource;
64import org.openstreetmap.josm.plugins.PluginHandler;
65import org.openstreetmap.josm.spi.preferences.Config;
66import org.openstreetmap.josm.tools.GBC;
67import org.openstreetmap.josm.tools.ImageProvider;
68import org.openstreetmap.josm.tools.InputMapUtils;
69import org.openstreetmap.josm.tools.JosmRuntimeException;
70import org.openstreetmap.josm.tools.ListenerList;
71import org.openstreetmap.josm.tools.Logging;
72import org.openstreetmap.josm.tools.OsmUrlToBounds;
73
74/**
75 * Dialog displayed to the user to download mapping data.
76 */
77public class DownloadDialog extends JDialog {
78
79 private static final IntegerProperty DOWNLOAD_TAB = new IntegerProperty("download.tab", 0);
80 private static final StringProperty DOWNLOAD_SOURCE_TAB = new StringProperty("download.source.tab", OSMDownloadSource.SIMPLE_NAME);
81 private static final BooleanProperty DOWNLOAD_AUTORUN = new BooleanProperty("download.autorun", false);
82 private static final BooleanProperty DOWNLOAD_ZOOMTODATA = new BooleanProperty("download.zoomtodata", true);
83
84 /** the unique instance of the download dialog */
85 private static DownloadDialog instance;
86
87 /**
88 * Replies the unique instance of the download dialog
89 *
90 * @return the unique instance of the download dialog
91 */
92 public static synchronized DownloadDialog getInstance() {
93 if (instance == null) {
94 instance = new DownloadDialog(MainApplication.getMainFrame());
95 }
96 return instance;
97 }
98
99 private static final ListenerList<DownloadSourceListener> downloadSourcesListeners = ListenerList.create();
100 private static final List<DownloadSource<?>> downloadSources = new ArrayList<>();
101 static {
102 // add default download sources
103 addDownloadSource(new OSMDownloadSource());
104 addDownloadSource(new OverpassDownloadSource());
105 }
106
107 protected final transient List<DownloadSelection> downloadSelections = new ArrayList<>();
108 protected final JTabbedPane tpDownloadAreaSelectors = new JTabbedPane();
109 protected final DownloadSourceTabs downloadSourcesTab = new DownloadSourceTabs();
110
111 private final ImageLabel latText;
112 private final ImageLabel lonText;
113 private final ImageLabel bboxText;
114 {
115 final LatLon sample = new LatLon(90, 180);
116 final ICoordinateFormat sampleFormat = DMSCoordinateFormat.INSTANCE;
117 final Color background = new JPanel().getBackground();
118 latText = new ImageLabel("lat", null, sampleFormat.latToString(sample).length(), background);
119 lonText = new ImageLabel("lon", null, sampleFormat.lonToString(sample).length(), background);
120 bboxText = new ImageLabel("bbox", null, sampleFormat.toString(sample, "").length() * 2, background);
121 }
122
123 protected JCheckBox cbStartup;
124 protected JCheckBox cbZoomToDownloadedData;
125 protected SlippyMapChooser slippyMapChooser;
126 protected JPanel mainPanel;
127 protected DownloadDialogSplitPane dialogSplit;
128
129 /*
130 * Keep the reference globally to avoid having it garbage collected
131 */
132 protected final transient ExpertToggleAction.ExpertModeChangeListener expertListener =
133 getExpertModeListenerForDownloadSources();
134 protected transient Bounds currentBounds;
135 protected boolean canceled;
136
137 protected JButton btnDownload;
138 protected JButton btnDownloadNewLayer;
139 protected JButton btnCancel;
140 protected JButton btnHelp;
141
142 /**
143 * Builds the main panel of the dialog.
144 * @return The panel of the dialog.
145 */
146 protected final JPanel buildMainPanel() {
147 mainPanel = new JPanel(new GridBagLayout());
148
149 // must be created before hook
150 slippyMapChooser = new SlippyMapChooser();
151
152 // predefined download selections
153 downloadSelections.add(slippyMapChooser);
154 downloadSelections.add(new BookmarkSelection());
155 downloadSelections.add(new BoundingBoxSelection());
156 downloadSelections.add(new PlaceSelection());
157 downloadSelections.add(new TileSelection());
158
159 // add selections from plugins
160 PluginHandler.addDownloadSelection(downloadSelections);
161
162 // register all default download selections
163 for (DownloadSelection s : downloadSelections) {
164 s.addGui(this);
165 }
166
167 // allow to collapse the panes, but reserve some space for tabs
168 downloadSourcesTab.setMinimumSize(new Dimension(0, 25));
169 tpDownloadAreaSelectors.setMinimumSize(new Dimension(0, 0));
170
171 dialogSplit = new DownloadDialogSplitPane(
172 downloadSourcesTab,
173 tpDownloadAreaSelectors);
174
175 ChangeListener tabChangedListener = getDownloadSourceTabChangeListener();
176 tabChangedListener.stateChanged(new ChangeEvent(downloadSourcesTab));
177 downloadSourcesTab.addChangeListener(tabChangedListener);
178
179 mainPanel.add(dialogSplit, GBC.eol().fill());
180
181 JPanel statusBarPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
182 statusBarPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY));
183 statusBarPanel.add(latText);
184 statusBarPanel.add(lonText);
185 statusBarPanel.add(bboxText);
186 mainPanel.add(statusBarPanel, GBC.eol().fill(GBC.HORIZONTAL));
187 ExpertToggleAction.addVisibilitySwitcher(statusBarPanel);
188
189 cbStartup = new JCheckBox(tr("Open this dialog on startup"));
190 cbStartup.setToolTipText(
191 tr("<html>Autostart ''Download from OSM'' dialog every time JOSM is started.<br>" +
192 "You can open it manually from File menu or toolbar.</html>"));
193 cbStartup.addActionListener(e -> DOWNLOAD_AUTORUN.put(cbStartup.isSelected()));
194
195 JLabel iconZoomToDownloadedData = new JLabel(ImageProvider.get("dialogs/autoscale/download", ImageProvider.ImageSizes.SMALLICON));
196 cbZoomToDownloadedData = new JCheckBox(tr("Zoom to downloaded data"));
197 cbZoomToDownloadedData.setToolTipText(tr("Select to zoom to entire newly downloaded data."));
198
199 JPanel checkboxPanel = new JPanel(new FlowLayout());
200 checkboxPanel.add(cbStartup);
201 checkboxPanel.add(Box.createHorizontalStrut(6));
202 checkboxPanel.add(iconZoomToDownloadedData);
203 checkboxPanel.add(cbZoomToDownloadedData);
204 mainPanel.add(checkboxPanel, GBC.eol());
205
206 ExpertToggleAction.addVisibilitySwitcher(cbZoomToDownloadedData);
207 ExpertToggleAction.addVisibilitySwitcher(iconZoomToDownloadedData);
208
209 JLabel infoLabel = new JLabel(
210 tr("Use left click&drag to select area, arrows or right mouse button to scroll map, wheel or +/- to zoom."));
211 mainPanel.add(infoLabel, GBC.eol().anchor(GBC.CENTER).insets(0, 0, 0, 0));
212
213 ExpertToggleAction.addExpertModeChangeListener(isExpert -> infoLabel.setVisible(!isExpert), true);
214
215 return mainPanel;
216 }
217
218 /**
219 * Builds the button pane of the dialog.
220 * @return The button panel of the dialog.
221 */
222 protected final JPanel buildButtonPanel() {
223 btnDownload = new JButton(new DownloadAction(false));
224 btnDownloadNewLayer = new JButton(new DownloadAction(true));
225 btnCancel = new JButton(new CancelAction());
226 btnHelp = new JButton(
227 new ContextSensitiveHelpAction(getRootPane().getClientProperty("help").toString()));
228
229 JPanel pnl = new JPanel(new FlowLayout());
230
231 pnl.add(btnDownload);
232 pnl.add(btnDownloadNewLayer);
233 pnl.add(btnCancel);
234 pnl.add(btnHelp);
235
236 InputMapUtils.enableEnter(btnDownload);
237 InputMapUtils.enableEnter(btnCancel);
238 InputMapUtils.addEscapeAction(getRootPane(), btnCancel.getAction());
239 InputMapUtils.enableEnter(btnHelp);
240
241 InputMapUtils.addEnterActionWhenAncestor(cbStartup, btnDownload.getAction());
242 InputMapUtils.addEnterActionWhenAncestor(cbZoomToDownloadedData, btnDownload.getAction());
243 InputMapUtils.addCtrlEnterAction(pnl, btnDownload.getAction());
244
245 return pnl;
246 }
247
248 /**
249 * Constructs a new {@code DownloadDialog}.
250 * @param parent the parent component
251 */
252 public DownloadDialog(Component parent) {
253 this(parent, ht("/Action/Download"));
254 }
255
256 /**
257 * Constructs a new {@code DownloadDialog}.
258 * @param parent the parent component
259 * @param helpTopic the help topic to assign
260 */
261 public DownloadDialog(Component parent, String helpTopic) {
262 super(GuiHelper.getFrameForComponent(parent), tr("Download"), ModalityType.DOCUMENT_MODAL);
263 HelpUtil.setHelpContext(getRootPane(), helpTopic);
264 getContentPane().setLayout(new BorderLayout());
265 getContentPane().add(buildMainPanel(), BorderLayout.CENTER);
266 getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH);
267
268 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
269 KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK), "checkClipboardContents");
270
271 getRootPane().getActionMap().put("checkClipboardContents", new AbstractAction() {
272 @Override
273 public void actionPerformed(ActionEvent e) {
274 String clip = ClipboardUtils.getClipboardStringContent();
275 if (clip == null) {
276 return;
277 }
278 Bounds b = OsmUrlToBounds.parse(clip);
279 if (b != null) {
280 boundingBoxChanged(new Bounds(b), null);
281 }
282 }
283 });
284 addWindowListener(new WindowEventHandler());
285 ExpertToggleAction.addExpertModeChangeListener(expertListener);
286 restoreSettings();
287
288 // if no bounding box is selected make sure it is still propagated.
289 if (currentBounds == null) {
290 boundingBoxChanged(null, null);
291 }
292 }
293
294 /**
295 * Distributes a "bounding box changed" from one DownloadSelection
296 * object to the others, so they may update or clear their input fields. Also informs
297 * download sources about the change, so they can react on it.
298 * @param b new current bounds
299 *
300 * @param eventSource - the DownloadSelection object that fired this notification.
301 */
302 public void boundingBoxChanged(Bounds b, DownloadSelection eventSource) {
303 this.currentBounds = b;
304 for (DownloadSelection s : downloadSelections) {
305 if (s != eventSource) {
306 s.setDownloadArea(currentBounds);
307 }
308 }
309
310 for (AbstractDownloadSourcePanel<?> ds : downloadSourcesTab.getAllPanels()) {
311 ds.boundingBoxChanged(b);
312 }
313
314 bboxText.setText(b == null ? "" : String.join(" ",
315 CoordinateFormatManager.getDefaultFormat().toString(b.getMin(), " "),
316 CoordinateFormatManager.getDefaultFormat().toString(b.getMax(), " ")));
317 }
318
319 /**
320 * Updates the coordinates after moving the mouse cursor
321 * @param latLon the coordinates under the mouse cursor
322 */
323 public void mapCursorChanged(ILatLon latLon) {
324 latText.setText(CoordinateFormatManager.getDefaultFormat().latToString(latLon));
325 lonText.setText(CoordinateFormatManager.getDefaultFormat().lonToString(latLon));
326 }
327
328 /**
329 * Starts download for the given bounding box
330 * @param b bounding box to download
331 */
332 public void startDownload(Bounds b) {
333 this.currentBounds = b;
334 startDownload();
335 }
336
337 /**
338 * Starts download.
339 */
340 public void startDownload() {
341 btnDownload.doClick();
342 }
343
344 /**
345 * Replies true if the user requires to zoom to new downloaded data
346 *
347 * @return true if the user requires to zoom to new downloaded data
348 * @since 11658
349 */
350 public boolean isZoomToDownloadedDataRequired() {
351 return cbZoomToDownloadedData.isSelected();
352 }
353
354 /**
355 * Determines if the dialog autorun is enabled in preferences.
356 * @return {@code true} if the download dialog must be open at startup, {@code false} otherwise.
357 */
358 public static boolean isAutorunEnabled() {
359 return DOWNLOAD_AUTORUN.get();
360 }
361
362 /**
363 * Adds a new download area selector to the download dialog.
364 *
365 * @param selector the download are selector.
366 * @param displayName the display name of the selector.
367 */
368 public void addDownloadAreaSelector(JPanel selector, String displayName) {
369 tpDownloadAreaSelectors.add(displayName, selector);
370 }
371
372 /**
373 * Add a listener to get events from the DownloadSelection window
374 *
375 * @param selection The DownloadSelection object to send the Bounds to
376 * @return See {@link List#add}
377 * @since 16684
378 */
379 public boolean addDownloadAreaListener(DownloadSelection selection) {
380 return downloadSelections.add(selection);
381 }
382
383 /**
384 * Remove a listener that was getting events from the DownloadSelection window
385 *
386 * @param selection The DownloadSelection object to not send the Bounds to
387 * @return See {@link List#remove}
388 * @since 16684
389 */
390 public boolean removeDownloadAreaListener(DownloadSelection selection) {
391 return downloadSelections.remove(selection);
392 }
393
394 /**
395 * Adds a new download source to the download dialog if it is not added.
396 *
397 * @param downloadSource The download source to be added.
398 * @param <T> The type of the download data.
399 * @throws JosmRuntimeException If the download source is already added. Note, download sources are
400 * compared by their reference.
401 * @since 12878
402 */
403 public static <T> void addDownloadSource(DownloadSource<T> downloadSource) {
404 if (downloadSources.contains(downloadSource)) {
405 throw new JosmRuntimeException("The download source you are trying to add already exists.");
406 }
407
408 downloadSources.add(downloadSource);
409 downloadSourcesListeners.fireEvent(l -> l.downloadSourceAdded(downloadSource));
410 }
411
412 /**
413 * Remove a download source from the download dialog
414 *
415 * @param downloadSource The download source to be removed.
416 * @return see {@link List#remove}
417 * @since 15542
418 */
419 public static boolean removeDownloadSource(DownloadSource<?> downloadSource) {
420 if (downloadSources.contains(downloadSource)) {
421 return downloadSources.remove(downloadSource);
422 }
423 return false;
424 }
425
426 /**
427 * Refreshes the tile sources.
428 * @since 6364
429 */
430 public final void refreshTileSources() {
431 if (slippyMapChooser != null) {
432 slippyMapChooser.refreshTileSources();
433 }
434 }
435
436 /**
437 * Remembers the current settings in the download dialog.
438 */
439 public void rememberSettings() {
440 DOWNLOAD_TAB.put(tpDownloadAreaSelectors.getSelectedIndex());
441 downloadSourcesTab.getAllPanels().forEach(AbstractDownloadSourcePanel::rememberSettings);
442 downloadSourcesTab.getSelectedPanel().ifPresent(panel -> DOWNLOAD_SOURCE_TAB.put(panel.getSimpleName()));
443 DOWNLOAD_ZOOMTODATA.put(cbZoomToDownloadedData.isSelected());
444 if (currentBounds != null) {
445 Config.getPref().put("osm-download.bounds", currentBounds.encodeAsString(";"));
446 }
447 }
448
449 /**
450 * Restores the previous settings in the download dialog.
451 */
452 public void restoreSettings() {
453 cbStartup.setSelected(isAutorunEnabled());
454 cbZoomToDownloadedData.setSelected(DOWNLOAD_ZOOMTODATA.get());
455
456 try {
457 tpDownloadAreaSelectors.setSelectedIndex(DOWNLOAD_TAB.get());
458 } catch (IndexOutOfBoundsException e) {
459 Logging.trace(e);
460 tpDownloadAreaSelectors.setSelectedIndex(0);
461 }
462
463 downloadSourcesTab.getAllPanels().forEach(AbstractDownloadSourcePanel::restoreSettings);
464 downloadSourcesTab.setSelected(DOWNLOAD_SOURCE_TAB.get());
465
466 if (MainApplication.isDisplayingMapView()) {
467 MapView mv = MainApplication.getMap().mapView;
468 currentBounds = new Bounds(
469 mv.getLatLon(0, mv.getHeight()),
470 mv.getLatLon(mv.getWidth(), 0)
471 );
472 boundingBoxChanged(currentBounds, null);
473 } else {
474 Bounds bounds = getSavedDownloadBounds();
475 if (bounds != null) {
476 currentBounds = bounds;
477 boundingBoxChanged(currentBounds, null);
478 }
479 }
480 }
481
482 /**
483 * Returns the previously saved bounding box from preferences.
484 * @return The bounding box saved in preferences if any, {@code null} otherwise.
485 * @since 6509
486 */
487 public static Bounds getSavedDownloadBounds() {
488 String value = Config.getPref().get("osm-download.bounds");
489 if (!value.isEmpty()) {
490 try {
491 return new Bounds(value, ";");
492 } catch (IllegalArgumentException e) {
493 Logging.warn(e);
494 }
495 }
496 return null;
497 }
498
499 /**
500 * Automatically opens the download dialog, if autorun is enabled.
501 * @see #isAutorunEnabled
502 */
503 public static void autostartIfNeeded() {
504 if (isAutorunEnabled()) {
505 MainApplication.getMenu().download.actionPerformed(null);
506 }
507 }
508
509 /**
510 * Returns an {@link Optional} of the currently selected download area.
511 * @return An {@link Optional} of the currently selected download area.
512 * @since 12574 Return type changed to optional
513 */
514 public Optional<Bounds> getSelectedDownloadArea() {
515 return Optional.ofNullable(currentBounds);
516 }
517
518 @Override
519 public void setVisible(boolean visible) {
520 if (visible) {
521 btnDownloadNewLayer.setEnabled(
522 !MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).isEmpty());
523 new WindowGeometry(
524 getClass().getName() + ".geometry",
525 WindowGeometry.centerInWindow(
526 getParent(),
527 new Dimension(1000, 600)
528 )
529 ).applySafe(this);
530 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
531 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
532 }
533 super.setVisible(visible);
534 }
535
536 /**
537 * Replies true if the dialog was canceled
538 *
539 * @return true if the dialog was canceled
540 */
541 public boolean isCanceled() {
542 return canceled;
543 }
544
545 /**
546 * Gets the global settings of the download dialog.
547 * @param newLayer The flag defining if a new layer must be created for the downloaded data.
548 * @return The {@link DownloadSettings} object that describes the current state of
549 * the download dialog.
550 */
551 public DownloadSettings getDownloadSettings(boolean newLayer) {
552 final Component areaSelector = tpDownloadAreaSelectors.getSelectedComponent();
553 final Bounds slippyMapBounds = areaSelector instanceof SlippyMapBBoxChooser
554 ? ((SlippyMapBBoxChooser) areaSelector).getVisibleMapArea()
555 : null;
556 return new DownloadSettings(currentBounds, slippyMapBounds, newLayer, isZoomToDownloadedDataRequired());
557 }
558
559 protected void setCanceled(boolean canceled) {
560 this.canceled = canceled;
561 }
562
563 /**
564 * Adds the download source to the download sources tab.
565 * @param downloadSource The download source to be added.
566 * @param <T> The type of the download data.
567 */
568 protected <T> void addNewDownloadSourceTab(DownloadSource<T> downloadSource) {
569 downloadSourcesTab.addPanel(downloadSource.createPanel(this));
570 }
571
572 /**
573 * Creates listener that removes/adds download sources from/to {@code downloadSourcesTab}
574 * depending on the current mode.
575 * @return The expert mode listener.
576 */
577 private ExpertToggleAction.ExpertModeChangeListener getExpertModeListenerForDownloadSources() {
578 return downloadSourcesTab::updateExpert;
579 }
580
581 /**
582 * Creates a listener that reacts on tab switches for {@code downloadSourcesTab} in order
583 * to adjust proper division of the dialog according to user saved preferences or minimal size
584 * of the panel.
585 * @return A listener to adjust dialog division.
586 */
587 private ChangeListener getDownloadSourceTabChangeListener() {
588 return ec -> downloadSourcesTab.getSelectedPanel().ifPresent(
589 panel -> dialogSplit.setPolicy(panel.getSizingPolicy()));
590 }
591
592 /**
593 * Action that is executed when the cancel button is pressed.
594 */
595 class CancelAction extends AbstractAction {
596 CancelAction() {
597 putValue(NAME, tr("Cancel"));
598 new ImageProvider("cancel").getResource().attachImageIcon(this);
599 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort downloading"));
600 }
601
602 /**
603 * Cancels the download
604 */
605 public void run() {
606 rememberSettings();
607 setCanceled(true);
608 setVisible(false);
609 }
610
611 @Override
612 public void actionPerformed(ActionEvent e) {
613 Optional<AbstractDownloadSourcePanel<?>> panel = downloadSourcesTab.getSelectedPanel();
614 run();
615 panel.ifPresent(AbstractDownloadSourcePanel::checkCancel);
616 }
617 }
618
619 /**
620 * Action that is executed when the download button is pressed.
621 */
622 class DownloadAction extends AbstractAction {
623 final boolean newLayer;
624 DownloadAction(boolean newLayer) {
625 this.newLayer = newLayer;
626 if (!newLayer) {
627 putValue(NAME, tr("Download"));
628 putValue(SHORT_DESCRIPTION, tr("Click to download the currently selected area"));
629 new ImageProvider("download").getResource().attachImageIcon(this);
630 } else {
631 putValue(NAME, tr("Download as new layer"));
632 putValue(SHORT_DESCRIPTION, tr("Click to download the currently selected area into a new data layer"));
633 new ImageProvider("download_new_layer").getResource().attachImageIcon(this);
634 }
635 setEnabled(!NetworkManager.isOffline(OnlineResource.OSM_API));
636 }
637
638 /**
639 * Starts the download and closes the dialog, if all requirements for the current download source are met.
640 * Otherwise the download is not started and the dialog remains visible.
641 */
642 public void run() {
643 rememberSettings();
644 downloadSourcesTab.getSelectedPanel().ifPresent(panel -> {
645 DownloadSettings downloadSettings = getDownloadSettings(newLayer);
646 if (panel.checkDownload(downloadSettings)) {
647 setCanceled(false);
648 setVisible(false);
649 panel.triggerDownload(downloadSettings);
650 }
651 });
652 }
653
654 @Override
655 public void actionPerformed(ActionEvent e) {
656 run();
657 }
658 }
659
660 class WindowEventHandler extends WindowAdapter {
661 @Override
662 public void windowClosing(WindowEvent e) {
663 new CancelAction().run();
664 }
665
666 @Override
667 public void windowActivated(WindowEvent e) {
668 btnDownload.requestFocusInWindow();
669 }
670 }
671
672 /**
673 * A special tabbed pane for {@link AbstractDownloadSourcePanel}s
674 * @author Michael Zangl
675 * @since 12706
676 */
677 private class DownloadSourceTabs extends JTabbedPane implements DownloadSourceListener {
678 private final List<AbstractDownloadSourcePanel<?>> allPanels = new ArrayList<>();
679
680 DownloadSourceTabs() {
681 downloadSources.forEach(this::downloadSourceAdded);
682 downloadSourcesListeners.addListener(this);
683 }
684
685 List<AbstractDownloadSourcePanel<?>> getAllPanels() {
686 return allPanels;
687 }
688
689 List<AbstractDownloadSourcePanel<?>> getVisiblePanels() {
690 return IntStream.range(0, getTabCount())
691 .mapToObj(this::getComponentAt)
692 .map(p -> (AbstractDownloadSourcePanel<?>) p)
693 .collect(Collectors.toList());
694 }
695
696 void setSelected(String simpleName) {
697 getVisiblePanels().stream()
698 .filter(panel -> simpleName.equals(panel.getSimpleName()))
699 .findFirst()
700 .ifPresent(this::setSelectedComponent);
701 }
702
703 void updateExpert(boolean isExpert) {
704 updateTabs();
705 }
706
707 void addPanel(AbstractDownloadSourcePanel<?> panel) {
708 allPanels.add(panel);
709 updateTabs();
710 }
711
712 private void updateTabs() {
713 // Not the best performance, but we don't do it often
714 removeAll();
715
716 boolean isExpert = ExpertToggleAction.isExpert();
717 allPanels.stream()
718 .filter(panel -> isExpert || !panel.getDownloadSource().onlyExpert())
719 .forEach(panel -> addTab(panel.getDownloadSource().getLabel(), panel.getIcon(), panel));
720 }
721
722 Optional<AbstractDownloadSourcePanel<?>> getSelectedPanel() {
723 return Optional.ofNullable((AbstractDownloadSourcePanel<?>) getSelectedComponent());
724 }
725
726 @Override
727 public void insertTab(String title, Icon icon, Component component, String tip, int index) {
728 if (!(component instanceof AbstractDownloadSourcePanel)) {
729 throw new IllegalArgumentException("Can only add AbstractDownloadSourcePanels");
730 }
731 super.insertTab(title, icon, component, tip, index);
732 }
733
734 @Override
735 public void downloadSourceAdded(DownloadSource<?> source) {
736 addPanel(source.createPanel(DownloadDialog.this));
737 }
738 }
739
740 /**
741 * A special split pane that acts according to a {@link DownloadSourceSizingPolicy}
742 *
743 * It attempts to size the top tab content correctly.
744 *
745 * @author Michael Zangl
746 * @since 12705
747 */
748 private static class DownloadDialogSplitPane extends JSplitPane {
749 private DownloadSourceSizingPolicy policy;
750 private final JTabbedPane topComponent;
751 /**
752 * If the height was explicitly set by the user.
753 */
754 private boolean heightAdjustedExplicitly;
755
756 DownloadDialogSplitPane(JTabbedPane newTopComponent, Component newBottomComponent) {
757 super(VERTICAL_SPLIT, newTopComponent, newBottomComponent);
758 this.topComponent = newTopComponent;
759
760 addComponentListener(new ComponentAdapter() {
761 @Override
762 public void componentResized(ComponentEvent e) {
763 // doLayout is called automatically when the component size decreases
764 // This seems to be the only way to call doLayout when the component size increases
765 // We need this since we sometimes want to increase the top component size.
766 revalidate();
767 }
768 });
769
770 addPropertyChangeListener(DIVIDER_LOCATION_PROPERTY, e -> heightAdjustedExplicitly = true);
771 }
772
773 public void setPolicy(DownloadSourceSizingPolicy policy) {
774 this.policy = policy;
775
776 super.setDividerLocation(policy.getComponentHeight() + computeOffset());
777 setDividerSize(policy.isHeightAdjustable() ? 10 : 0);
778 setEnabled(policy.isHeightAdjustable());
779 }
780
781 @Override
782 public void doLayout() {
783 // We need to force this height before the layout manager is run.
784 // We cannot do this in the setDividerLocation, since the offset cannot be computed there.
785 int offset = computeOffset();
786 if (policy.isHeightAdjustable() && heightAdjustedExplicitly) {
787 policy.storeHeight(Math.max(getDividerLocation() - offset, 0));
788 }
789 // At least 30 pixel for map, if we have enough space
790 int maxValidDividerLocation = getHeight() > 150 ? getHeight() - 40 : getHeight();
791
792 super.setDividerLocation(Math.min(policy.getComponentHeight() + offset, maxValidDividerLocation));
793 super.doLayout();
794 // Order is important (set this after setDividerLocation/doLayout called the listener)
795 this.heightAdjustedExplicitly = false;
796 }
797
798 /**
799 * @return The difference between the content height and the divider location
800 */
801 private int computeOffset() {
802 Component selectedComponent = topComponent.getSelectedComponent();
803 return topComponent.getHeight() - (selectedComponent == null ? 0 : selectedComponent.getHeight());
804 }
805 }
806}
Note: See TracBrowser for help on using the repository browser.