sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/03: More control on the initial coverage view (image versus tabular data).
Date Mon, 13 Apr 2020 21:06:05 GMT
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 24c4bea42bc6333fa5c2ec86f270ac9becb6eb4b
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Apr 13 22:30:47 2020 +0200

    More control on the initial coverage view (image versus tabular data).
---
 .../java/org/apache/sis/gui/coverage/Controls.java |   7 +
 .../apache/sis/gui/coverage/CoverageExplorer.java  | 148 ++++++++++++++++-----
 .../java/org/apache/sis/gui/coverage/GridView.java |  27 ++--
 .../org/apache/sis/gui/coverage/GridViewSkin.java  |  23 +---
 .../org/apache/sis/gui/dataset/DataWindow.java     |  12 +-
 .../apache/sis/gui/dataset/ResourceExplorer.java   |   2 +-
 .../org/apache/sis/gui/dataset/SelectedData.java   |  36 ++++-
 .../org/apache/sis/gui/dataset/WindowManager.java  |   2 +-
 .../sis/internal/gui/NonNullObjectProperty.java    |  55 ++++++++
 9 files changed, 249 insertions(+), 63 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
index 1db40f7..7636376 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
@@ -18,6 +18,7 @@ package org.apache.sis.gui.coverage;
 
 import javafx.geometry.Insets;
 import javafx.scene.Node;
+import javafx.scene.control.ButtonBase;
 import javafx.scene.control.Control;
 import javafx.scene.control.Label;
 import javafx.scene.layout.Border;
