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));
+ }
+}
|