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