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

Last change on this file was 18017, checked in by Don-vip, 3 years ago

fix #21034 - remember download OSM bounds from DownloadOsmTask instead of DownloadDialog

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