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

Last change on this file since 14418 was 14418, checked in by michael2402, 5 years ago

Fix #16945: Only store changed overpass height if user changes it. Add minimum size to overpass query / map view

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