This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit c57e6288110ce627a231a7bd3da45ce0976797b5 Author: Martin Desruisseaux <[email protected]> AuthorDate: Sat May 2 00:58:59 2020 +0200 Add data controls below the resource explorer. --- .../apache/sis/gui/coverage/CoverageExplorer.java | 29 +++-- .../apache/sis/gui/dataset/ResourceExplorer.java | 132 +++++++++++++++------ 2 files changed, 121 insertions(+), 40 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java index 2a52f87..aa1b600 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java @@ -53,11 +53,6 @@ import org.apache.sis.gui.Widget; */ public class CoverageExplorer extends Widget { /** - * Initial position of divider in split panes. - */ - private static final double INITIAL_SPLIT = 200; - - /** * Type of view shown in the explorer. * It may be either an image or a table of numerical values. */ @@ -203,15 +198,22 @@ public class CoverageExplorer extends Widget { final Controls c = views[0]; // First View enumeration is default value. group.selectToggle(group.getToggles().get(0)); content = new SplitPane(c.controls(), c.view()); - content.setDividerPosition(0, INITIAL_SPLIT); ToolbarButton.insert(content, buttons); viewTypeProperty.addListener((p,o,n) -> onViewTypeSpecified(n)); + /* + * The divider position is supposed to be a fraction between 0 and 1. A value of 1 would mean + * to give all the space to controls and no space to data, which is not what we want. However + * experience with JavaFX 14 shows that this setting gives just a reasonable space to controls + * and most space to data. I have not identified the cause of this surprising behavior. + * A smaller value result in too few space for the controls. + */ + content.setDividerPosition(0, 1); } return content; } /** - * Returns the region containing the only the data visualization part, without controls. + * Returns the region containing only the data visualization component, without controls. * This is a {@link GridView} or {@link CoverageCanvas} together with their {@link StatusBar}. * The {@link Region} subclass returned by this method is implementation dependent and may change * in any future version. @@ -225,6 +227,19 @@ public class CoverageExplorer extends Widget { } /** + * Returns the region containing only the controls, without data visualization component. + * The {@link Region} subclass returned by this method is implementation dependent and may + * change in any future version. + * + * @param view whether to obtain controls for {@link GridView} or {@link CoverageCanvas}. + * @return the controls on specified data view. + */ + public final Region getControls(final View view) { + ArgumentChecks.ensureNonNull("view", view); + return views[view.ordinal()].controls(); + } + + /** * The action to execute when the user selects a view. */ private final class Selector extends ToolbarButton { diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java index 3eaf800..388cbe6 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java @@ -19,8 +19,11 @@ package org.apache.sis.gui.dataset; import java.util.Objects; import java.util.Collection; import javafx.beans.property.ReadOnlyProperty; -import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.geometry.Orientation; +import javafx.scene.Node; import javafx.scene.layout.Region; import javafx.scene.control.ContextMenu; import javafx.scene.control.SplitPane; @@ -34,10 +37,10 @@ import org.apache.sis.gui.metadata.MetadataTree; import org.apache.sis.gui.metadata.StandardMetadataTree; import org.apache.sis.gui.coverage.ImageRequest; import org.apache.sis.gui.coverage.CoverageExplorer; -import org.apache.sis.internal.gui.Resources; import org.apache.sis.storage.GridCoverageResource; import org.apache.sis.util.collection.TableColumn; import org.apache.sis.util.resources.Vocabulary; +import org.apache.sis.internal.gui.Resources; /** @@ -56,19 +59,33 @@ public class ResourceExplorer extends WindowManager { private final ResourceTree resources; /** - * The data as a table, created when first needed. + * The currently selected resource. + * + * @see #getSelectedResourceProperty() */ - private FeatureTable features; + private final ReadOnlyObjectWrapper<Resource> selectedResource; /** - * The data as a grid coverage, created when first needed. + * The widget showing metadata about a selected resource. + */ + private final MetadataSummary metadata; + + /** + * The gridded data as an image or as a table, created when first needed. */ private CoverageExplorer coverage; /** - * The widget showing metadata about a selected resource. + * The vector data as a table, created when first needed. */ - private final MetadataSummary metadata; + private FeatureTable features; + + /** + * Controls for the image or tabular data. This is a vertical split pane. + * The upper part contains the {@link #resources} tree and the lower part + * contains the resource-dependent controls. + */ + private final SplitPane controls; /** * The control that put everything together. @@ -85,16 +102,12 @@ public class ResourceExplorer extends WindowManager { * because the data loading may be costly. * * @see #isDataTabSet + * @see #isDataTabSelected() * @see #updateDataTab(Resource) */ private final Tab viewTab, tableTab; /** - * The currently selected resource. - */ - public final ReadOnlyProperty<Resource> selectedResourceProperty; - - /** * Whether the setting of new values in {@link #viewTab} or {@link #tableTab} has been done. * The new values are set only if a data tab is visible, and otherwise are delayed until one * of data tab become visible. @@ -104,13 +117,26 @@ public class ResourceExplorer extends WindowManager { private boolean isDataTabSet; /** + * Last divider position as a fraction between 0 and 1, or {@code NaN} if undefined. + * This is used for keeping the position constant when adding and removing controls. + */ + private double dividerPosition; + + /** * Creates a new panel for exploring resources. */ public ResourceExplorer() { + /* + * Build the resource explorer. Must be first because `localized()` depends on it. + */ resources = new ResourceTree(); - metadata = new MetadataSummary(); - content = new SplitPane(); - + resources.getSelectionModel().getSelectedItems().addListener(this::selectResource); + resources.setPrefWidth(400); + selectedResource = new ReadOnlyObjectWrapper<>(this, "selectedResource"); + metadata = new MetadataSummary(); + /* + * Build the tabs. + */ final Resources localized = localized(); viewTab = new Tab(localized.getString(Resources.Keys.Visual)); // TODO: add contextual menu for window showing directly the visual. @@ -135,16 +161,21 @@ public class ResourceExplorer extends WindowManager { tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER); - - content.getItems().setAll(resources, tabs); - resources.getSelectionModel().getSelectedItems().addListener(this::selectResource); + /* + * Build the main pane which put everything together. + */ + controls = new SplitPane(resources); + controls.setOrientation(Orientation.VERTICAL); + content = new SplitPane(controls, tabs); + content.setDividerPosition(0, 0.2); + dividerPosition = Double.NaN; SplitPane.setResizableWithParent(resources, Boolean.FALSE); SplitPane.setResizableWithParent(tabs, Boolean.TRUE); - resources.setPrefWidth(400); - - selectedResourceProperty = new SimpleObjectProperty<>(this, "selectedResource"); - viewTab .selectedProperty().addListener((p,o,n) -> dataTabShown(n)); - tableTab.selectedProperty().addListener((p,o,n) -> dataTabShown(n)); + /* + * Register listeners last, for making sure we don't have undesired event. + */ + viewTab .selectedProperty().addListener((p,o,n) -> dataTabShown(n, true)); + tableTab.selectedProperty().addListener((p,o,n) -> dataTabShown(n, false)); } /** @@ -200,9 +231,9 @@ public class ResourceExplorer extends WindowManager { if (resource != null) break; } } - ((SimpleObjectProperty<Resource>) selectedResourceProperty).set(resource); + selectedResource.set(resource); metadata.setMetadata(resource); - isDataTabSet = viewTab.isSelected() || tableTab.isSelected(); + isDataTabSet = isDataTabSelected(); updateDataTab(isDataTabSet ? resource : null); if (!isDataTabSet) { setNewWindowDisabled(!(resource instanceof GridCoverageResource || resource instanceof FeatureSet)); @@ -210,8 +241,15 @@ public class ResourceExplorer extends WindowManager { } /** - * Sets the given resource to the {@link #viewTab} or {@link #tableTab}. Should be invoked only if - * a data tab is visible because data loading may be costly. It is caller responsibility to invoke + * Returns whether the currently selected tab is {@link #viewTab} or {@link #tableTab}. + */ + private boolean isDataTabSelected() { + return viewTab.isSelected() || tableTab.isSelected(); + } + + /** + * Assigns the given resource into the {@link #viewTab} and {@link #tableTab}. Should be invoked only + * if a data tab is visible because data loading may be costly. It is caller responsibility to invoke * {@link #setNewWindowDisabled(boolean)} after this method. * * <p>The {@link #isDataTabSet} flag should be set before to invoke this method. If {@code true}, then @@ -229,7 +267,7 @@ public class ResourceExplorer extends WindowManager { if (resource instanceof GridCoverageResource) { grid = new ImageRequest((GridCoverageResource) resource, null, 0); if (coverage == null) { - coverage = new CoverageExplorer(); + coverage = new CoverageExplorer(); // TODO: build in background thread. } image = coverage.getDataView(CoverageExplorer.View.IMAGE); table = coverage.getDataView(CoverageExplorer.View.TABLE); @@ -259,11 +297,30 @@ public class ResourceExplorer extends WindowManager { * or {@link #tableTab} if it has not been already set. * * @param selected whether the tab became the selected one. + * @param visual {@code true} for visual, or {@code false} for tabular data. */ - private void dataTabShown(final Boolean selected) { - if (selected && !isDataTabSet) { - isDataTabSet = true; // Must be set before to invoke `updateDataTab(…)`. - updateDataTab(selectedResourceProperty.getValue()); + private void dataTabShown(final Boolean selected, final boolean visual) { + Region controlPanel = null; + if (selected) { + if (!isDataTabSet) { + isDataTabSet = true; // Must be set before to invoke `updateDataTab(…)`. + updateDataTab(selectedResource.get()); + } + controlPanel = coverage.getControls(visual ? CoverageExplorer.View.IMAGE : CoverageExplorer.View.TABLE); + } + final ObservableList<Node> items = controls.getItems(); + if (items.size() >= 2) { + if (controlPanel != null) { + items.set(1, controlPanel); + } else { + dividerPosition = controls.getDividerPositions()[0]; + items.remove(1); + } + } else if (controlPanel != null) { + items.add(controlPanel); + if (dividerPosition >= 0) { + controls.setDividerPosition(0, dividerPosition); + } } } @@ -272,7 +329,7 @@ public class ResourceExplorer extends WindowManager { */ @Override final SelectedData getSelectedData() { - final Resource resource = selectedResourceProperty.getValue(); + final Resource resource = selectedResource.get(); if (resource == null) { return null; } @@ -304,4 +361,13 @@ public class ResourceExplorer extends WindowManager { } return new SelectedData(resources.getTitle(resource, false), table, grid, resources.localized); } + + /** + * Returns the property for currently selected resource. + * + * @return property for currently selected resource. + */ + public ReadOnlyProperty<Resource> getSelectedResourceProperty() { + return selectedResource.getReadOnlyProperty(); + } }
