001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.download; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.BorderLayout; 008import java.awt.Component; 009import java.awt.Dimension; 010import java.awt.FlowLayout; 011import java.awt.GridBagLayout; 012import java.awt.event.ActionEvent; 013import java.awt.event.InputEvent; 014import java.awt.event.KeyEvent; 015import java.awt.event.WindowAdapter; 016import java.awt.event.WindowEvent; 017import java.util.ArrayList; 018import java.util.List; 019import java.util.Optional; 020import java.util.stream.Collectors; 021import java.util.stream.IntStream; 022 023import javax.swing.AbstractAction; 024import javax.swing.Icon; 025import javax.swing.JButton; 026import javax.swing.JCheckBox; 027import javax.swing.JComponent; 028import javax.swing.JDialog; 029import javax.swing.JLabel; 030import javax.swing.JPanel; 031import javax.swing.JSplitPane; 032import javax.swing.JTabbedPane; 033import javax.swing.KeyStroke; 034import javax.swing.event.ChangeEvent; 035import javax.swing.event.ChangeListener; 036 037import org.openstreetmap.josm.Main; 038import org.openstreetmap.josm.actions.ExpertToggleAction; 039import org.openstreetmap.josm.data.Bounds; 040import org.openstreetmap.josm.data.preferences.BooleanProperty; 041import org.openstreetmap.josm.data.preferences.IntegerProperty; 042import org.openstreetmap.josm.data.preferences.StringProperty; 043import org.openstreetmap.josm.gui.MainApplication; 044import org.openstreetmap.josm.gui.MapView; 045import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 046import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 047import org.openstreetmap.josm.gui.help.HelpUtil; 048import org.openstreetmap.josm.gui.util.GuiHelper; 049import org.openstreetmap.josm.gui.util.WindowGeometry; 050import org.openstreetmap.josm.io.OnlineResource; 051import org.openstreetmap.josm.plugins.PluginHandler; 052import org.openstreetmap.josm.spi.preferences.Config; 053import org.openstreetmap.josm.tools.GBC; 054import org.openstreetmap.josm.tools.ImageProvider; 055import org.openstreetmap.josm.tools.InputMapUtils; 056import org.openstreetmap.josm.tools.JosmRuntimeException; 057import org.openstreetmap.josm.tools.ListenerList; 058import org.openstreetmap.josm.tools.Logging; 059import org.openstreetmap.josm.tools.OsmUrlToBounds; 060 061/** 062 * Dialog displayed to the user to download mapping data. 063 */ 064public class DownloadDialog extends JDialog { 065 066 private static final IntegerProperty DOWNLOAD_TAB = new IntegerProperty("download.tab", 0); 067 private static final StringProperty DOWNLOAD_SOURCE_TAB = new StringProperty("download.source.tab", OSMDownloadSource.SIMPLE_NAME); 068 private static final BooleanProperty DOWNLOAD_AUTORUN = new BooleanProperty("download.autorun", false); 069 private static final BooleanProperty DOWNLOAD_NEWLAYER = new BooleanProperty("download.newlayer", false); 070 private static final BooleanProperty DOWNLOAD_ZOOMTODATA = new BooleanProperty("download.zoomtodata", true); 071 072 /** the unique instance of the download dialog */ 073 private static DownloadDialog instance; 074 075 /** 076 * Replies the unique instance of the download dialog 077 * 078 * @return the unique instance of the download dialog 079 */ 080 public static synchronized DownloadDialog getInstance() { 081 if (instance == null) { 082 instance = new DownloadDialog(Main.parent); 083 } 084 return instance; 085 } 086 087 protected static final ListenerList<DownloadSourceListener> downloadSourcesListeners = ListenerList.create(); 088 protected static final List<DownloadSource<?>> downloadSources = new ArrayList<>(); 089 static { 090 // add default download sources 091 addDownloadSource(new OSMDownloadSource()); 092 addDownloadSource(new OverpassDownloadSource()); 093 } 094 095 protected final transient List<DownloadSelection> downloadSelections = new ArrayList<>(); 096 protected final JTabbedPane tpDownloadAreaSelectors = new JTabbedPane(); 097 protected final DownloadSourceTabs downloadSourcesTab = new DownloadSourceTabs(); 098 099 protected JCheckBox cbNewLayer; 100 protected JCheckBox cbStartup; 101 protected JCheckBox cbZoomToDownloadedData; 102 protected SlippyMapChooser slippyMapChooser; 103 protected JPanel mainPanel; 104 protected DownloadDialogSplitPane dialogSplit; 105 106 /* 107 * Keep the reference globally to avoid having it garbage collected 108 */ 109 protected final transient ExpertToggleAction.ExpertModeChangeListener expertListener = 110 getExpertModeListenerForDownloadSources(); 111 protected transient Bounds currentBounds; 112 protected boolean canceled; 113 114 protected JButton btnDownload; 115 protected JButton btnCancel; 116 protected JButton btnHelp; 117 118 /** 119 * Builds the main panel of the dialog. 120 * @return The panel of the dialog. 121 */ 122 protected final JPanel buildMainPanel() { 123 mainPanel = new JPanel(new GridBagLayout()); 124 125 // must be created before hook 126 slippyMapChooser = new SlippyMapChooser(); 127 128 // predefined download selections 129 downloadSelections.add(slippyMapChooser); 130 downloadSelections.add(new BookmarkSelection()); 131 downloadSelections.add(new BoundingBoxSelection()); 132 downloadSelections.add(new PlaceSelection()); 133 downloadSelections.add(new TileSelection()); 134 135 // add selections from plugins 136 PluginHandler.addDownloadSelection(downloadSelections); 137 138 // register all default download selections 139 for (DownloadSelection s : downloadSelections) { 140 s.addGui(this); 141 } 142 143 // allow to collapse the panes, but reserve some space for tabs 144 downloadSourcesTab.setMinimumSize(new Dimension(0, 25)); 145 tpDownloadAreaSelectors.setMinimumSize(new Dimension(0, 0)); 146 147 dialogSplit = new DownloadDialogSplitPane( 148 downloadSourcesTab, 149 tpDownloadAreaSelectors); 150 151 ChangeListener tabChangedListener = getDownloadSourceTabChangeListener(); 152 tabChangedListener.stateChanged(new ChangeEvent(downloadSourcesTab)); 153 downloadSourcesTab.addChangeListener(tabChangedListener); 154 155 mainPanel.add(dialogSplit, GBC.eol().fill()); 156 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 161 cbStartup = new JCheckBox(tr("Open this dialog on startup")); 162 cbStartup.setToolTipText( 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>")); 165 cbStartup.addActionListener(e -> DOWNLOAD_AUTORUN.put(cbStartup.isSelected())); 166 167 cbZoomToDownloadedData = new JCheckBox(tr("Zoom to downloaded data")); 168 cbZoomToDownloadedData.setToolTipText(tr("Select to zoom to entire newly downloaded data.")); 169 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)); 173 174 ExpertToggleAction.addVisibilitySwitcher(cbZoomToDownloadedData); 175 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 183 return mainPanel; 184 } 185 186 /** 187 * Builds the button pane of the dialog. 188 * @return The button panel of the dialog. 189 */ 190 protected final JPanel buildButtonPanel() { 191 btnDownload = new JButton(new DownloadAction()); 192 btnCancel = new JButton(new CancelAction()); 193 btnHelp = new JButton( 194 new ContextSensitiveHelpAction(getRootPane().getClientProperty("help").toString())); 195 196 JPanel pnl = new JPanel(new FlowLayout()); 197 198 pnl.add(btnDownload); 199 pnl.add(btnCancel); 200 pnl.add(btnHelp); 201 202 InputMapUtils.enableEnter(btnDownload); 203 InputMapUtils.enableEnter(btnCancel); 204 InputMapUtils.addEscapeAction(getRootPane(), btnCancel.getAction()); 205 InputMapUtils.enableEnter(btnHelp); 206 207 InputMapUtils.addEnterActionWhenAncestor(cbNewLayer, btnDownload.getAction()); 208 InputMapUtils.addEnterActionWhenAncestor(cbStartup, btnDownload.getAction()); 209 InputMapUtils.addEnterActionWhenAncestor(cbZoomToDownloadedData, btnDownload.getAction()); 210 211 return pnl; 212 } 213 214 /** 215 * Constructs a new {@code DownloadDialog}. 216 * @param parent the parent component 217 */ 218 public DownloadDialog(Component parent) { 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) { 228 super(GuiHelper.getFrameForComponent(parent), tr("Download"), ModalityType.DOCUMENT_MODAL); 229 HelpUtil.setHelpContext(getRootPane(), helpTopic); 230 getContentPane().setLayout(new BorderLayout()); 231 getContentPane().add(buildMainPanel(), BorderLayout.CENTER); 232 getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH); 233 234 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 235 KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK), "checkClipboardContents"); 236 237 getRootPane().getActionMap().put("checkClipboardContents", new AbstractAction() { 238 @Override 239 public void actionPerformed(ActionEvent e) { 240 String clip = ClipboardUtils.getClipboardStringContent(); 241 if (clip == null) { 242 return; 243 } 244 Bounds b = OsmUrlToBounds.parse(clip); 245 if (b != null) { 246 boundingBoxChanged(new Bounds(b), null); 247 } 248 } 249 }); 250 addWindowListener(new WindowEventHandler()); 251 ExpertToggleAction.addExpertModeChangeListener(expertListener); 252 restoreSettings(); 253 254 // if no bounding box is selected make sure it is still propagated. 255 if (currentBounds == null) { 256 boundingBoxChanged(null, null); 257 } 258 } 259 260 /** 261 * Distributes a "bounding box changed" from one DownloadSelection 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. 264 * @param b new current bounds 265 * 266 * @param eventSource - the DownloadSelection object that fired this notification. 267 */ 268 @SuppressWarnings("deprecation") 269 public void boundingBoxChanged(Bounds b, DownloadSelection eventSource) { 270 this.currentBounds = b; 271 for (DownloadSelection s : downloadSelections) { 272 if (s != eventSource) { 273 s.setDownloadArea(currentBounds); 274 } 275 } 276 277 for (AbstractDownloadSourcePanel<?> ds : downloadSourcesTab.getAllPanels()) { 278 ds.boundingBoxChanged(b); 279 ds.boudingBoxChanged(b); 280 } 281 } 282 283 /** 284 * Starts download for the given bounding box 285 * @param b bounding box to download 286 */ 287 public void startDownload(Bounds b) { 288 this.currentBounds = b; 289 startDownload(); 290 } 291 292 /** 293 * Starts download. 294 */ 295 public void startDownload() { 296 btnDownload.doClick(); 297 } 298 299 /** 300 * Replies true if the user requires to download into a new layer 301 * 302 * @return true if the user requires to download into a new layer 303 */ 304 public boolean isNewLayerRequired() { 305 return cbNewLayer.isSelected(); 306 } 307 308 /** 309 * Replies true if the user requires to zoom to new downloaded data 310 * 311 * @return true if the user requires to zoom to new downloaded data 312 * @since 11658 313 */ 314 public boolean isZoomToDownloadedDataRequired() { 315 return cbZoomToDownloadedData.isSelected(); 316 } 317 318 /** 319 * Determines if the dialog autorun is enabled in preferences. 320 * @return {@code true} if the download dialog must be open at startup, {@code false} otherwise. 321 */ 322 public static boolean isAutorunEnabled() { 323 return DOWNLOAD_AUTORUN.get(); 324 } 325 326 /** 327 * Adds a new download area selector to the download dialog. 328 * 329 * @param selector the download are selector. 330 * @param displayName the display name of the selector. 331 */ 332 public void addDownloadAreaSelector(JPanel selector, String displayName) { 333 tpDownloadAreaSelectors.add(displayName, selector); 334 } 335 336 /** 337 * Adds a new download source to the download dialog if it is not added. 338 * 339 * @param downloadSource The download source to be added. 340 * @param <T> The type of the download data. 341 * @throws JosmRuntimeException If the download source is already added. Note, download sources are 342 * compared by their reference. 343 * @since 12878 344 */ 345 public static <T> void addDownloadSource(DownloadSource<T> downloadSource) { 346 if (downloadSources.contains(downloadSource)) { 347 throw new JosmRuntimeException("The download source you are trying to add already exists."); 348 } 349 350 downloadSources.add(downloadSource); 351 downloadSourcesListeners.fireEvent(l -> l.downloadSourceAdded(downloadSource)); 352 } 353 354 /** 355 * Refreshes the tile sources. 356 * @since 6364 357 */ 358 public final void refreshTileSources() { 359 if (slippyMapChooser != null) { 360 slippyMapChooser.refreshTileSources(); 361 } 362 } 363 364 /** 365 * Remembers the current settings in the download dialog. 366 */ 367 public void rememberSettings() { 368 DOWNLOAD_TAB.put(tpDownloadAreaSelectors.getSelectedIndex()); 369 downloadSourcesTab.getAllPanels().forEach(AbstractDownloadSourcePanel::rememberSettings); 370 downloadSourcesTab.getSelectedPanel().ifPresent(panel -> DOWNLOAD_SOURCE_TAB.put(panel.getSimpleName())); 371 DOWNLOAD_NEWLAYER.put(cbNewLayer.isSelected()); 372 DOWNLOAD_ZOOMTODATA.put(cbZoomToDownloadedData.isSelected()); 373 if (currentBounds != null) { 374 Config.getPref().put("osm-download.bounds", currentBounds.encodeAsString(";")); 375 } 376 } 377 378 /** 379 * Restores the previous settings in the download dialog. 380 */ 381 public void restoreSettings() { 382 cbNewLayer.setSelected(DOWNLOAD_NEWLAYER.get()); 383 cbStartup.setSelected(isAutorunEnabled()); 384 cbZoomToDownloadedData.setSelected(DOWNLOAD_ZOOMTODATA.get()); 385 386 try { 387 tpDownloadAreaSelectors.setSelectedIndex(DOWNLOAD_TAB.get()); 388 } catch (IndexOutOfBoundsException e) { 389 Logging.trace(e); 390 tpDownloadAreaSelectors.setSelectedIndex(0); 391 } 392 393 downloadSourcesTab.getAllPanels().forEach(AbstractDownloadSourcePanel::restoreSettings); 394 downloadSourcesTab.setSelected(DOWNLOAD_SOURCE_TAB.get()); 395 396 if (MainApplication.isDisplayingMapView()) { 397 MapView mv = MainApplication.getMap().mapView; 398 currentBounds = new Bounds( 399 mv.getLatLon(0, mv.getHeight()), 400 mv.getLatLon(mv.getWidth(), 0) 401 ); 402 boundingBoxChanged(currentBounds, null); 403 } else { 404 Bounds bounds = getSavedDownloadBounds(); 405 if (bounds != null) { 406 currentBounds = bounds; 407 boundingBoxChanged(currentBounds, null); 408 } 409 } 410 } 411 412 /** 413 * Returns the previously saved bounding box from preferences. 414 * @return The bounding box saved in preferences if any, {@code null} otherwise. 415 * @since 6509 416 */ 417 public static Bounds getSavedDownloadBounds() { 418 String value = Config.getPref().get("osm-download.bounds"); 419 if (!value.isEmpty()) { 420 try { 421 return new Bounds(value, ";"); 422 } catch (IllegalArgumentException e) { 423 Logging.warn(e); 424 } 425 } 426 return null; 427 } 428 429 /** 430 * Automatically opens the download dialog, if autorun is enabled. 431 * @see #isAutorunEnabled 432 */ 433 public static void autostartIfNeeded() { 434 if (isAutorunEnabled()) { 435 MainApplication.getMenu().download.actionPerformed(null); 436 } 437 } 438 439 /** 440 * Returns an {@link Optional} of the currently selected download area. 441 * @return An {@link Optional} of the currently selected download area. 442 * @since 12574 Return type changed to optional 443 */ 444 public Optional<Bounds> getSelectedDownloadArea() { 445 return Optional.ofNullable(currentBounds); 446 } 447 448 @Override 449 public void setVisible(boolean visible) { 450 if (visible) { 451 new WindowGeometry( 452 getClass().getName() + ".geometry", 453 WindowGeometry.centerInWindow( 454 getParent(), 455 new Dimension(1000, 600) 456 ) 457 ).applySafe(this); 458 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775 459 new WindowGeometry(this).remember(getClass().getName() + ".geometry"); 460 } 461 super.setVisible(visible); 462 } 463 464 /** 465 * Replies true if the dialog was canceled 466 * 467 * @return true if the dialog was canceled 468 */ 469 public boolean isCanceled() { 470 return canceled; 471 } 472 473 /** 474 * Gets the global settings of the download dialog. 475 * @return The {@link DownloadSettings} object that describes the current state of 476 * the download dialog. 477 */ 478 public DownloadSettings getDownloadSettings() { 479 return new DownloadSettings(currentBounds, isNewLayerRequired(), isZoomToDownloadedDataRequired()); 480 } 481 482 protected void setCanceled(boolean canceled) { 483 this.canceled = canceled; 484 } 485 486 /** 487 * Adds the download source to the download sources tab. 488 * @param downloadSource The download source to be added. 489 * @param <T> The type of the download data. 490 */ 491 protected <T> void addNewDownloadSourceTab(DownloadSource<T> downloadSource) { 492 downloadSourcesTab.addPanel(downloadSource.createPanel(this)); 493 } 494 495 /** 496 * Creates listener that removes/adds download sources from/to {@code downloadSourcesTab} 497 * depending on the current mode. 498 * @return The expert mode listener. 499 */ 500 private ExpertToggleAction.ExpertModeChangeListener getExpertModeListenerForDownloadSources() { 501 return downloadSourcesTab::updateExpert; 502 } 503 504 /** 505 * Creates a listener that reacts on tab switches for {@code downloadSourcesTab} in order 506 * to adjust proper division of the dialog according to user saved preferences or minimal size 507 * of the panel. 508 * @return A listener to adjust dialog division. 509 */ 510 private ChangeListener getDownloadSourceTabChangeListener() { 511 return ec -> downloadSourcesTab.getSelectedPanel().ifPresent( 512 panel -> dialogSplit.setPolicy(panel.getSizingPolicy())); 513 } 514 515 /** 516 * Action that is executed when the cancel button is pressed. 517 */ 518 class CancelAction extends AbstractAction { 519 CancelAction() { 520 putValue(NAME, tr("Cancel")); 521 new ImageProvider("cancel").getResource().attachImageIcon(this); 522 putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort downloading")); 523 } 524 525 /** 526 * Cancels the download 527 */ 528 public void run() { 529 rememberSettings(); 530 setCanceled(true); 531 setVisible(false); 532 } 533 534 @Override 535 public void actionPerformed(ActionEvent e) { 536 Optional<AbstractDownloadSourcePanel<?>> panel = downloadSourcesTab.getSelectedPanel(); 537 run(); 538 panel.ifPresent(AbstractDownloadSourcePanel::checkCancel); 539 } 540 } 541 542 /** 543 * Action that is executed when the download button is pressed. 544 */ 545 class DownloadAction extends AbstractAction { 546 DownloadAction() { 547 putValue(NAME, tr("Download")); 548 new ImageProvider("download").getResource().attachImageIcon(this); 549 putValue(SHORT_DESCRIPTION, tr("Click to download the currently selected area")); 550 setEnabled(!Main.isOffline(OnlineResource.OSM_API)); 551 } 552 553 /** 554 * Starts the download and closes the dialog, if all requirements for the current download source are met. 555 * Otherwise the download is not started and the dialog remains visible. 556 */ 557 public void run() { 558 rememberSettings(); 559 downloadSourcesTab.getSelectedPanel().ifPresent(panel -> { 560 DownloadSettings downloadSettings = getDownloadSettings(); 561 if (panel.checkDownload(downloadSettings)) { 562 setCanceled(false); 563 setVisible(false); 564 panel.triggerDownload(downloadSettings); 565 } 566 }); 567 } 568 569 @Override 570 public void actionPerformed(ActionEvent e) { 571 run(); 572 } 573 } 574 575 class WindowEventHandler extends WindowAdapter { 576 @Override 577 public void windowClosing(WindowEvent e) { 578 new CancelAction().run(); 579 } 580 581 @Override 582 public void windowActivated(WindowEvent e) { 583 btnDownload.requestFocusInWindow(); 584 } 585 } 586 587 /** 588 * A special tabbed pane for {@link AbstractDownloadSourcePanel}s 589 * @author Michael Zangl 590 * @since 12706 591 */ 592 private class DownloadSourceTabs extends JTabbedPane implements DownloadSourceListener { 593 private final List<AbstractDownloadSourcePanel<?>> allPanels = new ArrayList<>(); 594 595 DownloadSourceTabs() { 596 downloadSources.forEach(this::downloadSourceAdded); 597 downloadSourcesListeners.addListener(this); 598 } 599 600 List<AbstractDownloadSourcePanel<?>> getAllPanels() { 601 return allPanels; 602 } 603 604 List<AbstractDownloadSourcePanel<?>> getVisiblePanels() { 605 return IntStream.range(0, getTabCount()) 606 .mapToObj(this::getComponentAt) 607 .map(p -> (AbstractDownloadSourcePanel<?>) p) 608 .collect(Collectors.toList()); 609 } 610 611 void setSelected(String simpleName) { 612 getVisiblePanels().stream() 613 .filter(panel -> simpleName.equals(panel.getSimpleName())) 614 .findFirst() 615 .ifPresent(this::setSelectedComponent); 616 } 617 618 void updateExpert(boolean isExpert) { 619 updateTabs(); 620 } 621 622 void addPanel(AbstractDownloadSourcePanel<?> panel) { 623 allPanels.add(panel); 624 updateTabs(); 625 } 626 627 private void updateTabs() { 628 // Not the best performance, but we don't do it often 629 removeAll(); 630 631 boolean isExpert = ExpertToggleAction.isExpert(); 632 allPanels.stream() 633 .filter(panel -> isExpert || !panel.getDownloadSource().onlyExpert()) 634 .forEach(panel -> addTab(panel.getDownloadSource().getLabel(), panel.getIcon(), panel)); 635 } 636 637 Optional<AbstractDownloadSourcePanel<?>> getSelectedPanel() { 638 return Optional.ofNullable((AbstractDownloadSourcePanel<?>) getSelectedComponent()); 639 } 640 641 @Override 642 public void insertTab(String title, Icon icon, Component component, String tip, int index) { 643 if (!(component instanceof AbstractDownloadSourcePanel)) { 644 throw new IllegalArgumentException("Can only add AbstractDownloadSourcePanels"); 645 } 646 super.insertTab(title, icon, component, tip, index); 647 } 648 649 @Override 650 public void downloadSourceAdded(DownloadSource<?> source) { 651 addPanel(source.createPanel(DownloadDialog.this)); 652 } 653 } 654 655 /** 656 * A special split pane that acts according to a {@link DownloadSourceSizingPolicy} 657 * 658 * It attempts to size the top tab content correctly. 659 * 660 * @author Michael Zangl 661 * @since 12705 662 */ 663 private static class DownloadDialogSplitPane extends JSplitPane { 664 private DownloadSourceSizingPolicy policy; 665 private final JTabbedPane topComponent; 666 667 DownloadDialogSplitPane(JTabbedPane newTopComponent, Component newBottomComponent) { 668 super(VERTICAL_SPLIT, newTopComponent, newBottomComponent); 669 this.topComponent = newTopComponent; 670 } 671 672 public void setPolicy(DownloadSourceSizingPolicy policy) { 673 this.policy = policy; 674 675 super.setDividerLocation(policy.getComponentHeight() + computeOffset()); 676 setDividerSize(policy.isHeightAdjustable() ? 10 : 0); 677 setEnabled(policy.isHeightAdjustable()); 678 } 679 680 @Override 681 public void doLayout() { 682 // We need to force this height before the layout manager is run. 683 // We cannot do this in the setDividerLocation, since the offset cannot be computed there. 684 int offset = computeOffset(); 685 if (policy.isHeightAdjustable()) { 686 policy.storeHeight(Math.max(getDividerLocation() - offset, 0)); 687 } 688 super.setDividerLocation(policy.getComponentHeight() + offset); 689 super.doLayout(); 690 } 691 692 /** 693 * @return The difference between the content height and the divider location 694 */ 695 private int computeOffset() { 696 Component selectedComponent = topComponent.getSelectedComponent(); 697 return topComponent.getHeight() - (selectedComponent == null ? 0 : selectedComponent.getHeight()); 698 } 699 } 700}