@@ -59,6 +60,12 @@ abstract class Controls {
             Styles.GROUP_BORDER, BorderStrokeStyle.SOLID, null, null));
 
     /**
+     * The toolbar button for selecting this view.
+     * This is initialized after construction.
+     */
+    ButtonBase selector;
+
+    /**
      * Creates a new control.
      */
     Controls() {
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 f18e9c1..b57e342 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
@@ -16,13 +16,13 @@
  */
 package org.apache.sis.gui.coverage;
 
+import javafx.scene.control.Control;
 import javafx.scene.control.SplitPane;
 import javafx.scene.control.Separator;
 import javafx.scene.control.ToggleGroup;
 import javafx.scene.control.Toggle;
 import javafx.scene.layout.Region;
 import javafx.event.ActionEvent;
-import javafx.collections.ObservableList;
 import javafx.beans.value.ObservableValue;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
@@ -30,6 +30,7 @@ import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.gui.Styles;
 import org.apache.sis.internal.gui.ToolbarButton;
+import org.apache.sis.internal.gui.NonNullObjectProperty;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.gui.Widget;
 
@@ -49,13 +50,42 @@ import org.apache.sis.gui.Widget;
  */
 public class CoverageExplorer extends Widget {
     /**
-     * Index in the {@link #views} array for the {@link GridView} (tabular data)
-     * or the {@link CoverageCanvas} (image).
+     * Type of view shown in the explorer.
+     * It may be either an image or a table of numerical values.
      */
-    private static final int TABLE_VIEW = 0, IMAGE_VIEW = 1;
+    public enum View {
+        /**
+         * Shows the coverage numerical value in a table. This view uses {@link GridView}.
+         * This is the default value of newly constructed {@link CoverageExplorer}.
+         */
+        TABLE("\uD83D\uDD22\uFE0F", Resources.Keys.Visualize),      // 🔢 — Input symbol
for numbers.
+
+        /**
+         * Shows the coverage visual as an image. This view uses {@link CoverageCanvas}.
+         */
+        IMAGE("\uD83D\uDDFA\uFE0F", Resources.Keys.TabularData);    // 🗺 — World map.
+
+        /**
+         * The Unicode characters to use as icon.
+         */
+        final String icon;
+
+        /**
+         * Key from {@link Resources} bundle for the localized text to use as tooltip.
+         */
+        final short tooltip;
+
+        /**
+         * Creates a new enumeration value.
+         */
+        private View(final String icon, short tooltip) {
+            this.icon = icon;
+            this.tooltip = tooltip;
+        }
+    }
 
     /**
-     * The coverage shown in this view. Note that setting this property to a non-null value
may not
+     * The coverage shown in this explorer. Note that setting this property to a non-null
value may not
      * modify the view content immediately. Instead, a background process will request the
tiles.
      *
      * <p>Current implementation is restricted to {@link GridCoverage} instances, but
a future
@@ -67,6 +97,20 @@ public class CoverageExplorer extends Widget {
     public final ObjectProperty<GridCoverage> coverageProperty;
 
     /**
+     * The type of view (image or tabular data) shown in this explorer.
+     * The default value is {@link View#TABLE}.
+     *
+     * <div class="note"><b>API note:</b>
+     * the reason for setting default value to tabular data is because it requires loading
much less data with
+     * {@link java.awt.image.RenderedImage}s supporting deferred tile loading. By contrast
{@link View#IMAGE}
+     * may require loading the full image.</div>
+     *
+     * @see #getViewType()
+     * @see #setViewType(View)
+     */
+    public final ObjectProperty<View> viewTypeProperty;
+
+    /**
      * Whether the {@link #coverageProperty} is in process of being set, in which case some
      * listeners should not react.
      */
@@ -91,56 +135,62 @@ public class CoverageExplorer extends Widget {
      */
     public CoverageExplorer() {
         coverageProperty = new SimpleObjectProperty<>(this, "coverage");
+        viewTypeProperty = new NonNullObjectProperty<>(this, "viewType", View.TABLE);
         coverageProperty.addListener(this::onCoverageSpecified);
+        viewTypeProperty.addListener(this::onViewTypeSpecified);
+        /*
+         * Prepare buttons to add on the toolbar. Those buttons are not managed by this class;
+         * they are managed by org.apache.sis.gui.dataset.DataWindow. We only declare here
the
+         * text and action for each button.
+         */
+        final View[]  viewTypes = View.values();
+        final ToggleGroup group = new ToggleGroup();
+        final Control[] buttons = new Control[viewTypes.length + 1];
+        buttons[0] = new Separator();
         /*
          * The coverage property may be shown in various ways (tabular data, image).
          * Each visualization way is an entry in the `views` array.
          */
         final Resources  localized  = Resources.forLocale(null);
         final Vocabulary vocabulary = Vocabulary.getResources(localized.getLocale());
-        views = new Controls[2];
-        views[TABLE_VIEW] = new GridControls(vocabulary);
-        views[IMAGE_VIEW] = new CoverageControls(vocabulary, coverageProperty);
-        for (final Controls c : views) {
+        views = new Controls[viewTypes.length];
+        for (final View type : viewTypes) {
+            final Controls c;
+            switch (type) {
+                case TABLE: c = new GridControls(vocabulary); break;
+                case IMAGE: c = new CoverageControls(vocabulary, coverageProperty); break;
+                default: throw new AssertionError(type);
+            }
             SplitPane.setResizableWithParent(c.controls(), Boolean.FALSE);
             SplitPane.setResizableWithParent(c.view(),     Boolean.TRUE);
+            c.selector = new Selector(type).createButton(group, type.icon, localized, type.tooltip);
+            buttons[buttons.length - type.ordinal() - 1] = c.selector;  // Buttons in reverse
order.
+            views[type.ordinal()] = c;
         }
-        final Controls c = views[TABLE_VIEW];
+        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, Styles.INITIAL_SPLIT);
-        /*
-         * Prepare buttons to add on the toolbar. Those buttons are not managed by this class;
-         * they are managed by org.apache.sis.gui.dataset.DataWindow. We only declare here
the
-         * text and action for each button.
-         */
-        final ToggleGroup group = new ToggleGroup();
-        ToolbarButton.insert(content,
-            new Separator(),
-            new Selector(IMAGE_VIEW).createButton(group, "\uD83D\uDDFA\uFE0F", localized,
Resources.Keys.Visualize),    // 🗺 — World map.
-            new Selector(TABLE_VIEW).createButton(group, "\uD83D\uDD22\uFE0F", localized,
Resources.Keys.TabularData)   // 🔢 — Input symbol for numbers.
-        );
-        final ObservableList<Toggle> toggles = group.getToggles();
-        group.selectToggle(toggles.get(toggles.size() - 1));
+        ToolbarButton.insert(content, buttons);
     }
 
     /**
      * The action to execute when the user selects a view.
      */
     private final class Selector extends ToolbarButton {
-        /** {@link #TABLE_VIEW} or {@link #IMAGE_VIEW}. */
-        private final int index;
+        /** The view to select when the button is pressed. */
+        private final View view;
 
         /** Creates a new action which will show the view at the given index. */
-        Selector(final int index) {
-            this.index = index;
+        Selector(final View view) {
+            this.view = view;
         }
 
         /** Invoked when the user selects another view to show (tabular data or the image).
*/
         @Override public void handle(final ActionEvent event) {
             final Toggle button = (Toggle) event.getSource();
             if (button.isSelected()) {
-                final Controls c = views[index];
-                content.getItems().setAll(c.controls(), c.view());
+                setViewType(view);
             } else {
                 button.setSelected(true);       // Prevent situation where all buttons are
unselected.
             }
@@ -246,7 +296,7 @@ public class CoverageExplorer extends Widget {
      * @param  source  the coverage or resource to load, or {@code null} if none.
      */
     private void startLoading(final ImageRequest source) {
-        final GridView main = (GridView) views[TABLE_VIEW].view();
+        final GridView main = (GridView) views[View.TABLE.ordinal()].view();
         main.setImage(source);
     }
 
@@ -261,4 +311,42 @@ public class CoverageExplorer extends Widget {
             c.updateBandTable(data);
         }
     }
+
+    /**
+     * Returns the type of view (image or tabular data) shown in this explorer.
+     * The default value is {@link View#TABLE}.
+     *
+     * @return the type of view shown in this explorer.
+     *
+     * @see #viewTypeProperty
+     */
+    public final View getViewType() {
+        return viewTypeProperty.get();
+    }
+
+    /**
+     * Sets the type of view to show in this explorer.
+     *
+     * @param  coverage  the type of view to show in this explorer.
+     *
+     * @see #viewTypeProperty
+     */
+    public final void setViewType(final View coverage) {
+        viewTypeProperty.set(coverage);
+    }
+
+    /**
+     * Invoked when a new view type has been specified.
+     *
+     * @param  property  the {@link #viewTypeProperty} (ignored).
+     * @param  previous  ignored.
+     * @param  view      the new view type.
+     */
+    private void onViewTypeSpecified(final ObservableValue<? extends View> property,
+                                     final View previous, final View view)
+    {
+        final Controls c = views[view.ordinal()];
+        content.getItems().setAll(c.controls(), c.view());
+        ((Toggle) c.selector).setSelected(true);
+    }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
index 125d510..b518a54 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
@@ -41,6 +41,7 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.internal.gui.BackgroundThreads;
 import org.apache.sis.internal.gui.Styles;
+import org.apache.sis.gui.map.StatusBar;
 
 
 /**
@@ -198,6 +199,11 @@ public class GridView extends Control {
     final CellFormat cellFormat;
 
     /**
+     * The status bar where to show coordinates of selected cell.
+     */
+    final StatusBar statusBar;
+
+    /**
      * Creates an initially empty grid view. The content can be set after
      * construction by a call to {@link #setImage(RenderedImage)}.
      */
@@ -211,6 +217,7 @@ public class GridView extends Control {
         headerBackground = new SimpleObjectProperty<>(this, "headerBackground", Color.GAINSBORO);
         headerFormat     = NumberFormat.getIntegerInstance();
         cellFormat       = new CellFormat(this);
+        statusBar        = new StatusBar();
         tileWidth        = 1;
         tileHeight       = 1;       // For avoiding division by zero.
 
@@ -304,7 +311,7 @@ public class GridView extends Control {
         loader = null;
         final ImageLoader result = (ImageLoader) event.getSource();
         setImage(result.getValue());
-        result.request.configure(((GridViewSkin) getSkin()).statusBar);
+        result.request.configure(statusBar);
     }
 
     /**
@@ -415,14 +422,6 @@ public class GridView extends Control {
     }
 
     /**
-     * Returns the offset to add for converting cell indices to pixel indices. They are often
the same indices,
-     * but may differ if the {@link RenderedImage} uses a coordinate system where coordinates
of the upper-left
-     * corner is not (0,0).
-     */
-    final int getImageMinX() {return minX;}
-    final int getImageMinY() {return minY;}
-
-    /**
      * Returns the bounds of a single tile in the image. This method is invoked only
      * if an error occurred during {@link RenderedImage#getTile(int, int)} invocation.
      * The returned bounds are zero-based (may not be the bounds in image coordinates).
@@ -527,6 +526,16 @@ public class GridView extends Control {
     }
 
     /**
+     * Converts and formats the given cell coordinates. An offset is added to the coordinate
values for converting
+     * cell indices to pixel indices. Those two kind of indices often have the same values,
but may differ if the
+     * {@link RenderedImage} uses a coordinate system where coordinates of the upper-left
corner is not (0,0).
+     * Then the pixel coordinates are converted to "real world" coordinates and formatted.
+     */
+    final void formatCoordinates(final int x, final int y) {
+        statusBar.setLocalCoordinates(minX + x, minY + y);
+    }
+
+    /**
      * Creates a new instance of the skin responsible for rendering this grid view.
      * From the perspective of this {@link Control}, the {@link Skin} is a black box.
      * It listens and responds to changes in state of this grid view. This method is
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
index 7b80ede..bd306ca 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
@@ -33,7 +33,6 @@ import javafx.scene.text.FontWeight;
 import javafx.scene.text.Font;
 import javafx.scene.input.MouseEvent;
 import javafx.event.EventHandler;
-import org.apache.sis.gui.map.StatusBar;
 import org.apache.sis.internal.gui.Styles;
 
 
@@ -133,11 +132,6 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
     private final Rectangle selection, selectedRow, selectedColumn;
 
     /**
-     * The status bar where to show coordinates of selected cell.
-     */
-    final StatusBar statusBar;
-
-    /**
      * Creates a new skin for the specified view.
      */
     GridViewSkin(final GridView view) {
@@ -179,14 +173,13 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
          * The status bar where to show coordinates of selected cell.
          * Mouse exit event is handled by `hideSelection(…)`.
          */
-        statusBar = new StatusBar();
-        flow.setOnMouseEntered(statusBar);
+        flow.setOnMouseEntered(view.statusBar);
         /*
          * The list of children is initially empty. We need to
          * add the virtual flow, otherwise nothing will appear.
          */
         getChildren().addAll(topBackground, leftBackground, selectedColumn, selectedRow,
-                             headerRow, selection, statusBar.getView(), flow);
+                             headerRow, selection, view.statusBar.getView(), flow);
     }
 
     /**
@@ -213,9 +206,7 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
                     selection.relocate(x, y);
                     selectedRow.setY(y);
                     selectedColumn.setX(x);
-                    final GridView view = getSkinnable();
-                    statusBar.setLocalCoordinates(view.getImageMinX() + ((int) visibleColumn)
+ firstVisibleColumn,
-                                                  view.getImageMinY() + row.getIndex());
+                    getSkinnable().formatCoordinates(((int) visibleColumn) + firstVisibleColumn,
row.getIndex());
                 }
             }
         }
@@ -223,7 +214,7 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
         selectedRow   .setVisible(visible);
         selectedColumn.setVisible(visible);
         if (!visible) {
-            statusBar.handle(null);
+            getSkinnable().statusBar.handle(null);
         }
     }
 
@@ -234,7 +225,7 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
         selection     .setVisible(false);
         selectedRow   .setVisible(false);
         selectedColumn.setVisible(false);
-        statusBar     .handle    (null);        // Hide the coordinates.
+        getSkinnable().statusBar.handle(null);      // Hide the coordinates.
     }
 
     /**
@@ -442,7 +433,7 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
         final Flow   flow         = (Flow) getVirtualFlow();
         final double cellHeight   = flow.getFixedCellSize();
         final double headerHeight = cellHeight + 2*cellSpacing;
-        final double statusHeight = statusBar.getView().getHeight();
+        final double statusHeight = view.statusBar.getView().getHeight();
         final double dataY        = y + headerHeight;
         final double dataHeight   = height - headerHeight - statusHeight;
         layoutAll |= (flow.getWidth() != width) || (flow.getHeight() != dataHeight);
@@ -491,7 +482,7 @@ final class GridViewSkin extends VirtualContainerBase<GridView, GridRow>
impleme
         if (layoutAll || oldPos != leftPosition) {
             layoutInArea(headerRow, x, y, width, headerHeight,
                          Node.BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.LEFT, VPos.TOP);
-            layoutInArea(statusBar.getView(), x, height - statusHeight, width, statusHeight,
+            layoutInArea(view.statusBar.getView(), x, height - statusHeight, width, statusHeight,
                          Node.BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.RIGHT, VPos.BOTTOM);
             final ObservableList<Node> children = headerRow.getChildren();
             final int count   = children.size();
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/DataWindow.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/DataWindow.java
index a4f45fc..d1ca566 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/DataWindow.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/DataWindow.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.gui.dataset;
 
+import java.util.EventObject;
 import javafx.geometry.Rectangle2D;
 import javafx.stage.Screen;
 import javafx.stage.Stage;
@@ -55,11 +56,12 @@ final class DataWindow extends Stage {
      * Creates a new window for the given data selected in the explorer or determined by
the active tab.
      * The new window will be positioned in the screen center but not yet shown.
      *
-     * @param  home  the window containing the main explorer, to be the target of "home"
button.
-     * @param  data  the data selected by user, to show in a new window.
+     * @param  event  the event (e.g. mouse action) requesting a new window, or {@code null}
if unknown.
+     * @param  home   the window containing the main explorer, to be the target of "home"
button.
+     * @param  data   the data selected by user, to show in a new window.
      */
-    DataWindow(final Stage home, final SelectedData data) {
-        final Region content = data.createView();
+    DataWindow(final EventObject event, final Stage home, final SelectedData data) {
+        final Region content = data.createView(event);
         /*
          * Build the tools bar. This bar will be hidden in full screen mode. Note that above
          * method assumes that the "home" button created below is the first one in the toolbar.
@@ -70,7 +72,7 @@ final class DataWindow extends Stage {
 
         final Button fullScreen = new Button("\u21F1\uFE0F");               // ⇱ — North
West Arrow to Corner
         fullScreen.setTooltip(new Tooltip(data.localized.getString(Resources.Keys.FullScreen)));
-        fullScreen.setOnAction((event) -> setFullScreen(true));
+        fullScreen.setOnAction((e) -> setFullScreen(true));
         fullScreenProperty().addListener((source, oldValue, newValue) -> onFullScreen(newValue));
 
         tools = new ToolBar(mainWindow, fullScreen);
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 d7672b8..27a6dcf 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
@@ -113,7 +113,7 @@ public class ResourceExplorer extends WindowManager {
 
         final Resources localized = localized();
         dataTab = new Tab(localized.getString(Resources.Keys.Data));
-        dataTab.setContextMenu(new ContextMenu(createNewWindowMenu()));
+        dataTab.setContextMenu(new ContextMenu(SelectedData.setTabularView(createNewWindowMenu())));
 
         final String nativeTabText = Vocabulary.getResources(localized.getLocale()).getString(Vocabulary.Keys.Format);
         final MetadataTree nativeMetadata = new MetadataTree(metadata);
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/SelectedData.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/SelectedData.java
index a7af6e2..efa04b2 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/SelectedData.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/SelectedData.java
@@ -16,7 +16,9 @@
  */
 package org.apache.sis.gui.dataset;
 
+import java.util.EventObject;
 import javafx.scene.layout.Region;
+import javafx.scene.control.MenuItem;
 import org.apache.sis.gui.coverage.CoverageExplorer;
 import org.apache.sis.gui.coverage.ImageRequest;
 import org.apache.sis.internal.gui.Resources;
@@ -42,6 +44,12 @@ import org.apache.sis.internal.gui.Resources;
  */
 final class SelectedData {
     /**
+     * Key of a property for storing {@link CoverageExplorer.View} value
+     * specifying the initial view of new windows.
+     */
+    private static final String COVERAGE_VIEW_KEY = "org.apache.sis.gui.CoverageView";
+
+    /**
      * A title to use for windows and menu items.
      */
     final String title;
@@ -74,14 +82,40 @@ final class SelectedData {
     }
 
     /**
+     * Specifies that the given menu item should create a window initialized to tabular data
+     * instead than the image.
+     */
+    static MenuItem setTabularView(final MenuItem item) {
+        item.getProperties().put(COVERAGE_VIEW_KEY, CoverageExplorer.View.TABLE);
+        return item;
+    }
+
+    /**
      * Creates the view for selected data.
+     *
+     * @param  event  the event (e.g. mouse action) requesting a new window, or {@code null}
if unknown.
      */
-    final Region createView() {
+    final Region createView(final EventObject event) {
         if (features != null) {
             return new FeatureTable(features);
         } else {
+            CoverageExplorer.View view = CoverageExplorer.View.IMAGE;
+            if (event != null) {
+                final Object source = event.getSource();
+                if (source instanceof MenuItem) {
+                    final Object value = ((MenuItem) source).getProperties().get(COVERAGE_VIEW_KEY);
+                    if (value instanceof CoverageExplorer.View) {
+                        view = (CoverageExplorer.View) value;
+                    }
+                }
+            }
             final CoverageExplorer ce = new CoverageExplorer();
             ce.setCoverage(coverage);
+            /*
+             * TODO: following line is disabled for now because it causes
+             *       the vertical scroll bar of `GridView` to disappear.
+             */
+//          ce.setViewType(view);
             return ce.getView();
         }
     }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java
index bb9f6c5..fcbf302 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java
@@ -154,7 +154,7 @@ abstract class WindowManager extends Widget {
     private void newDataWindow(final ActionEvent event) {
         final SelectedData selection = getSelectedData();
         if (selection != null) {
-            final DataWindow window = new DataWindow((Stage) getView().getScene().getWindow(),
selection);
+            final DataWindow window = new DataWindow(event, (Stage) getView().getScene().getWindow(),
selection);
             window.setTitle(selection.title + " — Apache SIS");
             window.show();
             if (showWindowMenus != null) {
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/NonNullObjectProperty.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/NonNullObjectProperty.java
new file mode 100644
index 0000000..85175ee
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/NonNullObjectProperty.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gui;
+
+import java.util.Objects;
+import javafx.beans.property.SimpleObjectProperty;
+
+
+/**
+ * A simple property implementation which does not accept null values.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @param <T> the type of the wrapped object.
+ *
+ * @since 1.1
+ * @module
+ */
+public final class NonNullObjectProperty<T> extends SimpleObjectProperty<T> {
+    /**
+     * Creates a new property.
+     *
+     * @param bean          object for which this property is a member.
+     * @param name          name of the property in the bean.
+     * @param initialValue  initial value of the property.
+     */
+    public NonNullObjectProperty(Object bean, String name, T initialValue) {
+        super(bean, name, initialValue);
+    }
+
+    /**
+     * Sets the property value.
+     *
+     * @param  newValue  the new property value.
+     */
+    @Override
+    public void set​(final T newValue) {
+        super.set(Objects.requireNonNull(newValue));
+    }
+}


Mime
View raw message