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 12036282c5601e68a7e7c0ec1a339c957d5c8fa6 Author: Martin Desruisseaux <[email protected]> AuthorDate: Tue Jan 21 12:20:21 2020 +0100 Move window management in a separated WindowManager class. --- .../org/apache/sis/gui/dataset/FeatureList.java | 2 +- .../org/apache/sis/gui/dataset/FeatureTable.java | 10 +- .../apache/sis/gui/dataset/ResourceExplorer.java | 174 +++------------- .../org/apache/sis/gui/dataset/WindowManager.java | 230 +++++++++++++++++++++ 4 files changed, 263 insertions(+), 153 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java index f333939..256a01f 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureList.java @@ -323,7 +323,7 @@ final class FeatureList extends ObservableListBase<Feature> { } /** - * If a loading process was under way, interrupts it and close the feature stream. + * If a loading process was under way, interrupts it and closes the feature stream. * This method returns immediately; the release of resources happens in a background thread. * * @see FeatureTable#interrupt() diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java index 4c5309a..044c3a6 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java @@ -162,7 +162,7 @@ public class FeatureTable extends TableView<Feature> { if (items instanceof FeatureList) { return (FeatureList) items; } else { - return (FeatureList) ((ExpandableList) getItems()).getSource(); + return (FeatureList) ((ExpandableList) items).getSource(); } } @@ -298,7 +298,7 @@ public class FeatureTable extends TableView<Feature> { } else { final ExpandableList list = getExpandableList(); list.setMultivaluedColumns(multiValued); - final TableColumn<Feature,Feature> column = new TableColumn<>(); + final TableColumn<Feature,Feature> column = new TableColumn<>("▤"); column.setCellValueFactory(IdentityValueFactory.instance()); column.setCellFactory(list); column.setReorderable(false); @@ -378,6 +378,9 @@ public class FeatureTable extends TableView<Feature> { * @todo For {@link ValueCell} only (not {@link ElementCell}), if the feature is {@link ExpandedFeature} * with {@code index != 0}, write text in gray. We could also use the value formatted at index 0 * for avoiding to format the same thing many times. + * + * @param value the new item for the cell. + * @param empty whether this cell is used to render an empty row. */ @Override protected void updateItem(final Object value, final boolean empty) { @@ -406,6 +409,9 @@ public class FeatureTable extends TableView<Feature> { /** * Invoked when a new value needs to be show. + * + * @param value the new item for the cell. + * @param empty whether this cell is used to render an empty row. */ @Override protected void updateItem(Object value, final boolean empty) { 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 deb27d5..d3f77c8 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 @@ -16,23 +16,14 @@ */ package org.apache.sis.gui.dataset; -import java.util.List; -import java.util.ArrayList; import java.util.Collection; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.ReadOnlyBooleanPropertyBase; import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.event.ActionEvent; import javafx.scene.layout.Region; import javafx.scene.control.ContextMenu; -import javafx.scene.control.MenuItem; -import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SplitPane; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TreeItem; -import javafx.stage.Stage; import org.apache.sis.storage.Resource; import org.apache.sis.storage.FeatureSet; import org.apache.sis.gui.metadata.MetadataSummary; @@ -49,7 +40,7 @@ import org.apache.sis.internal.gui.Resources; * @since 1.1 * @module */ -public class ResourceExplorer { +public class ResourceExplorer extends WindowManager { /** * The tree of resources. */ @@ -74,59 +65,15 @@ public class ResourceExplorer { private final SplitPane content; /** - * The contextual menu items for showing data in a new window. - * This is disabled if there is no data to show. - */ - private final List<MenuItem> windowMenus; - - /** - * The menu items for navigating to different windows. {@code ResourceExplorer} will automatically - * add or remove elements in this list when new windows are created or closed. - * - * @see #setWindowsItems(ObservableList) - */ - private ObservableList<MenuItem> windowsMenuItems; - - /** - * A property telling whether at least one data window created by this {@code ResourceExplorer} is - * still visible. - * - * @see #createNewWindowMenu() - * @see #setWindowsItems(ObservableList) - */ - public final ReadOnlyBooleanProperty hasWindowsProperty; - - /** - * The {@link ResourceExplorer#hasWindowsProperty} property implementation. - */ - private final class WindowsProperty extends ReadOnlyBooleanPropertyBase { - /** The property value. */ - private boolean hasWindows; - - /** Sets this property to the given value. */ - final void set(final boolean value) { - hasWindows = value; - fireValueChangedEvent(); - } - - /** Returns the current property value. */ - @Override public boolean get() {return hasWindows;} - @Override public Object getBean() {return ResourceExplorer.this;} - @Override public String getName() {return "hasWindows";} - } - - /** * Creates a new panel for exploring resources. */ public ResourceExplorer() { - resources = new ResourceTree(); - metadata = new MetadataSummary(); - features = new FeatureTable(); - content = new SplitPane(); - windowMenus = new ArrayList<>(2); - hasWindowsProperty = new WindowsProperty(); + resources = new ResourceTree(); + metadata = new MetadataSummary(); + features = new FeatureTable(); + content = new SplitPane(); - final Resources localized = resources.localized; + final Resources localized = localized(); final Tab dataTab = new Tab(localized.getString(Resources.Keys.Data), features); dataTab.setContextMenu(new ContextMenu(createNewWindowMenu())); final TabPane tabs = new TabPane( @@ -144,54 +91,26 @@ public class ResourceExplorer { } /** + * Returns resources for current locale. + */ + @Override + final Resources localized() { + return resources.localized; + } + + /** * Returns the region containing the resource tree, metadata panel or any other control managed * by this {@code ResourceExplorer}. The subclass is implementation dependent and may change in * any future version. * * @return the region to show. */ + @Override public final Region getView() { return content; } /** - * Creates a menu item for creating new windows for the currently selected resource. - * The new menu item is initially disabled. Its will become enabled automatically when - * a resource is selected. - * - * <p>Note: current implementation keeps a strong reference to created menu. - * Use this method only for menus that are expected to exist for application lifetime.</p> - * - * @return a "new window" menu item. - * - * @see #hasWindowsProperty - */ - public final MenuItem createNewWindowMenu() { - final MenuItem menu = new MenuItem(resources.localized.getString(Resources.Keys.NewWindow)); - menu.setOnAction(this::newDataWindow); - menu.setDisable(true); - windowMenus.add(menu); - return menu; - } - - /** - * Sets the list where to add or remove the name of data windows. New data windows are created when - * user selects a menu item given by {@link #createNewWindowMenu()}. {@code ResourceExplorer} will - * automatically add or remove elements in the given list. The position of the new menu item will - * be just before the last {@link SeparatorMenuItem} instance. If no {@code SeparatorMenuItem} is - * found, then one will be inserted at the beginning of the given list when needed. - * - * @param items the list where to add and remove the name of windows. - * - * @see #hasWindowsProperty - * @see #createNewWindowMenu() - */ - @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter") - public void setWindowsItems(final ObservableList<MenuItem> items) { - windowsMenuItems = items; - } - - /** * Loads all given sources in background threads and add them to the resource tree. * The given collection typically contains files to load, * but may also contain {@link Resource} instances to add directly. @@ -227,66 +146,21 @@ public class ResourceExplorer { final FeatureSet data = (resource instanceof FeatureSet) ? (FeatureSet) resource : null; metadata.setMetadata(resource); features.setFeatures(data); - for (final MenuItem m : windowMenus) { - m.setDisable(data == null); - } + setNewWindowDisabled(data == null); } /** - * Invoked when user asked to show the data in a new window. This method may be invoked from various sources: - * contextual menu on the tab, contextual menu in the explorer tree, or from the "new window" menu item. - * - * @param event ignored (can be {@code null}). + * Returns the set of currently selected data, or {@code null} if none. */ - private void newDataWindow(final ActionEvent event) { + @Override + final SelectedData getSelectedData() { final FeatureSet data = features.getFeatures(); if (data != null) { - final String title = resources.getTitle(data, false); - final DataWindow window = new DataWindow((Stage) content.getScene().getWindow(), features); - window.setTitle(title + " — Apache SIS"); - window.show(); - if (windowsMenuItems != null) { - /* - * Search for insertion point just before the menu separator. - * If no menu separator is found, add one. - */ - int insertAt = windowsMenuItems.size(); - do if (--insertAt < 0) { - windowsMenuItems.add(insertAt = 0, new SeparatorMenuItem()); - ((WindowsProperty) hasWindowsProperty).set(true); - break; - } while (!(windowsMenuItems.get(insertAt) instanceof SeparatorMenuItem)); - final MenuItem menu = new MenuItem(title); - menu.setOnAction((e) -> window.toFront()); - windowsMenuItems.add(insertAt, menu); - window.setOnHidden((e) -> removeDataWindow(menu)); - } - } - } - - /** - * Invoked when a window has been hidden. This method removes the window title from the "Windows" menu. - * The hidden window will be garbage collected at some later time. - */ - private void removeDataWindow(final MenuItem menu) { - final ObservableList<MenuItem> items = windowsMenuItems; - if (items != null) { - for (int i = items.size(); --i >= 0;) { - if (items.get(i) == menu) { - items.remove(i); - if (i == 0) { - if (!items.isEmpty()) { - if (items.get(0) instanceof SeparatorMenuItem) { - items.remove(0); - } else { - break; // Some other windows are still present. - } - } - ((WindowsProperty) hasWindowsProperty).set(false); - } - break; - } - } + final SelectedData selection = new SelectedData(); + selection.title = resources.getTitle(data, false); + selection.features = features; + return selection; } + return null; } } 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 new file mode 100644 index 0000000..6d2970d --- /dev/null +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java @@ -0,0 +1,230 @@ +/* + * 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.gui.dataset; + +import java.util.List; +import java.util.ArrayList; +import javafx.collections.ObservableList; +import javafx.stage.Stage; +import javafx.event.ActionEvent; +import javafx.scene.layout.Region; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanPropertyBase; +import org.apache.sis.internal.gui.Resources; + + +/** + * Manages the list of opened {@link DataWindow}s. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.1 + * @since 1.1 + * @module + */ +abstract class WindowManager { + /** + * The contextual menu items for creating a new window showing selected data. + * All menus in this list will be enabled or disabled depending on whether there is data to show. + */ + private final List<MenuItem> newWindowMenus; + + /** + * The menu items for navigating to different windows. {@link WindowManager} will automatically + * add or remove elements in that list when new windows are created or closed. + * + * @see #setWindowsItems(ObservableList) + */ + private ObservableList<MenuItem> showWindowMenus; + + /** + * A property telling whether at least one data window created by this class is still visible. + * + * @see #createNewWindowMenu() + * @see #setWindowsItems(ObservableList) + */ + public final ReadOnlyBooleanProperty hasWindowsProperty; + + /** + * The {@link WindowManager#hasWindowsProperty} property implementation. + */ + private final class WindowsProperty extends ReadOnlyBooleanPropertyBase { + /** The property value. */ + private boolean hasWindows; + + /** Sets this property to the given value. */ + final void set(final boolean value) { + hasWindows = value; + fireValueChangedEvent(); + } + + /** Returns the current property value. */ + @Override public boolean get() {return hasWindows;} + @Override public Object getBean() {return WindowManager.this;} + @Override public String getName() {return "hasWindows";} + } + + /** + * Creates a new manager of windows. + */ + WindowManager() { + newWindowMenus = new ArrayList<>(2); + hasWindowsProperty = new WindowsProperty(); + } + + /** + * Returns resources for current locale. We could fetch this information ourselves, + * but we currently ask to subclass because it has this information anyway. + */ + abstract Resources localized(); + + /** + * Returns the control shown in the main window. + */ + abstract Region getView(); + + /** + * Creates a menu item for creating new windows for the currently selected resource. + * The new menu item is initially disabled. Its will become enabled automatically when + * a resource is selected. + * + * <p>Note: current implementation keeps a strong reference to created menu. + * Use this method only for menus that are expected to exist for application lifetime.</p> + * + * @return a "new window" menu item. + * + * @see #hasWindowsProperty + */ + public final MenuItem createNewWindowMenu() { + final MenuItem menu = new MenuItem(localized().getString(Resources.Keys.NewWindow)); + menu.setOnAction(this::newDataWindow); + menu.setDisable(true); + newWindowMenus.add(menu); + return menu; + } + + /** + * Sets the list where to add or remove the name of data windows. New data windows are created when user + * selects a menu item given by {@link #createNewWindowMenu()}. {@code WindowManager} will automatically + * add or remove elements in the given list. The position of the new menu item will be just before the + * last {@link SeparatorMenuItem} instance. If no {@code SeparatorMenuItem} is found, then one will be + * inserted at the beginning of the given list when needed. + * + * @param items the list where to add and remove the name of windows. + * + * @see #hasWindowsProperty + * @see #createNewWindowMenu() + */ + @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter") + public void setWindowsItems(final ObservableList<MenuItem> items) { + showWindowMenus = items; + } + + /** + * Enables or disables all "new window" menus. Those menu should be disabled when the current selection + * does not contain any data we can show. + */ + final void setNewWindowDisabled(final boolean disabled) { + for (final MenuItem m : newWindowMenus) { + m.setDisable(disabled); + } + } + + /** + * A description of currently selected data. + */ + static final class SelectedData { + /** + * A title to use for windows and menu items. + */ + String title; + + /** + * The control that contains the currently selected data. + */ + FeatureTable features; + + /** + * Creates an initially empty set of selected data. + */ + SelectedData() { + } + } + + /** + * Returns the set of currently selected data, or {@code null} if none. + */ + abstract SelectedData getSelectedData(); + + /** + * Invoked when user asked to show the data in a new window. This method may be invoked from various sources: + * contextual menu on the tab, contextual menu in the explorer tree, or from the "new window" menu item. + * + * @param event ignored (can be {@code null}). + */ + private void newDataWindow(final ActionEvent event) { + final SelectedData selection = getSelectedData(); + if (selection != null) { + final DataWindow window = new DataWindow((Stage) getView().getScene().getWindow(), selection.features); + window.setTitle(selection.title + " — Apache SIS"); + window.show(); + if (showWindowMenus != null) { + /* + * Search for insertion point just before the menu separator. + * If no menu separator is found, add one. + */ + int insertAt = showWindowMenus.size(); + do if (--insertAt < 0) { + showWindowMenus.add(insertAt = 0, new SeparatorMenuItem()); + ((WindowsProperty) hasWindowsProperty).set(true); + break; + } while (!(showWindowMenus.get(insertAt) instanceof SeparatorMenuItem)); + final MenuItem menu = new MenuItem(selection.title); + menu.setOnAction((e) -> window.toFront()); + showWindowMenus.add(insertAt, menu); + window.setOnHidden((e) -> removeDataWindow(menu)); + } + } + } + + /** + * Invoked when a window has been hidden. This method removes the window title from the "Windows" menu. + * The hidden window will be garbage collected at some later time. + */ + private void removeDataWindow(final MenuItem menu) { + final ObservableList<MenuItem> items = showWindowMenus; + if (items != null) { + for (int i = items.size(); --i >= 0;) { + if (items.get(i) == menu) { + items.remove(i); + if (i == 0) { + if (!items.isEmpty()) { + if (items.get(0) instanceof SeparatorMenuItem) { + items.remove(0); + } else { + break; // Some other windows are still present. + } + } + ((WindowsProperty) hasWindowsProperty).set(false); + } + break; + } + } + } + } +}
